Skip to content

[finding] sync-template-versions.mjs declares its version-stamp targets but exports none of them and has no entry-point guard, so consumers must restate template paths literally #9554

Description

@os-project-manager

Found while implementing #9518 (widening cut-rc.yml's release file surface). Not fixed there — recording only.

What was observed

scripts/sync-template-versions.mjs already models its outputs well. It has a
TEXT_STAMPS table over (file, key, pattern, replacement) whose own header says it is
table-driven "so that adding a fourth declared version surface is a row", and a
findTemplateDirs() that walks TEMPLATE_ROOT — "Deliberately not a curated list."

But none of it is reachable from outside:

  • neither TEXT_STAMPS nor TEMPLATE_ROOT nor findTemplateDirs is exported;
  • the module has no entry-point guard, so the whole sync executes on import.

So a consumer that wants to know "which files does the version pass stamp?" cannot ask.
It has to restate the answer.

Why it cost something

cut-rc.yml's "Build the single version commit" step allowlists the paths a version pass
may write. #9518 widened it, and the docs half was sourced from a declaration rather than
copied: sync-docs-image-tags.mjs imports SURFACES from scripts/check-docs-image-tag.mjs,
which #9064 made import-safe with exactly this kind of entry-point guard:

if(resolve(process.argv[1]??'')===resolve(fileURLToPath(import.meta.url))){ ... }

The template half could not follow the same route, so two paths are spelled literally in
the workflow:

packages/create-objectstack/src/templates/blank/objectstack.config.ts
packages/create-objectstack/src/templates/blank/objectstack.manifest.json

Both hardcode the template name blank. findTemplateDirs() exists precisely because the
template set is not meant to be a curated list — so the day a second template ships, the
walk picks it up, the version pass stamps it, and the workflow's literal pair does not
cover it. That is the same failure #9518 fixed, one template later: the assertion trips and
the cut refuses to push, with nothing red until someone attempts a cut.

That objectstack.manifest.json was missing from the workflow's allowlist at all is the
first instance already realised — #9518's own card named only the three doc surfaces, and
the fourth path was found by measurement rather than by reading any list.

Shape of a fix

Export TEXT_STAMPS (or a derived stampedPaths() returning repo-relative paths across
all template dirs) and add the entry-point guard, mirroring check-docs-image-tag.mjs.
cut-rc.yml would then read both halves of its allowlist from declarations instead of one
half from a declaration and one from literals.

Related but distinct: #9348 records that this same script has no --self-test and runs
nowhere in CI. Different change to the same file; neither subsumes the other.


Generated by Claude Code

Metadata

Metadata

Assignees

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions