@@ -5,14 +5,17 @@ import { tmpdir } from "node:os"
55import { join } from "node:path"
66import { promisify } from "node:util"
77import { recipeRunDependencyOverlay } from "../packages/cli/src/commands/recipe-runtime-setup.js"
8+ import { validateWorkspaceRecipeSemantics } from "../packages/cli/src/recipe-validation.js"
89import { prepareRecipeDependencyOverlays , prepareRecipeRuntimeOverlays } from "../packages/cli/src/recipe-sources.js"
910import type { PreparedExtraPlugin } from "../packages/cli/src/recipe-sources.js"
11+ import { assertWorkspaceRecipeJsonSchema } from "../packages/runtime-core/src/recipe-schema.js"
1012import type { WorkspaceRecipe } from "../packages/runtime-core/src/runtime-contracts.js"
1113
1214const root = await mkdtemp ( join ( tmpdir ( ) , "wp-codebox-runtime-overlay-hydration-" ) )
1315const overlaySource = join ( root , "php-ai-client" )
1416const dependencySource = join ( root , "generic-composer-package" )
1517const nonGitDependencySource = join ( root , "non-git-composer-package" )
18+ const nonGitDependencyReference = "0123456789abcdef0123456789abcdef01234567"
1619const binDir = join ( root , "bin" )
1720const scoperPath = join ( root , "php-scoper.phar" )
1821const originalPath = process . env . PATH
@@ -149,6 +152,49 @@ const recipe: WorkspaceRecipe = {
149152 } ,
150153}
151154
155+ assertWorkspaceRecipeJsonSchema ( {
156+ schema : "wp-codebox/workspace-recipe/v1" ,
157+ workflow : { steps : [ { command : "wordpress.run-php" , args : [ "<?php" ] } ] } ,
158+ inputs : {
159+ dependency_overlays : [ {
160+ kind : "composer-package" ,
161+ package : "acme/non-git-package" ,
162+ source : nonGitDependencySource ,
163+ reference : nonGitDependencyReference ,
164+ consumer : "consumer-plugin" ,
165+ } ] ,
166+ } ,
167+ } )
168+ assert . throws ( ( ) => assertWorkspaceRecipeJsonSchema ( {
169+ schema : "wp-codebox/workspace-recipe/v1" ,
170+ workflow : { steps : [ { command : "wordpress.run-php" , args : [ "<?php" ] } ] } ,
171+ inputs : {
172+ dependency_overlays : [ {
173+ kind : "composer-package" ,
174+ package : "acme/non-git-package" ,
175+ source : nonGitDependencySource ,
176+ reference : "not-an-immutable-reference" ,
177+ consumer : "consumer-plugin" ,
178+ } ] ,
179+ } ,
180+ } ) )
181+ assert . deepEqual ( ( await validateWorkspaceRecipeSemantics ( {
182+ workflow : { steps : [ { command : "wordpress.run-php" , args : [ "<?php" ] } ] } ,
183+ inputs : {
184+ dependency_overlays : [ {
185+ kind : "composer-package" ,
186+ package : "acme/non-git-package" ,
187+ source : nonGitDependencySource ,
188+ reference : "not-an-immutable-reference" ,
189+ consumer : "consumer-plugin" ,
190+ } ] ,
191+ } ,
192+ } , root ) ) . filter ( ( issue ) => issue . path === "$.inputs.dependency_overlays[0].reference" ) , [ {
193+ code : "invalid-dependency-overlay-reference" ,
194+ path : "$.inputs.dependency_overlays[0].reference" ,
195+ message : "Dependency overlay reference must be a 40-64 character hexadecimal immutable reference when provided." ,
196+ } ] )
197+
152198const overlays = await prepareRecipeRuntimeOverlays ( recipe , root )
153199const consumers : PreparedExtraPlugin [ ] = [ {
154200 source : consumerSource ,
@@ -171,6 +217,7 @@ const dependencyOverlays = await prepareRecipeDependencyOverlays({
171217 kind : "composer-package" ,
172218 package : "acme/non-git-package" ,
173219 source : nonGitDependencySource ,
220+ reference : nonGitDependencyReference ,
174221 consumer : "consumer-plugin" ,
175222 } ] ,
176223 } ,
@@ -188,25 +235,26 @@ try {
188235 assert . equal ( dependencyOverlays [ 0 ] . reference , dependencyReference . trim ( ) , "clean Git source revision survives Composer staging" )
189236 assert . equal ( dependencyOverlays [ 0 ] . metadata . reference , dependencyReference . trim ( ) , "mounted dependency metadata preserves the source revision" )
190237 assert . equal ( recipeRunDependencyOverlay ( dependencyOverlays [ 0 ] ) . reference , dependencyReference . trim ( ) , "runtime dependency provenance exposes the source revision" )
191- assert . equal ( dependencyOverlays [ 1 ] . reference , undefined , "non-Git source has no fabricated revision" )
192- assert . equal ( dependencyOverlays [ 1 ] . metadata . reference , undefined , "non-Git source metadata omits the revision" )
238+ assert . equal ( dependencyOverlays [ 1 ] . reference , nonGitDependencyReference , "declared reference supports non-Git sources" )
239+ assert . equal ( dependencyOverlays [ 1 ] . metadata . reference , nonGitDependencyReference , "mounted dependency metadata preserves the declared reference" )
240+ assert . equal ( recipeRunDependencyOverlay ( dependencyOverlays [ 1 ] ) . reference , nonGitDependencyReference , "runtime dependency provenance exposes the declared reference" )
193241 assert . equal ( ( JSON . parse ( await readFile ( join ( consumerSource , "vendor" , "composer" , "installed.json" ) , "utf8" ) ) as { packages : Array < { source ?: unknown } > } ) . packages [ 0 ] . source , undefined , "original consumer Composer provenance remains unchanged" )
194242 const runtimeInstalled = JSON . parse ( await readFile ( join ( consumers [ 0 ] . source , "vendor" , "composer" , "installed.json" ) , "utf8" ) ) as { packages : Array < { name : string , version : string , source ?: { reference ?: string } } > }
195243 assert . deepEqual ( runtimeInstalled . packages [ 0 ] , { name : "acme/package" , version : "1.0.0+no-version-set" , source : { reference : dependencyReference . trim ( ) } } , "runtime Composer dependency provenance includes the immutable source reference" )
196- assert . deepEqual ( runtimeInstalled . packages [ 1 ] , { name : "acme/non-git-package" , version : "1.0.0+no-version-set" } , "runtime Composer provenance omits unresolved source references " )
244+ assert . deepEqual ( runtimeInstalled . packages [ 1 ] , { name : "acme/non-git-package" , version : "1.0.0+no-version-set" , source : { reference : nonGitDependencyReference } } , "runtime Composer provenance uses the declared source reference " )
197245 const { stdout : runtimeInstalledPhp } = await execFile ( "php" , [ "-r" , "echo json_encode(require $argv[1]);" , join ( consumers [ 0 ] . source , "vendor" , "composer" , "installed.php" ) ] )
198246 const runtimePhpVersions = JSON . parse ( runtimeInstalledPhp ) as { versions : Record < string , { reference ?: string | null } > }
199247 assert . equal ( runtimePhpVersions . versions [ "acme/package" ] ?. reference , dependencyReference . trim ( ) , "Composer runtime metadata includes the immutable source reference" )
200- assert . equal ( runtimePhpVersions . versions [ "acme/non-git-package" ] ?. reference , null , "Composer runtime metadata leaves unresolved references unchanged " )
248+ assert . equal ( runtimePhpVersions . versions [ "acme/non-git-package" ] ?. reference , nonGitDependencyReference , "Composer runtime metadata includes the declared source reference " )
201249 const runtimePackageRow = runtimePhpVersions . versions [ "acme/package" ]
202250 assert . deepEqual ( runtimePackageRow , {
203251 pretty_version : "1.0.0+no-version-set" ,
204252 reference : dependencyReference . trim ( ) ,
205253 } , "the final PHP-visible package row exposes the clean Git reference" )
206254 assert . deepEqual ( runtimePhpVersions . versions [ "acme/non-git-package" ] , {
207255 pretty_version : "1.0.0+no-version-set" ,
208- reference : null ,
209- } , "the final PHP-visible package row leaves an unavailable reference unchanged " )
256+ reference : nonGitDependencyReference ,
257+ } , "the final PHP-visible package row exposes the declared reference" )
210258 assert . match ( await readFile ( join ( overlays [ 0 ] . source , "src" , "Client.php" ) , "utf8" ) , / W o r d P r e s s \\ A i C l i e n t D e p e n d e n c i e s \\ P s r \\ L o g \\ L o g g e r I n t e r f a c e / )
211259} finally {
212260 await Promise . all ( [ ...overlays , ...dependencyOverlays , ...consumers ] . flatMap ( ( overlay ) => overlay . cleanupPaths ) . map ( ( path ) => rm ( path , { recursive : true , force : true } ) ) )
0 commit comments