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
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
143 changes: 143 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 @@ -12,6 +12,7 @@ import {
addResolutionOrOverride,
writePackageJSON,
getPackageManager,
packageManagerBinaryCommandForDirectory,
installNPMDependenciesRecursively,
addNPMDependencies,
DependencyVersion,
Expand Down Expand Up @@ -892,6 +893,21 @@ describe('getPackageManager', () => {
})
})

test('finds if bun is being used from bun.lock', async () => {
await inTemporaryDirectory(async (tmpDir) => {
// Given
await writePackageJSON(tmpDir, {name: 'mock name'})
await writeFile(joinPath(tmpDir, 'bun.lock'), '')
mockedCaptureOutput.mockReturnValueOnce(Promise.resolve(tmpDir))

// When
const packageManager = await getPackageManager(tmpDir)

// Then
expect(packageManager).toEqual('bun')
})
})

test('falls back to packageManagerFromUserAgent when npm prefix fails', async () => {
await inTemporaryDirectory(async (tmpDir) => {
// Given
Expand Down Expand Up @@ -927,6 +943,133 @@ describe('getPackageManager', () => {
})
})

describe('packageManagerBinaryCommandForDirectory', () => {
test('uses npm exec with -- for npm', async () => {
await inTemporaryDirectory(async (tmpDir) => {
await writePackageJSON(tmpDir, {name: 'mock name'})

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

test('uses exec without -- for pnpm when detected from an ancestor workspace marker', async () => {
await inTemporaryDirectory(async (tmpDir) => {
await writePackageJSON(tmpDir, {name: 'app-root'})
await writeFile(joinPath(tmpDir, 'pnpm-workspace.yaml'), '')
const extensionDirectory = joinPath(tmpDir, 'extensions', 'my-function')
await mkdir(extensionDirectory)
await writePackageJSON(extensionDirectory, {name: 'my-function'})

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

test('uses yarn run when detected from yarn.lock', async () => {
await inTemporaryDirectory(async (tmpDir) => {
await writePackageJSON(tmpDir, {name: 'mock name'})
await writeFile(joinPath(tmpDir, 'yarn.lock'), '')

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

test('uses bun x for bun when detected from bun.lock', async () => {
await inTemporaryDirectory(async (tmpDir) => {
await writePackageJSON(tmpDir, {name: 'mock name'})
await writeFile(joinPath(tmpDir, 'bun.lock'), '')

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

test('uses bun x for bun when detected from bun.lockb', async () => {
await inTemporaryDirectory(async (tmpDir) => {
await writePackageJSON(tmpDir, {name: 'mock name'})
await writeFile(joinPath(tmpDir, 'bun.lockb'), '')

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

test('falls back to yarn run when the user agent is yarn', async () => {
await inTemporaryDirectory(async (tmpDir) => {
const extensionDirectory = joinPath(tmpDir, 'subdir')
await mkdir(extensionDirectory)
vi.stubEnv('npm_config_user_agent', 'yarn/1.22.0')

try {
await expect(
packageManagerBinaryCommandForDirectory(
extensionDirectory,
'graphql-code-generator',
'--config',
'package.json',
),
).resolves.toEqual({
command: 'yarn',
args: ['run', 'graphql-code-generator', '--config', 'package.json'],
})
} finally {
vi.unstubAllEnvs()
}
})
})

test('falls back to npm when no package manager markers or user agent are available', async () => {
await inTemporaryDirectory(async (tmpDir) => {
const extensionDirectory = joinPath(tmpDir, 'subdir')
await mkdir(extensionDirectory)
vi.stubEnv('npm_config_user_agent', '')

try {
await expect(
packageManagerBinaryCommandForDirectory(
extensionDirectory,
'graphql-code-generator',
'--config',
'package.json',
),
).resolves.toEqual({
command: 'npm',
args: ['exec', '--', 'graphql-code-generator', '--config', 'package.json'],
})
} finally {
vi.unstubAllEnvs()
}
})
})
})

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
Loading
Loading