Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save kaznak/e9d9f3d8792afc6a9d7abbadff25b3b5 to your computer and use it in GitHub Desktop.

Select an option

Save kaznak/e9d9f3d8792afc6a9d7abbadff25b3b5 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment