Skip to content

Instantly share code, notes, and snippets.

@mizchi
Last active June 14, 2025 07:00
Show Gist options
  • Save mizchi/9f1ef81368507e6ca410779101260d7e to your computer and use it in GitHub Desktop.
Save mizchi/9f1ef81368507e6ca410779101260d7e to your computer and use it in GitHub Desktop.

This is typescript environment setup guide for LLM and humans

Baseline

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

Optional: eslint

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",
    },
  }
);

Optional: PROMPT

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

{}

Optional: neverthrow

pnpm add neverthrow

Additional prompt

Never throw exception in our project. Instead of throw, use neverthrow
@mizchi
Copy link
Author

mizchi commented Jun 10, 2025

How to use

Init current directory by this guide
deno run -A npm:@mizchi/readability https://gist.github.com/mizchi/9f1ef81368507e6ca410779101260d7e

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment