Skip to content

Refactor extension build process to use modular build steps#6867

Open
alfonso-noriega wants to merge 1 commit into01-build-steps-infrastructurefrom
02-wire-build-config-into-extension-specs
Open

Refactor extension build process to use modular build steps#6867
alfonso-noriega wants to merge 1 commit into01-build-steps-infrastructurefrom
02-wire-build-config-into-extension-specs

Conversation

@alfonso-noriega
Copy link
Contributor

@alfonso-noriega alfonso-noriega commented Feb 19, 2026

Wire extension specifications to use client steps

Summary

This PR wires all extension specifications to use the clientSteps contract introduced in #01-build-steps-infrastructure. The build() method in ExtensionInstance is refactored from a hard-coded mode switch to a generic step execution loop, and every spec now declares its own clientSteps array.

Depends on: 01-build-steps-infrastructure


What changed

ExtensionInstance.build() — mode switch replaced by step loop

Before:

async build(options: ExtensionBuildOptions): Promise<void> {
  const mode = this.specification.buildConfig.mode
  switch (mode) {
    case 'theme':
      await buildThemeExtension(this, options)
      return bundleThemeExtension(this, options)
    case 'function':
      return buildFunctionExtension(this, options)
    case 'ui':
      return buildUIExtension(this, options)
    case 'copy_files':
      return copyFilesForExtension(this, options)
    // ...
  }
}

After:

async build(options: ExtensionBuildOptions): Promise<void> {
  const {clientSteps} = this.specification

  const context: BuildContext = {
    extension: this,
    options,
    stepResults: new Map(),
  }

  const steps = clientSteps
    .filter((group) => group.lifecycle === 'deploy')
    .flatMap((group) => group.steps)

  for (const step of steps) {
    const result = await executeStep(step, context)
    context.stepResults.set(step.id, result)
  }
}

The mode switch and all its direct function call imports (buildThemeExtension, buildUIExtension, buildFunctionExtension, copyFilesForExtension) are removed. The step loop is uniform across all extension types.

extension.tsbuildThemeExtension removed

buildThemeExtension() and its runThemeCheck import are removed from extension.ts — the logic now lives exclusively in build-theme-step.ts.


Specs wired

All specs now declare clientSteps. The buildConfig.mode is kept as a transitional field.

UI extensions

// checkout_post_purchase, checkout_ui_extension, pos_ui_extension,
// product_subscription, web_pixel_extension, ui_extension
clientSteps: [{
  lifecycle: 'deploy',
  steps: [
    {id: 'bundle-ui',          name: 'Bundle UI Extension', type: 'bundle_ui',          config: {}},
    {id: 'copy-static-assets', name: 'Copy Static Assets',  type: 'copy_static_assets', config: {}},
  ],
}]

Theme

clientSteps: [{
  lifecycle: 'deploy',
  steps: [
    {id: 'build-theme',  name: 'Build Theme Extension',  type: 'build_theme',  config: {}},
    {id: 'bundle-theme', name: 'Bundle Theme Extension', type: 'bundle_theme', config: {}},
  ],
}]

Function

clientSteps: [{
  lifecycle: 'deploy',
  steps: [
    {id: 'build-function', name: 'Build Function', type: 'build_function', config: {}},
  ],
}]

Tax calculation

clientSteps: [{
  lifecycle: 'deploy',
  steps: [
    {id: 'create-tax-stub', name: 'Create Tax Stub', type: 'create_tax_stub', config: {}},
  ],
}]

Flow template

clientSteps: [{
  lifecycle: 'deploy',
  steps: [{
    id: 'copy-files', name: 'Copy Files', type: 'include_assets',
    config: {
      inclusions: [{
        type: 'pattern',
        include: ['**/*.flow', '**/*.json', '**/*.toml'],
      }],
    },
  }],
}]

Channel

clientSteps: [{
  lifecycle: 'deploy',
  steps: [{
    id: 'copy-files', name: 'Copy Files', type: 'include_assets',
    config: {
      inclusions: [{
        type: 'pattern',
        baseDir: 'specifications',
        destination: 'specifications',
        include: ['**/*.json', '**/*.toml', '**/*.yaml', '**/*.yml', '**/*.svg'],
      }],
    },
  }],
}]

Tests added

Each spec now has a dedicated build test file asserting:

  • buildConfig.mode is correct
  • clientSteps has the expected step structure
  • The config is JSON-serializable
  • extension.build() produces the correct output on disk (integration)
Test file Covers
ui_extension_build.test.ts bundle_ui + copy_static_assets steps
theme.test.ts build_theme + bundle_theme steps
function_build.test.ts build_function step delegates to buildFunctionExtension
tax_calculation_build.test.ts create_tax_stub writes (()=>{})();
flow_template.test.ts Copies .flow/.json/.toml, not .js/.ts; preserves subdirs
channel.test.ts Copies from specifications/ subdirectory into output/specifications/; filters by extension

Files changed

File Change
models/extensions/extension-instance.ts build() rewritten to use executeStep loop
services/build/extension.ts Removed buildThemeExtension + runThemeCheck import
specifications/ui_extension.ts Added clientSteps
specifications/checkout_post_purchase.ts Added clientSteps
specifications/checkout_ui_extension.ts Added clientSteps
specifications/pos_ui_extension.ts Added clientSteps
specifications/product_subscription.ts Added clientSteps
specifications/web_pixel_extension.ts Added clientSteps
specifications/theme.ts Added clientSteps
specifications/function.ts Added clientSteps
specifications/tax_calculation.ts Added clientSteps
specifications/flow_template.ts Added clientSteps with include_assets config
specifications/channel.ts New spec — include_assets scoped to specifications/ subdirectory
Test files (6) Build integration tests for all spec types

Measuring impact

How do we know this change was effective? Please choose one:

  • n/a - this doesn't need measurement, e.g. a linting rule or a bug-fix
  • Existing analytics will cater for this addition
  • PR includes analytics changes to measure impact

Checklist

  • I've considered possible cross-platform impacts (Mac, Linux, Windows)
  • I've considered possible documentation changes

Copy link
Contributor Author

alfonso-noriega commented Feb 19, 2026

Warning

This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
Learn more

This stack of pull requests is managed by Graphite. Learn more about stacking.

@alfonso-noriega alfonso-noriega changed the title Abstract build steps to externalize the build configuration Refactor extension build process to use modular build steps Feb 19, 2026
@alfonso-noriega alfonso-noriega force-pushed the 01-build-steps-infrastructure branch from 64d581f to c4c3353 Compare February 19, 2026 13:05
@alfonso-noriega alfonso-noriega force-pushed the 02-wire-build-config-into-extension-specs branch from 1c5c718 to 3ac919f Compare February 19, 2026 13:05
@alfonso-noriega alfonso-noriega force-pushed the 01-build-steps-infrastructure branch from c4c3353 to d2a2b21 Compare February 19, 2026 13:05
@alfonso-noriega alfonso-noriega force-pushed the 02-wire-build-config-into-extension-specs branch 2 times, most recently from 2b6f7ba to 68c2a9f Compare February 19, 2026 13:20
@alfonso-noriega alfonso-noriega force-pushed the 01-build-steps-infrastructure branch from d2a2b21 to 3837d71 Compare February 19, 2026 13:27
@alfonso-noriega alfonso-noriega force-pushed the 02-wire-build-config-into-extension-specs branch 2 times, most recently from 8096aa6 to f6ae0a0 Compare February 19, 2026 13:36
@alfonso-noriega alfonso-noriega force-pushed the 01-build-steps-infrastructure branch 2 times, most recently from 794d3e0 to 75deab1 Compare February 19, 2026 13:53
@alfonso-noriega alfonso-noriega force-pushed the 02-wire-build-config-into-extension-specs branch from f6ae0a0 to 1b503e0 Compare February 19, 2026 13:53
@alfonso-noriega alfonso-noriega force-pushed the 01-build-steps-infrastructure branch from 75deab1 to feab6d2 Compare February 19, 2026 14:20
@alfonso-noriega alfonso-noriega force-pushed the 02-wire-build-config-into-extension-specs branch from 1b503e0 to 01cfc8b Compare February 19, 2026 14:20
@alfonso-noriega alfonso-noriega force-pushed the 01-build-steps-infrastructure branch from feab6d2 to 27b8cfc Compare February 19, 2026 14:33
@alfonso-noriega alfonso-noriega force-pushed the 02-wire-build-config-into-extension-specs branch from 01cfc8b to 048e70a Compare February 19, 2026 14:33
@alfonso-noriega alfonso-noriega force-pushed the 01-build-steps-infrastructure branch from 27b8cfc to 34005f5 Compare February 19, 2026 15:31
@alfonso-noriega alfonso-noriega force-pushed the 02-wire-build-config-into-extension-specs branch from 048e70a to c25acc0 Compare February 19, 2026 15:31
@alfonso-noriega alfonso-noriega force-pushed the 02-wire-build-config-into-extension-specs branch 5 times, most recently from 4dd6917 to 56f0d4a Compare February 20, 2026 12:51
@github-actions
Copy link
Contributor

github-actions bot commented Feb 20, 2026

Coverage report

St.
Category Percentage Covered / Total
🟡 Statements 78.94% 14596/18490
🟡 Branches 73.19% 7263/9923
🟡 Functions 79.17% 3717/4695
🟡 Lines 79.29% 13794/17396

Test suite run success

3860 tests passing in 1486 suites.

Report generated by 🧪jest coverage report action from 4061625

@alfonso-noriega alfonso-noriega marked this pull request as ready for review February 20, 2026 13:10
@alfonso-noriega alfonso-noriega requested a review from a team as a code owner February 20, 2026 13:10
@alfonso-noriega alfonso-noriega force-pushed the 02-wire-build-config-into-extension-specs branch from 8b922b5 to 38a9028 Compare March 4, 2026 15:14
import {bundleThemeExtension, copyFilesForExtension} from '../../services/extensions/bundle.js'
import {ExtensionBuildOptions, bundleFunctionExtension} from '../../services/build/extension.js'
import {bundleThemeExtension} from '../../services/extensions/bundle.js'
import {BuildContext, executeStep} from '../../services/build/build-steps.js'

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Import target for build steps points to non-existent module (runtime crash)

ExtensionInstance imports BuildContext/executeStep from ../../services/build/build-steps.js, but that file does not exist in the repo. The actual implementation is packages/app/src/cli/services/build/client-steps.ts, which exports executeStep and BuildContext. This will fail Node ESM module resolution at runtime, breaking any command path that loads extension-instance.ts (build, deploy, etc.).

context.stepResults.set(step.id, result)

if (!result.success && !step.continueOnError) {
throw new Error(`Build step "${step.displayName}" failed: ${result.error?.message}`)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Step shape mismatch: executor expects name, configs/build loop use displayName

The build loop references step.displayName for error messages, while the executor (client-steps.ts) expects a ClientStep with name and logs using step.name. Updated step specs appear to use displayName rather than name, which can result in logs like Executing step: undefined and unreliable routing/error reporting. If TypeScript is strictly enforced, this should fail typechecking; otherwise it degrades observability and debugging.

@alfonso-noriega alfonso-noriega force-pushed the 02-wire-build-config-into-extension-specs branch from 38a9028 to a5303fd Compare March 4, 2026 16:01
@alfonso-noriega alfonso-noriega force-pushed the 01-build-steps-infrastructure branch 2 times, most recently from 1fa9fa8 to 174ca57 Compare March 4, 2026 16:12
@alfonso-noriega alfonso-noriega force-pushed the 02-wire-build-config-into-extension-specs branch from a5303fd to 0945d47 Compare March 4, 2026 16:12
@alfonso-noriega alfonso-noriega force-pushed the 01-build-steps-infrastructure branch from 174ca57 to 9964055 Compare March 4, 2026 16:16
@alfonso-noriega alfonso-noriega force-pushed the 02-wire-build-config-into-extension-specs branch from 0945d47 to c468483 Compare March 4, 2026 16:16
@alfonso-noriega alfonso-noriega force-pushed the 01-build-steps-infrastructure branch from 9964055 to d835caf Compare March 4, 2026 16:22
@alfonso-noriega alfonso-noriega force-pushed the 02-wire-build-config-into-extension-specs branch from c468483 to 067c371 Compare March 4, 2026 16:22
@alfonso-noriega alfonso-noriega changed the base branch from 01-build-steps-infrastructure to graphite-base/6867 March 4, 2026 16:29
@alfonso-noriega alfonso-noriega force-pushed the 02-wire-build-config-into-extension-specs branch from 067c371 to be84850 Compare March 4, 2026 16:32
@alfonso-noriega alfonso-noriega changed the base branch from graphite-base/6867 to 01-build-steps-infrastructure March 4, 2026 16:32
@alfonso-noriega alfonso-noriega force-pushed the 02-wire-build-config-into-extension-specs branch from be84850 to dd1d204 Compare March 4, 2026 16:38
@alfonso-noriega alfonso-noriega force-pushed the 01-build-steps-infrastructure branch from 57a0790 to 316c2ab Compare March 4, 2026 16:38
@alfonso-noriega alfonso-noriega force-pushed the 02-wire-build-config-into-extension-specs branch 6 times, most recently from e90a1b3 to 93493c9 Compare March 5, 2026 08:46
@binks-code-reviewer
Copy link

⚠️ Findings outside the diff

These findings are in files not modified by this PR and cannot be posted as inline comments.


packages/app/src/cli/models/extensions/extension-instance.ts:144outputFileName behavior changed for copy_files

outputFileName previously returned '' for mode === 'copy_files' || mode === 'theme'. Now it returns '' only for hosted_app_home and theme.

For copy_files extensions, outputFileName will now be ${handle}.js, which changes default outputPath calculation in the constructor (for non-esbuild types) and may affect bundling/copy destinations. Some code (like include_assets_step) has logic to treat outputPath as either a file or directory based on extname(outputPath), so a *.js outputPath can flip behavior from “directory output” to “file output’s parent directory”.

Impact:

  • User impact: copy_files extensions may copy files into dist/ unexpectedly or into a parent dir rather than the intended folder, depending on how outputPath is used downstream.
  • Scale: Affects all copy_files specs unless they override outputPath manually.
  • Consequences: Missing files in uploaded bundles, incorrect directory structure in artifacts.

@alfonso-noriega alfonso-noriega force-pushed the 01-build-steps-infrastructure branch from 316c2ab to a9d3692 Compare March 5, 2026 12:01
@alfonso-noriega alfonso-noriega force-pushed the 02-wire-build-config-into-extension-specs branch from 93493c9 to af69f6c Compare March 5, 2026 12:01
break
const steps = clientSteps
.filter((lifecycle) => lifecycle.lifecycle === 'deploy')
.flatMap((lifecycle) => lifecycle.steps)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Crash when specification.clientSteps is missing/undefined

ExtensionInstance.build() now does:

const steps = clientSteps
  .filter((lifecycle) => lifecycle.lifecycle === 'deploy')
  .flatMap((lifecycle) => lifecycle.steps)

If this.specification.clientSteps is undefined for any extension type (including internal/legacy specs not updated in this PR), this will throw: TypeError: Cannot read properties of undefined (reading 'filter').

Impact:

  • Extension builds/deploys will hard-fail for any extension type missing clientSteps, blocking development and deployments.
  • CI/release pipelines that build extensions will fail; could break deploys for affected extension types.
  • Potentially broad depending on how many specs are migrated; PR only updates some specs so risk is high.
  • Could cause widespread deployment failures.

@alfonso-noriega alfonso-noriega force-pushed the 01-build-steps-infrastructure branch from a9d3692 to ce4a75e Compare March 5, 2026 12:48
@alfonso-noriega alfonso-noriega force-pushed the 02-wire-build-config-into-extension-specs branch from af69f6c to 4061625 Compare March 5, 2026 12:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant