diff --git a/packages/php-wasm/universal/src/lib/index.ts b/packages/php-wasm/universal/src/lib/index.ts index 8288823706..ca69559315 100644 --- a/packages/php-wasm/universal/src/lib/index.ts +++ b/packages/php-wasm/universal/src/lib/index.ts @@ -64,7 +64,7 @@ export type { } from './php-request-handler'; export { rotatePHPRuntime } from './rotate-php-runtime'; export { writeFiles } from './write-files'; -export type { FileTree } from './write-files'; +export type { FileTree, FileTreeAsync } from './write-files'; export { DEFAULT_BASE_URL, diff --git a/packages/php-wasm/universal/src/lib/write-files.ts b/packages/php-wasm/universal/src/lib/write-files.ts index 8110abb7a2..b35e60a032 100644 --- a/packages/php-wasm/universal/src/lib/write-files.ts +++ b/packages/php-wasm/universal/src/lib/write-files.ts @@ -9,8 +9,13 @@ export interface WriteFilesOptions { rmRoot?: boolean; } -export interface FileTree - extends Record {} +type FileContent = Uint8Array | string | FileTree; +type MaybePromise = T | Promise; + +export interface FileTree extends Record {} + +export interface FileTreeAsync + extends Record> {} /** * Writes multiple files to a specified directory in the Playground @@ -32,7 +37,7 @@ export interface FileTree export async function writeFiles( php: UniversalPHP, root: string, - newFiles: FileTree, + newFiles: MaybePromise, { rmRoot = false }: WriteFilesOptions = {} ) { if (rmRoot) { @@ -40,7 +45,10 @@ export async function writeFiles( await php.rmdir(root, { recursive: true }); } } - for (const [relativePath, content] of Object.entries(newFiles)) { + newFiles = await newFiles; + for (const relativePath of Object.keys(newFiles)) { + const content = await newFiles[relativePath]; + const filePath = joinPaths(root, relativePath); if (!(await php.fileExists(dirname(filePath)))) { await php.mkdir(dirname(filePath)); diff --git a/packages/playground/blueprints/public/blueprint-schema-validator.js b/packages/playground/blueprints/public/blueprint-schema-validator.js index 598401f771..e894b815d4 100644 --- a/packages/playground/blueprints/public/blueprint-schema-validator.js +++ b/packages/playground/blueprints/public/blueprint-schema-validator.js @@ -177,6 +177,7 @@ const schema11 = { { $ref: '#/definitions/CoreThemeReference' }, { $ref: '#/definitions/CorePluginReference' }, { $ref: '#/definitions/UrlReference' }, + { $ref: '#/definitions/BlueprintAssetReference' }, ], }, VFSReference: { @@ -293,6 +294,24 @@ const schema11 = { required: ['resource', 'url'], additionalProperties: false, }, + BlueprintAssetReference: { + type: 'object', + properties: { + resource: { + type: 'string', + const: 'blueprint-asset', + description: + 'Identifies the file resource as a Blueprint Asset', + }, + path: { + type: 'string', + description: + 'The path to the file in the Blueprint package', + }, + }, + required: ['resource', 'path'], + additionalProperties: false, + }, StepDefinition: { type: 'object', discriminator: { propertyName: 'step' }, @@ -1038,6 +1057,8 @@ const schema11 = { anyOf: [ { $ref: '#/definitions/GitDirectoryReference' }, { $ref: '#/definitions/DirectoryLiteralReference' }, + { $ref: '#/definitions/VFSDirectoryReference' }, + { $ref: '#/definitions/BlueprintAssetDirectoryReference' }, ], }, GitDirectoryReference: { @@ -1068,7 +1089,6 @@ const schema11 = { }, DirectoryLiteralReference: { type: 'object', - additionalProperties: false, properties: { resource: { type: 'string', @@ -1079,18 +1099,78 @@ const schema11 = { files: { $ref: '#/definitions/FileTree' }, name: { type: 'string' }, }, - required: ['files', 'name', 'resource'], + required: ['resource', 'files', 'name'], + additionalProperties: false, }, FileTree: { type: 'object', - additionalProperties: { - anyOf: [ - { $ref: '#/definitions/FileTree' }, - { type: ['object', 'string'] }, - ], - }, + additionalProperties: { $ref: '#/definitions/FileContent' }, properties: {}, }, + FileContent: { + anyOf: [ + { + type: 'object', + properties: { + BYTES_PER_ELEMENT: { type: 'number' }, + buffer: { + type: 'object', + properties: { byteLength: { type: 'number' } }, + required: ['byteLength'], + additionalProperties: false, + }, + byteLength: { type: 'number' }, + byteOffset: { type: 'number' }, + length: { type: 'number' }, + }, + required: [ + 'BYTES_PER_ELEMENT', + 'buffer', + 'byteLength', + 'byteOffset', + 'length', + ], + additionalProperties: { type: 'number' }, + }, + { type: 'string' }, + { $ref: '#/definitions/FileTree' }, + ], + }, + VFSDirectoryReference: { + type: 'object', + properties: { + resource: { + type: 'string', + const: 'vfs', + description: + 'Identifies the file resource as Virtual File System (VFS)', + }, + path: { + type: 'string', + description: 'The path to the file in the VFS', + }, + }, + required: ['resource', 'path'], + additionalProperties: false, + }, + BlueprintAssetDirectoryReference: { + type: 'object', + properties: { + resource: { + type: 'string', + const: 'blueprint-asset:directory', + description: + 'Identifies the directory resource as a Blueprint Asset', + }, + path: { + type: 'string', + description: + 'The path to the directory in the Blueprint package', + }, + }, + required: ['resource', 'path'], + additionalProperties: false, + }, InstallPluginOptions: { type: 'object', properties: { @@ -1483,6 +1563,7 @@ const schema16 = { { $ref: '#/definitions/CoreThemeReference' }, { $ref: '#/definitions/CorePluginReference' }, { $ref: '#/definitions/UrlReference' }, + { $ref: '#/definitions/BlueprintAssetReference' }, ], }; const schema17 = { @@ -1595,6 +1676,22 @@ const schema21 = { required: ['resource', 'url'], additionalProperties: false, }; +const schema22 = { + type: 'object', + properties: { + resource: { + type: 'string', + const: 'blueprint-asset', + description: 'Identifies the file resource as a Blueprint Asset', + }, + path: { + type: 'string', + description: 'The path to the file in the Blueprint package', + }, + }, + required: ['resource', 'path'], + additionalProperties: false, +}; function validate12( data, { instancePath = '', parentData, parentDataProperty, rootData = data } = {} @@ -2957,12 +3054,177 @@ function validate12( } var _valid0 = _errs55 === errors; valid0 = valid0 || _valid0; + if (!valid0) { + const _errs65 = errors; + const _errs66 = errors; + if (errors === _errs66) { + if ( + data && + typeof data == 'object' && + !Array.isArray(data) + ) { + let missing7; + if ( + (data.resource === undefined && + (missing7 = 'resource')) || + (data.path === undefined && + (missing7 = 'path')) + ) { + const err44 = { + instancePath, + schemaPath: + '#/definitions/BlueprintAssetReference/required', + keyword: 'required', + params: { missingProperty: missing7 }, + message: + "must have required property '" + + missing7 + + "'", + }; + if (vErrors === null) { + vErrors = [err44]; + } else { + vErrors.push(err44); + } + errors++; + } else { + const _errs68 = errors; + for (const key7 in data) { + if ( + !( + key7 === 'resource' || + key7 === 'path' + ) + ) { + const err45 = { + instancePath, + schemaPath: + '#/definitions/BlueprintAssetReference/additionalProperties', + keyword: 'additionalProperties', + params: { + additionalProperty: key7, + }, + message: + 'must NOT have additional properties', + }; + if (vErrors === null) { + vErrors = [err45]; + } else { + vErrors.push(err45); + } + errors++; + break; + } + } + if (_errs68 === errors) { + if (data.resource !== undefined) { + let data19 = data.resource; + const _errs69 = errors; + if (typeof data19 !== 'string') { + const err46 = { + instancePath: + instancePath + + '/resource', + schemaPath: + '#/definitions/BlueprintAssetReference/properties/resource/type', + keyword: 'type', + params: { type: 'string' }, + message: 'must be string', + }; + if (vErrors === null) { + vErrors = [err46]; + } else { + vErrors.push(err46); + } + errors++; + } + if ('blueprint-asset' !== data19) { + const err47 = { + instancePath: + instancePath + + '/resource', + schemaPath: + '#/definitions/BlueprintAssetReference/properties/resource/const', + keyword: 'const', + params: { + allowedValue: + 'blueprint-asset', + }, + message: + 'must be equal to constant', + }; + if (vErrors === null) { + vErrors = [err47]; + } else { + vErrors.push(err47); + } + errors++; + } + var valid16 = _errs69 === errors; + } else { + var valid16 = true; + } + if (valid16) { + if (data.path !== undefined) { + const _errs71 = errors; + if ( + typeof data.path !== + 'string' + ) { + const err48 = { + instancePath: + instancePath + + '/path', + schemaPath: + '#/definitions/BlueprintAssetReference/properties/path/type', + keyword: 'type', + params: { + type: 'string', + }, + message: + 'must be string', + }; + if (vErrors === null) { + vErrors = [err48]; + } else { + vErrors.push(err48); + } + errors++; + } + var valid16 = + _errs71 === errors; + } else { + var valid16 = true; + } + } + } + } + } else { + const err49 = { + instancePath, + schemaPath: + '#/definitions/BlueprintAssetReference/type', + keyword: 'type', + params: { type: 'object' }, + message: 'must be object', + }; + if (vErrors === null) { + vErrors = [err49]; + } else { + vErrors.push(err49); + } + errors++; + } + } + var _valid0 = _errs65 === errors; + valid0 = valid0 || _valid0; + } } } } } if (!valid0) { - const err44 = { + const err50 = { instancePath, schemaPath: '#/anyOf', keyword: 'anyOf', @@ -2970,9 +3232,9 @@ function validate12( message: 'must match a schema in anyOf', }; if (vErrors === null) { - vErrors = [err44]; + vErrors = [err50]; } else { - vErrors.push(err44); + vErrors.push(err50); } errors++; validate12.errors = vErrors; @@ -2990,7 +3252,7 @@ function validate12( validate12.errors = vErrors; return errors === 0; } -const schema22 = { +const schema23 = { type: 'object', discriminator: { propertyName: 'step' }, required: ['step'], @@ -3701,7 +3963,7 @@ const schema22 = { }, ], }; -const schema27 = { +const schema31 = { type: 'object', properties: { activate: { @@ -3716,7 +3978,7 @@ const schema27 = { }, additionalProperties: false, }; -const schema28 = { +const schema32 = { type: 'object', properties: { activate: { @@ -3736,7 +3998,7 @@ const schema28 = { }, additionalProperties: false, }; -const schema35 = { +const schema39 = { type: 'object', properties: { adminUsername: { type: 'string' }, @@ -3744,13 +4006,15 @@ const schema35 = { }, additionalProperties: false, }; -const schema23 = { +const schema24 = { anyOf: [ { $ref: '#/definitions/GitDirectoryReference' }, { $ref: '#/definitions/DirectoryLiteralReference' }, + { $ref: '#/definitions/VFSDirectoryReference' }, + { $ref: '#/definitions/BlueprintAssetDirectoryReference' }, ], }; -const schema24 = { +const schema25 = { type: 'object', properties: { resource: { @@ -3771,9 +4035,42 @@ const schema24 = { required: ['resource', 'url', 'ref', 'path'], additionalProperties: false, }; -const schema25 = { +const schema29 = { + type: 'object', + properties: { + resource: { + type: 'string', + const: 'vfs', + description: + 'Identifies the file resource as Virtual File System (VFS)', + }, + path: { + type: 'string', + description: 'The path to the file in the VFS', + }, + }, + required: ['resource', 'path'], + additionalProperties: false, +}; +const schema30 = { type: 'object', + properties: { + resource: { + type: 'string', + const: 'blueprint-asset:directory', + description: + 'Identifies the directory resource as a Blueprint Asset', + }, + path: { + type: 'string', + description: 'The path to the directory in the Blueprint package', + }, + }, + required: ['resource', 'path'], additionalProperties: false, +}; +const schema26 = { + type: 'object', properties: { resource: { type: 'string', @@ -3783,19 +4080,444 @@ const schema25 = { files: { $ref: '#/definitions/FileTree' }, name: { type: 'string' }, }, - required: ['files', 'name', 'resource'], + required: ['resource', 'files', 'name'], + additionalProperties: false, }; -const schema26 = { +const schema27 = { type: 'object', - additionalProperties: { - anyOf: [ - { $ref: '#/definitions/FileTree' }, - { type: ['object', 'string'] }, - ], - }, + additionalProperties: { $ref: '#/definitions/FileContent' }, properties: {}, }; +const schema28 = { + anyOf: [ + { + type: 'object', + properties: { + BYTES_PER_ELEMENT: { type: 'number' }, + buffer: { + type: 'object', + properties: { byteLength: { type: 'number' } }, + required: ['byteLength'], + additionalProperties: false, + }, + byteLength: { type: 'number' }, + byteOffset: { type: 'number' }, + length: { type: 'number' }, + }, + required: [ + 'BYTES_PER_ELEMENT', + 'buffer', + 'byteLength', + 'byteOffset', + 'length', + ], + additionalProperties: { type: 'number' }, + }, + { type: 'string' }, + { $ref: '#/definitions/FileTree' }, + ], +}; const wrapper0 = { validate: validate20 }; +function validate21( + data, + { instancePath = '', parentData, parentDataProperty, rootData = data } = {} +) { + let vErrors = null; + let errors = 0; + const _errs0 = errors; + let valid0 = false; + const _errs1 = errors; + if (errors === _errs1) { + if (data && typeof data == 'object' && !Array.isArray(data)) { + let missing0; + if ( + (data.BYTES_PER_ELEMENT === undefined && + (missing0 = 'BYTES_PER_ELEMENT')) || + (data.buffer === undefined && (missing0 = 'buffer')) || + (data.byteLength === undefined && (missing0 = 'byteLength')) || + (data.byteOffset === undefined && (missing0 = 'byteOffset')) || + (data.length === undefined && (missing0 = 'length')) + ) { + const err0 = { + instancePath, + schemaPath: '#/anyOf/0/required', + keyword: 'required', + params: { missingProperty: missing0 }, + message: "must have required property '" + missing0 + "'", + }; + if (vErrors === null) { + vErrors = [err0]; + } else { + vErrors.push(err0); + } + errors++; + } else { + const _errs3 = errors; + for (const key0 in data) { + if ( + !( + key0 === 'BYTES_PER_ELEMENT' || + key0 === 'buffer' || + key0 === 'byteLength' || + key0 === 'byteOffset' || + key0 === 'length' + ) + ) { + let data0 = data[key0]; + const _errs4 = errors; + if (!(typeof data0 == 'number' && isFinite(data0))) { + const err1 = { + instancePath: + instancePath + + '/' + + key0 + .replace(/~/g, '~0') + .replace(/\//g, '~1'), + schemaPath: + '#/anyOf/0/additionalProperties/type', + keyword: 'type', + params: { type: 'number' }, + message: 'must be number', + }; + if (vErrors === null) { + vErrors = [err1]; + } else { + vErrors.push(err1); + } + errors++; + } + var valid1 = _errs4 === errors; + if (!valid1) { + break; + } + } + } + if (_errs3 === errors) { + if (data.BYTES_PER_ELEMENT !== undefined) { + let data1 = data.BYTES_PER_ELEMENT; + const _errs6 = errors; + if (!(typeof data1 == 'number' && isFinite(data1))) { + const err2 = { + instancePath: + instancePath + '/BYTES_PER_ELEMENT', + schemaPath: + '#/anyOf/0/properties/BYTES_PER_ELEMENT/type', + keyword: 'type', + params: { type: 'number' }, + message: 'must be number', + }; + if (vErrors === null) { + vErrors = [err2]; + } else { + vErrors.push(err2); + } + errors++; + } + var valid2 = _errs6 === errors; + } else { + var valid2 = true; + } + if (valid2) { + if (data.buffer !== undefined) { + let data2 = data.buffer; + const _errs8 = errors; + if (errors === _errs8) { + if ( + data2 && + typeof data2 == 'object' && + !Array.isArray(data2) + ) { + let missing1; + if ( + data2.byteLength === undefined && + (missing1 = 'byteLength') + ) { + const err3 = { + instancePath: + instancePath + '/buffer', + schemaPath: + '#/anyOf/0/properties/buffer/required', + keyword: 'required', + params: { + missingProperty: missing1, + }, + message: + "must have required property '" + + missing1 + + "'", + }; + if (vErrors === null) { + vErrors = [err3]; + } else { + vErrors.push(err3); + } + errors++; + } else { + const _errs10 = errors; + for (const key1 in data2) { + if (!(key1 === 'byteLength')) { + const err4 = { + instancePath: + instancePath + + '/buffer', + schemaPath: + '#/anyOf/0/properties/buffer/additionalProperties', + keyword: + 'additionalProperties', + params: { + additionalProperty: + key1, + }, + message: + 'must NOT have additional properties', + }; + if (vErrors === null) { + vErrors = [err4]; + } else { + vErrors.push(err4); + } + errors++; + break; + } + } + if (_errs10 === errors) { + if ( + data2.byteLength !== undefined + ) { + let data3 = data2.byteLength; + if ( + !( + typeof data3 == + 'number' && + isFinite(data3) + ) + ) { + const err5 = { + instancePath: + instancePath + + '/buffer/byteLength', + schemaPath: + '#/anyOf/0/properties/buffer/properties/byteLength/type', + keyword: 'type', + params: { + type: 'number', + }, + message: + 'must be number', + }; + if (vErrors === null) { + vErrors = [err5]; + } else { + vErrors.push(err5); + } + errors++; + } + } + } + } + } else { + const err6 = { + instancePath: instancePath + '/buffer', + schemaPath: + '#/anyOf/0/properties/buffer/type', + keyword: 'type', + params: { type: 'object' }, + message: 'must be object', + }; + if (vErrors === null) { + vErrors = [err6]; + } else { + vErrors.push(err6); + } + errors++; + } + } + var valid2 = _errs8 === errors; + } else { + var valid2 = true; + } + if (valid2) { + if (data.byteLength !== undefined) { + let data4 = data.byteLength; + const _errs13 = errors; + if ( + !( + typeof data4 == 'number' && + isFinite(data4) + ) + ) { + const err7 = { + instancePath: + instancePath + '/byteLength', + schemaPath: + '#/anyOf/0/properties/byteLength/type', + keyword: 'type', + params: { type: 'number' }, + message: 'must be number', + }; + if (vErrors === null) { + vErrors = [err7]; + } else { + vErrors.push(err7); + } + errors++; + } + var valid2 = _errs13 === errors; + } else { + var valid2 = true; + } + if (valid2) { + if (data.byteOffset !== undefined) { + let data5 = data.byteOffset; + const _errs15 = errors; + if ( + !( + typeof data5 == 'number' && + isFinite(data5) + ) + ) { + const err8 = { + instancePath: + instancePath + '/byteOffset', + schemaPath: + '#/anyOf/0/properties/byteOffset/type', + keyword: 'type', + params: { type: 'number' }, + message: 'must be number', + }; + if (vErrors === null) { + vErrors = [err8]; + } else { + vErrors.push(err8); + } + errors++; + } + var valid2 = _errs15 === errors; + } else { + var valid2 = true; + } + if (valid2) { + if (data.length !== undefined) { + let data6 = data.length; + const _errs17 = errors; + if ( + !( + typeof data6 == 'number' && + isFinite(data6) + ) + ) { + const err9 = { + instancePath: + instancePath + '/length', + schemaPath: + '#/anyOf/0/properties/length/type', + keyword: 'type', + params: { type: 'number' }, + message: 'must be number', + }; + if (vErrors === null) { + vErrors = [err9]; + } else { + vErrors.push(err9); + } + errors++; + } + var valid2 = _errs17 === errors; + } else { + var valid2 = true; + } + } + } + } + } + } + } + } else { + const err10 = { + instancePath, + schemaPath: '#/anyOf/0/type', + keyword: 'type', + params: { type: 'object' }, + message: 'must be object', + }; + if (vErrors === null) { + vErrors = [err10]; + } else { + vErrors.push(err10); + } + errors++; + } + } + var _valid0 = _errs1 === errors; + valid0 = valid0 || _valid0; + if (!valid0) { + const _errs19 = errors; + if (typeof data !== 'string') { + const err11 = { + instancePath, + schemaPath: '#/anyOf/1/type', + keyword: 'type', + params: { type: 'string' }, + message: 'must be string', + }; + if (vErrors === null) { + vErrors = [err11]; + } else { + vErrors.push(err11); + } + errors++; + } + var _valid0 = _errs19 === errors; + valid0 = valid0 || _valid0; + if (!valid0) { + const _errs21 = errors; + if ( + !wrapper0.validate(data, { + instancePath, + parentData, + parentDataProperty, + rootData, + }) + ) { + vErrors = + vErrors === null + ? wrapper0.validate.errors + : vErrors.concat(wrapper0.validate.errors); + errors = vErrors.length; + } + var _valid0 = _errs21 === errors; + valid0 = valid0 || _valid0; + } + } + if (!valid0) { + const err12 = { + instancePath, + schemaPath: '#/anyOf', + keyword: 'anyOf', + params: {}, + message: 'must match a schema in anyOf', + }; + if (vErrors === null) { + vErrors = [err12]; + } else { + vErrors.push(err12); + } + errors++; + validate21.errors = vErrors; + return false; + } else { + errors = _errs0; + if (vErrors !== null) { + if (_errs0) { + vErrors.length = _errs0; + } else { + vErrors = null; + } + } + } + validate21.errors = vErrors; + return errors === 0; +} function validate20( data, { instancePath = '', parentData, parentDataProperty, rootData = data } = {} @@ -3805,13 +4527,9 @@ function validate20( if (errors === 0) { if (data && typeof data == 'object' && !Array.isArray(data)) { for (const key0 in data) { - let data0 = data[key0]; const _errs2 = errors; - const _errs3 = errors; - let valid1 = false; - const _errs4 = errors; if ( - !wrapper0.validate(data0, { + !validate21(data[key0], { instancePath: instancePath + '/' + @@ -3823,74 +4541,10 @@ function validate20( ) { vErrors = vErrors === null - ? wrapper0.validate.errors - : vErrors.concat(wrapper0.validate.errors); + ? validate21.errors + : vErrors.concat(validate21.errors); errors = vErrors.length; } - var _valid0 = _errs4 === errors; - valid1 = valid1 || _valid0; - if (!valid1) { - const _errs5 = errors; - if ( - !( - data0 && - typeof data0 == 'object' && - !Array.isArray(data0) - ) && - typeof data0 !== 'string' - ) { - const err0 = { - instancePath: - instancePath + - '/' + - key0.replace(/~/g, '~0').replace(/\//g, '~1'), - schemaPath: '#/additionalProperties/anyOf/1/type', - keyword: 'type', - params: { - type: schema26.additionalProperties.anyOf[1] - .type, - }, - message: 'must be object,string', - }; - if (vErrors === null) { - vErrors = [err0]; - } else { - vErrors.push(err0); - } - errors++; - } - var _valid0 = _errs5 === errors; - valid1 = valid1 || _valid0; - } - if (!valid1) { - const err1 = { - instancePath: - instancePath + - '/' + - key0.replace(/~/g, '~0').replace(/\//g, '~1'), - schemaPath: '#/additionalProperties/anyOf', - keyword: 'anyOf', - params: {}, - message: 'must match a schema in anyOf', - }; - if (vErrors === null) { - vErrors = [err1]; - } else { - vErrors.push(err1); - } - errors++; - validate20.errors = vErrors; - return false; - } else { - errors = _errs3; - if (vErrors !== null) { - if (_errs3) { - vErrors.length = _errs3; - } else { - vErrors = null; - } - } - } var valid0 = _errs2 === errors; if (!valid0) { break; @@ -3922,9 +4576,9 @@ function validate19( if (data && typeof data == 'object' && !Array.isArray(data)) { let missing0; if ( + (data.resource === undefined && (missing0 = 'resource')) || (data.files === undefined && (missing0 = 'files')) || - (data.name === undefined && (missing0 = 'name')) || - (data.resource === undefined && (missing0 = 'resource')) + (data.name === undefined && (missing0 = 'name')) ) { validate19.errors = [ { @@ -4267,9 +4921,294 @@ function validate18( } var _valid0 = _errs13 === errors; valid0 = valid0 || _valid0; + if (!valid0) { + const _errs14 = errors; + const _errs15 = errors; + if (errors === _errs15) { + if (data && typeof data == 'object' && !Array.isArray(data)) { + let missing1; + if ( + (data.resource === undefined && + (missing1 = 'resource')) || + (data.path === undefined && (missing1 = 'path')) + ) { + const err8 = { + instancePath, + schemaPath: + '#/definitions/VFSDirectoryReference/required', + keyword: 'required', + params: { missingProperty: missing1 }, + message: + "must have required property '" + + missing1 + + "'", + }; + if (vErrors === null) { + vErrors = [err8]; + } else { + vErrors.push(err8); + } + errors++; + } else { + const _errs17 = errors; + for (const key1 in data) { + if (!(key1 === 'resource' || key1 === 'path')) { + const err9 = { + instancePath, + schemaPath: + '#/definitions/VFSDirectoryReference/additionalProperties', + keyword: 'additionalProperties', + params: { additionalProperty: key1 }, + message: + 'must NOT have additional properties', + }; + if (vErrors === null) { + vErrors = [err9]; + } else { + vErrors.push(err9); + } + errors++; + break; + } + } + if (_errs17 === errors) { + if (data.resource !== undefined) { + let data4 = data.resource; + const _errs18 = errors; + if (typeof data4 !== 'string') { + const err10 = { + instancePath: + instancePath + '/resource', + schemaPath: + '#/definitions/VFSDirectoryReference/properties/resource/type', + keyword: 'type', + params: { type: 'string' }, + message: 'must be string', + }; + if (vErrors === null) { + vErrors = [err10]; + } else { + vErrors.push(err10); + } + errors++; + } + if ('vfs' !== data4) { + const err11 = { + instancePath: + instancePath + '/resource', + schemaPath: + '#/definitions/VFSDirectoryReference/properties/resource/const', + keyword: 'const', + params: { allowedValue: 'vfs' }, + message: 'must be equal to constant', + }; + if (vErrors === null) { + vErrors = [err11]; + } else { + vErrors.push(err11); + } + errors++; + } + var valid4 = _errs18 === errors; + } else { + var valid4 = true; + } + if (valid4) { + if (data.path !== undefined) { + const _errs20 = errors; + if (typeof data.path !== 'string') { + const err12 = { + instancePath: + instancePath + '/path', + schemaPath: + '#/definitions/VFSDirectoryReference/properties/path/type', + keyword: 'type', + params: { type: 'string' }, + message: 'must be string', + }; + if (vErrors === null) { + vErrors = [err12]; + } else { + vErrors.push(err12); + } + errors++; + } + var valid4 = _errs20 === errors; + } else { + var valid4 = true; + } + } + } + } + } else { + const err13 = { + instancePath, + schemaPath: '#/definitions/VFSDirectoryReference/type', + keyword: 'type', + params: { type: 'object' }, + message: 'must be object', + }; + if (vErrors === null) { + vErrors = [err13]; + } else { + vErrors.push(err13); + } + errors++; + } + } + var _valid0 = _errs14 === errors; + valid0 = valid0 || _valid0; + if (!valid0) { + const _errs22 = errors; + const _errs23 = errors; + if (errors === _errs23) { + if ( + data && + typeof data == 'object' && + !Array.isArray(data) + ) { + let missing2; + if ( + (data.resource === undefined && + (missing2 = 'resource')) || + (data.path === undefined && (missing2 = 'path')) + ) { + const err14 = { + instancePath, + schemaPath: + '#/definitions/BlueprintAssetDirectoryReference/required', + keyword: 'required', + params: { missingProperty: missing2 }, + message: + "must have required property '" + + missing2 + + "'", + }; + if (vErrors === null) { + vErrors = [err14]; + } else { + vErrors.push(err14); + } + errors++; + } else { + const _errs25 = errors; + for (const key2 in data) { + if (!(key2 === 'resource' || key2 === 'path')) { + const err15 = { + instancePath, + schemaPath: + '#/definitions/BlueprintAssetDirectoryReference/additionalProperties', + keyword: 'additionalProperties', + params: { additionalProperty: key2 }, + message: + 'must NOT have additional properties', + }; + if (vErrors === null) { + vErrors = [err15]; + } else { + vErrors.push(err15); + } + errors++; + break; + } + } + if (_errs25 === errors) { + if (data.resource !== undefined) { + let data6 = data.resource; + const _errs26 = errors; + if (typeof data6 !== 'string') { + const err16 = { + instancePath: + instancePath + '/resource', + schemaPath: + '#/definitions/BlueprintAssetDirectoryReference/properties/resource/type', + keyword: 'type', + params: { type: 'string' }, + message: 'must be string', + }; + if (vErrors === null) { + vErrors = [err16]; + } else { + vErrors.push(err16); + } + errors++; + } + if ('blueprint-asset:directory' !== data6) { + const err17 = { + instancePath: + instancePath + '/resource', + schemaPath: + '#/definitions/BlueprintAssetDirectoryReference/properties/resource/const', + keyword: 'const', + params: { + allowedValue: + 'blueprint-asset:directory', + }, + message: + 'must be equal to constant', + }; + if (vErrors === null) { + vErrors = [err17]; + } else { + vErrors.push(err17); + } + errors++; + } + var valid6 = _errs26 === errors; + } else { + var valid6 = true; + } + if (valid6) { + if (data.path !== undefined) { + const _errs28 = errors; + if (typeof data.path !== 'string') { + const err18 = { + instancePath: + instancePath + '/path', + schemaPath: + '#/definitions/BlueprintAssetDirectoryReference/properties/path/type', + keyword: 'type', + params: { type: 'string' }, + message: 'must be string', + }; + if (vErrors === null) { + vErrors = [err18]; + } else { + vErrors.push(err18); + } + errors++; + } + var valid6 = _errs28 === errors; + } else { + var valid6 = true; + } + } + } + } + } else { + const err19 = { + instancePath, + schemaPath: + '#/definitions/BlueprintAssetDirectoryReference/type', + keyword: 'type', + params: { type: 'object' }, + message: 'must be object', + }; + if (vErrors === null) { + vErrors = [err19]; + } else { + vErrors.push(err19); + } + errors++; + } + } + var _valid0 = _errs22 === errors; + valid0 = valid0 || _valid0; + } + } } if (!valid0) { - const err8 = { + const err20 = { instancePath, schemaPath: '#/anyOf', keyword: 'anyOf', @@ -4277,9 +5216,9 @@ function validate18( message: 'must match a schema in anyOf', }; if (vErrors === null) { - vErrors = [err8]; + vErrors = [err20]; } else { - vErrors.push(err8); + vErrors.push(err20); } errors++; validate18.errors = vErrors; @@ -4297,7 +5236,7 @@ function validate18( validate18.errors = vErrors; return errors === 0; } -const schema29 = { +const schema33 = { type: 'object', properties: { method: { @@ -4394,12 +5333,12 @@ const schema29 = { required: ['url'], additionalProperties: false, }; -const schema30 = { +const schema34 = { type: 'string', enum: ['GET', 'POST', 'HEAD', 'OPTIONS', 'PATCH', 'PUT', 'DELETE'], }; -const schema31 = { type: 'object', additionalProperties: { type: 'string' } }; -function validate28( +const schema35 = { type: 'object', additionalProperties: { type: 'string' } }; +function validate30( data, { instancePath = '', parentData, parentDataProperty, rootData = data } = {} ) { @@ -4409,7 +5348,7 @@ function validate28( if (data && typeof data == 'object' && !Array.isArray(data)) { let missing0; if (data.url === undefined && (missing0 = 'url')) { - validate28.errors = [ + validate30.errors = [ { instancePath, schemaPath: '#/required', @@ -4431,7 +5370,7 @@ function validate28( key0 === 'body' ) ) { - validate28.errors = [ + validate30.errors = [ { instancePath, schemaPath: '#/additionalProperties', @@ -4449,7 +5388,7 @@ function validate28( let data0 = data.method; const _errs2 = errors; if (typeof data0 !== 'string') { - validate28.errors = [ + validate30.errors = [ { instancePath: instancePath + '/method', schemaPath: '#/definitions/HTTPMethod/type', @@ -4471,12 +5410,12 @@ function validate28( data0 === 'DELETE' ) ) { - validate28.errors = [ + validate30.errors = [ { instancePath: instancePath + '/method', schemaPath: '#/definitions/HTTPMethod/enum', keyword: 'enum', - params: { allowedValues: schema30.enum }, + params: { allowedValues: schema34.enum }, message: 'must be equal to one of the allowed values', }, @@ -4491,7 +5430,7 @@ function validate28( if (data.url !== undefined) { const _errs5 = errors; if (typeof data.url !== 'string') { - validate28.errors = [ + validate30.errors = [ { instancePath: instancePath + '/url', schemaPath: '#/properties/url/type', @@ -4522,7 +5461,7 @@ function validate28( if ( typeof data2[key1] !== 'string' ) { - validate28.errors = [ + validate30.errors = [ { instancePath: instancePath + @@ -4554,7 +5493,7 @@ function validate28( } } } else { - validate28.errors = [ + validate30.errors = [ { instancePath: instancePath + '/headers', @@ -6543,7 +7482,7 @@ function validate28( vErrors.push(err34); } errors++; - validate28.errors = vErrors; + validate30.errors = vErrors; return false; } else { errors = _errs14; @@ -6565,7 +7504,7 @@ function validate28( } } } else { - validate28.errors = [ + validate30.errors = [ { instancePath, schemaPath: '#/type', @@ -6577,10 +7516,10 @@ function validate28( return false; } } - validate28.errors = vErrors; + validate30.errors = vErrors; return errors === 0; } -const schema32 = { +const schema36 = { type: 'object', properties: { relativeUri: { @@ -6646,7 +7585,7 @@ const schema32 = { }, additionalProperties: false, }; -function validate30( +function validate32( data, { instancePath = '', parentData, parentDataProperty, rootData = data } = {} ) { @@ -6656,8 +7595,8 @@ function validate30( if (data && typeof data == 'object' && !Array.isArray(data)) { const _errs1 = errors; for (const key0 in data) { - if (!func2.call(schema32.properties, key0)) { - validate30.errors = [ + if (!func2.call(schema36.properties, key0)) { + validate32.errors = [ { instancePath, schemaPath: '#/additionalProperties', @@ -6674,7 +7613,7 @@ function validate30( if (data.relativeUri !== undefined) { const _errs2 = errors; if (typeof data.relativeUri !== 'string') { - validate30.errors = [ + validate32.errors = [ { instancePath: instancePath + '/relativeUri', schemaPath: '#/properties/relativeUri/type', @@ -6693,7 +7632,7 @@ function validate30( if (data.scriptPath !== undefined) { const _errs4 = errors; if (typeof data.scriptPath !== 'string') { - validate30.errors = [ + validate32.errors = [ { instancePath: instancePath + '/scriptPath', schemaPath: '#/properties/scriptPath/type', @@ -6712,7 +7651,7 @@ function validate30( if (data.protocol !== undefined) { const _errs6 = errors; if (typeof data.protocol !== 'string') { - validate30.errors = [ + validate32.errors = [ { instancePath: instancePath + '/protocol', @@ -6734,7 +7673,7 @@ function validate30( let data3 = data.method; const _errs8 = errors; if (typeof data3 !== 'string') { - validate30.errors = [ + validate32.errors = [ { instancePath: instancePath + '/method', @@ -6758,7 +7697,7 @@ function validate30( data3 === 'DELETE' ) ) { - validate30.errors = [ + validate32.errors = [ { instancePath: instancePath + '/method', @@ -6766,7 +7705,7 @@ function validate30( '#/definitions/HTTPMethod/enum', keyword: 'enum', params: { - allowedValues: schema30.enum, + allowedValues: schema34.enum, }, message: 'must be equal to one of the allowed values', @@ -6795,7 +7734,7 @@ function validate30( typeof data4[key1] !== 'string' ) { - validate30.errors = [ + validate32.errors = [ { instancePath: instancePath + @@ -6827,7 +7766,7 @@ function validate30( } } } else { - validate30.errors = [ + validate32.errors = [ { instancePath: instancePath + @@ -7465,7 +8404,7 @@ function validate30( vErrors.push(err12); } errors++; - validate30.errors = vErrors; + validate32.errors = vErrors; return false; } else { errors = _errs18; @@ -7498,7 +8437,7 @@ function validate30( key4 ] !== 'string' ) { - validate30.errors = + validate32.errors = [ { instancePath: @@ -7533,7 +8472,7 @@ function validate30( } } } else { - validate30.errors = [ + validate32.errors = [ { instancePath: instancePath + @@ -7574,7 +8513,7 @@ function validate30( key5 ] !== 'string' ) { - validate30.errors = + validate32.errors = [ { instancePath: @@ -7610,7 +8549,7 @@ function validate30( } } } else { - validate30.errors = [ + validate32.errors = [ { instancePath: instancePath + @@ -7639,7 +8578,7 @@ function validate30( typeof data.code !== 'string' ) { - validate30.errors = [ + validate32.errors = [ { instancePath: instancePath + @@ -7671,7 +8610,7 @@ function validate30( } } } else { - validate30.errors = [ + validate32.errors = [ { instancePath, schemaPath: '#/type', @@ -7683,7 +8622,7 @@ function validate30( return false; } } - validate30.errors = vErrors; + validate32.errors = vErrors; return errors === 0; } function validate14( @@ -9042,7 +9981,7 @@ function validate14( 'enum', params: { allowedValues: - schema22 + schema23 .oneOf[3] .properties .method @@ -10915,7 +11854,7 @@ function validate14( keyword: 'enum', params: { allowedValues: - schema22 + schema23 .oneOf[9] .properties .ifAlreadyInstalled @@ -11581,7 +12520,7 @@ function validate14( keyword: 'enum', params: { allowedValues: - schema22 + schema23 .oneOf[10] .properties .ifAlreadyInstalled @@ -13531,7 +14470,7 @@ function validate14( ) { const _errs264 = errors; if ( - !validate28( + !validate30( data.request, { instancePath: @@ -13547,9 +14486,9 @@ function validate14( ) { vErrors = vErrors === null - ? validate28.errors + ? validate30.errors : vErrors.concat( - validate28.errors + validate30.errors ); errors = vErrors.length; } @@ -14737,7 +15676,7 @@ function validate14( ) { const _errs319 = errors; if ( - !validate30( + !validate32( data.options, { instancePath: @@ -14753,9 +15692,9 @@ function validate14( ) { vErrors = vErrors === null - ? validate30.errors + ? validate32.errors : vErrors.concat( - validate30.errors + validate32.errors ); errors = vErrors.length; } diff --git a/packages/playground/blueprints/public/blueprint-schema.json b/packages/playground/blueprints/public/blueprint-schema.json index a59a25460d..d4ed96661f 100644 --- a/packages/playground/blueprints/public/blueprint-schema.json +++ b/packages/playground/blueprints/public/blueprint-schema.json @@ -207,6 +207,9 @@ }, { "$ref": "#/definitions/UrlReference" + }, + { + "$ref": "#/definitions/BlueprintAssetReference" } ] }, @@ -339,6 +342,22 @@ "required": ["resource", "url"], "additionalProperties": false }, + "BlueprintAssetReference": { + "type": "object", + "properties": { + "resource": { + "type": "string", + "const": "blueprint-asset", + "description": "Identifies the file resource as a Blueprint Asset" + }, + "path": { + "type": "string", + "description": "The path to the file in the Blueprint package" + } + }, + "required": ["resource", "path"], + "additionalProperties": false + }, "StepDefinition": { "type": "object", "discriminator": { @@ -1291,6 +1310,12 @@ }, { "$ref": "#/definitions/DirectoryLiteralReference" + }, + { + "$ref": "#/definitions/VFSDirectoryReference" + }, + { + "$ref": "#/definitions/BlueprintAssetDirectoryReference" } ] }, @@ -1320,7 +1345,6 @@ }, "DirectoryLiteralReference": { "type": "object", - "additionalProperties": false, "properties": { "resource": { "type": "string", @@ -1334,21 +1358,94 @@ "type": "string" } }, - "required": ["files", "name", "resource"] + "required": ["resource", "files", "name"], + "additionalProperties": false }, "FileTree": { "type": "object", "additionalProperties": { - "anyOf": [ - { - "$ref": "#/definitions/FileTree" + "$ref": "#/definitions/FileContent" + }, + "properties": {} + }, + "FileContent": { + "anyOf": [ + { + "type": "object", + "properties": { + "BYTES_PER_ELEMENT": { + "type": "number" + }, + "buffer": { + "type": "object", + "properties": { + "byteLength": { + "type": "number" + } + }, + "required": ["byteLength"], + "additionalProperties": false + }, + "byteLength": { + "type": "number" + }, + "byteOffset": { + "type": "number" + }, + "length": { + "type": "number" + } }, - { - "type": ["object", "string"] + "required": [ + "BYTES_PER_ELEMENT", + "buffer", + "byteLength", + "byteOffset", + "length" + ], + "additionalProperties": { + "type": "number" } - ] + }, + { + "type": "string" + }, + { + "$ref": "#/definitions/FileTree" + } + ] + }, + "VFSDirectoryReference": { + "type": "object", + "properties": { + "resource": { + "type": "string", + "const": "vfs", + "description": "Identifies the file resource as Virtual File System (VFS)" + }, + "path": { + "type": "string", + "description": "The path to the file in the VFS" + } }, - "properties": {} + "required": ["resource", "path"], + "additionalProperties": false + }, + "BlueprintAssetDirectoryReference": { + "type": "object", + "properties": { + "resource": { + "type": "string", + "const": "blueprint-asset:directory", + "description": "Identifies the directory resource as a Blueprint Asset" + }, + "path": { + "type": "string", + "description": "The path to the directory in the Blueprint package" + } + }, + "required": ["resource", "path"], + "additionalProperties": false }, "InstallPluginOptions": { "type": "object", diff --git a/packages/playground/blueprints/src/index.ts b/packages/playground/blueprints/src/index.ts index 579fd0b317..1c0d863871 100644 --- a/packages/playground/blueprints/src/index.ts +++ b/packages/playground/blueprints/src/index.ts @@ -12,6 +12,8 @@ export type { OnStepCompleted, } from './lib/compile'; export type { + BlueprintAssetReference, + BlueprintAssetDirectoryReference, CachedResource, CorePluginReference, CorePluginResource, diff --git a/packages/playground/blueprints/src/lib/resources.ts b/packages/playground/blueprints/src/lib/resources.ts index e9524aa21a..505a4474ba 100644 --- a/packages/playground/blueprints/src/lib/resources.ts +++ b/packages/playground/blueprints/src/lib/resources.ts @@ -2,8 +2,8 @@ import { cloneResponseMonitorProgress, ProgressTracker, } from '@php-wasm/progress'; -import { FileTree, UniversalPHP } from '@php-wasm/universal'; -import { dirname, Semaphore } from '@php-wasm/util'; +import { FileTree, FileTreeAsync, UniversalPHP } from '@php-wasm/universal'; +import { dirname, joinPaths, Semaphore } from '@php-wasm/util'; import { listDescendantFiles, listGitFiles, @@ -19,6 +19,8 @@ export const ResourceTypes = [ 'wordpress.org/plugins', 'url', 'git:directory', + 'blueprint-asset', + 'blueprint-asset:directory', ] as const; export type VFSReference = { @@ -27,6 +29,12 @@ export type VFSReference = { /** The path to the file in the VFS */ path: string; }; +export type VFSDirectoryReference = { + /** Identifies the file resource as Virtual File System (VFS) */ + resource: 'vfs'; + /** The path to the file in the VFS */ + path: string; +}; export type LiteralReference = { /** Identifies the file resource as a literal file */ resource: 'literal'; @@ -65,13 +73,28 @@ export type GitDirectoryReference = { /** The path to the directory in the git repository */ path: string; }; +export type BlueprintAssetReference = { + /** Identifies the file resource as a Blueprint Asset */ + resource: 'blueprint-asset'; + /** The path to the file in the Blueprint package */ + path: string; +}; +export type BlueprintAssetDirectoryReference = { + /** Identifies the directory resource as a Blueprint Asset */ + resource: 'blueprint-asset:directory'; + /** The path to the directory in the Blueprint package */ + path: string; +}; + export interface Directory { - files: FileTree; + files: FileTreeAsync; name: string; } -export type DirectoryLiteralReference = Directory & { +export type DirectoryLiteralReference = { /** Identifies the file resource as a git directory */ resource: 'literal:directory'; + files: FileTree; + name: string; }; export type FileReference = @@ -79,11 +102,14 @@ export type FileReference = | LiteralReference | CoreThemeReference | CorePluginReference - | UrlReference; + | UrlReference + | BlueprintAssetReference; export type DirectoryReference = | GitDirectoryReference - | DirectoryLiteralReference; + | DirectoryLiteralReference + | VFSDirectoryReference + | BlueprintAssetDirectoryReference; export function isResourceReference(ref: any): ref is FileReference { return ( @@ -96,6 +122,7 @@ export function isResourceReference(ref: any): ref is FileReference { export abstract class Resource { /** Optional progress tracker to monitor progress */ + // TODO: Why is this kept at the resource level and not just passed to resolve()? protected _progress?: ProgressTracker; get progress() { return this._progress; @@ -167,6 +194,12 @@ export abstract class Resource { case 'literal:directory': resource = new LiteralDirectoryResource(ref, progress); break; + case 'blueprint-asset': + resource = new BlueprintAssetResource(ref, progress); + break; + case 'blueprint-asset:directory': + resource = new BlueprintAssetDirectoryResource(ref, progress); + break; default: throw new Error(`Invalid resource: ${ref}`); } @@ -249,6 +282,107 @@ export class VFSResource extends Resource { } } +export class VFSDirectoryResource extends Resource { + constructor( + private reference: VFSReference, + public override _progress?: ProgressTracker + ) { + super(); + } + + async resolve() { + const name = this.name; + const files = await createLazyVFSFileTree( + this.reference.path, + this.playground! + ); + return { name, files }; + } + + /** @inheritDoc */ + get name() { + return this.reference.path.split('/').pop() || ''; + } +} + +export class BlueprintAssetResource extends VFSResource { + constructor( + private originalReference: BlueprintAssetReference, + public override _progress?: ProgressTracker + ) { + const vfsReference: VFSReference = { + resource: 'vfs', + path: joinPaths( + // TODO: This should be at least a constant if we're not using a path mapping function for Blueprint assets. + '/internal/shared/blueprint-assets', + originalReference.path + ), + }; + super(vfsReference, _progress); + } + + /** @inheritDoc */ + override get name() { + return this.originalReference.path.split('/').pop() || ''; + } +} + +export class BlueprintAssetDirectoryResource extends VFSDirectoryResource { + constructor( + private originalReference: BlueprintAssetDirectoryReference, + public override _progress?: ProgressTracker + ) { + const vfsReference: VFSReference = { + resource: 'vfs', + path: joinPaths( + // TODO: This should be at least a constant if we're not using a path mapping function for Blueprint assets. + '/internal/shared/blueprint-assets', + originalReference.path + ), + }; + super(vfsReference, _progress); + } + + /** @inheritDoc */ + override get name() { + return this.originalReference.path.split('/').pop() || ''; + } +} + +// TODO: Should we export this? +async function createLazyVFSFileTree( + path: string, + playground: UniversalPHP +): Promise { + const lazyFileTree: FileTreeAsync = {}; + + if (!(await playground.isDir(path))) { + throw new Error(`Path "${path}" is not a directory`); + } + + for (const fileName of await playground.listFiles(path)) { + Object.defineProperty(lazyFileTree, fileName, { + configurable: false, + enumerable: true, + async get() { + const fullPath = joinPaths(path, fileName); + + if (!(await playground.fileExists(fullPath))) { + return undefined; + } + + if (await playground.isDir(fullPath)) { + return createLazyVFSFileTree(fullPath, playground); + } else { + return playground.readFileAsBuffer(fullPath); + } + }, + }); + } + + return lazyFileTree; +} + /** * A `Resource` that represents a literal file. */ diff --git a/packages/playground/blueprints/src/lib/steps/install-plugin.spec.ts b/packages/playground/blueprints/src/lib/steps/install-plugin.spec.ts index f6d7ad1051..134ee9966f 100644 --- a/packages/playground/blueprints/src/lib/steps/install-plugin.spec.ts +++ b/packages/playground/blueprints/src/lib/steps/install-plugin.spec.ts @@ -68,6 +68,7 @@ describe('Blueprint step installPlugin', () => { let php: PHP; // Create plugins folder let rootPath = ''; + let pluginsPath = ''; let installedPluginPath = ''; const pluginName = 'test-plugin'; const zipFileName = `${pluginName}-0.0.1.zip`; @@ -80,8 +81,9 @@ describe('Blueprint step installPlugin', () => { php = await handler.getPrimaryPhp(); rootPath = php.documentRoot; - php.mkdir(`${rootPath}/wp-content/plugins`); - installedPluginPath = `${rootPath}/wp-content/plugins/${pluginName}`; + pluginsPath = `${rootPath}/wp-content/plugins`; + php.mkdir(pluginsPath); + installedPluginPath = `${pluginsPath}/${pluginName}`; }); it('should install a plugin', async () => { @@ -97,6 +99,23 @@ describe('Blueprint step installPlugin', () => { expect(php.fileExists(installedPluginPath)).toBe(true); }); + it('should install a single PHP file as a plugin', async () => { + const rawPluginContent = ` { // @ts-ignore await installPlugin(php, { diff --git a/packages/playground/blueprints/src/lib/steps/install-plugin.ts b/packages/playground/blueprints/src/lib/steps/install-plugin.ts index 4a8f72882d..5a97d19eb0 100644 --- a/packages/playground/blueprints/src/lib/steps/install-plugin.ts +++ b/packages/playground/blueprints/src/lib/steps/install-plugin.ts @@ -1,6 +1,7 @@ import { StepHandler } from '.'; import { InstallAssetOptions, installAsset } from './install-asset'; import { activatePlugin } from './activate-plugin'; +import { writeFile } from './write-file'; import { zipNameToHumanName } from '../utils/zip-name-to-human-name'; import { Directory } from '../resources'; import { joinPaths } from '@php-wasm/util'; @@ -99,31 +100,52 @@ export const installPlugin: StepHandler< ); } - const targetFolderName = 'targetFolderName' in options ? options.targetFolderName : ''; + const pluginsDirectoryPath = joinPaths( + await playground.documentRoot, + 'wp-content', + 'plugins' + ); + const targetFolderName = + 'targetFolderName' in options ? options.targetFolderName : ''; let assetFolderPath = ''; let assetNiceName = ''; if (pluginData instanceof File) { - // @TODO: Consider validating whether this is a zip file? - const zipFileName = pluginData.name.split('/').pop() || 'plugin.zip'; - assetNiceName = zipNameToHumanName(zipFileName); + if (pluginData.name.endsWith('.php')) { + const destinationFilePath = joinPaths( + pluginsDirectoryPath, + pluginData.name + ); + await writeFile(playground, { + path: destinationFilePath, + data: pluginData, + }); + assetFolderPath = pluginsDirectoryPath; + assetNiceName = pluginData.name; + } else { + // Assume any other file is a zip file + // @TODO: Consider validating whether this is a zip file? + const zipFileName = + pluginData.name.split('/').pop() || 'plugin.zip'; + assetNiceName = zipNameToHumanName(zipFileName); - progress?.tracker.setCaption(`Installing the ${assetNiceName} plugin`); - const assetResult = await installAsset(playground, { - ifAlreadyInstalled, - zipFile: pluginData, - targetPath: `${await playground.documentRoot}/wp-content/plugins`, - targetFolderName: targetFolderName - }); - assetFolderPath = assetResult.assetFolderPath; - assetNiceName = assetResult.assetFolderName; + progress?.tracker.setCaption( + `Installing the ${assetNiceName} plugin` + ); + const assetResult = await installAsset(playground, { + ifAlreadyInstalled, + zipFile: pluginData, + targetPath: `${await playground.documentRoot}/wp-content/plugins`, + targetFolderName: targetFolderName, + }); + assetFolderPath = assetResult.assetFolderPath; + assetNiceName = assetResult.assetFolderName; + } } else if (pluginData) { assetNiceName = pluginData.name; progress?.tracker.setCaption(`Installing the ${assetNiceName} plugin`); const pluginDirectoryPath = joinPaths( - await playground.documentRoot, - 'wp-content', - 'plugins', + pluginsDirectoryPath, targetFolderName || pluginData.name ); await writeFiles(playground, pluginDirectoryPath, pluginData.files, { diff --git a/packages/playground/cli/src/cli.ts b/packages/playground/cli/src/cli.ts index 9b2fd3b23e..9919345094 100644 --- a/packages/playground/cli/src/cli.ts +++ b/packages/playground/cli/src/cli.ts @@ -121,14 +121,31 @@ async function run() { } } if (args.blueprint !== undefined) { - const blueprintPath = path.resolve( - process.cwd(), - args.blueprint - ); + let blueprintPath = path.resolve(process.cwd(), args.blueprint); if (!fs.existsSync(blueprintPath)) { throw new Error('Blueprint file does not exist'); } + if (fs.lstatSync(blueprintPath).isDirectory()) { + const blueprintPackageDir = blueprintPath; + + blueprintPath = path.join( + blueprintPath, + 'META-INF', + 'blueprint.json' + ); + if (!fs.existsSync(blueprintPath)) { + throw new Error( + 'Blueprint file does not exist in the package' + ); + } + + args.mountBeforeInstall = args.mountBeforeInstall || []; + args.mountBeforeInstall.push( + `${blueprintPackageDir}:/internal/shared/blueprint-assets` + ); + } + const content = fs.readFileSync(blueprintPath, 'utf-8'); try { args.blueprint = JSON.parse(content);