@@ -3,7 +3,7 @@ import { readFileSync } from "node:fs"
33import { mkdir , readFile , writeFile } from "node:fs/promises"
44import { createRequire } from "node:module"
55import { basename , dirname , join , resolve } from "node:path"
6- import { DEFAULT_WORDPRESS_VERSION , createRuntime , normalizeRecipeRunSummary , normalizeRuntimeEnvRecord , parseCommandOptions , type ArtifactBundle , type ArtifactPackageIdentity , type ArtifactPackageProvenance , type Runtime , type RuntimeAssetSpec , type RuntimePreviewSpec , type RuntimeRunRegistry , type WorkspaceRecipe , type WorkspaceRecipeComponentManifest , type WorkspaceRecipeExtraPlugin , type WorkspaceRecipeFixtureDatabase , type WorkspaceRecipeFuzzCasePhase } from "@automattic/wp-codebox-core"
6+ import { DEFAULT_WORDPRESS_VERSION , createRuntime , normalizeRecipeRunSummary , normalizeRuntimeEnvRecord , parseCommandOptions , validateRuntimePolicy , type ArtifactBundle , type ArtifactPackageIdentity , type ArtifactPackageProvenance , type Runtime , type RuntimeAssetSpec , type RuntimePolicy , type RuntimePreviewSpec , type RuntimeRunRegistry , type WorkspaceRecipe , type WorkspaceRecipeComponentManifest , type WorkspaceRecipeExtraPlugin , type WorkspaceRecipeFixtureDatabase , type WorkspaceRecipeFuzzCasePhase } from "@automattic/wp-codebox-core"
77import { stripUndefined } from "@automattic/wp-codebox-core/internals"
88import { recipeExecutionSpec , sandboxWorkspaceContract } from "../agent-sandbox.js"
99import { captureStdout , printRecipeHumanOutput , printRecipeValidateHumanOutput , serializeError } from "../output.js"
@@ -14,7 +14,7 @@ import { recipeExternalServiceBoundarySummaries } from "../recipe-external-servi
1414import { resolveRecipeSecretEnv } from "../recipe-secret-env.js"
1515import type { PreparedRuntimeBackendPackage } from "../recipe-backend-package.js"
1616import { cleanupRecipePreparedSources , recipeBlueprintWithBootActivePlugins , recipeExtraPlugins , type PreparedDependencyOverlay , type PreparedExtraPlugin , type PreparedRuntimeOverlay , type PreparedStagedFile , type PreparedWorkspaceMount } from "../recipe-sources.js"
17- import { loadWorkspaceRecipe , recipePolicy , recipeWorkflowSteps , validateWorkspaceRecipe , type RecipeWorkflowPhase } from "../recipe-validation.js"
17+ import { loadWorkspaceRecipe , recipePolicy , recipeWorkflowSteps , validateRecipeRuntimePolicy , validateWorkspaceRecipe , type RecipeWorkflowPhase } from "../recipe-validation.js"
1818import { resolveCliRuntimeBackend } from "../runtime-backends.js"
1919import { previewSpec , releaseRuntime , runtimeMetadata , type RunOutput } from "../runtime-command-wrappers.js"
2020import { artifactManifestFilesByPath , parseBenchResults , writeBenchmarkArtifactEvidence } from "./recipe-run-benchmark-artifacts.js"
@@ -106,7 +106,10 @@ async function runRecipe(options: RecipeRunOptions, interruption?: RecipeInterru
106106 let { runRecord } = context
107107 runRecord = await runRegistry . update ( runRecord . runId , { metadata : { provenance : recipeRunProvenance ( recipe , recipePath ) } } )
108108 await artifactPointer . update ( { commandStatus : "queued" } )
109- const issues = await validateWorkspaceRecipe ( recipe , recipePath )
109+ const issues = [
110+ ...await validateWorkspaceRecipe ( recipe , recipePath ) ,
111+ ...validateRecipeRuntimePolicy ( recipe , options . policy ) ,
112+ ]
110113 if ( issues . length > 0 ) {
111114 const failure = {
112115 name : "RecipeValidationError" ,
@@ -127,7 +130,8 @@ async function runRecipe(options: RecipeRunOptions, interruption?: RecipeInterru
127130 }
128131
129132 const plan = await planWorkspaceRecipe ( recipe , recipeDirectory , { recipePath, artifactsDirectory : configuredArtifactsDirectory } , { defaultWordPressVersion : DEFAULT_WORDPRESS_VERSION , resolveExecutionSpec : recipeExecutionSpec } )
130- const { valid : _policyValid , issues : _policyIssues , ...policy } = plan . policy
133+ const { valid : _policyValid , issues : _policyIssues , ...plannedPolicy } = plan . policy
134+ const policy = options . policy ?? plannedPolicy
131135 const runtimeEnv = {
132136 ...distributionRuntimeEnv ( recipe ) ,
133137 ...normalizeRuntimeEnv ( recipe . inputs ?. runtimeEnv ?? { } ) ,
@@ -610,7 +614,10 @@ async function validateRecipe(options: RecipeValidateOptions): Promise<RecipeVal
610614 const recipePath = resolve ( options . recipePath )
611615 try {
612616 const recipe = await loadWorkspaceRecipe ( recipePath )
613- const issues = await validateWorkspaceRecipe ( recipe , recipePath )
617+ const issues = [
618+ ...await validateWorkspaceRecipe ( recipe , recipePath ) ,
619+ ...validateRecipeRuntimePolicy ( recipe , options . policy ) ,
620+ ]
614621
615622 return {
616623 success : issues . length === 0 ,
@@ -711,6 +718,9 @@ function parseRecipeRunOptions(args: string[]): RecipeRunOptions {
711718 case "--timeout" :
712719 options . timeoutMs = parseRecipeRunTimeoutMs ( value )
713720 break
721+ case "--policy" :
722+ options . policy = parseRecipePolicy ( value )
723+ break
714724 default :
715725 throw new Error ( `Unknown option: ${ name } ` )
716726 }
@@ -741,6 +751,17 @@ function parseRecipeRunTimeoutMs(value: unknown): number {
741751 return timeoutMs
742752}
743753
754+ function parseRecipePolicy ( value : string ) : RuntimePolicy {
755+ const raw = value . trim ( ) . startsWith ( "{" ) ? value : readFileSync ( resolve ( value ) , "utf8" )
756+ const policy = JSON . parse ( raw ) as unknown
757+ const result = validateRuntimePolicy ( policy )
758+ if ( ! result . valid ) {
759+ throw new Error ( `Runtime policy is invalid: ${ result . issues . map ( ( issue ) => issue . message ) . join ( "; " ) } ` )
760+ }
761+
762+ return policy as RuntimePolicy
763+ }
764+
744765function parseRecipeValidateOptions ( args : string [ ] ) : RecipeValidateOptions {
745766 const parsed = parseCommandOptions ( args , new Set ( [ "--json" ] ) )
746767 if ( parsed . positionals . length > 0 ) {
@@ -755,6 +776,9 @@ function parseRecipeValidateOptions(args: string[]): RecipeValidateOptions {
755776 case "--recipe" :
756777 options . recipePath = value
757778 break
779+ case "--policy" :
780+ options . policy = parseRecipePolicy ( value )
781+ break
758782 default :
759783 throw new Error ( `Unknown option: ${ name } ` )
760784 }
0 commit comments