Skip to content

Add support for the extends keyword - #1306

Open
Sam Frost (SamuelFrost) wants to merge 5 commits into
devcontainers:mainfrom
SamuelFrost:extends
Open

Sam Frost (SamuelFrost) wants to merge 5 commits into
devcontainers:mainfrom
SamuelFrost:extends

Conversation

@SamuelFrost

@SamuelFrost Sam Frost (SamuelFrost) commented Sep 18, 2026 •

Copy link
Copy Markdown

Summary

Implements configuration inheritance from spec#22: a devcontainer.json can inherit another JSON or JSONC file in the same repository via extends, with optional extendsMergeMode (combine | override).

This reapplies PR #311 on current main. The default merge path follows review on CLI #311: reuse image metadata merge logic, not deepmerge.

Companion spec PR: devcontainers/spec#772

Does not bump the package version (release PR separately).

Changes

  • readDevContainerConfigObject (configContainer.ts): parse config, resolve extends recursively with cycle detection (seen), validate path and merge mode, merge parent + current file, strip extends / extendsMergeMode from the resolved object before substitution and workspace setup in readDevContainerConfigFile.
  • extends: relative path only (resolved from the declaring file); absolute paths and URL-like schemes rejected; stripped from merged output.
  • extendsMergeMode (on the file that declares extends, default combine):
    • combine: same rules as image metadata merge (forwardPorts union, hostRequirements max, object map key merge, and so on).
    • override: all set top-level keys replace values from extends target; omitted keys keep the resolved parent.
  • mergeDevContainerConfigs (imageMetadata.ts): shared merge implementation for extends and image-metadata layering.
  • Types: DevContainerExtendsMergeMode and optional extends / extendsMergeMode on DevContainerConfig (configuration.ts).
  • Tests:
    • src/test/configContainer.test.ts — readDevContainerConfigFile with fixtures under src/test/configs/extends/ (default combine, nested chain, cycle, missing parent, invalid merge mode, override mode).
    • src/test/imageMetadata.test.ts — top-level mergeDevContainerConfigs unit tests (combine and override), sibling to describe('Image Metadata') so they do not run the Docker/npm install hook.
  • CHANGELOG: Unreleased entry.

Technical choices (from CLI #311 review)

Christof Marti, Josh Spicer, and Chuck Lantz asked to align extends with image metadata merge for the default path so inheriting a file matches combining a prebuilt image’s metadata with a project devcontainer.json.

Relative-path-only matches spec#22 (same repository). Nested extends is supported with cycle detection (CLI #311 left nested references unresolved).

Test plan

  • env TS_NODE_PROJECT=src/test/tsconfig.json npx mocha -r ts-node/register --exit src/test/configContainer.test.ts
  • env TS_NODE_PROJECT=src/test/tsconfig.json npx mocha -r ts-node/register --exit src/test/imageMetadata.test.ts -g mergeDevContainerConfigs
  • yarn type-check
  • yarn lint
  • CI: configContainer.test.ts runs in the catch-all tests-matrix shard (src/test/**/*.test.ts with excludes); no dedicated matrix row for extends.

Additional decisions (beyond original CLI #311 and changes requested during the review)

Decision Commit
Add extendsMergeMode (combine default, override) instead of merge-only behavior 7c7d1ca c4e9697

Follow-up

After maintainer approval, CONTRIBUTING.md asks for a docs PR to the devcontainer.json reference in vscode-docs.

Implements spec#22. Supersedes CLI #311.

Allow one devcontainer.json to inherit another using the existing image metadata merge logic, rebasing the approach from spec#22 and CLI#311 onto current main.
@SamuelFrost

Copy link
Copy Markdown
Author

@microsoft-github-policy-service agree

Optional extendsMergeMode on devcontainer.json selects image-metadata
combine (default) or overlay-style override when resolving extends.
Cover override merge in configContainer tests and devcontainer up
against the packaged CLI, with lightweight ubuntu fixtures and CI matrix entry.
Comment thread src/spec-node/configContainer.ts
Comment thread src/spec-node/imageMetadata.ts
Override merge now fully replaces each set top-level property from the
child config instead of shallow-merging object maps and hostRequirements.
Comment thread src/spec-node/imageMetadata.ts
Remove extends up e2e tests and CI matrix entry; slim extends fixtures and colocate mergeDevContainerConfigs tests with imageMetadata.
Comment thread src/test/configs/extends/.devcontainer.base.json
Comment thread src/test/configs/extends/.devcontainer.cycle-a.json
Comment thread src/test/configs/extends/.devcontainer.cycle-b.json
Comment thread src/test/configs/extends/.devcontainer.invalid-merge.json
Comment thread src/test/configs/extends/.devcontainer.json
Comment thread src/test/configs/extends/.devcontainer.missing.json
Comment thread src/test/configs/extends/.devcontainer.nested.json
Comment thread src/test/configs/extends/.devcontainer.override.json
Comment thread src/test/configContainer.test.ts
Comment thread src/test/imageMetadata.test.ts
@SamuelFrost
Sam Frost (SamuelFrost) marked this pull request as ready for review September 24, 2026 08:53
@SamuelFrost
Sam Frost (SamuelFrost) requested a review from a team as a code owner September 24, 2026 08:53
@SamuelFrost

Sam Frost (SamuelFrost) commented Sep 24, 2026 •

Copy link
Copy Markdown
Author

Requesting review from Christof Marti (@chrmarti), joshspicer, Samruddhi Khandale (@samruddhikhandale), Chuck Lantz (@Chuxel), and @devcontainers/maintainers when you have bandwidth.

This implements spec#22 / supersedes CLI #311: extends JSON/JSONC and optional extendsMergeMode (combine default, override).

Behavior (short):

  • combine (default): same merge rules as image metadata (mergeDevContainerConfigs in imageMetadata.ts), per earlier CLI #311 discussion.
  • override: all set top-level keys replace values from extends target; omitted keys keep the resolved parent.

Companion spec PR: devcontainers/spec#772

Where to look:

  • json file reading resolution: readDevContainerConfigObject in configContainer.ts.
  • merge logic: mergeDevContainerConfigs in imageMetadata.ts (combine vs override).
  • tests: configContainer.test.ts + fixtures under src/test/configs/extends/; unit merge tests in imageMetadata.test.ts (mergeDevContainerConfigs).
    (succinct AI generated code explanations are also provided for more context as resolved comment threads on PR changes for reviewer convenience)

Happy to adjust anything that should match maintainer expectations before spec PR #772 merges. Thanks!

This branch has not been deployed

No deployments
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