Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/app/src/cli/models/app/app.test-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ import {Project} from '../project/project.js'
import {Session} from '@shopify/cli-kit/node/session'
import {vi} from 'vitest'
import {joinPath} from '@shopify/cli-kit/node/path'
import {PackageManager} from '@shopify/cli-kit/node/node-package-manager'
import {ProjectPackageManager} from '@shopify/cli-kit/node/node-package-manager'

export const DEFAULT_CONFIG = {
application_url: 'https://myapp.com',
Expand Down Expand Up @@ -154,7 +154,7 @@ export function testAppWithConfig(options?: TestAppWithConfigOptions): AppLinked

interface TestProjectOptions {
directory?: string
packageManager?: PackageManager
packageManager?: ProjectPackageManager | 'unknown'
nodeDependencies?: Record<string, string>
usesWorkspaces?: boolean
}
Expand Down
14 changes: 14 additions & 0 deletions packages/app/src/cli/models/project/project-integration.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {Project} from './project.js'
import {resolveDotEnv, resolveHiddenConfig, extensionFilesForConfig, webFilesForConfig} from './config-selection.js'
import {requireProjectPackageManagerForOperations} from '../../utilities/project-package-manager.js'
import {loadApp, reloadApp} from '../app/loader.js'
import {AppLinkedInterface} from '../app/app.js'
import {loadLocalExtensionsSpecifications} from '../extensions/load-specifications.js'
Expand Down Expand Up @@ -212,6 +213,19 @@ describe('Project integration', () => {
})
})

test('requireProjectPackageManagerForOperations errors when the app root has no package.json', async () => {
await inTemporaryDirectory(async (dir) => {
await setupRealApp(dir)
await removeFile(joinPath(dir, 'package.json'))

const project = await Project.load(dir)

expect(() => requireProjectPackageManagerForOperations(project)).toThrow(
/Could not determine the project package manager/,
)
})
})

test('multi-config project discovers all configs', async () => {
await inTemporaryDirectory(async (dir) => {
await setupRealApp(dir)
Expand Down
12 changes: 12 additions & 0 deletions packages/app/src/cli/services/dependencies.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,16 @@ describe('installAppDependencies', () => {
deep: 3,
})
})

test('errors before install when the project package manager is unknown', async () => {
const project = testProject({packageManager: 'unknown', directory: '/tmp/project'})

await installAppDependencies(project)

const tasks = vi.mocked(renderTasks).mock.calls[0]![0] as any
const task = tasks[0]

await expect(task.task()).rejects.toThrow(/Could not determine the project package manager/)
expect(installNPMDependenciesRecursively).not.toHaveBeenCalled()
})
})
3 changes: 2 additions & 1 deletion packages/app/src/cli/services/dependencies.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {Project} from '../models/project/project.js'
import {requireProjectPackageManagerForOperations} from '../utilities/project-package-manager.js'
import {installNPMDependenciesRecursively} from '@shopify/cli-kit/node/node-package-manager'
import {renderTasks} from '@shopify/cli-kit/node/ui'

Expand All @@ -14,7 +15,7 @@ export async function installAppDependencies(project: Project) {
title: 'Installing dependencies',
task: async () => {
await installNPMDependenciesRecursively({
packageManager: project.packageManager,
packageManager: requireProjectPackageManagerForOperations(project),
directory: project.directory,
deep: 3,
})
Expand Down
24 changes: 24 additions & 0 deletions packages/app/src/cli/services/generate/extension.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import * as functionBuild from '../function/build.js'
import {
checkoutUITemplate,
testDeveloperPlatformClient,
testProject,
testRemoteExtensionTemplates,
} from '../../models/app/app.test-data.js'
import {ExtensionTemplate} from '../../models/app/template.js'
Expand Down Expand Up @@ -91,6 +92,29 @@ describe('initialize a extension', async () => {
})
})

test('errors before installing extension dependencies when the project package manager is unknown', async () => {
await withTemporaryApp(async (tmpDir) => {
const app = (await loadApp({
directory: tmpDir,
specifications,
userProvidedConfigName: undefined,
})) as AppLinkedInterface

const result = generateExtensionTemplate({
extensionTemplate: checkoutUITemplate,
app,
project: testProject({directory: tmpDir, packageManager: 'unknown'}),
extensionChoices: {name: 'extension-name', flavor: 'vanilla-js'},
developerPlatformClient: testDeveloperPlatformClient(),
onGetTemplateRepository,
})

await expect(result).rejects.toThrow(/Could not determine the project package manager/)
expect(vi.mocked(installNodeModules)).not.toHaveBeenCalled()
expect(vi.mocked(addNPMDependenciesIfNeeded)).not.toHaveBeenCalled()
})
})

test('successfully generates the extension when another extension exists', async () => {
await withTemporaryApp(async (tmpDir) => {
const name1 = 'my-ext-1'
Expand Down
9 changes: 6 additions & 3 deletions packages/app/src/cli/services/generate/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {buildGraphqlTypes, PREFERRED_FUNCTION_NPM_PACKAGE_MAJOR_VERSION} from '.
import {GenerateExtensionContentOutput} from '../../prompts/generate/extension.js'
import {ExtensionFlavor, ExtensionTemplate} from '../../models/app/template.js'
import {ensureDownloadedExtensionFlavorExists, ensureExtensionDirectoryExists} from '../extensions/common.js'
import {requireProjectPackageManagerForOperations} from '../../utilities/project-package-manager.js'
import {DeveloperPlatformClient} from '../../utilities/developer-platform-client.js'
import {reloadApp} from '../../models/app/loader.js'
import {
Expand Down Expand Up @@ -190,14 +191,16 @@ async function functionExtensionInit({
taskList.push({
title: 'Installing additional dependencies',
task: async () => {
const packageManager = requireProjectPackageManagerForOperations(project)

// We need to run install once to setup the workspace correctly
if (project.usesWorkspaces) {
await installNodeModules({packageManager: project.packageManager, directory: project.directory})
await installNodeModules({packageManager, directory: project.directory})
}

const requiredDependencies = getFunctionRuntimeDependencies(templateLanguage)
await addNPMDependenciesIfNeeded(requiredDependencies, {
packageManager: project.packageManager,
packageManager,
type: 'prod',
directory: project.usesWorkspaces ? directory : project.directory,
})
Expand Down Expand Up @@ -258,7 +261,7 @@ async function uiExtensionInit({
{
title: 'Installing dependencies',
task: async () => {
const packageManager = project.packageManager
const packageManager = requireProjectPackageManagerForOperations(project)
if (project.usesWorkspaces) {
// Only install dependencies if the extension is javascript
if (getTemplateLanguage(extensionFlavor?.value) === 'javascript') {
Expand Down
22 changes: 22 additions & 0 deletions packages/app/src/cli/utilities/project-package-manager.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import {requireProjectPackageManagerForOperations} from './project-package-manager.js'
import {describe, expect, test} from 'vitest'

describe('requireProjectPackageManagerForOperations', () => {
test.each(['npm', 'pnpm', 'yarn', 'bun'])('returns %s for supported project package managers', (packageManager) => {
const result = requireProjectPackageManagerForOperations({
packageManager,
directory: '/tmp/project',
} as any)

expect(result).toBe(packageManager)
})

test('throws when the project package manager is unknown', () => {
expect(() =>
requireProjectPackageManagerForOperations({
packageManager: 'unknown',
directory: '/tmp/project',
} as any),
).toThrow(/Could not determine the project package manager for \/tmp\/project/)
})
})
18 changes: 18 additions & 0 deletions packages/app/src/cli/utilities/project-package-manager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import {Project} from '../models/project/project.js'
import {ProjectPackageManager} from '@shopify/cli-kit/node/node-package-manager'
import {AbortError} from '@shopify/cli-kit/node/error'

/**
* Narrows project package-manager metadata for install/mutation operations.
*/
export function requireProjectPackageManagerForOperations(
project: Pick<Project, 'packageManager' | 'directory'>,
): ProjectPackageManager {
if (project.packageManager === 'unknown') {
throw new AbortError(
`Could not determine the project package manager for ${project.directory}. Add a package.json to the app root before running dependency operations.`,
)
}

return project.packageManager
}
Loading