Skip to content

Commit 1b607d9

Browse files
committed
Use types from ui-extensions
1 parent 3cbca04 commit 1b607d9

3 files changed

Lines changed: 44 additions & 126 deletions

File tree

packages/app/src/cli/models/extensions/specifications/type-generation.test.ts

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -55,17 +55,8 @@ interface CreateApplicationEmailIntentRequest {
5555
value?: CreateApplicationEmailIntentValue;
5656
}
5757
58-
interface ShopifyGeneratedIntentResponse<Data = unknown> {
59-
ok(data?: Data): Promise<void>;
60-
}
61-
62-
interface ShopifyGeneratedIntentsApi<Request = unknown, ResponseData = unknown> {
63-
request: Request;
64-
response?: ShopifyGeneratedIntentResponse<ResponseData>;
65-
}
66-
6758
type ShopifyGeneratedIntentVariants =
68-
| ShopifyGeneratedIntentsApi<CreateApplicationEmailIntentRequest, CreateApplicationEmailIntentOutput>
59+
| import('@shopify/ui-extensions/admin').ShopifyGeneratedIntentVariant<CreateApplicationEmailIntentRequest, CreateApplicationEmailIntentOutput>
6960
`)
7061
})
7162

@@ -111,9 +102,9 @@ type ShopifyGeneratedIntentVariants =
111102
expect(result).toContain('type CreateApplicationEmailIntentOutput = unknown')
112103
expect(result).toContain('interface EditShopifyProductIntentRequest')
113104
expect(result).toContain('type EditShopifyProductIntentValue = string')
114-
expect(result).toContain('response?: ShopifyGeneratedIntentResponse<ResponseData>;')
105+
expect(result).not.toContain('ShopifyGeneratedIntentsApi')
115106
expect(result).toContain(
116-
'ShopifyGeneratedIntentsApi<EditShopifyProductIntentRequest, EditShopifyProductIntentOutput>',
107+
"import('@shopify/ui-extensions/admin').ShopifyGeneratedIntentVariant<EditShopifyProductIntentRequest, EditShopifyProductIntentOutput>",
117108
)
118109
})
119110

packages/app/src/cli/models/extensions/specifications/type-generation.ts

Lines changed: 25 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {zod} from '@shopify/cli-kit/node/schema'
88
import {createRequire} from 'module'
99

1010
const require = createRequire(import.meta.url)
11+
const generatedTypesHelperImportPath = '@shopify/ui-extensions/admin'
1112

1213
export function parseApiVersion(apiVersion: string): {year: number; month: number} | null {
1314
const [year, month] = apiVersion.split('-')
@@ -197,99 +198,17 @@ function buildShopifyType(targets: string[], {includesTools, includesIntents}: S
197198
return baseShopifyType
198199
}
199200

200-
const wrappers = [
201-
...(includesIntents ? ['WithGeneratedIntents'] : []),
202-
...(includesTools ? ['WithGeneratedTools'] : []),
203-
]
204-
205-
return wrappers.reduce((shopifyType, wrapper) => `${wrapper}<${shopifyType}>`, baseShopifyType)
206-
}
207-
208-
function buildShopifyUtilityTypes({includesTools, includesIntents}: ShopifyTypeOptions): string {
209-
const utilityTypes: string[] = []
210-
211-
if (includesTools) {
212-
utilityTypes.push(`interface GeneratedToolsConstraint<Tools> {
213-
tools?: Tools;
214-
}
215-
216-
interface GeneratedToolsOverride<Tools> {
217-
tools: Omit<NonNullable<Tools>, 'register'> & ShopifyTools;
218-
}
219-
220-
interface GeneratedToolsFallback {
221-
tools: ShopifyTools;
222-
}
223-
224-
type WithGeneratedTools<T> = T extends GeneratedToolsConstraint<infer Tools>
225-
? Omit<T, 'tools'> & GeneratedToolsOverride<Tools>
226-
: T & GeneratedToolsFallback;`)
227-
}
201+
let shopifyType = baseShopifyType
228202

229203
if (includesIntents) {
230-
utilityTypes.push(`interface GeneratedIntentResponseConstraint<Response> {
231-
response?: Response;
232-
}
233-
234-
interface GeneratedIntentResponseOverride<BaseResponse, GeneratedResponse> {
235-
response?: Omit<NonNullable<BaseResponse>, 'ok'> & NonNullable<GeneratedResponse>;
236-
}
237-
238-
interface GeneratedIntentResponseFallback<GeneratedResponse> {
239-
response?: NonNullable<GeneratedResponse>;
240-
}
241-
242-
interface GeneratedIntentRequestConstraint<Request> {
243-
request: Request;
244-
}
245-
246-
type ReplaceSubscribableValue<Base, Value> = Base extends {value: unknown; subscribe: (callback: (value: infer _) => void) => () => void}
247-
? Omit<Base, 'value' | 'subscribe'> & {
248-
readonly value: Value;
249-
subscribe: (callback: (value: Value) => void) => () => void;
250-
}
251-
: {
252-
readonly value: Value;
253-
subscribe: (callback: (value: Value) => void) => () => void;
254-
};
255-
256-
interface GeneratedIntentsConstraint<Intents> {
257-
intents?: Intents;
258-
}
259-
260-
interface GeneratedIntentsOverride<Intents> {
261-
intents: Omit<NonNullable<Intents>, 'request' | 'response'> &
262-
MergeGeneratedIntentResponse<NonNullable<Intents>>;
263-
}
264-
265-
interface GeneratedIntentsFallback {
266-
intents: ShopifyGeneratedIntentVariants;
267-
}
204+
shopifyType = `import('${generatedTypesHelperImportPath}').WithGeneratedIntents<${shopifyType}, ShopifyGeneratedIntentVariants>`
205+
}
268206

269-
type MergeGeneratedIntentResponse<Intents> =
270-
ShopifyGeneratedIntentVariants extends infer Generated
271-
? Generated extends GeneratedIntentRequestConstraint<infer GeneratedRequest>
272-
? Omit<Generated, 'request' | 'response'> & {
273-
request: Intents extends GeneratedIntentRequestConstraint<infer BaseRequest>
274-
? ReplaceSubscribableValue<BaseRequest, GeneratedRequest | null>
275-
: {
276-
readonly value: GeneratedRequest | null;
277-
subscribe: (callback: (value: GeneratedRequest | null) => void) => () => void;
278-
};
279-
} & (Generated extends GeneratedIntentResponseConstraint<infer GeneratedResponse>
280-
? Intents extends GeneratedIntentResponseConstraint<infer BaseResponse>
281-
? GeneratedIntentResponseOverride<BaseResponse, GeneratedResponse>
282-
: GeneratedIntentResponseFallback<GeneratedResponse>
283-
: unknown)
284-
: Generated
285-
: never;`)
286-
287-
utilityTypes.push(`type WithGeneratedIntents<T> = T extends GeneratedIntentsConstraint<infer Intents>
288-
? Omit<T, 'intents'> & GeneratedIntentsOverride<Intents>
289-
: T & GeneratedIntentsFallback;`)
207+
if (includesTools) {
208+
shopifyType = `import('${generatedTypesHelperImportPath}').WithGeneratedTools<${shopifyType}, ShopifyTools>`
290209
}
291210

292-
return utilityTypes.join('\n\n')
211+
return shopifyType
293212
}
294213

295214
export function createTypeDefinition({
@@ -301,6 +220,9 @@ export function createTypeDefinition({
301220
intentsTypeDefinition,
302221
}: CreateTypeDefinitionOptions): string | null {
303222
try {
223+
const includesTools = Boolean(toolsTypeDefinition)
224+
const includesIntents = Boolean(intentsTypeDefinition)
225+
304226
// Validate that all targets can be resolved
305227
for (const target of targets) {
306228
try {
@@ -315,21 +237,27 @@ export function createTypeDefinition({
315237
}
316238
}
317239

318-
const relativePath = relativizePath(fullPath, dirname(typeFilePath))
319-
const includesTools = Boolean(toolsTypeDefinition)
320-
const includesIntents = Boolean(intentsTypeDefinition)
240+
if (includesTools || includesIntents) {
241+
try {
242+
require.resolve(generatedTypesHelperImportPath, {paths: [fullPath, typeFilePath]})
243+
} catch (_) {
244+
const {year, month} = parseApiVersion(apiVersion) ?? {year: 2025, month: 10}
245+
throw new AbortError(
246+
`Type reference for ${generatedTypesHelperImportPath} could not be found. You might be using the wrong @shopify/ui-extensions version.`,
247+
`Fix the error by ensuring you have the correct version of @shopify/ui-extensions, for example ~${year}.${month}.0, in your dependencies.`,
248+
)
249+
}
250+
}
321251

252+
const relativePath = relativizePath(fullPath, dirname(typeFilePath))
322253
const shopifyType = buildShopifyType(targets, {includesTools, includesIntents})
323254
if (!shopifyType) return null
324255

325-
const shopifyUtilityTypes = buildShopifyUtilityTypes({includesTools, includesIntents})
326-
327256
const lines = [
328257
'//@ts-ignore',
329258
`declare module './${relativePath}' {`,
330259
...(toolsTypeDefinition ? [toolsTypeDefinition] : []),
331260
...(intentsTypeDefinition ? [intentsTypeDefinition] : []),
332-
...(shopifyUtilityTypes ? [shopifyUtilityTypes] : []),
333261
` const shopify: ${shopifyType};`,
334262
' const globalThis: { shopify: typeof shopify };',
335263
'}',
@@ -453,20 +381,15 @@ export async function createIntentsTypeDefinition(intents: IntentTypeDefinition[
453381

454382
const generatedIntents = types
455383
.map(({requestTypeName, outputTypeName}) => {
456-
return ` | ShopifyGeneratedIntentsApi<${requestTypeName}, ${outputTypeName}>`
384+
return ` | import('${generatedTypesHelperImportPath}').ShopifyGeneratedIntentVariant<${requestTypeName}, ${outputTypeName}>`
457385
})
458386
.join('\n')
459387

460388
return `${types
461389
.map(
462390
({inputType, valueType, outputType, requestType}) => `${inputType}\n${valueType}\n${outputType}\n${requestType}`,
463391
)
464-
.join('\n\n')}\n\ninterface ShopifyGeneratedIntentResponse<Data = unknown> {
465-
ok(data?: Data): Promise<void>;
466-
}\n\ninterface ShopifyGeneratedIntentsApi<Request = unknown, ResponseData = unknown> {
467-
request: Request;
468-
response?: ShopifyGeneratedIntentResponse<ResponseData>;
469-
}\n\ntype ShopifyGeneratedIntentVariants =\n${generatedIntents}\n`
392+
.join('\n\n')}\n\ntype ShopifyGeneratedIntentVariants =\n${generatedIntents}\n`
470393
}
471394

472395
/**
@@ -514,7 +437,7 @@ export async function createToolsTypeDefinition(tools: ToolDefinition[]): Promis
514437
.split('\n')
515438
.map((line) => ` * ${line}`)
516439
.join('\n')
517-
return ` /**\n${formattedDescription}\n */\n register(name: '${name}', handler: (input: ${inputTypeName}) => ${outputTypeName} | Promise<${outputTypeName}>);`
440+
return ` /**\n${formattedDescription}\n */\n register(name: '${name}', handler: (input: ${inputTypeName}) => ${outputTypeName} | Promise<${outputTypeName}>): () => void;`
518441
})
519442
.join('\n')
520443

packages/app/src/cli/models/extensions/specifications/ui_extension.test.ts

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1195,6 +1195,10 @@ Please check the configuration in ${uiExtension.configurationPath}`),
11951195
const nodeModulesPath = joinPath(tmpDir, 'node_modules', '@shopify', 'ui-extensions')
11961196
await mkdir(nodeModulesPath)
11971197

1198+
const generatedTypesHelperPath = joinPath(nodeModulesPath, 'admin')
1199+
await mkdir(generatedTypesHelperPath)
1200+
await writeFile(joinPath(generatedTypesHelperPath, 'index.js'), '// Mock generated types helper exports')
1201+
11981202
const targetPath = joinPath(nodeModulesPath, target)
11991203
await mkdir(targetPath)
12001204
await writeFile(joinPath(targetPath, 'index.js'), '// Mock UI extension target')
@@ -2063,8 +2067,8 @@ Please check the configuration in ${uiExtension.configurationPath}`),
20632067
expect(typeDefinition).toContain('interface SearchProductsInput')
20642068
expect(typeDefinition).toContain('interface SearchProductsOutput')
20652069
expect(typeDefinition).toContain("name: 'search_products'")
2066-
expect(typeDefinition).toContain('interface GeneratedToolsConstraint<Tools>')
2067-
expect(typeDefinition).toContain("tools: Omit<NonNullable<Tools>, 'register'> & ShopifyTools")
2070+
expect(typeDefinition).toContain("import('@shopify/ui-extensions/admin').WithGeneratedTools<")
2071+
expect(typeDefinition).not.toContain('interface GeneratedToolsConstraint<Tools>')
20682072
})
20692073
})
20702074

@@ -2284,7 +2288,7 @@ Please check the configuration in ${uiExtension.configurationPath}`),
22842288
// Entry point should have ShopifyTools
22852289
const entryPointType = types.find((t) => t.includes('./src/index.jsx'))
22862290
expect(entryPointType).toContain('ShopifyTools')
2287-
expect(entryPointType).toContain('tools: ShopifyTools')
2291+
expect(entryPointType).toContain("import('@shopify/ui-extensions/admin').WithGeneratedTools<")
22882292

22892293
// Imported file should NOT have ShopifyTools
22902294
const helperType = types.find((t) => t.includes('./src/utils/helper.js'))
@@ -2342,14 +2346,13 @@ Please check the configuration in ${uiExtension.configurationPath}`),
23422346
expect(typeDefinition).toContain('interface CreateApplicationEmailIntentRequest')
23432347
expect(typeDefinition).toContain(`action: 'create';`)
23442348
expect(typeDefinition).toContain(`type: 'application/email';`)
2345-
expect(typeDefinition).toContain('interface ShopifyGeneratedIntentResponse<Data = unknown>')
2346-
expect(typeDefinition).toContain('interface ShopifyGeneratedIntentsApi<')
2347-
expect(typeDefinition).toContain('Request = unknown,')
2348-
expect(typeDefinition).toContain('ResponseData = unknown,')
2349-
expect(typeDefinition).toContain('type ShopifyGeneratedIntentVariants = ShopifyGeneratedIntentsApi<')
2350-
expect(typeDefinition).toContain('CreateApplicationEmailIntentRequest,')
2349+
expect(typeDefinition).not.toContain('interface ShopifyGeneratedIntentResponse<Data = unknown>')
2350+
expect(typeDefinition).not.toContain('interface ShopifyGeneratedIntentsApi<')
2351+
expect(typeDefinition).toContain('type ShopifyGeneratedIntentVariants =')
2352+
expect(typeDefinition).toContain("import('@shopify/ui-extensions/admin').ShopifyGeneratedIntentVariant<")
2353+
expect(typeDefinition).toContain('CreateApplicationEmailIntentRequest')
23512354
expect(typeDefinition).toContain('CreateApplicationEmailIntentOutput')
2352-
expect(typeDefinition).toContain('WithGeneratedIntents<')
2355+
expect(typeDefinition).toContain("import('@shopify/ui-extensions/admin').WithGeneratedIntents<")
23532356
})
23542357
})
23552358

@@ -2400,11 +2403,12 @@ Please check the configuration in ${uiExtension.configurationPath}`),
24002403
expect(types).toHaveLength(2)
24012404

24022405
const entryPointType = types.find((t) => t.includes('./src/index.jsx'))
2403-
expect(entryPointType).toContain('ShopifyGeneratedIntentsApi')
2406+
expect(entryPointType).toContain('ShopifyGeneratedIntentVariants')
24042407
expect(entryPointType).toContain('CreateApplicationEmailIntentRequest')
2408+
expect(entryPointType).toContain("import('@shopify/ui-extensions/admin').WithGeneratedIntents<")
24052409

24062410
const helperType = types.find((t) => t.includes('./src/utils/helper.js'))
2407-
expect(helperType).not.toContain('ShopifyGeneratedIntentsApi')
2411+
expect(helperType).not.toContain('ShopifyGeneratedIntentVariants')
24082412
expect(helperType).not.toContain('CreateApplicationEmailIntentRequest')
24092413
})
24102414
})

0 commit comments

Comments
 (0)