Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 35 additions & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ on:
jobs:
build:
# Named explicitly so that `experimental` stays out of the job name.
name: "build (${{ matrix.ruby }}, ${{ matrix.rails }}, ${{ matrix.ember }})"
name: "build (${{ matrix.ruby }}, ${{ matrix.rails }}, ${{ matrix.ember }}, ${{ matrix.package_manager }})"
runs-on: "ubuntu-latest"

# Jobs normally finish in about 3 minutes; a hung job once ran for
Expand All @@ -28,23 +28,47 @@ jobs:
# dummy app. `>= 6.8` uses the Vite-based blueprint, older
# versions use the classic Broccoli-based build.
ember: ["7.0.0"]
# `package_manager` installs the dummy app's NodeJS dependencies:
# `npm`, `yarn`, or `pnpm`.
package_manager: ["npm"]
include:
- ruby: "4.0"
rails: "main"
ember: "7.0.0"
package_manager: "npm"
experimental: true
# Classic (Broccoli-based) blueprint coverage
- ruby: "3.4"
rails: "8.1"
ember: "5.12.0"
package_manager: "npm"
- ruby: "3.4"
rails: "8.1"
ember: "6.7.2"
package_manager: "npm"
# Yarn and pnpm coverage, for both blueprints
- ruby: "3.4"
rails: "8.1"
ember: "7.0.0"
package_manager: "yarn"
- ruby: "3.4"
rails: "8.1"
ember: "5.12.0"
package_manager: "yarn"
- ruby: "3.4"
rails: "8.1"
ember: "7.0.0"
package_manager: "pnpm"
- ruby: "3.4"
rails: "8.1"
ember: "5.12.0"
package_manager: "pnpm"

env:
RAILS_ENV: "test"
RAILS_VERSION: "${{ matrix.rails }}"
EMBER_VERSION: "${{ matrix.ember }}"
PACKAGE_MANAGER: "${{ matrix.package_manager }}"

steps:
- uses: "actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1" # v7.0.1
Expand All @@ -54,6 +78,16 @@ jobs:
with:
node-version: "22.x"

- name: "Install Yarn"
if: "matrix.package_manager == 'yarn'"
run: |
npm install -g yarn@1

- name: "Install pnpm"
if: "matrix.package_manager == 'pnpm'"
run: |
npm install -g pnpm@latest-10

- name: "Install Ruby ${{ matrix.ruby }}"
uses: "ruby/setup-ruby@95ef2b042f9d7a56d8268cba8559e2842e2ad01b" # v1.321.0
with:
Expand Down
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
main
------

* Add the `package_manager` option to install an application's NodeJS
dependencies with pnpm (`package_manager: :pnpm`), yarn, or npm. `pnpm_path`
implies pnpm the way `yarn_path` implies yarn. The package manager is read
through the new `EmberCli::App#package_manager` and `EmberCli::App#pnpm?`
* Deprecate `yarn: true` in favour of `package_manager: :yarn`. The shorthand
still selects yarn, with a deprecation warning, and will be removed in `1.0`
* Identify a project with a pnpm application to Heroku's NodeJS buildpack
from `rails generate ember:heroku`, with a root-level `pnpm-lock.yaml`, and
pin the package manager version in the generated `package.json` from the
`packageManager` the Ember applications declare, read through the new
`EmberCli::App#package_manager_spec`
* Stop adding `rails_12factor` to the `Gemfile` from `rails generate
ember:heroku`. Rails serves the twelve-factor behaviour the gem backported
natively since `5.0`, and every Rails version this gem supports is `>= 5.2`.
Expand Down
17 changes: 14 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,10 @@ c.app :frontend, path: "~/projects/my-ember-app"

- `silent` - this provides `--silent` option for Ember CLI commands to control verbosity of their output.

- `yarn` - enables the [yarn](https://github.com/yarnpkg/yarn) package manager when installing dependencies
- `package_manager` - the package manager that installs the application's
NodeJS dependencies: `:npm` (the default), `:yarn`, or `:pnpm`. Name the
executable with `npm_path`, `yarn_path`, or `pnpm_path` when it is not on
the `$PATH`.

- `dev_server` - configures [Vite's development server](#vite-based-applications)
for Vite-based applications in `development`. Pass `false` to opt out of it,
Expand Down Expand Up @@ -388,6 +391,14 @@ writes to the build directory, and Rails serves the result.
**NOTE** Run the generator each time you introduce additional EmberCLI
applications into the project.

**Package manager** — the buildpack installs the package manager the *project
root's* lockfile names. The generator writes an empty `yarn.lock` or
`pnpm-lock.yaml` when an application is configured with yarn or pnpm as its
`package_manager`, and copies the `packageManager` field the Ember
applications declare into the generated `package.json` so that the buildpack
installs that version. Without a `packageManager` field the buildpack installs
the latest pnpm release.

**NodeJS version** — the buildpack reads `engines.node` from the *project
root's* `package.json` (the file the generator writes), not from the Ember
application's, and builds on the current LTS release when it names no version.
Expand All @@ -411,8 +422,8 @@ A build-pack solution for this is discussed in [Issue #491][#491].

EmberCLI-Rails installs the Ember application's NodeJS dependencies during
EmberCLI's compilation, triggered by the `assets:precompile` rake task. It runs
`npm install`, or `yarn install` for an application configured with the `yarn`
option.
`npm install`, `yarn install`, or `pnpm install`, as the application's
`package_manager` option names.

The executables it runs are required to be defined in the deployment SSH
session's `$PATH`. It is not sufficient to modify the session's `$PATH` in a
Expand Down
38 changes: 34 additions & 4 deletions bin/setup_ember
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,36 @@ set -e
# classic Broccoli-based build.
EMBER_VERSION="${EMBER_VERSION:-7.0.0}"

# PACKAGE_MANAGER selects the package manager that installs the app's NodeJS
# dependencies: `npm` (the default), `yarn`, or `pnpm`. The dummy
# application's initializer reads the same variable, so that
# `ember-cli-rails` is configured with the package manager that populated
# `node_modules`.
PACKAGE_MANAGER="${PACKAGE_MANAGER:-npm}"

case "$PACKAGE_MANAGER" in
npm|yarn|pnpm)
;;
*)
echo "Unsupported PACKAGE_MANAGER '${PACKAGE_MANAGER}'; use npm, yarn, or pnpm" >&2
exit 1
;;
esac

add_dev_dependency() {
case "$PACKAGE_MANAGER" in
yarn)
yarn add --dev "$@"
;;
pnpm)
pnpm add --save-dev "$@"
;;
*)
npm install --save-dev "$@"
;;
esac
}

setup_ember() {
local target="${1-spec/dummy/my-app}"

Expand All @@ -30,13 +60,13 @@ setup_ember() {
if [ -f "$target/vite.config.mjs" ]; then
# `ember-cli-rails-addon` is incompatible with the Vite-based
# blueprint, and `ember-cli-rails` does not require it there.
echo '-- Install NPM dependencies'
echo "-- Install NodeJS dependencies with ${PACKAGE_MANAGER}"
(cd $target &&
npm install)
$PACKAGE_MANAGER install)
else
echo '-- Install ember-cli-rails-addon'
echo "-- Install ember-cli-rails-addon with ${PACKAGE_MANAGER}"
(cd $target &&
npm install --save-dev ember-cli-rails-addon@rondale-sc/ember-cli-rails-addon)
add_dev_dependency ember-cli-rails-addon@rondale-sc/ember-cli-rails-addon)
fi

if [ -f "bower.json" ]; then
Expand Down
5 changes: 5 additions & 0 deletions lib/ember_cli.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
require "fileutils"
require "active_support/deprecation"
require "ember-cli-rails-assets"
require "ember_cli/engine"
require "ember_cli/configuration"
Expand All @@ -16,6 +17,10 @@ def configuration
Configuration.instance
end

def deprecator
@deprecator ||= ActiveSupport::Deprecation.new("1.0", "ember-cli-rails")
end

def app(name)
apps.fetch(name) do
fail KeyError, "#{name.inspect} app is not defined"
Expand Down
31 changes: 25 additions & 6 deletions lib/ember_cli/app.rb
Original file line number Diff line number Diff line change
Expand Up @@ -93,22 +93,31 @@ def mountable?
deploy.mountable?
end

def package_manager
paths.package_manager
end

def yarn?
paths.yarn?
end

def pnpm?
paths.pnpm?
end

def bower?
paths.bower_json.exist?
end

def node_engine
package_json = paths.package_json
package_json_value("engines", "node")
end

if package_json.exist?
JSON.parse(package_json.read).dig("engines", "node")
end
rescue JSON::ParserError
nil
# The `packageManager` field of the application's `package.json`, such as
# `"pnpm@10.0.0"`: the package manager version Corepack and Heroku's
# NodeJS buildpack install.
def package_manager_spec
package_json_value("packageManager")
end

def to_rack
Expand All @@ -135,6 +144,16 @@ def dev_server?

private

def package_json_value(*keys)
package_json = paths.package_json

if package_json.exist?
JSON.parse(package_json.read).dig(*keys)
end
rescue JSON::ParserError
nil
end

def development?
env.to_s == "development"
end
Expand Down
8 changes: 8 additions & 0 deletions lib/ember_cli/engine.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,13 @@ class Engine < Rails::Engine
initializer "ember-cli-rails.setup" do
require "ember_cli/route_helpers"
end

# Rails 7.1 and later manage the deprecators of engines with the
# application's own, so that `config.active_support.deprecation` applies.
initializer "ember-cli-rails.deprecator" do |app|
if app.respond_to?(:deprecators)
app.deprecators[:ember_cli_rails] = EmberCli.deprecator
end
end
end
end
78 changes: 63 additions & 15 deletions lib/ember_cli/path_set.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@

module EmberCli
class PathSet
PACKAGE_MANAGERS = %i[npm yarn pnpm].freeze

# npm ships with NodeJS, so it has no installation instructions of its own
# to point at when its executable is missing.
INSTALL_INSTRUCTIONS = {
yarn: "https://yarnpkg.com/lang/en/docs/install/",
pnpm: "https://pnpm.io/installation",
}.freeze

def initialize(app:, rails_root:, ember_cli_root:, environment:)
@app = app
@rails_root = rails_root
Expand Down Expand Up @@ -121,25 +130,59 @@ def npm

def yarn
if yarn?
@yarn ||= path_for_executable("yarn").tap do |yarn|
unless File.executable?(yarn.to_s)
fail DependencyError.new(<<-MSG.strip_heredoc)
EmberCLI has been configured to install NodeJS dependencies with Yarn, but the Yarn executable is unavailable.

Install it by following the instructions at https://yarnpkg.com/lang/en/docs/install/
@yarn ||= package_manager_executable(:yarn)
end
end

MSG
end
end
def pnpm
if pnpm?
@pnpm ||= package_manager_executable(:pnpm)
end
end

def node_modules
@node_modules ||= root.join("node_modules")
end

# The package manager that installs the application's NodeJS
# dependencies: the one the `package_manager` option names, else the one
# whose executable a `yarn_path` or `pnpm_path` option names, else npm.
# The deprecated `yarn: true` still selects yarn, with a warning.
def package_manager
@package_manager ||= begin
requested = app_options[:package_manager]

if requested.present?
requested.to_s.to_sym.tap do |name|
unless PACKAGE_MANAGERS.include?(name)
fail ArgumentError,
"Unsupported package manager #{requested.inspect} for " \
"`#{app_name}`; use one of #{PACKAGE_MANAGERS.inspect}"
end
end
elsif app_options[:yarn].present?
EmberCli.deprecator.warn(
"The `yarn` option of the `#{app_name}` Ember application is " \
"deprecated; configure it with `package_manager: :yarn` instead",
)

:yarn
elsif app_options[:yarn_path].present?
:yarn
elsif app_options[:pnpm_path].present?
:pnpm
else
:npm
end
end
end

def yarn?
app_options[:yarn].present? || app_options[:yarn_path].present?
package_manager == :yarn
end

def pnpm?
package_manager == :pnpm
end

def tee
Expand Down Expand Up @@ -169,11 +212,16 @@ def path_for_executable(command)
end
end

def package_manager
if yarn?
"yarn"
else
"npm"
def package_manager_executable(name)
path_for_executable(name.to_s).tap do |path|
unless File.executable?(path.to_s)
fail DependencyError.new(<<-MSG.strip_heredoc)
EmberCLI has been configured to install NodeJS dependencies with #{name}, but the #{name} executable is unavailable.

Install it by following the instructions at #{INSTALL_INSTRUCTIONS.fetch(name)}

MSG
end
end
end

Expand Down
5 changes: 4 additions & 1 deletion lib/ember_cli/shell.rb
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,11 @@ def install
clean_ember_dependencies!
end

if paths.yarn
case paths.package_manager
when :yarn
run! "#{paths.yarn} install"
when :pnpm
run! "#{paths.pnpm} install"
else
run! "#{paths.npm} prune && #{paths.npm} install"
end
Expand Down
Loading