diff --git a/packages/app/src/cli/models/extensions/extension-instance.test.ts b/packages/app/src/cli/models/extensions/extension-instance.test.ts index 2adc27a7d1..d8d1ddce37 100644 --- a/packages/app/src/cli/models/extensions/extension-instance.test.ts +++ b/packages/app/src/cli/models/extensions/extension-instance.test.ts @@ -192,6 +192,77 @@ describe('build', async () => { }) }) + test('copies intent schema files when building UI extensions with intents', async () => { + await inTemporaryDirectory(async (tmpDir) => { + // Given + const intentsDir = joinPath(tmpDir, 'intents') + await mkdir(intentsDir) + await writeFile(joinPath(intentsDir, 'create-schema.json'), '{"type": "create"}') + await writeFile(joinPath(intentsDir, 'update-schema.json'), '{"type": "update"}') + + const distDir = joinPath(tmpDir, 'dist') + await mkdir(distDir) + + const extensionInstance = await testUIExtension({ + type: 'ui_extension', + directory: tmpDir, + configuration: { + name: 'test-ui-extension', + type: 'ui_extension', + api_version: '2025-10', + metafields: [], + capabilities: { + network_access: false, + api_access: false, + block_progress: false, + }, + extension_points: [ + { + target: 'EXTENSION::POINT::A', + module: './src/ExtensionPointA.js', + build_manifest: { + assets: { + main: { + filepath: 'test-ui-extension.js', + module: './src/ExtensionPointA.js', + }, + intents: [ + { + filepath: 'test-ui-extension-intent-create-app_intent-create-schema.json', + module: './intents/create-schema.json', + static: true, + }, + { + filepath: 'test-ui-extension-intent-update-app_intent-update-schema.json', + module: './intents/update-schema.json', + static: true, + }, + ], + }, + }, + }, + ], + }, + outputPath: joinPath(distDir, 'test-ui-extension.js'), + }) + + // When + await extensionInstance.copyStaticAssets() + + // Then + const createSchemaOutput = joinPath(distDir, 'test-ui-extension-intent-create-app_intent-create-schema.json') + const updateSchemaOutput = joinPath(distDir, 'test-ui-extension-intent-update-app_intent-update-schema.json') + + expect(fileExistsSync(createSchemaOutput)).toBe(true) + expect(fileExistsSync(updateSchemaOutput)).toBe(true) + + const createContent = await readFile(createSchemaOutput) + const updateContent = await readFile(updateSchemaOutput) + expect(createContent).toBe('{"type": "create"}') + expect(updateContent).toBe('{"type": "update"}') + }) + }) + test('does not copy shopify.extension.toml file when bundling theme extensions', async () => { await inTemporaryDirectory(async (tmpDir) => { // Given diff --git a/packages/app/src/cli/models/extensions/schemas.ts b/packages/app/src/cli/models/extensions/schemas.ts index 5fe195ff3e..9cda9fc526 100644 --- a/packages/app/src/cli/models/extensions/schemas.ts +++ b/packages/app/src/cli/models/extensions/schemas.ts @@ -47,6 +47,17 @@ const NewExtensionPointSchema = zod.object({ should_render: ShouldRenderSchema.optional(), tools: zod.string().optional(), instructions: zod.string().optional(), + intents: zod + .array( + zod.object({ + type: zod.string(), + action: zod.string(), + schema: zod.string(), + name: zod.string().optional(), + description: zod.string().optional(), + }), + ) + .optional(), metafields: zod.array(MetafieldSchema).optional(), default_placement: zod.string().optional(), urls: zod diff --git a/packages/app/src/cli/models/extensions/specification.ts b/packages/app/src/cli/models/extensions/specification.ts index d40a17dd11..2074198592 100644 --- a/packages/app/src/cli/models/extensions/specification.ts +++ b/packages/app/src/cli/models/extensions/specification.ts @@ -40,6 +40,7 @@ export enum AssetIdentifier { Main = 'main', Tools = 'tools', Instructions = 'instructions', + Intents = 'intents', } export interface Asset { diff --git a/packages/app/src/cli/models/extensions/specifications/ui_extension.test.ts b/packages/app/src/cli/models/extensions/specifications/ui_extension.test.ts index f74aa72e8c..de41987ff7 100644 --- a/packages/app/src/cli/models/extensions/specifications/ui_extension.test.ts +++ b/packages/app/src/cli/models/extensions/specifications/ui_extension.test.ts @@ -693,6 +693,260 @@ describe('ui_extension', async () => { ]) }) + test('build_manifest includes intents assets when intents are present', async () => { + const allSpecs = await loadLocalExtensionsSpecifications() + const specification = allSpecs.find((spec) => spec.identifier === 'ui_extension')! + const configuration = { + targeting: [ + { + target: 'EXTENSION::POINT::A', + module: './src/ExtensionPointA.js', + intents: [ + { + type: 'app_intent', + action: 'create', + schema: './intents/create-schema.json', + name: 'Create Intent', + description: 'Creates a new item', + }, + ], + }, + ], + api_version: '2023-01' as const, + name: 'UI Extension', + description: 'This is an ordinary test extension', + type: 'ui_extension', + handle: 'test-ui-extension', + capabilities: { + block_progress: false, + network_access: false, + api_access: false, + collect_buyer_consent: { + customer_privacy: true, + sms_marketing: false, + }, + iframe: { + sources: [], + }, + }, + settings: {}, + } + + // When + const parsed = specification.parseConfigurationObject(configuration) + if (parsed.state !== 'ok') { + throw new Error("Couldn't parse configuration") + } + + const got = parsed.data + + // Then + expect(got.extension_points).toStrictEqual([ + { + target: 'EXTENSION::POINT::A', + module: './src/ExtensionPointA.js', + metafields: [], + default_placement_reference: undefined, + capabilities: undefined, + preloads: {}, + build_manifest: { + assets: { + main: { + filepath: 'test-ui-extension.js', + module: './src/ExtensionPointA.js', + }, + intents: [ + { + filepath: 'test-ui-extension-intent-create-app_intent-create-schema.json', + module: './intents/create-schema.json', + static: true, + }, + ], + }, + }, + intents: [ + { + type: 'app_intent', + action: 'create', + schema: './intents/create-schema.json', + name: 'Create Intent', + description: 'Creates a new item', + }, + ], + urls: {}, + }, + ]) + }) + + test('build_manifest includes multiple intents assets when multiple intents are present', async () => { + const allSpecs = await loadLocalExtensionsSpecifications() + const specification = allSpecs.find((spec) => spec.identifier === 'ui_extension')! + const configuration = { + targeting: [ + { + target: 'EXTENSION::POINT::A', + module: './src/ExtensionPointA.js', + intents: [ + { + type: 'app_intent', + action: 'create', + schema: './intents/create-schema.json', + }, + { + type: 'app_intent', + action: 'update', + schema: './intents/update-schema.json', + }, + { + type: 'app_intent', + action: 'delete', + schema: './intents/delete-schema.json', + }, + ], + }, + ], + api_version: '2023-01' as const, + name: 'UI Extension', + description: 'This is an ordinary test extension', + type: 'ui_extension', + handle: 'test-ui-extension', + capabilities: { + block_progress: false, + network_access: false, + api_access: false, + collect_buyer_consent: { + customer_privacy: true, + sms_marketing: false, + }, + iframe: { + sources: [], + }, + }, + settings: {}, + } + + // When + const parsed = specification.parseConfigurationObject(configuration) + if (parsed.state !== 'ok') { + throw new Error("Couldn't parse configuration") + } + + const got = parsed.data + + // Then + expect(got.extension_points[0]?.build_manifest?.assets?.intents).toHaveLength(3) + expect(got.extension_points[0]?.build_manifest?.assets?.intents).toEqual([ + { + filepath: 'test-ui-extension-intent-create-app_intent-create-schema.json', + module: './intents/create-schema.json', + static: true, + }, + { + filepath: 'test-ui-extension-intent-update-app_intent-update-schema.json', + module: './intents/update-schema.json', + static: true, + }, + { + filepath: 'test-ui-extension-intent-delete-app_intent-delete-schema.json', + module: './intents/delete-schema.json', + static: true, + }, + ]) + }) + + test('build_manifest includes both intents and other assets when both are present', async () => { + const allSpecs = await loadLocalExtensionsSpecifications() + const specification = allSpecs.find((spec) => spec.identifier === 'ui_extension')! + const configuration = { + targeting: [ + { + target: 'EXTENSION::POINT::A', + module: './src/ExtensionPointA.js', + tools: './tools.json', + instructions: './instructions.md', + intents: [ + { + type: 'app_intent', + action: 'create', + schema: './intents/create-schema.json', + }, + ], + }, + ], + api_version: '2023-01' as const, + name: 'UI Extension', + description: 'This is an ordinary test extension', + type: 'ui_extension', + handle: 'test-ui-extension', + capabilities: { + block_progress: false, + network_access: false, + api_access: false, + collect_buyer_consent: { + customer_privacy: true, + sms_marketing: false, + }, + iframe: { + sources: [], + }, + }, + settings: {}, + } + + // When + const parsed = specification.parseConfigurationObject(configuration) + if (parsed.state !== 'ok') { + throw new Error("Couldn't parse configuration") + } + + const got = parsed.data + + // Then + expect(got.extension_points).toStrictEqual([ + { + target: 'EXTENSION::POINT::A', + module: './src/ExtensionPointA.js', + metafields: [], + default_placement_reference: undefined, + capabilities: undefined, + preloads: {}, + build_manifest: { + assets: { + main: { + filepath: 'test-ui-extension.js', + module: './src/ExtensionPointA.js', + }, + tools: { + filepath: 'test-ui-extension-tools-tools.json', + module: './tools.json', + static: true, + }, + instructions: { + filepath: 'test-ui-extension-instructions-instructions.md', + module: './instructions.md', + static: true, + }, + intents: [ + { + filepath: 'test-ui-extension-intent-create-app_intent-create-schema.json', + module: './intents/create-schema.json', + static: true, + }, + ], + }, + }, + intents: [ + { + type: 'app_intent', + action: 'create', + schema: './intents/create-schema.json', + }, + ], + urls: {}, + }, + ]) + }) + test('returns error if there is no targeting or extension_points', async () => { // Given const allSpecs = await loadLocalExtensionsSpecifications() @@ -852,6 +1106,189 @@ Please check the configuration in ${joinPath(tmpDir, 'shopify.extension.toml')}` }) }) + test('shows an error when an intent schema file is missing', async () => { + await inTemporaryDirectory(async (tmpDir) => { + await mkdir(joinPath(tmpDir, 'src')) + await touchFile(joinPath(tmpDir, 'src', 'ExtensionPointA.js')) + + const allSpecs = await loadLocalExtensionsSpecifications() + const specification = allSpecs.find((spec) => spec.identifier === 'ui_extension')! + + const configuration = { + targeting: [ + { + target: 'EXTENSION::POINT::A', + module: './src/ExtensionPointA.js', + intents: [ + { + type: 'app_intent', + action: 'create', + schema: './intents/create-schema.json', + }, + ], + }, + ], + api_version: '2023-01' as const, + name: 'UI Extension', + description: 'This is an ordinary test extension', + type: 'ui_extension', + handle: 'test-ui-extension', + capabilities: { + block_progress: false, + network_access: false, + api_access: false, + collect_buyer_consent: { + customer_privacy: true, + sms_marketing: false, + }, + iframe: { + sources: [], + }, + }, + settings: {}, + } + + const parsed = specification.parseConfigurationObject(configuration) + if (parsed.state !== 'ok') { + throw new Error("Couldn't parse configuration") + } + + const result = await specification.validate?.(parsed.data, joinPath(tmpDir, 'shopify.extension.toml'), tmpDir) + + const notFoundPath = joinPath(tmpDir, './intents/create-schema.json') + expect(result).toEqual( + err(`Couldn't find ${notFoundPath} + Please check the intent schema path for EXTENSION::POINT::A + +Please check the configuration in ${joinPath(tmpDir, 'shopify.extension.toml')}`), + ) + }) + }) + + test('shows multiple errors when multiple intent schema files are missing', async () => { + await inTemporaryDirectory(async (tmpDir) => { + await mkdir(joinPath(tmpDir, 'src')) + await touchFile(joinPath(tmpDir, 'src', 'ExtensionPointA.js')) + + const allSpecs = await loadLocalExtensionsSpecifications() + const specification = allSpecs.find((spec) => spec.identifier === 'ui_extension')! + + const configuration = { + targeting: [ + { + target: 'EXTENSION::POINT::A', + module: './src/ExtensionPointA.js', + intents: [ + { + type: 'app_intent', + action: 'create', + schema: './intents/create-schema.json', + }, + { + type: 'app_intent', + action: 'update', + schema: './intents/update-schema.json', + }, + ], + }, + ], + api_version: '2023-01' as const, + name: 'UI Extension', + description: 'This is an ordinary test extension', + type: 'ui_extension', + handle: 'test-ui-extension', + capabilities: { + block_progress: false, + network_access: false, + api_access: false, + collect_buyer_consent: { + customer_privacy: true, + sms_marketing: false, + }, + iframe: { + sources: [], + }, + }, + settings: {}, + } + + const parsed = specification.parseConfigurationObject(configuration) + if (parsed.state !== 'ok') { + throw new Error("Couldn't parse configuration") + } + + const result = await specification.validate?.(parsed.data, joinPath(tmpDir, 'shopify.extension.toml'), tmpDir) + + const notFoundPath1 = joinPath(tmpDir, './intents/create-schema.json') + const notFoundPath2 = joinPath(tmpDir, './intents/update-schema.json') + expect(result).toEqual( + err(`Couldn't find ${notFoundPath1} + Please check the intent schema path for EXTENSION::POINT::A + +Couldn't find ${notFoundPath2} + Please check the intent schema path for EXTENSION::POINT::A + +Please check the configuration in ${joinPath(tmpDir, 'shopify.extension.toml')}`), + ) + }) + }) + + test('succeeds when intent schema files exist', async () => { + await inTemporaryDirectory(async (tmpDir) => { + await mkdir(joinPath(tmpDir, 'src')) + await touchFile(joinPath(tmpDir, 'src', 'ExtensionPointA.js')) + + await mkdir(joinPath(tmpDir, 'intents')) + await writeFile(joinPath(tmpDir, 'intents', 'create-schema.json'), '{"schema": "content"}') + + const allSpecs = await loadLocalExtensionsSpecifications() + const specification = allSpecs.find((spec) => spec.identifier === 'ui_extension')! + + const configuration = { + targeting: [ + { + target: 'EXTENSION::POINT::A', + module: './src/ExtensionPointA.js', + intents: [ + { + type: 'app_intent', + action: 'create', + schema: './intents/create-schema.json', + }, + ], + }, + ], + api_version: '2023-01' as const, + name: 'UI Extension', + description: 'This is an ordinary test extension', + type: 'ui_extension', + handle: 'test-ui-extension', + capabilities: { + block_progress: false, + network_access: false, + api_access: false, + collect_buyer_consent: { + customer_privacy: true, + sms_marketing: false, + }, + iframe: { + sources: [], + }, + }, + settings: {}, + } + + const parsed = specification.parseConfigurationObject(configuration) + if (parsed.state !== 'ok') { + throw new Error("Couldn't parse configuration") + } + + const result = await specification.validate?.(parsed.data, joinPath(tmpDir, 'shopify.extension.toml'), tmpDir) + + expect(result).toStrictEqual(ok({})) + }) + }) + test('build_manifest includes both tools and instructions when both are present', async () => { const allSpecs = await loadLocalExtensionsSpecifications() const specification = allSpecs.find((spec) => spec.identifier === 'ui_extension')! diff --git a/packages/app/src/cli/models/extensions/specifications/ui_extension.ts b/packages/app/src/cli/models/extensions/specifications/ui_extension.ts index f1acb096c8..7dc368555c 100644 --- a/packages/app/src/cli/models/extensions/specifications/ui_extension.ts +++ b/packages/app/src/cli/models/extensions/specifications/ui_extension.ts @@ -31,6 +31,7 @@ export interface BuildManifest { [AssetIdentifier.ShouldRender]?: BuildAsset [AssetIdentifier.Tools]?: BuildAsset [AssetIdentifier.Instructions]?: BuildAsset + [AssetIdentifier.Intents]?: BuildAsset[] } } @@ -78,6 +79,17 @@ export const UIExtensionSchema = BaseSchema.extend({ }, } : null), + ...(targeting.intents + ? { + [AssetIdentifier.Intents]: targeting.intents.map((intent) => { + return { + filepath: `${config.handle}-intent-${intent.action}-${intent.type}-${basename(intent.schema)}`, + module: intent.schema, + static: true, + } + }), + } + : null), }, } @@ -92,6 +104,7 @@ export const UIExtensionSchema = BaseSchema.extend({ build_manifest: buildManifest, tools: targeting.tools, instructions: targeting.instructions, + ...(targeting.intents ? {intents: targeting.intents} : {}), } }) return {...config, extension_points: extensionPoints} @@ -162,14 +175,11 @@ const uiExtensionSpec = createExtensionSpecification({ if (!('build_manifest' in extensionPoint)) return [] return Object.entries(extensionPoint.build_manifest.assets).map(([_, asset]) => { - if (asset.static && asset.module) { - const sourceFile = joinPath(directory, asset.module) - const outputFilePath = joinPath(dirname(outputPath), asset.filepath) - return copyFile(sourceFile, outputFilePath).catch((error) => { - throw new Error(`Failed to copy static asset ${asset.module} to ${outputFilePath}: ${error.message}`) - }) + if (Array.isArray(asset)) { + return Promise.all(asset.map((childAsset) => copyAsset(childAsset, directory, outputPath))) } - return Promise.resolve() + + return copyAsset(asset, directory, outputPath) }) }), ) @@ -336,10 +346,15 @@ function addDistPathToAssets(extP: NewExtensionPointSchemaType & {build_manifest assets: Object.fromEntries( Object.entries(extP.build_manifest.assets).map(([key, value]) => [ key as AssetIdentifier, - { - ...value, - filepath: joinPath('dist', value.filepath), - }, + Array.isArray(value) + ? value.map((asset) => ({ + ...asset, + filepath: joinPath('dist', asset.filepath), + })) + : { + ...value, + filepath: joinPath('dist', value.filepath), + }, ]), ), }, @@ -403,6 +418,17 @@ async function validateUIExtensionPointConfig( errors.push(missingInstructionsError) } + // Validate intent schema files + const intentsAssets = buildManifest?.assets[AssetIdentifier.Intents] + if (Array.isArray(intentsAssets)) { + for await (const intentAsset of intentsAssets) { + const missingIntentError = await checkForMissingPath(directory, intentAsset.module, target, 'intent schema') + if (missingIntentError) { + errors.push(missingIntentError) + } + } + } + if (uniqueTargets.includes(target)) { duplicateTargets.push(target) } else { @@ -455,4 +481,15 @@ function buildShouldRenderAsset( } } +function copyAsset({module, filepath, static: isStatic}: BuildAsset, directory: string, outputPath: string) { + if (isStatic) { + const sourceFile = joinPath(directory, module) + const outputFilePath = joinPath(dirname(outputPath), filepath) + return copyFile(sourceFile, outputFilePath).catch((error) => { + throw new Error(`Failed to copy static asset ${module} to ${outputFilePath}: ${error.message}`) + }) + } + return Promise.resolve() +} + export default uiExtensionSpec diff --git a/packages/app/src/cli/services/dev/extension/payload.test.ts b/packages/app/src/cli/services/dev/extension/payload.test.ts index a01fce3af9..4ea6777a3d 100644 --- a/packages/app/src/cli/services/dev/extension/payload.test.ts +++ b/packages/app/src/cli/services/dev/extension/payload.test.ts @@ -338,6 +338,97 @@ describe('getUIExtensionPayload', () => { }) }) + test('returns the right payload for UI Extensions with intents in build_manifest', async () => { + await inTemporaryDirectory(async (tmpDir) => { + // Given + const outputPath = joinPath(tmpDir, 'test-ui-extension.js') + await touchFile(outputPath) + + const intentsDir = joinPath(tmpDir, 'intents') + await mkdir(intentsDir) + await writeFile(joinPath(intentsDir, 'create-schema.json'), '{"type": "object"}') + await writeFile(joinPath(intentsDir, 'update-schema.json'), '{"type": "object"}') + + const buildManifest = { + assets: { + main: {module: './src/ExtensionPointA.js', filepath: '/test-ui-extension.js'}, + intents: [ + { + module: './intents/create-schema.json', + filepath: '/test-ui-extension-intent-create-app_intent-create-schema.json', + static: true, + }, + { + module: './intents/update-schema.json', + filepath: '/test-ui-extension-intent-update-app_intent-update-schema.json', + static: true, + }, + ], + }, + } + + const uiExtension = await testUIExtension({ + outputPath, + directory: tmpDir, + configuration: { + name: 'test-ui-extension', + type: 'ui_extension', + extension_points: [ + { + target: 'CUSTOM_EXTENSION_POINT', + build_manifest: buildManifest, + intents: [ + {type: 'application/email', action: 'create', schema: './intents/create-schema.json'}, + {type: 'application/email', action: 'update', schema: './intents/update-schema.json'}, + ], + }, + ], + }, + devUUID: 'devUUID', + }) + + // When + const got = await getUIExtensionPayload(uiExtension, 'mock-bundle-path', { + ...createMockOptions(tmpDir, [uiExtension]), + currentDevelopmentPayload: {hidden: true, status: 'success'}, + }) + + // Then + expect(got.extensionPoints).toMatchObject([ + { + target: 'CUSTOM_EXTENSION_POINT', + assets: { + main: { + name: 'main', + url: 'http://tunnel-url.com/extensions/devUUID/assets/test-ui-extension.js', + lastUpdated: expect.any(Number), + }, + }, + intents: [ + { + type: 'application/email', + action: 'create', + schema: { + name: 'schema', + url: 'http://tunnel-url.com/extensions/devUUID/assets/test-ui-extension-intent-create-app_intent-create-schema.json', + lastUpdated: expect.any(Number), + }, + }, + { + type: 'application/email', + action: 'update', + schema: { + name: 'schema', + url: 'http://tunnel-url.com/extensions/devUUID/assets/test-ui-extension-intent-update-app_intent-update-schema.json', + lastUpdated: expect.any(Number), + }, + }, + ], + }, + ]) + }) + }) + test('returns the right payload for post-purchase extensions', async () => { await inTemporaryDirectory(async (tmpDir) => { // Given diff --git a/packages/app/src/cli/services/dev/extension/payload.ts b/packages/app/src/cli/services/dev/extension/payload.ts index b33279a7cb..e93e074ab8 100644 --- a/packages/app/src/cli/services/dev/extension/payload.ts +++ b/packages/app/src/cli/services/dev/extension/payload.ts @@ -6,7 +6,7 @@ import {getUIExtensionResourceURL} from '../../../utilities/extensions/configura import {getUIExtensionRendererVersion} from '../../../models/app/app.js' import {ExtensionInstance} from '../../../models/extensions/extension-instance.js' import {BuildManifest} from '../../../models/extensions/specifications/ui_extension.js' -import {BuildAsset} from '../../../models/extensions/specification.js' +import {AssetIdentifier, BuildAsset} from '../../../models/extensions/specification.js' import {NewExtensionPointSchemaType} from '../../../models/extensions/schemas.js' import {fileLastUpdatedTimestamp} from '@shopify/cli-kit/node/fs' import {useConcurrentOutputContext} from '@shopify/cli-kit/node/ui/components' @@ -19,7 +19,8 @@ export type GetUIExtensionPayloadOptions = Omit> { - const payload = await getAssetPayload(identifier, asset, url, extension) + const payload = Array.isArray(asset) + ? await Promise.all(asset.map((child) => getAssetPayload(child.module, child, url, extension))) + : [await getAssetPayload(identifier, asset, url, extension)] + return { - assets: {[payload.name]: payload}, + assets: Object.fromEntries(payload.map((payload) => [payload.name, payload])), } } +/** + * Intents asset mapper - transforms intents and places them at extension point level + */ +async function intentsAssetMapper({ + asset, + extensionPoint, + url, + extension, +}: AssetMapperContext): Promise> { + if (!extensionPoint.intents || !Array.isArray(asset)) return {} + + const intents = await Promise.all( + extensionPoint.intents.map(async (intent, index) => { + const intentAsset = asset[index] + if (!intentAsset) throw new Error(`Missing intent asset for ${intent.action}`) + return { + type: intent.type, + action: intent.action, + ...(intent.name && {name: intent.name}), + ...(intent.description && {description: intent.description}), + schema: await getAssetPayload('schema', intentAsset, url, extension), + } + }), + ) + + return {intents} +} + +/** + * Asset mappers registry - defines how each asset type should be handled + */ +const ASSET_MAPPERS: { + [key in AssetIdentifier]?: (context: AssetMapperContext) => Promise> +} = { + [AssetIdentifier.Intents]: intentsAssetMapper, +} + /** * Maps build manifest assets to payload format * Each mapper returns a partial that gets merged into the extension point @@ -172,7 +213,10 @@ async function mapBuildManifestToPayload( const mappingResults = await Promise.all( Object.entries(buildManifest.assets).map(async ([identifier, asset]) => { - return defaultAssetMapper({identifier, asset, url, extension}) + return ( + ASSET_MAPPERS[identifier as AssetIdentifier]?.({identifier, asset, extensionPoint, url, extension}) ?? + defaultAssetMapper({identifier, asset, extensionPoint, url, extension}) + ) }), ) diff --git a/packages/app/src/cli/services/dev/extension/payload/models.ts b/packages/app/src/cli/services/dev/extension/payload/models.ts index e132629e6f..5ee6a7079b 100644 --- a/packages/app/src/cli/services/dev/extension/payload/models.ts +++ b/packages/app/src/cli/services/dev/extension/payload/models.ts @@ -32,7 +32,7 @@ interface Asset { lastUpdated: number } -export interface DevNewExtensionPointSchema extends NewExtensionPointSchemaType { +export interface DevNewExtensionPointSchema extends Omit { build_manifest: BuildManifest assets: { [name: string]: Asset @@ -43,6 +43,13 @@ export interface DevNewExtensionPointSchema extends NewExtensionPointSchemaType resource: { url: string } + intents?: { + type: string + action: string + name?: string + description?: string + schema: Asset + }[] } export interface UIExtensionPayload {