Class: Seams::CLI::Resolve

Inherits:
Object
  • Object
show all
Defined in:
lib/seams/cli/resolve.rb

Overview

Implementation behind ‘bin/seams resolve` — gap-report 1.2 from the 2026-05 framework feature-gap survey: the documented escape hatch from seams’ generators.

Three modes:

bin/seams resolve --eject <engine>/<file>

  Marks a single host file as host-owned. The next regeneration
  of the engine skips this file. The file already lives in the
  host's working tree (seams generates "the code is in your
  repo") — eject just prepends an explicit ownership header
  and tells the engine generator to leave it alone.

bin/seams resolve --list-markers <engine>

  Lists every `# seams:insertion-point ...` marker the engine
  ships across all of its templated files. Helps the host
  operator see which extension points are public contract
  before writing a follow-up generator.

bin/seams resolve --list-ejected

  Surveys engines/ for files marked with the eject header and
  lists them. Useful for "what's diverged from the gem".

Returns true on success / false on failure. The caller (the ‘bin/seams` shim) translates that into a non-zero exit code.

Several methods here legitimately return true/false to signal success/failure but are command verbs (‘run_eject`, `engine_present?` is fine, `fail_with` etc). Rubocop’s PredicateMethod cop wants every bool-returning method renamed with a trailing ‘?`, but that’s wrong for the run_* dispatchers (they’re imperative, not predicates). AbcSize / CyclomaticComplexity likewise trigger on the run_* methods because CLI command branches are inherently branchy. The cops are disabled at file scope and the methods are kept linear and well-commented. rubocop:disable Naming/PredicateMethod, Metrics/AbcSize, Metrics/CyclomaticComplexity

Constant Summary collapse

DEFAULT_ENGINES_ROOT =
"engines"
EJECT_HEADER_PREFIX =

Header injected at the top of every ejected file. Position matters: future regenerations check the FIRST line of an existing destination file for this exact prefix.

"# seams:ejected from"
EJECT_HEADER_LINES =
lambda do |from|
  <<~HEADER
    #{EJECT_HEADER_PREFIX} #{from}
    # Re-running `bin/rails generate seams:#{from.split(".").first}` will NOT overwrite this file.
    # To return to the gem version: delete this file and re-run the generator.
  HEADER
end
INELIGIBLE_RELATIVE_PATTERNS =

Files at this list of relative paths under engines/<engine>/ are NOT eject-eligible. See doc note in EjectAware module.

[
  %r{\Adb/migrate/},                # one-shot, host runs them
  %r{\Alib/[^/]+/engine\.rb\z},     # framework-managed boot file
  %r{\Alib/[^/]+/version\.rb\z},    # framework-managed version constant
  /\AGemfile\z/,                    # engine's own Gemfile
  %r{\A[^/]+\.gemspec\z},           # engine's gemspec
  /\ARakefile\z/                    # engine's Rakefile (loads engine tasks)
].freeze

Instance Method Summary collapse

Constructor Details

#initialize(mode:, argument: nil, engines_root: DEFAULT_ENGINES_ROOT, output: $stdout, error: $stderr) ⇒ Resolve

Returns a new instance of Resolve.



73
74
75
76
77
78
79
# File 'lib/seams/cli/resolve.rb', line 73

def initialize(mode:, argument: nil, engines_root: DEFAULT_ENGINES_ROOT, output: $stdout, error: $stderr)
  @mode         = mode
  @argument     = argument
  @engines_root = engines_root
  @output       = output
  @error        = error
end

Instance Method Details

#callObject



81
82
83
84
85
86
87
88
89
# File 'lib/seams/cli/resolve.rb', line 81

def call
  case @mode
  when :eject         then run_eject
  when :list_markers  then run_list_markers
  when :list_ejected  then run_list_ejected
  else
    fail_with("unknown mode: #{@mode.inspect}")
  end
end