Skip to content

Commit 9b94024

Browse files
committed
Improved Blueprints v1 compat after testing on popular Blueprints
1 parent 46d4448 commit 9b94024

File tree

9 files changed

+4335
-4342
lines changed

9 files changed

+4335
-4342
lines changed

components/Blueprints/DataReference/InlineDirectory.php

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -88,23 +88,23 @@ public function as_directory(): Directory {
8888
*
8989
* @return self The created instance.
9090
*/
91-
public static function from_array( array $data ): self {
92-
if ( ! isset( $data['name'] ) || ! isset( $data['children'] ) || ! is_array( $data['children'] ) ) {
91+
public static function from_blueprint_data( array $data ): self {
92+
if ( ! isset( $data['directoryName'] ) || ! isset( $data['files'] ) || ! is_array( $data['files'] ) ) {
9393
throw new InvalidArgumentException( 'Invalid inline directory data' );
9494
}
9595

9696
$children = [];
97-
foreach ( $data['children'] as $child ) {
98-
if ( InlineFile::is_valid( $child ) ) {
99-
$children[] = InlineFile::from_blueprint_data( $child );
97+
foreach ( $data['files'] as $fileName => $child ) {
98+
if ( is_string( $child ) ) {
99+
$children[$fileName] = new InlineFile( $fileName, $child );
100100
} elseif ( self::is_valid( $child ) ) {
101-
$children[] = self::from_array( $child );
101+
$children[$fileName] = self::from_blueprint_data( $child );
102102
} else {
103103
throw new InvalidArgumentException( 'Invalid inline directory child' );
104104
}
105105
}
106106

107-
return new self( $data['name'], $children );
107+
return new self( $data['directoryName'], $children );
108108
}
109109

110110
/**
@@ -115,7 +115,7 @@ public static function from_array( array $data ): self {
115115
* @return bool Whether the array is valid.
116116
*/
117117
public static function is_valid( $data ): bool {
118-
return is_array( $data ) && isset( $data['name'] ) && isset( $data['children'] ) && is_array( $data['children'] );
118+
return is_array( $data ) && isset( $data['directoryName'] ) && isset( $data['files'] ) && is_array( $data['files'] );
119119
}
120120

121121
/**

components/Blueprints/Runner.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -113,11 +113,11 @@ public function __construct( RunnerConfiguration $configuration ) {
113113
* Store cached HTTP responses in a temporary directory with a stable path
114114
* to reuse across multiple runs.
115115
*/
116-
// 'cache' => new FilesystemCache(
117-
// LocalFilesystem::create(
118-
// sys_get_temp_dir() . '/wp-blueprints'
119-
// )
120-
// ),
116+
'cache' => new FilesystemCache(
117+
LocalFilesystem::create(
118+
sys_get_temp_dir() . '/wp-blueprints'
119+
)
120+
),
121121
] );
122122
$this->mainTracker = new Tracker();
123123

components/Blueprints/Steps/RunPHPStep.php

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,6 @@ class RunPHPStep implements StepInterface {
2222
public $scriptPath;
2323
/** @var array<string, string>|null */
2424
public $env;
25-
/** @var array<string, string>|null */
26-
public $__SERVER; // Renamed from $__SERVER to avoid PHP superglobal conflict
2725

2826
public function __construct( DataReference $code, ?array $env = null ) {
2927
$this->code = $code;
@@ -34,10 +32,6 @@ public function run( Runtime $runtime, Tracker $tracker ) {
3432
$tracker->setCaption( 'Running custom PHP code' );
3533

3634
$env = $this->env ?? [];
37-
if ( ! empty( $this->__SERVER ?? [] ) ) {
38-
$env['$_SERVER'] = $this->__SERVER ?? [];
39-
}
40-
4135
$resolvedCode = $runtime->resolve( $this->code );
4236
if ( $resolvedCode instanceof File ) {
4337
$code = $resolvedCode->getStream()->consume_all();

components/Blueprints/Validator/HumanFriendlySchemaValidator.php

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ public function __toString(): string {
1919
return $this->value;
2020
}
2121
}
22-
$MISSING = new Symbol( 'missing' );
2322

2423
/**
2524
* A lite JSON schema validator with human-centric error messages.
@@ -106,12 +105,15 @@ final class HumanFriendlySchemaValidator {
106105
*/
107106
private $arrayIsValidObject;
108107

108+
private $MISSING;
109+
109110
public function __construct(
110111
array $schema,
111112
array $options = []
112113
) {
113114
$this->schema = $schema;
114115
$this->arrayIsValidObject = $options['array_is_valid_object'] ?? true;
116+
$this->MISSING = new Symbol( 'missing' );
115117
}
116118

117119
/**
@@ -14020,15 +14022,15 @@ private function explainAggregateMismatch(
1402014022
$disc = $this->inferDiscriminator( $parentSchema['discriminator'] ?? null, $branches );
1402114023
if ( $disc ) {
1402214024
[ $prop, $allowedDiscriminatorValues ] = $disc;
14023-
$actualValue = $GLOBALS['MISSING']; // Default to missing
14025+
$actualValue = $this->MISSING; // Default to missing
1402414026
if ( is_array( $data ) && array_key_exists( $prop, $data ) ) {
1402514027
$actualValue = $data[ $prop ];
1402614028
} elseif ( is_object( $data ) && property_exists( $data, $prop ) ) {
1402714029
$actualValue = $data->$prop;
1402814030
}
1402914031

1403014032
if ( ! in_array( $actualValue, $allowedDiscriminatorValues, true ) ) {
14031-
$actual_humanized = ( $actualValue === $GLOBALS['MISSING'] ) ? 'missing' : $this->valueSnippet( $actualValue );
14033+
$actual_humanized = ( $actualValue === $this->MISSING ) ? 'missing' : $this->valueSnippet( $actualValue );
1403214034

1403314035
return new ValidationError(
1403414036
$pointer,
@@ -14041,7 +14043,7 @@ private function explainAggregateMismatch(
1404114043
),
1404214044
[
1404314045
'expected' => [ 'property' => $prop, 'allowedValues' => $allowedDiscriminatorValues ],
14044-
'actual' => [ 'value' => ( $actualValue === $GLOBALS['MISSING'] ) ? null : $actualValue,
14046+
'actual' => [ 'value' => ( $actualValue === $this->MISSING ) ? null : $actualValue,
1404514047
'snippet' => $this->valueSnippet( $actualValue ),
1404614048
],
1404714049
]
@@ -14383,6 +14385,7 @@ private function inferDiscriminator( ?array $explicit, array $branches ): ?array
1438314385
}
1438414386
}
1438514387
}
14388+
// print_R($objs);
1438614389

1438714390
if ( count( $candidates ) === 1 ) { // Only one property serves as a unique discriminator
1438814391
return [ key( $candidates ), current( $candidates ) ];

components/Blueprints/Versions/Version1/V1ToV2Transpiler.php

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -468,9 +468,20 @@ public function upgrade( array $validated_v1_blueprint ): array {
468468
// Prefix paths with "writeToPath".
469469
// The rest of the data format is compliant with v2.
470470
$base_path = self::translatePath( $v1step['writeToPath'] );
471-
foreach ( $v1step['filesTree'] as $path => $data ) {
472-
$joined_path = wp_join_paths( $base_path, $path );
473-
$v2step['files'][ $joined_path ] = $data;
471+
if(isset($v1step['filesTree']['resource'])) {
472+
$v2step['files']['/'] = self::convertV1ResourceToV2Reference( $v1step['filesTree'], $base_path );
473+
} else {
474+
foreach ( $v1step['filesTree'] as $path => $data ) {
475+
$joined_path = wp_join_paths( $base_path, $path );
476+
$v2step['files'][ $joined_path ] = is_string( $data )
477+
? [
478+
'filename' => basename( $path ),
479+
'content' => $data,
480+
]
481+
: self::convertV1ResourceToV2Reference(
482+
$data
483+
);
484+
}
474485
}
475486
$v2steps[] = $v2step;
476487
break;
@@ -498,7 +509,7 @@ protected static function convertV1ResourceToV2Reference( $resource ) {
498509
return $resource;
499510
} elseif ( is_array( $resource ) ) {
500511
if ( ! isset( $resource['resource'] ) ) {
501-
throw new BlueprintExecutionException( 'Unknown resource type: ' . $resource['resource'] );
512+
throw new BlueprintExecutionException( 'Missing resource type in ' . json_encode( $resource ) );
502513
}
503514
switch ( $resource['resource'] ) {
504515
case 'literal':
@@ -530,9 +541,17 @@ protected static function convertV1ResourceToV2Reference( $resource ) {
530541
return $path;
531542
case "literal:directory":
532543
// InlineDirectory
544+
$files = [];
545+
foreach ( $resource['files'] as $name => $file ) {
546+
if ( is_string( $file ) ) {
547+
$files[$name] = $file;
548+
} else {
549+
$files[$name] = self::convertV1ResourceToV2Reference( $file );
550+
}
551+
}
533552
return [
534553
'directoryName' => $resource['name'],
535-
'files' => $resource['files'],
554+
'files' => $files,
536555
];
537556
case "git:directory":
538557
// GitDirectoryReference
@@ -565,6 +584,9 @@ protected static function convertPhpCode( $code ) {
565584
if ( $id === T_CONSTANT_ENCAPSED_STRING && strncmp( trim( $text, '\'"' ), '/wordpress/', strlen( '/wordpress/' ) ) === 0 ) {
566585
$convertedCode .= 'getenv(\'DOCROOT\') . ' . var_export( substr( trim( $text, '\'"' ), strlen( '/wordpress/' ) ),
567586
true );
587+
} else if ( $id === T_CONSTANT_ENCAPSED_STRING && strncmp( trim( $text, '\'"' ), 'wordpress/', strlen( 'wordpress/' ) ) === 0 ) {
588+
$convertedCode .= 'getenv(\'DOCROOT\') . ' . var_export( substr( trim( $text, '\'"' ), strlen( 'wordpress/' ) ),
589+
true );
568590
} else {
569591
$convertedCode .= $text;
570592
}

components/Blueprints/Versions/Version2/json-schema/regenerate-schema.ts

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,73 @@ const cfg: Config = {
1414
type: "Blueprint",
1515
additionalProperties: false,
1616
skipTypeCheck: false,
17+
1718
};
1819

19-
const schema = createGenerator( cfg ).createSchema( "Blueprint" );
20+
const schema = createGenerator(cfg).createSchema("Blueprint");
21+
/**
22+
* The ts-json-schema-generator library converts string | false to "boolean".
23+
* We need to convert it back to string | false.
24+
*/
25+
schema.definitions.Blueprint.properties.siteOptions.properties.permalink_structure = {
26+
anyOf: [
27+
{
28+
type: "boolean",
29+
enum: [false],
30+
},
31+
{
32+
type: "string",
33+
},
34+
],
35+
};
36+
37+
function resolveRef(ref: string): any {
38+
if (!ref.startsWith("#/definitions/")) {
39+
throw new Error(`Unsupported $ref format: ${ref}`);
40+
}
41+
const defName = ref.replace("#/definitions/", "");
42+
const def = schema.definitions[defName];
43+
if (!def) {
44+
throw new Error(`Definition not found for $ref: ${ref}`);
45+
}
46+
return def;
47+
}
48+
49+
const steps = schema.definitions.Blueprint.properties.additionalStepsAfterExecution.items;
50+
51+
// Validation: Assert that each step type has a unique "step" value (via "const" or "enum")
52+
const seenStepValues = new Set<string>();
53+
if (Array.isArray(steps)) {
54+
for (let stepSchema of steps) {
55+
// Resolve $ref if present
56+
if (stepSchema && stepSchema.$ref) {
57+
stepSchema = resolveRef(stepSchema.$ref);
58+
}
59+
if (!stepSchema || stepSchema.type !== "object") {
60+
throw new Error(
61+
`Each step schema must be an object type. Found: ${JSON.stringify(stepSchema)}`
62+
);
63+
}
64+
if (stepSchema.properties && stepSchema.properties.step) {
65+
const stepProp = stepSchema.properties.step;
66+
let values: string[] = [];
67+
if (typeof stepProp.const === "string") {
68+
values = [stepProp.const];
69+
} else if (Array.isArray(stepProp.enum)) {
70+
values = stepProp.enum;
71+
}
72+
for (const val of values) {
73+
if (seenStepValues.has(val)) {
74+
throw new Error(
75+
`Duplicate step value "${val}" found in additionalStepsAfterExecution.items. Each step value must be unique.`
76+
);
77+
}
78+
seenStepValues.add(val);
79+
}
80+
}
81+
}
82+
}
83+
2084
const json = JSON.stringify( schema, null, 2 );
2185

2286
writeFileSync( out, json );

0 commit comments

Comments
 (0)