Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions packages/app/src/cli/models/extensions/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,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
Expand Down
1 change: 1 addition & 0 deletions packages/app/src/cli/models/extensions/specification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export enum AssetIdentifier {
Main = 'main',
Tools = 'tools',
Instructions = 'instructions',
Intents = 'intents',
}

export interface Asset {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,64 +77,6 @@ describe('ui_extension', async () => {
})
}

async function setupToolsExtension(
tmpDir: string,
options: {tools?: string; instructions?: string; createFiles?: boolean} = {},
) {
await mkdir(joinPath(tmpDir, 'src'))
await touchFile(joinPath(tmpDir, 'src', 'ExtensionPointA.js'))

if (options.createFiles) {
if (options.tools) {
await writeFile(joinPath(tmpDir, options.tools), '{"tools": []}')
}
if (options.instructions) {
await writeFile(joinPath(tmpDir, options.instructions), '# Instructions')
}
}

const allSpecs = await loadLocalExtensionsSpecifications()
const specification = allSpecs.find((spec) => spec.identifier === 'ui_extension')!

const targetConfig: any = {
target: 'EXTENSION::POINT::A',
module: './src/ExtensionPointA.js',
}

if (options.tools) targetConfig.tools = options.tools
if (options.instructions) targetConfig.instructions = options.instructions

const configuration = {
targeting: [targetConfig],
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)
return {result, tmpDir}
}

describe('validate()', () => {
test('returns ok({}) if there are no errors', async () => {
await inTemporaryDirectory(async (tmpDir) => {
Expand Down Expand Up @@ -208,6 +150,7 @@ describe('ui_extension', async () => {
target: 'EXTENSION::POINT::A',
tools: undefined,
instructions: undefined,
intents: undefined,
module: './src/ExtensionPointA.js',
metafields: [{namespace: 'test', key: 'test'}],
default_placement_reference: undefined,
Expand Down Expand Up @@ -275,6 +218,7 @@ describe('ui_extension', async () => {
target: 'EXTENSION::POINT::A',
tools: undefined,
instructions: undefined,
intents: undefined,
module: './src/ExtensionPointA.js',
metafields: [],
default_placement_reference: 'PLACEMENT_REFERENCE1',
Expand Down Expand Up @@ -338,6 +282,7 @@ describe('ui_extension', async () => {
target: 'EXTENSION::POINT::A',
tools: undefined,
instructions: undefined,
intents: undefined,
module: './src/ExtensionPointA.js',
metafields: [],
urls: {},
Expand Down Expand Up @@ -401,6 +346,7 @@ describe('ui_extension', async () => {
target: 'EXTENSION::POINT::A',
tools: undefined,
instructions: undefined,
intents: undefined,
module: './src/ExtensionPointA.js',
metafields: [],
default_placement_reference: undefined,
Expand Down Expand Up @@ -467,6 +413,7 @@ describe('ui_extension', async () => {
target: 'EXTENSION::POINT::A',
tools: undefined,
instructions: undefined,
intents: undefined,
module: './src/ExtensionPointA.js',
metafields: [],
default_placement_reference: undefined,
Expand Down Expand Up @@ -535,6 +482,7 @@ describe('ui_extension', async () => {
target: 'EXTENSION::POINT::A',
tools: undefined,
instructions: undefined,
intents: undefined,
module: './src/ExtensionPointA.js',
metafields: [],
default_placement_reference: undefined,
Expand Down Expand Up @@ -603,6 +551,7 @@ describe('ui_extension', async () => {
module: './src/ExtensionPointA.js',
tools: './tools.json',
instructions: undefined,
intents: undefined,
metafields: [],
default_placement_reference: undefined,
capabilities: undefined,
Expand All @@ -613,11 +562,6 @@ describe('ui_extension', async () => {
filepath: 'test-ui-extension.js',
module: './src/ExtensionPointA.js',
},
tools: {
filepath: 'test-ui-extension-tools-tools.json',
module: './tools.json',
static: true,
},
},
},
urls: {},
Expand Down Expand Up @@ -671,6 +615,7 @@ describe('ui_extension', async () => {
module: './src/ExtensionPointA.js',
tools: undefined,
instructions: './instructions.md',
intents: undefined,
metafields: [],
default_placement_reference: undefined,
capabilities: undefined,
Expand All @@ -681,11 +626,6 @@ describe('ui_extension', async () => {
filepath: 'test-ui-extension.js',
module: './src/ExtensionPointA.js',
},
instructions: {
filepath: 'test-ui-extension-instructions-instructions.md',
module: './instructions.md',
static: true,
},
},
},
urls: {},
Expand Down Expand Up @@ -791,67 +731,6 @@ Please check the configuration in ${uiExtension.configurationPath}`),
})
})

test('shows an error when the tools file is missing', async () => {
await inTemporaryDirectory(async (tmpDir) => {
const {result} = await setupToolsExtension(tmpDir, {tools: './tools.json'})

const notFoundPath = joinPath(tmpDir, './tools.json')
expect(result).toEqual(
err(`Couldn't find ${notFoundPath}
Please check the tools path for EXTENSION::POINT::A

Please check the configuration in ${joinPath(tmpDir, 'shopify.extension.toml')}`),
)
})
})

test('shows an error when the instructions file is missing', async () => {
await inTemporaryDirectory(async (tmpDir) => {
const {result} = await setupToolsExtension(tmpDir, {instructions: './instructions.md'})

const notFoundPath = joinPath(tmpDir, './instructions.md')
expect(result).toEqual(
err(`Couldn't find ${notFoundPath}
Please check the instructions path for EXTENSION::POINT::A

Please check the configuration in ${joinPath(tmpDir, 'shopify.extension.toml')}`),
)
})
})

test('shows multiple errors when both tools and instructions files are missing', async () => {
await inTemporaryDirectory(async (tmpDir) => {
const {result} = await setupToolsExtension(tmpDir, {
tools: './tools.json',
instructions: './instructions.md',
})

const toolsNotFoundPath = joinPath(tmpDir, './tools.json')
const instructionsNotFoundPath = joinPath(tmpDir, './instructions.md')
expect(result).toEqual(
err(`Couldn't find ${toolsNotFoundPath}
Please check the tools path for EXTENSION::POINT::A

Couldn't find ${instructionsNotFoundPath}
Please check the instructions path for EXTENSION::POINT::A

Please check the configuration in ${joinPath(tmpDir, 'shopify.extension.toml')}`),
)
})
})

test('succeeds when both tools and instructions files exist', async () => {
await inTemporaryDirectory(async (tmpDir) => {
const {result} = await setupToolsExtension(tmpDir, {
tools: './tools.json',
instructions: './instructions.md',
createFiles: true,
})

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')!
Expand Down Expand Up @@ -899,6 +778,7 @@ Please check the configuration in ${joinPath(tmpDir, 'shopify.extension.toml')}`
module: './src/ExtensionPointA.js',
tools: './tools.json',
instructions: './instructions.md',
intents: undefined,
metafields: [],
default_placement_reference: undefined,
capabilities: undefined,
Expand All @@ -909,16 +789,6 @@ Please check the configuration in ${joinPath(tmpDir, 'shopify.extension.toml')}`
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,
},
},
},
urls: {},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {ExtensionInstance} from '../extension-instance.js'
import {formatContent} from '../../../utilities/file-formatter.js'
import {err, ok, Result} from '@shopify/cli-kit/node/result'
import {copyFile, fileExists, readFile} from '@shopify/cli-kit/node/fs'
import {joinPath, basename, dirname} from '@shopify/cli-kit/node/path'
import {joinPath, dirname} from '@shopify/cli-kit/node/path'
import {outputContent, outputToken, outputWarn} from '@shopify/cli-kit/node/output'
import {zod} from '@shopify/cli-kit/node/schema'

Expand All @@ -29,8 +29,6 @@ export interface BuildManifest {
// Main asset is always required
[AssetIdentifier.Main]: BuildAsset
[AssetIdentifier.ShouldRender]?: BuildAsset
[AssetIdentifier.Tools]?: BuildAsset
[AssetIdentifier.Instructions]?: BuildAsset
}
}

Expand Down Expand Up @@ -61,24 +59,6 @@ export const UIExtensionSchema = BaseSchema.extend({
},
}
: null),
...(targeting.tools
? {
[AssetIdentifier.Tools]: {
filepath: `${config.handle}-${AssetIdentifier.Tools}-${basename(targeting.tools)}`,
module: targeting.tools,
static: true,
},
}
: null),
...(targeting.instructions
? {
[AssetIdentifier.Instructions]: {
filepath: `${config.handle}-${AssetIdentifier.Instructions}-${basename(targeting.instructions)}`,
module: targeting.instructions,
static: true,
},
}
: null),
},
}

Expand All @@ -93,6 +73,7 @@ export const UIExtensionSchema = BaseSchema.extend({
build_manifest: buildManifest,
tools: targeting.tools,
instructions: targeting.instructions,
intents: targeting.intents,
}
})
return {...config, extension_points: extensionPoints}
Expand All @@ -108,8 +89,35 @@ const uiExtensionSpec = createExtensionSpecification({
{
lifecycle: 'deploy',
steps: [
{id: 'bundle-ui', name: 'Bundle UI Extension', type: 'bundle_ui', config: {}},
{id: 'copy-static-assets', name: 'Copy Static Assets', type: 'copy_static_assets', config: {}},
{id: 'bundle-ui', generatesAssetsManifest: true, name: 'Bundle UI Extension', type: 'bundle_ui', config: {}},
{
id: 'include-ui-extension-assets',
name: 'Include UI Extension Assets',
type: 'include_assets',
config: {
generatesAssetsManifest: true,
inclusions: [
{
type: 'configKey',
anchor: 'extension_points[]',
groupBy: 'target',
key: 'extension_points[].tools',
},
{
type: 'configKey',
anchor: 'extension_points[]',
groupBy: 'target',
key: 'extension_points[].instructions',
},
{
type: 'configKey',
anchor: 'extension_points[]',
groupBy: 'target',
key: 'extension_points[].intents[].schema',
},
],
},
},
],
},
],
Expand Down Expand Up @@ -388,33 +396,13 @@ async function validateUIExtensionPointConfig(
}

for await (const extensionPoint of extensionPoints) {
const {module, target, build_manifest: buildManifest} = extensionPoint
const {module, target} = extensionPoint

const missingModuleError = await checkForMissingPath(directory, module, target, 'module')
if (missingModuleError) {
errors.push(missingModuleError)
}

const missingToolsError = await checkForMissingPath(
directory,
buildManifest?.assets[AssetIdentifier.Tools]?.module,
target,
AssetIdentifier.Tools,
)
if (missingToolsError) {
errors.push(missingToolsError)
}

const missingInstructionsError = await checkForMissingPath(
directory,
buildManifest?.assets[AssetIdentifier.Instructions]?.module,
target,
AssetIdentifier.Instructions,
)
if (missingInstructionsError) {
errors.push(missingInstructionsError)
}

if (uniqueTargets.includes(target)) {
duplicateTargets.push(target)
} else {
Expand Down
Loading
Loading