Skip to content

torqlab/js-project-template

Repository files navigation

TORQ TypeScript/Node.js Project Template

This is the standardized starter template for all TORQ TypeScript/Node.js projects. It provides complete project scaffolding with:

  • TypeScript strict mode with dual-format builds (ESM + CommonJS)
  • Bun runtime for fast builds, testing, and package management
  • ESLint with TypeScript, immutability, and JSDoc enforcement
  • Prettier auto-formatting (100-char width, integrated with ESLint)
  • Husky git hooks for pre-commit formatting and pre-push validation
  • commitlint enforcing conventional commits with required scope
  • validate-branch-name enforcing semantic branch naming (<type>/<ticket_id>-<description>)
  • semantic-release automated versioning, changelog generation, and npm publishing
  • GitHub Actions CI/CD workflows (test, lint, type-check, build, publish)
  • OIDC Trusted Publishing keyless npm publishing (no stored credentials)

Setup Instructions

Choose your setup path based on your project:

  • Greenfield: Creating a brand new project from scratch
  • Brownfield: Aligning an existing project with this template

Greenfield: New Project Setup

For new projects being created from the template.

1. Create Repository from Template

Use GitHub's template feature or the gh CLI:

# Via GitHub CLI (recommended)
gh repo create <org>/<project-name> \
  --template torqlab/js-project-template \
  --public  # or --private

Or use GitHub web UI: Click "Use this template" → "Create a new repository"

2. Clone and Install Dependencies

git clone https://github.com/<org>/<project-name>.git
cd <project-name>

# Install all dependencies (also installs git hooks via npm prepare hook)
bun install

What bun install does:

  • Installs npm dependencies (ESLint, Prettier, Bun test runner, etc.)
  • Runs npm run prepare automatically (via npm lifecycle hooks)
  • Initializes Husky git hooks in .husky/
  • Validates eslint.config.mjs and .prettierrc

3. Customize Project Metadata

Edit package.json:

  • "name": Project name or scoped name (e.g., @torqlab/my-lib)
  • "description": Short description for npm registry
  • "version": Start at 0.1.0 (semantic-release will manage this)
  • "author": Your name and email
  • "homepage": Link to project repository
  • "repository.url": GitHub repository URL

4. (Optional) Configure .env for Publishing

If you plan to publish to npm, set up GitHub App credentials:

cp .env.example .env

Edit .env:

# GitHub App for authenticated API calls (optional, not needed for OIDC)
GITHUB_APP_ID=your-app-id
GITHUB_APP_PRIVATE_KEY_PATH=/path/to/private-key.pem
GITHUB_INSTALLATION_ID=your-installation-id

Publishing note: GitHub Actions uses OIDC trusted publishing (keyless, secure). The .env is only for local development if needed. No GitHub token required in GitHub Secrets.

5. Setup .claude Symlink (Monorepo Pattern Only)

If this project is part of the torq monorepo:

ln -s ../../.claude .claude

This links to shared skills and configuration. If standalone, skip this step.

6. Configure Branch Rulesets

IMPORTANT: GitHub does not clone branch rulesets from templates. You must configure them manually for each repository.

Go to GitHub repository settings → Rules → Create rule for main branch:

Reference: Use the same rules as torqlab/strava-api:

  • ✅ Restrict deletion
  • ✅ Require linear history (no merge commits)
  • ✅ Require pull request with:
    • Dismiss stale reviews on new push
    • Require review thread resolution
    • Restrict to squash merge only
  • ✅ Required status checks: Build, Check Formatting, Lint, Test

Or apply via GitHub API:

gh api repos/torqlab/strava-api/rulesets/13422315 | \
  gh api repos/<org>/<project>/rulesets --input -

7. Verify Everything Works

# Run all validation checks (same as pre-push hook)
bun run build
tsc --noEmit
bun run test
bun run lint
bun run format:check

# Or just run the pre-push hook manually
npm run prepare  # Reinstall hooks if needed
git push  # Simulated — will run all checks before actual push

Brownfield: Aligning Existing Repos

For projects that already exist and need to be aligned with this template's standards.

Scope: This process standardizes tooling, configuration, and workflows without disrupting existing code.

1. Assess Current State

Before aligning, audit your existing project:

cd <existing-project>

# Check what you have
ls -la                           # List all files
git log --oneline | head -5      # Recent commits
cat package.json | grep -A 5 scripts  # Existing scripts
ls -la .husky/ 2>/dev/null       # Git hooks
ls -la .github/workflows/ 2>/dev/null  # CI/CD workflows

Key questions:

  • Do you have Husky hooks? (.husky/ directory)
  • Do you have ESLint and Prettier? (check package.json)
  • Do you have GitHub Actions workflows? (.github/workflows/)
  • Do you have commitlint? (commitlint.config.js)
  • Do you have semantic-release? (.releaserc.json or release config in package.json)

2. Pull Template Files

Add the template as a remote and fetch its files:

# Add template as remote
git remote add template https://github.com/torqlab/js-project-template.git

# Fetch template files (creates a local tracking branch)
git fetch template main

# Create a working branch for alignment
git checkout -b chore/align-with-template

3. Merge Template Configuration Files

Copy template configuration files into your repo. Choose your merge strategy:

Option A: Cherry-pick Individual Files (Recommended for Existing Custom Config)

# Copy specific files from template
git show template/main:.github/workflows/verify.yml > .github/workflows/verify.yml
git show template/main:.github/workflows/publish.yml > .github/workflows/publish.yml
git show template/main:.husky/commit-msg > .husky/commit-msg
git show template/main:.husky/pre-push > .husky/pre-push
git show template/main:eslint.config.mjs > eslint.config.mjs
git show template/main:.prettierrc > .prettierrc
git show template/main:commitlint.config.js > commitlint.config.js
git show template/main:.releaserc.json > .releaserc.json
git show template/main:.env.example > .env.example
git show template/main:tsconfig.json > tsconfig.json
git show template/main:tsconfig.build.json > tsconfig.build.json

# Stage files
git add .github .husky eslint.config.mjs .prettierrc commitlint.config.js .releaserc.json .env.example tsconfig* 

Option B: Merge Entire Directory (Use If Starting Fresh)

# Merge template's .github, .husky directories
git checkout template/main -- .github .husky eslint.config.mjs .prettierrc commitlint.config.js .releaserc.json tsconfig*

git add .

4. Update package.json Dependencies

Add missing dependencies from template:

# View template package.json (don't copy entire file — merge carefully)
git show template/main:package.json | grep -A 50 '"devDependencies"'

Add template devDependencies to your package.json:

bun add -d \
  @types/bun \
  @typescript-eslint/eslint-plugin \
  @typescript-eslint/parser \
  commitlint \
  eslint \
  eslint-config-prettier \
  eslint-plugin-jsdoc \
  husky \
  prettier \
  semantic-release \
  validate-branch-name

Important: Keep your existing dependencies section. Only merge devDependencies from template.

5. Update package.json Scripts

Merge template scripts with your existing ones. Keep your custom scripts, add missing template scripts:

{
  "scripts": {
    "build": "bun run build:types && bun run build:esm && bun run build:cjs",
    "build:types": "tsc --declaration --emitDeclarationOnly --outDir dist",
    "build:esm": "tsc --module esnext --outDir dist",
    "build:cjs": "tsc --module commonjs --outDir dist/cjs",
    "clean": "rm -rf dist/",
    "lint": "eslint . --ext .ts",
    "lint:fix": "eslint . --ext .ts --fix",
    "format": "prettier --write .",
    "format:check": "prettier --check .",
    "prepare": "husky install",
    "test": "bun test"
  }
}

6. Verify Configuration Files Are Valid

# Validate JSON files
bunx jq empty .releaserc.json
bunx jq empty package.json

# Validate ESLint config
bunx eslint --print-config . > /dev/null

# Validate Prettier
bunx prettier --check . 2>&1 | head -10

# Check TypeScript config
tsc --noEmit

7. Install Dependencies and Initialize Hooks

# Install merged dependencies
bun install

# This automatically runs `npm run prepare` and installs Husky hooks
npm run prepare

Verify hooks are installed:

ls -la .husky/
# Should show: commit-msg, pre-push (both executable)

8. Test Git Hooks

Verify hooks work before committing:

# Test commit validation (should succeed)
git commit --allow-empty -m "feat(test): test commit"

# Test pre-push validation (dry run, don't actually push yet)
bun run build
tsc --noEmit
bun run test
bun run lint
bun run format:check

9. Fix ESLint and Formatting Issues

Template ESLint rules are strict. You'll likely have violations:

# See all linting issues
bun run lint

# Auto-fix what you can
bun run lint:fix
bun run format

# Manually fix remaining issues
# (Review violations and update code)

# Verify all fixed
bun run lint
bun run format:check

10. Commit Alignment Changes

Once all checks pass, commit with conventional format:

git add .
git commit -m "chore(tooling, #1): align project with template standards

- Update ESLint configuration and rules
- Add Prettier formatting configuration
- Add commitlint and branch validation hooks
- Add GitHub Actions CI/CD workflows (verify, publish)
- Add semantic-release configuration
- Standardize build scripts and tsconfig

This aligns the project with TORQ template standards for
consistent tooling and publishing workflows across all projects."

11. Update Branch Rulesets

Go to GitHub repository settings → Rules → Create rule for main branch (same as greenfield setup):

Reference: Use the same rules as torqlab/strava-api:

  • ✅ Restrict deletion
  • ✅ Require linear history (no merge commits)
  • ✅ Require pull request with:
    • Dismiss stale reviews on new push
    • Require review thread resolution
    • Restrict to squash merge only
  • ✅ Required status checks: Build, Check Formatting, Lint, Test

Or apply via GitHub API:

gh api repos/torqlab/strava-api/rulesets/13422315 | \
  gh api repos/<org>/<project>/rulesets --input -

12. Verify CI/CD Workflows Run

# Push branch to GitHub
git push -u origin chore/align-with-template

# Go to GitHub Actions and verify:
# - verify.yml runs (test, lint, types, format, build)
# - All checks pass

13. Create and Merge PR

  1. Create PR on GitHub
  2. Get code review
  3. Merge to main
  4. Delete feature branch

After merge:

  • GitHub Actions runs verification
  • If this is a published package, semantic-release may bump version + publish

Troubleshooting Brownfield Alignment

Conflict: ESLint Rules Too Strict

If existing code violates template ESLint rules extensively:

# View all violations
bun run lint

# Option 1: Auto-fix what you can
bun run lint:fix

# Option 2: Disable specific rules temporarily (not recommended)
# Edit eslint.config.mjs to disable rules

# Option 3: Create separate ESLint configuration for migration
# Gradually tighten rules over multiple commits

Conflict: TypeScript Strict Mode

If existing code has TypeScript errors:

# See all type errors
tsc --noEmit

# Fix types gradually, commit in separate commit
git add .
git commit -m "fix(types): resolve TypeScript strict mode violations"

Conflict: Branch/Commit History

Your existing commits may not follow conventional format. That's OK:

  • Hooks only validate new commits going forward
  • Existing history remains unchanged
  • semantic-release analyzes commits since last published version
  • If you haven't published yet, first release will use new commit format

Conflict: Existing Dependencies

If template dependencies conflict with yours:

# Check what's installed vs what template requires
bun pm list

# Resolve conflicts manually:
# 1. Keep versions that work with your code
# 2. Update template versions if needed
# 3. Run full test suite to verify compatibility

Quick Start for Development

Once setup is complete:

# Create feature branch (enforced naming: <type>/<ticket_id>-<description>)
git checkout -b feat/1-my-feature

# Make changes, write tests
# ... code ...

# Pre-commit hook auto-formats and fixes linting (runs on git commit)
git add .
git commit -m "feat(core, #1): add my feature"

# Pre-push hook validates everything (runs on git push):
#  - Builds TypeScript (ESM + CJS + types)
#  - Checks types (tsc --noEmit)
#  - Runs tests
#  - Lints code
#  - Checks formatting
#  - Validates branch name
git push -u origin feat/1-my-feature

# Create PR on GitHub, get reviewed, merge to main
# semantic-release automatically:
#  - Analyzes commits since last tag
#  - Determines version (major/minor/patch based on commit types)
#  - Updates CHANGELOG.md
#  - Publishes to npm with OIDC token
#  - Creates GitHub release with tag

Available Scripts

Development

bun run build              # Full build: types + ESM + CJS
bun run build:types       # Generate .d.ts type declarations only
bun run build:esm         # Build ES module format only
bun run build:cjs         # Build CommonJS format only
bun run clean             # Remove dist/ directory

Code Quality

bun run lint              # Run ESLint (check only)
bun run lint:fix          # Auto-fix ESLint issues
bun run format            # Auto-format with Prettier
bun run format:check      # Verify formatting (read-only)

Testing & Types

bun test                  # Run all tests with Bun test runner
tsc --noEmit              # Type-check without emitting files
npm run prepare           # Install/update Husky git hooks

Validation (Manual Pre-Push Check)

# Run all pre-push validations manually:
bun run build && tsc --noEmit && bun run test && \
  bun run lint && bun run format:check && npx validate-branch-name

Git Workflow

Branch Naming Convention

All branches must follow this pattern (enforced by pre-push hook):

<type>/<ticket_id>-<description>

Type: feat, fix, chore, docs, test, refactor, perf
Ticket ID: Numeric (required, e.g., 1, 42, 123)
Description: Lowercase, hyphens for spaces, no spaces

Examples:

git checkout -b feat/1-add-user-authentication
git checkout -b fix/42-resolve-race-condition
git checkout -b chore/99-upgrade-dependencies
git checkout -b docs/15-update-readme

Special branches (no validation):

  • main, master, develop

Conventional Commit Format

All commits must follow conventional commits (enforced by commit-msg hook):

<type>(<scope>, #<ticket_id>): <subject>

<optional body explaining the change>

Addresses #<ticket_id>

Example:

feat(api, #1): add user authentication endpoint

Implements OAuth 2.0 with JWT tokens. Adds refresh token rotation
and session management for secure user sessions.

Addresses #1

Rules:

  • Type: feat, fix, perf, chore, docs, refactor, test
  • Scope: REQUIRED (e.g., api, types, auth, db, core)
  • Subject: Imperative mood, max 50 characters, no period
  • Ticket ID: Numeric, in scope or footer
  • Footer: Addresses #<ticket_id> links to GitHub issue

Semantic Versioning

Commit types determine version bumps automatically (when merged to main):

Type Bump Example
feat MINOR 1.0.01.1.0
fix PATCH 1.0.01.0.1
perf PATCH 1.0.01.0.1
feat! or fix! MAJOR 1.0.02.0.0 (breaking change)
chore, docs, test, refactor NONE No version bump

Breaking changes: Add ! after type to trigger MAJOR bump:

feat!: remove deprecated API endpoint

Git Hooks Explained

This project uses Husky for three automated git hooks:

1. Pre-Commit Hook (Before commit is saved)

When: git commit is executed
What it does:

bun run format        # Auto-format code with Prettier
bun run lint:fix      # Auto-fix ESLint issues

Effect: Your code is automatically formatted and linting issues are fixed before the commit is saved. Review changes before committing if desired.

If it fails: Fix the remaining issues manually:

bun run lint          # See what ESLint can't auto-fix
# ... fix issues manually ...
git add .
git commit  # Try again

2. Commit-Msg Hook (After commit message is entered)

When: Git processes your commit message
What it validates:

  • ✅ Message has a type (feat, fix, etc.)
  • ✅ Message has a scope (never empty)
  • ✅ Follows conventional commit format
  • ✅ Subject is under 50 characters
  • ✅ Type and scope are valid

If it fails:

❌ Subject must not be empty
   Fix: git commit --amend

Solution:

git commit --amend    # Edit the message
# Reformat to: type(scope): subject
# Example: feat(api): add user authentication

3. Pre-Push Hook (Before push to remote)

When: git push is executed
What it does:

bun run build                 # Full TypeScript build (types + ESM + CJS)
tsc --noEmit                  # Type-check without emitting
bun run test                  # Run all tests
bun run lint                  # Run ESLint (full check)
bun run format:check          # Verify formatting
npx validate-branch-name      # Validate branch name pattern

Effect: Comprehensive validation before code reaches the remote. If any check fails, push is blocked.

If it fails: Fix the issue and try again:

# If tests fail
bun test                      # See which tests fail
# ... fix code ...
git add .
git commit -m "test: fix failing tests"
git push

# If linting fails
bun run lint                  # See ESLint errors
bun run lint:fix              # Auto-fix what you can
# ... manually fix remaining ...
git add .
git commit -m "fix: resolve linting errors"
git push

# If branch name is wrong
git branch -m feat/1-correct-name
git push -u origin feat/1-correct-name

Publishing to npm

Automatic Publishing (Recommended)

When you merge a PR to main:

  1. GitHub Actions triggers (publish.yml workflow)
  2. semantic-release analyzes commits since the last published version
  3. Determines version bump based on commit types
  4. Updates CHANGELOG.md with all changes
  5. Publishes to npm using OIDC trusted publishing (keyless, secure)
  6. Creates GitHub release with git tag and release notes

Zero manual steps required!

OIDC Trusted Publishing

This template uses GitHub-to-npm OIDC trust:

  • Secure: No long-lived credentials stored in GitHub
  • Automatic: Token generated at publish time
  • Auditable: npm records which GitHub repo published each version
  • Keyless: No NPM_TOKEN needed in GitHub Secrets

Setup is automatic — no configuration needed on GitHub Actions side.

Dry-Run Publishing (Test Before Merging)

To test semantic-release locally without publishing:

npx semantic-release --dry-run

This will show what version would be bumped and what would be published, without actually doing it.


Project Structure

<project>/
├── src/
│   ├── index.ts              # Entry point (exported as main/module/types)
│   └── *.ts                  # Your source code
├── dist/
│   ├── index.d.ts            # TypeScript types
│   ├── index.js              # CommonJS format (generated)
│   ├── index.mjs             # ES module format (generated)
│   └── ...                   # Built output (auto-generated, don't edit)
├── .github/workflows/
│   ├── verify.yml            # Test, lint, format, build on PR
│   └── publish.yml           # semantic-release on main branch
├── .husky/
│   ├── commit-msg            # Validate commit message (commitlint)
│   └── pre-push              # Validate build, tests, lint, format, branch
├── .env.example              # GitHub App setup (optional)
├── package.json              # Dependencies, scripts, metadata
├── tsconfig.json             # TypeScript strict mode config
├── tsconfig.build.json       # Build-time TypeScript overrides
├── eslint.config.mjs         # ESLint rules (flat config)
├── .prettierrc                # Prettier formatting rules
├── commitlint.config.js      # Conventional commit rules
├── .releaserc.json           # semantic-release config
├── README.md                 # This file
└── LICENSE                   # MIT license

Configuration Files

package.json

Key sections:

  • "type": "module" — Uses ES modules (not CommonJS)
  • "main" — Entry point for CommonJS consumers
  • "module" — Entry point for ESM consumers
  • "types" — TypeScript type definitions
  • "engines": { "node": "24.x" } — Node.js version requirement
  • "scripts" — All available commands
  • "devDependencies" — Build and validation tools
  • "publishConfig.provenance" — Enables OIDC trusted publishing

tsconfig.json

TypeScript strict mode configuration:

  • "strict": true — Enforces strict type checking
  • "target": "ES2022" — Modern JavaScript syntax
  • "declaration": true — Generates .d.ts files

eslint.config.mjs

Code quality rules:

  • Immutability: Only const, no let/var
  • Max line length: 100 characters
  • JSDoc required: All functions must have JSDoc comments
  • Node builtins: Use node: prefix (e.g., import fs from 'node:fs')
  • ESLint + Prettier: Integrated to avoid conflicts

.prettierrc

Code formatting rules:

  • 100-character print width
  • Single quotes for strings
  • Trailing commas in objects/arrays
  • 2-space indentation
  • Semicolons required

Troubleshooting

Git Hooks Not Running

Symptom: Bad commits were created, validation didn't trigger

Solution:

npm run prepare      # Reinstall hooks
git log --oneline    # Verify latest commits
ls -la .husky/       # Check hooks are installed

Check hook file permissions:

# Should show as executable (x flag)
ls -la .husky/commit-msg .husky/pre-push

Commit Message Rejected

Symptom:

❌ Subject must not be empty
   Fix: git commit --amend

Cause: Commit message doesn't follow conventional format

Fix:

git commit --amend
# Format must be: type(scope): subject
# Example: feat(api): add user authentication

Branch Name Rejected on Push

Symptom:

❌ Branch name must match pattern: <type>/<ticket_id>-<description>

Fix:

# Create new branch with correct name
git checkout -b feat/1-correct-name

# Push new branch
git push -u origin feat/1-correct-name

# Delete old branch locally and remotely
git branch -d old-branch
git push origin --delete old-branch

ESLint or Prettier Fails on Push

Symptom:

❌ ESLint found 3 errors

Fix:

# Auto-fix formatting
bun run format

# Auto-fix ESLint issues
bun run lint:fix

# Check what can't be auto-fixed
bun run lint

# Manually fix remaining issues, then:
git add .
git commit -m "fix(code): resolve linting errors"
git push

Tests Fail on Pre-Push

Symptom:

❌ FAIL src/index.test.ts

Fix:

# Run tests locally to see details
bun test

# Fix code and tests
git add .
git commit -m "test(core): fix failing tests"
git push

Type Checking Fails

Symptom:

❌ TypeScript error: Type 'X' is not assignable to type 'Y'

Fix:

# See full type errors
tsc --noEmit

# Fix type issues in your code
# Then try push again

License

MIT

About

Canonical TypeScript/Node.js project template for TORQ ecosystem with semantic versioning, conventional commits, and automated publishing.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors