Skip to content

Commit f17aee3

Browse files
committed
Standardize json reading
1 parent b0d1bdb commit f17aee3

2 files changed

Lines changed: 59 additions & 32 deletions

File tree

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

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,20 @@ interface GeneratedIntentResponseFallback<GeneratedResponse> {
239239
response?: NonNullable<GeneratedResponse>;
240240
}
241241
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+
242256
interface GeneratedIntentsConstraint<Intents> {
243257
intents?: Intents;
244258
}
@@ -254,11 +268,19 @@ interface GeneratedIntentsFallback {
254268
255269
type MergeGeneratedIntentResponse<Intents> =
256270
ShopifyGeneratedIntentVariants extends infer Generated
257-
? Generated extends GeneratedIntentResponseConstraint<infer GeneratedResponse>
258-
? Omit<Generated, 'response'> &
259-
(Intents extends GeneratedIntentResponseConstraint<infer BaseResponse>
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>
260281
? GeneratedIntentResponseOverride<BaseResponse, GeneratedResponse>
261-
: GeneratedIntentResponseFallback<GeneratedResponse>)
282+
: GeneratedIntentResponseFallback<GeneratedResponse>
283+
: unknown)
262284
: Generated
263285
: never;`)
264286

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

Lines changed: 33 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -286,21 +286,11 @@ const uiExtensionSpec = createExtensionSpecification({
286286
let toolsTypeDefinition = ''
287287
if (toolsDefinition) {
288288
try {
289-
const toolsFilePath = joinPath(extension.directory, toolsDefinition)
290-
if (await fileExists(toolsFilePath)) {
291-
// Read and parse the tools JSON file
292-
const toolsContent = await readFile(toolsFilePath)
293-
const tools = ToolsFileSchema.safeParse(JSON.parse(toolsContent))
294-
if (tools.success) {
295-
// Generate tools type definition
296-
toolsTypeDefinition = await createToolsTypeDefinition(tools.data)
297-
} else {
298-
outputWarn(
299-
`Invalid tools definition in "${toolsDefinition}": ${tools.error.issues
300-
.map((issue) => issue.message)
301-
.join(', ')}`,
302-
)
303-
}
289+
const tools = await readAndValidateJsonAsset(extension.directory, toolsDefinition, ToolsFileSchema)
290+
if (tools.status === 'ok') {
291+
toolsTypeDefinition = await createToolsTypeDefinition(tools.data)
292+
} else if (tools.status === 'invalid') {
293+
outputWarn(`Invalid tools definition in "${toolsDefinition}": ${tools.issues}`)
304294
}
305295
// eslint-disable-next-line no-catch-all/no-catch-all
306296
} catch (error) {
@@ -382,26 +372,41 @@ function addDistPathToAssets(extP: NewExtensionPointSchemaType & {build_manifest
382372
}
383373
}
384374

375+
type JsonAssetResult<T> = {status: 'ok'; data: T} | {status: 'missing'} | {status: 'invalid'; issues: string}
376+
377+
async function readAndValidateJsonAsset<T>(
378+
extensionDirectory: string,
379+
relativePath: string,
380+
schema: zod.ZodType<T>,
381+
): Promise<JsonAssetResult<T>> {
382+
const filePath = joinPath(extensionDirectory, relativePath)
383+
const exists = await fileExists(filePath)
384+
if (!exists) return {status: 'missing'}
385+
386+
const content = await readFile(filePath)
387+
const parsed = schema.safeParse(JSON.parse(content))
388+
if (!parsed.success) {
389+
return {
390+
status: 'invalid',
391+
issues: parsed.error.issues.map((issue) => issue.message).join(', '),
392+
}
393+
}
394+
395+
return {status: 'ok', data: parsed.data}
396+
}
397+
385398
async function parseIntentTypeDefinitions(
386399
extensionDirectory: string,
387400
intents: NonNullable<NewExtensionPointSchemaType['intents']>,
388401
): Promise<GeneratedIntentTypeDefinition[]> {
389402
const parsedIntentDefinitions = await Promise.all(
390403
intents.map(async (intent) => {
391404
try {
392-
const intentSchemaFilePath = joinPath(extensionDirectory, intent.schema)
393-
const schemaExists = await fileExists(intentSchemaFilePath)
394-
if (!schemaExists) return null
395-
396-
const intentSchemaContent = await readFile(intentSchemaFilePath)
397-
const intentSchema = IntentSchemaFileSchema.safeParse(JSON.parse(intentSchemaContent))
398-
399-
if (!intentSchema.success) {
400-
outputWarn(
401-
`Invalid intent schema in "${intent.schema}": ${intentSchema.error.issues
402-
.map((issue) => issue.message)
403-
.join(', ')}`,
404-
)
405+
const intentSchema = await readAndValidateJsonAsset(extensionDirectory, intent.schema, IntentSchemaFileSchema)
406+
if (intentSchema.status === 'missing') return null
407+
408+
if (intentSchema.status === 'invalid') {
409+
outputWarn(`Invalid intent schema in "${intent.schema}": ${intentSchema.issues}`)
405410
return null
406411
}
407412

0 commit comments

Comments
 (0)