Module: GeneratorMessages::PackageManagerDetection
- Included in:
- GeneratorMessages
- Defined in:
- lib/generators/react_on_rails/generator_messages/package_manager_detection.rb
Overview
Package-manager detection helpers used by the install generator and the post-install message. Split out of GeneratorMessages to keep that class under Metrics/ClassLength and to group related logic together.
Constant Summary collapse
- SUPPORTED_PACKAGE_MANAGERS =
%w[npm pnpm yarn bun].freeze
- LOCKFILE_CANDIDATES_BY_MANAGER =
Hash insertion order is the detection priority used by detect_package_manager_from_lockfiles (yarn → pnpm → bun → npm).
{ "yarn" => ["yarn.lock"], "pnpm" => ["pnpm-lock.yaml"], "bun" => ["bun.lock", "bun.lockb"], "npm" => ["package-lock.json"] }.freeze
Instance Method Summary collapse
-
#detect_package_manager(app_root: Dir.pwd, package_json: PACKAGE_JSON_UNSET) ⇒ Object
Detects the package manager in priority order: 1.
- #detect_package_manager_from_lockfiles(app_root: Dir.pwd) ⇒ Object
-
#detect_package_manager_with_source(app_root: Dir.pwd, package_json: PACKAGE_JSON_UNSET) ⇒ Object
source is one of :env, :package_json, :lockfile, :default — used to name the originating source when surfacing detection errors.
- #lockfile_filename_for(package_manager, app_root: Dir.pwd) ⇒ Object
-
#lockfile_for_manager?(package_manager, app_root: Dir.pwd) ⇒ Boolean
Used by the CI scaffold so
cache:/<pm> installnever reference a lockfile that's not on disk (e.g.packageManager: pnpmwithoutpnpm-lock.yaml, which breaksactions/setup-node's cache step). -
#package_manager_declared?(manager:, app_root: Dir.pwd, package_json: PACKAGE_JSON_UNSET) ⇒ Boolean
Returns true when package.json declares a top-level
packageManagerfield with an npm-style version/range/tag (e.g."pnpm@9.0.0","pnpm@^10.0.0", or"pnpm@latest") for the requestedmanager. - #package_manager_executable_available?(package_manager) ⇒ Boolean
-
#read_package_json(app_root) ⇒ Object
Parses package.json once and returns the hash, or nil if the file is missing or unreadable.
- #supported_package_manager?(package_manager) ⇒ Boolean
Instance Method Details
#detect_package_manager(app_root: Dir.pwd, package_json: PACKAGE_JSON_UNSET) ⇒ Object
Detects the package manager in priority order:
- REACT_ON_RAILS_PACKAGE_MANAGER env variable
- packageManager field in package.json (Corepack standard)
- Lockfile on disk
- Falls back to "npm" (Shakapacker 8.x default)
Pass app_root: to resolve paths against a specific directory
(e.g. destination_root in generators) instead of Dir.pwd.
Omit package_json: (the default) to read package.json from disk.
Pass package_json: <parsed_hash> to reuse an already-parsed package.json and
avoid a re-read (callers that also inspect scripts/deps should parse once and
pass the hash).
Pass package_json: nil when the caller already attempted to read package.json and
wants detection to fall through directly to lockfile heuristics.
38 39 40 41 42 43 |
# File 'lib/generators/react_on_rails/generator_messages/package_manager_detection.rb', line 38 def detect_package_manager(app_root: Dir.pwd, package_json: PACKAGE_JSON_UNSET) detect_package_manager_with_source( app_root:, package_json: ).first end |
#detect_package_manager_from_lockfiles(app_root: Dir.pwd) ⇒ Object
105 106 107 108 109 |
# File 'lib/generators/react_on_rails/generator_messages/package_manager_detection.rb', line 105 def detect_package_manager_from_lockfiles(app_root: Dir.pwd) LOCKFILE_CANDIDATES_BY_MANAGER.keys.find do |pm| lockfile_for_manager?(pm, app_root:) end end |
#detect_package_manager_with_source(app_root: Dir.pwd, package_json: PACKAGE_JSON_UNSET) ⇒ Object
source is one of :env, :package_json, :lockfile, :default — used to name the originating source when surfacing detection errors.
See detect_package_manager for the package_json: three-way semantics
(omitted = read from disk, nil = caller cached absent, Hash = pre-parsed).
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
# File 'lib/generators/react_on_rails/generator_messages/package_manager_detection.rb', line 50 def detect_package_manager_with_source(app_root: Dir.pwd, package_json: PACKAGE_JSON_UNSET) env_package_manager = ENV.fetch("REACT_ON_RAILS_PACKAGE_MANAGER", nil)&.strip&.downcase return [env_package_manager, :env] if supported_package_manager?(env_package_manager) content = package_json_content( app_root:, package_json: ) pm_from_json = content ? package_manager_name_from_content(content) : nil return [pm_from_json, :package_json] if pm_from_json pm_from_lockfile = detect_package_manager_from_lockfiles(app_root:) return [pm_from_lockfile, :lockfile] if pm_from_lockfile ["npm", :default] end |
#lockfile_filename_for(package_manager, app_root: Dir.pwd) ⇒ Object
67 68 69 70 71 |
# File 'lib/generators/react_on_rails/generator_messages/package_manager_detection.rb', line 67 def lockfile_filename_for(package_manager, app_root: Dir.pwd) LOCKFILE_CANDIDATES_BY_MANAGER[package_manager]&.find do |name| File.exist?(File.join(app_root, name)) end end |
#lockfile_for_manager?(package_manager, app_root: Dir.pwd) ⇒ Boolean
Used by the CI scaffold so cache: / <pm> install never reference a lockfile
that's not on disk (e.g. packageManager: pnpm without pnpm-lock.yaml, which
breaks actions/setup-node's cache step).
101 102 103 |
# File 'lib/generators/react_on_rails/generator_messages/package_manager_detection.rb', line 101 def lockfile_for_manager?(package_manager, app_root: Dir.pwd) !lockfile_filename_for(package_manager, app_root:).nil? end |
#package_manager_declared?(manager:, app_root: Dir.pwd, package_json: PACKAGE_JSON_UNSET) ⇒ Boolean
Returns true when package.json declares a top-level packageManager field with an
npm-style version/range/tag (e.g. "pnpm@9.0.0", "pnpm@^10.0.0", or
"pnpm@latest") for the requested manager. The CI scaffold treats these as
declared so it does not inject a conflicting fallback version:. Projects that
need reproducible Corepack behavior should prefer an exact version, optionally
with a hash (e.g. "pnpm@9.0.0+sha256.abc"). A bare name without @<version>
returns false because pnpm/action-setup has no version to resolve from it.
Used by the CI scaffold to decide whether pnpm/action-setup needs an explicit
version: key; exact SemVer validation belongs only where a caller needs to
extract a reproducible version pin.
Pass package_json: <parsed_hash> to reuse an already-parsed package.json and
package_json: nil to preserve a cached missing/unreadable read.
85 86 87 88 89 90 91 92 93 94 95 96 |
# File 'lib/generators/react_on_rails/generator_messages/package_manager_detection.rb', line 85 def package_manager_declared?(manager:, app_root: Dir.pwd, package_json: PACKAGE_JSON_UNSET) content = package_json_content( app_root:, package_json: ) return false unless content declared = versioned_package_manager_name_from_content(content) return false if declared.nil? declared == manager.to_s.downcase end |
#package_manager_executable_available?(package_manager) ⇒ Boolean
115 116 117 118 119 |
# File 'lib/generators/react_on_rails/generator_messages/package_manager_detection.rb', line 115 def package_manager_executable_available?(package_manager) return false unless supported_package_manager?(package_manager) ReactOnRails::Utils.command_available?(package_manager) end |
#read_package_json(app_root) ⇒ Object
Parses package.json once and returns the hash, or nil if the file is missing or unreadable. Generator code can reuse the same parsed hash across setup, template, and message paths.
Intentionally public: install_generator and other generator callers read package.json once and pass the result to detect_package_manager / package_manager_declared? to avoid repeated disk reads. When this returns nil, pass package_json: nil to those helpers to preserve that cached missing/unreadable state.
132 133 134 135 136 137 138 139 |
# File 'lib/generators/react_on_rails/generator_messages/package_manager_detection.rb', line 132 def read_package_json(app_root) package_json_path = File.join(app_root, "package.json") return nil unless File.exist?(package_json_path) JSON.parse(File.read(package_json_path)) rescue JSON::ParserError, Errno::EACCES, Errno::ENOENT nil end |
#supported_package_manager?(package_manager) ⇒ Boolean
111 112 113 |
# File 'lib/generators/react_on_rails/generator_messages/package_manager_detection.rb', line 111 def supported_package_manager?(package_manager) SUPPORTED_PACKAGE_MANAGERS.include?(package_manager) end |