This is typescript environment setup guide for LLM and humans
Always setup baseline settings
- pnpm
- typescript
- vitest
pnpm init --init-type module
pnpm add typescript vitest @vitest/coverage-v8 @types/node -D
echo "node_modules\ntmp\ncoverage" > .gitignore
mkdir -p src
# git
git init
git add .
git commit -m "init"
package.json
{
"private": true,
"type": "module",
"scripts": {
"typecheck": "tsc --noEmit",
"test": "vitest run",
"test:cov": "vitest run --coverage"
},
"license": "MIT",
"devDependencies": {
"@types/node": "^24.0.0",
"@vitest/coverage-v8": "3.2.3",
"typescript": "^5.8.3",
"vitest": "^3.2.3"
},
"pnpm": {
"onlyBuiltDependencies": ["esbuild"]
}
}
tsconfig.json
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"moduleResolution": "bundler",
"noEmit": true,
"esModuleInterop": true,
"allowImportingTsExtensions": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true,
"types": ["vitest/importMeta"]
}
}
or pnpm tsc --init --module esnext --moduleResolution Bundler --target esnext --noEmit
vitest.config.ts
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
includeSource: ["src/**/*.{ts}"],
include: ["src/**/*.{test,spec}.{ts}"],
},
});
Entrypoint: src/index.ts
/**
* explation of this module
*/
export {};
if (import.meta.vitest) {
const { test, expect } = import.meta.vitest;
test("init", () => {
expect(true).toBe(true);
});
}
.github/workflows/ci.yaml
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 10
- uses: actions/setup-node@v4
with:
node-version: 24
cache: "pnpm"
- run: pnpm install --frozen-lockfile
- run: pnpm test
- run: pnpm typecheck
Ask to user: Do you want to use eslint?
pnpm add -d @eslint/js eslint typescript-eslint
package.json
{
"scripts": {
"lint": "eslint . --quiet",
"lint:warn": "eslint .",
"lint:fix": "eslint . --fix"
}
}
eslint.config.ts
import eslint from "@eslint/js";
import tseslint from "typescript-eslint";
export default tseslint.config(
eslint.configs.recommended,
...tseslint.configs.recommendedTypeCheckedOnly,
{
languageOptions: {
parserOptions: {
projectService: true,
tsconfigRootDir: import.meta.dirname,
},
},
},
{
// base rules
rules: {
"@typescript-eslint/no-redeclare": "error",
"@typescript-eslint/prefer-ts-expect-error": "error",
"@typescript-eslint/no-floating-promises": "error",
"@typescript-eslint/only-throw-error": "error",
"no-throw-literal": "off",
},
},
{
// warning for refactoring
rules: {
"@typescript-eslint/no-empty-interface": "warn",
"@typescript-eslint/no-empty-object-type": "warn",
"@typescript-eslint/no-empty-function": "warn",
"@typescript-eslint/no-explicit-any": "warn",
"@typescript-eslint/no-deprecated": "warn",
"@typescript-eslint/await-thenable": "warn",
"@typescript-eslint/require-await": "warn",
"@typescript-eslint/consistent-type-imports": [
"warn",
{
prefer: "type-imports",
disallowTypeAnnotations: false,
fixStyle: "inline-type-imports",
},
],
"@typescript-eslint/ban-ts-comment": [
"warn",
{
"ts-ignore": true,
"ts-expect-error": "allow-with-description",
},
],
"prefer-const": "warn",
complexity: ["warn", { max: 7 }],
"no-console": "warn",
},
},
{
// allow console in examples
files: ["examples/*.ts"],
rules: {
"no-console": "off",
},
}
);
CLAUDE.md
## Coding Rules
- File covention: `src/<snake_case>.ts`
- Add test `src/*.test.ts` for `src/*.ts`
- Use function and function scope instead of class
- Add `.ts` extensions to import. eg. `import {} from "./x.ts"` for deno compatibility.
- Never disable any lint rules without explicit user approval
.claude/settings.json
{
"permissions": {
"allow": ["Bash(pnpm test)", "Bash(ls:*)", "Bash(grep:*)"],
"deny": []
}
}
.mcp.json
{}
pnpm add neverthrow
Additional prompt
Never throw exception in our project. Instead of throw, use neverthrow
How to use