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|
  "    \#{EJECT_HEADER_PREFIX} \#{from}\n    # Re-running `bin/rails generate seams:\#{from.split(\".\").first}` will NOT overwrite this file.\n    # To return to the gem version: delete this file and re-run the generator.\n  HEADER\nend\n"
INELIGIBLE_RELATIVE_PATTERNS =

Files at this list of relative paths under engines// 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