# TypeScript/Node.js-Specific Guidelines

This document contains TypeScript and Node.js-specific instructions for AO projects. Reference this alongside the main `copilot-instructions.md`.

## Testing Framework: Vitest/Jest

### Preferred Test Structure
```typescript
// tests/feature.test.ts
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { FeatureClass } from '../src/feature';

describe('FeatureClass', () => {
  let instance: FeatureClass;
  
  beforeEach(() => {
    instance = new FeatureClass();
  });
  
  afterEach(() => {
    // Cleanup
  });
  
  it('should handle happy path', () => {
    expect(instance.process('input')).toBe('expected');
  });
  
  it('should throw on invalid input', () => {
    expect(() => instance.process(null)).toThrow();
  });
});
```

### Test Isolation Patterns

**Use temporary directories:**
```typescript
import { mkdtemp, rm } from 'fs/promises';
import { tmpdir } from 'os';
import { join } from 'path';

describe('FileOperations', () => {
  let tempDir: string;
  
  beforeEach(async () => {
    tempDir = await mkdtemp(join(tmpdir(), 'test-'));
  });
  
  afterEach(async () => {
    await rm(tempDir, { recursive: true });
  });
  
  it('should write file to temp directory', async () => {
    const filePath = join(tempDir, 'test.txt');
    await writeFile(filePath, 'content');
    expect(await readFile(filePath, 'utf-8')).toBe('content');
  });
});
```

**Use mocks for external services:**
```typescript
import { vi } from 'vitest';

vi.mock('../src/external-service', () => ({
  fetchData: vi.fn().mockResolvedValue({ data: 'mocked' }),
}));
```

## Package Management: npm/pnpm/yarn

### npm Commands
```bash
# Install dependencies
npm install

# Add a dependency
npm install package-name

# Add dev dependency
npm install --save-dev package-name

# Run script
npm run test
npm run build

# Execute binary
npx tsc
```

### pnpm Commands (preferred)
```bash
pnpm install
pnpm add package-name
pnpm add -D package-name
pnpm run test
pnpm exec tsc
```

### package.json Structure
```json
{
  "name": "package-name",
  "version": "0.1.0",
  "type": "module",
  "main": "dist/index.js",
  "types": "dist/index.d.ts",
  "scripts": {
    "build": "tsc",
    "test": "vitest",
    "lint": "eslint src/",
    "format": "prettier --write ."
  },
  "dependencies": {
    "zod": "^3.0.0"
  },
  "devDependencies": {
    "@types/node": "^20.0.0",
    "typescript": "^5.0.0",
    "vitest": "^1.0.0",
    "eslint": "^8.0.0",
    "prettier": "^3.0.0"
  }
}
```

## Code Quality: ESLint + Prettier

### ESLint Configuration
```javascript
// eslint.config.js
import eslint from '@eslint/js';
import tseslint from 'typescript-eslint';

export default tseslint.config(
  eslint.configs.recommended,
  ...tseslint.configs.recommended,
  {
    rules: {
      '@typescript-eslint/no-unused-vars': 'error',
      '@typescript-eslint/explicit-function-return-type': 'warn',
    },
  },
);
```

### Prettier Configuration
```json
// .prettierrc
{
  "semi": true,
  "singleQuote": true,
  "tabWidth": 2,
  "trailingComma": "es5",
  "printWidth": 100
}
```

### Commands
```bash
# Lint
npx eslint src/

# Fix auto-fixable
npx eslint src/ --fix

# Format
npx prettier --write .
```

## Type Checking: TypeScript

### tsconfig.json
```json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "outDir": "./dist",
    "rootDir": "./src",
    "declaration": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}
```

### Common Patterns
```typescript
// Type definitions
interface User {
  id: string;
  name: string;
  email?: string;  // Optional
}

// Generic types
function processArray<T>(items: T[]): T | undefined {
  return items[0];
}

// Union types
type Status = 'pending' | 'active' | 'completed';

// Type guards
function isUser(obj: unknown): obj is User {
  return typeof obj === 'object' && obj !== null && 'id' in obj;
}
```

## Build System Commands

### Full Validation Cycle
```bash
# Clean and build
rm -rf dist
npx tsc

# Lint and format
npx eslint src/ --fix
npx prettier --write .

# Run tests
npx vitest run

# Type check without emitting
npx tsc --noEmit
```

## Common Patterns

### Error Handling
```typescript
class AppError extends Error {
  constructor(
    message: string,
    public readonly code: string,
    public readonly statusCode: number = 500
  ) {
    super(message);
    this.name = 'AppError';
  }
}

function handleError(error: unknown): never {
  if (error instanceof AppError) {
    console.error(`[${error.code}] ${error.message}`);
  } else if (error instanceof Error) {
    console.error(error.message);
  }
  process.exit(1);
}
```

### Async Patterns
```typescript
// Async/await with error handling
async function fetchData(url: string): Promise<Data> {
  const response = await fetch(url);
  if (!response.ok) {
    throw new AppError('Fetch failed', 'FETCH_ERROR', response.status);
  }
  return response.json();
}

// Parallel execution
const results = await Promise.all([
  fetchData('url1'),
  fetchData('url2'),
]);

// Sequential with reduce
const sequential = await urls.reduce(
  async (prev, url) => {
    const results = await prev;
    const data = await fetchData(url);
    return [...results, data];
  },
  Promise.resolve([] as Data[])
);
```

### CLI with Commander
```typescript
import { Command } from 'commander';

const program = new Command()
  .name('cli')
  .version('1.0.0')
  .description('CLI description');

program
  .command('process <input>')
  .option('-v, --verbose', 'verbose output')
  .action((input, options) => {
    if (options.verbose) {
      console.log(`Processing: ${input}`);
    }
    // Process input
  });

program.parse();
```

## Build Pipeline

### Pipeline Stages

A comprehensive TypeScript build pipeline runs these stages:

| Stage | Tool | Purpose |
|-------|------|---------|
| 1. Format | Prettier | Auto-format code |
| 2. Lint | ESLint | Check code quality |
| 3. Type Check | tsc --noEmit | Verify types without emitting |
| 4. Unit Tests | Vitest/Jest | Run tests with coverage |
| 5. E2E Tests | Playwright | End-to-end browser tests |
| 6. Coverage | c8/v8 | Enforce coverage thresholds |
| 7. Complexity | complexity-report | Code complexity analysis |

### ESLint Flat Config (Modern)

```javascript
// eslint.config.js
import js from "@eslint/js";
import globals from "globals";
import tseslint from "typescript-eslint";

export default tseslint.config([
  { ignores: ["dist", "node_modules", "coverage"] },
  {
    files: ["**/*.{ts,tsx,js,jsx}"],
    extends: [
      js.configs.recommended,
      ...tseslint.configs.recommended,
    ],
    languageOptions: {
      globals: {
        ...globals.browser,
        ...globals.node,
      },
    },
    rules: {
      "@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }],
      "@typescript-eslint/explicit-function-return-type": "warn",
      "@typescript-eslint/no-explicit-any": "warn",
    },
  },
]);
```

### Vitest Configuration with Coverage

```typescript
// vitest.config.ts
import { defineConfig } from 'vitest/config';

export default defineConfig({
  test: {
    globals: true,
    environment: 'node',
    coverage: {
      provider: 'v8',
      reporter: ['text', 'html', 'cobertura'],
      reportsDirectory: './coverage',
      thresholds: {
        lines: 70,
        functions: 70,
        branches: 60,
        statements: 70,
      },
      exclude: [
        'node_modules/',
        'tests/',
        '**/*.d.ts',
        '**/*.config.*',
      ],
    },
  },
});
```

### Playwright E2E Configuration

```typescript
// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  testDir: './tests/e2e',
  fullyParallel: true,
  forbidOnly: !!process.env.CI,
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? 1 : undefined,
  reporter: [
    ['line'],
    ['json', { outputFile: 'test-results/playwright.json' }],
    ['html', { open: 'never' }],
  ],
  use: {
    baseURL: 'http://localhost:3000',
    trace: 'on-first-retry',
  },
  projects: [
    { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
    { name: 'firefox', use: { ...devices['Desktop Firefox'] } },
  ],
  webServer: {
    command: 'npm run dev',
    url: 'http://localhost:3000',
    reuseExistingServer: !process.env.CI,
  },
});
```

### Package.json Scripts Pattern

```json
{
  "scripts": {
    "build": "tsc",
    "build:full": "node build.js",
    "dev": "vite dev",
    "test": "vitest run",
    "test:watch": "vitest",
    "test:coverage": "vitest run --coverage",
    "test:e2e": "playwright test",
    "lint": "eslint . --max-warnings 0",
    "lint:fix": "eslint . --fix",
    "format": "prettier --write .",
    "format:check": "prettier --check .",
    "typecheck": "tsc --noEmit",
    "validate": "npm run format:check && npm run lint && npm run typecheck && npm run test:coverage"
  }
}
```

### Monorepo Considerations

For monorepos with multiple packages:

```json
// Root package.json
{
  "workspaces": ["packages/*", "apps/*"],
  "scripts": {
    "build": "turbo run build",
    "test": "turbo run test",
    "lint": "turbo run lint"
  }
}
```

```javascript
// eslint.config.js for monorepo
export default [
  {
    ignores: ["**/dist/**", "**/node_modules/**"],
  },
  // Shared config for all packages
  {
    files: ["packages/**/*.ts", "apps/**/*.ts"],
    // ... rules
  },
];
```

## CI/CD Integration

### GitHub Actions

```yaml
# .github/workflows/node.yml
name: Node.js Build

on: [push, pull_request]

jobs:
  build:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: [18, 20, 22]
    
    steps:
    - uses: actions/checkout@v4
    
    - name: Setup Node.js
      uses: actions/setup-node@v4
      with:
        node-version: ${{ matrix.node-version }}
        cache: 'npm'
    
    - name: Install dependencies
      run: npm ci
    
    - name: Format check
      run: npm run format:check
    
    - name: Lint
      run: npm run lint
    
    - name: Type check
      run: npm run typecheck
    
    - name: Run tests
      run: npm run test:coverage
    
    - name: Upload coverage
      uses: codecov/codecov-action@v4
      with:
        files: ./coverage/cobertura-coverage.xml
        fail_ci_if_error: true

  e2e:
    runs-on: ubuntu-latest
    needs: build
    steps:
    - uses: actions/checkout@v4
    - uses: actions/setup-node@v4
      with:
        node-version: 20
        cache: 'npm'
    
    - run: npm ci
    
    - name: Install Playwright
      run: npx playwright install --with-deps
    
    - name: Run E2E tests
      run: npm run test:e2e
    
    - uses: actions/upload-artifact@v4
      if: always()
      with:
        name: playwright-report
        path: playwright-report/
```

### Azure DevOps

```yaml
# azure-pipelines.yml
trigger:
  - main

pool:
  vmImage: 'ubuntu-latest'

steps:
- task: NodeTool@0
  inputs:
    versionSpec: '20.x'

- script: npm ci
  displayName: 'Install dependencies'

- script: |
    npm run format:check
    npm run lint
    npm run typecheck
    npm run test:coverage
  displayName: 'Run Build Pipeline'

- task: PublishCodeCoverageResults@2
  inputs:
    codeCoverageTool: 'Cobertura'
    summaryFileLocation: 'coverage/cobertura-coverage.xml'
```

## Troubleshooting

### Common Issues

#### "Cannot find module" errors

```bash
# Clear node_modules and reinstall
rm -rf node_modules package-lock.json
npm install

# For TypeScript path aliases, ensure tsconfig paths match
# Check moduleResolution is set correctly
```

#### "ESLint flat config not working"

```bash
# Ensure ESLint 9+ is installed
npm install eslint@latest

# Check filename is eslint.config.js (not .eslintrc.*)
# For legacy support:
npm install @eslint/eslintrc
```

#### "Coverage below threshold"

```bash
# See uncovered lines
npm run test:coverage

# Open HTML report
# coverage/index.html

# Temporarily disable thresholds in vitest.config.ts
# thresholds: { lines: 0 }
```

#### "Playwright tests fail in CI"

```bash
# Install browser dependencies
npx playwright install --with-deps

# Run with debugging
DEBUG=pw:api npm run test:e2e

# Check for viewport/timing issues
# Add waitForSelector/waitForTimeout as needed
```

#### "TypeScript and ESLint version mismatch"

```bash
# Use typescript-eslint compatible versions
npm install typescript@latest @typescript-eslint/parser@latest @typescript-eslint/eslint-plugin@latest

# Or use the unified package
npm install typescript-eslint
```

#### "pnpm/yarn workspace issues"

```bash
# For pnpm, ensure .npmrc has:
# shamefully-hoist=true

# For workspace commands
pnpm -r run build  # Run in all packages
pnpm --filter package-name run test  # Run in specific package
```
