Skip to content

Commit

Permalink
Merge pull request #5153 from Shopify/ah.validate-shopify-function-pa…
Browse files Browse the repository at this point in the history
…ckage-version

Validate `@shopify/shopify_function` version on build
  • Loading branch information
andrewhassan authored Jan 13, 2025
2 parents e28e357 + f4c7577 commit 3b4a49f
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 16 deletions.
5 changes: 5 additions & 0 deletions .changeset/tough-guests-behave.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@shopify/app': patch
---

Validate the @shopify/shopify_function NPM package version is compatible with the Javy version
25 changes: 25 additions & 0 deletions packages/app/src/cli/services/function/build.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ async function installShopifyLibrary(tmpDir: string) {
const runModule = joinPath(shopifyFunctionDir, 'run.ts')
await writeFile(runModule, '')

const packageJson = joinPath(shopifyFunctionDir, 'package.json')
await writeFile(packageJson, JSON.stringify({version: '1.0.0'}))

return shopifyFunction
}

Expand Down Expand Up @@ -136,6 +139,28 @@ describe('bundleExtension', () => {
})
})

test('errors if shopify library is not on a compatible version', async () => {
await inTemporaryDirectory(async (tmpDir) => {
// Given
const incompatibleVersion = '0.0.1'
const ourFunction = await testFunctionExtension({dir: tmpDir})
ourFunction.entrySourceFilePath = joinPath(tmpDir, 'src/index.ts')
await installShopifyLibrary(tmpDir)
await writeFile(
joinPath(tmpDir, 'node_modules/@shopify/shopify_function/package.json'),
JSON.stringify({version: incompatibleVersion}),
)

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

// Then
await expect(got).rejects.toThrow(
/The installed version of the Shopify Functions JavaScript library is not compatible with this version of Shopify CLI./,
)
})
})

test('errors if user function not found', async () => {
await inTemporaryDirectory(async (tmpDir) => {
// Given
Expand Down
58 changes: 44 additions & 14 deletions packages/app/src/cli/services/function/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,34 @@ import {outputContent, outputDebug, outputToken} from '@shopify/cli-kit/node/out
import {exec} from '@shopify/cli-kit/node/system'
import {dirname, joinPath} from '@shopify/cli-kit/node/path'
import {build as esBuild, BuildResult} from 'esbuild'
import {findPathUp, inTemporaryDirectory, writeFile} from '@shopify/cli-kit/node/fs'
import {findPathUp, inTemporaryDirectory, readFile, writeFile} from '@shopify/cli-kit/node/fs'
import {AbortSignal} from '@shopify/cli-kit/node/abort'
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 {Writable} from 'stream'

export const SHOPIFY_FUNCTION_NPM_PACKAGE_MAJOR_VERSION = '1'

class InvalidShopifyFunctionPackageError extends AbortError {
constructor(message: string) {
super(
message,
outputContent`Make sure you have a compatible version of the ${outputToken.yellow(
'@shopify/shopify_function',
)} library installed.`,
[
outputContent`Add ${outputToken.green(
`"@shopify/shopify_function": "~${SHOPIFY_FUNCTION_NPM_PACKAGE_MAJOR_VERSION}.0.0"`,
)} to the dependencies section of the package.json file in your function's directory, if not already present.`
.value,
`Run your package manager's install command to update dependencies.`,
],
)
}
}

interface JSFunctionBuildOptions {
stdout: Writable
stderr: Writable
Expand Down Expand Up @@ -117,19 +137,7 @@ async function checkForShopifyFunctionRuntimeEntrypoint(fun: ExtensionInstance<F
})

if (!entryPoint || !runModule) {
throw new AbortError(
'Could not find the Shopify Functions JavaScript library.',
outputContent`Make sure you have the latest ${outputToken.yellow(
'@shopify/shopify_function',
)} library installed.`,
[
outputContent`Add ${outputToken.green(
'"@shopify/shopify_function": "1.0.0"',
)} to the dependencies section of the package.json file in your function's directory, if not already present.`
.value,
`Run your package manager's install command to update dependencies.`,
],
)
throw new InvalidShopifyFunctionPackageError('Could not find the Shopify Functions JavaScript library.')
}

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

async function validateShopifyFunctionPackageVersion(fun: ExtensionInstance<FunctionConfigType>) {
const packageJsonPath = await findPathUp('node_modules/@shopify/shopify_function/package.json', {
type: 'file',
cwd: fun.directory,
})

if (!packageJsonPath) {
throw new InvalidShopifyFunctionPackageError('Could not find the Shopify Functions JavaScript library.')
}

const packageJson = JSON.parse(await readFile(packageJsonPath))
const majorVersion = packageJson.version.split('.')[0]

if (majorVersion !== SHOPIFY_FUNCTION_NPM_PACKAGE_MAJOR_VERSION) {
throw new InvalidShopifyFunctionPackageError(
'The installed version of the Shopify Functions JavaScript library is not compatible with this version of Shopify CLI.',
)
}
}

export async function bundleExtension(
fun: ExtensionInstance<FunctionConfigType>,
options: JSFunctionBuildOptions,
processEnv = process.env,
) {
await validateShopifyFunctionPackageVersion(fun)
const entryPoint = await checkForShopifyFunctionRuntimeEntrypoint(fun)

const esbuildOptions = {
Expand Down Expand Up @@ -276,6 +305,7 @@ export class ExportJavyBuilder implements JavyBuilder {
}

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

const contents = this.entrypointContents
Expand Down
7 changes: 5 additions & 2 deletions packages/app/src/cli/services/generate/extension.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {configurationFileNames, versions} from '../../constants.js'
import {AppInterface} from '../../models/app/app.js'
import {buildGraphqlTypes} from '../function/build.js'
import {buildGraphqlTypes, SHOPIFY_FUNCTION_NPM_PACKAGE_MAJOR_VERSION} from '../function/build.js'
import {GenerateExtensionContentOutput} from '../../prompts/generate/extension.js'
import {ExtensionFlavor, ExtensionTemplate} from '../../models/app/template.js'
import {ensureDownloadedExtensionFlavorExists, ensureExtensionDirectoryExists} from '../extensions/common.js'
Expand Down Expand Up @@ -299,7 +299,10 @@ function getSrcFileExtension(extensionFlavor: ExtensionFlavorValue): SrcFileExte
export function getFunctionRuntimeDependencies(templateLanguage: string): DependencyVersion[] {
const dependencies: DependencyVersion[] = []
if (templateLanguage === 'javascript') {
dependencies.push({name: '@shopify/shopify_function', version: '1.0.0'})
dependencies.push({
name: '@shopify/shopify_function',
version: `~${SHOPIFY_FUNCTION_NPM_PACKAGE_MAJOR_VERSION}.0.0`,
})
}
return dependencies
}
Expand Down

0 comments on commit 3b4a49f

Please sign in to comment.