@@ -178,6 +178,147 @@ describe("runPrePrChecksWithFixes", () => {
178178 expect ( result . failures ) . toHaveLength ( 1 ) ;
179179 } ) ;
180180
181+ it ( "runs a repository's setup commands before its checks, in authored order" , async ( ) => {
182+ const shellCommands : string [ ] = [ ] ;
183+ mockRunCommand . mockImplementation ( ( cmd , args ) => {
184+ if ( cmd === "cat" && args [ 0 ] === WORKSPACE_MANIFEST_PATH ) {
185+ return commandResult ( 0 , JSON . stringify ( manifest ) ) ;
186+ }
187+ if ( cmd === "git" && args [ 0 ] === "-C" && args [ 2 ] === "rev-parse" ) {
188+ return commandResult ( 0 , "web-head" ) ;
189+ }
190+ const shell = shellCommand ( cmd ) ;
191+ if ( shell ) shellCommands . push ( shell ) ;
192+ return commandResult ( 0 , "" ) ;
193+ } ) ;
194+
195+ const result = await runPrePrChecksWithFixes (
196+ "sbx-test-123" ,
197+ {
198+ repositories : [
199+ {
200+ provider : "github" ,
201+ repoPath : "acme/web" ,
202+ setup : [ "make bootstrap" , "make deps" ] ,
203+ commands : [ "pnpm typecheck" ] ,
204+ } ,
205+ ] ,
206+ } ,
207+ "codex" ,
208+ "gpt-5" ,
209+ 0 ,
210+ ) ;
211+
212+ expect ( result . passed ) . toBe ( true ) ;
213+ expect ( result . setupFailed ) . toBe ( false ) ;
214+ expect ( shellCommands ) . toEqual ( [ "make bootstrap" , "make deps" , "pnpm typecheck" ] ) ;
215+ expect ( mockRunCommand ) . toHaveBeenCalledWith ( {
216+ cmd : "bash" ,
217+ args : [ "-lc" , "make bootstrap" ] ,
218+ cwd : "/vercel/sandbox" ,
219+ } ) ;
220+ } ) ;
221+
222+ it ( "stops a repository's checks and runs no fix cycles when its setup fails" , async ( ) => {
223+ const shellCommands : string [ ] = [ ] ;
224+ mockRunCommand . mockImplementation ( ( cmd , args ) => {
225+ const artifact = phaseArtifactCommand ( cmd , args , "codex" ) ;
226+ if ( artifact ) return artifact ;
227+ if ( cmd === "cat" && args [ 0 ] === WORKSPACE_MANIFEST_PATH ) {
228+ return commandResult ( 0 , JSON . stringify ( manifest ) ) ;
229+ }
230+ if ( cmd === "git" && args [ 0 ] === "-C" && args [ 2 ] === "rev-parse" ) {
231+ return commandResult ( 0 , "web-head" ) ;
232+ }
233+ const shell = shellCommand ( cmd ) ;
234+ if ( shell ) {
235+ shellCommands . push ( shell ) ;
236+ if ( shell === "make bootstrap" ) {
237+ return commandResult ( 127 , "" , "bash: line 1: toolchain: command not found" ) ;
238+ }
239+ }
240+ return commandResult ( 0 , "" ) ;
241+ } ) ;
242+
243+ const result = await runPrePrChecksWithFixes (
244+ "sbx-test-123" ,
245+ {
246+ repositories : [
247+ {
248+ provider : "github" ,
249+ repoPath : "acme/web" ,
250+ setup : [ "make bootstrap" ] ,
251+ commands : [ "pnpm typecheck" ] ,
252+ } ,
253+ ] ,
254+ } ,
255+ "codex" ,
256+ "gpt-5" ,
257+ ) ;
258+
259+ expect ( result . passed ) . toBe ( false ) ;
260+ expect ( result . setupFailed ) . toBe ( true ) ;
261+ expect ( result . fixCycles ) . toBe ( 0 ) ;
262+ expect ( result . results ) . toEqual ( [ ] ) ;
263+ expect ( shellCommands ) . toEqual ( [ "make bootstrap" ] ) ;
264+ expect ( mockWriteFiles ) . not . toHaveBeenCalled ( ) ;
265+ } ) ;
266+
267+ it ( "reports a setup failure distinctly from a check failure" , async ( ) => {
268+ mockRunCommand . mockImplementation ( ( cmd , args ) => {
269+ if ( cmd === "cat" && args [ 0 ] === WORKSPACE_MANIFEST_PATH ) {
270+ return commandResult ( 0 , JSON . stringify ( manifest ) ) ;
271+ }
272+ if ( cmd === "git" && args [ 0 ] === "-C" && args [ 2 ] === "rev-parse" ) {
273+ return commandResult ( 0 , "changed-head" ) ;
274+ }
275+ const shell = shellCommand ( cmd ) ;
276+ if ( shell === "make bootstrap" ) {
277+ return commandResult ( 127 , "" , "bash: line 1: toolchain: command not found" ) ;
278+ }
279+ if ( shell === "pnpm test" ) return commandResult ( 1 , "" , "2 tests failed" ) ;
280+ return commandResult ( 0 , "" ) ;
281+ } ) ;
282+
283+ const result = await runPrePrChecksWithFixes (
284+ "sbx-test-123" ,
285+ {
286+ repositories : [
287+ {
288+ provider : "github" ,
289+ repoPath : "acme/web" ,
290+ setup : [ "make bootstrap" ] ,
291+ commands : [ "pnpm typecheck" ] ,
292+ } ,
293+ { provider : "gitlab" , repoPath : "acme/api" , commands : [ "pnpm test" ] } ,
294+ ] ,
295+ } ,
296+ "codex" ,
297+ "gpt-5" ,
298+ 0 ,
299+ ) ;
300+
301+ expect ( result . failures ) . toHaveLength ( 2 ) ;
302+ expect ( result . failures [ 0 ] ) . toMatchObject ( {
303+ provider : "github" ,
304+ repoPath : "acme/web" ,
305+ command : "make bootstrap" ,
306+ exitCode : 127 ,
307+ phase : "setup" ,
308+ } ) ;
309+ expect ( result . failures [ 1 ] ) . toMatchObject ( {
310+ provider : "gitlab" ,
311+ repoPath : "acme/api" ,
312+ command : "pnpm test" ,
313+ } ) ;
314+ expect ( result . failures [ 1 ] ! . phase ) . toBeUndefined ( ) ;
315+ expect ( result . summary ) . toContain ( "SETUP FAILED for github:acme/web" ) ;
316+ expect ( result . summary ) . toContain ( "toolchain: command not found" ) ;
317+ expect ( result . summary ) . toContain ( "no agent fix cycles were run" ) ;
318+ expect ( result . summary ) . toContain ( "gitlab:acme/api" ) ;
319+ expect ( result . summary ) . not . toContain ( "SETUP FAILED for gitlab:acme/api" ) ;
320+ } ) ;
321+
181322 it ( "fails a check that exits 0 while reporting its dependencies are not installed" , async ( ) => {
182323 mockRunCommand . mockImplementation ( ( cmd , args ) => {
183324 if ( cmd === "cat" && args [ 0 ] === WORKSPACE_MANIFEST_PATH ) {
@@ -732,6 +873,22 @@ function phaseArtifactCommand(
732873 return null ;
733874}
734875
876+ /** The shell command text of a `bash -lc` invocation, or null for anything else. */
877+ function shellCommand ( cmd : unknown ) : string | null {
878+ const objectCommand = cmd as { cmd ?: unknown ; args ?: unknown } ;
879+ if (
880+ typeof cmd === "object" &&
881+ cmd !== null &&
882+ objectCommand . cmd === "bash" &&
883+ Array . isArray ( objectCommand . args ) &&
884+ objectCommand . args [ 0 ] === "-lc" &&
885+ typeof objectCommand . args [ 1 ] === "string"
886+ ) {
887+ return objectCommand . args [ 1 ] ;
888+ }
889+ return null ;
890+ }
891+
735892function isConfiguredCheck ( cmd : unknown ) : boolean {
736893 const objectCommand = cmd as { cmd ?: unknown ; args ?: unknown } ;
737894 return (
0 commit comments