v0.3.0
Released: May 3, 2026
Node.js 22 floor + sync robustness
Two upgrades aimed at a smoother first-run experience.
Node.js 22 minimum (breaking)
Templates and generated dockerfiles already required Node 22, but the init prerequisite check let Node 20 through, which produced a misleading cascade of errors mid-install. The check now matches reality.
If you're on Node 20: upgrade to 22+ before running npx tsdevstack init.
- nvm:
nvm install 22 && nvm use 22 - Homebrew:
brew install node@22
The four CLI plugins (@tsdevstack/cli, cli-cloud-secrets, cli-infra, cli-mcp) now declare engines: { node: ">=22.0.0" } so npm itself warns before our runtime check fires. Generated Next.js / NestJS dockerfiles and the AWS db-init / db-delete generators all build on node:22 images now.
sync actually waits for databases on cold start
The generated postgres and redis services now declare healthchecks (pg_isready and redis-cli ping respectively). docker compose up --wait previously returned the moment a container process started — not when the service was ready to accept connections — which on first run could let prisma migrate fire before initdb finished, producing a misleading P1010 "denied access" error. With healthchecks declared, --wait blocks until services are actually up.
No action needed — the next npx tsdevstack sync regenerates compose with healthchecks and you're done.
init no longer claims success after silent failures
npx tsdevstack init previously printed "🎉 Project created successfully!" even if npm install, build, sync, or the initial migration had failed mid-run. It now tracks per-step failures and switches to a "created with issues" summary listing what failed and how to recover. The happy path is unchanged.
Private npm packages — auto-detected, no config
If your project depends on private npm packages — internal company scopes, private GitHub Packages, restricted-access third-party SDKs — tsdevstack now wires authentication through the entire pipeline automatically. Same flow locally and in CI, identical across GCP, AWS, and Azure.
Trigger: commit an .npmrc at your project root with ${NPM_TOKEN}-style env-var interpolation:
.npmrc, not gitignore it
The file contains no actual secret — ${NPM_TOKEN} is an env-var reference that npm interpolates at install time. Committing the template is the standard npm-ecosystem pattern. The reason .npmrc is sometimes gitignored is to catch npm login-generated files that contain real tokens; a hand-written template with ${NPM_TOKEN} is safe to commit.
If your .gitignore blanket-excludes **/.npmrc, add an exception:
Setup:
- Locally:
export NPM_TOKEN=…in your shell (or in~/.zshrc/~/.bashrc). If you skip this,npm installfails with a404 Not Foundon private packages — that's the symptom of an unsetNPM_TOKEN, not a genuinely missing package. (Project.npmrcoverrides home.npmrcfor the same registry, so even a working~/.npmrcwon't help — the project file with empty interpolation wins.) - In CI: add
NPM_TOKENas a single GitHub repository secret (one value, not per-environment).
That's it. Run npx tsdevstack infra:generate-docker and infra:generate-ci and the wiring lands automatically.
What it does for you
- Generated Dockerfiles include
.npmrcin the deps stage and emit a BuildKit env-source secret mount onnpm ci: infra:build-dockerappends--secret id=npm_token,env=NPM_TOKENto the docker build invocation.NPM_TOKENis read from your shell env viaprocess.env.- Generated CI workflows expose
NPM_TOKENat the job level on every job that runsnpm cior invokes tsdevstack:
Workflows that aren't produced by infra:generate-ci — your own publish pipelines, doc deploys, third-party CI integrations — won't be touched by tsdevstack. If any of them run npm ci (or npm publish) on a project with a committed .npmrc, you need to add NPM_TOKEN: ${{ secrets.NPM_TOKEN }} to those jobs' env: block manually. Without it, npm reads the project .npmrc, finds an empty ${NPM_TOKEN} interpolation, and the registry returns 404 Not Found (npm hides "unauthorized" behind 404, so the error is misleading).
Properties
- The token lives only during the
npm ciRUNstep's env — never persisted in image layers, never visible indocker history. - The
.npmrcfile is included in the deps and build intermediate stages but not the production stage, so the final shipped image is clean. The file content itself is just${NPM_TOKEN}template (no actual secret), so even cache layers pushed to a registry leak nothing sensitive. - If
.npmrcis present butNPM_TOKENis unset at build time, BuildKit fails fast with a clear error — misconfiguration is loud, not silent. - If
.npmrcis absent, generated workflows and Dockerfiles emit noNPM_TOKENwiring at all (behavior identical to public-only projects, no opt-out flag needed).
Why a single NPM_TOKEN secret in CI (not per-env)
Registry tokens identify your developer / org account, not a deployment target. The same token works for installs to dev, staging, and prod. Unlike provider credentials (which are per-env via GCP_WIF_DEV, AWS_ROLE_ARN_DEV, etc.), NPM_TOKEN is a single global GitHub repo secret.
Migration for existing projects
If you don't have private npm dependencies, no action needed — behavior is unchanged.
If you do:
- Create
.npmrcat project root with//registry.npmjs.org/:_authToken=${NPM_TOKEN}(or your registry equivalent). - Commit
.npmrcto git. If.gitignoreblanket-excludes**/.npmrc, add an exception (!/.npmrc) so the project-root template is tracked. The file has no actual secret — only the${NPM_TOKEN}env-var reference. export NPM_TOKEN=…locally and addNPM_TOKENas a GitHub repo secret in CI.- Regenerate:
npx tsdevstack infra:generate-docker && npx tsdevstack infra:generate-ci.
See CI/CD Setup — Private npm packages for the full setup walkthrough.
NestJS Dockerfile generator: cleaner production stage
The NestJS Dockerfile generator no longer emits a workspace-level node_modules overlay COPY in the production stage:
The line was a defensive overlay against an older npm-hoisting scenario where Prisma's client could land at the workspace level instead of the root. Under current Prisma versions (the schema's output = "../src/generated/prisma" writes the client into the source tree, not into node_modules) and current npm hoisting behavior, the line never overlaid anything real — the source path doesn't exist in the build stage on cold builds.
When a registry-cached production layer existed (typical for projects already deploying), BuildKit reused the cached layer and the no-op COPY never ran. On cold builds — fresh GCP/AWS/Azure registries, CI runners with no cache, or any path that bypasses --cache-from (e.g. local docker build --no-cache) — BuildKit re-executed the COPY and aborted with failed to compute cache key: … not found. That class of failure is now resolved.
Action
Regenerate Dockerfiles in projects that include hasDatabase: true services:
Then redeploy normally (npx tsdevstack infra:deploy --env <env>). Existing deployments don't need to be redeployed unless you're cutting a fresh registry — in which case the regenerated Dockerfiles are required.