From 27d6062992bd4b37523e6e59bb14077621058fb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E6=A9=98?= Date: Thu, 28 May 2026 09:49:00 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20normalize=20workflow=20v2=20=E2=80=94?= =?UTF-8?q?=20JS/MJS=20project=20support?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - typescript role: detect .ts/.tsx files, skip tsc for pure JS/MJS projects - testing role: vitest --passWithNoTests for empty test suites - committer role: tsconfig.json optional in spot-check - all roles: proper frontmatter schemas with repoPath 小橘 --- workflows/normalize-bun-monorepo.yaml | 696 ++++++++++++++++++++++++++ 1 file changed, 696 insertions(+) create mode 100644 workflows/normalize-bun-monorepo.yaml diff --git a/workflows/normalize-bun-monorepo.yaml b/workflows/normalize-bun-monorepo.yaml new file mode 100644 index 0000000..a15eff1 --- /dev/null +++ b/workflows/normalize-bun-monorepo.yaml @@ -0,0 +1,696 @@ +name: normalize-bun-monorepo +graph: + ci: + done: + role: solve-issue-workflow + prompt: CI configured. Register solve-issue workflow for repo at {{{repoPath}}}. + failed: + role: solve-issue-workflow + prompt: CI setup failed ({{{reason}}}), but continue. Register solve-issue workflow for repo at {{{repoPath}}}. + biome: + done: + role: package-metadata + prompt: Biome configured. Standardize package metadata for repo at {{{repoPath}}}. + failed: + role: package-metadata + prompt: Biome setup failed ({{{reason}}}), but continue. Standardize package metadata for repo at {{{repoPath}}}. + $START: + _: + role: workspace + prompt: Set up bun workspace structure for repo at {{{repoPath}}}. + release: + done: + role: testing + prompt: Release pipeline configured. Set up vitest for repo at {{{repoPath}}}. + failed: + role: testing + prompt: Release pipeline failed ({{{reason}}}), but continue. Set up vitest for repo at {{{repoPath}}}. + testing: + done: + role: ci + prompt: Testing configured. Set up Gitea CI for repo at {{{repoPath}}}. + failed: + role: ci + prompt: Testing setup failed ({{{reason}}}), but continue. Set up Gitea CI for repo at {{{repoPath}}}. + committer: + failed: + role: $END + prompt: "Commit failed: {{{reason}}}." + committed: + role: $END + prompt: "Normalization committed: {{{commitHash}}}." + no_changes: + role: $END + prompt: Repo already normalized, no changes needed. + workspace: + done: + role: typescript + prompt: Workspace ready. Configure TypeScript for repo at {{{repoPath}}}. + failed: + role: typescript + prompt: Workspace setup failed ({{{reason}}}), but continue. Configure TypeScript for repo at {{{repoPath}}}. + guardrails: + done: + role: committer + prompt: All normalization complete. Commit changes in repo at {{{repoPath}}}. + failed: + role: committer + prompt: Guardrails failed ({{{reason}}}), but commit whatever was done in repo at {{{repoPath}}}. + typescript: + done: + role: biome + prompt: TypeScript configured. Set up Biome for repo at {{{repoPath}}}. + failed: + role: biome + prompt: TypeScript setup failed ({{{reason}}}), but continue. Set up Biome for repo at {{{repoPath}}}. + package-metadata: + done: + role: release + prompt: Package metadata standardized. Configure release pipeline for repo at {{{repoPath}}}. + failed: + role: release + prompt: Package metadata failed ({{{reason}}}), but continue. Configure release pipeline for repo at {{{repoPath}}}. + solve-issue-workflow: + done: + role: guardrails + prompt: Solve-issue workflow registered. Install guardrails for repo at {{{repoPath}}}. + failed: + role: guardrails + prompt: Solve-issue workflow failed ({{{reason}}}), but continue. Install guardrails for repo at {{{repoPath}}}. +roles: + ci: + goal: You configure Gitea Actions CI for build, lint, and test on push/PR. + output: Describe the CI pipeline configured. Set $status to done or failed. + procedure: | + cd into the repo path from your task prompt. + + IMPORTANT: If `.gitea/workflows/ci.yml` already exists, review it for completeness but don't overwrite unless it's missing key steps. + If `.github/workflows/` exists (GitHub Actions), keep it — add `.gitea/workflows/` alongside it. + + Create `.gitea/workflows/ci.yml` (if not present): + ```yaml + name: CI + + on: + push: + branches: ['*'] + pull_request: + branches: [main] + + jobs: + check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: oven-sh/setup-bun@v2 + + - run: bun install + + - name: Build + run: bun run build + + - name: Lint + run: bun run check + + - name: Test + run: bun run test:ci + ``` + + ## Verification + ```bash + # 1. CI file exists + test -f .gitea/workflows/ci.yml + # 2. YAML is valid + node -e " + const fs = require('fs'); + const content = fs.readFileSync('.gitea/workflows/ci.yml', 'utf8'); + if (!content.includes('bun')) { console.error('Missing bun setup'); process.exit(1); } + if (!content.includes('test')) { console.error('Missing test step'); process.exit(1); } + console.log('CI YAML looks valid'); + " + # 3. Required root scripts exist for CI to work + node -e " + const pkg = require('./package.json'); + const required = ['build', 'check', 'test:ci']; + const missing = required.filter(s => !pkg.scripts?.[s]); + if (missing.length) { console.error('Missing scripts for CI:', missing.join(', ')); process.exit(1); } + console.log('All CI-required scripts present'); + " + ``` + + Post-condition: CI file exists, YAML references bun and test, all required scripts exist in package.json. + description: Set up Gitea CI workflow + frontmatter: + oneOf: + - properties: + $status: + const: done + repoPath: + type: string + - properties: + $status: + const: failed + reason: + type: string + repoPath: + type: string + capabilities: + - ci-config + biome: + goal: You configure Biome for consistent code quality across the monorepo. + output: List what was configured and any remaining lint issues. Set $status to done or failed. + procedure: | + cd into the repo path from your task prompt. + + IMPORTANT: Be idempotent — if biome.json already exists, merge missing settings rather than overwriting. + + Check and fix: + 1. Install biome: add `@biomejs/biome` to root devDependencies (skip if already present) + 2. Root `biome.json` must exist with at minimum: + - `files.includes`: `["**", "!**/dist", "!**/node_modules"]` + - `formatter`: indentStyle space, indentWidth 2, lineWidth 100 + - `javascript.formatter`: quoteStyle double, semicolons always + - `linter.rules.nursery.noConsole: "error"` (production code) + - Override for test files (`**/__tests__/**`): `noConsole: "off"`, `noExplicitAny: "off"` + - `assist.actions.source.organizeImports: "on"` + If biome.json already exists, only add missing fields — preserve existing customizations. + 3. Root scripts must include: `"check"` (should include `biome check .`), `"format": "biome format --write ."` + 4. Run `bunx biome check .` — fix auto-fixable issues with `bunx biome check . --fix` + 5. Remaining unfixable issues: list them but don't block + + ## Verification + ```bash + # 1. biome.json exists + test -f biome.json + # 2. biome is installed + bunx biome --version + # 3. check runs (exit 0 or list remaining issues) + bunx biome check . 2>&1 || true + ``` + + Post-condition: `biome.json` exists, `bunx biome check .` runs (may have warnings but no infrastructure errors). + description: Configure Biome linter and formatter + frontmatter: + oneOf: + - properties: + $status: + const: done + repoPath: + type: string + - properties: + $status: + const: failed + reason: + type: string + repoPath: + type: string + capabilities: + - linter-config + release: + goal: "You set up the complete release pipeline: changesets for version management, publish script for npm release." + output: Describe what was configured. Set $status to done or failed. + procedure: | + cd into the repo path from your task prompt. + + IMPORTANT: Be idempotent — skip steps that are already done. + + ## Part 1: Changesets + 1. Install: add `@changesets/cli` to root devDependencies (skip if present), run `bun install` + 2. If `.changeset/config.json` does not exist, run `bunx changeset init` + 3. Verify `.changeset/config.json` has: + - `"access": "public"` (for @scoped packages) + - `"baseBranch": "main"` + 4. Add `.changeset/README.md` if missing + + ## Part 2: Publish Script + 5. Create `scripts/publish-all.mjs` (skip if already exists and looks correct): + - Scan `packages/` for non-private packages (skip `"private": true`) + - Determine publish order by resolving workspace dependency graph (dependees before dependents) + - For each package in order: + a. Replace `"workspace:^"` deps with actual versions from workspace + b. Run `npm publish --access public --ignore-scripts` (MUST use --ignore-scripts to bypass prepublishOnly guardrail) + c. Restore original package.json + - Support flags: `--dry-run`, `--tag ` + - Handle errors: if one package fails, restore and continue + CRITICAL: The publish command MUST include `--ignore-scripts` because the guardrails role adds a `prepublishOnly` script that blocks direct publishing. + + ## Part 3: Root Scripts + 6. Root scripts must include: + - `"changeset": "bunx changeset"` + - `"version": "bunx changeset version"` + - `"release": "bun run build && bun run test && node scripts/publish-all.mjs"` + + ## Verification + ```bash + # 1. changeset config exists and is valid + test -f .changeset/config.json + node -e "const c = require('./.changeset/config.json'); console.log('access:', c.access, 'baseBranch:', c.baseBranch)" + # 2. changeset status works + bunx changeset status 2>&1 || true + # 3. publish script exists and uses --ignore-scripts + test -f scripts/publish-all.mjs + grep -q 'ignore-scripts' scripts/publish-all.mjs + # 4. dry run works + node scripts/publish-all.mjs --dry-run + ``` + + Post-condition: All verification commands pass. + description: Configure changesets and publish pipeline + frontmatter: + oneOf: + - properties: + $status: + const: done + repoPath: + type: string + - properties: + $status: + const: failed + reason: + type: string + repoPath: + type: string + capabilities: + - changeset-config + - release-config + testing: + goal: You set up vitest test infrastructure across the monorepo. + output: List what was configured per package. Set $status to done or failed. + procedure: | + cd into the repo path from your task prompt. + + IMPORTANT: Be idempotent — do NOT overwrite existing vitest.config.ts or test files. + + Check and fix: + 1. Add `vitest` to root devDependencies (skip if present), run `bun install` + 2. For each package under `packages/`: + - If `vitest.config.ts` does NOT already exist, create it: + ```ts + import { defineConfig } from "vitest/config"; + export default defineConfig({ + test: { + include: ["src/__tests__/**/*.test.ts"], + }, + }); + ``` + - If package.json has no `"test"` script, add: `"test": "vitest run --passWithNoTests"`, `"test:ci": "vitest run --passWithNoTests"` + - Create `src/__tests__/` directory if it doesn't exist + 3. Root package.json scripts must include: + - `"test": "bun run --filter './packages/*' test"` + - `"test:ci": "bun run --filter './packages/*' test:ci"` + + ## Verification + ```bash + # 1. vitest is installed + bunx vitest --version + # 2. each package has vitest config + for d in packages/*/; do + if [ -f "$d/vitest.config.ts" ]; then echo "$d: ✅"; else echo "$d: ❌ missing vitest.config.ts"; fi + done + # 3. root test script works + bun run test 2>&1 || true + ``` + + Post-condition: `bun run test` runs without infrastructure errors (no tests is OK, test failures are OK). + description: Configure vitest for all packages + frontmatter: + oneOf: + - properties: + $status: + const: done + repoPath: + type: string + - properties: + $status: + const: failed + reason: + type: string + repoPath: + type: string + capabilities: + - test-config + committer: + goal: You commit all the changes made by previous roles in a single clean commit. + output: List files changed and commit hash. Set $status to committed or no_changes. + procedure: | + cd into the repo path from your task prompt. + + 1. Review all changes: `git diff --stat` and `git status` + 2. Verify key files exist from previous roles (spot-check): + ```bash + echo "=== Spot-check ===" + if [ -f tsconfig.json ]; then echo "✅ tsconfig.json"; else echo "ℹ️ tsconfig.json (skipped for JS/MJS)"; fi + for f in .gitignore biome.json .changeset/config.json .gitea/workflows/ci.yml .githooks/pre-push; do + test -f "$f" && echo "✅ $f" || echo "⚠️ MISSING: $f" + done + node -e " + const p = require('./package.json'); + const required = ['build', 'check', 'test', 'test:ci', 'format', 'preinstall', 'prepublishOnly']; + const missing = required.filter(s => !p.scripts?.[s]); + if (missing.length) console.log('⚠️ Missing scripts:', missing.join(', ')); + else console.log('✅ All scripts present'); + if (!p.packageManager) console.log('⚠️ Missing packageManager'); + else console.log('✅ packageManager:', p.packageManager); + " + ``` + List any missing items as warnings but still commit what exists. + 3. If no changes: set $status=no_changes + 4. Stage all: `git add -A` + 5. Commit: `git commit -m "chore: normalize to bun monorepo conventions"` + 6. Push: `git push` + + Post-condition: Clean commit pushed, `git status` shows clean working tree. + description: Commits all normalization changes + frontmatter: + oneOf: + - properties: + $status: + const: committed + commitHash: + type: string + repoPath: + type: string + - properties: + $status: + const: no_changes + repoPath: + type: string + - properties: + $status: + const: failed + reason: + type: string + repoPath: + type: string + capabilities: [] + workspace: + goal: You set up the foundational bun workspace configuration for a monorepo. + output: List what was changed. Set $status to done (workspace working) or failed (with reason). + procedure: | + cd into the repo path provided in your task prompt. + + IMPORTANT: Be idempotent — check before modifying. If something is already correct, skip it. + + Check and fix: + 1. Root `package.json` must have `"workspaces": ["packages/*"]` + 2. Root `package.json` must have `"private": true` + 3. If packages exist in other locations (e.g. root src/, top-level dirs), migrate them under `packages/` + 4. Each package under `packages/` must have its own `package.json` with `"name"` and `"type": "module"` + 5. `.gitignore` must exist and include at minimum: + ``` + node_modules/ + dist/ + *.tsbuildinfo + ``` + If `.gitignore` is missing or doesn't cover these, append the missing entries (don't overwrite existing content). + 6. If node_modules/ is already tracked in git, remove it: `git rm -r --cached node_modules/ */node_modules/ 2>/dev/null` + 7. Run `bun install` to verify workspace resolution works + + ## Verification (must all pass) + ```bash + # 1. bun install works + bun install + # 2. gitignore covers essentials + grep -q 'node_modules' .gitignore + grep -q 'dist' .gitignore + # 3. no node_modules tracked + test -z "$(git ls-files | grep node_modules)" + # 4. all packages have package.json + for d in packages/*/; do test -f "$d/package.json" || echo "MISSING: $d/package.json"; done + ``` + + Post-condition: All verification commands pass. + description: Ensure bun workspace structure + frontmatter: + oneOf: + - properties: + $status: + const: done + repoPath: + type: string + - properties: + $status: + const: failed + reason: + type: string + repoPath: + type: string + capabilities: + - workspace-setup + guardrails: + goal: You configure enforcement mechanisms that block npm/pnpm/yarn usage and direct npm publish. + output: List what guardrails were installed. Set $status to done or failed. + procedure: | + cd into the repo path from your task prompt. + + IMPORTANT: Be idempotent — check before adding. + + ## 1. Block wrong package manager + Add to root `package.json` (if not already present): + - `"packageManager": "bun@"` — use the version from `bun --version` + - `"scripts.preinstall": "npx only-allow bun"` — blocks npm/pnpm/yarn install + + ## 2. Block direct npm publish + Add to root `package.json` (if not already present): + - `"scripts.prepublishOnly": "echo 'Use bun run release instead' && exit 1"` + + For each non-private package under `packages/`: + - Add `"scripts.prepublishOnly": "echo 'Use bun run release from repo root' && exit 1"` to their package.json (if not present) + + If `scripts/publish-all.mjs` exists, verify it uses `--ignore-scripts` in the npm publish command. + If it doesn't, add `--ignore-scripts` to the publish command. + + ## 3. Git hooks + Create `.githooks/pre-push` (if not already present): + ```bash + #!/usr/bin/env bash + set -euo pipefail + echo "🔍 Running checks..." + bun run check + echo "🧪 Running tests..." + bun run test + echo "✅ All checks passed!" + ``` + Make it executable: `chmod +x .githooks/pre-push` + Configure git to use hooks dir: `git config core.hooksPath .githooks` + + ## Verification + ```bash + # 1. packageManager field exists + node -e "const p = require('./package.json'); if (!p.packageManager) { console.error('❌ missing packageManager'); process.exit(1); } console.log('✅ packageManager:', p.packageManager)" + # 2. preinstall guard exists + node -e "const p = require('./package.json'); if (!p.scripts?.preinstall?.includes('only-allow')) { console.error('❌ missing preinstall guard'); process.exit(1); } console.log('✅ preinstall guard')" + # 3. npm install is blocked (with timeout to prevent hang) + timeout 10 npm install 2>&1 | head -5 || true + # 4. prepublishOnly exists + node -e "const p = require('./package.json'); if (!p.scripts?.prepublishOnly) { console.error('❌ missing prepublishOnly'); process.exit(1); } console.log('✅ prepublishOnly guard')" + # 5. publish script uses --ignore-scripts + if [ -f scripts/publish-all.mjs ]; then grep -q 'ignore-scripts' scripts/publish-all.mjs && echo '✅ publish uses --ignore-scripts' || echo '❌ publish missing --ignore-scripts'; fi + # 6. git hooks configured + test -f .githooks/pre-push && echo '✅ pre-push hook' || echo '❌ missing pre-push hook' + ``` + + Post-condition: All verification checks pass. + description: Install project guardrails to prevent wrong package manager and publish workflow + frontmatter: + oneOf: + - properties: + $status: + const: done + repoPath: + type: string + - properties: + $status: + const: failed + reason: + type: string + repoPath: + type: string + capabilities: + - guardrails + typescript: + goal: You configure TypeScript for a bun monorepo with composite project references. + output: List what was configured. Set $status to done or failed. + procedure: |- + cd into the repo path from your task prompt. + + IMPORTANT: Be idempotent — if tsconfig.json already exists with correct settings, don't overwrite. + + ## Step 0: Detect if project needs TypeScript compilation + + ```bash + TS_FILES=$(find packages/ -name '*.ts' -o -name '*.tsx' | grep -v node_modules | grep -v dist | grep -v '.d.ts' | head -5) + ``` + + **If there are NO .ts/.tsx source files** (pure JS/MJS project): + - Do NOT create root tsconfig.json + - Do NOT add `bunx tsc --build` as the build script + - Do NOT add typescript/bun-types to devDependencies unless already present + - Preserve the existing `build` script (e.g. vite build, esbuild, etc.) + - If no build script exists, add one based on the project's bundler (check for vite.config, esbuild, etc.) + - Set $status=done with note "JS/MJS project — skipped TypeScript setup" + - STOP HERE. + + **If .ts/.tsx files exist**, continue with full TypeScript setup: + + Check and fix: + 1. Root `tsconfig.json` must exist with: + - `"compilerOptions"`: target ES2022, module NodeNext, moduleResolution NodeNext, strict true, composite true, declaration true, declarationMap true, sourceMap true + - `"references"`: array with `{ "path": "packages/" }` for each package + - `"files": []` (root does not compile files directly) + 2. Each package must have `tsconfig.json` with: + - `"extends": "../../tsconfig.json"` (inherit root config) + - `"compilerOptions": { "rootDir": "src", "outDir": "dist" }` + - `"include": ["src"]` + - `"references"` to sibling packages it depends on + 3. Root scripts must include: `"build": "bunx tsc --build"`, `"typecheck": "bunx tsc --build"` + 4. `devDependencies` at root: `typescript`, `bun-types`, `@types/node` + + ## Verification + ```bash + if [ ! -f tsconfig.json ]; then + echo "JS/MJS project — no tsconfig needed" + node -e "const p = require('./package.json'); if (!p.scripts?.build) { console.error('Missing build script'); process.exit(1); } console.log('build:', p.scripts.build)" + bun run build + exit 0 + fi + test -f tsconfig.json + for d in packages/*/; do test -f "$d/tsconfig.json" || echo "MISSING: $d/tsconfig.json"; done + bunx tsc --build + ``` + + Post-condition: For TS projects — `bunx tsc --build` succeeds. For JS/MJS projects — `bun run build` succeeds. + description: Configure TypeScript with project references + frontmatter: + oneOf: + - properties: + $status: + const: done + repoPath: + type: string + - properties: + $status: + const: failed + reason: + type: string + repoPath: + type: string + capabilities: + - typescript-config + package-metadata: + goal: You ensure every package has consistent metadata for publishing and discoverability. + output: List what was standardized per package. Set $status to done or failed. + procedure: | + cd into the repo path from your task prompt. + + IMPORTANT: Be idempotent — skip fields that are already correctly set. + + For each package under `packages/`: + 1. `"type": "module"` — must be set + 2. `"files": ["src", "dist", "package.json"]` — for published packages (non-private) + 3. `"publishConfig": { "access": "public" }` — for @scoped public packages (non-private) + 4. `"repository"` — must point to the correct git remote and directory + - Read remote URL: `git remote get-url origin` + - Set `"directory": "packages/"` + 5. `"exports"` — conditional exports for TypeScript packages: + ```json + ".": { + "bun": "./src/index.ts", + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + } + ``` + Skip if package has no `src/index.ts`. + 6. Private packages (server, frontend, tools) must have `"private": true` and can skip publishConfig/files + 7. Each package should have a `"scripts"` section with at least `"test"` if tests exist + 8. Workspace dependencies should use `"workspace:^"` protocol, not version numbers + - Check: `grep -r '"@uncaged/' packages/*/package.json | grep -v 'workspace:'` + + ## Verification + ```bash + # Check every non-private package has required fields + for d in packages/*/; do + pkg="$d/package.json" + test -f "$pkg" || continue + node -e " + const p = require('./$pkg'); + if (p.private) { console.log('$d: private, skip'); process.exit(0); } + const issues = []; + if (p.type !== 'module') issues.push('missing type:module'); + if (!p.exports) issues.push('missing exports'); + if (!p.publishConfig) issues.push('missing publishConfig'); + if (!p.files) issues.push('missing files'); + if (issues.length) console.log('$d:', issues.join(', ')); + else console.log('$d: OK'); + " + done + # No hardcoded workspace deps + ! grep -r '"@uncaged/' packages/*/package.json | grep -v 'workspace:' | grep -v node_modules + ``` + + Post-condition: Verification script shows OK for all non-private packages, no hardcoded workspace versions. + description: Standardize package.json metadata across all packages + frontmatter: + oneOf: + - properties: + $status: + const: done + repoPath: + type: string + - properties: + $status: + const: failed + reason: + type: string + repoPath: + type: string + capabilities: + - package-config + solve-issue-workflow: + goal: You set up the solve-issue workflow YAML so the project can use `uwf` for issue resolution. + output: Describe the workflow registered. Set $status to done or failed. + procedure: | + cd into the repo path from your task prompt. + + 1. Check if `uwf` CLI is available: `which uwf` + - If not available: set $status=failed, reason="uwf CLI not installed" + 2. Create `workflows/solve-issue.yaml` adapted for this project: + - Copy the standard solve-issue workflow structure (planner → developer → reviewer → tester → committer) + - Adjust the developer role procedure to use this project's test runner and build commands + - The workflow should reference the correct repo path and build toolchain (bun) + 3. Register with uwf: `uwf workflow add workflows/solve-issue.yaml` + + ## Verification + ```bash + # 1. workflow file exists + test -f workflows/solve-issue.yaml + # 2. registered in uwf + uwf workflow list --format json | node -e " + let d=''; process.stdin.on('data',c=>d+=c); process.stdin.on('end',()=>{ + const wfs = JSON.parse(d); + if (wfs.some(w => w.name === 'solve-issue')) console.log('✅ solve-issue registered'); + else { console.error('❌ solve-issue not found'); process.exit(1); } + }) + " + ``` + + Post-condition: `uwf workflow list` includes solve-issue. + description: Register solve-issue workflow for the project + frontmatter: + oneOf: + - properties: + $status: + const: done + repoPath: + type: string + - properties: + $status: + const: failed + reason: + type: string + repoPath: + type: string + capabilities: + - workflow-config +description: Normalize an existing project to @uncaged bun monorepo conventions. Supports both TypeScript and JS/MJS projects. Each role handles one configuration layer. All roles allow fail.