diff --git a/README.md b/README.md index 54e4544..74eefcd 100644 --- a/README.md +++ b/README.md @@ -29,32 +29,27 @@ def deps do end ``` -Once installed, change your `config/config.exs` to pick your -Node.js version of choice: - -```elixir -config :nodelix, version: "20.10.0" -``` - Now you can install Node.js by running: ```shell -$ mix nodelix.install +$ mix nodelix.install --version 18.18.2 ``` And invoke Node.js with: ```shell -$ mix nodelix some-script.js --some-option +$ mix nodelix --version 18.18.2 some-script.js --some-option ``` +If you omit the `--version` flag, the latest known +[LTS version](https://nodejs.org/en/about/previous-releases) at the +time of publishing will used. + The Node.js installation is located at `_build/dev/nodejs/versions/$VERSION`. ## Nodelix configuration -There are two global configurations for the nodelix application: - -- `:version` - the Node.js version to use +There is one global configuration for the nodelix application: - `:cacerts_path` - the directory to find certificates for https connections @@ -66,13 +61,12 @@ which you can configure its args, current directory and environment: ```elixir config :nodelix, - version: "20.10.0", default: [ args: ~w( some-script.js --some-option ), - cd: Path.expand("../assets", __DIR__) + cd: Path.expand("../assets", __DIR__), ], custom: [ args: ~w( diff --git a/config/config.exs b/config/config.exs index 1cf8a1d..e0890c8 100644 --- a/config/config.exs +++ b/config/config.exs @@ -1,7 +1,6 @@ import Config config :nodelix, - version: "20.10.0", test_profile: [ args: ["--version"] ] diff --git a/lib/mix/tasks/nodelix.ex b/lib/mix/tasks/nodelix.ex index d9a048f..ace0ed0 100644 --- a/lib/mix/tasks/nodelix.ex +++ b/lib/mix/tasks/nodelix.ex @@ -1,6 +1,8 @@ defmodule Mix.Tasks.Nodelix do use Mix.Task + alias Nodelix.VersionManager + @moduledoc """ Invokes `node` with the provided arguments. @@ -21,7 +23,10 @@ defmodule Mix.Tasks.Nodelix do ## Options - - `--profile` - name of the profile to use + - `--version` - Node.js version to use, defaults to latest known + LTS version (`#{VersionManager.latest_lts_version()}`) + + - `--profile` - name of the profile to use, defaults to `default` - `--runtime-config` - load the runtime configuration before executing command @@ -29,7 +34,7 @@ defmodule Mix.Tasks.Nodelix do Flags to control this Mix task must be given before the node arguments: - $ mix nodelix --profile default --runtime-config some-script.js --some-option + $ mix nodelix --version 18.18.2 --profile default --runtime-config some-script.js --some-option """ @@ -40,7 +45,7 @@ defmodule Mix.Tasks.Nodelix do @impl Mix.Task @spec run([String.t()]) :: :ok def run(args) do - switches = [profile: :string, runtime_config: :boolean] + switches = [version: :string, profile: :string, runtime_config: :boolean] {opts, remaining_args, invalid_opts} = OptionParser.parse_head(args, strict: switches) node_args = Enum.map(invalid_opts, &elem(&1, 0)) ++ remaining_args @@ -50,6 +55,10 @@ defmodule Mix.Tasks.Nodelix do Mix.ensure_application!(:ssl) end + version = opts[:version] || VersionManager.latest_lts_version() + + profile = opts[:profile] || "default" + if opts[:runtime_config] do Mix.Task.run("app.config") else @@ -57,16 +66,19 @@ defmodule Mix.Tasks.Nodelix do Application.ensure_all_started(:nodelix) end - profile = opts[:profile] || "default" - Mix.Task.reenable("nodelix") - install_and_run([profile | node_args]) + install_and_run(version, profile, node_args) end - defp install_and_run([profile | args] = all) do - case Nodelix.install_and_run(String.to_atom(profile), args) do - 0 -> :ok - status -> Mix.raise("`mix nodelix #{Enum.join(all, " ")}` exited with #{status}") + defp install_and_run(version, profile, args) do + case Nodelix.install_and_run(version, String.to_atom(profile), args) do + 0 -> + :ok + + status -> + Mix.raise( + "`mix nodelix --version #{version} --profile #{profile} #{Enum.join(args, " ")}` exited with #{status}" + ) end end end diff --git a/lib/mix/tasks/nodelix.install.ex b/lib/mix/tasks/nodelix.install.ex index 3b0efc0..6dbd862 100644 --- a/lib/mix/tasks/nodelix.install.ex +++ b/lib/mix/tasks/nodelix.install.ex @@ -12,19 +12,19 @@ defmodule Mix.Tasks.Nodelix.Install do > API _should not_ be considered stable, and using a pinned version is _recommended_. $ mix nodelix.install + $ mix nodelix.install --version 18.18.2 $ mix nodelix.install --force - By default, it installs #{VersionManager.latest_lts_version()} but you - can configure it in your config files, such as: + ## Options - config :nodelix, :version, "#{VersionManager.latest_lts_version()}" + - `--version` - name of the profile to use, defaults to latest known + LTS version (`#{VersionManager.latest_lts_version()}`) - ## Options + - `--force` - install even if the given version is already present - `--runtime-config` - load the runtime configuration before executing command - - `--force` - install even if the given version is already present """ @shortdoc "Installs Node.js" @@ -34,7 +34,7 @@ defmodule Mix.Tasks.Nodelix.Install do @impl Mix.Task @spec run([String.t()]) :: :ok def run(args) do - valid_options = [runtime_config: :boolean, force: :boolean, assets: :boolean] + valid_options = [version: :string, force: :boolean, runtime_config: :boolean] {opts, archive_base_url} = case OptionParser.parse_head!(args, strict: valid_options) do @@ -50,16 +50,17 @@ defmodule Mix.Tasks.Nodelix.Install do mix nodelix.install mix nodelix.install 'https://nodejs.org/dist/v$version/node-v$version-$target.$ext' - mix nodelix.install --runtime-config + mix nodelix.install --version 18.18.2 mix nodelix.install --force + mix nodelix.install --runtime-config """) end - if opts[:runtime_config], do: Mix.Task.run("app.config") + version = opts[:version] || VersionManager.latest_lts_version() - configured_version = Nodelix.configured_version() + if opts[:runtime_config], do: Mix.Task.run("app.config") - if VersionManager.is_installed?(configured_version) and !opts[:force] do + if VersionManager.is_installed?(version) and !opts[:force] do :ok else if function_exported?(Mix, :ensure_application!, 1) do @@ -68,7 +69,7 @@ defmodule Mix.Tasks.Nodelix.Install do end Mix.Task.run("loadpaths") - VersionManager.install(configured_version, archive_base_url) + VersionManager.install(version, archive_base_url) end end end diff --git a/lib/mix/tasks/nodelix.npm.ex b/lib/mix/tasks/nodelix.npm.ex index 85f4488..54fd676 100644 --- a/lib/mix/tasks/nodelix.npm.ex +++ b/lib/mix/tasks/nodelix.npm.ex @@ -1,6 +1,8 @@ defmodule Mix.Tasks.Nodelix.Npm do use Mix.Task + alias Nodelix.VersionManager + @moduledoc """ Invokes `npm` with the provided arguments. @@ -15,22 +17,37 @@ defmodule Mix.Tasks.Nodelix.Npm do Example: - $ mix nodelix.npm install --save-dev semantic-release semantic-release-hex + $ mix nodelix.npm install --save-dev tailwindcss esbuild Refer to `Mix.Tasks.Nodelix` for task options and to `Nodelix` for more information on configuration and profiles. - """ + ## Options - alias Nodelix.VersionManager + - `--version` - Node.js version to use, defaults to latest known + LTS version (`#{VersionManager.latest_lts_version()}`) + + Flags to control this Mix task must be given before the + node arguments: + + $ mix nodelix.npm --version 18.18.2 install --save-dev tailwindcss esbuild + + """ @shortdoc "Invokes npm with the provided arguments" @impl Mix.Task @spec run([String.t()]) :: :ok def run(args) do - npm_path = VersionManager.bin_path(:npm, Nodelix.configured_version()) + switches = [version: :string] + + {opts, remaining_args, invalid_opts} = OptionParser.parse_head(args, strict: switches) + npm_args = Enum.map(invalid_opts, &elem(&1, 0)) ++ remaining_args + + version = opts[:version] || VersionManager.latest_lts_version() + + npm_path = VersionManager.bin_path(:npm, version) - Mix.Task.run("nodelix", [npm_path | args]) + Mix.Tasks.Nodelix.run(["--version", version] ++ [npm_path | npm_args]) end end diff --git a/lib/nodelix.ex b/lib/nodelix.ex index 6836a4b..b028663 100644 --- a/lib/nodelix.ex +++ b/lib/nodelix.ex @@ -14,9 +14,7 @@ defmodule Nodelix do ## Nodelix configuration - There are two global configurations for the nodelix application: - - - `:version` - the Node.js version to use + There is one global configuration for the nodelix application: - `:cacerts_path` - the directory to find certificates for https connections @@ -28,21 +26,23 @@ defmodule Nodelix do ```elixir config :nodelix, - version: "#{VersionManager.latest_lts_version()}", - default: [ - args: ~w( - some-script.js - --some-option - ), - cd: Path.expand("../assets", __DIR__), - ], - custom: [ - args: ~w( - another-script.js - --another-option - ), - cd: Path.expand("../assets/scripts", __DIR__), - ] + default: [ + args: ~w( + some-script.js + --some-option + ), + cd: Path.expand("../assets", __DIR__), + ], + custom: [ + args: ~w( + another-script.js + --another-option + ), + cd: Path.expand("../assets/scripts", __DIR__), + env: [ + NODE_DEBUG: "*" + ] + ] ``` The default current directory is your project's root. @@ -61,28 +61,9 @@ defmodule Nodelix do @doc false def start(_, _) do - unless Application.get_env(:nodelix, :version) do - latest_lts_version = VersionManager.latest_lts_version() - - Logger.warning(""" - Node.js version is not configured. Please set it in your config files: - - config :nodelix, :version, "#{latest_lts_version}" - - Using latest known LTS version at the time of release: #{latest_lts_version} - """) - end - Supervisor.start_link([], strategy: :one_for_one) end - @doc """ - Returns the configured Node.js version. - """ - def configured_version do - Application.get_env(:nodelix, :version, VersionManager.latest_lts_version()) - end - @doc """ Returns the configuration for the given profile. @@ -94,7 +75,6 @@ defmodule Nodelix do Unknown nodelix profile. Make sure the profile is defined in your config/config.exs file, such as: config :nodelix, - version: "#{VersionManager.latest_lts_version()}", #{profile}: [ args: ~w( some-script.js @@ -112,7 +92,7 @@ defmodule Nodelix do The task output will be streamed directly to stdio. It returns the status of the underlying call. """ - def run(profile, extra_args \\ []) when is_atom(profile) and is_list(extra_args) do + def run(version, profile, extra_args \\ []) when is_atom(profile) and is_list(extra_args) do config = config_for!(profile) args = (config[:args] || []) ++ extra_args @@ -127,7 +107,7 @@ defmodule Nodelix do stderr_to_stdout: true ] - VersionManager.bin_path(:node, Nodelix.configured_version()) + VersionManager.bin_path(:node, version) |> System.cmd(args, opts) |> elem(1) end @@ -136,15 +116,13 @@ defmodule Nodelix do Installs Node.js if the configured version is not available, and then runs `node`. - Returns the same as `run/2`. + Returns the same as `run/3`. """ - def install_and_run(profile, args) do - version = configured_version() - + def install_and_run(version, profile, args) do unless VersionManager.is_installed?(version) do VersionManager.install(version) end - run(profile, args) + run(version, profile, args) end end diff --git a/lib/nodelix/version_manager.ex b/lib/nodelix/version_manager.ex index 65dae76..1a80b98 100644 --- a/lib/nodelix/version_manager.ex +++ b/lib/nodelix/version_manager.ex @@ -69,7 +69,7 @@ defmodule Nodelix.VersionManager do unpack_archive(version) Logger.debug( - "Succesfully installed Node.js v#{Nodelix.configured_version()} in #{Map.get(paths(version), :destination)}" + "Succesfully installed Node.js v#{version} in #{Map.get(paths(version), :destination)}" ) :ok @@ -196,7 +196,7 @@ defmodule Nodelix.VersionManager do end defp fetch_archive(version, archive_base_url) do - archive_url = get_url(archive_base_url) + archive_url = get_url(archive_base_url, version) %{archive: archive_path} = paths(version) Logger.debug("Downloading Node.js from #{archive_url}") @@ -205,7 +205,7 @@ defmodule Nodelix.VersionManager do end defp fetch_checksums(version) do - checksums_url = get_url(@signed_checksums_base_url) + checksums_url = get_url(@signed_checksums_base_url, version) %{checksums: checksums_path} = paths(version) @@ -268,9 +268,9 @@ defmodule Nodelix.VersionManager do end end - defp get_url(base_url) do + defp get_url(base_url, version) do base_url - |> String.replace("$version", Nodelix.configured_version()) + |> String.replace("$version", version) |> String.replace("$target", target()) |> String.replace("$ext", extension()) end diff --git a/test/nodelix_test.exs b/test/nodelix_test.exs index eb24f2c..73013b3 100644 --- a/test/nodelix_test.exs +++ b/test/nodelix_test.exs @@ -8,11 +8,6 @@ defmodule NodelixTest do Mix.Task.run("nodelix.install") end - setup do - Application.put_env(:nodelix, :version, @version) - :ok - end - test "run without profile" do assert ExUnit.CaptureIO.capture_io(fn -> assert Mix.Task.rerun("nodelix", ["--version"]) == :ok @@ -26,15 +21,10 @@ defmodule NodelixTest do end test "installs and runs multiple versions" do - Application.put_env(:nodelix, :version, "18.18.2") - Mix.Task.rerun("nodelix.install") - assert ExUnit.CaptureIO.capture_io(fn -> - assert Mix.Task.rerun("nodelix", ["--version"]) == :ok + assert Mix.Task.rerun("nodelix", ["--version", "18.18.2", "--version"]) == :ok end) =~ "18.18.2" - Application.put_env(:nodelix, :version, @version) - assert ExUnit.CaptureIO.capture_io(fn -> assert Mix.Task.rerun("nodelix", ["--version"]) == :ok end) =~ @version