Skip to content

Commit

Permalink
Merge pull request #31 from akoutmos/deno-vendor-support
Browse files Browse the repository at this point in the history
Deno vendor support
  • Loading branch information
adkron committed Jun 23, 2023
2 parents 5deda20 + 91cda43 commit 8147d2b
Show file tree
Hide file tree
Showing 13 changed files with 259 additions and 34 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,6 @@ deno_ex-*.tar
# Dialyzer PLT files
/priv/plts/*.plt
/priv/plts/*.plt.hash

# Used in a real DenoEx install, but not to be checked in on working with DenoEx.
/priv/deno
40 changes: 36 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,17 @@

# Contents

- [Introduction](#introduction)
- [Installation](#installation)
- [Supporting DenoEx](#supporting-denoex)
- [Using DenoEx](#using-denoex)
- [Contents](#contents)
- [Introduction](#introduction)
- [Installation](#installation)
- [Installing the Runtime](#installing-the-runtime)
- [Using DenoEx to Install Copies of the Runtime](#using-denoex-to-install-copies-of-the-runtime)
- [Supporting DenoEx](#supporting-denoex)
- [Gold Sponsors](#gold-sponsors)
- [Silver Sponsors](#silver-sponsors)
- [Bronze Sponsors](#bronze-sponsors)
- [Using DenoEx](#using-denoex)
- [Handling Dependencies](#handling-dependencies)

## Introduction

Expand Down Expand Up @@ -111,3 +118,28 @@ Open iex using `iex -S mix` and then run the TypeScript file:
```elixir
iex > DenoEx.run({:file, "path/to/file.ts"})
```

### Handling Dependencies

Scripts download dependencies on their first run. The output from downloading ends up in the scripts output. In
order to avoid the time to download and vendoring at runtime we encourage users to vendor their dependencies.
You will first need to configure vendoring.

```elixir
config :deno_ex,
scripts_path: [
Path.join(~w[test support **]),
Path.join(~w[my_scripts hello.ts])
]
```

`scripts_path` can be a list of paths to scripts or wildcards.

`mix deno_ex.deps.get` will load all dependencies in the cache and update the lock file.


In order to ensure that your scripts use the dependencies that are cached and locked your
scripts need a few more arguments.

`cached_only: true` - tells the script to only used cached dependencies
`lock: path_to_lockfile` - tells the script where the lock file is located
3 changes: 3 additions & 0 deletions config/dev.exs
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
import Config

config :deno_ex,
scripts_path: Path.join(~w[test support])
3 changes: 3 additions & 0 deletions config/test.exs
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
import Config

config :deno_ex,
scripts_path: Path.join(~w[test support *.ts])
2 changes: 1 addition & 1 deletion coveralls.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"skip_files": ["test/"],
"skip_files": ["test/", "lib/tasks/install.ex", "lib/tasks/deps/get.ex"],
"minimum_coverage": 65
}
7 changes: 6 additions & 1 deletion lib/deno_downloader.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,14 @@ defmodule DenoEx.DenoDownloader do
"""

use OctoFetch,
latest_version: "1.33.4",
latest_version: "1.34.3",
github_repo: "denoland/deno",
download_versions: %{
"1.34.3" => [
{:darwin, :arm64, "fe48d39286fe973211500f6426300181a8f19103dd278dcbe679a586b14d8eb6"},
{:darwin, :amd64, "d25b6f0de52ccdf5818df184e5c795a01d06e5e28c14c4845c1ad8272c2eadad"},
{:linux, :amd64, "f2d496a83509937b7e4e0c9316355f2ff4efcf6042c2cf297919e09e42645c39"}
],
"1.33.4" => [
{:darwin, :arm64, "ea504cac8ba53ef583d0f912d7834f4bff88eb647cfb10cb1dd24962b1dc062d"},
{:darwin, :amd64, "1e2d79b4a237443e201578fc825052245d2a71c9a17e2a5d1327fa35f9e8fc0e"},
Expand Down
67 changes: 57 additions & 10 deletions lib/deno_ex.ex
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
defmodule DenoEx do
@default_executable_location :deno_ex |> :code.priv_dir() |> Path.join("bin")
@default_executable_path :deno_ex |> :code.priv_dir() |> Path.join("bin")
@env_location_variable "DENO_LOCATION"

alias DenoEx.Pipe
Expand All @@ -19,7 +19,7 @@ defmodule DenoEx do
### Function Option
iex> DenoEx.run({:file, Path.join(~w[test support hello.ts])}, [], [deno_location: "#{@default_executable_location}"])
iex> DenoEx.run({:file, Path.join(~w[test support hello.ts])}, [], [deno_location: "#{@default_executable_path}"])
{:ok, "Hello, world.#{"\\n"}"}
### Application Configuration
Expand All @@ -34,11 +34,11 @@ defmodule DenoEx do
`#{@env_location_variable}=path`
"""

@executable_location Application.compile_env(
:deno_ex,
:exectutable_location,
@default_executable_location
)
@executable_path Application.compile_env(
:deno_ex,
:exectutable_location,
@default_executable_path
)

@typedoc """
The path to the script that should be executed, or a tuple denoting
Expand Down Expand Up @@ -90,11 +90,58 @@ defmodule DenoEx do
end)
end

@doc """
Vendors Deno dependencies into the given location
"""
def vendor_dependencies(script_paths, vendor_location, lock_file_location, args) do
deno_path = Path.join(DenoEx.executable_path(), "deno")

System.cmd(
deno_path,
~w[vendor #{Enum.join(script_paths, " ")} --output #{vendor_location} --lock=#{lock_file_location}] ++ args
)
end

@doc """
Locks the Deno dependencies
"""
def lock_dependencies(script_paths, lock_file_location, _args) do
deno_path = Path.join(DenoEx.executable_path(), "deno")

Enum.each(script_paths, fn script_path ->
System.cmd(deno_path, ~w[cache --lock=#{lock_file_location} #{script_path}])
end)
end

@doc """
Returns the location where the deno script is expected to be located.
"""
@spec executable_location() :: String.t()
def executable_location do
System.get_env(@env_location_variable, @executable_location)
@spec executable_path() :: String.t()
def executable_path do
System.get_env(@env_location_variable, @executable_path)
end

@doc """
Returns the vendor location where deno script dependencies will be stored
"""
@spec vendor_dir(atom()) :: String.t()
def vendor_dir(app) do
Path.join([:code.priv_dir(app), "deno"])
end

@doc """
Returns the default import map path
"""
@spec import_map_path(atom()) :: String.t()
def import_map_path(app) do
Path.join(vendor_dir(app), "import_map.json")
end

@doc """
Returns the default lock file path
"""
@spec lock_file_path(atom()) :: String.t()
def lock_file_path(app) do
Path.join([vendor_dir(app), "deno.lock"])
end
end
29 changes: 27 additions & 2 deletions lib/deno_ex/pipe.ex
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,22 @@ defmodule DenoEx.Pipe do
allow_all: [
type: :boolean,
doc: "Turns on all options and bypasses all security measures"
],
no_remote: [
type: :boolean,
doc: "Disallows installing of imports from remote locations"
],
import_map: [
type: :string,
doc: "The location of the import map json file for finding vendored dependencies"
],
lock: [
type: :string,
doc: "The location of the lock file for running scripts"
],
cached_only: [
type: :boolean,
doc: "Only allows chaced dependencies to be used"
]
]
|> NimbleOptions.new!()
Expand Down Expand Up @@ -159,7 +175,7 @@ defmodule DenoEx.Pipe do
def new({:stdin, script}, script_args, options) do
with {:ok, options} <- NimbleOptions.validate(options, @run_options_schema),
{deno_location, deno_options} <-
Keyword.pop(options, :deno_location, DenoEx.executable_location()) do
Keyword.pop(options, :deno_location, DenoEx.executable_path()) do
deno_options = Enum.map(deno_options, &to_command_line_option/1)

%__MODULE__{
Expand All @@ -181,7 +197,7 @@ defmodule DenoEx.Pipe do
def new({:file, script}, script_args, options) do
with {:ok, options} <- NimbleOptions.validate(options, @run_options_schema),
{deno_location, deno_options} <-
Keyword.pop(options, :deno_location, DenoEx.executable_location()) do
Keyword.pop(options, :deno_location, DenoEx.executable_path()) do
deno_options = Enum.map(deno_options, &to_command_line_option/1)

%__MODULE__{
Expand Down Expand Up @@ -335,6 +351,15 @@ defmodule DenoEx.Pipe do
"--#{string_option}=#{string_values}"
end

defp to_command_line_option({option, value}) when is_binary(value) do
string_option =
option
|> to_string()
|> String.replace("_", "-")

"--#{string_option}=#{value}"
end

defp start_proccess(pipe, command) do
exectution =
command
Expand Down
6 changes: 3 additions & 3 deletions lib/tasks/compile/deno.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ defmodule Mix.Tasks.Compile.Deno do
"""
use Mix.Task.Compiler

@impl true
@impl Mix.Task.Compiler
def run(_) do
deno_path = Path.join(DenoEx.executable_location(), "deno")
deno_path = Path.join(DenoEx.executable_path(), "deno")

if File.exists?(deno_path) do
{:noop, []}
else
_ = DenoEx.DenoDownloader.install(DenoEx.executable_location(), 0o770)
_ = DenoEx.DenoDownloader.install(DenoEx.executable_path(), 0o770)

if File.exists?(deno_path) do
{:ok, ["Deno installation complete"]}
Expand Down
34 changes: 34 additions & 0 deletions lib/tasks/deps/get.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
defmodule Mix.Tasks.DenoEx.Deps.Get do
@moduledoc """
A mix task for loading deno dependencies cache
This delegates to `deno cache`
"""
use Mix.Task

@shortdoc """
Creates a lock file of the dependecies for deno
"""

@requirements ["app.config"]

@doc false
@impl Mix.Task
def run(args) do
app = Keyword.get(Mix.Project.config(), :app)
lock_file_path = DenoEx.lock_file_path(app)
lock_file_dir = Path.dirname(lock_file_path)

unless File.exists?(lock_file_dir) do
File.mkdir_p!(lock_file_dir)
end

scripts =
Application.get_env(:deno_ex, :scripts_path)
|> List.wrap()
|> Enum.flat_map(&Path.wildcard/1)

:ok = DenoEx.lock_dependencies(scripts, lock_file_path, args)
Mix.shell().info("Created #{lock_file_path}")
end
end
2 changes: 1 addition & 1 deletion lib/tasks/install.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ defmodule Mix.Tasks.DenoEx.Install do
@options_schema [
path: [
type: :string,
default: DenoEx.executable_location(),
default: DenoEx.executable_path(),
doc: "The path to install deno."
],
chmod: [
Expand Down
4 changes: 2 additions & 2 deletions test/deno_ex/pipe_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ defmodule DenoEx.PipeTest do
Pipe.new({:file, @script}, ~w[arg], allow_env: ~w[USER SHELL])

assert command == [
Path.join(DenoEx.executable_location(), "deno"),
Path.join(DenoEx.executable_path(), "deno"),
"run",
["--allow-env=USER,SHELL"],
@script,
Expand Down Expand Up @@ -53,7 +53,7 @@ defmodule DenoEx.PipeTest do

test "support chardata scripts" do
assert %{status: :running} =
{:stdin, ["console.log(", 'hello', ?)]}
{:stdin, ["console.log(", ~c"hello", ?)]}
|> Pipe.new([])
|> Pipe.run()
end
Expand Down
Loading

0 comments on commit 8147d2b

Please sign in to comment.