Skip to content

Commit c23529b

Browse files
committed
Enable uploading tools schema
1 parent bf902a4 commit c23529b

File tree

5 files changed

+101
-2
lines changed

5 files changed

+101
-2
lines changed

packages/app/src/cli/models/extensions/extension-instance.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,9 @@ export class ExtensionInstance<TConfiguration extends BaseConfigType = BaseConfi
374374

375375
await this.build(options)
376376

377+
// Copy static assets after build completes
378+
await this.copyStaticAssets()
379+
377380
const bundleInputPath = joinPath(bundleDirectory, this.getOutputFolderId(outputId))
378381
await this.keepBuiltSourcemapsLocally(bundleInputPath)
379382
}
@@ -492,6 +495,16 @@ export class ExtensionInstance<TConfiguration extends BaseConfigType = BaseConfi
492495
return [...new Set(watchedFiles.map((file) => normalizePath(file)))]
493496
}
494497

498+
/**
499+
* Copy static assets from the extension directory to the output path
500+
* Used by both dev and deploy builds
501+
*/
502+
async copyStaticAssets() {
503+
if (this.specification.copyStaticAssets) {
504+
return this.specification.copyStaticAssets(this.configuration, this.directory, this.outputPath)
505+
}
506+
}
507+
495508
/**
496509
* Rescans imports for this extension and updates the cached import paths
497510
* Returns true if the imports changed

packages/app/src/cli/models/extensions/schemas.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ const NewExtensionPointSchema = zod.object({
4545
target: zod.string(),
4646
module: zod.string(),
4747
should_render: ShouldRenderSchema.optional(),
48+
tools: zod.string().optional(),
4849
metafields: zod.array(MetafieldSchema).optional(),
4950
default_placement: zod.string().optional(),
5051
urls: zod

packages/app/src/cli/models/extensions/specification.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ type UidStrategy = 'single' | 'dynamic' | 'uuid'
3838
export enum AssetIdentifier {
3939
ShouldRender = 'should_render',
4040
Main = 'main',
41+
Tools = 'tools',
4142
}
4243

4344
export interface Asset {
@@ -53,6 +54,49 @@ type BuildConfig =
5354
* Extension specification with all the needed properties and methods to load an extension.
5455
*/
5556
export interface ExtensionSpecification<TConfiguration extends BaseConfigType = BaseConfigType> {
57+
copyStaticAssets?: <
58+
TConfiguration extends {
59+
name?: string | undefined
60+
type?: string | undefined
61+
handle?: string | undefined
62+
uid?: string | undefined
63+
description?: string | undefined
64+
api_version?: string | undefined
65+
extension_points?: unknown
66+
capabilities?:
67+
| {
68+
network_access?: boolean | undefined
69+
block_progress?: boolean | undefined
70+
api_access?: boolean | undefined
71+
collect_buyer_consent?:
72+
| {sms_marketing?: boolean | undefined; customer_privacy?: boolean | undefined}
73+
| undefined
74+
iframe?: {sources?: string[] | undefined} | undefined
75+
}
76+
| undefined
77+
settings?:
78+
| {
79+
fields?:
80+
| {
81+
type: string
82+
name?: string | undefined
83+
description?: string | undefined
84+
key?: string | undefined
85+
required?: boolean | undefined
86+
default_value?: unknown
87+
validations?: unknown[] | undefined
88+
marketingActivityCreateUrl?: string | undefined
89+
marketingActivityDeleteUrl?: string | undefined
90+
}[]
91+
| undefined
92+
}
93+
| undefined
94+
},
95+
>(
96+
configuration: TConfiguration,
97+
directory: string,
98+
outputPath: string,
99+
) => Promise<void>
56100
identifier: string
57101
externalIdentifier: string
58102
externalName: string

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

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ import {loadLocalesConfig} from '../../../utilities/extensions/locales-configura
55
import {getExtensionPointTargetSurface} from '../../../services/dev/extension/utilities.js'
66
import {ExtensionInstance} from '../extension-instance.js'
77
import {err, ok, Result} from '@shopify/cli-kit/node/result'
8-
import {fileExists} from '@shopify/cli-kit/node/fs'
9-
import {joinPath} from '@shopify/cli-kit/node/path'
8+
import {copyFile, fileExists} from '@shopify/cli-kit/node/fs'
9+
import {joinPath, basename, dirname} from '@shopify/cli-kit/node/path'
1010
import {outputContent, outputToken} from '@shopify/cli-kit/node/output'
1111
import {zod} from '@shopify/cli-kit/node/schema'
1212

@@ -27,6 +27,7 @@ export interface BuildManifest {
2727
[key in AssetIdentifier]?: {
2828
filepath: string
2929
module?: string
30+
static?: boolean
3031
}
3132
}
3233
}
@@ -57,6 +58,15 @@ export const UIExtensionSchema = BaseSchema.extend({
5758
},
5859
}
5960
: null),
61+
...(targeting.tools
62+
? {
63+
[AssetIdentifier.Tools]: {
64+
filepath: `${config.handle}-${AssetIdentifier.Tools}-${basename(targeting.tools)}`,
65+
module: targeting.tools,
66+
static: true,
67+
},
68+
}
69+
: null),
6070
},
6171
}
6272

@@ -125,6 +135,11 @@ const uiExtensionSpec = createExtensionSpecification({
125135
return
126136
}
127137

138+
// Skip static assets - they are copied after esbuild completes in rebuildContext
139+
if (asset.static && asset.module) {
140+
return
141+
}
142+
128143
assets[identifier] = {
129144
identifier: identifier as AssetIdentifier,
130145
outputFileName: asset.filepath,
@@ -143,6 +158,29 @@ const uiExtensionSpec = createExtensionSpecification({
143158
...(assetsArray.length ? {assets: assetsArray} : {}),
144159
}
145160
},
161+
copyStaticAssets: async (config, directory, outputPath) => {
162+
if (!isRemoteDomExtension(config)) return
163+
const extensionPoints = (config.extension_points as NewExtensionPointSchemaType[]) || []
164+
165+
await Promise.all(
166+
extensionPoints.map((extensionPoint) => {
167+
if (!('build_manifest' in extensionPoint)) return Promise.resolve()
168+
169+
return Object.entries(
170+
(extensionPoint as NewExtensionPointSchemaType & {build_manifest: BuildManifest}).build_manifest.assets,
171+
).map(([_identifier, asset]) => {
172+
if (asset.static && asset.module) {
173+
const sourceFile = joinPath(directory, asset.module)
174+
const outputFilePath = joinPath(dirname(outputPath), asset.filepath)
175+
return copyFile(sourceFile, outputFilePath).catch((error) => {
176+
throw new Error(`Failed to copy static asset ${asset.module} to ${outputFilePath}: ${error.message}`)
177+
})
178+
}
179+
return Promise.resolve()
180+
})
181+
}),
182+
)
183+
},
146184
hasExtensionPointTarget: (config, requestedTarget) => {
147185
return (
148186
config.extension_points?.find((extensionPoint) => {

packages/app/src/cli/services/dev/app-events/app-watcher-esbuild.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,9 @@ export class ESBuildContextManager {
8080
if (!context) return
8181
await Promise.all(context.map((ctxt) => ctxt.rebuild()))
8282

83+
// Copy static assets after build completes
84+
await extension.copyStaticAssets()
85+
8386
// The default output path for a extension is now inside `.shopify/bundle/<ext_id>/dist`,
8487
// all extensions output need to be under the same directory so that it can all be zipped together.
8588

0 commit comments

Comments
 (0)