@@ -21,7 +21,7 @@ import { readConfigFile, setCwd, spawnCommandInGhWorkspace } from './utils.js';
2121 */
2222const middlewares = [ setCwd ] ;
2323
24- const buildOptions = ( y : Argv ) => {
24+ export const buildOptions = < T > ( y : Argv < T > ) => {
2525 return y
2626 . option ( 'target-branch' , {
2727 type : 'string' ,
@@ -37,27 +37,44 @@ const buildOptions = (y: Argv) => {
3737 } )
3838 . option ( 'base-commit' , {
3939 type : 'string' ,
40+ demandOption : false ,
4041 } ) ;
4142} ;
4243
43- const resolveChangedActors = async (
44- { targetBranch, sourceBranch, baseCommit } : Config ,
45- { isLatest } : { isLatest : boolean } ,
46- ) => {
47- const actorConfigs = await readConfigFile ( ) ;
44+ /**
45+ * Actor-selection flags, applied to every command that reads the actor config so a caller can
46+ * narrow the set it operates on (e.g. two-stage releases: `--ignore X`, then `--actors X`).
47+ * Kept separate from `buildOptions` so the read-only git commands don't advertise flags they ignore.
48+ */
49+ export const actorSelectionOptions = < T > ( y : Argv < T > ) => {
50+ return y
51+ . option ( 'actors' , {
52+ type : 'string' ,
53+ array : true ,
54+ default : [ ] as string [ ] ,
55+ } )
56+ . option ( 'ignore' , {
57+ type : 'string' ,
58+ array : true ,
59+ default : [ ] as string [ ] ,
60+ } ) ;
61+ } ;
62+
63+ const resolveChangedActors = async ( config : Config , { isLatest } : { isLatest : boolean } ) => {
64+ const actorConfigs = await readConfigFile ( config ) ;
4865
49- // This is an optimization for the common case where a branch only has cosmetic changes but had to merge in
66+ // This is an optimization for the common case where a branch only has cosmetic changes but had to smerge in
5067 // functional changes from master (being up-to-date is a CI requirement). Master is already validated, and
5168 // since the branch has no functional changes of its own, there is nothing new to validate.
5269 // Exception: if the branch has any functional changes alongside the merge, we must re-test — even
5370 // individually validated changes can have novel interactions when combined.
54- if ( hasMergeFromTarget ( sourceBranch , targetBranch ) ) {
71+ if ( hasMergeFromTarget ( config . sourceBranch , config . targetBranch ) ) {
5572 console . error (
5673 '[MERGE-FROM-TARGET-OPTIMIZATION]: There is merge from target branch, checking if there are no functional changes in our own branch. If so, we can skip tests' ,
5774 ) ;
58- const branchOnlyFiles = getBranchOnlyChangedFiles ( sourceBranch , targetBranch ) ;
75+ const branchOnlyFiles = getBranchOnlyChangedFiles ( config . sourceBranch , config . targetBranch ) ;
5976 // Omit baseCommit to get full branch history. Validated functional commits can still interact with merged ones
60- const allBranchCommits = getCommits ( { sourceBranch , targetBranch , baseCommit : undefined } ) ;
77+ const allBranchCommits = getCommits ( { ... config , baseCommit : undefined } ) ;
6178 const branchOnlyActorsChanged = getChangedActors ( {
6279 filepathsChanged : branchOnlyFiles ,
6380 actorConfigs,
@@ -73,7 +90,7 @@ const resolveChangedActors = async (
7390 }
7491
7592 // If the optimization doesn't apply, we check all branch commits including merges for full coverage. We don't reuse the merge optimization results because here we can apply baseCommit and check merge commits (they might be functional or just cosmetic)
76- const commits = getCommits ( { targetBranch , sourceBranch , baseCommit } ) ;
93+ const commits = getCommits ( config ) ;
7794 const changedFiles = getChangedFiles ( commits ) ;
7895 return getChangedActors ( { filepathsChanged : changedFiles , actorConfigs, isLatest, commits } ) ;
7996} ;
@@ -103,22 +120,19 @@ await yargs()
103120 const changedFiles = getChangedFiles ( commits ) ;
104121 console . log ( JSON . stringify ( changedFiles ) ) ;
105122 } )
123+ . command ( 'get-actor-configs' , '' , actorSelectionOptions , async ( { actors, ignore } ) => {
124+ const actorConfigs = await readConfigFile ( { actors, ignore } ) ;
125+ console . log ( JSON . stringify ( actorConfigs ) ) ;
126+ } )
106127 . command (
107- 'get-actor-configs ' ,
128+ 'get-affected-actors ' ,
108129 '' ,
109- ( _ ) => _ ,
110- async ( ) => {
111- const actorConfigs = await readConfigFile ( ) ;
112- console . log ( JSON . stringify ( actorConfigs ) ) ;
130+ ( args ) => actorSelectionOptions ( buildOptions ( args ) ) ,
131+ async ( config ) => {
132+ const actorsChanged = await resolveChangedActors ( config , { isLatest : false } ) ;
133+ console . log ( JSON . stringify ( actorsChanged ) ) ;
113134 } ,
114135 )
115- . command ( 'get-affected-actors' , '' , buildOptions , async ( { targetBranch, sourceBranch, baseCommit } ) => {
116- const actorsChanged = await resolveChangedActors (
117- { targetBranch, sourceBranch, baseCommit } ,
118- { isLatest : false } ,
119- ) ;
120- console . log ( JSON . stringify ( actorsChanged ) ) ;
121- } )
122136 . command (
123137 'report-tests' ,
124138 '' ,
@@ -135,12 +149,9 @@ await yargs()
135149 . command (
136150 'build' ,
137151 '' ,
138- ( args ) => buildOptions ( args ) . option ( 'dry-run' , { type : 'boolean' , default : false } ) ,
139- async ( { targetBranch, sourceBranch, baseCommit, dryRun, useDockerCache } ) => {
140- const actorsChanged = await resolveChangedActors (
141- { targetBranch, sourceBranch, baseCommit } ,
142- { isLatest : false } ,
143- ) ;
152+ ( args ) => actorSelectionOptions ( buildOptions ( args ) ) . option ( 'dry-run' , { type : 'boolean' , default : false } ) ,
153+ async ( config ) => {
154+ const actorsChanged = await resolveChangedActors ( config , { isLatest : false } ) ;
144155 // https://github.com/apify-store/google-maps#:actors/lukaskrivka_google-maps-with-contact-details
145156 // git@github .com:apify-store/google-maps#:actors/lukaskrivka_google-maps-with-contact-details
146157 const repoUrl = spawnCommandInGhWorkspace ( `git remote get-url origin` ) . replace (
@@ -151,9 +162,9 @@ await yargs()
151162 const builds = await runBuilds ( {
152163 repoUrl,
153164 actorConfigs : actorsChanged ,
154- branch : sourceBranch . replace ( 'origin/' , '' ) ,
155- dryRun,
156- useDockerCache,
165+ branch : config . sourceBranch . replace ( 'origin/' , '' ) ,
166+ dryRun : config . dryRun ,
167+ useDockerCache : config . useDockerCache ,
157168 } ) ;
158169 console . log ( JSON . stringify ( builds ) ) ;
159170 } ,
@@ -162,7 +173,7 @@ await yargs()
162173 'release' ,
163174 '' ,
164175 ( args ) =>
165- args
176+ actorSelectionOptions ( args )
166177 . option ( 'push-event-path' , { type : 'string' , demandOption : true } )
167178 . option ( 'dry-run' , { type : 'boolean' , default : false } )
168179 . option ( 'report-slack-channel' , { type : 'string' } )
@@ -173,7 +184,7 @@ await yargs()
173184 args . pushEventPath ,
174185 ) ;
175186 const isLatest = true ;
176- const actorConfigs = await readConfigFile ( ) ;
187+ const actorConfigs = await readConfigFile ( args ) ;
177188 const actorsChanged = getChangedActors ( {
178189 filepathsChanged : changedFiles ,
179190 actorConfigs,
@@ -206,37 +217,30 @@ await yargs()
206217 . command (
207218 'build-from-local' ,
208219 '' ,
209- ( args ) =>
210- args
211- . option ( 'actors' , {
212- type : 'string' ,
213- description :
214- 'Comma-separated actor names (owner/name) to build. Defaults to all actors in the repo.' ,
215- } )
216- . option ( 'dry-run' , { type : 'boolean' , default : false } ) ,
217- async ( { actors, dryRun } ) => {
218- const allActorConfigs = await readConfigFile ( ) ;
219- const actorConfigs = actors
220- ? actors . split ( ',' ) . map ( ( name ) => {
221- const trimmed = name . trim ( ) ;
222- const config = allActorConfigs . find ( ( c ) => c . actorFullName === trimmed ) ;
223- if ( ! config ) throw new Error ( `Actor "${ trimmed } " not found in repo` ) ;
224- return config ;
225- } )
226- : allActorConfigs ;
220+ ( args ) => actorSelectionOptions ( args ) . option ( 'dry-run' , { type : 'boolean' , default : false } ) ,
221+ async ( { actors, ignore, dryRun } ) => {
222+ const actorConfigs = await readConfigFile ( { actors, ignore } ) ;
227223 const builds = await runBuildsFromLocal ( { actorConfigs, dryRun } ) ;
228224 console . log ( JSON . stringify ( builds ) ) ;
229225 } ,
230226 )
231- . command (
232- 'delete-old-builds' ,
233- '' ,
234- ( _ ) => _ ,
235- async ( ) => {
236- const actorConfigs = await readConfigFile ( ) ;
237- await deleteOldBuilds ( actorConfigs ) ;
238- } ,
239- )
227+ . command ( 'delete-old-builds' , '' , actorSelectionOptions , async ( { actors, ignore } ) => {
228+ const actorConfigs = await readConfigFile ( { actors, ignore } ) ;
229+ await deleteOldBuilds ( actorConfigs ) ;
230+ } )
240231 . strictCommands ( )
241232 . demandCommand ( 1 , 'Command is required' )
233+ . fail ( ( msg , err , yargsInstance ) => {
234+ // Errors thrown from a command handler (e.g. an unknown actor passed to --actors/--ignore,
235+ // or a missing config file) arrive here as `err`. A malformed selection must fail loudly
236+ // rather than silently operate on the wrong set of actors — print the message, no stack.
237+ if ( err ) {
238+ console . error ( `[ERROR]: ${ err . message } ` ) ;
239+ } else {
240+ // Argument-parsing/validation failure — keep yargs' usage output.
241+ console . error ( yargsInstance . help ( ) ) ;
242+ console . error ( `\n${ msg } ` ) ;
243+ }
244+ process . exit ( 1 ) ;
245+ } )
242246 . parse ( hideBin ( process . argv ) ) ;
0 commit comments