Skip to content

Commit f5706b0

Browse files
committed
Fix installing function dependencies when using npm with workspaces
1 parent b1a8e33 commit f5706b0

File tree

3 files changed

+69
-45
lines changed

3 files changed

+69
-45
lines changed

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

Lines changed: 61 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,11 @@ import checkoutPostPurchaseExtension from '../../models/templates/ui-specificati
1515
import {ExtensionTemplate} from '../../models/app/template.js'
1616
import {describe, expect, vi, test} from 'vitest'
1717
import * as output from '@shopify/cli-kit/node/output'
18-
import {addNPMDependenciesIfNeeded, addResolutionOrOverride} from '@shopify/cli-kit/node/node-package-manager'
18+
import {
19+
installNodeModules,
20+
addNPMDependenciesIfNeeded,
21+
addResolutionOrOverride,
22+
} from '@shopify/cli-kit/node/node-package-manager'
1923
import * as template from '@shopify/cli-kit/node/liquid'
2024
import * as file from '@shopify/cli-kit/node/fs'
2125
import * as git from '@shopify/cli-kit/node/git'
@@ -28,6 +32,7 @@ vi.mock('@shopify/cli-kit/node/node-package-manager', async () => {
2832
...actual,
2933
addNPMDependenciesIfNeeded: vi.fn(),
3034
addResolutionOrOverride: vi.fn(),
35+
installNodeModules: vi.fn(),
3136
}
3237
})
3338

@@ -324,48 +329,52 @@ describe('initialize a extension', async () => {
324329
})
325330

326331
test('generates a JS function', async () => {
327-
await withTemporaryApp(async (tmpDir) => {
328-
// Given
329-
const name = 'my-fun-1'
330-
const specification = allFunctionTemplates.find((spec) => spec.identifier === 'order_discounts')!
331-
const extensionFlavor = 'vanilla-js'
332-
333-
vi.spyOn(git, 'downloadGitRepository').mockResolvedValue()
334-
vi.spyOn(extensionsCommon, 'ensureDownloadedExtensionFlavorExists').mockImplementationOnce(async () => tmpDir)
335-
vi.spyOn(template, 'recursiveLiquidTemplateCopy').mockImplementationOnce(async (_origin, destination) => {
336-
await file.writeFile(
337-
joinPath(destination, 'shopify.function.extension.toml'),
338-
`name = "my-fun-1"
332+
await withTemporaryApp(
333+
async (tmpDir) => {
334+
// Given
335+
const name = 'my-fun-1'
336+
const specification = allFunctionTemplates.find((spec) => spec.identifier === 'order_discounts')!
337+
const extensionFlavor = 'vanilla-js'
338+
339+
vi.spyOn(git, 'downloadGitRepository').mockResolvedValue()
340+
vi.spyOn(extensionsCommon, 'ensureDownloadedExtensionFlavorExists').mockImplementationOnce(async () => tmpDir)
341+
vi.spyOn(template, 'recursiveLiquidTemplateCopy').mockImplementationOnce(async (_origin, destination) => {
342+
await file.writeFile(
343+
joinPath(destination, 'shopify.function.extension.toml'),
344+
`name = "my-fun-1"
339345
type = "order_discounts"
340346
api_version = "2023-01"
341347
342348
[build]
343349
path = "dist/function.wasm"`,
344-
)
345-
await file.mkdir(joinPath(destination, 'src'))
346-
await file.writeFile(joinPath(destination, 'src', 'index'), '')
347-
})
348-
const buildGraphqlTypesSpy = vi.spyOn(functionBuild, 'buildGraphqlTypes').mockResolvedValue()
349-
350-
// When
351-
const extensionDir = await createFromTemplate({
352-
name,
353-
extensionTemplate: specification,
354-
extensionFlavor,
355-
appDirectory: tmpDir,
356-
specifications,
357-
})
350+
)
351+
await file.mkdir(joinPath(destination, 'src'))
352+
await file.writeFile(joinPath(destination, 'src', 'index'), '')
353+
})
354+
const buildGraphqlTypesSpy = vi.spyOn(functionBuild, 'buildGraphqlTypes').mockResolvedValue()
358355

359-
// Then
360-
const app = await loadApp({directory: tmpDir, specifications})
361-
const generatedFunction = app.extensions.function[0]!
362-
expect(extensionDir).toEqual(joinPath(tmpDir, 'extensions', name))
363-
expect(generatedFunction.configuration.name).toBe(name)
364-
expect(generatedFunction.entrySourceFilePath).toBe(joinPath(extensionDir, 'src', 'index.js'))
356+
// When
357+
const extensionDir = await createFromTemplate({
358+
name,
359+
extensionTemplate: specification,
360+
extensionFlavor,
361+
appDirectory: tmpDir,
362+
specifications,
363+
})
365364

366-
expect(addNPMDependenciesIfNeeded).toHaveBeenCalledOnce()
367-
expect(buildGraphqlTypesSpy).toHaveBeenCalledOnce()
368-
})
365+
// Then
366+
const app = await loadApp({directory: tmpDir, specifications})
367+
const generatedFunction = app.extensions.function[0]!
368+
expect(extensionDir).toEqual(joinPath(tmpDir, 'extensions', name))
369+
expect(generatedFunction.configuration.name).toBe(name)
370+
expect(generatedFunction.entrySourceFilePath).toBe(joinPath(extensionDir, 'src', 'index.js'))
371+
372+
expect(installNodeModules).toHaveBeenCalledOnce()
373+
expect(addNPMDependenciesIfNeeded).toHaveBeenCalledOnce()
374+
expect(buildGraphqlTypesSpy).toHaveBeenCalledOnce()
375+
},
376+
{useWorkspaces: true},
377+
)
369378
})
370379

371380
test('throws an error if there is no folder for selected flavor', async () => {
@@ -417,7 +426,10 @@ async function createFromTemplate({
417426
})
418427
return result[0]!.directory
419428
}
420-
async function withTemporaryApp(callback: (tmpDir: string) => Promise<void> | void) {
429+
async function withTemporaryApp(
430+
callback: (tmpDir: string) => Promise<void> | void,
431+
options?: {useWorkspaces: boolean},
432+
) {
421433
await file.inTemporaryDirectory(async (tmpDir) => {
422434
const appConfigurationPath = joinPath(tmpDir, configurationFileNames.app)
423435
const webConfigurationPath = joinPath(tmpDir, blocks.web.directoryName, blocks.web.configurationName)
@@ -436,7 +448,18 @@ async function withTemporaryApp(callback: (tmpDir: string) => Promise<void> | vo
436448
await file.writeFile(appConfigurationPath, appConfiguration)
437449
await file.mkdir(dirname(webConfigurationPath))
438450
await file.writeFile(webConfigurationPath, webConfiguration)
439-
await file.writeFile(joinPath(tmpDir, 'package.json'), JSON.stringify({dependencies: {}, devDependencies: {}}))
451+
452+
const packageJsonContents: {
453+
workspaces?: string[]
454+
dependencies: {[key: string]: string}
455+
devDependencies: {[key: string]: string}
456+
} = {dependencies: {}, devDependencies: {}}
457+
458+
if (options?.useWorkspaces) {
459+
packageJsonContents.workspaces = ['extensions/*']
460+
}
461+
462+
await file.writeFile(joinPath(tmpDir, 'package.json'), JSON.stringify(packageJsonContents))
440463
return callback(tmpDir)
441464
})
442465
}

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

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {
1313
addNPMDependenciesIfNeeded,
1414
addResolutionOrOverride,
1515
DependencyVersion,
16-
installNPMDependenciesRecursively,
16+
installNodeModules,
1717
readAndParsePackageJson,
1818
} from '@shopify/cli-kit/node/node-package-manager'
1919
import {recursiveLiquidTemplateCopy} from '@shopify/cli-kit/node/liquid'
@@ -142,6 +142,11 @@ async function functionExtensionInit({directory, url, app, name, extensionFlavor
142142
taskList.push({
143143
title: 'Installing additional dependencies',
144144
task: async () => {
145+
// We need to run `npm install` once to setup the workspace correctly
146+
if (app.usesWorkspaces && app.packageManager === 'npm') {
147+
await installNodeModules({packageManager: 'npm', directory: app.directory})
148+
}
149+
145150
const requiredDependencies = getFunctionRuntimeDependencies(templateLanguage)
146151
await addNPMDependenciesIfNeeded(requiredDependencies, {
147152
packageManager: app.packageManager,
@@ -201,11 +206,7 @@ async function uiExtensionInit({directory, url, app, name, extensionFlavor}: Ext
201206
directory: app.directory,
202207
})
203208
}
204-
await installNPMDependenciesRecursively({
205-
packageManager,
206-
directory: app.directory,
207-
deep: 0,
208-
})
209+
await installNodeModules({packageManager, directory: app.directory})
209210
} else {
210211
await addResolutionOrOverrideIfNeeded(app.directory, extensionFlavor?.value)
211212
const extensionPackageJsonPath = joinPath(directory, 'package.json')

packages/cli-kit/src/public/node/node-package-manager.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ export async function installNPMDependenciesRecursively(
154154

155155
interface InstallNodeModulesOptions {
156156
directory: string
157-
args: string[]
157+
args?: string[]
158158
packageManager: PackageManager
159159
stdout?: Writable
160160
stderr?: Writable

0 commit comments

Comments
 (0)