Skip to content

Comments

Add a build manifest build step#6869

Draft
alfonso-noriega wants to merge 1 commit into03-hosted-static-app-build-pipelinefrom
02-19-add_a_build_manifest_build_step
Draft

Add a build manifest build step#6869
alfonso-noriega wants to merge 1 commit into03-hosted-static-app-build-pipelinefrom
02-19-add_a_build_manifest_build_step

Conversation

@alfonso-noriega
Copy link
Contributor

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

WHY are these changes introduced?

To support the generation of build manifests for UI extensions, which will be used to map extension points to their compiled assets.

Add build_manifest build step

Introduces a new build_manifest step type for the declarative build pipeline. The step generates a JSON manifest that maps logical asset identifiers to their resolved filepaths and source
modules, and writes it to the extension's output directory.


How the step is configured

{
  "id": "write-build-manifest",
  "displayName": "Write Build Manifest",
  "type": "build_manifest",
  "config": {
    "outputFile": "build-manifest.json",
    "forEach": { "tomlKey": "extension_points", "keyBy": "target" },
    "assets": { ... }
  }
}

Top-level fields

Field Type Default Description
outputFile string "build-manifest.json" Output filename, relative to the extension output directory
forEach object When set, iterates a TOML config array and produces one manifest entry per item
assets Record<string, AssetEntry> required Map of asset identifier → asset config

forEach

When forEach is set the step iterates the named config array and writes an array of { [keyBy]: value, build_manifest: { assets } } objects — one per item. This mirrors the shape that
UIExtensionSchema.transform attaches to each extension_point.

Field Description
tomlKey Key in the extension config that points to the array to iterate
keyBy Field on each item used to identify the manifest entry (e.g. "target")

Without forEach the step writes a single { assets } object.


Asset entries

Shorthand — resolves the entire filepath from a single TOML key:

{ "tomlKey": "static_root" }
{ "tomlKey": "static_root", "static": true }

Explicit:

Field Type Description
filepath FilepathValue Required. See below.
module { "tomlKey": string } Optional source module path
static boolean Mark as a static asset
optional boolean Silently skip this asset if filepath or module cannot be resolved

FilepathValue

A filepath can be specified in three forms:

Literal string — used as-is:

"dist/index.js"

TOML key shorthand — whole filepath resolved from the extension config:

{ "tomlKey": "entry_file" }

Composed — assembled as {path/}{prefix}{filename}:

{ "path": "dist", "prefix": { "tomlKey": "handle" }, "filename": ".js" }

The prefix field accepts:

Form Behaviour
Literal string Used as-is, e.g. "static"
{ "tomlKey": "..." } Resolved from the current forEach item first, falls back to the top-level extension config. If the resolved value is an array, the current outer iteration index is used as the prefix.

module resolution

module is always { "tomlKey": string }. In forEach context the key is looked up on the current item first, falling back to the top-level config.

Nested arrays

When a tomlKey path crosses an array at the config level (e.g. extension_points.module), the current outer iteration index is used to pick the right element automatically.

When a tomlKey path crosses a nested array on the current item (e.g. should_render.module where should_render is itself an array), the asset expands into an array in the manifest — one
entry per inner item. The inner index is inserted between the config-defined prefix and the filename to keep filepaths unique.


Examples

Single manifest — static hosted app

{
  "type": "build_manifest",
  "config": {
    "assets": {
      "main": { "tomlKey": "static_root" }
    }
  }
}

Output (build-manifest.json):

{ "assets": { "main": { "filepath": "public" } } }

Per-target manifest — UI extension

{
  "type": "build_manifest",
  "config": {
    "forEach": { "tomlKey": "extension_points", "keyBy": "target" },
    "assets": {
      "main": {
        "filepath": { "prefix": { "tomlKey": "handle" }, "filename": ".js" },
        "module": { "tomlKey": "module" }
      },
      "should_render": {
        "filepath": { "prefix": { "tomlKey": "handle" }, "filename": "-conditions.js" },
        "module": { "tomlKey": "should_render.module" },
        "optional": true
      },
      "tools": {
        "filepath": { "prefix": { "tomlKey": "handle" }, "filename": "-tools.json" },
        "module": { "tomlKey": "tools" },
        "static": true,
        "optional": true
      }
    }
  }
}

Given this TOML:

handle = "my-ext"

[[extension_points]]
target = "purchase.checkout.block.render"
module = "./src/checkout.tsx"

  [extension_points.should_render]
  module = "./src/conditions.tsx"

[[extension_points]]
target = "admin.product-details.action.render"
module = "./src/admin.tsx"

Output (build-manifest.json):

[
  {
    "target": "purchase.checkout.block.render",
    "build_manifest": {
      "assets": {
        "main":          { "filepath": "my-ext.js",            "module": "./src/checkout.tsx" },
        "should_render": { "filepath": "my-ext-conditions.js", "module": "./src/conditions.tsx" }
      }
    }
  },
  {
    "target": "admin.product-details.action.render",
    "build_manifest": {
      "assets": {
        "main": { "filepath": "my-ext.js", "module": "./src/admin.tsx" }
      }
    }
  }
]

should_render is absent from the second entry because should_render.module is not present on that item and the asset is optional: true.


Nested array expansion

When should_render is itself an array of objects, the manifest reflects that structure:

[[extension_points]]
target = "purchase.checkout.block.render"
module = "./src/checkout.tsx"

[[extension_points.should_render]]
module = "./src/conditions-a.tsx"

[[extension_points.should_render]]
module = "./src/conditions-b.tsx"

Output for that entry:

{
  "target": "purchase.checkout.block.render",
  "build_manifest": {
    "assets": {
      "main": { "filepath": "my-ext.js", "module": "./src/checkout.tsx" },
      "should_render": [
        { "filepath": "my-ext-0-conditions.js", "module": "./src/conditions-a.tsx" },
        { "filepath": "my-ext-1-conditions.js", "module": "./src/conditions-b.tsx" }
      ]
    }
  }
}

The inner index is inserted between the config-defined prefix (my-ext) and the filename (-conditions.js), keeping each filepath unique while preserving the array structure of the source
TOML.

The implementation includes:

  • Full test coverage for all resolution scenarios
  • Support for optional assets that are silently skipped when not available
  • Handling of nested arrays in configuration

How to test your changes?

  1. Create an extension with multiple extension points
  2. Add a build_manifest step to its build config
  3. Build the extension and verify the manifest is generated correctly
  4. Check that the manifest contains the expected asset mappings

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

Checklist

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

Copy link
Contributor Author

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 force-pushed the 03-hosted-static-app-build-pipeline branch from 146db7f to e56e3c3 Compare February 20, 2026 10:38
@alfonso-noriega alfonso-noriega force-pushed the 02-19-add_a_build_manifest_build_step branch from a3c7106 to 038b372 Compare February 20, 2026 10:38
@alfonso-noriega alfonso-noriega force-pushed the 03-hosted-static-app-build-pipeline branch from e56e3c3 to ed19082 Compare February 20, 2026 10:46
@alfonso-noriega alfonso-noriega force-pushed the 02-19-add_a_build_manifest_build_step branch 2 times, most recently from 227bbf6 to fe18a99 Compare February 20, 2026 12:29
@alfonso-noriega alfonso-noriega force-pushed the 03-hosted-static-app-build-pipeline branch from ed19082 to d5a9a3f Compare February 20, 2026 12:29
@alfonso-noriega alfonso-noriega force-pushed the 02-19-add_a_build_manifest_build_step branch from fe18a99 to 2ecdc39 Compare February 20, 2026 12:36
@alfonso-noriega alfonso-noriega force-pushed the 03-hosted-static-app-build-pipeline branch 2 times, most recently from c356f95 to d0d8bed Compare February 20, 2026 12:51
@alfonso-noriega alfonso-noriega force-pushed the 02-19-add_a_build_manifest_build_step branch from 2ecdc39 to 6ca3b6b Compare February 20, 2026 12:51
@alfonso-noriega alfonso-noriega force-pushed the 03-hosted-static-app-build-pipeline branch from d0d8bed to 61f38e1 Compare February 20, 2026 13:16
@alfonso-noriega alfonso-noriega force-pushed the 02-19-add_a_build_manifest_build_step branch 2 times, most recently from 510cdf7 to b990700 Compare February 20, 2026 13:45
@alfonso-noriega alfonso-noriega force-pushed the 03-hosted-static-app-build-pipeline branch from 61f38e1 to 7ea9b07 Compare February 20, 2026 13:45
@alfonso-noriega alfonso-noriega force-pushed the 03-hosted-static-app-build-pipeline branch from 7ea9b07 to ea8da6c Compare February 20, 2026 15:00
@alfonso-noriega alfonso-noriega force-pushed the 02-19-add_a_build_manifest_build_step branch from b990700 to 4188667 Compare February 20, 2026 15:00
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