Scaffold Node/TypeScript Project
Generate a production-ready Node/TypeScript project following established CI/CD patterns. Read the scaffold-project skill first for standard files (README, AGENTS.md, LICENSE, CONTRIBUTING.md, llms.txt).
When to Use
- Creating a new Node.js CLI, library, web app, or static site
- Adding CI/CD to an existing Node/TypeScript project missing workflows
- Standardizing a Node project to match org conventions
Generated Files
.github/workflows/ci.yml
name: CI
on:
pull_request:
branches: [main]
workflow_call:
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
- run: pnpm install --frozen-lockfile
- name: Check formatting & lint
run: pnpm exec biome check
typecheck:
name: Type Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm exec tsc --noEmit
build:
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm run build
test:
name: Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm test
.github/workflows/release.yml
name: Release
on:
push:
branches: [main]
workflow_dispatch:
concurrency:
group: release
cancel-in-progress: false
permissions:
contents: write
jobs:
ci:
if: github.actor != 'sr[bot]'
uses: ./.github/workflows/ci.yml
release:
needs: ci
runs-on: ubuntu-latest
steps:
- name: Generate app token
id: app-token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.SR_RELEASER_APP_ID }}
private-key: ${{ secrets.SR_RELEASER_PRIVATE_KEY }}
repositories: ${{ github.event.repository.name }}
- uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ steps.app-token.outputs.token }}
- uses: urmzd/sr@v8
id: sr
with:
github-token: ${{ steps.app-token.outputs.token }}
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} # needed only if sr.yaml declares publish: { type: npm }
outputs:
released: ${{ steps.sr.outputs.released }}
tag: ${{ steps.sr.outputs.tag }}
version: ${{ steps.sr.outputs.version }}
To enable npm publishing, add publish: { type: npm, workspace: true, access: public } to sr.yaml (below). The sr npm publisher auto-detects pnpm/npm/yarn from the lockfile.
sr.yaml
See sync-release skill for full sr.yaml reference.
git:
tag_prefix: "v"
floating_tag: true
commit:
types:
minor: [feat]
patch: [fix, perf, refactor]
none: [docs, revert, chore, ci, test, build, style]
changelog:
file: CHANGELOG.md
packages:
- path: .
version_files:
- package.json
stage_files:
- pnpm-lock.yaml
# Uncomment to publish to npm (the npm publisher auto-detects pnpm/yarn
# from the lockfile and runs the tool's native recursive publish):
# publish:
# type: npm
# workspace: true # pnpm publish -r / npm publish --workspaces
# access: public # needed for scoped @org/pkg on first publish
package.json scripts
{
"scripts": {
"build": "tsc",
"test": "vitest run",
"lint": "biome check",
"fmt": "biome check --write",
"typecheck": "tsc --noEmit",
"check": "pnpm run fmt && pnpm run lint && pnpm run typecheck && pnpm test",
"record": "teasr showme",
"dev": "tsc --watch"
}
}
Use pnpm run <task> for all operations. pnpm is the package manager.
.envrc
use fnm # Loads the version pinned in .nvmrc
PATH_add node_modules/.bin
biome.json
{
"$schema": "https://biomejs.dev/schemas/2.0.0/schema.json",
"vcs": {
"enabled": true,
"clientKind": "git",
"useIgnoreFile": true
},
"formatter": {
"enabled": true,
"indentStyle": "space",
"indentWidth": 2,
"lineWidth": 100
},
"linter": {
"enabled": true,
"rules": {
"recommended": true
}
}
}
Variants
Static Sites (Astro, Next.js)
- Replace
pnpm testwithpnpm run check(framework-specific checks likeastro check) - Add build artifact upload for deployment workflows
- Add deploy job if using AWS S3/CloudFront (see
scaffold-terraformfor infra)
Monorepo Variant (Turbo)
For multi-package repos, add turbo for cached, parallel task execution:
pnpm add turbo --save-dev -wat workspace root- Add
turbo.jsonwith pipeline definitions (build,test,lint,typecheck) - Replace direct
pnpm runcalls withturbo runin scripts and CI - Turbo caches task outputs; subsequent runs skip unchanged packages
{
"$schema": "https://turbo.build/schema.json",
"tasks": {
"build": { "dependsOn": ["^build"], "outputs": ["dist/**"] },
"test": { "dependsOn": ["build"] },
"lint": {},
"typecheck": {}
}
}
Gotchas
- Use
pnpm install --frozen-lockfile(notpnpm install) in CI for reproducible installs biome checkhandles both formatting and linting in one passbiome check --writeto auto-fix (replacesprettier --write+eslint --fix)- Node 22 is the current LTS. Use
node-version: 22 cache: pnpmin setup-node handles caching automaticallypnpm/action-setup@v4must come beforeactions/setup-nodein CI- For framework-specific type checks (e.g.,
astro check), add a separatecheckjob cancel-in-progress: falseon release workflow to prevent partial releases- For monorepos, use
turbo run <task>instead of running tasks per-package manually