Skip to content

Commit 3b4a49f

Browse files
authored
Merge pull request #5153 from Shopify/ah.validate-shopify-function-package-version
Validate `@shopify/shopify_function` version on build
2 parents e28e357 + f4c7577 commit 3b4a49f

File tree

4 files changed

+79
-16
lines changed

4 files changed

+79
-16
lines changed

.changeset/tough-guests-behave.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@shopify/app': patch
3+
---
4+
5+
Validate the @shopify/shopify_function NPM package version is compatible with the Javy version

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

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@ async function installShopifyLibrary(tmpDir: string) {
6767
const runModule = joinPath(shopifyFunctionDir, 'run.ts')
6868
await writeFile(runModule, '')
6969

70+
const packageJson = joinPath(shopifyFunctionDir, 'package.json')
71+
await writeFile(packageJson, JSON.stringify({version: '1.0.0'}))
72+
7073
return shopifyFunction
7174
}
7275

@@ -136,6 +139,28 @@ describe('bundleExtension', () => {
136139
})
137140
})
138141

142+
test('errors if shopify library is not on a compatible version', async () => {
143+
await inTemporaryDirectory(async (tmpDir) => {
144+
// Given
145+
const incompatibleVersion = '0.0.1'
146+
const ourFunction = await testFunctionExtension({dir: tmpDir})
147+
ourFunction.entrySourceFilePath = joinPath(tmpDir, 'src/index.ts')
148+
await installShopifyLibrary(tmpDir)
149+
await writeFile(
150+
joinPath(tmpDir, 'node_modules/@shopify/shopify_function/package.json'),
151+
JSON.stringify({version: incompatibleVersion}),
152+
)
153+
154+
// When
155+
const got = bundleExtension(ourFunction, {stdout, stderr, signal, app})
156+
157+
// Then
158+
await expect(got).rejects.toThrow(
159+
/The installed version of the Shopify Functions JavaScript library is not compatible with this version of Shopify CLI./,
160+
)
161+
})
162+
})
163+
139164
test('errors if user function not found', async () => {
140165
await inTemporaryDirectory(async (tmpDir) => {
141166
// Given

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

Lines changed: 44 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,34 @@ import {outputContent, outputDebug, outputToken} from '@shopify/cli-kit/node/out
99
import {exec} from '@shopify/cli-kit/node/system'
1010
import {dirname, joinPath} from '@shopify/cli-kit/node/path'
1111
import {build as esBuild, BuildResult} from 'esbuild'
12-
import {findPathUp, inTemporaryDirectory, writeFile} from '@shopify/cli-kit/node/fs'
12+
import {findPathUp, inTemporaryDirectory, readFile, writeFile} from '@shopify/cli-kit/node/fs'
1313
import {AbortSignal} from '@shopify/cli-kit/node/abort'
1414
import {renderTasks} from '@shopify/cli-kit/node/ui'
1515
import {pickBy} from '@shopify/cli-kit/common/object'
1616
import {runWithTimer} from '@shopify/cli-kit/node/metadata'
1717
import {AbortError} from '@shopify/cli-kit/node/error'
1818
import {Writable} from 'stream'
1919

20+
export const SHOPIFY_FUNCTION_NPM_PACKAGE_MAJOR_VERSION = '1'
21+
22+
class InvalidShopifyFunctionPackageError extends AbortError {
23+
constructor(message: string) {
24+
super(
25+
message,
26+
outputContent`Make sure you have a compatible version of the ${outputToken.yellow(
27+
'@shopify/shopify_function',
28+
)} library installed.`,
29+
[
30+
outputContent`Add ${outputToken.green(
31+
`"@shopify/shopify_function": "~${SHOPIFY_FUNCTION_NPM_PACKAGE_MAJOR_VERSION}.0.0"`,
32+
)} to the dependencies section of the package.json file in your function's directory, if not already present.`
33+
.value,
34+
`Run your package manager's install command to update dependencies.`,
35+
],
36+
)
37+
}
38+
}
39+
2040
interface JSFunctionBuildOptions {
2141
stdout: Writable
2242
stderr: Writable
@@ -117,19 +137,7 @@ async function checkForShopifyFunctionRuntimeEntrypoint(fun: ExtensionInstance<F
117137
})
118138

119139
if (!entryPoint || !runModule) {
120-
throw new AbortError(
121-
'Could not find the Shopify Functions JavaScript library.',
122-
outputContent`Make sure you have the latest ${outputToken.yellow(
123-
'@shopify/shopify_function',
124-
)} library installed.`,
125-
[
126-
outputContent`Add ${outputToken.green(
127-
'"@shopify/shopify_function": "1.0.0"',
128-
)} to the dependencies section of the package.json file in your function's directory, if not already present.`
129-
.value,
130-
`Run your package manager's install command to update dependencies.`,
131-
],
132-
)
140+
throw new InvalidShopifyFunctionPackageError('Could not find the Shopify Functions JavaScript library.')
133141
}
134142

135143
if (!fun.entrySourceFilePath) {
@@ -139,11 +147,32 @@ async function checkForShopifyFunctionRuntimeEntrypoint(fun: ExtensionInstance<F
139147
return entryPoint
140148
}
141149

150+
async function validateShopifyFunctionPackageVersion(fun: ExtensionInstance<FunctionConfigType>) {
151+
const packageJsonPath = await findPathUp('node_modules/@shopify/shopify_function/package.json', {
152+
type: 'file',
153+
cwd: fun.directory,
154+
})
155+
156+
if (!packageJsonPath) {
157+
throw new InvalidShopifyFunctionPackageError('Could not find the Shopify Functions JavaScript library.')
158+
}
159+
160+
const packageJson = JSON.parse(await readFile(packageJsonPath))
161+
const majorVersion = packageJson.version.split('.')[0]
162+
163+
if (majorVersion !== SHOPIFY_FUNCTION_NPM_PACKAGE_MAJOR_VERSION) {
164+
throw new InvalidShopifyFunctionPackageError(
165+
'The installed version of the Shopify Functions JavaScript library is not compatible with this version of Shopify CLI.',
166+
)
167+
}
168+
}
169+
142170
export async function bundleExtension(
143171
fun: ExtensionInstance<FunctionConfigType>,
144172
options: JSFunctionBuildOptions,
145173
processEnv = process.env,
146174
) {
175+
await validateShopifyFunctionPackageVersion(fun)
147176
const entryPoint = await checkForShopifyFunctionRuntimeEntrypoint(fun)
148177

149178
const esbuildOptions = {
@@ -276,6 +305,7 @@ export class ExportJavyBuilder implements JavyBuilder {
276305
}
277306

278307
async bundle(fun: ExtensionInstance<FunctionConfigType>, options: JSFunctionBuildOptions, processEnv = process.env) {
308+
await validateShopifyFunctionPackageVersion(fun)
279309
await checkForShopifyFunctionRuntimeEntrypoint(fun)
280310

281311
const contents = this.entrypointContents

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {configurationFileNames, versions} from '../../constants.js'
22
import {AppInterface} from '../../models/app/app.js'
3-
import {buildGraphqlTypes} from '../function/build.js'
3+
import {buildGraphqlTypes, SHOPIFY_FUNCTION_NPM_PACKAGE_MAJOR_VERSION} from '../function/build.js'
44
import {GenerateExtensionContentOutput} from '../../prompts/generate/extension.js'
55
import {ExtensionFlavor, ExtensionTemplate} from '../../models/app/template.js'
66
import {ensureDownloadedExtensionFlavorExists, ensureExtensionDirectoryExists} from '../extensions/common.js'
@@ -299,7 +299,10 @@ function getSrcFileExtension(extensionFlavor: ExtensionFlavorValue): SrcFileExte
299299
export function getFunctionRuntimeDependencies(templateLanguage: string): DependencyVersion[] {
300300
const dependencies: DependencyVersion[] = []
301301
if (templateLanguage === 'javascript') {
302-
dependencies.push({name: '@shopify/shopify_function', version: '1.0.0'})
302+
dependencies.push({
303+
name: '@shopify/shopify_function',
304+
version: `~${SHOPIFY_FUNCTION_NPM_PACKAGE_MAJOR_VERSION}.0.0`,
305+
})
303306
}
304307
return dependencies
305308
}

0 commit comments

Comments
 (0)