# `@atharvdange/toolchain`

A reusable CLI that scaffolds a complete dev toolchain - ESLint, Prettier, Husky, commitlint, and strict TypeScript - into any JS/TS project with one command.

```bash
pnpm dlx @atharvdange/toolchain init
```

## Features

- **Framework-aware** - detects Next.js, Express, React, or plain TS and tailors ESLint rules accordingly
- **Monorepo-ready** - detects `workspaces` / `pnpm.workspaces` and generates tsconfig project references with per-package eslint overrides
- **Zero-config** - interactive prompts guide you through, or use `--yes` to skip
- **All in one** - linter, formatter, hooks, commit linting, and type checking
- **Project scaffolding** - create new projects from scratch with `scaffold` command
- **Opinionated defaults** - strict TypeScript config, sensible ESLint rules, proper monorepo structure

## Commands

### `init` - Add toolchain to existing project

```bash
# Interactive
pnpm dlx @atharvdange/toolchain init

# Non-interactive (defaults)
pnpm dlx @atharvdange/toolchain init --yes
```

### `scaffold` - Create a new project

```bash
# Create a React project with npm
pnpm dlx @atharvdange/toolchain scaffold my-app --template react --pm npm

# Create a Next.js project with pnpm
pnpm dlx @atharvdange/toolchain scaffold my-app --template nextjs --pm pnpm

# Create a plain TypeScript project
pnpm dlx @atharvdange/toolchain scaffold my-app --template plain

# Create a monorepo with Turborepo
pnpm dlx @atharvdange/toolchain scaffold my-app --template turbo --pm pnpm

# Interactive template selection
pnpm dlx @atharvdange/toolchain scaffold my-app
```

#### Available templates

| Template        | Description                                         | Source                 |
| --------------- | --------------------------------------------------- | ---------------------- |
| `plain`         | Basic TypeScript project                            | Generated by toolchain |
| `react`         | React 19 with Vite and TypeScript                   | `create-vite`          |
| `nextjs`        | Next.js 15 with App Router                          | `create-next-app`      |
| `express`       | Express 5 REST API with TypeScript                  | Generated by toolchain |
| `pnpm-monorepo` | pnpm workspaces monorepo with apps and packages     | Generated by toolchain |
| `turbo`         | Turborepo monorepo with apps and packages           | Generated by toolchain |

#### Package managers

Use `--pm` to specify the package manager: `npm`, `pnpm`, `yarn`, or `bun`.

> **Note:** React and Next.js projects use their official scaffolding tools (`create-vite` and `create-next-app`), then the toolchain is added on top.

> **Note:** `pnpm-monorepo` template requires `pnpm` and will override `--pm` if set to a different package manager.

#### Monorepo structure

Both `pnpm-monorepo` and `turbo` templates generate a standard monorepo structure:

```
my-app/
├── apps/
│   ├── web/          # Web application
│   ├── mobile/       # Mobile application
│   └── api/          # API server
├── packages/
│   ├── shared/       # Shared utilities (logger, formatDate)
│   ├── config/       # Environment configuration (typed)
│   └── db/           # Database client
├── pnpm-workspace.yaml
├── turbo.json        # (turbo only)
└── package.json
```

Each package has its own `package.json`, `tsconfig.json`, and source files with workspace dependencies wired up.

## What it sets up

| Config                            | Generated variant                                                   |
| --------------------------------- | ------------------------------------------------------------------- |
| `.editorconfig`                   | Static                                                              |
| `.prettierrc` / `.prettierignore` | Static                                                              |
| `commitlint.config.js`            | Static                                                              |
| `eslint.config.mjs`               | Plain TS / React / Next.js / Express rules, ignores `.expo/`        |
| `tsconfig.json`                   | Strict config with project references (monorepo)                    |
| `.husky/pre-commit`               | `lint-staged` + `typecheck`                                         |
| `.husky/commit-msg`               | `commitlint`                                                        |
| `package.json`                    | Scripts, `lint-staged` config, `config.commitizen`, devDependencies |

## TypeScript configuration

The generated `tsconfig.json` includes strict options:

| Option                           | Description                                       |
| -------------------------------- | ------------------------------------------------- |
| `strict`                         | Enable all strict type-checking options           |
| `noUnusedLocals`                 | Error on unused local variables                   |
| `noUnusedParameters`             | Error on unused function parameters               |
| `noFallthroughCasesInSwitch`     | Error on fallthrough cases in switch statements   |
| `noUncheckedIndexedAccess`       | Add `undefined` to any untyped index access       |
| `noImplicitOverride`             | Require `override` keyword in derived classes     |
| `exactOptionalPropertyTypes`     | Distinguish between `undefined` and missing props |
| `noPropertyAccessFromIndexSignature` | Force bracket notation for index signatures  |
| `verbatimModuleSyntax`           | Enforce `import type` for type-only imports       |
| `moduleResolution: bundler`      | Modern module resolution for bundlers             |

## ESLint variants

The generated `eslint.config.mjs` adapts based on what's in your `package.json`:

| Detected | Plugins included                                                                                                                         |
| -------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| Plain TS | `typescript-eslint`, `unicorn`, `perfectionist`, `eslint-plugin-import-x`                                                                |
| React    | Above + `eslint-plugin-react`, `eslint-plugin-react-hooks`, `eslint-plugin-react-you-might-not-need-an-effect`, `eslint-plugin-jsx-a11y` |
| Next.js  | Above + `@next/eslint-plugin-next`                                                                                                       |
| Express  | Same as Plain TS but `no-unsafe-*` rules relaxed to `warn`                                                                               |

`eslint-plugin-import-x` is applied to every project, resolved through `eslint-import-resolver-typescript` so it understands tsconfig paths and monorepo workspace packages. Only `no-cycle`, `no-duplicates`, `no-self-import`, and `no-useless-path-segments` are enabled - the resolution rules (`no-unresolved`, `named`, `namespace`, `default`, `export`) are left off since `tsc` already catches those, and they're prone to false positives on path aliases.

**Ignored directories:** `node_modules`, `dist`, `.next`, `build`, `.expo`, `.pnpm-store`, `*.config.*`, `.env*`

For monorepos, also ignores `**/packages/*/dist/**`.

## Commit messages

Every generated project gets an interactive commit prompt on top of commitlint's enforcement:

```bash
pnpm run commit
```

This runs `cz`, powered by `commitizen` and `@commitlint/cz-commitlint`. It reads the same `commitlint.config.js` this tool generates, so the prompt's types and rules always match what the `commit-msg` hook actually enforces - there's no separate config to keep in sync.

## Detection logic

- **Package manager**: checks `npm_config_user_agent` first (set by the package manager that invoked the CLI), then lockfiles (`pnpm-lock.yaml` → pnpm, `yarn.lock` → yarn, `bun.lockb` → bun), then `devEngines` in package.json, else npm
- **Monorepo**: checks for `workspaces` in package.json or `pnpm.workspaces`
- **Framework**: checks `dependencies` / `devDependencies` for `next`, `express`, `react`

## Troubleshooting

### Husky hooks not running after `git init`

If you delete `.git` and re-initialize with `git init`, husky hooks will stop working. Run:

```bash
pnpm run prepare
```

This reconfigures `core.hooksPath` to point to `.husky`.

### npx fails with `EBADDEVENGINES`

`npx @atharvdange/toolchain init` will **not** work when the package's `packageManager` is set to `pnpm` (if you initialized a blank project with `pnpm init`), causing npm to throw an `EBADDEVENGINES` error. Use `pnpm dlx`, `yarn dlx`, or `bunx` instead.
