Skip to content

chore(build): give the shared build config one home each - #60

Merged
Azerothian merged 1 commit into
mainfrom
chore/build-plumbing
Sep 1, 2026
Merged

chore(build): give the shared build config one home each#60
Azerothian merged 1 commit into
mainfrom
chore/build-plumbing

Conversation

@Azerothian

Copy link
Copy Markdown
Owner

Group 2 of the condense/dedupe/optimise pass. Pure plumbing — no behaviour change, no source touched.

Three pieces of per-package config that had been copy-pasted nine times with nothing keeping the copies in step.

SWC config: 18 files → 2 + one override

16 of the 18 .swcrc / .swcrc-cjs files were the same settings byte-for-byte; nestize held the only real difference (legacy decorators). The shared pair moves to the repo root, reached via --config-file — the mechanism the CJS build already used.

swc-build.ts prefers a local .swcrc when one exists, so the exception lives next to the package it belongs to rather than in a list elsewhere.

Note for future readers: SWC has no extends in .swcrc. It rejects the key and enumerates the fields it accepts. I tried it first; --config-file is the supported route.

SWC flags: 9 copies → 1

This was repeated verbatim in nine manifests:

swc src --out-dir publish/lib --out-file-extension mjs --strip-leading-paths -s --ignore "**/*.d.ts"

One package quietly losing a flag would produce a subtly different published artifact. Now in scripts/build/swc-build.ts, beside fix-esm-extensions and prepare-package, which were already shared.

Jest config: 203 lines → 32 + a partial base

Only the resolution/compilation block and the coverage block were genuinely common — and the four-line collectCoverageFrom comment had nine homes, so nine chances to stop being true.

The base is deliberately partial. An earlier pass collapsed all nine configs to one call and broke four suites. These stay per-package because they are load-bearing:

Setting Why
maxWorkers: 1 suites share a process-global sequelize/model registry
forceExit ioredis holds handles past the last suite
TEMPORALIZE_INTEGRATION gate that suite downloads a real Temporal server
decorator transform nestize only — Nest DI needs the metadata

Dependency versions: pnpm catalog

27 toolchain packages pinned once instead of nine times. There was no drift — it was maintained by hand, which is precisely the reason to make it structural.

devDependencies only, and that boundary matters. prepare-package.ts strips devDependencies from the published manifest but does not resolve catalog: — so a catalog reference in dependencies or peerDependencies would publish verbatim and break installs. Anything that is also a runtime or peer dep is excluded, and the peer ranges are deliberately wider than the dev pin beside them (graphql ^17.0.0 vs 17.0.2, sequelize ^6.35.1 vs ^6.37.7) — a single shared version would silently narrow them.

Verification

  • Full pnpm build across all 9 packages, artifacts compared before/after: decorators present in both ESM and CJS output, ESM specifiers rewritten, CJS still CJS
  • pnpm install --frozen-lockfile resolves
  • Coverage denominators unmoved — a broken collectCoverageFrom reports less, it does not fail, so this was checked explicitly
  • 1518 tests pass, pnpm typecheck and pnpm lint clean

Stacked on top of #59 conceptually but independent — this branch is off main and touches no source files.

🤖 Generated with Claude Code

https://claude.ai/code/session_019fGumVzMfMXDZ5vS7PJGDW

Three pieces of per-package plumbing that had been copy-pasted nine times, with
no mechanism keeping the copies in step.

SWC config. Sixteen of the eighteen `.swcrc` / `.swcrc-cjs` files were byte-for-
byte the same settings; `nestize` held the only real difference (legacy
decorators). The shared pair now lives at the repo root and is reached with
`--config-file`, which is the mechanism the CJS build already used. A package
that needs its own keeps it, and `swc-build.ts` prefers a local file when one
exists — so the exception lives next to the package it belongs to rather than in
a list somewhere else.

Note for anyone tempted by the tidier spelling: SWC has no `extends` in
`.swcrc`. It rejects the key outright and enumerates the fields it does accept.
`--config-file` is the supported route.

SWC flags. The invocation was repeated verbatim in nine manifests, including
`--out-file-extension mjs --strip-leading-paths -s --ignore "**/*.d.ts"`, where
one package quietly losing a flag would produce a subtly different published
artifact. It now lives once in `scripts/build/swc-build.ts`, beside the
`fix-esm-extensions` and `prepare-package` scripts that were already shared.
Verified byte-identical output before and after, per package and per format.

Jest config. Only the resolution/compilation block and the coverage block were
genuinely common — and the four-line comment explaining `collectCoverageFrom`
had nine homes, so nine chances to stop being true. Those are now
`baseProject` / `coverage` / `conventions` in `scripts/jest/base-config.js`.

Everything else stays where it is, because it is load-bearing and specific:
`maxWorkers: 1` wherever suites share a process-global sequelize/model registry,
`forceExit` where ioredis holds handles past the last suite, the
`TEMPORALIZE_INTEGRATION` gate on a suite that downloads a Temporal server, and
nestize's decorator transform. An earlier pass at this collapsed all nine to one
call and broke four suites; the base is deliberately partial as a result.

Dependency versions. 27 toolchain packages move to a pnpm catalog, so
`@swc/core`, `jest`, `tsx` and `typescript` are pinned once instead of nine
times. There was no drift — it was maintained by hand, which is the reason to
make it structural.

devDependencies only, and that boundary is load-bearing: `prepare-package.ts`
strips devDependencies from the published manifest but does not resolve
`catalog:`, so a catalog reference in `dependencies` or `peerDependencies` would
be published verbatim and break installs. Anything that is also a runtime or
peer dependency is excluded for that reason — and the peer ranges are
deliberately wider than the dev pin beside them (`graphql` `^17.0.0` against
`17.0.2`, `sequelize` `^6.35.1` against `^6.37.7`), which a single shared version
would silently narrow.

No behaviour change. Verified: full build across all nine packages, artifacts
compared (decorators present in both formats, ESM specifiers rewritten, CJS
still CJS), `pnpm install --frozen-lockfile` resolves, and coverage denominators
are unmoved — a broken `collectCoverageFrom` reports less rather than failing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019fGumVzMfMXDZ5vS7PJGDW
@Azerothian
Azerothian merged commit 9d8759f into main Sep 1, 2026
1 check passed
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