22# frozen_string_literal: true
33
44#
5- # Entry point for the importmap-update.
6- #
7- # Modes:
8- # --print-config Print the resolved config and exit.
9- # --print-plan Run parsers + planner against captured
10- # outdated/audit output.
11- # --print-actions Run planner + reconciler. Existing PRs come
12- # from a YAML file (offline) or from gh.
13- # (default) Full run: parse outdated/audit, plan,
14- # reconcile against live gh, execute.
5+ # Entry point for the importmap-update action.
156#
167# Environment variables consumed by the action:
178# INPUT_CONFIG_FILE Path to the YAML config (default
@@ -42,20 +33,14 @@ options = {
4233 config_path : ENV . fetch ( "INPUT_CONFIG_FILE" , ".github/importmap-updates.yml" ) ,
4334 outdated_file : nil ,
4435 audit_file : nil ,
45- existing_prs_file : nil ,
4636 dry_run : %w[ true 1 yes ] . include? ( ENV [ "IMPORTMAP_DRY_RUN" ] . to_s . downcase )
4737}
48- action = :run
4938
5039OptionParser . new do |opts |
5140 opts . banner = "Usage: importmap-update [options]"
5241 opts . on ( "-c" , "--config PATH" , "Path to config YAML" ) { |p | options [ :config_path ] = p }
53- opts . on ( "--print-config" , "Print resolved config and exit" ) { action = :print_config }
54- opts . on ( "--print-plan" , "Run planner; needs --outdated-file --audit-file" ) { action = :print_plan }
55- opts . on ( "--print-actions" , "Run planner + reconciler; needs --outdated-file --audit-file [--existing-prs]" ) { action = :print_actions }
5642 opts . on ( "--outdated-file PATH" , "Captured `bin/importmap outdated` output" ) { |p | options [ :outdated_file ] = p }
5743 opts . on ( "--audit-file PATH" , "Captured `bin/importmap audit` output" ) { |p | options [ :audit_file ] = p }
58- opts . on ( "--existing-prs PATH" , "YAML file listing current PRs (offline)" ) { |p | options [ :existing_prs_file ] = p }
5944 opts . on ( "--dry-run" , "Do not perform side effects; log what would happen" ) { options [ :dry_run ] = true }
6045 opts . on ( "-h" , "--help" ) {
6146 puts opts
@@ -70,127 +55,66 @@ rescue Importmap::Update::Config::ConfigError => e
7055 exit 2
7156end
7257
73- # ---- helpers shared by debug and run modes ----
74-
75- def require_files! ( opts , *keys )
76- missing = keys . select { |k | opts [ k ] . nil? }
77- return if missing . empty?
58+ missing = [ :outdated_file , :audit_file ] . select { |k | options [ k ] . nil? }
59+ unless missing . empty?
7860 warn "Missing required option(s): #{ missing . map { |k | "--#{ k . to_s . tr ( "_" , "-" ) } " } . join ( ", " ) } "
7961 exit 2
8062end
8163
82- def build_plan ( opts , config )
83- outdated_output = File . read ( opts [ :outdated_file ] )
84- audit_output = File . read ( opts [ :audit_file ] )
85- outdated = Importmap ::Update ::Parsers ::OutdatedParser . parse ( outdated_output )
86- vulnerabilities = Importmap ::Update ::Parsers ::AuditParser . parse ( audit_output )
87- Importmap ::Update ::Planner . new (
88- outdated : outdated , vulnerabilities : vulnerabilities , config : config
89- ) . call
64+ repo = ENV [ "GITHUB_REPOSITORY" ]
65+ if repo . nil? || repo . empty?
66+ warn "GITHUB_REPOSITORY is not set; refusing to run."
67+ exit 2
9068end
9169
92- def load_existing_prs_from_file ( path )
93- return [ ] if path . nil? || !File . exist? ( path )
94- raw = YAML . safe_load_file ( path , permitted_classes : [ ] , aliases : false ) || [ ]
95- raw . map { |h |
96- Importmap ::Update ::Reconciler ::ExistingPR . new (
97- number : h [ "number" ] , branch : h [ "branch" ] , title : h [ "title" ] , body : h [ "body" ] . to_s
98- )
99- }
70+ token = ENV [ "GITHUB_TOKEN" ] || ENV [ "GH_TOKEN" ]
71+ if token . nil? || token . empty?
72+ warn "GITHUB_TOKEN (or GH_TOKEN) is not set; refusing to run."
73+ exit 2
10074end
10175
102- # ---- modes ----
103-
104- case action
105- when :print_config
106- puts config . to_yaml
107-
108- when :print_plan
109- require_files! ( options , :outdated_file , :audit_file )
110- plan = build_plan ( options , config )
111- puts ( {
112- "pr_specs" => plan . pr_specs . map { |s |
113- {
114- "kind" => s . kind . to_s , "branch" => s . branch , "title" => s . title ,
115- "packages" => s . packages . map { |p |
116- h = { "name" => p . name , "from" => p . from , "to" => p . to , "semver_kind" => p . semver_kind . to_s }
117- h [ "severity" ] = p . advisory [ :severity ] if p . advisory
118- h
119- }
120- }
121- } ,
122- "warnings" => plan . warnings
123- } . to_yaml )
124-
125- when :print_actions
126- require_files! ( options , :outdated_file , :audit_file )
127- plan = build_plan ( options , config )
128- existing_prs = load_existing_prs_from_file ( options [ :existing_prs_file ] )
129- result = Importmap ::Update ::Reconciler . new ( plan : plan , existing_prs : existing_prs ) . call
130- puts ( {
131- "actions" => result . actions . map { |a |
132- h = { "type" => a . type . to_s }
133- h [ "branch" ] = ( a . pr_spec || a . existing_pr ) . branch
134- h [ "existing_pr_number" ] = a . existing_pr . number if a . existing_pr
135- h [ "title" ] = a . pr_spec . title if a . pr_spec
136- h [ "reason" ] = a . reason if a . reason
137- h
138- } ,
139- "ignored" => result . ignored . map { |pr | { "number" => pr . number , "branch" => pr . branch } } ,
140- "warnings" => plan . warnings
141- } . to_yaml )
142-
143- when :run
144- require_files! ( options , :outdated_file , :audit_file )
145-
146- repo = ENV [ "GITHUB_REPOSITORY" ]
147- if repo . nil? || repo . empty?
148- warn "GITHUB_REPOSITORY is not set; refusing to run."
149- exit 2
150- end
151-
152- token = ENV [ "GITHUB_TOKEN" ] || ENV [ "GH_TOKEN" ]
153- if token . nil? || token . empty?
154- warn "GITHUB_TOKEN (or GH_TOKEN) is not set; refusing to run."
155- exit 2
156- end
157-
158- rails_root = ENV . fetch ( "RAILS_ROOT" , "." )
159- runner = Importmap ::Update ::Commands ::ShellRunner . new ( cwd : rails_root )
160- gh = Importmap ::Update ::GitHubClient . new ( repo : repo , token : token )
161- git = Importmap ::Update ::GitClient . new (
162- repo : Git . open ( rails_root ) ,
163- author_name : ENV . fetch ( "IMPORTMAP_AUTHOR_NAME" , "github-actions[bot]" ) ,
164- author_email : ENV . fetch ( "IMPORTMAP_AUTHOR_EMAIL" , "github-actions[bot]@users.noreply.github.com" )
165- )
166-
167- plan = build_plan ( options , config )
168- existing_prs = gh . list_open_prs ( branch_prefix : config . branch_prefix )
169- reconciled = Importmap ::Update ::Reconciler . new ( plan : plan , existing_prs : existing_prs ) . call
170-
171- executor = Importmap ::Update ::Executor . new (
172- gh : gh , git : git , runner : runner ,
173- base_branch : ENV . fetch ( "IMPORTMAP_BASE_BRANCH" , "main" ) ,
174- commit_message_prefix : config . commit_message . prefix ,
175- labels : config . labels ,
176- dry_run : options [ :dry_run ]
177- )
178- report = executor . call ( reconciled . actions )
179-
180- # Compact, easy-to-read run summary on stderr; the Actions log captures this.
181- warn "===== importmap-update summary ====="
182- warn "Dry run: #{ options [ :dry_run ] } "
183- warn "Plan warnings:"
184- plan . warnings . each { |w | warn " - #{ w } " }
185- warn "Reconciler ignored #{ reconciled . ignored . size } foreign PR(s)."
186- warn "Actions:"
187- report . outcomes . each do |o |
188- desc = [ o . branch || "?" , "PR##{ o . pr_number } " ] . compact . join ( " " )
189- warn " - #{ o . type } [#{ o . status } ] #{ desc } #{ "\u2014 " + o . detail if o . detail } "
190- end
191- report . warnings . each { |w | warn " ! #{ w } " }
192-
193- # Exit non-zero only if any non-skipped outcome failed; in dry run all are
194- # skipped, which is a successful run.
195- exit ( report . outcomes . any? ( &:failed? ) ? 1 : 0 )
76+ rails_root = ENV . fetch ( "RAILS_ROOT" , "." )
77+ runner = Importmap ::Update ::Commands ::ShellRunner . new ( cwd : rails_root )
78+ gh = Importmap ::Update ::GitHubClient . new ( repo : repo , token : token )
79+ git = Importmap ::Update ::GitClient . new (
80+ repo : Git . open ( rails_root ) ,
81+ author_name : ENV . fetch ( "IMPORTMAP_AUTHOR_NAME" , "github-actions[bot]" ) ,
82+ author_email : ENV . fetch ( "IMPORTMAP_AUTHOR_EMAIL" , "github-actions[bot]@users.noreply.github.com" )
83+ )
84+
85+ outdated_output = File . read ( options [ :outdated_file ] )
86+ audit_output = File . read ( options [ :audit_file ] )
87+ outdated = Importmap ::Update ::Parsers ::OutdatedParser . parse ( outdated_output )
88+ vulnerabilities = Importmap ::Update ::Parsers ::AuditParser . parse ( audit_output )
89+ plan = Importmap ::Update ::Planner . new (
90+ outdated : outdated , vulnerabilities : vulnerabilities , config : config
91+ ) . call
92+
93+ existing_prs = gh . list_open_prs ( branch_prefix : config . branch_prefix )
94+ reconciled = Importmap ::Update ::Reconciler . new ( plan : plan , existing_prs : existing_prs ) . call
95+
96+ executor = Importmap ::Update ::Executor . new (
97+ gh : gh , git : git , runner : runner ,
98+ base_branch : ENV . fetch ( "IMPORTMAP_BASE_BRANCH" , "main" ) ,
99+ commit_message_prefix : config . commit_message . prefix ,
100+ labels : config . labels ,
101+ dry_run : options [ :dry_run ]
102+ )
103+ report = executor . call ( reconciled . actions )
104+
105+ # Compact, easy-to-read run summary on stderr; the Actions log captures this.
106+ warn "===== importmap-update summary ====="
107+ warn "Dry run: #{ options [ :dry_run ] } "
108+ warn "Plan warnings:"
109+ plan . warnings . each { |w | warn " - #{ w } " }
110+ warn "Reconciler ignored #{ reconciled . ignored . size } foreign PR(s)."
111+ warn "Actions:"
112+ report . outcomes . each do |o |
113+ desc = [ o . branch || "?" , "PR##{ o . pr_number } " ] . compact . join ( " " )
114+ warn " - #{ o . type } [#{ o . status } ] #{ desc } #{ "— " + o . detail if o . detail } "
196115end
116+ report . warnings . each { |w | warn " ! #{ w } " }
117+
118+ # Exit non-zero only if any non-skipped outcome failed; in dry run all are
119+ # skipped, which is a successful run.
120+ exit ( report . outcomes . any? ( &:failed? ) ? 1 : 0 )
0 commit comments