Skip to content

Commit c77befd

Browse files
Fix CI: update test expectations, lint formatting, unexport AppAssetsConfig
Co-authored-by: Claude Code <claude-code@anthropic.com>
1 parent 8f59f38 commit c77befd

14 files changed

Lines changed: 73 additions & 73 deletions

File tree

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

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,6 @@ export interface AppInterface<
229229
realExtensions: ExtensionInstance[]
230230
nonConfigExtensions: ExtensionInstance[]
231231
draftableExtensions: ExtensionInstance[]
232-
appAssetsConfigs: Record<string, string> | undefined
233232
errors: AppErrors
234233
hiddenConfig: AppHiddenConfig
235234
includeConfigOnDeploy: boolean | undefined
@@ -335,15 +334,6 @@ export class App<
335334
)
336335
}
337336

338-
get appAssetsConfigs(): Record<string, string> | undefined {
339-
if (!this.realExtensions.some((ext) => ext.specification.appAssetsConfig)) return undefined
340-
return this.realExtensions.reduce<Record<string, string>>((acc, ext) => {
341-
const config = ext.specification.appAssetsConfig?.(ext.configuration)
342-
if (config) acc[config.assetsKey] = joinPath(this.directory, config.assetsDir)
343-
return acc
344-
}, {})
345-
}
346-
347337
setDevApplicationURLs(devApplicationURLs: ApplicationURLs) {
348338
this.patchAppConfiguration(devApplicationURLs)
349339
this.realExtensions.forEach((ext) => ext.patchWithAppDevURLs(devApplicationURLs))

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

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -144,18 +144,6 @@ export interface ExtensionSpecification<TConfiguration extends BaseConfigType =
144144
*/
145145
devSessionWatchConfig?: (extension: ExtensionInstance<TConfiguration>) => DevSessionWatchConfig | undefined
146146

147-
/**
148-
* App assets configuration for this extension.
149-
* Return undefined if this extension doesn't serve app assets.
150-
*/
151-
appAssetsConfig?: (config: TConfiguration) => AppAssetsConfig | undefined
152-
}
153-
154-
export interface AppAssetsConfig {
155-
/** The config key that points to the assets directory (e.g. 'admin.static_root') */
156-
assetsKey: string
157-
/** The assets directory relative to the extension directory */
158-
assetsDir: string
159147
}
160148

161149
export interface DevSessionWatchConfig {

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

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,12 @@ const AdminSchema = zod.object({
77
admin: zod
88
.object({
99
static_root: zod.string().optional(),
10+
allowed_domains: zod.array(zod.string()).optional(),
1011
})
1112
.optional(),
1213
})
1314

14-
type AdminConfigType = zod.infer<typeof AdminSchema> & BaseConfigType
15+
export type AdminConfigType = zod.infer<typeof AdminSchema> & BaseConfigType
1516

1617
const adminSpecificationSpec = createExtensionSpecification<AdminConfigType>({
1718
identifier: 'admin',
@@ -33,6 +34,8 @@ const adminSpecificationSpec = createExtensionSpecification<AdminConfigType>({
3334
admin: {
3435
// eslint-disable-next-line @typescript-eslint/no-explicit-any
3536
static_root: (remoteContent as any).admin.static_root,
37+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
38+
allowed_domains: (remoteContent as any).admin.allowed_domains,
3639
},
3740
}
3841
},
@@ -62,11 +65,7 @@ const adminSpecificationSpec = createExtensionSpecification<AdminConfigType>({
6265
},
6366
],
6467
appModuleFeatures: () => [],
65-
appAssetsConfig: (config) => {
66-
const dir = config.admin?.static_root
67-
if (!dir) return undefined
68-
return {assetsKey: 'staticRoot', assetsDir: dir}
69-
},
68+
7069
})
7170

7271
export default adminSpecificationSpec

packages/app/src/cli/services/build/steps/include-assets-step.test.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1082,10 +1082,7 @@ describe('executeIncludeAssetsStep', () => {
10821082

10831083
// When / Then — overwrites existing manifest.json
10841084
await expect(executeIncludeAssetsStep(step, contextWithConfig)).resolves.not.toThrow()
1085-
expect(fs.writeFile).toHaveBeenCalledWith(
1086-
'/test/output/manifest.json',
1087-
expect.any(String),
1088-
)
1085+
expect(fs.writeFile).toHaveBeenCalledWith('/test/output/manifest.json', expect.any(String))
10891086
})
10901087

10911088
test('writes an empty manifest when anchor resolves to a non-array value', async () => {

packages/app/src/cli/services/dev/extension.test.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,14 @@ describe('devUIExtensions()', () => {
3333

3434
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
3535
// @ts-ignore
36-
vi.spyOn(store, 'ExtensionsPayloadStore').mockImplementation(() => ({mock: 'payload-store'}))
36+
vi.spyOn(store, 'ExtensionsPayloadStore').mockImplementation(
37+
() =>
38+
({
39+
mock: 'payload-store',
40+
updateAdminConfigFromExtensionEvents: vi.fn(),
41+
getAppAssetDirectories: vi.fn(),
42+
}) as unknown as store.ExtensionsPayloadStore,
43+
)
3744
vi.spyOn(server, 'setupHTTPServer').mockReturnValue({
3845
mock: 'http-server',
3946
close: serverCloseSpy,
@@ -67,7 +74,7 @@ describe('devUIExtensions()', () => {
6774
// THEN
6875
expect(server.setupHTTPServer).toHaveBeenCalledWith({
6976
devOptions: {...options, websocketURL: 'wss://mock.url/extensions'},
70-
payloadStore: {mock: 'payload-store'},
77+
payloadStore: expect.objectContaining({mock: 'payload-store'}),
7178
getExtensions: expect.any(Function),
7279
})
7380
})
@@ -94,7 +101,7 @@ describe('devUIExtensions()', () => {
94101
expect(websocket.setupWebsocketConnection).toHaveBeenCalledWith({
95102
...options,
96103
httpServer: expect.objectContaining({mock: 'http-server'}),
97-
payloadStore: {mock: 'payload-store'},
104+
payloadStore: expect.objectContaining({mock: 'payload-store'}),
98105
websocketURL: 'wss://mock.url/extensions',
99106
})
100107
})

packages/app/src/cli/services/dev/extension.ts

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import {ExtensionInstance} from '../../models/extensions/extension-instance.js'
1212
import {AbortSignal} from '@shopify/cli-kit/node/abort'
1313
import {outputDebug} from '@shopify/cli-kit/node/output'
1414
import {DotEnvFile} from '@shopify/cli-kit/node/dot-env'
15-
import {getArrayRejectingUndefined} from '@shopify/cli-kit/common/array'
1615
import {Writable} from 'stream'
1716

1817
export interface ExtensionDevOptions {
@@ -114,10 +113,6 @@ export interface ExtensionDevOptions {
114113
*/
115114
appWatcher: AppEventWatcher
116115

117-
/**
118-
* Map of asset key to absolute directory path for app-level assets (e.g., admin static_root)
119-
*/
120-
appAssets?: Record<string, string>
121116
}
122117

123118
export async function devUIExtensions(options: ExtensionDevOptions): Promise<void> {
@@ -139,12 +134,10 @@ export async function devUIExtensions(options: ExtensionDevOptions): Promise<voi
139134
}
140135

141136
outputDebug(`Setting up the UI extensions HTTP server...`, payloadOptions.stdout)
142-
const getAppAssets = () => payloadOptions.appAssets
143137
const httpServer = setupHTTPServer({
144138
devOptions: payloadOptions,
145139
payloadStore,
146140
getExtensions,
147-
getAppAssets,
148141
})
149142

150143
outputDebug(`Setting up the UI extensions Websocket server...`, payloadOptions.stdout)
@@ -156,13 +149,7 @@ export async function devUIExtensions(options: ExtensionDevOptions): Promise<voi
156149
extensions = app.allExtensions.filter((ext) => ext.isPreviewable)
157150
}
158151

159-
// Handle App Assets updates.
160-
const appAssetsConfigs = extensionEvents.map((event) =>
161-
event.extension.specification.appAssetsConfig?.(event.extension.configuration),
162-
)
163-
getArrayRejectingUndefined(appAssetsConfigs).forEach((config) => {
164-
payloadStore.updateAppAssetTimestamp(config.assetsKey)
165-
})
152+
payloadStore.updateAdminConfigFromExtensionEvents(extensionEvents)
166153

167154
for (const event of extensionEvents) {
168155
if (!event.extension.isPreviewable) continue

packages/app/src/cli/services/dev/extension/payload/models.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ interface ExtensionsPayloadInterface {
88
url: string
99
mobileUrl: string
1010
title: string
11+
allowed_domains?: string[]
1112
assets?: {
1213
[key: string]: {
1314
url: string

packages/app/src/cli/services/dev/extension/payload/store.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ describe('getExtensionsPayloadStoreRawPayload()', () => {
5555
})
5656

5757
describe('ExtensionsPayloadStore()', () => {
58-
const mockOptions = {} as unknown as ExtensionsPayloadStoreOptions
58+
const mockOptions = {extensions: []} as unknown as ExtensionsPayloadStoreOptions
5959

6060
test('getRawPayload() returns the raw payload', async () => {
6161
// Given

packages/app/src/cli/services/dev/extension/payload/store.ts

Lines changed: 45 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@ import {ExtensionDevOptions} from '../../extension.js'
33
import {getUIExtensionPayload, isNewExtensionPointsSchema} from '../payload.js'
44
import {buildAppURLForMobile, buildAppURLForWeb} from '../../../../utilities/app/app-url.js'
55
import {ExtensionInstance} from '../../../../models/extensions/extension-instance.js'
6+
import {AdminConfigType} from '../../../../models/extensions/specifications/admin.js'
7+
import {ExtensionEvent} from '../../app-events/app-event-watcher.js'
8+
import {joinPath} from '@shopify/cli-kit/node/path'
69
import {deepMergeObjects} from '@shopify/cli-kit/common/object'
710
import {outputDebug, outputContent} from '@shopify/cli-kit/node/output'
811
import {EventEmitter} from 'events'
912

1013
export interface ExtensionsPayloadStoreOptions extends ExtensionDevOptions {
1114
websocketURL: string
12-
appAssets?: Record<string, string>
1315
}
1416

1517
export enum ExtensionsPayloadStoreEvent {
@@ -42,27 +44,49 @@ export async function getExtensionsPayloadStoreRawPayload(
4244
extensions: await Promise.all(options.extensions.map((ext) => getUIExtensionPayload(ext, bundlePath, options))),
4345
}
4446

45-
if (options.appAssets) {
46-
const assets: Record<string, {url: string; lastUpdated: number}> = {}
47-
for (const assetKey of Object.keys(options.appAssets)) {
48-
assets[assetKey] = {
49-
url: new URL(`/extensions/assets/${assetKey}/`, options.url).toString(),
50-
lastUpdated: Date.now(),
47+
// Admin extension contributes app-level config to the payload
48+
const adminExtension = options.extensions.find((ext) => ext.type === 'admin')
49+
if (adminExtension) {
50+
const adminConfig = (adminExtension.configuration as AdminConfigType).admin
51+
if (adminConfig?.allowed_domains) {
52+
payload.app.allowed_domains = adminConfig.allowed_domains
53+
}
54+
if (adminConfig?.static_root) {
55+
const assetKey = 'staticRoot'
56+
payload.app.assets = {
57+
[assetKey]: {
58+
url: new URL(`/extensions/assets/${assetKey}/`, options.url).toString(),
59+
lastUpdated: Date.now(),
60+
},
5161
}
5262
}
53-
payload.app.assets = assets
5463
}
64+
5565
return payload
5666
}
5767

5868
export class ExtensionsPayloadStore extends EventEmitter {
5969
private readonly options: ExtensionsPayloadStoreOptions
6070
private rawPayload: ExtensionsEndpointPayload
71+
private appAssetDirectories: Record<string, string> | undefined
6172

6273
constructor(rawPayload: ExtensionsEndpointPayload, options: ExtensionsPayloadStoreOptions) {
6374
super()
6475
this.rawPayload = rawPayload
6576
this.options = options
77+
78+
// Compute asset directory mapping from admin extension config
79+
const adminExtension = options.extensions.find((ext) => ext.type === 'admin')
80+
if (adminExtension) {
81+
const staticRoot = (adminExtension.configuration as AdminConfigType).admin?.static_root
82+
if (staticRoot) {
83+
this.appAssetDirectories = {staticRoot: joinPath(options.appDirectory, staticRoot)}
84+
}
85+
}
86+
}
87+
88+
getAppAssetDirectories(): Record<string, string> | undefined {
89+
return this.appAssetDirectories
6690
}
6791

6892
getConnectedPayload() {
@@ -183,12 +207,20 @@ export class ExtensionsPayloadStore extends EventEmitter {
183207
this.emitUpdate([extension.devUUID])
184208
}
185209

186-
updateAppAssetTimestamp(assetKey: string) {
187-
const asset = this.rawPayload.app.assets?.[assetKey]
188-
if (asset) {
189-
asset.lastUpdated = Date.now()
190-
this.emitUpdate([])
210+
updateAdminConfigFromExtensionEvents(extensionEvents: ExtensionEvent[]) {
211+
const adminEvent = extensionEvents.find((event) => event.extension.type === 'admin')
212+
if (!adminEvent) return
213+
214+
const adminConfig = (adminEvent.extension.configuration as AdminConfigType).admin
215+
this.rawPayload.app.allowed_domains = adminConfig?.allowed_domains
216+
217+
if (this.rawPayload.app.assets) {
218+
for (const key of Object.keys(this.rawPayload.app.assets)) {
219+
this.rawPayload.app.assets[key]!.lastUpdated = Date.now()
220+
}
191221
}
222+
223+
this.emitUpdate([])
192224
}
193225

194226
private emitUpdate(extensionIds: string[]) {

packages/app/src/cli/services/dev/extension/server.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ interface SetupHTTPServerOptions {
2020
devOptions: ExtensionsPayloadStoreOptions
2121
payloadStore: ExtensionsPayloadStore
2222
getExtensions: () => ExtensionInstance[]
23-
getAppAssets?: () => Record<string, string> | undefined
2423
}
2524

2625
export function setupHTTPServer(options: SetupHTTPServerOptions) {
@@ -30,8 +29,9 @@ export function setupHTTPServer(options: SetupHTTPServerOptions) {
3029
httpApp.use(getLogMiddleware(options))
3130
httpApp.use(corsMiddleware)
3231
httpApp.use(noCacheMiddleware)
33-
if (options.getAppAssets) {
34-
httpRouter.use('/extensions/assets/:assetKey/**:filePath', getAppAssetsMiddleware(options.getAppAssets))
32+
const getAppAssetDirectories = () => options.payloadStore.getAppAssetDirectories()
33+
if (getAppAssetDirectories()) {
34+
httpRouter.use('/extensions/assets/:assetKey/**:filePath', getAppAssetsMiddleware(getAppAssetDirectories))
3535
}
3636
httpRouter.use('/extensions/dev-console', devConsoleIndexMiddleware)
3737
httpRouter.use('/extensions/dev-console/assets/**:assetPath', devConsoleAssetsMiddleware)

0 commit comments

Comments
 (0)