Skip to content

Commit

Permalink
feat!: remove version from application configuration
Browse files Browse the repository at this point in the history
BREAKING CHANGE: `version` is now an argument / mix flag instead of a global configuration
  • Loading branch information
sheerlox committed Dec 1, 2023
1 parent 600e833 commit 06c539f
Show file tree
Hide file tree
Showing 8 changed files with 93 additions and 102 deletions.
22 changes: 8 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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(
Expand Down
1 change: 0 additions & 1 deletion config/config.exs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import Config

config :nodelix,
version: "20.10.0",
test_profile: [
args: ["--version"]
]
32 changes: 22 additions & 10 deletions lib/mix/tasks/nodelix.ex
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
defmodule Mix.Tasks.Nodelix do
use Mix.Task

alias Nodelix.VersionManager

@moduledoc """
Invokes `node` with the provided arguments.
Expand All @@ -21,15 +23,18 @@ 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
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
"""

Expand All @@ -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
Expand All @@ -50,23 +55,30 @@ 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
Mix.Task.run("loadpaths")
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
23 changes: 12 additions & 11 deletions lib/mix/tasks/nodelix.install.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
27 changes: 22 additions & 5 deletions lib/mix/tasks/nodelix.npm.ex
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
defmodule Mix.Tasks.Nodelix.Npm do
use Mix.Task

alias Nodelix.VersionManager

@moduledoc """
Invokes `npm` with the provided arguments.
Expand All @@ -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
68 changes: 23 additions & 45 deletions lib/nodelix.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.
Expand All @@ -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.
Expand All @@ -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
Expand All @@ -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

Expand All @@ -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
Expand All @@ -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
Loading

0 comments on commit 06c539f

Please sign in to comment.