diff --git a/packages/app/src/cli/models/extensions/extension-instance.ts b/packages/app/src/cli/models/extensions/extension-instance.ts index b68a22720cd..120e5b1fe47 100644 --- a/packages/app/src/cli/models/extensions/extension-instance.ts +++ b/packages/app/src/cli/models/extensions/extension-instance.ts @@ -112,6 +112,12 @@ export class ExtensionInstance group.lifecycle === 'deploy' && group.steps.length > 0) ?? false + ) + } + get features(): ExtensionFeature[] { return this.specification.appModuleFeatures(this.configuration) } @@ -340,16 +346,14 @@ export class ExtensionInstance) => string @@ -220,7 +215,6 @@ export function createExtensionSpecification clientSteps?: ClientSteps - buildConfig?: BuildConfig appModuleFeatures?: (config?: TConfiguration) => ExtensionFeature[] transformConfig: TransformationConfig | CustomTransformationConfig uidStrategy?: UidStrategy @@ -288,7 +281,6 @@ export function createConfigExtensionSpecification, | 'identifier' | 'appModuleFeatures' - | 'buildConfig' | 'uidStrategy' | 'clientSteps' | 'experience' @@ -312,7 +303,6 @@ export function createContractBasedModuleSpecification, appModuleFeatures: spec.appModuleFeatures, experience: spec.experience, - buildConfig: spec.buildConfig ?? {mode: 'none'}, clientSteps: spec.clientSteps, uidStrategy: spec.uidStrategy, transformRemoteToLocal: spec.transformRemoteToLocal, diff --git a/packages/app/src/cli/models/extensions/specifications/admin.ts b/packages/app/src/cli/models/extensions/specifications/admin.ts index bdd20cb2131..7aad744d238 100644 --- a/packages/app/src/cli/models/extensions/specifications/admin.ts +++ b/packages/app/src/cli/models/extensions/specifications/admin.ts @@ -41,10 +41,6 @@ const adminSpecificationSpec = createExtensionSpecification({ }, } }, - buildConfig: { - mode: 'copy_files', - filePatterns: [], - }, clientSteps: [ { lifecycle: 'deploy', diff --git a/packages/app/src/cli/models/extensions/specifications/channel.ts b/packages/app/src/cli/models/extensions/specifications/channel.ts index fc3ca3b5a9e..3973fa91d26 100644 --- a/packages/app/src/cli/models/extensions/specifications/channel.ts +++ b/packages/app/src/cli/models/extensions/specifications/channel.ts @@ -1,5 +1,4 @@ import {createContractBasedModuleSpecification} from '../specification.js' -import {joinPath} from '@shopify/cli-kit/node/path' const SUBDIRECTORY_NAME = 'specifications' const FILE_EXTENSIONS = ['json', 'toml', 'yaml', 'yml', 'svg'] @@ -8,10 +7,6 @@ const channelSpecificationSpec = createContractBasedModuleSpecification({ identifier: 'channel_config', uidStrategy: 'single', experience: 'extension', - buildConfig: { - mode: 'copy_files', - filePatterns: FILE_EXTENSIONS.map((ext) => joinPath(SUBDIRECTORY_NAME, '**', `*.${ext}`)), - }, clientSteps: [ { lifecycle: 'deploy', diff --git a/packages/app/src/cli/models/extensions/specifications/checkout_post_purchase.ts b/packages/app/src/cli/models/extensions/specifications/checkout_post_purchase.ts index cfc32f4582e..fad34726ed6 100644 --- a/packages/app/src/cli/models/extensions/specifications/checkout_post_purchase.ts +++ b/packages/app/src/cli/models/extensions/specifications/checkout_post_purchase.ts @@ -16,7 +16,6 @@ const checkoutPostPurchaseSpec = createExtensionSpecification({ partnersWebIdentifier: 'post_purchase', schema: CheckoutPostPurchaseSchema, appModuleFeatures: (_) => ['ui_preview', 'cart_url', 'esbuild', 'single_js_entry_path'], - buildConfig: {mode: 'ui'}, getOutputRelativePath: (extension: ExtensionInstance) => `dist/${extension.handle}.js`, clientSteps: [ diff --git a/packages/app/src/cli/models/extensions/specifications/checkout_ui_extension.ts b/packages/app/src/cli/models/extensions/specifications/checkout_ui_extension.ts index bd6715fa762..239f164c9b2 100644 --- a/packages/app/src/cli/models/extensions/specifications/checkout_ui_extension.ts +++ b/packages/app/src/cli/models/extensions/specifications/checkout_ui_extension.ts @@ -23,7 +23,6 @@ const checkoutSpec = createExtensionSpecification({ dependency, schema: CheckoutSchema, appModuleFeatures: (_) => ['ui_preview', 'cart_url', 'esbuild', 'single_js_entry_path', 'generates_source_maps'], - buildConfig: {mode: 'ui'}, getOutputRelativePath: (extension: ExtensionInstance) => `dist/${extension.handle}.js`, clientSteps: [ { diff --git a/packages/app/src/cli/models/extensions/specifications/flow_template.ts b/packages/app/src/cli/models/extensions/specifications/flow_template.ts index 67f3759ad76..805f953c98d 100644 --- a/packages/app/src/cli/models/extensions/specifications/flow_template.ts +++ b/packages/app/src/cli/models/extensions/specifications/flow_template.ts @@ -49,7 +49,6 @@ const flowTemplateSpec = createExtensionSpecification({ identifier: 'flow_template', schema: FlowTemplateExtensionSchema, appModuleFeatures: (_) => ['ui_preview'], - buildConfig: {mode: 'copy_files', filePatterns: ['**/*.flow', '**/*.json', '**/*.toml']}, clientSteps: [ { lifecycle: 'deploy', diff --git a/packages/app/src/cli/models/extensions/specifications/function.ts b/packages/app/src/cli/models/extensions/specifications/function.ts index d40d83e523b..2d73e819d01 100644 --- a/packages/app/src/cli/models/extensions/specifications/function.ts +++ b/packages/app/src/cli/models/extensions/specifications/function.ts @@ -88,7 +88,6 @@ const functionSpec = createExtensionSpecification({ ], schema: FunctionExtensionSchema, appModuleFeatures: (_) => ['function'], - buildConfig: {mode: 'function'}, getOutputRelativePath: (_extension: ExtensionInstance) => joinPath('dist', 'index.wasm'), devSessionWatchConfig: (extension: ExtensionInstance) => { const config = extension.configuration diff --git a/packages/app/src/cli/models/extensions/specifications/pos_ui_extension.ts b/packages/app/src/cli/models/extensions/specifications/pos_ui_extension.ts index 05134ab44d8..3835d873ec7 100644 --- a/packages/app/src/cli/models/extensions/specifications/pos_ui_extension.ts +++ b/packages/app/src/cli/models/extensions/specifications/pos_ui_extension.ts @@ -15,7 +15,6 @@ const posUISpec = createExtensionSpecification({ dependency, schema: PosUISchema, appModuleFeatures: (_) => ['ui_preview', 'esbuild', 'single_js_entry_path'], - buildConfig: {mode: 'ui'}, getOutputRelativePath: (extension: ExtensionInstance) => `dist/${extension.handle}.js`, clientSteps: [ { diff --git a/packages/app/src/cli/models/extensions/specifications/product_subscription.ts b/packages/app/src/cli/models/extensions/specifications/product_subscription.ts index 6efb44d4a48..77c95fde10f 100644 --- a/packages/app/src/cli/models/extensions/specifications/product_subscription.ts +++ b/packages/app/src/cli/models/extensions/specifications/product_subscription.ts @@ -16,7 +16,6 @@ const productSubscriptionSpec = createExtensionSpecification({ graphQLType: 'subscription_management', schema: BaseSchema, appModuleFeatures: (_) => ['ui_preview', 'esbuild', 'single_js_entry_path'], - buildConfig: {mode: 'ui'}, getOutputRelativePath: (extension: ExtensionInstance) => `dist/${extension.handle}.js`, clientSteps: [ { diff --git a/packages/app/src/cli/models/extensions/specifications/tax_calculation.ts b/packages/app/src/cli/models/extensions/specifications/tax_calculation.ts index ea4a012330a..1cc2caa3517 100644 --- a/packages/app/src/cli/models/extensions/specifications/tax_calculation.ts +++ b/packages/app/src/cli/models/extensions/specifications/tax_calculation.ts @@ -31,7 +31,6 @@ const spec = createExtensionSpecification({ identifier: 'tax_calculation', schema: TaxCalculationsSchema, appModuleFeatures: (_) => [], - buildConfig: {mode: 'tax_calculation'}, getOutputRelativePath: (extension: ExtensionInstance) => joinPath('dist', `${extension.handle}.js`), clientSteps: [ diff --git a/packages/app/src/cli/models/extensions/specifications/theme.ts b/packages/app/src/cli/models/extensions/specifications/theme.ts index c01fa7d5761..8e6d27d27e4 100644 --- a/packages/app/src/cli/models/extensions/specifications/theme.ts +++ b/packages/app/src/cli/models/extensions/specifications/theme.ts @@ -12,7 +12,6 @@ const themeSpec = createExtensionSpecification({ schema: BaseSchema, partnersWebIdentifier: 'theme_app_extension', graphQLType: 'theme_app_extension', - buildConfig: {mode: 'theme'}, clientSteps: [ { lifecycle: 'deploy', diff --git a/packages/app/src/cli/models/extensions/specifications/ui_extension.ts b/packages/app/src/cli/models/extensions/specifications/ui_extension.ts index 2656bfb5a16..55377653b4a 100644 --- a/packages/app/src/cli/models/extensions/specifications/ui_extension.ts +++ b/packages/app/src/cli/models/extensions/specifications/ui_extension.ts @@ -83,7 +83,6 @@ const uiExtensionSpec = createExtensionSpecification({ identifier: 'ui_extension', dependency, schema: UIExtensionSchema, - buildConfig: {mode: 'ui'}, getOutputRelativePath: (extension: ExtensionInstance) => `dist/${extension.handle}.js`, clientSteps: [ { diff --git a/packages/app/src/cli/models/extensions/specifications/web_pixel_extension.ts b/packages/app/src/cli/models/extensions/specifications/web_pixel_extension.ts index 903fbaa8288..a6547c226cd 100644 --- a/packages/app/src/cli/models/extensions/specifications/web_pixel_extension.ts +++ b/packages/app/src/cli/models/extensions/specifications/web_pixel_extension.ts @@ -33,7 +33,6 @@ const webPixelSpec = createExtensionSpecification({ partnersWebIdentifier: 'web_pixel', schema: WebPixelSchema, appModuleFeatures: (_) => ['esbuild', 'single_js_entry_path'], - buildConfig: {mode: 'ui'}, getOutputRelativePath: (extension: ExtensionInstance) => `dist/${extension.handle}.js`, clientSteps: [ { diff --git a/packages/app/src/cli/services/deploy.test.ts b/packages/app/src/cli/services/deploy.test.ts index 8a695e91e60..4807b4ebdd2 100644 --- a/packages/app/src/cli/services/deploy.test.ts +++ b/packages/app/src/cli/services/deploy.test.ts @@ -79,6 +79,12 @@ beforeEach(() => { // Mock filterOutImportedExtensions to return the extensions by default vi.mocked(filterOutImportedExtensions).mockImplementation((_app, extensions) => extensions) + + // Mirror real bundleAndBuildExtensions behavior: return the bundlePath when + // at least one extension has deploy steps, otherwise undefined. + vi.mocked(bundleAndBuildExtensions).mockImplementation(async (opts) => + opts.app.allExtensions.some((ext) => ext.hasDeploySteps) ? opts.bundlePath : undefined, + ) }) describe('deploy', () => { diff --git a/packages/app/src/cli/services/deploy.ts b/packages/app/src/cli/services/deploy.ts index 66848fa8666..2f9d31a27ba 100644 --- a/packages/app/src/cli/services/deploy.ts +++ b/packages/app/src/cli/services/deploy.ts @@ -213,20 +213,19 @@ export async function deploy(options: DeployOptions) { let uploadExtensionsBundleResult!: UploadExtensionsBundleOutput try { - const bundle = app.allExtensions.some((ext) => ext.specification.buildConfig.mode !== 'none') - let bundlePath: string | undefined - - if (bundle) { - bundlePath = joinPath(options.app.directory, '.shopify', `deploy-bundle.${developerPlatformClient.bundleFormat}`) - await mkdir(dirname(bundlePath)) - } + const candidateBundlePath = joinPath( + options.app.directory, + '.shopify', + `deploy-bundle.${developerPlatformClient.bundleFormat}`, + ) + await mkdir(dirname(candidateBundlePath)) const appManifest = await app.manifest(identifiers) - await bundleAndBuildExtensions({ + const bundlePath = await bundleAndBuildExtensions({ app, appManifest, - bundlePath, + bundlePath: candidateBundlePath, identifiers, skipBuild: options.skipBuild, isDevDashboardApp: developerPlatformClient.supportsAtomicDeployments, diff --git a/packages/app/src/cli/services/deploy/bundle.test.ts b/packages/app/src/cli/services/deploy/bundle.test.ts index e30d3e091fc..2ec28f886b7 100644 --- a/packages/app/src/cli/services/deploy/bundle.test.ts +++ b/packages/app/src/cli/services/deploy/bundle.test.ts @@ -1,5 +1,11 @@ import {bundleAndBuildExtensions} from './bundle.js' -import {testApp, testFunctionExtension, testThemeExtensions, testUIExtension} from '../../models/app/app.test-data.js' +import { + testApp, + testAppConfigExtensions, + testFunctionExtension, + testThemeExtensions, + testUIExtension, +} from '../../models/app/app.test-data.js' import {AppInterface, AppManifest, WebType} from '../../models/app/app.js' import * as bundle from '../bundle.js' import * as functionBuild from '../function/build.js' @@ -477,4 +483,38 @@ describe('bundleAndBuildExtensions', () => { await expect(file.fileExists(bundlePath)).resolves.toBeTruthy() }) }) + + test('returns undefined and skips compression when no extension has deploy steps', async () => { + await file.inTemporaryDirectory(async (tmpDir: string) => { + // Given + const bundlePath = joinPath(tmpDir, '.shopify', 'deploy-bundle.zip') + const compressSpy = vi.spyOn(bundle, 'compressBundle').mockResolvedValue() + + const configExtension = await testAppConfigExtensions() + const app = testApp({allExtensions: [configExtension], directory: tmpDir}) + + const identifiers = { + app: 'app-id', + extensions: {}, + extensionIds: {}, + extensionsNonUuidManaged: {[configExtension.localIdentifier]: configExtension.localIdentifier}, + } + appManifest = await app.manifest(identifiers) + + // When + const result = await bundleAndBuildExtensions({ + app, + appManifest, + identifiers, + bundlePath, + skipBuild: false, + isDevDashboardApp: false, + }) + + // Then + expect(result).toBeUndefined() + expect(compressSpy).not.toHaveBeenCalled() + await expect(file.fileExists(bundlePath)).resolves.toBe(false) + }) + }) }) diff --git a/packages/app/src/cli/services/deploy/bundle.ts b/packages/app/src/cli/services/deploy/bundle.ts index 5811ff368a9..216645b51a0 100644 --- a/packages/app/src/cli/services/deploy/bundle.ts +++ b/packages/app/src/cli/services/deploy/bundle.ts @@ -12,13 +12,19 @@ import {Writable} from 'stream' interface BundleOptions { app: AppInterface appManifest: AppManifest - bundlePath?: string + bundlePath: string identifiers?: Identifiers skipBuild: boolean isDevDashboardApp: boolean } -export async function bundleAndBuildExtensions(options: BundleOptions) { +/** + * Builds all extensions into a bundle directory and compresses it when at + * least one extension declares deploy steps. Returns the bundlePath in that + * case, or undefined when no extension has deploy steps and there's nothing + * to upload beyond the manifest. + */ +export async function bundleAndBuildExtensions(options: BundleOptions): Promise { const bundleDirectory = joinPath(options.app.directory, '.shopify', 'deploy-bundle') await rmdir(bundleDirectory, {force: true}) await mkdir(bundleDirectory) @@ -73,7 +79,12 @@ export async function bundleAndBuildExtensions(options: BundleOptions) { showTimestamps: false, }) - if (options.bundlePath) { + const hasExtensionOutput = options.app.allExtensions.some((ext) => ext.hasDeploySteps) + + if (hasExtensionOutput) { await compressBundle(bundleDirectory, options.bundlePath) + return options.bundlePath } + + return undefined }