Skip to content

Commit 91cd896

Browse files
Merge pull request #6484 from Shopify/fix-default-function-output-path
Fix default output build path for functions
2 parents 02bfea7 + a070a07 commit 91cd896

File tree

6 files changed

+179
-19
lines changed

6 files changed

+179
-19
lines changed

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

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -551,4 +551,59 @@ describe('draftMessages', async () => {
551551
expect(extensionInstance.uid).toBe('orders/delete::123::https://my-app.com/webhooks')
552552
})
553553
})
554+
555+
describe('outputPath for function extensions', async () => {
556+
test('uses default path when build is undefined', async () => {
557+
// Given
558+
const config = {
559+
name: 'foo',
560+
type: 'function',
561+
api_version: '2023-07',
562+
configuration_ui: true,
563+
// build is intentionally omitted to test undefined case
564+
} as FunctionConfigType
565+
566+
const extensionInstance = await testFunctionExtension({
567+
config,
568+
dir: 'test-function',
569+
})
570+
571+
// Then
572+
expect(extensionInstance.outputPath).toBe(joinPath('test-function', 'dist', 'index.wasm'))
573+
})
574+
575+
test('uses default path when build.path is undefined', async () => {
576+
// Given
577+
const config = functionConfiguration()
578+
config.build = {
579+
wasm_opt: true,
580+
// path is not defined
581+
}
582+
583+
const extensionInstance = await testFunctionExtension({
584+
config,
585+
dir: 'test-function',
586+
})
587+
588+
// Then
589+
expect(extensionInstance.outputPath).toBe(joinPath('test-function', 'dist', 'index.wasm'))
590+
})
591+
592+
test('uses custom path when build.path is defined', async () => {
593+
// Given
594+
const config = functionConfiguration()
595+
config.build = {
596+
wasm_opt: true,
597+
path: 'custom/output.wasm',
598+
}
599+
600+
const extensionInstance = await testFunctionExtension({
601+
config,
602+
dir: 'test-function',
603+
})
604+
605+
// Then
606+
expect(extensionInstance.outputPath).toBe(joinPath('test-function', 'custom/output.wasm'))
607+
})
608+
})
554609
})

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,8 @@ export class ExtensionInstance<TConfiguration extends BaseConfigType = BaseConfi
169169

170170
if (this.isFunctionExtension) {
171171
const config = this.configuration as unknown as FunctionConfigType
172-
this.outputPath = joinPath(this.directory, config.build.path ?? joinPath('dist', 'index.wasm'))
172+
const defaultPath = joinPath('dist', 'index.wasm')
173+
this.outputPath = joinPath(this.directory, config.build?.path ?? defaultPath)
173174
}
174175
}
175176

@@ -280,7 +281,7 @@ export class ExtensionInstance<TConfiguration extends BaseConfigType = BaseConfi
280281
// Functions specific properties
281282
get buildCommand() {
282283
const config = this.configuration as unknown as FunctionConfigType
283-
return config.build.command
284+
return config.build?.command
284285
}
285286

286287
// Paths to be watched in a dev session
@@ -302,7 +303,7 @@ export class ExtensionInstance<TConfiguration extends BaseConfigType = BaseConfi
302303
get watchBuildPaths() {
303304
if (this.isFunctionExtension) {
304305
const config = this.configuration as unknown as FunctionConfigType
305-
const configuredPaths = config.build.watch ? [config.build.watch].flat() : []
306+
const configuredPaths = config.build?.watch ? [config.build.watch].flat() : []
306307

307308
if (!this.isJavaScript && configuredPaths.length === 0) {
308309
return null

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

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,4 +305,27 @@ describe('functionConfiguration', () => {
305305
expect(got.ui?.ui_extension_handle).toBeUndefined()
306306
})
307307
})
308+
309+
test('accepts configuration without build section', async () => {
310+
// Given
311+
const configWithoutBuild = {
312+
name: 'function',
313+
type: 'function',
314+
metafields: [],
315+
description: 'my function',
316+
// build section is omitted
317+
configuration_ui: false,
318+
api_version: '2022-07',
319+
}
320+
321+
// When
322+
const extension = await testFunctionExtension({
323+
dir: '/function',
324+
config: configWithoutBuild as FunctionConfigType,
325+
})
326+
327+
// Then
328+
expect(extension.configuration.build).toBeUndefined()
329+
expect(extension.outputPath).toBe(joinPath('/function', 'dist', 'index.wasm'))
330+
})
308331
})

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

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,17 @@ interface UI {
1818

1919
export type FunctionConfigType = zod.infer<typeof FunctionExtensionSchema>
2020
const FunctionExtensionSchema = BaseSchema.extend({
21-
build: zod.object({
22-
command: zod
23-
.string()
24-
.transform((value) => (value.trim() === '' ? undefined : value))
25-
.optional(),
26-
path: zod.string().optional(),
27-
watch: zod.union([zod.string(), zod.string().array()]).optional(),
28-
wasm_opt: zod.boolean().optional().default(true),
29-
}),
21+
build: zod
22+
.object({
23+
command: zod
24+
.string()
25+
.transform((value) => (value.trim() === '' ? undefined : value))
26+
.optional(),
27+
path: zod.string().optional(),
28+
watch: zod.union([zod.string(), zod.string().array()]).optional(),
29+
wasm_opt: zod.boolean().optional().default(true),
30+
})
31+
.optional(),
3032
name: zod.string(),
3133
type: zod.string(),
3234
configuration_ui: zod.boolean().optional().default(true),

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

Lines changed: 84 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ describe('buildFunctionExtension', () => {
4848

4949
test('delegates the build to system when the build command is present', async () => {
5050
// Given
51-
extension.configuration.build.command = './scripts/build.sh argument'
51+
extension.configuration.build!.command = './scripts/build.sh argument'
5252

5353
// When
5454
await expect(
@@ -73,7 +73,7 @@ describe('buildFunctionExtension', () => {
7373

7474
test('fails when is not a JS function and build command is not present', async () => {
7575
// Given
76-
extension.configuration.build.command = undefined
76+
extension.configuration.build!.command = undefined
7777

7878
// Then
7979
await expect(
@@ -91,7 +91,7 @@ describe('buildFunctionExtension', () => {
9191
test('succeeds when is a JS function and build command is not present', async () => {
9292
// Given
9393
extension = await testFunctionExtension({config: defaultConfig, entryPath: 'src/index.js'})
94-
extension.configuration.build.command = undefined
94+
extension.configuration.build!.command = undefined
9595

9696
// When
9797
await expect(
@@ -118,7 +118,7 @@ describe('buildFunctionExtension', () => {
118118
test('succeeds when is a JS function and build command is present', async () => {
119119
// Given
120120
extension = await testFunctionExtension({config: defaultConfig, entryPath: 'src/index.js'})
121-
extension.configuration.build.command = './scripts/build.sh argument'
121+
extension.configuration.build!.command = './scripts/build.sh argument'
122122

123123
// When
124124
await expect(
@@ -182,7 +182,7 @@ describe('buildFunctionExtension', () => {
182182
test('skips wasm-opt execution when the disable-wasm-opt is true', async () => {
183183
// Given
184184
vi.mocked(fileExistsSync).mockResolvedValue(true)
185-
extension.configuration.build.wasm_opt = false
185+
extension.configuration.build!.wasm_opt = false
186186

187187
// When
188188
await expect(
@@ -215,4 +215,83 @@ describe('buildFunctionExtension', () => {
215215
).rejects.toThrow(AbortError)
216216
expect(releaseLock).not.toHaveBeenCalled()
217217
})
218+
219+
test('handles function with undefined build config', async () => {
220+
// Given
221+
const configWithoutBuild = {
222+
name: 'MyFunction',
223+
type: 'product_discounts',
224+
description: '',
225+
configuration_ui: true,
226+
api_version: '2022-07',
227+
metafields: [],
228+
} as unknown as FunctionConfigType
229+
230+
extension = await testFunctionExtension({config: configWithoutBuild, entryPath: 'src/index.js'})
231+
vi.mocked(fileExistsSync).mockResolvedValue(true)
232+
233+
// When
234+
await expect(
235+
buildFunctionExtension(extension, {
236+
stdout,
237+
stderr,
238+
signal,
239+
app,
240+
environment: 'production',
241+
}),
242+
).resolves.toBeUndefined()
243+
244+
// Then
245+
expect(buildJSFunction).toHaveBeenCalledWith(extension, {
246+
stdout,
247+
stderr,
248+
signal,
249+
app,
250+
environment: 'production',
251+
})
252+
expect(releaseLock).toHaveBeenCalled()
253+
// wasm_opt should not be called when build config is undefined
254+
expect(runWasmOpt).not.toHaveBeenCalled()
255+
})
256+
257+
test('handles function with build config but undefined path', async () => {
258+
// Given
259+
const configWithoutPath = {
260+
name: 'MyFunction',
261+
type: 'product_discounts',
262+
description: '',
263+
build: {
264+
command: 'make build',
265+
wasm_opt: true,
266+
// path is undefined
267+
},
268+
configuration_ui: true,
269+
api_version: '2022-07',
270+
metafields: [],
271+
} as unknown as FunctionConfigType
272+
273+
extension = await testFunctionExtension({config: configWithoutPath})
274+
vi.mocked(fileExistsSync).mockResolvedValue(true)
275+
276+
// When
277+
await expect(
278+
buildFunctionExtension(extension, {
279+
stdout,
280+
stderr,
281+
signal,
282+
app,
283+
environment: 'production',
284+
}),
285+
).resolves.toBeUndefined()
286+
287+
// Then
288+
expect(exec).toHaveBeenCalledWith('make', ['build'], {
289+
stdout,
290+
stderr,
291+
cwd: extension.directory,
292+
signal,
293+
})
294+
expect(releaseLock).toHaveBeenCalled()
295+
expect(runWasmOpt).toHaveBeenCalled()
296+
})
218297
})

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ export async function buildFunctionExtension(
151151
try {
152152
const bundlePath = extension.outputPath
153153
const relativeBuildPath =
154-
(extension as ExtensionInstance<FunctionConfigType>).configuration.build.path ?? joinPath('dist', 'index.wasm')
154+
(extension as ExtensionInstance<FunctionConfigType>).configuration.build?.path ?? joinPath('dist', 'index.wasm')
155155

156156
extension.outputPath = joinPath(extension.directory, relativeBuildPath)
157157

@@ -161,7 +161,7 @@ export async function buildFunctionExtension(
161161
await buildOtherFunction(extension, options)
162162
}
163163

164-
const wasmOpt = (extension as ExtensionInstance<FunctionConfigType>).configuration.build.wasm_opt
164+
const wasmOpt = (extension as ExtensionInstance<FunctionConfigType>).configuration.build?.wasm_opt
165165
if (fileExistsSync(extension.outputPath) && wasmOpt) {
166166
await runWasmOpt(extension.outputPath)
167167
}

0 commit comments

Comments
 (0)