Skip to content
Draft
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
42 changes: 42 additions & 0 deletions packages/app/src/cli/services/function/build.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,20 @@ import {
import {testApp, testFunctionExtension} from '../../models/app/app.test-data.js'
import {beforeEach, describe, expect, test, vi} from 'vitest'
import {exec} from '@shopify/cli-kit/node/system'
import {packageManagerBinaryCommandForDirectory} from '@shopify/cli-kit/node/node-package-manager'
import {dirname, joinPath} from '@shopify/cli-kit/node/path'
import {inTemporaryDirectory, mkdir, readFileSync, writeFile, removeFile} from '@shopify/cli-kit/node/fs'
import {build as esBuild} from 'esbuild'

vi.mock('@shopify/cli-kit/node/fs')
vi.mock('@shopify/cli-kit/node/system')
vi.mock('@shopify/cli-kit/node/node-package-manager', async () => {
const actual: any = await vi.importActual('@shopify/cli-kit/node/node-package-manager')
return {
...actual,
packageManagerBinaryCommandForDirectory: vi.fn(),
}
})

vi.mock('./binaries.js', async (importOriginal) => {
const actual: any = await importOriginal()
Expand Down Expand Up @@ -76,6 +84,10 @@ beforeEach(async () => {
stderr = {write: vi.fn()}
stdout = {write: vi.fn()}
signal = vi.fn()
vi.mocked(packageManagerBinaryCommandForDirectory).mockResolvedValue({
command: 'npm',
args: ['exec', '--', 'graphql-code-generator', '--config', 'package.json'],
})
})

describe('buildGraphqlTypes', () => {
Expand All @@ -88,13 +100,40 @@ describe('buildGraphqlTypes', () => {

// Then
await expect(got).resolves.toBeUndefined()
expect(packageManagerBinaryCommandForDirectory).toHaveBeenCalledTimes(1)
expect(packageManagerBinaryCommandForDirectory).toHaveBeenCalledWith(
ourFunction.directory,
'graphql-code-generator',
'--config',
'package.json',
)
expect(exec).toHaveBeenCalledWith('npm', ['exec', '--', 'graphql-code-generator', '--config', 'package.json'], {
cwd: ourFunction.directory,
stderr,
signal,
})
})

test('generate types executes the command returned by the shared helper', {timeout: 20000}, async () => {
// Given
const ourFunction = await testFunctionExtension({entryPath: 'src/index.js'})
vi.mocked(packageManagerBinaryCommandForDirectory).mockResolvedValue({
command: 'pnpm',
args: ['exec', 'graphql-code-generator', '--config', 'package.json'],
})

// When
const got = buildGraphqlTypes(ourFunction, {stdout, stderr, signal, app})

// Then
await expect(got).resolves.toBeUndefined()
expect(exec).toHaveBeenCalledWith('pnpm', ['exec', 'graphql-code-generator', '--config', 'package.json'], {
cwd: ourFunction.directory,
stderr,
signal,
})
})

test('errors if function is not a JS function and no typegen_command', async () => {
// Given
const ourFunction = await testFunctionExtension()
Expand All @@ -105,6 +144,7 @@ describe('buildGraphqlTypes', () => {

// Then
await expect(got).rejects.toThrow(/No typegen_command specified/)
expect(packageManagerBinaryCommandForDirectory).not.toHaveBeenCalled()
})

test('runs custom typegen_command when provided', async () => {
Expand All @@ -129,6 +169,7 @@ describe('buildGraphqlTypes', () => {

// Then
await expect(got).resolves.toBeUndefined()
expect(packageManagerBinaryCommandForDirectory).not.toHaveBeenCalled()
expect(exec).toHaveBeenCalledWith('npx', ['shopify-function-codegen', '--schema', 'schema.graphql'], {
cwd: ourFunction.directory,
stdout,
Expand Down Expand Up @@ -159,6 +200,7 @@ describe('buildGraphqlTypes', () => {

// Then
await expect(got).resolves.toBeUndefined()
expect(packageManagerBinaryCommandForDirectory).not.toHaveBeenCalled()
expect(exec).toHaveBeenCalledWith('custom-typegen', ['--output', 'types.ts'], {
cwd: ourFunction.directory,
stdout,
Expand Down
10 changes: 9 additions & 1 deletion packages/app/src/cli/services/function/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {renderTasks} from '@shopify/cli-kit/node/ui'
import {pickBy} from '@shopify/cli-kit/common/object'
import {runWithTimer} from '@shopify/cli-kit/node/metadata'
import {AbortError} from '@shopify/cli-kit/node/error'
import {packageManagerBinaryCommandForDirectory} from '@shopify/cli-kit/node/node-package-manager'
import {Writable} from 'stream'

export const PREFERRED_FUNCTION_NPM_PACKAGE_MAJOR_VERSION = '2'
Expand Down Expand Up @@ -143,8 +144,15 @@ export async function buildGraphqlTypes(
)
}

const command = await packageManagerBinaryCommandForDirectory(
fun.directory,
'graphql-code-generator',
'--config',
'package.json',
)

return runWithTimer('cmd_all_timing_network_ms')(async () => {
return exec('npm', ['exec', '--', 'graphql-code-generator', '--config', 'package.json'], {
return exec(command.command, command.args, {
cwd: fun.directory,
stderr: options.stderr,
signal: options.signal,
Expand Down
85 changes: 85 additions & 0 deletions packages/cli-kit/src/public/node/node-package-manager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ import {
inferPackageManager,
PackageManager,
npmLockfile,
pnpmLockfile,
yarnLockfile,
bunLockfile,
packageManagerBinaryCommandForDirectory,
} from './node-package-manager.js'
import {captureOutput, exec} from './system.js'
import {inTemporaryDirectory, mkdir, touchFile, writeFile} from './fs.js'
Expand Down Expand Up @@ -917,6 +921,87 @@ describe('getPackageManager', () => {
})
})

describe('packageManagerBinaryCommandForDirectory', () => {
test('returns npm exec arguments for npm projects', async () => {
await inTemporaryDirectory(async (tmpDir) => {
await writePackageJSON(tmpDir, {name: 'mock name'})
await writeFile(joinPath(tmpDir, npmLockfile), '')

const command = await packageManagerBinaryCommandForDirectory(
tmpDir,
'graphql-code-generator',
'--config',
'package.json',
)

expect(command).toEqual({
command: 'npm',
args: ['exec', '--', 'graphql-code-generator', '--config', 'package.json'],
})
})
})

test('returns pnpm exec arguments for nested pnpm workspace packages', async () => {
await inTemporaryDirectory(async (tmpDir) => {
await writePackageJSON(tmpDir, {name: 'root'})
await writeFile(joinPath(tmpDir, pnpmLockfile), '')
const nested = joinPath(tmpDir, 'extensions', 'cart-transformer')
await mkdir(nested)
await writePackageJSON(nested, {name: 'cart-transformer'})

const command = await packageManagerBinaryCommandForDirectory(
nested,
'graphql-code-generator',
'--config',
'package.json',
)

expect(command).toEqual({
command: 'pnpm',
args: ['exec', 'graphql-code-generator', '--config', 'package.json'],
})
})
})

test('returns yarn run arguments for yarn projects', async () => {
await inTemporaryDirectory(async (tmpDir) => {
await writePackageJSON(tmpDir, {name: 'mock name'})
await writeFile(joinPath(tmpDir, yarnLockfile), '')

const command = await packageManagerBinaryCommandForDirectory(
tmpDir,
'graphql-code-generator',
'--config',
'package.json',
)

expect(command).toEqual({
command: 'yarn',
args: ['run', 'graphql-code-generator', '--config', 'package.json'],
})
})
})

test('returns bun x arguments for bun projects', async () => {
await inTemporaryDirectory(async (tmpDir) => {
await writePackageJSON(tmpDir, {name: 'mock name'})
await writeFile(joinPath(tmpDir, bunLockfile), '')

const command = await packageManagerBinaryCommandForDirectory(
tmpDir,
'graphql-code-generator',
'--config',
'package.json',
)

expect(command).toEqual({
command: 'bun',
args: ['x', 'graphql-code-generator', '--config', 'package.json'],
})
})
})
})

describe('addNPMDependencies', () => {
test('when using npm with multiple dependencies they should be installed one by one, adding --save-exact if needed', async () => {
await inTemporaryDirectory(async (tmpDir) => {
Expand Down
22 changes: 22 additions & 0 deletions packages/cli-kit/src/public/node/node-package-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,28 @@ export async function getPackageManager(fromDirectory: string): Promise<PackageM
return 'npm'
}

export async function packageManagerBinaryCommandForDirectory(
directory: string,
binary: string,
...args: string[]
): Promise<{command: string; args: string[]}> {
const packageManager = await getPackageManager(directory)

switch (packageManager) {
case 'npm':
return {command: 'npm', args: ['exec', '--', binary, ...args]}
case 'pnpm':
return {command: 'pnpm', args: ['exec', binary, ...args]}
case 'yarn':
return {command: 'yarn', args: ['run', binary, ...args]}
case 'bun':
return {command: 'bun', args: ['x', binary, ...args]}
case 'homebrew':
case 'unknown':
throw new UnknownPackageManagerError()
}
}

interface InstallNPMDependenciesRecursivelyOptions {
/**
* The dependency manager to use to install the dependencies.
Expand Down
Loading