Context
Products install@stackra/* from their own repositories (0023), so packages need a
registry. Today every member’s exports points at ./src and validate-repository.mjs enforces it; a consumer outside the
workspace needs compiled JavaScript and declarations instead. Runtimes (Node, Encore, Metro, Vite) must load one
artifact, and a second module format would load @stackra/container twice and split its DI tokens (the dual-package hazard).
0013 also versions and tags private apps and services, which gives deployables a
version number they are never deployed by.
Decision
- The registry is
npm.figentra.com, a private npm registry (npflared) running on Cloudflare Workers. The@stackrascope maps to it. Publishing to public npm is a later option, not part of this decision. - The publish token is a CI secret. The release job obtains it from Infisical
/ciover OIDC (0009). No token appears in.npmrcor any other file. - Packages are not consumed as source. Every package builds with tsdown to ESM-only
dist/*.mjsplus.d.mts. There is no CJS output. Runtimes (Node, Encore, Metro, Vite) always loaddist.- A custom export condition
@stackra/source, listed first in eachexportsentry, points at./src. TypeScript (customConditions), Vitest and Vite set it to read source for fast feedback; nothing that ships sets it. - A package’s
config/directory (outsidesrc, Laravel-style publishable defaults) is compiled and exported as./config. - A package’s static
resources/directory is exported as./resources/*. - The
validate-repository.mjsrule that everyexportstarget starts with./src/must change to accept this shape (planned).
- A custom export condition
- Changesets versions packages only. Apps and services are not versioned: they deploy by commit SHA.
privatePackagesin.changeset/config.jsonbecomes{ "version": false, "tag": false }. - 0013 decisions 1, 2, 4, 5 and 6 carry forward unchanged (changesets owns versions,
CHANGELOG.mdandLICENSEin every member, no replaced versions, release notes for breaking releases, a human approves every release).
Consequences
- A publishable package flips
"private": falsewhen it first publishes, as/standards/membersalready says. - Fast feedback still reads source through
@stackra/source; a runtime that forgets the condition loadsdist, so a build is part of running anything outside tests and typecheck, and turbodependsOn: ["^build"]carries it. - A package whose
dist/differs from itssrc/behaviour is a new failure mode; the publish job must test the packed artifact. - Apps and services lose their per-member version and tag. Their release narrative is the deploy log by SHA.
- MIT code on a private registry is consistent (0024): the licence permits reuse; the registry controls distribution.
- The registry is one more Worker to run; it shares the Cloudflare account with the remote cache (0026).
Enforcement
Planned — the release workflow (scope mapping, OIDC token, tsdown build, publish), a packed-artifact smoke test, theprivatePackages change in .changeset/config.json, the @stackra/source condition in the shared tsconfig and vitest
presets, and a rewrite of the exports rule in validate-repository.mjs (today it still demands ./src/ targets;
nothing publishes and nothing checks the dist side).