Skip to content

feat: resolve placeholders from consumer dependency pins - #15

Merged
kboshold merged 4 commits into
mainfrom
feat/8-placeholder-from-dependency
Aug 14, 2026
Merged

feat: resolve placeholders from consumer dependency pins#15
kboshold merged 4 commits into
mainfrom
feat/8-placeholder-from-dependency

Conversation

@kboshold

Copy link
Copy Markdown
Collaborator

Contributes to sidebase/base-config#8

What

A placeholder in a payload manifest can now declare fromDependency: "<package>". At render time it resolves to the consuming repo's actual pin for that package, stripped to its range floor (^6.19.3 becomes 6.19.3). Resolution order per placeholder: explicit config value, then the dependency floor, then the static default. Only full x.y.z floors count (prereleases included); partial pins like ^6, protocol specs, and absent deps fall back to the default so a rendered file never carries a non-reproducible version.

Along the way:

  • Placeholder substitution now runs to a fixpoint instead of a single manifest-order pass, so a placeholder value may contain another placeholder's token regardless of declaration order. A cycle fails loud with CONFIG_INVALID naming the file and the tokens involved.
  • The CLI accepts payload schemaVersion 2 and 3. fromDependency requires 3: an old CLI meeting a v3 payload still gets the clean "upgrade the CLI" message, and the new CLI rejects fromDependency under a v2 manifest so a payload author cannot forget the bump.
  • sync, check, and status all feed the same dependency map into rendering (read once per run), so the three commands compose identical bytes.

Why

base-config's Dockerfile hardcodes ARG PRISMA_VERSION=6.19.1 while consumers pin their own prisma version; the two drift apart silently and a consumer ahead of the baseline boots its container with an older Prisma CLI than it runs everywhere else (sidebase/base-config#8). With this, the payload declares the intent once and every consumer's rendered ARG tracks its own pin, with --build-arg and a config knob still available as overrides.

Renders of schemaVersion-2 payloads are byte-identical to the current engine (verified by rendering the base-config fixtures through both engines and comparing tree hashes). Two behavior notes for the release description: sync now reads package.json even with versionSync: false, so a malformed package.json fails sync before any write instead of after; and the SCHEMA_UNSUPPORTED error's supported detail is now an array of versions instead of a single number.

The CLI now accepts schemaVersion {2, 3} instead of a single version, so v2
payloads keep parsing. Placeholders may declare `fromDependency`; a payload
using it while declaring schemaVersion 2 is rejected in loadPresetManifest,
because zod strictness alone only protects older CLIs.
resolvePlaceholder gains a middle step between the config value and the static
default: a placeholder declaring `fromDependency` takes the consumer's pin for
that package, floored via the now-exported parseRangeMin. Only full x.y.z(-pre)
floors are used — a partial core like `^6` would render different bytes as the
registry moves, so it falls through to the default. The dependency map arrives
as data through a new optional renderFile param, so rendering stays pure.
A placeholder value carrying another placeholder's token used to resolve only if
the manifest happened to declare the keys in the right order. Substitution now
repeats until the output is stable, capped at 10 passes; on the cap it throws
CONFIG_INVALID naming the file and every token in the cycle, found by following
the values rather than reading the last pass's leftovers (a cycle leaves only
one member behind at a time). Values are resolved and pattern-checked once, up
front, and still land verbatim through the callback replacement form.
sync, check/drift and status now build the same package.json dependency map once
per run (dependencies shadowing devDependencies) and thread it through compose
into renderFile, so all three commands compose identical bytes and a check after
a sync stays clean. sync reads package.json before the plan pass instead of
after it and no longer skips the read when versionSync is off, since rendering
needs the map either way — a malformed package.json therefore fails before any
managed write on that path too.
@kboshold
kboshold merged commit 136a88e into main Aug 14, 2026
8 checks passed
@kboshold
kboshold deleted the feat/8-placeholder-from-dependency branch August 14, 2026 09:06
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