diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index e7e18a54ad..9ded0f668b 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,5 +1,5 @@ // Devcontainer: the only outright zero on the readiness report. -// Matches the documented toolchain exactly: Node 24 + pnpm 11.24 +// Matches the documented toolchain exactly: Node 24 + pnpm 12.16.1 // (packageManager pin), Python 3.14 via uv (ai/ requires-python), // and docker-outside-of-docker for the throwaway pe test database // (make pe-test-db). Submodules are checked out by the post-create diff --git a/GEMINI.md b/GEMINI.md index d1c8931026..2df9dbdcf9 100644 --- a/GEMINI.md +++ b/GEMINI.md @@ -49,8 +49,7 @@ Python): # Frontend & Services (Astro 6 / React 19 / Express) pnpm dev # Start Astro frontend (port 5173) pnpm dev:all-services # Start complete local service ecosystem -pnpm typecheck # Run TypeScript compiler checks -pnpm lint # ESLint & formatting audits +pnpm lint # oxlint (type-aware) and repo lint gates pnpm vitest run -c config/vitest.config.ts # Run unit/integration tests pnpm build # Production build @@ -92,6 +91,6 @@ uv run python -m # Run Python module 1. **Grounded Approach**: Confirm understanding against Foresight context. 2. **Minimal-Safe Edits**: Implement surgical code changes. 3. **Targeted Verification**: Run tests (`pnpm vitest`, `uv run pytest`) and - linting (`pnpm typecheck`). + linting (`pnpm lint`). 4. **State Capture**: Update `pending_items` or store new decisions in Foresight before closing. diff --git a/Makefile b/Makefile index 5436f0bc47..831101ae81 100644 --- a/Makefile +++ b/Makefile @@ -60,11 +60,11 @@ format: ## Format all code format-check: ## Check formatting pnpm format:check -typecheck: ## Run TypeScript type check - pnpm typecheck +typecheck: ## Run oxlint type-aware lint (see AGENTS.md — no tsc/astro check) + pnpm lint -check-all: ## Run all checks (typecheck + lint + format) - pnpm check:all +check-all: ## Run lint gates (formerly check:all; typecheck scripts removed) + pnpm lint # ── Docker ────────────────────────────────────────────── diff --git a/apps/web/src/content-store/docs/development-setup.md b/apps/web/src/content-store/docs/development-setup.md index 6a2f405fa0..c0ee2a826b 100644 --- a/apps/web/src/content-store/docs/development-setup.md +++ b/apps/web/src/content-store/docs/development-setup.md @@ -124,7 +124,6 @@ pnpm run diagnostics ## Additional Development Tools -- **Code Linting**: `pnpm lint` -- **Type Checking**: `pnpm typecheck` +- **Code Linting**: `pnpm lint` (oxlint, type-aware) - **Testing**: `pnpm test` - **Formatting**: `pnpm format` diff --git a/apps/web/src/content-store/docs/getting-started/setup.md b/apps/web/src/content-store/docs/getting-started/setup.md index 6a2f405fa0..c0ee2a826b 100644 --- a/apps/web/src/content-store/docs/getting-started/setup.md +++ b/apps/web/src/content-store/docs/getting-started/setup.md @@ -124,7 +124,6 @@ pnpm run diagnostics ## Additional Development Tools -- **Code Linting**: `pnpm lint` -- **Type Checking**: `pnpm typecheck` +- **Code Linting**: `pnpm lint` (oxlint, type-aware) - **Testing**: `pnpm test` - **Formatting**: `pnpm format` diff --git a/apps/web/src/scripts/fix-ts-errors.sh b/apps/web/src/scripts/fix-ts-errors.sh deleted file mode 100644 index 971cab4513..0000000000 --- a/apps/web/src/scripts/fix-ts-errors.sh +++ /dev/null @@ -1,79 +0,0 @@ -#!/bin/bash -set -euo pipefail - -# Fix TypeScript Errors - Shell Script Wrapper -# This script provides a convenient way to run the TypeScript error fixer - -# Colors for better output -# shellcheck disable=SC2034 -# shellcheck disable=SC2034 -# shellcheck disable=SC2034 -# shellcheck disable=SC2034 -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -RED='\033[0;31m' -BLUE='\033[0;34m' -NC='\033[0m' # No Color - -# Check if we're in the right environment -if [[ -z ${CONDA_DEFAULT_ENV} ]] || [[ ${CONDA_DEFAULT_ENV} != "pixelated" ]]; then - echo -e "${YELLOW}Warning: Not in the pixelated conda environment.${NC}" - echo -e "${YELLOW}It's recommended to run: conda activate pixelated${NC}" - read -p "Continue anyway? (y/n) " -n 1 -r - echo - if [[ ! ${REPLY} =~ ^[Yy]$ ]]; then - exit 1 - fi -fi -# Change to the project root directory -if [[ -z ${PROJECT_ROOT} ]]; then - PROJECT_ROOT="$(cd "$(dirname "${0}")/../.." && pwd)" -fi -if [[ -z ${PROJECT_ROOT} ]]; then - echo -e "${RED}Error: Could not find project root directory.${NC}" - exit 1 -fi -cd "${PROJECT_ROOT}" || { - echo -e "${RED}Error: Could not change to project root directory.${NC}" -} - -echo -e "${BLUE}=== TypeScript Error Fixer ===${NC}" -echo -e "${BLUE}Project root: ${PROJECT_ROOT}${NC}" - -# Check if the TypeScript fixer script exists -TS_FIXER="src/scripts/fix-ts-errors.ts" -if [[ ! -f ${TS_FIXER} ]]; then - echo -e "${RED}Error: TypeScript fixer script not found at ${TS_FIXER}${NC}" - exit 1 -fi - -# Check if arguments are provided -if [[ ${1} == "--help" ]]; then - echo "Usage: ./fix-ts-errors.sh [options]" - echo "" - echo "Options:" - echo " --dry-run Preview changes without applying them" - echo " --verbose Show detailed logging" - echo " --list List files with TypeScript errors" - echo " --fix-file Fix a specific file (e.g. --fix-file src/components/Chat.tsx)" - echo " --help Show this help message" - exit 0 -fi - -# Run the TypeScript fixer script -echo -e "${GREEN}Running TypeScript error fixer...${NC}" -npx tsx "${TS_FIXER}" "$@" - -# Check the result -if [[ ${?} -eq 0 ]]; then - echo -e "${GREEN}TypeScript error fixer completed successfully.${NC}" - - # Provide helpful next steps - echo -e "${BLUE}Next steps:${NC}" - echo -e "1. Run ${YELLOW}npx tsc --noEmit${NC} to check for remaining errors" - echo -e "2. To fix specific files, use ${YELLOW}./fix-ts-errors.sh --fix-file path/to/file.ts${NC}" - echo -e "3. To run in dry-run mode (preview only), use ${YELLOW}./fix-ts-errors.sh --dry-run${NC}" -else - echo -e "${RED}TypeScript error fixer encountered problems.${NC}" - echo -e "Try running with ${YELLOW}--verbose${NC} for more details." -fi diff --git a/apps/web/src/scripts/ts-error-docs.md b/apps/web/src/scripts/ts-error-docs.md deleted file mode 100644 index 2e4fac1355..0000000000 --- a/apps/web/src/scripts/ts-error-docs.md +++ /dev/null @@ -1,177 +0,0 @@ -# TypeScript Error Fixing Guide - -This document outlines common TypeScript errors found in the project and how -they're addressed by the `fix-ts-errors.ts` script. - -## Common Error Patterns - -### 1. Underscore Variable Pattern - -**Problem:** Variables are defined with an underscore prefix (`_variable`) but -then used without the underscore (`variable`). - -**Example:** - -```typescript -// Error: -const user = await getUser() -console.log(user.name) // Error: 'user' is not defined - -// Fixed: -const user = await getUser() -console.log(user.name) // Correctly using user -``` - -### 2. Missing Properties in AIMessage Interface - -**Problem:** The `AIMessage` interface requires a `name` property, but it's -often omitted. - -**Example:** - -```typescript -// Error: -const message: AIMessage = { - role: 'user', - content: 'Hello', -} // Error: Property 'name' is missing - -// Fixed: -const message: AIMessage = { - role: 'user', - content: 'Hello', - name: '', // Added required property -} -``` - -### 3. Nullable Property Access - -**Problem:** Accessing properties on objects that could be null or undefined -without optional chaining. - -**Example:** - -```typescript -// Error: -const name = error.message // Error if error is null or undefined - -// Fixed: -const name = error?.message // Safely accesses message if error exists -``` - -## Using the TypeScript Error Fixer - -The `fix-ts-errors.ts` script automates the process of fixing these common -errors. Here's how to use it: - -### Prerequisites - -1. Make sure you're in the correct conda environment: - - ```bash - conda activate pixelated - ``` - -2. Run the script from the project root directory. - -### Commands - -1. **List files with TypeScript errors:** - - ```bash - ./src/scripts/fix-ts-errors.sh --list - ``` - -2. **Fix all TypeScript errors:** - - ```bash - ./src/scripts/fix-ts-errors.sh - ``` - -3. **Preview fixes without making changes:** - - ```bash - ./src/scripts/fix-ts-errors.sh --dry-run - ``` - -4. **Fix errors in a specific file:** - - ```bash - ./src/scripts/fix-ts-errors.sh --fix-file src/components/demo/ChatDemo.tsx - ``` - -5. **Get detailed logs during fixing:** - ```bash - ./src/scripts/fix-ts-errors.sh --verbose - ``` - -## Manual Fixes - -Some TypeScript errors require manual intervention because they involve complex -logic or project-specific knowledge. Here are some tips for common manual fixes: - -### 1. Type Assertion When Needed - -```typescript -// Error: -const id = someFunction() // Type 'unknown' is not assignable to type 'string' - -// Fixed using type assertion: -const id = someFunction() as string -``` - -### 2. Properly Typing React Components - -```typescript -// Error: -function MyComponent(props) { - /* ... */ -} // 'props' implicitly has 'any' type - -// Fixed: -interface MyComponentProps { - name: string - age?: number -} - -function MyComponent(props: MyComponentProps) { - /* ... */ -} -``` - -### 3. Adding Type Guards - -```typescript -// Error: -function processData(data: any) { - return data.value // Unsafe property access -} - -// Fixed with type guard: -function processData(data: unknown) { - if (data && typeof data === 'object' && 'value' in data) { - return data.value - } - return undefined -} -``` - -## Troubleshooting - -If you encounter issues with the automatic fixer: - -1. Try running TypeScript compiler to see detailed error messages: - - ```bash - npx tsc --noEmit - ``` - -2. Fix one file at a time using the `--fix-file` option. - -3. Some complex errors may require manual fixing. Use the error messages from - the TypeScript compiler as guidance. - ---- - -For more information, contact the development team or refer to the TypeScript -documentation. diff --git a/package.json b/package.json index 820487a9f3..82662ed18f 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "dev:training-server": "NODE_ENV=development tsx apps/web/src/lib/services/training/server.ts", "dev:websocket": "NODE_ENV=development tsx apps/web/src/server.ts", "dev:academic-sourcing": "ENV=development uv run uvicorn ai.sourcing.academic.api.main:app --host 0.0.0.0 --port 8000 --reload", - "dev:all-services": "scripts/dev/health-check.sh --quiet && concurrently \"pnpm dev\" \"pnpm dev:bias-detection\" \"pnpm dev:ai-service\" \"pnpm dev:analytics\" \"pnpm dev:worker\" \"pnpm dev:training-server\" \"pnpm dev:websocket\" \"pnpm dev:academic-sourcing\" \"pnpm bias:py\"", + "dev:all-services": "concurrently \"pnpm dev\" \"pnpm dev:bias-detection\" \"pnpm dev:ai-service\" \"pnpm dev:analytics\" \"pnpm dev:worker\" \"pnpm dev:training-server\" \"pnpm dev:websocket\" \"pnpm dev:academic-sourcing\" \"pnpm bias:py\"", "agent:runner": "uv run python -m tools.agent_runner run", "agent:runner:once": "uv run python -m tools.agent_runner run --once", "agent:triage": "uv run python -m tools.agent_runner triage", @@ -88,21 +88,12 @@ "lint:markdown:ci": "node scripts/ci/lint-changed.mjs --markdown", "lint:markdown:ci:all": "pnpm exec markdownlint --config .markdownlint.json --ignore node_modules --ignore '**/node_modules/**' \"**/*.md\" \"**/*.mdx\"", "lint:fix": "oxlint -c .oxlintrc.json --fix-dangerously . && pnpm lint:no-suppressions", - "typecheck": "NODE_OPTIONS='--max-old-space-size=8192' astro check && NODE_OPTIONS='--max-old-space-size=8192' tsc --noEmit", - "typecheck:strict": "NODE_OPTIONS='--max-old-space-size=8192' tsx scripts/validate-typescript.ts", - "type-check": "pnpm run typecheck", - "check": "NODE_OPTIONS='--max-old-space-size=8192' astro check", - "check:all": "pnpm run check && pnpm run typecheck", "ts:debug": "./debug-typescript.sh normal", "ts:debug:verbose": "./debug-typescript.sh verbose", "ts:debug:minimal": "./debug-typescript.sh minimal", "ts:debug:clean": "./debug-typescript.sh clean", "ts:debug:repro": "./debug-typescript.sh repro", "ts:debug:monitor": "./debug-typescript.sh monitor", - "ts:check:debug": "tsc --noEmit --project config/tsconfig.debug.json", - "ts:check:main": "tsc --noEmit --project config/tsconfig.json", - "ts:check:aws": "tsc --noEmit --project config/tsconfig.aws.json", - "ts:compare": "pnpm run ts:check:debug && echo '✅ Debug config works' && pnpm run ts:check:main && echo '✅ Main config works'", "ts:switch:debug": "cp config/tsconfig.json config/tsconfig.json.backup && cp config/tsconfig.debug.json config/tsconfig.json", "ts:switch:main": "cp config/tsconfig.json.backup config/tsconfig.json", "ts:logs": "echo 'TypeScript logs location varies by system. Run: ./debug-typescript.sh normal'", @@ -206,7 +197,7 @@ "submodules:update": "git submodule update", "submodules:sync": "git submodule init && git submodule update", "jules:setup": "bash scripts/devops/jules-setup.sh", - "jules:check": "git status | grep 'on branch staging' || (echo '❌ ERROR: Jules must work on staging branch' && exit 1) && pnpm check:all", + "jules:check": "git status | grep 'on branch staging' || (echo '❌ ERROR: Jules must work on staging branch' && exit 1) && pnpm lint", "hooks:install": "if [ -f scripts/devops/install-git-hooks.sh ]; then bash scripts/devops/install-git-hooks.sh; else echo \"Skipping git hooks install: scripts/devops/install-git-hooks.sh not found.\"; fi", "prepare": "husky && pnpm run -s hooks:install", "safe": "./scripts/utils/no-fail.sh", @@ -216,23 +207,16 @@ "safe:test:e2e": "./scripts/utils/no-fail.sh pnpm run test:e2e", "safe:test:all": "./scripts/utils/no-fail.sh pnpm run test:all", "safe:lint": "./scripts/utils/no-fail.sh pnpm run lint", - "safe:typecheck": "./scripts/utils/no-fail.sh pnpm run typecheck", - "tags:create": "node scripts/tag-manager.js create", - "tags:list": "node scripts/tag-manager.js list", - "tags:validate": "node scripts/tag-manager.js validate", - "tags:cleanup": "node scripts/tag-manager.js cleanup", - "tags:maintenance": "node scripts/tag-maintenance.js full-maintenance", - "version:release": "node scripts/version-manager.js release", - "version:info": "node scripts/version-manager.js info", + "tags:create": "node scripts/utils/tag-manager.cjs create", + "tags:list": "node scripts/utils/tag-manager.cjs list", + "tags:validate": "node scripts/utils/tag-manager.cjs validate", + "tags:cleanup": "node scripts/utils/tag-manager.cjs cleanup", "toolbar:on": "astro preferences enable devToolbar", "toolbar:off": "astro preferences disable devToolbar", "blog-publisher": "tsx apps/web/src/lib/scripts/blog-publisher.ts", - "blog": "node scripts/blog-web.js", + "blog": "node scripts/content/blog-web.js", "schedule-posts": "node scripts/content/schedule-posts.js", "mcp:installer": "pnpm exec cursor-mcp-installer-free /workspaces/pixelated", - "mcp:list": "node scripts/mcp-manager.js list", - "mcp:run": "node scripts/mcp-manager.js run", - "mcp:manage": "node scripts/mcp-manager.js", "backend:up": "set -a && . ./.env.backend && set +a && docker compose -f infra/docker/docker-compose.backend.yml up -d", "backend:down": "set -a && . ./.env.backend && set +a && docker compose -f infra/docker/docker-compose.backend.yml down", "backend:logs": "set -a && . ./.env.backend && set +a && docker compose -f infra/docker/docker-compose.backend.yml logs -f", @@ -402,7 +386,6 @@ "@ai-sdk/mcp": "^2.0.29", "@ai-sdk/provider": "^4.0.8", "@ai-sdk/sandbox-vercel": "^1.0.87", - "@astrojs/check": "^0.9.10", "@astrojs/markdoc": "^2.1.1", "@astrojs/node": "^11.1.4", "@astrojs/react": "^7.1.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 31874ca5fc..5e247978d2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -790,9 +790,6 @@ importers: '@ai-sdk/sandbox-vercel': specifier: ^1.0.87 version: 1.0.153(ws@8.22.0(utf-8-validate@6.0.6))(zod@4.6.7) - '@astrojs/check': - specifier: ^0.9.10 - version: 0.9.10(@emnapi/runtime@1.11.3)(prettier-plugin-astro@1.1.1(prettier@3.9.10))(prettier@3.9.10)(typescript@5.8.3) '@astrojs/markdoc': specifier: ^2.1.1 version: 2.1.1(@types/react@19.3.0)(astro@7.4.2(patch_hash=74950e75e5c1b6b481917ab76bdc51751e1359ee6e6c5618fa69037bad96d2a5)(@azure/storage-blob@12.34.0(supports-color@10.2.2))(@types/node@26.6.5)(@upstash/redis@1.40.0)(@vercel/functions@3.10.1(@aws-sdk/credential-provider-web-identity@3.972.80)(ws@8.22.0(utf-8-validate@6.0.6)))(crossws@0.4.15(srvx@1.0.5))(db0@0.4.1)(esbuild@0.28.2)(ioredis@5.11.1(supports-color@10.2.2))(jiti@2.7.0)(ocache@0.3.0)(terser@5.51.2)(tsx@4.23.15)(yaml@2.8.4))(react@19.3.1) @@ -2090,12 +2087,6 @@ packages: resolution: {integrity: sha512-GMDC7Eo0myBUA/QLBl0u+OcegJ8LiBhccmNZR34KmprQNP8gQUJlWlOOZWUsxNA2rNS6GuPYcGDyLoPnjmn4/Q==} engines: {node: ^20.19.0 || >=22.12.0} - '@astrojs/check@0.9.10': - resolution: {integrity: sha512-zgx/UQMozdjOa3bOxjgeCFdtpE3c9rRX6xHwa+2QXvy8z8Akifu2AtubHyv/zzC2znO8dl8fFWL4K+Ba9kS8HQ==} - hasBin: true - peerDependencies: - typescript: ^5.0.0 || ^6.0.0 - '@astrojs/compiler-binding-android-arm64@0.5.2': resolution: {integrity: sha512-kckIxon6Rm/Bxzh9mGPYpRRqUbToUQoHptCmpMmE+Yt0Xumq8CqrKUidyYhrBsrHVO7bxuRzzWUUqvpf+iYceQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -16466,18 +16457,6 @@ snapshots: transitivePeerDependencies: - '@emnapi/runtime' - '@astrojs/check@0.9.10(@emnapi/runtime@1.11.3)(prettier-plugin-astro@1.1.1(prettier@3.9.10))(prettier@3.9.10)(typescript@5.8.3)': - dependencies: - '@astrojs/language-server': 2.17.2(@emnapi/runtime@1.11.3)(prettier-plugin-astro@1.1.1(prettier@3.9.10))(prettier@3.9.10)(typescript@5.8.3) - chokidar: 4.0.3 - kleur: 4.1.5 - typescript: 5.8.3 - yargs: 18.2.0 - transitivePeerDependencies: - - '@emnapi/runtime' - - prettier - - prettier-plugin-astro - '@astrojs/compiler-binding-android-arm64@0.5.2': optional: true diff --git a/reasonix.toml b/reasonix.toml index a09424b8f3..c6bf89be1d 100644 --- a/reasonix.toml +++ b/reasonix.toml @@ -1,6 +1,2 @@ [permissions] -allow = [ - "Bash=pnpm run typecheck 2>&1 | head -50", - "Bash=NODE_OPTIONS='--max-old-space-size=8192' npx astro check 2>&1 | head -50", - "Bash=NODE_OPTIONS='--max-old-space-size=8192' npx astro check 2>&1 | tail -30", -] +allow = [] diff --git a/scripts/content/blog-web.js b/scripts/content/blog-web.js index 109e8756e6..3564405105 100644 --- a/scripts/content/blog-web.js +++ b/scripts/content/blog-web.js @@ -11,7 +11,7 @@ import { fileURLToPath } from 'url' /* jshint esversion: 6, node: true */ // IMPORTANT: Import Sentry instrumentation first -import '../instrument.mjs' +import '../../config/instrument.mjs' // Get current directory const __filename = fileURLToPath(import.meta.url) @@ -439,7 +439,7 @@ const server = http.createServer((req, res) => { // Start server const PORT = process.env.PORT || 3333 -server.listen(PORT, () => { +server.listen(PORT, '127.0.0.1', () => { const url = `http://localhost:${PORT}` console.log(`Blog management interface running at ${url}`) diff --git a/scripts/devops/aggregate-readiness.py b/scripts/devops/aggregate-readiness.py index 9e0c1cf720..f04699e550 100644 --- a/scripts/devops/aggregate-readiness.py +++ b/scripts/devops/aggregate-readiness.py @@ -201,17 +201,17 @@ def run_lint(dry_run: bool, cwd: str) -> LaneResult: def run_typecheck(dry_run: bool, cwd: str) -> LaneResult: - """Run the project's type checker (tsc + astro check).""" + """Run the type-aware lint gate (oxlint) — replaces the removed tsc/astro check.""" if dry_run: - logger.info(" ▌ [dry-run] Typecheck (astro check + tsc)") + logger.info(" ▌ [dry-run] Type-aware lint (oxlint)") return { - "command": "pnpm typecheck", + "command": "pnpm lint", "status": "pass", "exitCode": 0, "stdout": "", "stderr": "", } - return run_command(["pnpm", "typecheck"], timeout=600, cwd=cwd, max_retries=2) + return run_command(["pnpm", "lint"], timeout=600, cwd=cwd, max_retries=2) def run_tests(dry_run: bool, cwd: str) -> LaneResult: diff --git a/scripts/testing/run-final-tests.sh b/scripts/testing/run-final-tests.sh index ae1eb59616..eb8d03f46d 100755 --- a/scripts/testing/run-final-tests.sh +++ b/scripts/testing/run-final-tests.sh @@ -71,13 +71,13 @@ else exit 1 fi -# Run type checking -npm run typecheck +# Run type-aware lint (oxlint) — typecheck scripts were removed per AGENTS.md +pnpm lint if [ $? -eq 0 ]; then - echo -e "${GREEN}✅ Type checking passed!${NC}" + echo -e "${GREEN}✅ Type-aware lint passed!${NC}" else - echo -e "${RED}❌ Type checking failed!${NC}" + echo -e "${RED}❌ Type-aware lint failed!${NC}" exit 1 fi diff --git a/scripts/utils/tag-manager.cjs b/scripts/utils/tag-manager.cjs index 060e53a092..b3eff9e28b 100644 --- a/scripts/utils/tag-manager.cjs +++ b/scripts/utils/tag-manager.cjs @@ -1,11 +1,11 @@ #!/usr/bin/env node -// Minimal tag-manager.cjs for rollback workflow (CommonJS) +// Minimal tag-manager.js for rollback workflow const { execFileSync } = require('child_process') const args = process.argv.slice(2) function usage() { console.log( - 'Usage: node tag-manager.cjs [--message="msg"] [--push]', + 'Usage: node tag-manager.js [--message="msg"] [--push]', ) process.exit(1) } @@ -24,7 +24,9 @@ function getTags(pattern) { const tags = execFileSync( 'git', ['tag', '-l', pattern, '--sort=-committerdate'], - { encoding: 'utf8' }, + { + encoding: 'utf8', + }, ) return tags.split('\n').filter(Boolean) } catch { @@ -41,6 +43,30 @@ if (command === 'validate') { } } +if (command === 'list') { + const tags = getTags(`${env}-*`) + for (const t of tags) { + console.log(t) + } + process.exit(0) +} + +if (command === 'cleanup') { + const tags = getTags(`${env}-*`) + // Keep the 2 most recent tags (rollback needs a pair); delete the rest + const stale = tags.slice(2) + for (const t of stale) { + try { + execFileSync('git', ['tag', '-d', t]) + console.log(`Deleted tag: ${t}`) + } catch (e) { + console.error(`Failed to delete tag ${t}: ${e.message}`) + } + } + console.log(`Cleanup complete: deleted ${stale.length}, kept ${Math.min(tags.length, 2)}`) + process.exit(0) +} + if (command === 'create') { // Parse message and push let message = '' @@ -53,12 +79,17 @@ if (command === 'create') { push = true } } - const timestamp = new Date().toISOString().replace(/[-:T]/g, '').slice(0, 14) - const tagName = `${env}-${timestamp}` + // Millisecond precision + uniqueness loop: same-second creates must not collide + let tagName = `${env}-${new Date().toISOString().replace(/[-:T]/g, '').slice(0, 17)}` + const existing = getTags(`${env}-*`) + let suffix = 1 + while (existing.includes(tagName)) { + tagName = `${env}-${new Date().toISOString().replace(/[-:T]/g, '').slice(0, 17)}-${suffix++}` + } try { execFileSync('git', ['tag', '-a', tagName, '-m', message || 'Rollback tag']) if (push) { - const remote = process.env.GIT_PUSH_REMOTE ?? 'origin' + const remote = process.env.GIT_PUSH_REMOTE || 'origin' execFileSync('git', ['push', remote, tagName]) } console.log(`Created tag: ${tagName}`) diff --git a/scripts/utils/tag-manager.js b/scripts/utils/tag-manager.js deleted file mode 100644 index ab657cb476..0000000000 --- a/scripts/utils/tag-manager.js +++ /dev/null @@ -1,74 +0,0 @@ -#!/usr/bin/env node -// Minimal tag-manager.js for rollback workflow -const { execFileSync } = require('child_process') -const args = process.argv.slice(2) - -function usage() { - console.log( - 'Usage: node tag-manager.js [--message="msg"] [--push]', - ) - process.exit(1) -} - -if (args.length < 2) { - usage() -} -const command = args[0] -const env = args[1] -if (!['staging', 'production'].includes(env)) { - usage() -} - -function getTags(pattern) { - try { - const tags = execFileSync( - 'git', - ['tag', '-l', pattern, '--sort=-committerdate'], - { - encoding: 'utf8', - }, - ) - return tags.split('\n').filter(Boolean) - } catch { - return [] - } -} - -if (command === 'validate') { - const tags = getTags(`${env}-*`) - if (tags.length >= 2) { - process.exit(0) - } else { - process.exit(1) - } -} - -if (command === 'create') { - // Parse message and push - let message = '' - let push = false - for (let i = 2; i < args.length; i++) { - if (args[i].startsWith('--message=')) { - message = args[i].replace('--message=', '') - } - if (args[i] === '--push') { - push = true - } - } - const timestamp = new Date().toISOString().replace(/[-:T]/g, '').slice(0, 14) - const tagName = `${env}-${timestamp}` - try { - execFileSync('git', ['tag', '-a', tagName, '-m', message || 'Rollback tag']) - if (push) { - const remote = process.env.GIT_PUSH_REMOTE || 'origin' - execFileSync('git', ['push', remote, tagName]) - } - console.log(`Created tag: ${tagName}`) - process.exit(0) - } catch (e) { - console.error('Failed to create tag:', e.message) - process.exit(1) - } -} - -usage()