@@ -275,7 +275,109 @@ for (const workflowPath of workflowPaths) {
275275 }
276276 }
277277
278- // Remove unused "Setup Scripts" step from update_cache_memory jobs.
278+ // For smoke-services: inject GitHub Actions services block (Redis + PostgreSQL) into the
279+ // agent job and replace --enable-host-access with --allow-host-service-ports 6379,5432.
280+ // The gh-aw compiler does not natively support GitHub Actions `services:` in the
281+ // frontmatter, so we inject them via post-processing. These services are required for
282+ // the smoke test to connect to Redis and PostgreSQL via host.docker.internal.
283+ const isServicesSmoke = workflowPath . includes ( 'smoke-services.lock.yml' ) ;
284+ if ( isServicesSmoke ) {
285+ // Inject services block after the agent job's "runs-on: ubuntu-latest" line.
286+ // The agent job uses `needs: activation` (single value) to distinguish it from the
287+ // detection job which uses a multi-line `needs:` array.
288+ const agentJobServicesBlock =
289+ ' services:\n' +
290+ ' redis:\n' +
291+ ' image: redis:7-alpine\n' +
292+ ' ports:\n' +
293+ ' - 6379:6379\n' +
294+ ' options: >-\n' +
295+ ' --health-cmd "redis-cli ping"\n' +
296+ ' --health-interval 10s\n' +
297+ ' --health-timeout 5s\n' +
298+ ' --health-retries 5\n' +
299+ ' postgres:\n' +
300+ ' image: postgres:15-alpine\n' +
301+ ' env:\n' +
302+ ' POSTGRES_USER: postgres\n' +
303+ ' POSTGRES_PASSWORD: testpass\n' +
304+ ' POSTGRES_DB: smoketest\n' +
305+ ' ports:\n' +
306+ ' - 5432:5432\n' +
307+ ' options: >-\n' +
308+ ' --health-cmd pg_isready\n' +
309+ ' --health-interval 10s\n' +
310+ ' --health-timeout 5s\n' +
311+ ' --health-retries 5\n' ;
312+
313+ // Match the agent job's needs/runs-on block (unique pattern: single-value needs)
314+ // followed immediately by permissions or services. Use flexible whitespace to
315+ // tolerate compiler indentation changes and handle both fresh and already-processed files.
316+ // The agent job has `needs: activation` (single string value); the detection job uses
317+ // a multi-value array (`needs:\n - activation\n - agent`), making this unique.
318+ const agentJobNeedsRunsOnRegex =
319+ / ^ ( { 2 } a g e n t : \n { 4 } n e e d s : a c t i v a t i o n \n { 4 } r u n s - o n : u b u n t u - l a t e s t \n ) ( { 4 } p e r m i s s i o n s : ) / m;
320+ const agentJobWithServicesRegex =
321+ / ^ ( { 2 } a g e n t : \n { 4 } n e e d s : a c t i v a t i o n \n { 4 } r u n s - o n : u b u n t u - l a t e s t \n { 4 } s e r v i c e s : ) / m;
322+
323+ if ( ! agentJobWithServicesRegex . test ( content ) ) {
324+ if ( agentJobNeedsRunsOnRegex . test ( content ) ) {
325+ // No services block yet — inject it
326+ content = content . replace (
327+ agentJobNeedsRunsOnRegex ,
328+ `$1${ agentJobServicesBlock } $2`
329+ ) ;
330+ modified = true ;
331+ console . log ( ` Injected services block (Redis + PostgreSQL) into agent job` ) ;
332+ } else {
333+ console . warn (
334+ ` WARNING: Could not find agent job pattern to inject services block. ` +
335+ `The compiled lock file may have changed structure. Manual review required.`
336+ ) ;
337+ }
338+ } else {
339+ console . log ( ` Services block already present in agent job` ) ;
340+ }
341+
342+ // Replace --enable-host-access with --allow-host-service-ports 6379,5432
343+ // only in the agent job's awf invocation (not the detection job).
344+ // The agent job's command is identifiable by its long --allow-domains list enclosed
345+ // in single quotes (the detection job uses a shorter unquoted domain list). We match
346+ // only within a single line and bound the match with the later --build-local flag to
347+ // avoid cross-line over-matching.
348+ // --allow-domains '...' <other flags> --enable-host-access --build-local
349+ const agentJobEnableHostAccessRegex =
350+ / ( - - a l l o w - d o m a i n s ' [ ^ ' ] * ' [ ^ \n ] * ) - - e n a b l e - h o s t - a c c e s s ( - - b u i l d - l o c a l ) / ;
351+ const agentJobHostServicePortsRegex =
352+ / ( - - a l l o w - d o m a i n s ' [ ^ ' ] * ' [ ^ \n ] * ) - - a l l o w - h o s t - s e r v i c e - p o r t s 6 3 7 9 , 5 4 3 2 ( - - b u i l d - l o c a l ) / ;
353+
354+ if ( ! agentJobHostServicePortsRegex . test ( content ) ) {
355+ if ( agentJobEnableHostAccessRegex . test ( content ) ) {
356+ const matchCount = ( content . match ( new RegExp ( agentJobEnableHostAccessRegex . source , 'g' ) ) || [ ] ) . length ;
357+ if ( matchCount > 1 ) {
358+ console . warn (
359+ ` WARNING: Found ${ matchCount } matches for agent job --enable-host-access pattern. ` +
360+ `Only the first will be replaced. Manual review recommended.`
361+ ) ;
362+ }
363+ content = content . replace (
364+ agentJobEnableHostAccessRegex ,
365+ `$1--allow-host-service-ports 6379,5432$2`
366+ ) ;
367+ modified = true ;
368+ console . log ( ` Replaced --enable-host-access with --allow-host-service-ports 6379,5432 in agent job` ) ;
369+ } else {
370+ console . warn (
371+ ` WARNING: Could not find --enable-host-access in agent job awf command. ` +
372+ `The compiled lock file may have changed structure. Manual review required.`
373+ ) ;
374+ }
375+ } else {
376+ console . log ( ` --allow-host-service-ports 6379,5432 already present in agent job` ) ;
377+ }
378+ }
379+
380+
279381 // The step downloads a private action but is never used in these jobs,
280382 // causing 401 Unauthorized failures when permissions: {} is set.
281383 const updateCacheSetupMatches = content . match ( updateCacheSetupScriptRegex ) ;
0 commit comments