Skip to content

Commit 39a8042

Browse files
Merge pull request #5943 from Shopify/shauns/06-04-add_support_for_deploy_without_build
Add support for deploy without build
2 parents f5639ba + 17e63d7 commit 39a8042

File tree

14 files changed

+342
-33
lines changed

14 files changed

+342
-33
lines changed

.changeset/little-rockets-serve.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@shopify/app': minor
3+
'@shopify/cli': minor
4+
---
5+
6+
Add `--no-build` flag to `shopify app deploy`. When provided, the deploy command will assume you have already run `shopify app build` or otherwise put build files in place.

docs-shopify.dev/commands/interfaces/app-deploy.interface.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ export interface appdeploy {
2424
*/
2525
'--message <value>'?: string
2626

27+
/**
28+
* Use with caution: Skips building any elements of the app that require building. You should ensure your app has been prepared in advance, such as by running `shopify app build` or by caching build artifacts.
29+
* @environment SHOPIFY_FLAG_NO_BUILD
30+
*/
31+
'--no-build'?: ''
32+
2733
/**
2834
* Disable color output.
2935
* @environment SHOPIFY_FLAG_NO_COLOR

docs-shopify.dev/generated/generated_docs_data.json

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,15 @@
322322
"isOptional": true,
323323
"environmentValue": "SHOPIFY_FLAG_MESSAGE"
324324
},
325+
{
326+
"filePath": "docs-shopify.dev/commands/interfaces/app-deploy.interface.ts",
327+
"syntaxKind": "PropertySignature",
328+
"name": "--no-build",
329+
"value": "\"\"",
330+
"description": "Use with caution: Skips building any elements of the app that require building. You should ensure your app has been prepared in advance, such as by running `shopify app build` or by caching build artifacts.",
331+
"isOptional": true,
332+
"environmentValue": "SHOPIFY_FLAG_NO_BUILD"
333+
},
325334
{
326335
"filePath": "docs-shopify.dev/commands/interfaces/app-deploy.interface.ts",
327336
"syntaxKind": "PropertySignature",
@@ -404,7 +413,7 @@
404413
"environmentValue": "SHOPIFY_FLAG_FORCE"
405414
}
406415
],
407-
"value": "export interface appdeploy {\n /**\n * The Client ID of your app.\n * @environment SHOPIFY_FLAG_CLIENT_ID\n */\n '--client-id <value>'?: string\n\n /**\n * The name of the app configuration.\n * @environment SHOPIFY_FLAG_APP_CONFIG\n */\n '-c, --config <value>'?: string\n\n /**\n * Deploy without asking for confirmation.\n * @environment SHOPIFY_FLAG_FORCE\n */\n '-f, --force'?: ''\n\n /**\n * Optional message that will be associated with this version. This is for internal use only and won't be available externally.\n * @environment SHOPIFY_FLAG_MESSAGE\n */\n '--message <value>'?: string\n\n /**\n * Disable color output.\n * @environment SHOPIFY_FLAG_NO_COLOR\n */\n '--no-color'?: ''\n\n /**\n * Creates a version but doesn't release it - it's not made available to merchants.\n * @environment SHOPIFY_FLAG_NO_RELEASE\n */\n '--no-release'?: ''\n\n /**\n * The path to your app directory.\n * @environment SHOPIFY_FLAG_PATH\n */\n '--path <value>'?: string\n\n /**\n * Reset all your settings.\n * @environment SHOPIFY_FLAG_RESET\n */\n '--reset'?: ''\n\n /**\n * URL associated with the new app version.\n * @environment SHOPIFY_FLAG_SOURCE_CONTROL_URL\n */\n '--source-control-url <value>'?: string\n\n /**\n * Increase the verbosity of the output.\n * @environment SHOPIFY_FLAG_VERBOSE\n */\n '--verbose'?: ''\n\n /**\n * Optional version tag that will be associated with this app version. If not provided, an auto-generated identifier will be generated for this app version.\n * @environment SHOPIFY_FLAG_VERSION\n */\n '--version <value>'?: string\n}"
416+
"value": "export interface appdeploy {\n /**\n * The Client ID of your app.\n * @environment SHOPIFY_FLAG_CLIENT_ID\n */\n '--client-id <value>'?: string\n\n /**\n * The name of the app configuration.\n * @environment SHOPIFY_FLAG_APP_CONFIG\n */\n '-c, --config <value>'?: string\n\n /**\n * Deploy without asking for confirmation.\n * @environment SHOPIFY_FLAG_FORCE\n */\n '-f, --force'?: ''\n\n /**\n * Optional message that will be associated with this version. This is for internal use only and won't be available externally.\n * @environment SHOPIFY_FLAG_MESSAGE\n */\n '--message <value>'?: string\n\n /**\n * Use with caution: Skips building any elements of the app that require building. You should ensure your app has been prepared in advance, such as by running `shopify app build` or by caching build artifacts.\n * @environment SHOPIFY_FLAG_NO_BUILD\n */\n '--no-build'?: ''\n\n /**\n * Disable color output.\n * @environment SHOPIFY_FLAG_NO_COLOR\n */\n '--no-color'?: ''\n\n /**\n * Creates a version but doesn't release it - it's not made available to merchants.\n * @environment SHOPIFY_FLAG_NO_RELEASE\n */\n '--no-release'?: ''\n\n /**\n * The path to your app directory.\n * @environment SHOPIFY_FLAG_PATH\n */\n '--path <value>'?: string\n\n /**\n * Reset all your settings.\n * @environment SHOPIFY_FLAG_RESET\n */\n '--reset'?: ''\n\n /**\n * URL associated with the new app version.\n * @environment SHOPIFY_FLAG_SOURCE_CONTROL_URL\n */\n '--source-control-url <value>'?: string\n\n /**\n * Increase the verbosity of the output.\n * @environment SHOPIFY_FLAG_VERBOSE\n */\n '--verbose'?: ''\n\n /**\n * Optional version tag that will be associated with this app version. If not provided, an auto-generated identifier will be generated for this app version.\n * @environment SHOPIFY_FLAG_VERSION\n */\n '--version <value>'?: string\n}"
408417
}
409418
}
410419
}

packages/app/src/cli/commands/app/deploy.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,12 @@ export default class Deploy extends AppLinkedCommand {
4444
env: 'SHOPIFY_FLAG_NO_RELEASE',
4545
default: false,
4646
}),
47+
'no-build': Flags.boolean({
48+
description:
49+
'Use with caution: Skips building any elements of the app that require building. You should ensure your app has been prepared in advance, such as by running `shopify app build` or by caching build artifacts.',
50+
env: 'SHOPIFY_FLAG_NO_BUILD',
51+
default: false,
52+
}),
4753
message: Flags.string({
4854
hidden: false,
4955
description:
@@ -111,6 +117,7 @@ export default class Deploy extends AppLinkedCommand {
111117
message: flags.message,
112118
version: flags.version,
113119
commitReference: flags['source-control-url'],
120+
skipBuild: flags['no-build'],
114121
})
115122

116123
return {app: result.app}

packages/app/src/cli/models/extensions/extension-instance.ts

Lines changed: 67 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
buildFunctionExtension,
1919
buildThemeExtension,
2020
buildUIExtension,
21+
bundleFunctionExtension,
2122
} from '../../services/build/extension.js'
2223
import {bundleThemeExtension} from '../../services/extensions/bundle.js'
2324
import {Identifiers} from '../app/identifiers.js'
@@ -29,7 +30,7 @@ import {constantize, slugify} from '@shopify/cli-kit/common/string'
2930
import {hashString, nonRandomUUID} from '@shopify/cli-kit/node/crypto'
3031
import {partnersFqdn} from '@shopify/cli-kit/node/context/fqdn'
3132
import {joinPath, basename} from '@shopify/cli-kit/node/path'
32-
import {fileExists, touchFile, moveFile, writeFile, glob} from '@shopify/cli-kit/node/fs'
33+
import {fileExists, touchFile, moveFile, writeFile, glob, copyFile} from '@shopify/cli-kit/node/fs'
3334
import {getPathValue} from '@shopify/cli-kit/common/object'
3435
import {outputDebug} from '@shopify/cli-kit/node/output'
3536

@@ -44,6 +45,8 @@ export const CONFIG_EXTENSION_IDS: string[] = [
4445
WebhooksSpecIdentifier,
4546
]
4647

48+
type BuildMode = 'theme' | 'function' | 'ui' | 'flow' | 'tax_calculation' | 'none'
49+
4750
/**
4851
* Class that represents an instance of a local extension
4952
* Before creating this class we've validated that:
@@ -335,20 +338,23 @@ export class ExtensionInstance<TConfiguration extends BaseConfigType = BaseConfi
335338
}
336339

337340
async build(options: ExtensionBuildOptions): Promise<void> {
338-
if (this.isThemeExtension) {
339-
return buildThemeExtension(this, options)
340-
} else if (this.isFunctionExtension) {
341-
return buildFunctionExtension(this, options)
342-
} else if (this.features.includes('esbuild')) {
343-
return buildUIExtension(this, options)
344-
} else if (this.specification.identifier === 'flow_template' && options.environment === 'production') {
345-
return buildFlowTemplateExtension(this, options)
346-
}
347-
348-
// Workaround for tax_calculations because they remote spec NEEDS a valid js file to be included.
349-
if (this.type === 'tax_calculation') {
350-
await touchFile(this.outputPath)
351-
await writeFile(this.outputPath, '(()=>{})();')
341+
const mode = this.buildMode(options)
342+
343+
switch (mode) {
344+
case 'theme':
345+
return buildThemeExtension(this, options)
346+
case 'function':
347+
return buildFunctionExtension(this, options)
348+
case 'ui':
349+
return buildUIExtension(this, options)
350+
case 'flow':
351+
return buildFlowTemplateExtension(this, options)
352+
case 'tax_calculation':
353+
await touchFile(this.outputPath)
354+
await writeFile(this.outputPath, '(()=>{})();')
355+
break
356+
case 'none':
357+
break
352358
}
353359
}
354360

@@ -368,6 +374,33 @@ export class ExtensionInstance<TConfiguration extends BaseConfigType = BaseConfi
368374
await this.keepBuiltSourcemapsLocally(bundleDirectory, extensionId)
369375
}
370376

377+
async copyIntoBundle(options: ExtensionBuildOptions, bundleDirectory: string, identifiers?: Identifiers) {
378+
const extensionId = this.getOutputFolderId(identifiers?.extensions[this.localIdentifier])
379+
380+
const defaultOutputPath = this.outputPath
381+
382+
if (this.features.includes('bundling')) {
383+
this.outputPath = this.getOutputPathForDirectory(bundleDirectory, extensionId)
384+
}
385+
386+
const buildMode = this.buildMode(options)
387+
388+
if (buildMode !== 'none') {
389+
outputDebug(`Will copy pre-built file from ${defaultOutputPath} to ${this.outputPath}`)
390+
if (await fileExists(defaultOutputPath)) {
391+
await copyFile(defaultOutputPath, this.outputPath)
392+
393+
if (buildMode === 'function') {
394+
await bundleFunctionExtension(this.outputPath, this.outputPath)
395+
}
396+
}
397+
398+
if (this.isThemeExtension) {
399+
await bundleThemeExtension(this, options)
400+
}
401+
}
402+
}
403+
371404
getOutputFolderId(extensionId?: string) {
372405
return extensionId ?? this.uid ?? this.handle
373406
}
@@ -437,6 +470,25 @@ export class ExtensionInstance<TConfiguration extends BaseConfigType = BaseConfi
437470
await this.specification.contributeToSharedTypeFile?.(this, typeDefinitionsByFile)
438471
}
439472

473+
private buildMode(options: ExtensionBuildOptions): BuildMode {
474+
if (this.isThemeExtension) {
475+
return 'theme'
476+
} else if (this.isFunctionExtension) {
477+
return 'function'
478+
} else if (this.features.includes('esbuild')) {
479+
return 'ui'
480+
} else if (this.specification.identifier === 'flow_template' && options.environment === 'production') {
481+
return 'flow'
482+
}
483+
484+
// Workaround for tax_calculations because they remote spec NEEDS a valid js file to be included.
485+
if (this.type === 'tax_calculation') {
486+
return 'tax_calculation'
487+
}
488+
489+
return 'none'
490+
}
491+
440492
private buildHandle() {
441493
switch (this.specification.uidStrategy) {
442494
case 'single':

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

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -186,9 +186,7 @@ export async function buildFunctionExtension(
186186
}
187187

188188
if (fileExistsSync(extension.outputPath) && bundlePath !== extension.outputPath) {
189-
const base64Contents = await readFile(extension.outputPath, {encoding: 'base64'})
190-
await touchFile(bundlePath)
191-
await writeFile(bundlePath, base64Contents)
189+
await bundleFunctionExtension(extension.outputPath, bundlePath)
192190
}
193191
// eslint-disable-next-line @typescript-eslint/no-explicit-any
194192
} catch (error: any) {
@@ -210,6 +208,13 @@ export async function buildFunctionExtension(
210208
}
211209
}
212210

211+
export async function bundleFunctionExtension(wasmPath: string, bundlePath: string) {
212+
outputDebug(`Converting WASM from ${wasmPath} to base64 in ${bundlePath}`)
213+
const base64Contents = await readFile(wasmPath, {encoding: 'base64'})
214+
await touchFile(bundlePath)
215+
await writeFile(bundlePath, base64Contents)
216+
}
217+
213218
async function runCommandOrBuildJSFunction(extension: ExtensionInstance, options: BuildFunctionExtensionOptions) {
214219
if (extension.buildCommand) {
215220
return runCommand(extension.buildCommand, extension, options)

packages/app/src/cli/services/context.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ const deployOptions = (app: AppLinkedInterface, reset = false, force = false): D
9595
force,
9696
noRelease: false,
9797
developerPlatformClient: buildDeveloperPlatformClient(),
98+
skipBuild: false,
9899
}
99100
}
100101

@@ -513,6 +514,7 @@ describe('ensureDeployContext', () => {
513514
force: false,
514515
noRelease: false,
515516
developerPlatformClient: buildDeveloperPlatformClient({supportsAtomicDeployments: true}),
517+
skipBuild: false,
516518
}
517519
await ensureDeployContext(options)
518520

@@ -554,6 +556,7 @@ describe('ensureDeployContext', () => {
554556
force: false,
555557
noRelease: false,
556558
developerPlatformClient: buildDeveloperPlatformClient({supportsAtomicDeployments: true}),
559+
skipBuild: false,
557560
}
558561
await ensureDeployContext(options)
559562

packages/app/src/cli/services/deploy.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -548,5 +548,6 @@ async function testDeployBundle({
548548
version: options?.version,
549549
...(commitReference ? {commitReference} : {}),
550550
developerPlatformClient,
551+
skipBuild: false,
551552
})
552553
}

packages/app/src/cli/services/deploy.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ export interface DeployOptions {
4343

4444
/** The git reference url of the app version */
4545
commitReference?: string
46+
47+
/** If true, skip building any elements of the app that require building */
48+
skipBuild: boolean
4649
}
4750

4851
interface TasksContext {
@@ -76,7 +79,7 @@ export async function deploy(options: DeployOptions) {
7679
bundlePath = joinPath(options.app.directory, '.shopify', `deploy-bundle.${developerPlatformClient.bundleFormat}`)
7780
await mkdir(dirname(bundlePath))
7881
}
79-
await bundleAndBuildExtensions({app, bundlePath, identifiers})
82+
await bundleAndBuildExtensions({app, bundlePath, identifiers, skipBuild: options.skipBuild})
8083

8184
let uploadTaskTitle
8285

0 commit comments

Comments
 (0)