@@ -1329,6 +1329,98 @@ describe("scripts/crabbox-wrapper", () => {
13291329 expect ( remoteCommand ) . toContain ( `&& ${ shellScript } ` ) ;
13301330 } ) ;
13311331
1332+ it ( "preserves sparse changed-gate Git bootstrap for timeout-wrapped shell commands" , ( ) => {
1333+ const shellScript =
1334+ "/usr/bin/time -v timeout 1200s node scripts/check-changed.mjs --base origin/main --head HEAD" ;
1335+ const result = runWrapper (
1336+ "provider: hetzner, aws, local-container, blacksmith-testbox, or cloudflare\n" ,
1337+ [ "run" , "--provider" , "aws" , "--shell" , "--" , shellScript ] ,
1338+ {
1339+ gitResponses : {
1340+ [ "config\u0000--bool\u0000core.sparseCheckout" ] : { stdout : "true\n" } ,
1341+ [ "status\u0000--porcelain=v1" ] : { stdout : "" } ,
1342+ [ "merge-base\u0000origin/main\u0000HEAD" ] : { stdout : "abc123\n" } ,
1343+ } ,
1344+ } ,
1345+ ) ;
1346+
1347+ const output = parseFakeCrabboxOutput ( result ) ;
1348+ const remoteCommand = normalizeShellLineEndings ( output . args . at ( - 1 ) ?? "" ) ;
1349+ expect ( result . status ) . toBe ( 0 ) ;
1350+ expect ( remoteCommand ) . toContain ( "git init -q" ) ;
1351+ expect ( remoteCommand ) . toContain (
1352+ "git fetch -q --depth=1 origin abc123:refs/remotes/origin/main" ,
1353+ ) ;
1354+ expect ( remoteCommand ) . toContain ( `&& ${ shellScript } ` ) ;
1355+ } ) ;
1356+
1357+ it ( "preserves sparse changed-gate Git bootstrap for direct timeout-wrapped node commands" , ( ) => {
1358+ const result = runWrapper (
1359+ "provider: hetzner, aws, local-container, blacksmith-testbox, or cloudflare\n" ,
1360+ [
1361+ "run" ,
1362+ "--provider" ,
1363+ "aws" ,
1364+ "--" ,
1365+ "timeout" ,
1366+ "1200s" ,
1367+ "node" ,
1368+ "scripts/check-changed.mjs" ,
1369+ "--base" ,
1370+ "origin/main" ,
1371+ "--head" ,
1372+ "HEAD" ,
1373+ ] ,
1374+ {
1375+ gitResponses : {
1376+ [ "config\u0000--bool\u0000core.sparseCheckout" ] : { stdout : "true\n" } ,
1377+ [ "status\u0000--porcelain=v1" ] : { stdout : "" } ,
1378+ [ "merge-base\u0000origin/main\u0000HEAD" ] : { stdout : "abc123\n" } ,
1379+ } ,
1380+ } ,
1381+ ) ;
1382+
1383+ const output = parseFakeCrabboxOutput ( result ) ;
1384+ const remoteCommand = normalizeShellLineEndings ( output . args . at ( - 1 ) ?? "" ) ;
1385+ expect ( result . status ) . toBe ( 0 ) ;
1386+ expect ( output . args ) . toContain ( "--shell" ) ;
1387+ expect ( remoteCommand ) . toContain ( "git init -q" ) ;
1388+ expect ( remoteCommand ) . toMatch (
1389+ / & & t i m e o u t 1 2 0 0 s n o d e s c r i p t s \/ c h e c k - c h a n g e d \. m j s - - b a s e o r i g i n \/ m a i n - - h e a d H E A D $ / u,
1390+ ) ;
1391+ } ) ;
1392+
1393+ it ( "preserves sparse changed-gate Git bootstrap for direct timeout-wrapped shell commands" , ( ) => {
1394+ const result = runWrapper (
1395+ "provider: hetzner, aws, local-container, blacksmith-testbox, or cloudflare\n" ,
1396+ [
1397+ "run" ,
1398+ "--provider" ,
1399+ "aws" ,
1400+ "--" ,
1401+ "timeout" ,
1402+ "1200s" ,
1403+ "bash" ,
1404+ "-lc" ,
1405+ "pnpm check:changed" ,
1406+ ] ,
1407+ {
1408+ gitResponses : {
1409+ [ "config\u0000--bool\u0000core.sparseCheckout" ] : { stdout : "true\n" } ,
1410+ [ "status\u0000--porcelain=v1" ] : { stdout : "" } ,
1411+ [ "merge-base\u0000origin/main\u0000HEAD" ] : { stdout : "abc123\n" } ,
1412+ } ,
1413+ } ,
1414+ ) ;
1415+
1416+ const output = parseFakeCrabboxOutput ( result ) ;
1417+ const remoteCommand = normalizeShellLineEndings ( output . args . at ( - 1 ) ?? "" ) ;
1418+ expect ( result . status ) . toBe ( 0 ) ;
1419+ expect ( output . args ) . toContain ( "--shell" ) ;
1420+ expect ( remoteCommand ) . toContain ( "git init -q" ) ;
1421+ expect ( remoteCommand ) . toMatch ( / & & t i m e o u t 1 2 0 0 s b a s h - l c ' p n p m c h e c k : c h a n g e d ' $ / u) ;
1422+ } ) ;
1423+
13321424 it ( "does not treat quoted sparse shell text as a changed gate" , ( ) => {
13331425 const result = runWrapper (
13341426 "provider: hetzner, aws, local-container, blacksmith-testbox, or cloudflare\n" ,
0 commit comments