diff --git a/.github/workflows/deploy-demo-preview-smart.yml b/.github/workflows/deploy-demo-preview-smart.yml index cc11b0e..7bf8cba 100644 --- a/.github/workflows/deploy-demo-preview-smart.yml +++ b/.github/workflows/deploy-demo-preview-smart.yml @@ -104,7 +104,7 @@ jobs: DEPLOY_PATH: ag-grid-react-components-pr-${{ github.event.pull_request.number }} run: | # Generate version info - node scripts/generate-version-info.js + node scripts/build/generate-version-info.js # Build demo PR_NUMBER=${{ github.event.pull_request.number }} diff --git a/.github/workflows/deploy-demo-preview.yml b/.github/workflows/deploy-demo-preview.yml index 0a12ffc..8500f31 100644 --- a/.github/workflows/deploy-demo-preview.yml +++ b/.github/workflows/deploy-demo-preview.yml @@ -47,7 +47,7 @@ jobs: DEPLOY_PATH: ag-grid-react-components-pr-${{ github.event.pull_request.number }} run: | # Generate version info - node scripts/generate-version-info.js + node scripts/build/generate-version-info.js # Build demo PR_NUMBER=${{ github.event.pull_request.number }} diff --git a/.github/workflows/deploy-demo.yml b/.github/workflows/deploy-demo.yml index e07cf7f..fe8beee 100644 --- a/.github/workflows/deploy-demo.yml +++ b/.github/workflows/deploy-demo.yml @@ -38,7 +38,7 @@ jobs: VITE_BASE_PATH: /ag-grid-react-components/ run: | # Generate version info - node scripts/generate-version-info.js + node scripts/build/generate-version-info.js # Build demo npm run build:demo diff --git a/.github/workflows/update-stackblitz-examples.yml b/.github/workflows/update-stackblitz-examples.yml index 2df2e8a..cf1366f 100644 --- a/.github/workflows/update-stackblitz-examples.yml +++ b/.github/workflows/update-stackblitz-examples.yml @@ -27,7 +27,7 @@ jobs: run: npm ci - name: Generate examples - run: node scripts/create-stackblitz-examples.js + run: node scripts/build/create-stackblitz-examples.js - name: Commit and push if changed run: | diff --git a/.gitignore b/.gitignore index e7bbdff..4327423 100644 --- a/.gitignore +++ b/.gitignore @@ -147,3 +147,6 @@ COALESCE_LABS_*.md CLEANUP_*.md R2_CLEANUP_*.md test-framework-*.mdEXPOSED-CREDENTIALS-PRIVATE.txt + +# Scripts archive (preserved but not in version control) +scripts-archive/ diff --git a/package.json b/package.json index 79b5a22..7ecd90b 100644 --- a/package.json +++ b/package.json @@ -13,11 +13,11 @@ "scripts": { "// Development": "Development commands", "dev": "concurrently \"npm run frontend\" \"npm run api\"", - "frontend": "node scripts/generate-version-info.js && vite", + "frontend": "node scripts/build/generate-version-info.js && vite", "api": "wrangler dev api/index.js --local --port 8787", - "demo": "node scripts/generate-version-info.js && vite", + "demo": "node scripts/build/generate-version-info.js && vite", "build": "tsc -p tsconfig.build.json && vite build", - "build:demo": "node scripts/generate-version-info.js && vite build --config vite.config.demo.ts", + "build:demo": "node scripts/build/generate-version-info.js && vite build --config vite.config.demo.ts", "preview": "vite preview", "preview:demo": "vite preview --config vite.config.demo.ts", "dev:safe": "npm run quality && npm run dev", @@ -30,9 +30,9 @@ "test:e2e:ui": "playwright test --ui", "test:e2e:debug": "playwright test --debug", "test:e2e:headed": "playwright test --headed", - "test:browser": "node ./scripts/validate-demo.js", - "test:thorough": "node ./scripts/thorough-demo-check.js", - "test:filter-click": "node ./scripts/test-filter-click.js", + "test:browser": "node ./scripts/dev/validate-demo.js", + "test:thorough": "node ./scripts/dev/thorough-demo-check.js", + "test:filter-click": "node ./scripts/dev/test-filter-click.js", "test:file": "vitest run", "coverage:report": "npm run test:coverage && open coverage/index.html", "// Code Quality": "Linting and formatting via Trunk", @@ -46,31 +46,21 @@ "check": "trunk check --no-fix && npm run typecheck", "quality": "trunk check --no-fix && npm run check:whitespace", "pre-commit": "trunk fmt && npm run fix:whitespace && trunk check --fix && npm run typecheck && npm run check:codeql", - "pre-push": "./scripts/pre-push.sh", - "pre-push:quick": "./scripts/pre-push-quick.sh", - "check:codeql": "node scripts/check-codeql.js", - "check:whitespace": "./scripts/check-whitespace.sh", - "fix:whitespace": "./scripts/fix-whitespace.sh", + "pre-push": "./scripts/dev/pre-push.sh", + "pre-push:quick": "./scripts/dev/pre-push-quick.sh", + "check:codeql": "node scripts/quality/check-codeql.js", + "check:whitespace": "./scripts/quality/check-whitespace.sh", + "fix:whitespace": "./scripts/quality/fix-whitespace.sh", "// Legacy commands": "Direct tool commands (prefer Trunk)", "lint:direct": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", "format:direct": "prettier --write .", "// Utilities": "Helper commands", "clean": "rm -rf dist coverage .parcel-cache playwright-report test-results", "fresh": "npm run clean && npm install && npm run build", - "run-tsx": "node ./scripts/run-tsx.js", + "run-tsx": "node ./scripts/utils/run-tsx.js", "bundle-size": "npm run build && echo '๐Ÿ“ฆ Bundle Size:' && du -sh dist/* | sort -h", - "cleanup:pr-deployments": "node scripts/cleanup-merged-pr-deployments.js", - "// Project Management": "GitHub issues and project sync", - "bootstrap:project": "./scripts/bootstrap-all.sh", - "sync:labels": "node scripts/add-missing-labels.js", - "sync:project": "node scripts/bootstrap-project-sync.js", - "sync:project:all": "node scripts/bootstrap-project-sync-all.js", - "sync:pr-labels": "node scripts/sync-pr-labels-from-issues.js", - "sync:issue-status": "node scripts/sync-issue-status.js", + "// Project Management (archived)": "Scripts moved to scripts-archive/bot-automation/", "// Milestone Management": "Release planning and tracking", - "milestone:create": "node scripts/create-milestone.js", - "milestone:assign": "node scripts/assign-to-milestone.js", - "milestone:overview": "node scripts/milestone-overview.js", "milestone:list": "gh milestone list", "// Git & Release": "Version control and publishing", "commit": "cz", diff --git a/scripts/README.md b/scripts/README.md index eb74293..d0ecdcf 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -1,105 +1,103 @@ -# Project Management Scripts +# Scripts Directory -This directory contains scripts for managing GitHub issues and project synchronization. +This directory contains scripts essential for the development and maintenance of ag-grid-react-components. -## ๐Ÿš€ Quick Start +## Directory Structure -To bootstrap everything at once: +``` +scripts/ +โ”œโ”€โ”€ dev/ # Development utilities +โ”œโ”€โ”€ quality/ # Code quality tools +โ”œโ”€โ”€ build/ # Build and release utilities +โ”œโ”€โ”€ release/ # Release management +โ””โ”€โ”€ utils/ # Shared utilities +``` -```bash -npm run bootstrap:project -```text +## Scripts by Category -Or run the shell script directly: +### Development Scripts (`/dev`) -```bash -./scripts/bootstrap-all.sh -```text +| Script | Purpose | Usage | +|--------|---------|-------| +| `pre-push.sh` | Comprehensive pre-push validation | Automatically run by git pre-push hook | +| `pre-push-quick.sh` | Quick pre-push validation (skips tests) | `npm run pre-push:quick` | +| `validate-demo.js` | Validates demo functionality | `npm run test:browser` | +| `thorough-demo-check.js` | Comprehensive demo validation | `npm run test:thorough` | +| `test-filter-click.js` | Tests filter click functionality | `npm run test:filter-click` | -## ๐Ÿ“œ Available Scripts +### Code Quality Scripts (`/quality`) -### bootstrap-all.sh -Complete bootstrap that runs all scripts in the correct order: -1. Adds missing required labels to issues -2. Syncs project fields to issue labels -3. Triggers GitHub Actions for final sync +| Script | Purpose | Usage | +|--------|---------|-------| +| `check-whitespace.sh` | Checks for whitespace issues | `npm run check:whitespace` | +| `fix-whitespace.sh` | Fixes whitespace issues | `npm run fix:whitespace` | +| `check-codeql.js` | Validates CodeQL configuration | `npm run check:codeql` | +| `check-fonts.js` | Checks font usage in code blocks | Direct execution | +| `test-code-block-fonts.js` | Tests font rendering in code blocks | Direct execution | -### add-missing-labels.js -Ensures all issues have required labels: -- Adds default type label if missing (enhancement) -- Adds default priority label if missing (medium) -- Adds default area label if missing (components) -- Adds default status label if missing (needs-triage) +### Build Scripts (`/build`) -```bash -node scripts/add-missing-labels.js -```text +| Script | Purpose | Usage | +|--------|---------|-------| +| `generate-version-info.js` | Generates version info for demos | Automatically run during build | +| `generate-og-image.js` | Generates Open Graph images | Direct execution | +| `publish-local.sh` | Publishes package locally for testing | Direct execution | -### bootstrap-project-sync.js -Syncs all project field values to issue labels: -- Reads current project field values -- Updates issue labels to match -- Removes conflicting labels -- Handles all field types (Priority, Area, Type, Component, Status) +### Release Scripts (`/release`) -```bash -node scripts/bootstrap-project-sync.js -```text +| Script | Purpose | Usage | +|--------|---------|-------| +| `bump-version.js` | Bumps package version | Part of release workflow | +| `generate-changelog.js` | Generates changelog entries | Part of release workflow | +| `prepare-release.js` | Prepares release artifacts | Part of release workflow | -## ๐Ÿ”„ How Sync Works +### Utility Scripts (`/utils`) -1. **Project โ†’ Labels**: When you change a field in the project, labels update automatically -2. **Labels โ†’ Project**: When you change labels on an issue, project fields update automatically -3. **Bidirectional**: Changes in either place stay synchronized +| Script | Purpose | Usage | +|--------|---------|-------| +| `run-tsx.js` | Runs TypeScript files directly | `npm run run-tsx ` | +| `loader.js` | TypeScript loader for Node.js | Used by run-tsx.js | +| `ensure-project-root.mjs` | Ensures scripts run from project root | Imported by other scripts | -## ๐Ÿ“‹ Label Categories +## Script Requirements -### Required Labels (one from each) -- **Type**: bug, enhancement, documentation, question -- **Priority**: priority: critical/high/medium/low -- **Area**: area: components/demo/build/ci-cd/testing/docs +### Environment Variables -### Optional Labels -- **Status**: status: needs-triage/triaging/backlog/in-progress/in-review/done -- **Component**: component: date-filter/quick-filter-dropdown/active-filters/etc +Most scripts don't require environment variables, but some GitHub-related scripts (now archived) required: +- `GITHUB_TOKEN`: For GitHub API access +- `NODE_ENV`: Development/production environment -## ๐Ÿ› ๏ธ Troubleshooting +### Dependencies -### "Command not found" error -Make sure you have Node.js installed and the GitHub CLI: -```bash -# Install GitHub CLI -brew install gh +Scripts assume the following tools are installed: +- Node.js (v18+) +- npm (v10+) +- Git +- GitHub CLI (`gh`) for release scripts -# Authenticate -gh auth login -```text +## Best Practices -### "GraphQL request failed" error -Your GitHub token might not have the right permissions: -```bash -# Check current auth status -gh auth status +1. **Always run scripts from project root**: Most scripts use `ensure-project-root.mjs` to enforce this +2. **Use npm scripts when available**: Prefer `npm run - -`; - - const dashboardPath = join(rootDir, '.bot/dashboard.html'); - fs.writeFileSync(dashboardPath, dashboardHtml); - console.log(`\n๐Ÿ“Š Dashboard created: ${dashboardPath}`); - console.log(' Open in browser: open ' + dashboardPath); -} - -// Run orchestrator -orchestrate().catch(console.error); \ No newline at end of file diff --git a/scripts/bot-workflow/coordinator/launch-bot-army.js b/scripts/bot-workflow/coordinator/launch-bot-army.js deleted file mode 100755 index 14e3194..0000000 --- a/scripts/bot-workflow/coordinator/launch-bot-army.js +++ /dev/null @@ -1,151 +0,0 @@ -#!/usr/bin/env node - -import { execSync, spawn } from 'child_process'; -import { fileURLToPath } from 'url'; -import { dirname, join } from 'path'; -import fs from 'fs'; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); -const rootDir = join(__dirname, '../../..'); - -console.log('๐Ÿค– Bot Army Launcher'); -console.log('====================\n'); - -// Parse command line arguments -const args = process.argv.slice(2); -const issueNumbers = args.map(arg => parseInt(arg)).filter(n => !isNaN(n)); - -if (issueNumbers.length === 0) { - console.log('Usage: node launch-bot-army.js ...'); - console.log('Example: node launch-bot-army.js 47 48 49 50 51 52'); - process.exit(1); -} - -// Check if tmux is installed -try { - execSync('which tmux', { stdio: 'ignore' }); -} catch (error) { - console.error('โŒ tmux is not installed. Please install it first:'); - console.error(' brew install tmux'); - process.exit(1); -} - -// Kill any existing bot-army session -try { - execSync('tmux kill-session -t bot-army 2>/dev/null'); -} catch (error) { - // Session doesn't exist, that's fine -} - -console.log(`๐Ÿš€ Launching bot army for issues: ${issueNumbers.join(', ')}\n`); - -// Create tmux session with dashboard -console.log('๐Ÿ“Š Creating tmux session with dashboard...'); -execSync('tmux new-session -d -s bot-army -n dashboard'); - -// Set up the dashboard pane -const dashboardCmd = `cd ${rootDir} && watch -n 5 'echo "BOT ARMY DASHBOARD - $(date)" && echo "===================" && node scripts/bot-workflow/core/bot-status-all.js'`; -execSync(`tmux send-keys -t bot-army:dashboard '${dashboardCmd}' Enter`); - -// Create a pane for each bot -issueNumbers.forEach((issueNumber, index) => { - console.log(`๐Ÿค– Setting up bot for issue #${issueNumber}...`); - - // Create new window for this bot - const windowName = `bot-${issueNumber}`; - execSync(`tmux new-window -t bot-army -n ${windowName}`); - - // Create the bot instruction file - const botInstructions = `# Bot Instructions for Issue #${issueNumber} - -## Status -โœ… Issue #${issueNumber} has been claimed -โœ… Worktree created at: ~/ag-grid-worktrees/feature/${issueNumber}-* - -## HUMAN: Enter these commands: - -\`\`\`bash -cd ~/ag-grid-worktrees/feature/${issueNumber}-* -claude -\`\`\` - -Note: If you get permission errors, use: -\`\`\`bash -claude --dangerously-skip-permissions -\`\`\` - -## HUMAN: Once Claude starts, paste this entire message: - ---- - -You are working on issue #${issueNumber}. Follow these steps: - -1. Read the issue details and understand the requirements: - \`\`\`bash - gh issue view ${issueNumber} - \`\`\` - -2. Implement the feature according to the specifications, following TDD practices: - - Write tests first - - Implement the functionality - - Ensure all tests pass - - Follow the patterns in CLAUDE.md - -3. Save checkpoints as you work: - \`\`\`bash - node ~/code-repos/github/ryanrozich/ag-grid-react-components/scripts/bot-workflow/core/bot-checkpoint.js "Progress description" - \`\`\` - -4. When complete, create a PR: - \`\`\`bash - git add -A - git commit -m "feat: implement issue #${issueNumber}" - node ~/code-repos/github/ryanrozich/ag-grid-react-components/scripts/bot-workflow/core/bot-create-pr.js - \`\`\` - -Start by reading the issue details and creating the necessary file structure. - ---- - -## Troubleshooting - -- **Can't find worktree**: Check exact name with \`ls ~/ag-grid-worktrees/feature/\` -- **Claude won't start**: Make sure you have Claude CLI installed -- **Permission issues**: Use \`claude --dangerously-skip-permissions\` -`; - - const instructionFile = join(rootDir, `.bot/instructions-${issueNumber}.md`); - fs.writeFileSync(instructionFile, botInstructions); - - // Send commands to claim the issue - const claimCmd = `cd ${rootDir} && node scripts/bot-workflow/core/bot-claim-issue.js ${issueNumber}`; - execSync(`tmux send-keys -t bot-army:${windowName} '${claimCmd}' Enter`); - - // Wait a moment for the claim to process - execSync('sleep 2'); - - // Add instruction to open the instructions file - const openInstructionsCmd = `echo "\\n๐Ÿ“‹ Bot instructions saved to: ${instructionFile}\\n" && cat ${instructionFile}`; - execSync(`tmux send-keys -t bot-army:${windowName} '${openInstructionsCmd}' Enter`); -}); - -// Create a final window for monitoring -execSync('tmux new-window -t bot-army -n monitor'); -const monitorCmd = `cd ${rootDir} && echo "๐Ÿ“Š Use this window to run monitoring commands:" && echo " - gh issue list --milestone \\"Filter Presets v1\\"" && echo " - gh pr list" && echo " - node scripts/bot-workflow/core/bot-status-all.js"`; -execSync(`tmux send-keys -t bot-army:monitor '${monitorCmd}' Enter`); - -// Switch back to dashboard -execSync('tmux select-window -t bot-army:dashboard'); - -console.log('\nโœ… Bot army launched successfully!\n'); -console.log('๐Ÿ“บ To view the tmux session:'); -console.log(' tmux attach -t bot-army\n'); -console.log('๐ŸŽฎ Tmux controls:'); -console.log(' - Switch windows: Ctrl+B, then window number (0-9)'); -console.log(' - Next window: Ctrl+B, n'); -console.log(' - Previous window: Ctrl+B, p'); -console.log(' - List windows: Ctrl+B, w'); -console.log(' - Detach: Ctrl+B, d'); -console.log(' - Kill session: tmux kill-session -t bot-army\n'); -console.log('๐Ÿค– Each bot has instructions in their window!'); \ No newline at end of file diff --git a/scripts/bot-workflow/coordinator/monitor-progress.js b/scripts/bot-workflow/coordinator/monitor-progress.js deleted file mode 100755 index 0b34e93..0000000 --- a/scripts/bot-workflow/coordinator/monitor-progress.js +++ /dev/null @@ -1,357 +0,0 @@ -#!/usr/bin/env node - -/** - * Monitor progress of bot-assigned tasks - * Usage: node monitor-progress.js [tracking-issue] - */ - -import { execSync } from 'child_process'; -import fs from 'fs'; -import path from 'path'; -import { ensureProjectRoot } from '../../utils/ensure-project-root.mjs'; -// Ensure we're in the project root -ensureProjectRoot('monitor-progress.js'); - - -// Parse arguments -const [trackingIssue] = process.argv.slice(2); - -console.log(`๐Ÿ“Š Bot Task Progress Monitor`); -console.log(`${'โ•'.repeat(50)}\n`); - -/** - * Get all bot-assigned tasks - */ -async function getBotTasks(tracking) { - const tasks = []; - - try { - if (tracking) { - // Get tasks linked to tracking issue - console.log(`๐Ÿ” Checking tracking issue #${tracking}...`); - const issueBody = JSON.parse( - execSync(`gh issue view ${tracking} --json body`, { encoding: 'utf8' }) - ).body; - - // Extract linked issue numbers - const issueMatches = issueBody.matchAll(/#(\d+)/g); - for (const match of issueMatches) { - const issueNum = parseInt(match[1]); - if (issueNum !== parseInt(tracking)) { - tasks.push(issueNum); - } - } - } else { - // Get all issues with agent labels - console.log(`๐Ÿ” Finding all bot-assigned tasks...`); - const issues = JSON.parse( - execSync(`gh issue list --json number,title,labels,assignees,createdAt,updatedAt --limit 100`, { encoding: 'utf8' }) - ); - - const agentIssues = issues.filter(issue => - issue.labels.some(l => l.name.startsWith('agent:')) - ); - - tasks.push(...agentIssues.map(i => i.number)); - } - } catch (error) { - console.error(`Error fetching tasks:`, error.message); - } - - return tasks; -} - -/** - * Get detailed task status - */ -async function getTaskStatus(issueNumber) { - try { - const issue = JSON.parse( - execSync(`gh issue view ${issueNumber} --json number,title,labels,state,createdAt,updatedAt,comments`, { encoding: 'utf8' }) - ); - - // Determine status from labels - const labels = issue.labels.map(l => l.name); - let status = 'unknown'; - let progress = 0; - - if (labels.includes('agent:done')) { - status = 'completed'; - progress = 100; - } else if (labels.includes('agent:needs-review')) { - status = 'review'; - progress = 90; - } else if (labels.includes('agent:wip')) { - status = 'in-progress'; - progress = 50; - } else if (labels.includes('agent:todo')) { - status = 'pending'; - progress = 0; - } else if (labels.includes('agent:failed')) { - status = 'failed'; - progress = -1; - } - - // Check for PR - let pr = null; - const prComments = issue.comments.filter(c => - c.body.includes('pull request') || c.body.includes('/pull/') - ); - - if (prComments.length > 0) { - const prMatch = prComments[0].body.match(/\/pull\/(\d+)/); - if (prMatch) { - pr = parseInt(prMatch[1]); - } - } - - // Look for checkpoints in comments - const checkpoints = issue.comments.filter(c => - c.body.includes('Checkpoint') || c.body.includes('checkpoint') - ).length; - - // Calculate time metrics - const created = new Date(issue.createdAt); - const updated = new Date(issue.updatedAt); - const now = new Date(); - const ageHours = (now - created) / (1000 * 60 * 60); - const idleHours = (now - updated) / (1000 * 60 * 60); - - return { - number: issue.number, - title: issue.title, - status: status, - progress: progress, - labels: labels, - pr: pr, - checkpoints: checkpoints, - ageHours: Math.round(ageHours), - idleHours: Math.round(idleHours), - state: issue.state - }; - } catch (error) { - console.error(`Error getting status for #${issueNumber}:`, error.message); - return null; - } -} - -/** - * Display progress visualization - */ -function displayProgress(statuses) { - const grouped = { - pending: [], - 'in-progress': [], - review: [], - completed: [], - failed: [] - }; - - // Group by status - statuses.forEach(s => { - if (s && grouped[s.status]) { - grouped[s.status].push(s); - } - }); - - // Display each group - console.log(`๐Ÿ“‹ Task Status Overview\n`); - - // Pending - if (grouped.pending.length > 0) { - console.log(`โณ Pending (${grouped.pending.length})`); - grouped.pending.forEach(task => { - console.log(` #${task.number}: ${task.title}`); - console.log(` Age: ${task.ageHours}h`); - }); - console.log(''); - } - - // In Progress - if (grouped['in-progress'].length > 0) { - console.log(`๐Ÿ”„ In Progress (${grouped['in-progress'].length})`); - grouped['in-progress'].forEach(task => { - console.log(` #${task.number}: ${task.title}`); - console.log(` Checkpoints: ${task.checkpoints} | Idle: ${task.idleHours}h`); - if (task.idleHours > 24) { - console.log(` โš ๏ธ No activity for ${task.idleHours}h`); - } - }); - console.log(''); - } - - // In Review - if (grouped.review.length > 0) { - console.log(`๐Ÿ‘€ In Review (${grouped.review.length})`); - grouped.review.forEach(task => { - console.log(` #${task.number}: ${task.title}`); - if (task.pr) { - console.log(` PR: #${task.pr}`); - } - }); - console.log(''); - } - - // Completed - if (grouped.completed.length > 0) { - console.log(`โœ… Completed (${grouped.completed.length})`); - grouped.completed.forEach(task => { - console.log(` #${task.number}: ${task.title}`); - }); - console.log(''); - } - - // Failed - if (grouped.failed.length > 0) { - console.log(`โŒ Failed (${grouped.failed.length})`); - grouped.failed.forEach(task => { - console.log(` #${task.number}: ${task.title}`); - console.log(` โš ๏ธ Requires human intervention`); - }); - console.log(''); - } - - // Summary metrics - const total = statuses.length; - const completed = grouped.completed.length; - const inProgress = grouped['in-progress'].length + grouped.review.length; - const pending = grouped.pending.length; - const failed = grouped.failed.length; - - console.log(`${'โ”€'.repeat(50)}`); - console.log(`๐Ÿ“ˆ Summary`); - console.log(` Total Tasks: ${total}`); - console.log(` Completed: ${completed} (${Math.round(completed/total*100)}%)`); - console.log(` In Progress: ${inProgress}`); - console.log(` Pending: ${pending}`); - if (failed > 0) { - console.log(` Failed: ${failed} โš ๏ธ`); - } - - // Progress bar - const progressBar = 'โ–ˆ'.repeat(Math.round(completed/total*20)) + 'โ–‘'.repeat(20 - Math.round(completed/total*20)); - console.log(`\n Progress: [${progressBar}] ${Math.round(completed/total*100)}%`); - - // Warnings - const stale = statuses.filter(s => s && s.status === 'in-progress' && s.idleHours > 24); - if (stale.length > 0) { - console.log(`\nโš ๏ธ Warnings:`); - console.log(` ${stale.length} tasks have been idle for >24h`); - } -} - -/** - * Generate actionable insights - */ -function generateInsights(statuses) { - console.log(`\n๐Ÿ’ก Insights & Actions\n`); - - // Stale tasks - const stale = statuses.filter(s => - s && s.status === 'in-progress' && s.idleHours > 24 - ); - - if (stale.length > 0) { - console.log(`๐Ÿ”ธ Stale Tasks (need attention):`); - stale.forEach(task => { - console.log(` - #${task.number}: Idle for ${task.idleHours}h`); - console.log(` Action: node scripts/bot-workflow/core/bot-resume-work.js ${task.number}`); - }); - console.log(''); - } - - // Failed tasks - const failed = statuses.filter(s => s && s.status === 'failed'); - if (failed.length > 0) { - console.log(`๐Ÿ”ธ Failed Tasks (need human intervention):`); - failed.forEach(task => { - console.log(` - #${task.number}: ${task.title}`); - console.log(` Action: gh issue view ${task.number}`); - }); - console.log(''); - } - - // Ready to assign - const ready = statuses.filter(s => s && s.status === 'pending'); - if (ready.length > 0) { - console.log(`๐Ÿ”ธ Ready for Bot Assignment:`); - ready.slice(0, 3).forEach(task => { - console.log(` - #${task.number}: ${task.title}`); - console.log(` Action: node scripts/bot-workflow/coordinator/assign-bot.js ${task.number}`); - }); - console.log(''); - } - - // Performance metrics - const completed = statuses.filter(s => s && s.status === 'completed'); - if (completed.length > 0) { - const avgTime = completed.reduce((sum, t) => sum + t.ageHours, 0) / completed.length; - console.log(`๐Ÿ“Š Performance Metrics:`); - console.log(` - Average completion time: ${Math.round(avgTime)}h`); - console.log(` - Tasks completed: ${completed.length}`); - } -} - -/** - * Main monitoring function - */ -async function monitorProgress() { - try { - // Get tasks to monitor - const tasks = await getBotTasks(trackingIssue); - - if (tasks.length === 0) { - console.log('No bot tasks found to monitor.'); - return; - } - - console.log(`Found ${tasks.length} tasks to monitor\n`); - - // Get detailed status for each task - const statuses = []; - for (const task of tasks) { - const status = await getTaskStatus(task); - if (status) { - statuses.push(status); - } - } - - // Display progress - displayProgress(statuses); - - // Generate insights - generateInsights(statuses); - - // Output for automation - const result = { - timestamp: new Date().toISOString(), - trackingIssue: trackingIssue ? parseInt(trackingIssue) : null, - totalTasks: statuses.length, - summary: { - completed: statuses.filter(s => s.status === 'completed').length, - inProgress: statuses.filter(s => s.status === 'in-progress').length, - review: statuses.filter(s => s.status === 'review').length, - pending: statuses.filter(s => s.status === 'pending').length, - failed: statuses.filter(s => s.status === 'failed').length - }, - tasks: statuses.map(s => ({ - number: s.number, - status: s.status, - idleHours: s.idleHours - })) - }; - - console.log(`\n๐Ÿ”ง Automation output:`); - console.log(JSON.stringify(result, null, 2)); - - } catch (error) { - console.error(`\nโŒ Error monitoring progress:`, error.message); - process.exit(1); - } -} - -// Run the monitor -monitorProgress().catch(error => { - console.error('Fatal error:', error); - process.exit(1); -}); \ No newline at end of file diff --git a/scripts/bot-workflow/coordinator/plan-feature.js b/scripts/bot-workflow/coordinator/plan-feature.js deleted file mode 100755 index c838de3..0000000 --- a/scripts/bot-workflow/coordinator/plan-feature.js +++ /dev/null @@ -1,295 +0,0 @@ -#!/usr/bin/env node - -/** - * Coordinator agent plans a feature and creates issues for worker bots - * Usage: node plan-feature.js "feature description" - */ - -import { execSync } from 'child_process'; -import fs from 'fs'; -import path from 'path'; -import { ensureProjectRoot } from '../../utils/ensure-project-root.mjs'; -// Ensure we're in the project root -ensureProjectRoot('plan-feature.js'); - - -// Get feature description -const featureDescription = process.argv.slice(2).join(' '); - -if (!featureDescription) { - console.error('โŒ Usage: node plan-feature.js "feature description"'); - console.error(' Example: node plan-feature.js "Add timezone support to DateFilter component"'); - process.exit(1); -} - -console.log(`๐ŸŽฏ Planning feature: ${featureDescription}`); -console.log(`${'โ•'.repeat(50)}\n`); - -/** - * Break down a feature into discrete tasks - * In a real implementation, this could use AI to analyze the feature - */ -function analyzeFeature(description) { - // This is a simple heuristic-based breakdown - // In production, this would use AI or more sophisticated analysis - - const tasks = []; - const lowerDesc = description.toLowerCase(); - - // Common patterns for feature breakdown - if (lowerDesc.includes('component') || lowerDesc.includes('ui')) { - tasks.push({ - type: 'component', - title: `Create UI component for ${description}`, - priority: 'high', - area: 'components', - description: `Implement the user interface component with proper React patterns, TypeScript types, and accessibility.` - }); - - tasks.push({ - type: 'tests', - title: `Add tests for ${description}`, - priority: 'high', - area: 'testing', - description: `Write comprehensive unit tests and integration tests for the new functionality.` - }); - } - - if (lowerDesc.includes('api') || lowerDesc.includes('logic') || lowerDesc.includes('support')) { - tasks.push({ - type: 'logic', - title: `Implement business logic for ${description}`, - priority: 'high', - area: 'components', - description: `Implement the core business logic, data handling, and state management.` - }); - } - - if (lowerDesc.includes('filter') || lowerDesc.includes('ag-grid')) { - tasks.push({ - type: 'integration', - title: `Integrate with AG Grid for ${description}`, - priority: 'medium', - area: 'components', - description: `Ensure proper integration with AG Grid's filter API and handle edge cases.` - }); - } - - // Always add documentation and demo - tasks.push({ - type: 'docs', - title: `Document ${description}`, - priority: 'medium', - area: 'docs', - description: `Update README, API documentation, and inline code comments.` - }); - - tasks.push({ - type: 'demo', - title: `Add demo example for ${description}`, - priority: 'low', - area: 'demo', - description: `Create a demo example showcasing the new functionality in the demo application.` - }); - - return tasks; -} - -/** - * Create tracking issue for the feature - */ -async function createTrackingIssue(feature, tasks) { - console.log(`๐Ÿ“‹ Creating tracking issue...`); - - const trackingBody = `# ๐ŸŽฏ Feature: ${feature} - -This is a tracking issue for implementing the feature. The work has been broken down into the following tasks: - -## ๐Ÿ“ Tasks - -${tasks.map((task, i) => `${i + 1}. [ ] #ISSUE-${i + 1} - ${task.title}`).join('\n')} - -## ๐Ÿค– Bot Coordination - -Each sub-task will be handled by a worker bot. This tracking issue will be updated as tasks are completed. - -## ๐Ÿ“Š Progress - -- Total tasks: ${tasks.length} -- Completed: 0 -- In Progress: 0 -- Remaining: ${tasks.length} - ---- -*This issue was created by the Coordinator Agent*`; - - try { - const result = execSync( - `gh issue create --title "Feature: ${feature}" --body "${trackingBody.replace(/"/g, '\\"')}" --label "enhancement" --label "priority: high" --label "area: components"`, - { encoding: 'utf8' } - ); - - const match = result.match(/\/issues\/(\d+)/); - return match ? parseInt(match[1]) : null; - } catch (error) { - console.error('Failed to create tracking issue:', error.message); - return null; - } -} - -/** - * Create individual task issues - */ -async function createTaskIssues(tasks, trackingIssue) { - const createdIssues = []; - - for (const [index, task] of tasks.entries()) { - console.log(`\n๐Ÿ“ Creating issue ${index + 1}/${tasks.length}: ${task.title}`); - - const issueBody = `## ๐Ÿ“‹ Task Description - -${task.description} - -## ๐Ÿ”— Context - -This task is part of the feature tracked in #${trackingIssue}. - -## โœ… Acceptance Criteria - -- [ ] Implementation follows project conventions -- [ ] All tests pass -- [ ] Code is properly typed (no \`any\`) -- [ ] Documentation is updated -- [ ] Follows TDD approach - -## ๐Ÿค– Bot Instructions - -1. Claim this issue using \`/bot claim\` -2. Follow TDD - write tests first -3. Checkpoint progress regularly -4. Create PR when implementation is complete -5. Request human review if blocked - ---- -*This issue was created by the Coordinator Agent for bot processing*`; - - try { - const labels = [ - task.priority === 'high' ? 'priority: high' : 'priority: medium', - `area: ${task.area}`, - 'agent:todo', - 'enhancement' - ]; - - const result = execSync( - `gh issue create --title "${task.title}" --body "${issueBody.replace(/"/g, '\\"')}" ${labels.map(l => `--label "${l}"`).join(' ')}`, - { encoding: 'utf8' } - ); - - const match = result.match(/\/issues\/(\d+)/); - if (match) { - const issueNumber = parseInt(match[1]); - createdIssues.push({ - number: issueNumber, - task: task - }); - console.log(` โœ… Created issue #${issueNumber}`); - } - } catch (error) { - console.error(` โŒ Failed to create issue: ${error.message}`); - } - } - - return createdIssues; -} - -/** - * Update tracking issue with created task links - */ -async function updateTrackingIssue(trackingIssue, createdIssues) { - console.log(`\n๐Ÿ”„ Updating tracking issue #${trackingIssue}...`); - - const updateBody = `## ๐Ÿ“Š Task Issues Created - -${createdIssues.map((issue, i) => `${i + 1}. [ ] #${issue.number} - ${issue.task.title}`).join('\n')} - -## ๐Ÿš€ Next Steps - -The worker bots can now claim these issues and begin implementation. Monitor progress through this tracking issue. - ---- -*Updated by Coordinator Agent at ${new Date().toISOString()}*`; - - try { - execSync( - `gh issue comment ${trackingIssue} --body "${updateBody.replace(/"/g, '\\"')}"`, - { stdio: 'inherit' } - ); - console.log(` โœ… Updated tracking issue`); - } catch (error) { - console.error(` โŒ Failed to update tracking issue: ${error.message}`); - } -} - -/** - * Main coordination function - */ -async function coordinateFeature() { - try { - // Step 1: Analyze and break down the feature - console.log(`๐Ÿง  Analyzing feature requirements...`); - const tasks = analyzeFeature(featureDescription); - - console.log(`\n๐Ÿ“Š Identified ${tasks.length} tasks:`); - tasks.forEach((task, i) => { - console.log(` ${i + 1}. ${task.title} (${task.priority})`); - }); - - // Step 2: Create tracking issue - const trackingIssue = await createTrackingIssue(featureDescription, tasks); - if (!trackingIssue) { - throw new Error('Failed to create tracking issue'); - } - console.log(`\nโœ… Created tracking issue #${trackingIssue}`); - - // Step 3: Create individual task issues - const createdIssues = await createTaskIssues(tasks, trackingIssue); - - // Step 4: Update tracking issue with links - await updateTrackingIssue(trackingIssue, createdIssues); - - // Summary - console.log(`\n${'โ•'.repeat(50)}`); - console.log(`โœ… Feature planning complete!\n`); - console.log(`๐Ÿ“‹ Tracking Issue: #${trackingIssue}`); - console.log(`๐Ÿ“ Task Issues: ${createdIssues.map(i => `#${i.number}`).join(', ')}`); - console.log(`\n๐Ÿค– Bot Instructions:`); - console.log(` Bots can now claim issues with: node scripts/bot-workflow/core/bot-claim-issue.js `); - - // Output for automation - const result = { - success: true, - feature: featureDescription, - trackingIssue: trackingIssue, - tasks: createdIssues.map(i => ({ - number: i.number, - title: i.task.title, - type: i.task.type, - priority: i.task.priority - })) - }; - - console.log(`\n๐Ÿ”ง Automation output:`); - console.log(JSON.stringify(result, null, 2)); - - } catch (error) { - console.error(`\nโŒ Error coordinating feature:`, error.message); - process.exit(1); - } -} - -// Run the coordination -coordinateFeature().catch(error => { - console.error('Fatal error:', error); - process.exit(1); -}); \ No newline at end of file diff --git a/scripts/bot-workflow/core/bot-checkpoint.js b/scripts/bot-workflow/core/bot-checkpoint.js deleted file mode 100755 index 415bd6b..0000000 --- a/scripts/bot-workflow/core/bot-checkpoint.js +++ /dev/null @@ -1,139 +0,0 @@ -#!/usr/bin/env node - -/** - * Bot saves progress checkpoint - * Usage: node bot-checkpoint.js "checkpoint message" - * Must be run from within the worktree directory - */ - -import { execSync } from 'child_process'; -import fs from 'fs'; -import path from 'path'; -import { ensureProjectRoot } from '../../utils/ensure-project-root.mjs'; -// Ensure we're in the project root -ensureProjectRoot('bot-checkpoint.js'); - - -// Get checkpoint message -const message = process.argv.slice(2).join(' '); - -if (!message) { - console.error('โŒ Usage: node bot-checkpoint.js "checkpoint message"'); - process.exit(1); -} - -console.log(`๐Ÿ’พ Creating checkpoint: ${message}`); - -try { - // Check if we're in a worktree with bot state - const botStateDir = path.join(process.cwd(), '.bot'); - if (!fs.existsSync(botStateDir)) { - throw new Error('Not in a bot worktree directory (no .bot directory found)'); - } - - // Load context - const contextPath = path.join(botStateDir, 'context.json'); - const context = JSON.parse(fs.readFileSync(contextPath, 'utf8')); - - // Get current git status - const gitStatus = execSync('git status --porcelain', { encoding: 'utf8' }); - const gitDiff = execSync('git diff', { encoding: 'utf8' }); - const gitDiffStaged = execSync('git diff --staged', { encoding: 'utf8' }); - - // Update checkpoint info - const checkpointTime = new Date().toISOString(); - context.lastCheckpoint = checkpointTime; - context.checkpointMessage = message; - context.checkpoints = (context.checkpoints || 0) + 1; - - // Save current state - const statePath = path.join(botStateDir, `checkpoint-${Date.now()}.json`); - const checkpointData = { - timestamp: checkpointTime, - message: message, - gitStatus: gitStatus, - gitDiff: gitDiff, - gitDiffStaged: gitDiffStaged, - context: context - }; - - fs.writeFileSync(statePath, JSON.stringify(checkpointData, null, 2)); - console.log(`๐Ÿ“„ Saved state to ${path.basename(statePath)}`); - - // Update context - fs.writeFileSync(contextPath, JSON.stringify(context, null, 2)); - - // Update memory log - const memoryPath = path.join(botStateDir, 'memory.md'); - const memoryEntry = `\n## ${checkpointTime}\n- **Checkpoint**: ${message}\n`; - - // Add file changes summary if any - if (gitStatus) { - const changes = gitStatus.split('\n').filter(Boolean); - memoryEntry + `- Files changed: ${changes.length}\n`; - changes.forEach(change => { - memoryEntry + ` - ${change}\n`; - }); - } - - fs.appendFileSync(memoryPath, memoryEntry); - - // Stage all changes - if (gitStatus) { - console.log(`๐Ÿ“ฆ Staging changes...`); - execSync('git add -A', { stdio: 'inherit' }); - } - - // Commit with checkpoint message - const commitMessage = `checkpoint: ${message}\n\nBot checkpoint #${context.checkpoints} for issue #${context.issue}`; - console.log(`๐Ÿ’ฌ Committing changes...`); - - try { - execSync(`git commit -m "${commitMessage}"`, { stdio: 'inherit' }); - console.log(`โœ… Changes committed`); - } catch (e) { - console.log(`โ„น๏ธ No changes to commit`); - } - - // Update checklist if it exists - const checklistPath = path.join(botStateDir, 'checklist.md'); - if (fs.existsSync(checklistPath)) { - console.log(`๐Ÿ“‹ Checklist found - remember to update task status`); - } - - // If PR exists, update it - if (context.pr) { - console.log(`๐Ÿ”„ Updating PR #${context.pr}...`); - try { - // Push changes - execSync(`git push origin ${context.branch}`, { stdio: 'inherit' }); - - // Add checkpoint comment to PR - execSync(`gh pr comment ${context.pr} --body "๐Ÿ”„ **Checkpoint**: ${message}\n\n- Checkpoint #${context.checkpoints}\n- Time: ${checkpointTime}\n- Commits: $(git rev-list --count HEAD)"`, { stdio: 'inherit' }); - } catch (e) { - console.log(`โš ๏ธ Could not update PR: ${e.message}`); - } - } - - console.log(`\nโœ… Checkpoint saved successfully!`); - console.log(`๐Ÿ“Š Total checkpoints: ${context.checkpoints}`); - console.log(`๐Ÿ• Last checkpoint: ${checkpointTime}`); - - // Output for automation - const result = { - success: true, - checkpoint: context.checkpoints, - message: message, - timestamp: checkpointTime, - hasChanges: !!gitStatus, - issue: context.issue, - branch: context.branch - }; - - console.log(`\n๐Ÿ”ง Automation output:`); - console.log(JSON.stringify(result, null, 2)); - -} catch (error) { - console.error(`\nโŒ Error creating checkpoint:`, error.message); - process.exit(1); -} \ No newline at end of file diff --git a/scripts/bot-workflow/core/bot-claim-issue.js b/scripts/bot-workflow/core/bot-claim-issue.js deleted file mode 100755 index 273b38b..0000000 --- a/scripts/bot-workflow/core/bot-claim-issue.js +++ /dev/null @@ -1,207 +0,0 @@ -#!/usr/bin/env node - -/** - * Bot claims an issue and sets up development environment - * Usage: node bot-claim-issue.js - */ - -import { execSync } from 'child_process'; -import fs from 'fs'; -import path from 'path'; -import { ensureProjectRoot } from '../../utils/ensure-project-root.mjs'; -import { fileURLToPath } from 'url'; - -const __dirname = path.dirname(fileURLToPath(import.meta.url)); - -// Ensure we're in the project root -ensureProjectRoot('bot-claim-issue.js'); - -// Check for required environment variables -if (!process.env.GITHUB_TOKEN) { - console.error('โŒ GITHUB_TOKEN environment variable is required'); - process.exit(1); -} - -const issueNumber = process.argv[2]; - -if (!issueNumber) { - console.error('โŒ Usage: node bot-claim-issue.js '); - process.exit(1); -} - -console.log(`๐Ÿค– Bot claiming issue #${issueNumber}...`); - -async function claimIssue() { - try { - // Check if issue exists and is claimable - console.log(`๐Ÿ“‹ Checking issue #${issueNumber}...`); - const issueInfo = JSON.parse( - execSync(`gh issue view ${issueNumber} --json state,labels,assignees`, { encoding: 'utf8' }) - ); - - if (issueInfo.state !== 'OPEN') { - throw new Error(`Issue #${issueNumber} is not open (state: ${issueInfo.state})`); - } - - // Check if already claimed - const hasAgentWIP = issueInfo.labels.some(label => label.name === 'agent:wip'); - if (hasAgentWIP) { - throw new Error(`Issue #${issueNumber} is already claimed by another bot`); - } - - // Check if it's marked for bot work - const hasAgentTodo = issueInfo.labels.some(label => label.name === 'agent:todo'); - if (!hasAgentTodo) { - console.warn(`โš ๏ธ Issue #${issueNumber} is not labeled as 'agent:todo' - proceeding anyway`); - } - - // Add claim comment to prevent race conditions - console.log(`๐Ÿ’ฌ Adding claim comment...`); - const claimTime = new Date().toISOString(); - execSync(`gh issue comment ${issueNumber} --body "๐Ÿค– **Bot Claiming Issue**\n\nThis issue has been claimed by a bot at ${claimTime}.\n\n*Status: Setting up development environment...*"`, { stdio: 'inherit' }); - - // Update labels - console.log(`๐Ÿท๏ธ Updating labels...`); - // Remove agent:todo if present - if (hasAgentTodo) { - execSync(`gh issue edit ${issueNumber} --remove-label "agent:todo"`, { stdio: 'inherit' }); - } - // Add agent:wip and status:in-progress - execSync(`gh issue edit ${issueNumber} --add-label "agent:wip" --add-label "status: in-progress"`, { stdio: 'inherit' }); - - // Get issue details for branch name - const issueDetails = JSON.parse( - execSync(`gh issue view ${issueNumber} --json title`, { encoding: 'utf8' }) - ); - - // Create safe branch description from title - const description = issueDetails.title - .toLowerCase() - .replace(/[^a-z0-9]+/g, '-') - .replace(/^-+|-+$/g, '') - .substring(0, 50); - - // Set up worktree - console.log(`๐ŸŒณ Setting up worktree...`); - let worktreeInfo; - try { - const setupResult = execSync( - `node ${path.join(__dirname, '../worktree/setup-worktree.js')} ${issueNumber} "${description}"`, - { encoding: 'utf8', stdio: 'pipe' } - ); - - // Parse the last JSON output from setup-worktree - // Look for JSON that starts with { and ends with } on its own line - const lines = setupResult.split('\n'); - let jsonStr = ''; - let inJson = false; - - for (let i = lines.length - 1; i >= 0; i--) { - const line = lines[i].trim(); - if (line === '}' && !inJson) { - jsonStr = '}' + jsonStr; - inJson = true; - } else if (inJson) { - jsonStr = line + '\n' + jsonStr; - if (line.startsWith('{')) { - break; - } - } - } - - if (jsonStr) { - worktreeInfo = JSON.parse(jsonStr); - } - - // If we couldn't parse JSON but the worktree exists, consider it successful - if (!worktreeInfo) { - // Check if worktree was created by looking for the directory - const worktreePath = `${process.env.HOME}/ag-grid-worktrees/feature/${issueNumber}-${description}`; - if (fs.existsSync(worktreePath)) { - worktreeInfo = { - success: true, - worktree: worktreePath, - branch: `feature/${issueNumber}-${description}`, - botStateDir: path.join(worktreePath, '.bot') - }; - } - } - } catch (error) { - // Even if the command had warnings, check if worktree exists - const worktreePath = `${process.env.HOME}/ag-grid-worktrees/feature/${issueNumber}-${description}`; - if (fs.existsSync(worktreePath)) { - worktreeInfo = { - success: true, - worktree: worktreePath, - branch: `feature/${issueNumber}-${description}`, - botStateDir: path.join(worktreePath, '.bot') - }; - } else { - throw new Error(`Failed to set up worktree: ${error.message}`); - } - } - - if (!worktreeInfo || !worktreeInfo.success) { - throw new Error('Failed to set up worktree - no worktree information returned'); - } - - // Initialize bot state with issue context - const botStateDir = worktreeInfo.botStateDir; - const contextPath = path.join(botStateDir, 'context.json'); - const context = JSON.parse(fs.readFileSync(contextPath, 'utf8')); - - // Enhance context with issue information - context.issue = parseInt(issueNumber); - context.issueTitle = issueDetails.title; - context.claimedAt = claimTime; - context.status = 'claimed'; - - fs.writeFileSync(contextPath, JSON.stringify(context, null, 2)); - - // Update memory log - const memoryPath = path.join(botStateDir, 'memory.md'); - const memoryContent = fs.readFileSync(memoryPath, 'utf8'); - fs.writeFileSync( - memoryPath, - memoryContent + `\n## ${claimTime}\n- Claimed issue #${issueNumber}: ${issueDetails.title}\n- Updated labels: removed agent:todo, added agent:wip\n` - ); - - // Final status comment - console.log(`โœ… Updating status...`); - execSync(`gh issue comment ${issueNumber} --body "โœ… **Setup Complete**\n\n- Branch: \`${worktreeInfo.branch}\`\n- Worktree: \`${worktreeInfo.worktree}\`\n- Status: Ready to begin development\n\nThe bot will now start working on this issue."`, { stdio: 'inherit' }); - - console.log(`\nโœ… Successfully claimed issue #${issueNumber}!`); - console.log(`๐Ÿ“ Worktree: ${worktreeInfo.worktree}`); - console.log(`๐ŸŒฟ Branch: ${worktreeInfo.branch}`); - console.log(`\nBot can now begin development work.`); - - // Return success for automation - return { - success: true, - issue: parseInt(issueNumber), - branch: worktreeInfo.branch, - worktree: worktreeInfo.worktree - }; - - } catch (error) { - console.error(`\nโŒ Error claiming issue:`, error.message); - - // Try to update issue with error status - try { - execSync(`gh issue comment ${issueNumber} --body "โŒ **Failed to claim issue**\n\nError: ${error.message}\n\nThe bot was unable to claim this issue. Human intervention may be required."`); - } catch (e) { - // Ignore errors in error reporting - } - - process.exit(1); - } -} - -// Run the claim process -claimIssue().then(result => { - console.log(`\n๐Ÿ”ง Automation output:`); - console.log(JSON.stringify(result, null, 2)); -}).catch(error => { - console.error('Fatal error:', error); - process.exit(1); -}); \ No newline at end of file diff --git a/scripts/bot-workflow/core/bot-create-pr.js b/scripts/bot-workflow/core/bot-create-pr.js deleted file mode 100755 index 61266db..0000000 --- a/scripts/bot-workflow/core/bot-create-pr.js +++ /dev/null @@ -1,140 +0,0 @@ -#!/usr/bin/env node - -/** - * Bot creates a pull request and updates issue labels - * Usage: node bot-create-pr.js [options] - * Must be run from within the worktree directory - */ - -import { execSync } from 'child_process'; -import fs from 'fs'; -import path from 'path'; - -console.log(`๐Ÿš€ Creating pull request...`); - -try { - // Check if we're in a worktree with bot state - const botStateDir = path.join(process.cwd(), '.bot'); - if (!fs.existsSync(botStateDir)) { - throw new Error('Not in a bot worktree directory (no .bot directory found)'); - } - - // Load context - const contextPath = path.join(botStateDir, 'context.json'); - const context = JSON.parse(fs.readFileSync(contextPath, 'utf8')); - - if (!context.issue) { - throw new Error('No issue number found in bot context'); - } - - // Get the current branch - const currentBranch = execSync('git branch --show-current', { encoding: 'utf8' }).trim(); - - // Push to origin if not already pushed - console.log(`๐Ÿ“ค Pushing to origin...`); - try { - execSync(`git push -u origin ${currentBranch}`, { stdio: 'inherit' }); - } catch (e) { - console.log(`โ„น๏ธ Branch may already be pushed`); - } - - // Parse command line arguments - const args = process.argv.slice(2); - let title = ''; - let body = ''; - - // Simple argument parsing - for (let i = 0; i < args.length; i++) { - if (args[i] === '--title' && args[i + 1]) { - title = args[i + 1]; - i++; - } else if (args[i] === '--body' && args[i + 1]) { - body = args[i + 1]; - i++; - } - } - - // If no title provided, use a default based on the issue - if (!title) { - const issueInfo = JSON.parse( - execSync(`gh issue view ${context.issue} --json title`, { encoding: 'utf8' }) - ); - title = issueInfo.title; - } - - // If no body provided, create a default one - if (!body) { - body = `Closes #${context.issue}\\n\\nImplemented as per issue specifications.`; - } - - // Create the PR - console.log(`๐Ÿ“ Creating PR for issue #${context.issue}...`); - const prCommand = `gh pr create --title "${title}" --body "${body}" --base main`; - - let prUrl = ''; - try { - const output = execSync(prCommand, { encoding: 'utf8' }); - // Extract PR URL from output - const urlMatch = output.match(/https:\/\/github\.com\/[^\s]+\/pull\/\d+/); - if (urlMatch) { - prUrl = urlMatch[0]; - const prNumber = prUrl.match(/\/pull\/(\d+)/)[1]; - context.pr = parseInt(prNumber); - console.log(`โœ… PR #${prNumber} created: ${prUrl}`); - } - } catch (e) { - console.error(`โŒ Failed to create PR: ${e.message}`); - throw e; - } - - // Update context with PR info - fs.writeFileSync(contextPath, JSON.stringify(context, null, 2)); - - // Update issue labels: remove agent:wip, add agent:needs-review and status:in-code-review - console.log(`๐Ÿท๏ธ Updating issue labels...`); - try { - // Remove agent:wip label from issue - execSync(`gh issue edit ${context.issue} --remove-label "agent:wip"`, { stdio: 'inherit' }); - console.log(`โœ… Removed 'agent:wip' label from issue #${context.issue}`); - - // Add both agent and status labels to issue - execSync(`gh issue edit ${context.issue} --add-label "agent:needs-review" --add-label "status: in-code-review"`, { stdio: 'inherit' }); - console.log(`โœ… Added 'agent:needs-review' and 'status: in-code-review' labels to issue #${context.issue}`); - - // Also add labels to the PR - if (context.pr) { - console.log(`๐Ÿท๏ธ Adding labels to PR #${context.pr}...`); - execSync(`gh pr edit ${context.pr} --add-label "agent:needs-review" --add-label "status: in-code-review"`, { stdio: 'inherit' }); - console.log(`โœ… Added 'agent:needs-review' and 'status: in-code-review' labels to PR #${context.pr}`); - } - } catch (e) { - console.warn(`โš ๏ธ Could not update labels: ${e.message}`); - } - - // Update memory log - const memoryPath = path.join(botStateDir, 'memory.md'); - const timestamp = new Date().toISOString(); - const memoryEntry = `\n## ${timestamp}\n- **PR Created**: #${context.pr} - ${title}\n- URL: ${prUrl}\n- Labels updated: removed 'agent:wip', added 'agent:needs-review'\n`; - fs.appendFileSync(memoryPath, memoryEntry); - - console.log(`\nโœ… Pull request created successfully!`); - console.log(`๐Ÿ“Š Issue: #${context.issue}`); - console.log(`๐Ÿ”— PR: ${prUrl}`); - console.log(`๐Ÿท๏ธ Labels updated`); - - // Output for automation - const result = { - success: true, - issue: context.issue, - pr: context.pr, - url: prUrl, - branch: currentBranch - }; - - console.log(`\n๐Ÿ”ง Automation output:`); - console.log(JSON.stringify(result, null, 2)); - -} catch (error) { - console.error(`\nโŒ Error creating PR:`, error.message); - process.exit(1); -} \ No newline at end of file diff --git a/scripts/bot-workflow/core/bot-handoff.js b/scripts/bot-workflow/core/bot-handoff.js deleted file mode 100755 index 04f7542..0000000 --- a/scripts/bot-workflow/core/bot-handoff.js +++ /dev/null @@ -1,213 +0,0 @@ -#!/usr/bin/env node - -/** - * Bot prepares work for human takeover - * Usage: node bot-handoff.js "reason for handoff" - * Must be run from within the worktree directory - */ - -import { execSync } from 'child_process'; -import fs from 'fs'; -import path from 'path'; -import { ensureProjectRoot } from '../../utils/ensure-project-root.mjs'; -// Ensure we're in the project root -ensureProjectRoot('bot-handoff.js'); - - -// Get handoff reason -const reason = process.argv.slice(2).join(' '); - -if (!reason) { - console.error('โŒ Usage: node bot-handoff.js "reason for handoff"'); - console.error(' Example: node bot-handoff.js "Need decision on API design"'); - process.exit(1); -} - -console.log(`๐Ÿค Preparing handoff: ${reason}`); - -try { - // Check if we're in a worktree with bot state - const botStateDir = path.join(process.cwd(), '.bot'); - if (!fs.existsSync(botStateDir)) { - throw new Error('Not in a bot worktree directory (no .bot directory found)'); - } - - // Load context - const contextPath = path.join(botStateDir, 'context.json'); - const context = JSON.parse(fs.readFileSync(contextPath, 'utf8')); - - // Ensure all changes are committed - const gitStatus = execSync('git status --porcelain', { encoding: 'utf8' }); - if (gitStatus) { - console.log(`๐Ÿ“ฆ Committing pending changes...`); - execSync('git add -A', { stdio: 'inherit' }); - execSync(`git commit -m "bot: checkpoint before handoff\n\nReason: ${reason}"`, { stdio: 'inherit' }); - } - - // Push all changes - console.log(`๐Ÿ”„ Pushing changes...`); - execSync(`git push origin ${context.branch}`, { stdio: 'inherit' }); - - // Create or update PR if needed - let prNumber = context.pr; - - if (!prNumber) { - console.log(`๐Ÿ“ Creating pull request...`); - const prResult = execSync( - `gh pr create --title "WIP: Issue #${context.issue}" --body "๐Ÿค– Bot-created PR for issue #${context.issue}\n\n**Status**: Needs human review\n**Reason**: ${reason}" --draft`, - { encoding: 'utf8' } - ); - - // Extract PR number from output - const prMatch = prResult.match(/\/pull\/(\d+)/); - if (prMatch) { - prNumber = parseInt(prMatch[1]); - context.pr = prNumber; - fs.writeFileSync(contextPath, JSON.stringify(context, null, 2)); - } - } - - // Generate handoff summary - console.log(`๐Ÿ“‹ Generating handoff summary...`); - - const handoffTime = new Date().toISOString(); - const commitCount = execSync('git rev-list --count HEAD', { encoding: 'utf8' }).trim(); - const filesChanged = execSync('git diff --name-only origin/main...HEAD', { encoding: 'utf8' }) - .split('\n') - .filter(Boolean); - - // Read memory log for summary - const memoryPath = path.join(botStateDir, 'memory.md'); - const memoryContent = fs.readFileSync(memoryPath, 'utf8'); - - // Read checklist if exists - let checklistSummary = ''; - const checklistPath = path.join(botStateDir, 'checklist.md'); - if (fs.existsSync(checklistPath)) { - const checklist = fs.readFileSync(checklistPath, 'utf8'); - const tasks = checklist.match(/- \[[x ]\] .*/g) || []; - const completed = tasks.filter(t => t.includes('[x]')).length; - checklistSummary = `\n### ๐Ÿ“‹ Progress\n- Completed: ${completed}/${tasks.length} tasks\n\n${checklist}`; - } - - // Create handoff document - const handoffDoc = `# ๐Ÿค Bot Handoff Summary - -**Issue**: #${context.issue}${context.issueTitle ? ` - ${context.issueTitle}` : ''} -**PR**: #${prNumber || 'Not created yet'} -**Branch**: \`${context.branch}\` -**Handoff Time**: ${handoffTime} -**Reason**: ${reason} - -## ๐Ÿ“Š Work Summary -- Total commits: ${commitCount} -- Total checkpoints: ${context.checkpoints || 0} -- Files changed: ${filesChanged.length} -- Work started: ${context.claimedAt || context.createdAt} -- Last checkpoint: ${context.lastCheckpoint || 'None'} - -## ๐Ÿ“ Files Modified -${filesChanged.map(f => `- ${f}`).join('\n')} - -${checklistSummary} - -## ๐Ÿง  Bot Memory Log -${memoryContent} - -## ๐Ÿšจ Handoff Reason -${reason} - -## ๐ŸŽฏ Next Steps -The human developer should: -1. Review the changes made by the bot -2. Address the handoff reason: "${reason}" -3. Continue development or provide guidance -4. Update labels and PR status as needed - -## ๐Ÿ”ง To Resume Bot Work -\`\`\`bash -node scripts/bot-workflow/core/bot-resume-work.js ${prNumber || context.issue} -\`\`\` -`; - - // Save handoff document - const handoffPath = path.join(botStateDir, 'HANDOFF.md'); - fs.writeFileSync(handoffPath, handoffDoc); - console.log(`๐Ÿ“„ Created handoff document: ${handoffPath}`); - - // Update context - context.status = 'handoff'; - context.handoffAt = handoffTime; - context.handoffReason = reason; - fs.writeFileSync(contextPath, JSON.stringify(context, null, 2)); - - // Update PR description with handoff info - if (prNumber) { - console.log(`๐Ÿ”„ Updating PR #${prNumber}...`); - - const prBody = `๐Ÿค– Bot-created PR for issue #${context.issue} - -## ๐Ÿค Handoff Required -**Status**: Needs human review -**Reason**: ${reason} -**Time**: ${handoffTime} - -## ๐Ÿ“Š Summary -- Commits: ${commitCount} -- Checkpoints: ${context.checkpoints || 0} -- Files changed: ${filesChanged.length} - -## ๐Ÿ“ Handoff Details -See \`.bot/HANDOFF.md\` for complete handoff documentation. - -## ๐ŸŽฏ Next Steps -1. Review the bot's work -2. Address: "${reason}" -3. Continue development or provide guidance - ---- -*This PR was created by an automated bot and requires human intervention.*`; - - execSync(`gh pr edit ${prNumber} --body "${prBody.replace(/"/g, '\\"')}"`, { stdio: 'inherit' }); - - // Add labels - execSync(`gh pr edit ${prNumber} --add-label "needs-human-review"`, { stdio: 'inherit' }); - - // Add comment - execSync(`gh pr comment ${prNumber} --body "๐Ÿค **Handoff to Human Developer**\n\n**Reason**: ${reason}\n\n@${process.env.GITHUB_REPOSITORY_OWNER || 'owner'} - This PR needs your attention.\n\nSee \`.bot/HANDOFF.md\` for detailed handoff information."`, { stdio: 'inherit' }); - } - - // Update issue - console.log(`๐Ÿ”„ Updating issue #${context.issue}...`); - execSync(`gh issue comment ${context.issue} --body "๐Ÿค **Bot Handoff**\n\n**Reason**: ${reason}\n**PR**: #${prNumber || 'Not created'}\n**Time**: ${handoffTime}\n\nThe bot has prepared a handoff for human review. See the PR for details."`, { stdio: 'inherit' }); - - // Update issue labels - execSync(`gh issue edit ${context.issue} --add-label "agent:needs-review" --remove-label "agent:wip"`, { stdio: 'inherit' }); - - console.log(`\nโœ… Handoff prepared successfully!`); - console.log(`๐Ÿ“‹ Issue: #${context.issue}`); - if (prNumber) { - console.log(`๐Ÿ”— PR: #${prNumber}`); - } - console.log(`๐Ÿ“„ Handoff document: .bot/HANDOFF.md`); - console.log(`\nThe human developer has been notified.`); - - // Output for automation - const result = { - success: true, - issue: context.issue, - pr: prNumber, - branch: context.branch, - reason: reason, - handoffTime: handoffTime, - filesChanged: filesChanged.length, - commits: parseInt(commitCount) - }; - - console.log(`\n๐Ÿ”ง Automation output:`); - console.log(JSON.stringify(result, null, 2)); - -} catch (error) { - console.error(`\nโŒ Error preparing handoff:`, error.message); - process.exit(1); -} \ No newline at end of file diff --git a/scripts/bot-workflow/core/bot-integration-check.js b/scripts/bot-workflow/core/bot-integration-check.js deleted file mode 100755 index 4644a81..0000000 --- a/scripts/bot-workflow/core/bot-integration-check.js +++ /dev/null @@ -1,169 +0,0 @@ -#!/usr/bin/env node - -/** - * Checks integration readiness for a milestone - * Identifies dependencies, conflicts, and integration order - */ - -import { execSync } from 'child_process'; -import fs from 'fs'; -import path from 'path'; - -const CONTRACTS_DIR = 'src/contracts'; - -async function checkIntegration(milestone) { - console.log(`๐Ÿ” Integration Check for Milestone: ${milestone}\n`); - - try { - // Get all issues in milestone - const issues = JSON.parse( - execSync(`gh issue list --milestone "${milestone}" --json number,title,labels,body,state --limit 100`, - { encoding: 'utf8' }) - ); - - // Get all PRs - const prs = JSON.parse( - execSync(`gh pr list --json number,title,state,mergeable,headRefName,labels --limit 100`, - { encoding: 'utf8' }) - ); - - // Build dependency graph - const dependencies = {}; - const contracts = {}; - - for (const issue of issues) { - const deps = extractDependencies(issue.body || ''); - dependencies[issue.number] = deps; - - const contract = extractContract(issue.body || ''); - if (contract) { - contracts[issue.number] = contract; - } - } - - // Check PR readiness - console.log('๐Ÿ“‹ PR Status:'); - console.log('โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€'); - - for (const issue of issues) { - const pr = prs.find(p => { - const issueMatch = p.headRefName.match(/(\d+)/); - return issueMatch && parseInt(issueMatch[1]) === issue.number; - }); - - if (pr) { - const status = pr.mergeable ? 'โœ…' : 'โŒ'; - console.log(`${status} #${issue.number}: ${issue.title}`); - console.log(` PR #${pr.number} - ${pr.state} ${pr.mergeable ? '(ready to merge)' : '(conflicts!)'}`); - } else if (issue.state === 'OPEN') { - console.log(`โณ #${issue.number}: ${issue.title} (no PR yet)`); - } else { - console.log(`โœ… #${issue.number}: ${issue.title} (closed)`); - } - - // Show dependencies - if (dependencies[issue.number].length > 0) { - console.log(` Depends on: ${dependencies[issue.number].join(', ')}`); - } - } - - // Check contracts - console.log('\n๐Ÿ“„ Contract Status:'); - console.log('โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€'); - - for (const [issueNum, contractFile] of Object.entries(contracts)) { - const contractPath = path.join(CONTRACTS_DIR, contractFile); - const exists = fs.existsSync(contractPath); - console.log(`${exists ? 'โœ…' : 'โŒ'} #${issueNum}: ${contractFile}`); - } - - // Suggest merge order - console.log('\n๐Ÿ”„ Suggested Integration Order:'); - console.log('โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€'); - - const order = topologicalSort(dependencies); - order.forEach((issueNum, index) => { - const issue = issues.find(i => i.number === issueNum); - if (issue) { - console.log(`${index + 1}. #${issueNum}: ${issue.title}`); - } - }); - - // Integration readiness - console.log('\n๐ŸŽฏ Integration Readiness:'); - console.log('โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€'); - - const ready = issues.filter(i => { - const pr = prs.find(p => { - const match = p.headRefName.match(/(\d+)/); - return match && parseInt(match[1]) === i.number; - }); - return pr && pr.mergeable && pr.state === 'OPEN'; - }); - - const notReady = issues.filter(i => i.state === 'OPEN' && !ready.includes(i)); - - console.log(`โœ… Ready to merge: ${ready.length}`); - console.log(`โณ Still in progress: ${notReady.length}`); - - if (ready.length > 0) { - console.log('\n๐Ÿ’ก Next steps:'); - console.log('1. Review and merge PRs in the suggested order'); - console.log('2. Run integration tests after each merge'); - console.log('3. Update dependent PRs if needed'); - } - - } catch (error) { - console.error('โŒ Error:', error.message); - process.exit(1); - } -} - -function extractDependencies(body) { - const deps = []; - const depMatch = body.match(/\*\*Depends on:\*\*\s*(.+)/i); - if (depMatch) { - const matches = depMatch[1].matchAll(/#(\d+)/g); - for (const match of matches) { - deps.push(parseInt(match[1])); - } - } - return deps; -} - -function extractContract(body) { - const contractMatch = body.match(/\*\*Contract:\*\*\s*`(.+)`/i); - return contractMatch ? contractMatch[1] : null; -} - -function topologicalSort(dependencies) { - const visited = new Set(); - const result = []; - - function visit(node) { - if (visited.has(node)) return; - visited.add(node); - - const deps = dependencies[node] || []; - for (const dep of deps) { - visit(dep); - } - - result.push(node); - } - - for (const node of Object.keys(dependencies)) { - visit(parseInt(node)); - } - - return result; -} - -// Get milestone from command line -const milestone = process.argv[2]; -if (!milestone) { - console.error('Usage: node bot-integration-check.js "Milestone Name"'); - process.exit(1); -} - -checkIntegration(milestone); \ No newline at end of file diff --git a/scripts/bot-workflow/core/bot-release-ready.js b/scripts/bot-workflow/core/bot-release-ready.js deleted file mode 100755 index 3a01afb..0000000 --- a/scripts/bot-workflow/core/bot-release-ready.js +++ /dev/null @@ -1,207 +0,0 @@ -#!/usr/bin/env node - -/** - * Checks if a milestone is ready for release candidate - * Validates all integration criteria - */ - -import { execSync } from 'child_process'; -import fs from 'fs'; -import path from 'path'; -import { fileURLToPath } from 'url'; - -const __dirname = path.dirname(fileURLToPath(import.meta.url)); -const rootDir = path.join(__dirname, '../../..'); - -async function checkReleaseReadiness(milestone) { - console.log(`๐Ÿš€ Release Readiness Check for: ${milestone}\n`); - - const checks = { - allIssuesClosed: false, - allPRsMerged: false, - testsPass: false, - noTypeErrors: false, - documentationComplete: false, - integrationTestsExist: false, - e2eTestsPass: false, - contractsImplemented: false - }; - - try { - // 1. Check all issues are closed - console.log('๐Ÿ“‹ Checking issues...'); - const openIssues = JSON.parse( - execSync(`gh issue list --milestone "${milestone}" --state open --json number,title`, - { encoding: 'utf8' }) - ); - checks.allIssuesClosed = openIssues.length === 0; - console.log(` ${checks.allIssuesClosed ? 'โœ…' : 'โŒ'} All issues closed (${openIssues.length} open)`); - - // 2. Check all PRs are merged - console.log('\n๐Ÿ”€ Checking pull requests...'); - const openPRs = JSON.parse( - execSync(`gh pr list --state open --json number,title,labels`, { encoding: 'utf8' }) - ); - - // Filter for PRs related to this milestone - const milestonePRs = openPRs.filter(pr => - pr.labels.some(label => label.name.includes('agent:')) - ); - checks.allPRsMerged = milestonePRs.length === 0; - console.log(` ${checks.allPRsMerged ? 'โœ…' : 'โŒ'} All PRs merged (${milestonePRs.length} open)`); - - // 3. Check tests pass - console.log('\n๐Ÿงช Checking tests...'); - try { - execSync('npm run test:unit -- --reporter=json', { - cwd: rootDir, - stdio: 'pipe' - }); - checks.testsPass = true; - console.log(' โœ… All unit tests pass'); - } catch (e) { - console.log(' โŒ Unit tests failing'); - } - - // 4. Check TypeScript - console.log('\n๐Ÿ“ Checking TypeScript...'); - try { - execSync('npm run typecheck', { - cwd: rootDir, - stdio: 'pipe' - }); - checks.noTypeErrors = true; - console.log(' โœ… No TypeScript errors'); - } catch (e) { - console.log(' โŒ TypeScript errors found'); - } - - // 5. Check documentation - console.log('\n๐Ÿ“š Checking documentation...'); - const docFiles = [ - 'README.md', - 'docs/FILTER_PRESETS_API.md', - 'llms.txt' - ]; - - let docsComplete = true; - for (const doc of docFiles) { - const exists = fs.existsSync(path.join(rootDir, doc)); - if (!exists) { - docsComplete = false; - console.log(` โŒ Missing: ${doc}`); - } - } - checks.documentationComplete = docsComplete; - if (docsComplete) { - console.log(' โœ… Core documentation complete'); - } - - // 6. Check integration tests - console.log('\n๐Ÿ”— Checking integration tests...'); - const integrationTestPattern = '**/FilterPreset*.integration.test.{ts,tsx}'; - try { - const integrationTests = execSync( - `find ${rootDir}/src -name "*.integration.test.ts" -o -name "*.integration.test.tsx" | wc -l`, - { encoding: 'utf8' } - ).trim(); - checks.integrationTestsExist = parseInt(integrationTests) > 0; - console.log(` ${checks.integrationTestsExist ? 'โœ…' : 'โŒ'} Integration tests exist (${integrationTests} found)`); - } catch (e) { - console.log(' โŒ Could not check integration tests'); - } - - // 7. Check E2E tests - console.log('\n๐ŸŽญ Checking E2E tests...'); - try { - execSync('npm run test:e2e -- --project=chromium --reporter=json', { - cwd: rootDir, - stdio: 'pipe', - timeout: 60000 - }); - checks.e2eTestsPass = true; - console.log(' โœ… E2E tests pass'); - } catch (e) { - console.log(' โŒ E2E tests failing or not found'); - } - - // 8. Check contracts - console.log('\n๐Ÿ“„ Checking contracts...'); - const contractsDir = path.join(rootDir, 'src/contracts'); - if (fs.existsSync(contractsDir)) { - const contracts = fs.readdirSync(contractsDir); - checks.contractsImplemented = contracts.length > 0; - console.log(` ${checks.contractsImplemented ? 'โœ…' : 'โŒ'} Contracts defined (${contracts.length} found)`); - } else { - console.log(' โŒ No contracts directory found'); - } - - // Summary - console.log('\n' + '='.repeat(50)); - console.log('๐Ÿ“Š RELEASE READINESS SUMMARY'); - console.log('='.repeat(50)); - - const passedChecks = Object.values(checks).filter(v => v).length; - const totalChecks = Object.keys(checks).length; - const percentage = Math.round((passedChecks / totalChecks) * 100); - - console.log(`\nStatus: ${passedChecks}/${totalChecks} checks passed (${percentage}%)\n`); - - for (const [check, passed] of Object.entries(checks)) { - console.log(`${passed ? 'โœ…' : 'โŒ'} ${formatCheckName(check)}`); - } - - // Release decision - console.log('\n๐ŸŽฏ Release Decision:'); - if (percentage === 100) { - console.log('โœ… READY FOR RELEASE CANDIDATE'); - console.log('\nNext steps:'); - console.log('1. Create release branch: git checkout -b release/filter-presets-v1'); - console.log('2. Run full test suite: npm run test:all'); - console.log('3. Build production: npm run build'); - console.log('4. Create release PR: gh pr create --base main --label "release"'); - } else if (percentage >= 80) { - console.log('๐ŸŸก ALMOST READY - Address remaining issues'); - } else { - console.log('โŒ NOT READY - Significant work remaining'); - } - - // Generate release notes preview - if (percentage >= 80) { - console.log('\n๐Ÿ“ Release Notes Preview:'); - console.log('โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€'); - - const mergedPRs = JSON.parse( - execSync(`gh pr list --state merged --limit 20 --json number,title,mergedAt`, - { encoding: 'utf8' }) - ); - - const featurePRs = mergedPRs.filter(pr => - pr.title.toLowerCase().includes('filter preset') || - pr.title.includes('#47') || pr.title.includes('#48') || - pr.title.includes('#49') || pr.title.includes('#50') || - pr.title.includes('#51') || pr.title.includes('#52') - ); - - console.log('\n### โœจ New Features'); - featurePRs.forEach(pr => { - console.log(`- ${pr.title} (#${pr.number})`); - }); - } - - } catch (error) { - console.error('\nโŒ Error:', error.message); - process.exit(1); - } -} - -function formatCheckName(check) { - return check - .replace(/([A-Z])/g, ' $1') - .toLowerCase() - .replace(/^./, str => str.toUpperCase()); -} - -// Get milestone from command line -const milestone = process.argv[2] || 'Filter Presets v1'; -checkReleaseReadiness(milestone); \ No newline at end of file diff --git a/scripts/bot-workflow/core/bot-resume-work.js b/scripts/bot-workflow/core/bot-resume-work.js deleted file mode 100755 index 4d8ce0d..0000000 --- a/scripts/bot-workflow/core/bot-resume-work.js +++ /dev/null @@ -1,226 +0,0 @@ -#!/usr/bin/env node - -/** - * Bot resumes work on an existing PR or issue - * Usage: node bot-resume-work.js [pr-number|issue-number] - * If no argument provided, tries to find the most recent bot work - */ - -import { execSync } from 'child_process'; -import fs from 'fs'; -import path from 'path'; -import { ensureProjectRoot } from '../../utils/ensure-project-root.mjs'; -import { fileURLToPath } from 'url'; - -const __dirname = path.dirname(fileURLToPath(import.meta.url)); -// Ensure we're in the project root -ensureProjectRoot('bot-resume-work.js'); - - -// Configuration -const BOT_WORKSPACE_DIR = process.env.BOT_WORKSPACE_DIR || path.join(process.env.HOME, 'ag-grid-worktrees'); - -// Parse arguments -const identifier = process.argv[2]; - -console.log(`๐Ÿค– Bot resuming work...`); - -async function findWorkToResume() { - if (identifier) { - console.log(`๐Ÿ” Looking for PR/Issue #${identifier}...`); - - // Try as PR first - try { - const prInfo = JSON.parse( - execSync(`gh pr view ${identifier} --json number,headRefName,state`, { encoding: 'utf8' }) - ); - - if (prInfo.state === 'OPEN') { - return { - type: 'pr', - number: prInfo.number, - branch: prInfo.headRefName - }; - } - } catch (e) { - // Not a PR or not found - } - - // Try as issue - try { - const issueInfo = JSON.parse( - execSync(`gh issue view ${identifier} --json number,state,labels`, { encoding: 'utf8' }) - ); - - if (issueInfo.state === 'OPEN') { - // Find associated branch/worktree - const hasAgentWIP = issueInfo.labels.some(label => label.name === 'agent:wip'); - if (hasAgentWIP) { - // Look for branch pattern - const branchPattern = `feature/${issueInfo.number}-`; - const branches = execSync('git branch -a', { encoding: 'utf8' }) - .split('\n') - .map(b => b.trim()) - .filter(b => b.includes(branchPattern)); - - if (branches.length > 0) { - const branch = branches[0].replace(/^remotes\/origin\//, ''); - return { - type: 'issue', - number: issueInfo.number, - branch: branch - }; - } - } - } - } catch (e) { - // Not an issue or not found - } - } else { - console.log(`๐Ÿ” Finding most recent bot work...`); - - // Find open PRs with bot labels - try { - const prs = JSON.parse( - execSync(`gh pr list --json number,headRefName,labels --limit 10`, { encoding: 'utf8' }) - ); - - const botPRs = prs.filter(pr => - pr.labels.some(label => label.name.startsWith('agent:')) - ); - - if (botPRs.length > 0) { - return { - type: 'pr', - number: botPRs[0].number, - branch: botPRs[0].headRefName - }; - } - } catch (e) { - console.error('Could not list PRs:', e.message); - } - } - - return null; -} - -async function resumeWork() { - try { - const work = await findWorkToResume(); - - if (!work) { - throw new Error('No work found to resume. Specify a PR or issue number.'); - } - - console.log(`โœ… Found ${work.type} #${work.number} on branch ${work.branch}`); - - // Check if worktree exists - const worktreePath = path.join(BOT_WORKSPACE_DIR, work.branch); - - if (!fs.existsSync(worktreePath)) { - console.log(`โš ๏ธ Worktree not found at ${worktreePath}`); - console.log(`๐ŸŒณ Setting up worktree...`); - - // Extract issue number from branch or use PR number - const issueMatch = work.branch.match(/feature\/(\d+)-/); - const issueNumber = issueMatch ? issueMatch[1] : work.number; - - execSync( - `node ${path.join(__dirname, '../worktree/setup-worktree.js')} ${issueNumber}`, - { stdio: 'inherit' } - ); - } - - // Load bot context - const botStateDir = path.join(worktreePath, '.bot'); - if (!fs.existsSync(botStateDir)) { - throw new Error(`Bot state not found in worktree. This may not be a bot-managed branch.`); - } - - const contextPath = path.join(botStateDir, 'context.json'); - const context = JSON.parse(fs.readFileSync(contextPath, 'utf8')); - - // Update context with resume info - context.lastResumed = new Date().toISOString(); - context.resumeCount = (context.resumeCount || 0) + 1; - if (work.type === 'pr' && !context.pr) { - context.pr = work.number; - } - - fs.writeFileSync(contextPath, JSON.stringify(context, null, 2)); - - // Show current status - console.log(`\n๐Ÿ“Š Current Status:`); - console.log(`- Issue: #${context.issue}`); - console.log(`- Branch: ${context.branch}`); - console.log(`- Worktree: ${worktreePath}`); - console.log(`- Checkpoints: ${context.checkpoints || 0}`); - console.log(`- Last checkpoint: ${context.lastCheckpoint || 'None'}`); - if (context.checkpointMessage) { - console.log(`- Last message: "${context.checkpointMessage}"`); - } - - // Check for pending changes - process.chdir(worktreePath); - const gitStatus = execSync('git status --porcelain', { encoding: 'utf8' }); - if (gitStatus) { - console.log(`\nโš ๏ธ Uncommitted changes detected:`); - console.log(gitStatus); - } - - // Show recent memory - const memoryPath = path.join(botStateDir, 'memory.md'); - if (fs.existsSync(memoryPath)) { - console.log(`\n๐Ÿ“ Recent memory entries:`); - const memory = fs.readFileSync(memoryPath, 'utf8'); - const lines = memory.split('\n'); - const recentLines = lines.slice(-10).join('\n'); - console.log(recentLines); - } - - // Show checklist status if exists - const checklistPath = path.join(botStateDir, 'checklist.md'); - if (fs.existsSync(checklistPath)) { - console.log(`\n๐Ÿ“‹ Checklist status:`); - const checklist = fs.readFileSync(checklistPath, 'utf8'); - const tasks = checklist.match(/- \[[x ]\] .*/g) || []; - const completed = tasks.filter(t => t.includes('[x]')).length; - console.log(`Progress: ${completed}/${tasks.length} tasks completed`); - } - - // Update memory log - fs.appendFileSync( - memoryPath, - `\n## ${context.lastResumed}\n- Resumed work (resume #${context.resumeCount})\n- Current directory: ${worktreePath}\n` - ); - - console.log(`\nโœ… Successfully resumed work!`); - console.log(`๐Ÿ“ Working directory: ${worktreePath}`); - console.log(`\nYou can now continue development.`); - - // Output for automation - const result = { - success: true, - type: work.type, - number: work.number, - issue: context.issue, - branch: context.branch, - worktree: worktreePath, - checkpoints: context.checkpoints || 0, - hasUncommittedChanges: !!gitStatus - }; - - console.log(`\n๐Ÿ”ง Automation output:`); - console.log(JSON.stringify(result, null, 2)); - - } catch (error) { - console.error(`\nโŒ Error resuming work:`, error.message); - process.exit(1); - } -} - -// Run the resume process -resumeWork().catch(error => { - console.error('Fatal error:', error); - process.exit(1); -}); \ No newline at end of file diff --git a/scripts/bot-workflow/core/bot-status-all.js b/scripts/bot-workflow/core/bot-status-all.js deleted file mode 100755 index 2666862..0000000 --- a/scripts/bot-workflow/core/bot-status-all.js +++ /dev/null @@ -1,243 +0,0 @@ -#!/usr/bin/env node - -/** - * Shows status of all bot-managed work - * Usage: node bot-status-all.js - */ - -import { execSync } from 'child_process'; -import fs from 'fs'; -import path from 'path'; -import { ensureProjectRoot } from '../../utils/ensure-project-root.mjs'; -// Ensure we're in the project root -ensureProjectRoot('bot-status-all.js'); - - -// Configuration -const BOT_WORKSPACE_DIR = process.env.BOT_WORKSPACE_DIR || path.join(process.env.HOME, 'ag-grid-worktrees'); - -console.log(`๐Ÿ“Š Bot Work Status Report`); -console.log(`========================\n`); - -async function getWorkStatus() { - const status = { - active: [], - stale: [], - completed: [] - }; - - try { - // Get all open issues with agent labels - console.log(`๐Ÿ” Checking issues...`); - const issues = JSON.parse( - execSync(`gh issue list --json number,title,labels,updatedAt --limit 50`, { encoding: 'utf8' }) - ); - - const agentIssues = issues.filter(issue => - issue.labels.some(label => label.name.startsWith('agent:')) - ); - - // Get all open PRs with agent labels - console.log(`๐Ÿ” Checking pull requests...`); - const prs = JSON.parse( - execSync(`gh pr list --json number,title,labels,updatedAt,isDraft,headRefName --limit 50`, { encoding: 'utf8' }) - ); - - const agentPRs = prs.filter(pr => - pr.labels.some(label => label.name.startsWith('agent:')) - ); - - // Check worktrees - console.log(`๐Ÿ” Checking worktrees...`); - let worktrees = []; - if (fs.existsSync(BOT_WORKSPACE_DIR)) { - // Check both root and feature subdirectory - const checkDirs = [BOT_WORKSPACE_DIR, path.join(BOT_WORKSPACE_DIR, 'feature')]; - - for (const checkDir of checkDirs) { - if (!fs.existsSync(checkDir)) continue; - - const dirs = fs.readdirSync(checkDir); - for (const dir of dirs) { - const worktreePath = path.join(checkDir, dir); - const stat = fs.statSync(worktreePath); - - if (stat.isDirectory()) { - const botStateDir = path.join(worktreePath, '.bot'); - - if (fs.existsSync(botStateDir)) { - try { - const contextPath = path.join(botStateDir, 'context.json'); - if (fs.existsSync(contextPath)) { - const context = JSON.parse( - fs.readFileSync(contextPath, 'utf8') - ); - worktrees.push({ - path: worktreePath, - branch: dir, - context: context - }); - } - } catch (e) { - // Invalid context file - } - } - } - } - } - } - - // Process issues - for (const issue of agentIssues) { - const labels = issue.labels.map(l => l.name); - const issueStatus = { - number: issue.number, - title: issue.title, - labels: labels, - updatedAt: issue.updatedAt, - type: 'issue' - }; - - // Find associated PR - const associatedPR = agentPRs.find(pr => { - const branchMatch = pr.headRefName.match(/(\d+)/); - return branchMatch && parseInt(branchMatch[1]) === issue.number; - }); - - if (associatedPR) { - issueStatus.pr = associatedPR.number; - issueStatus.prDraft = associatedPR.isDraft; - } - - // Find worktree - const worktree = worktrees.find(w => w.context.issue === issue.number); - if (worktree) { - issueStatus.worktree = worktree.branch; - issueStatus.checkpoints = worktree.context.checkpoints || 0; - issueStatus.lastCheckpoint = worktree.context.lastCheckpoint; - } - - // Categorize - const lastUpdate = new Date(issue.updatedAt); - const hoursSinceUpdate = (Date.now() - lastUpdate) / (1000 * 60 * 60); - - if (labels.includes('agent:done')) { - status.completed.push(issueStatus); - } else if (hoursSinceUpdate > 24) { - status.stale.push(issueStatus); - } else { - status.active.push(issueStatus); - } - } - - // Sort by update time - const sortByUpdate = (a, b) => new Date(b.updatedAt) - new Date(a.updatedAt); - status.active.sort(sortByUpdate); - status.stale.sort(sortByUpdate); - status.completed.sort(sortByUpdate); - - } catch (error) { - console.error(`Error gathering status:`, error.message); - } - - return status; -} - -function formatTime(dateString) { - const date = new Date(dateString); - const now = Date.now(); - const diff = now - date; - - const hours = Math.floor(diff / (1000 * 60 * 60)); - const days = Math.floor(hours / 24); - - if (days > 0) { - return `${days}d ago`; - } else if (hours > 0) { - return `${hours}h ago`; - } else { - return 'recently'; - } -} - -async function displayStatus() { - const status = await getWorkStatus(); - - // Active work - if (status.active.length > 0) { - console.log(`๐ŸŸข Active Work (${status.active.length})`); - console.log(`โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€`); - - for (const work of status.active) { - console.log(`\n#${work.number}: ${work.title}`); - console.log(` Status: ${work.labels.filter(l => l.startsWith('agent:')).join(', ')}`); - console.log(` Updated: ${formatTime(work.updatedAt)}`); - - if (work.pr) { - console.log(` PR: #${work.pr}${work.prDraft ? ' (draft)' : ''}`); - } - - if (work.worktree) { - console.log(` Worktree: ${work.worktree}`); - if (work.checkpoints > 0) { - console.log(` Checkpoints: ${work.checkpoints} (last: ${formatTime(work.lastCheckpoint)})`); - } - } - } - } - - // Stale work - if (status.stale.length > 0) { - console.log(`\n\n๐ŸŸก Stale Work (>24h) (${status.stale.length})`); - console.log(`โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€`); - - for (const work of status.stale) { - console.log(`\n#${work.number}: ${work.title}`); - console.log(` โš ๏ธ Last update: ${formatTime(work.updatedAt)}`); - console.log(` Status: ${work.labels.filter(l => l.startsWith('agent:')).join(', ')}`); - - if (work.pr) { - console.log(` PR: #${work.pr}${work.prDraft ? ' (draft)' : ''}`); - } - } - } - - // Completed recently - const recentCompleted = status.completed.slice(0, 5); - if (recentCompleted.length > 0) { - console.log(`\n\nโœ… Recently Completed (${recentCompleted.length})`); - console.log(`โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€`); - - for (const work of recentCompleted) { - console.log(`\n#${work.number}: ${work.title}`); - console.log(` Completed: ${formatTime(work.updatedAt)}`); - } - } - - // Summary - console.log(`\n\n๐Ÿ“ˆ Summary`); - console.log(`โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€`); - console.log(`Active: ${status.active.length}`); - console.log(`Stale: ${status.stale.length}`); - console.log(`Completed: ${status.completed.length}`); - - // Worktree summary - try { - const worktreeList = execSync('git worktree list', { encoding: 'utf8' }); - const worktreeCount = worktreeList.split('\n').filter(Boolean).length - 1; // Exclude main - console.log(`Worktrees: ${worktreeCount}`); - } catch (e) { - // Git command failed - } - - console.log(`\n๐Ÿ’ก Tips:`); - console.log(`- Resume stale work: node scripts/bot-workflow/core/bot-resume-work.js `); - console.log(`- Check specific issue: gh issue view `); - console.log(`- View PR: gh pr view `); -} - -// Run the status report -displayStatus().catch(error => { - console.error('Fatal error:', error); - process.exit(1); -}); \ No newline at end of file diff --git a/scripts/bot-workflow/update-issue-dependencies.js b/scripts/bot-workflow/update-issue-dependencies.js deleted file mode 100755 index 4a0ba4e..0000000 --- a/scripts/bot-workflow/update-issue-dependencies.js +++ /dev/null @@ -1,88 +0,0 @@ -#!/usr/bin/env node - -/** - * Updates issues with dependency information - * This helps bots understand their relationships - */ - -import { execSync } from 'child_process'; - -const DEPENDENCIES = { - 48: { - depends: [47], - blocking: [51, 52], - contract: 'src/contracts/IFilterPresetUI.ts' - }, - 49: { - depends: [47], - blocking: [51, 52], - contract: 'src/contracts/IPresetSharing.ts' - }, - 50: { - depends: [47], - blocking: [51, 52], - contract: 'src/contracts/ISystemPresets.ts' - }, - 51: { - depends: [47, 48, 49, 50], - blocking: [52], - contract: null - }, - 52: { - depends: [47, 48, 49, 50, 51], - blocking: [], - contract: null - } -}; - -async function updateDependencies() { - console.log('๐Ÿ“‹ Updating issue dependencies...\n'); - - for (const [issueNum, deps] of Object.entries(DEPENDENCIES)) { - try { - // Get current issue body - const issue = JSON.parse( - execSync(`gh issue view ${issueNum} --json body`, { encoding: 'utf8' }) - ); - - let body = issue.body || ''; - - // Add dependency section if not exists - if (!body.includes('## Dependencies')) { - body += '\n\n## Dependencies\n\n'; - - if (deps.depends.length > 0) { - body += `**Depends on:** ${deps.depends.map(n => `#${n}`).join(', ')}\n`; - } - - if (deps.blocking.length > 0) { - body += `**Blocking:** ${deps.blocking.map(n => `#${n}`).join(', ')}\n`; - } - - if (deps.contract) { - body += `**Contract:** \`${deps.contract}\`\n`; - } - - body += '\n## Integration Notes\n\n'; - body += '- This issue is part of parallel development\n'; - body += '- Check contract file for interface definitions\n'; - body += '- Coordinate with dependent issues before major changes\n'; - - // Update issue - execSync(`gh issue edit ${issueNum} --body "${body.replace(/"/g, '\\"')}"`, - { stdio: 'inherit' }); - - console.log(`โœ… Updated issue #${issueNum} with dependencies`); - } else { - console.log(`โญ๏ธ Issue #${issueNum} already has dependencies`); - } - - } catch (error) { - console.error(`โŒ Error updating issue #${issueNum}:`, error.message); - } - } - - console.log('\nโœ… Dependency update complete!'); -} - -updateDependencies(); \ No newline at end of file diff --git a/scripts/bot-workflow/utils/bot-fix-ci.js b/scripts/bot-workflow/utils/bot-fix-ci.js deleted file mode 100755 index a73fc87..0000000 --- a/scripts/bot-workflow/utils/bot-fix-ci.js +++ /dev/null @@ -1,337 +0,0 @@ -#!/usr/bin/env node - -/** - * Bot attempts to fix CI failures automatically - * Usage: node bot-fix-ci.js - * Must be run from within the worktree directory - */ - -const { execSync } = require('child_process'); -const fs = require('fs'); -const path = require('path'); - -// Parse arguments -const prNumber = process.argv[2]; - -if (!prNumber) { - console.error('โŒ Usage: node bot-fix-ci.js '); - process.exit(1); -} - -console.log(`๐Ÿ”ง Bot attempting to fix CI failures for PR #${prNumber}...`); - -/** - * Get CI check runs for a PR - */ -async function getCIStatus(pr) { - try { - const checks = JSON.parse( - execSync(`gh pr checks ${pr} --json name,state,conclusion`, { encoding: 'utf8' }) - ); - - return checks; - } catch (error) { - console.error('Failed to get CI status:', error.message); - return []; - } -} - -/** - * Attempt to fix formatting issues - */ -async function fixFormatting() { - console.log(`๐ŸŽจ Attempting to fix formatting...`); - - try { - // Run format fix - execSync('npm run format:fix', { stdio: 'inherit' }); - - // Check if there are changes - const changes = execSync('git diff --name-only', { encoding: 'utf8' }); - - if (changes) { - console.log(`โœ… Fixed formatting in ${changes.split('\n').filter(Boolean).length} files`); - return true; - } else { - console.log(`โ„น๏ธ No formatting changes needed`); - return false; - } - } catch (error) { - console.error(`Failed to fix formatting:`, error.message); - return false; - } -} - -/** - * Attempt to fix linting issues - */ -async function fixLinting() { - console.log(`๐Ÿ” Attempting to fix linting issues...`); - - try { - // Run lint fix - execSync('npm run lint:fix', { stdio: 'inherit' }); - - // Check if there are changes - const changes = execSync('git diff --name-only', { encoding: 'utf8' }); - - if (changes) { - console.log(`โœ… Fixed linting in ${changes.split('\n').filter(Boolean).length} files`); - return true; - } else { - console.log(`โ„น๏ธ No linting changes needed`); - return false; - } - } catch (error) { - console.error(`Failed to fix linting:`, error.message); - return false; - } -} - -/** - * Attempt to fix whitespace issues - */ -async function fixWhitespace() { - console.log(`๐Ÿ“ Attempting to fix whitespace...`); - - try { - // Run whitespace fix - execSync('npm run fix:whitespace', { stdio: 'inherit' }); - - // Check if there are changes - const changes = execSync('git diff --name-only', { encoding: 'utf8' }); - - if (changes) { - console.log(`โœ… Fixed whitespace issues`); - return true; - } else { - console.log(`โ„น๏ธ No whitespace issues found`); - return false; - } - } catch (error) { - console.error(`Failed to fix whitespace:`, error.message); - return false; - } -} - -/** - * Analyze test failures - */ -async function analyzeTestFailures() { - console.log(`๐Ÿงช Analyzing test failures...`); - - try { - // Run tests and capture output - const testOutput = execSync('npm run test:unit 2>&1', { encoding: 'utf8' }).toString(); - - // Parse common test failure patterns - const failurePatterns = { - typeError: /TypeError: Cannot read property/g, - importError: /Cannot find module/g, - assertionError: /AssertionError/g, - timeoutError: /Timeout/g - }; - - const analysis = { - failures: [], - suggestions: [] - }; - - Object.entries(failurePatterns).forEach(([type, pattern]) => { - const matches = testOutput.match(pattern); - if (matches) { - analysis.failures.push({ - type: type, - count: matches.length - }); - } - }); - - // Generate suggestions - if (analysis.failures.some(f => f.type === 'importError')) { - analysis.suggestions.push('Check import paths and install missing dependencies'); - } - - if (analysis.failures.some(f => f.type === 'typeError')) { - analysis.suggestions.push('Add null checks and validate data types'); - } - - return analysis; - } catch (error) { - // Tests failed, which is expected - return { - failures: [{ type: 'general', count: 1 }], - suggestions: ['Review test output for specific failures'] - }; - } -} - -/** - * Attempt to fix type errors - */ -async function fixTypeErrors() { - console.log(`๐Ÿ“ Checking for type errors...`); - - try { - // Run typecheck and capture output - const output = execSync('npm run typecheck 2>&1', { encoding: 'utf8' }).toString(); - - // Look for common type errors - if (output.includes('is not assignable to type')) { - console.log(`โš ๏ธ Type errors detected - manual intervention required`); - return false; - } - - console.log(`โœ… No type errors found`); - return true; - } catch (error) { - // Type errors exist - const output = error.stdout?.toString() || error.message; - - // Extract error count - const errorMatch = output.match(/Found (\d+) error/); - const errorCount = errorMatch ? parseInt(errorMatch[1]) : 'unknown'; - - console.log(`โŒ Found ${errorCount} type errors`); - console.log(` These require manual fixing`); - - return false; - } -} - -/** - * Main CI fix function - */ -async function fixCI() { - try { - // Get current CI status - console.log(`\n๐Ÿ“Š Checking CI status...`); - const checks = await getCIStatus(prNumber); - - const failedChecks = checks.filter(c => c.conclusion === 'failure'); - - if (failedChecks.length === 0) { - console.log(`โœ… All CI checks are passing!`); - return; - } - - console.log(`\nโŒ Found ${failedChecks.length} failing checks:`); - failedChecks.forEach(check => { - console.log(` - ${check.name}`); - }); - - // Track what was fixed - const fixes = { - formatting: false, - linting: false, - whitespace: false, - tests: false, - types: false - }; - - // Attempt fixes based on failed checks - console.log(`\n๐Ÿ”ง Attempting automatic fixes...\n`); - - if (failedChecks.some(c => c.name.toLowerCase().includes('format'))) { - fixes.formatting = await fixFormatting(); - } - - if (failedChecks.some(c => c.name.toLowerCase().includes('lint'))) { - fixes.linting = await fixLinting(); - } - - if (failedChecks.some(c => c.name.toLowerCase().includes('whitespace'))) { - fixes.whitespace = await fixWhitespace(); - } - - if (failedChecks.some(c => c.name.toLowerCase().includes('type'))) { - fixes.types = await fixTypeErrors(); - } - - if (failedChecks.some(c => c.name.toLowerCase().includes('test'))) { - const testAnalysis = await analyzeTestFailures(); - console.log(`\n๐Ÿ“‹ Test failure analysis:`); - testAnalysis.failures.forEach(f => { - console.log(` - ${f.type}: ${f.count} failures`); - }); - console.log(`\n๐Ÿ’ก Suggestions:`); - testAnalysis.suggestions.forEach(s => { - console.log(` - ${s}`); - }); - } - - // Check if any fixes were made - const changesApplied = Object.values(fixes).some(f => f === true); - - if (changesApplied) { - console.log(`\nโœ… Applied automatic fixes!`); - - // Commit and push changes - console.log(`\n๐Ÿ“ค Committing and pushing fixes...`); - execSync('git add -A', { stdio: 'inherit' }); - execSync(`git commit -m "bot: fix CI failures\n\nAutomatically fixed:\n${Object.entries(fixes).filter(([_, v]) => v).map(([k, _]) => `- ${k}`).join('\n')}"`, { stdio: 'inherit' }); - - // Get branch name - const branch = execSync('git rev-parse --abbrev-ref HEAD', { encoding: 'utf8' }).trim(); - execSync(`git push origin ${branch}`, { stdio: 'inherit' }); - - // Add comment to PR - const comment = `๐Ÿค– **CI Fix Applied** - -I've automatically fixed the following issues: -${Object.entries(fixes).filter(([_, v]) => v).map(([k, _]) => `- โœ… ${k}`).join('\n')} - -The fixes have been committed and pushed. CI should re-run automatically.`; - - execSync(`gh pr comment ${prNumber} --body "${comment.replace(/"/g, '\\"')}"`, { stdio: 'inherit' }); - - } else { - console.log(`\nโš ๏ธ No automatic fixes could be applied`); - - // Add comment about manual intervention needed - const comment = `๐Ÿค– **CI Fix Attempt** - -I analyzed the CI failures but could not apply automatic fixes. - -**Failed checks:** -${failedChecks.map(c => `- โŒ ${c.name}`).join('\n')} - -**Manual intervention required for:** -${Object.entries(fixes).filter(([_, v]) => v === false).map(([k, _]) => `- ${k}`).join('\n')} - -Please review the CI logs and fix the issues manually.`; - - execSync(`gh pr comment ${prNumber} --body "${comment.replace(/"/g, '\\"')}"`, { stdio: 'inherit' }); - } - - // Update bot state - const botStateDir = path.join(process.cwd(), '.bot'); - if (fs.existsSync(botStateDir)) { - const memoryPath = path.join(botStateDir, 'memory.md'); - const memoryEntry = `\n## ${new Date().toISOString()}\n- Attempted CI fixes for PR #${prNumber}\n- Fixed: ${Object.entries(fixes).filter(([_, v]) => v).map(([k, _]) => k).join(', ') || 'none'}\n`; - fs.appendFileSync(memoryPath, memoryEntry); - } - - // Output for automation - const result = { - success: changesApplied, - pr: parseInt(prNumber), - failedChecks: failedChecks.length, - fixes: fixes, - timestamp: new Date().toISOString() - }; - - console.log(`\n๐Ÿ”ง Automation output:`); - console.log(JSON.stringify(result, null, 2)); - - } catch (error) { - console.error(`\nโŒ Error fixing CI:`, error.message); - process.exit(1); - } -} - -// Run the CI fix -fixCI().catch(error => { - console.error('Fatal error:', error); - process.exit(1); -}); \ No newline at end of file diff --git a/scripts/bot-workflow/utils/setup-bot-labels.js b/scripts/bot-workflow/utils/setup-bot-labels.js deleted file mode 100755 index 0051e80..0000000 --- a/scripts/bot-workflow/utils/setup-bot-labels.js +++ /dev/null @@ -1,275 +0,0 @@ -#!/usr/bin/env node - -/** - * Set up enhanced bot label system - * Usage: node setup-bot-labels.js - */ - -import { execSync } from 'child_process'; - -console.log(`๐Ÿท๏ธ Setting up enhanced bot label system...`); -console.log(`${'โ•'.repeat(50)}\n`); - -// Define bot-specific labels -const botLabels = [ - // Bot states - { - name: 'agent:todo', - color: '0E8A16', - description: 'Ready for bot assignment' - }, - { - name: 'agent:wip', - color: 'FEF3C7', - description: 'Bot actively working' - }, - { - name: 'agent:needs-review', - color: '1E40AF', - description: 'Bot work ready for human review' - }, - { - name: 'agent:failed', - color: 'B91C1C', - description: 'Bot encountered error' - }, - { - name: 'agent:done', - color: '6EE7B7', - description: 'Bot work completed and merged' - }, - - // Bot metadata - { - name: 'bot-created', - color: '8B5CF6', - description: 'Created by automated bot' - }, - { - name: 'needs-human-review', - color: 'F59E0B', - description: 'Requires human intervention' - }, - { - name: 'bot:checkpoint', - color: '6B7280', - description: 'Bot saved checkpoint' - }, - - // Coordinator labels - { - name: 'coordinator:tracking', - color: '7C3AED', - description: 'Feature tracking issue' - }, - { - name: 'coordinator:planned', - color: 'A78BFA', - description: 'Planned by coordinator' - }, - - // Performance labels - { - name: 'bot:stale', - color: 'DC2626', - description: 'Bot work idle >24h' - }, - { - name: 'bot:timeout', - color: 'EF4444', - description: 'Bot exceeded time limit' - } -]; - -/** - * Create or update a label - */ -async function createOrUpdateLabel(label) { - try { - // Check if label exists - const existingLabels = JSON.parse( - execSync('gh label list --json name,color,description --limit 1000', { encoding: 'utf8' }) - ); - - const existing = existingLabels.find(l => l.name === label.name); - - if (existing) { - // Update if different - if (existing.color !== label.color || existing.description !== label.description) { - console.log(`๐Ÿ”„ Updating label: ${label.name}`); - execSync( - `gh label edit "${label.name}" --color "${label.color}" --description "${label.description}"`, - { stdio: 'inherit' } - ); - } else { - console.log(`โœ… Label exists: ${label.name}`); - } - } else { - // Create new - console.log(`โž• Creating label: ${label.name}`); - execSync( - `gh label create "${label.name}" --color "${label.color}" --description "${label.description}"`, - { stdio: 'inherit' } - ); - } - } catch (error) { - console.error(`โŒ Error with label ${label.name}:`, error.message); - } -} - -/** - * Set up label aliases for backward compatibility - */ -function setupAliases() { - console.log(`\n๐Ÿ”— Setting up label aliases...`); - - // Map old labels to new ones if needed - const aliases = { - 'bot-work': 'agent:todo', - 'bot-in-progress': 'agent:wip', - 'bot-review': 'agent:needs-review' - }; - - Object.entries(aliases).forEach(([oldLabel, newLabel]) => { - try { - // Find issues with old label - const issues = JSON.parse( - execSync(`gh issue list --label "${oldLabel}" --json number --limit 100`, { encoding: 'utf8' }) - ); - - if (issues.length > 0) { - console.log(` Migrating ${issues.length} issues from ${oldLabel} to ${newLabel}`); - issues.forEach(issue => { - execSync( - `gh issue edit ${issue.number} --remove-label "${oldLabel}" --add-label "${newLabel}"`, - { stdio: 'ignore' } - ); - }); - } - } catch (e) { - // Old label doesn't exist - } - }); -} - -/** - * Create label groups documentation - */ -function createLabelDocs() { - const docs = `# Bot Label System - -## State Labels (agent:*) -These track the bot's progress through the development lifecycle: - -- \`agent:todo\` - Issue is ready for bot assignment -- \`agent:wip\` - Bot is actively working on the issue -- \`agent:needs-review\` - Bot has created PR, needs human review -- \`agent:failed\` - Bot encountered an error and needs help -- \`agent:done\` - Work is complete and merged - -## Metadata Labels -Additional information about bot work: - -- \`bot-created\` - PR/issue was created by a bot -- \`needs-human-review\` - Requires human intervention -- \`bot:checkpoint\` - Bot has saved progress checkpoints -- \`bot:stale\` - No activity for >24 hours -- \`bot:timeout\` - Bot exceeded time limits - -## Coordinator Labels -For multi-bot coordination: - -- \`coordinator:tracking\` - Main tracking issue for a feature -- \`coordinator:planned\` - Work items planned by coordinator - -## Label Transitions - -\`\`\`mermaid -graph LR - A[agent:todo] --> B[agent:wip] - B --> C[agent:needs-review] - B --> D[agent:failed] - C --> E[agent:done] - D --> F[needs-human-review] -\`\`\` - -## Usage Examples - -### Assign work to bot: -\`\`\`bash -gh issue edit 123 --add-label "agent:todo" -\`\`\` - -### Mark as failed: -\`\`\`bash -gh issue edit 123 --add-label "agent:failed" --add-label "needs-human-review" -\`\`\` - -### Query bot work: -\`\`\`bash -# All bot work -gh issue list --label "agent:wip" - -# Stale bot work -gh issue list --label "bot:stale" - -# Failed bot work needing help -gh issue list --label "agent:failed" -\`\`\` -`; - - console.log(`\n๐Ÿ“„ Label documentation created`); - return docs; -} - -/** - * Main setup function - */ -async function setupBotLabels() { - try { - // Create/update all bot labels - console.log(`Creating bot labels...`); - for (const label of botLabels) { - await createOrUpdateLabel(label); - } - - // Set up aliases - setupAliases(); - - // Create documentation - const docs = createLabelDocs(); - - // Summary - console.log(`\n${'โ•'.repeat(50)}`); - console.log(`โœ… Bot label system setup complete!\n`); - console.log(`๐Ÿ“Š Labels created: ${botLabels.length}`); - console.log(`\n๐Ÿท๏ธ Available bot states:`); - console.log(` - agent:todo โ†’ agent:wip โ†’ agent:needs-review โ†’ agent:done`); - console.log(` - agent:failed + needs-human-review (for errors)`); - - console.log(`\n๐Ÿ“ Next steps:`); - console.log(` 1. Update bot scripts to use new labels`); - console.log(` 2. Configure automation rules`); - console.log(` 3. Train team on label usage`); - - // Save documentation - import fs from 'fs'; - import path from 'path'; -import { fileURLToPath } from 'url'; - -const __dirname = path.dirname(fileURLToPath(import.meta.url)); - const docsPath = path.join(__dirname, '../BOT_LABELS.md'); - fs.writeFileSync(docsPath, docs); - console.log(`\n๐Ÿ“„ Documentation saved to: ${docsPath}`); - - } catch (error) { - console.error(`\nโŒ Error setting up labels:`, error.message); - process.exit(1); - } -} - -// Run the setup -setupBotLabels().catch(error => { - console.error('Fatal error:', error); - process.exit(1); -}); \ No newline at end of file diff --git a/scripts/bot-workflow/worktree/cleanup-worktree.js b/scripts/bot-workflow/worktree/cleanup-worktree.js deleted file mode 100755 index 5af4716..0000000 --- a/scripts/bot-workflow/worktree/cleanup-worktree.js +++ /dev/null @@ -1,123 +0,0 @@ -#!/usr/bin/env node - -/** - * Cleanup a git worktree after bot work is complete - * Usage: node cleanup-worktree.js - */ - -import { execSync } from 'child_process'; -import fs from 'fs'; -import path from 'path'; -import { ensureProjectRoot } from '../../utils/ensure-project-root.mjs'; -// Ensure we're in the project root -ensureProjectRoot('cleanup-worktree.js'); - - -// Configuration -const BOT_WORKSPACE_DIR = process.env.BOT_WORKSPACE_DIR || path.join(process.env.HOME, 'ag-grid-worktrees'); - -// Parse arguments -const [branchName] = process.argv.slice(2); - -if (!branchName) { - console.error('โŒ Usage: node cleanup-worktree.js '); - console.error(' Example: node cleanup-worktree.js feature/123-add-timezone'); - process.exit(1); -} - -const worktreePath = path.join(BOT_WORKSPACE_DIR, branchName); - -console.log(`๐Ÿงน Cleaning up worktree for branch: ${branchName}`); -console.log(`๐Ÿ“ Worktree path: ${worktreePath}`); - -try { - // Check if worktree exists - if (!fs.existsSync(worktreePath)) { - console.log(`โš ๏ธ Worktree not found at ${worktreePath}`); - console.log(` Running git worktree prune anyway...`); - execSync(`git worktree prune`); - } else { - // Save any important state before cleanup - const botStateDir = path.join(worktreePath, '.bot'); - if (fs.existsSync(botStateDir)) { - console.log(`๐Ÿ’พ Saving bot state before cleanup...`); - - // Read final state - const contextPath = path.join(botStateDir, 'context.json'); - if (fs.existsSync(contextPath)) { - const context = JSON.parse(fs.readFileSync(contextPath, 'utf8')); - context.cleanedUpAt = new Date().toISOString(); - - // Save to main repo's cleanup log - const cleanupLogDir = path.join(process.cwd(), '.bot-cleanup-logs'); - if (!fs.existsSync(cleanupLogDir)) { - fs.mkdirSync(cleanupLogDir, { recursive: true }); - } - - const logFile = path.join(cleanupLogDir, `${branchName.replace(/\//g, '-')}.json`); - fs.writeFileSync(logFile, JSON.stringify(context, null, 2)); - console.log(`๐Ÿ“‹ Saved cleanup log to ${logFile}`); - } - } - - // Remove worktree directory - console.log(`๐Ÿ—‘๏ธ Removing worktree directory...`); - execSync(`rm -rf ${worktreePath}`); - } - - // Prune worktree references - console.log(`๐ŸŒณ Pruning worktree references...`); - execSync(`git worktree prune`); - - // Check if branch was merged - let branchMerged = false; - try { - execSync(`git branch --merged | grep -q "${branchName}"`); - branchMerged = true; - } catch (e) { - // Branch not merged or doesn't exist - } - - if (branchMerged) { - console.log(`๐Ÿ”€ Branch ${branchName} has been merged`); - - // Delete local branch - console.log(`๐Ÿ—‘๏ธ Deleting local branch...`); - try { - execSync(`git branch -d ${branchName}`); - } catch (e) { - console.log(`โš ๏ธ Could not delete local branch (might not exist locally)`); - } - - // Delete remote branch - console.log(`๐Ÿ—‘๏ธ Deleting remote branch...`); - try { - execSync(`git push origin --delete ${branchName}`); - } catch (e) { - console.log(`โš ๏ธ Could not delete remote branch (might not exist or no permissions)`); - } - } else { - console.log(`โš ๏ธ Branch ${branchName} has not been merged - keeping branch`); - } - - // List remaining worktrees - console.log(`\n๐Ÿ“‹ Remaining worktrees:`); - execSync(`git worktree list`, { stdio: 'inherit' }); - - console.log(`\nโœ… Worktree cleanup complete!`); - - // Output JSON for automation - const result = { - success: true, - branch: branchName, - worktree: worktreePath, - branchDeleted: branchMerged - }; - - console.log(`\n๐Ÿ”ง Automation output:`); - console.log(JSON.stringify(result, null, 2)); - -} catch (error) { - console.error(`\nโŒ Error cleaning up worktree:`, error.message); - process.exit(1); -} \ No newline at end of file diff --git a/scripts/bot-workflow/worktree/setup-worktree.js b/scripts/bot-workflow/worktree/setup-worktree.js deleted file mode 100755 index bae8ade..0000000 --- a/scripts/bot-workflow/worktree/setup-worktree.js +++ /dev/null @@ -1,130 +0,0 @@ -#!/usr/bin/env node - -/** - * Setup a git worktree for bot development - * Usage: node setup-worktree.js - */ - -import { execSync } from 'child_process'; -import fs from 'fs'; -import path from 'path'; -import { ensureProjectRoot } from '../../utils/ensure-project-root.mjs'; -// Ensure we're in the project root -ensureProjectRoot('setup-worktree.js'); - - -// Configuration -const BOT_WORKSPACE_DIR = process.env.BOT_WORKSPACE_DIR || path.join(process.env.HOME, 'ag-grid-worktrees'); -const DEFAULT_BRANCH = process.env.BOT_DEFAULT_BRANCH || 'main'; - -// Parse arguments -const [issueNumber, description] = process.argv.slice(2); - -if (!issueNumber) { - console.error('โŒ Usage: node setup-worktree.js [description]'); - process.exit(1); -} - -// Generate branch name -const safeBranchName = description - ? `feature/${issueNumber}-${description.toLowerCase().replace(/[^a-z0-9]+/g, '-')}` - : `feature/issue-${issueNumber}`; - -const worktreePath = path.join(BOT_WORKSPACE_DIR, safeBranchName); - -console.log(`๐ŸŒณ Setting up worktree for issue #${issueNumber}`); -console.log(`๐Ÿ“ Workspace: ${BOT_WORKSPACE_DIR}`); -console.log(`๐ŸŒฟ Branch: ${safeBranchName}`); - -try { - // Ensure workspace directory exists - if (!fs.existsSync(BOT_WORKSPACE_DIR)) { - console.log(`๐Ÿ“ Creating workspace directory...`); - fs.mkdirSync(BOT_WORKSPACE_DIR, { recursive: true }); - } - - // Fetch latest from origin - console.log(`๐Ÿ”„ Fetching latest from origin...`); - execSync(`git fetch origin ${DEFAULT_BRANCH}`, { stdio: 'inherit' }); - - // Check if branch already exists - let branchExists = false; - try { - execSync(`git show-ref --verify --quiet refs/heads/${safeBranchName}`); - branchExists = true; - } catch (e) { - // Branch doesn't exist, which is fine - } - - if (!branchExists) { - // Create branch from main - console.log(`๐ŸŒฟ Creating branch ${safeBranchName} from ${DEFAULT_BRANCH}...`); - execSync(`git branch ${safeBranchName} origin/${DEFAULT_BRANCH}`); - } - - // Check if worktree already exists - if (fs.existsSync(worktreePath)) { - console.log(`โš ๏ธ Worktree already exists at ${worktreePath}`); - console.log(` Removing old worktree...`); - execSync(`rm -rf ${worktreePath}`); - execSync(`git worktree prune`); - } - - // Create worktree - console.log(`๐ŸŒณ Creating worktree...`); - execSync(`git worktree add ${worktreePath} ${safeBranchName}`); - - // Initialize bot state directory - const botStateDir = path.join(worktreePath, '.bot'); - if (!fs.existsSync(botStateDir)) { - console.log(`๐Ÿค– Initializing bot state directory...`); - fs.mkdirSync(botStateDir); - - // Create initial context file - const context = { - issue: parseInt(issueNumber), - branch: safeBranchName, - worktree: worktreePath, - createdAt: new Date().toISOString(), - status: 'initialized' - }; - - fs.writeFileSync( - path.join(botStateDir, 'context.json'), - JSON.stringify(context, null, 2) - ); - - // Create initial memory file - fs.writeFileSync( - path.join(botStateDir, 'memory.md'), - `# Bot Memory Log - Issue #${issueNumber}\n\n## ${new Date().toISOString()}\n- Initialized worktree for issue #${issueNumber}\n- Branch: ${safeBranchName}\n- Worktree: ${worktreePath}\n` - ); - } - - // Install dependencies in worktree - console.log(`๐Ÿ“ฆ Installing dependencies in worktree...`); - execSync(`cd ${worktreePath} && npm ci`, { stdio: 'inherit' }); - - console.log(`\nโœ… Worktree setup complete!`); - console.log(`\n๐Ÿ“ Working directory: ${worktreePath}`); - console.log(`๐ŸŒฟ Branch: ${safeBranchName}`); - console.log(`\nNext steps:`); - console.log(` cd ${worktreePath}`); - console.log(` # Start working on issue #${issueNumber}`); - - // Output JSON for automation - const result = { - success: true, - issue: parseInt(issueNumber), - branch: safeBranchName, - worktree: worktreePath, - botStateDir: botStateDir - }; - - console.log(`\n๐Ÿ”ง Automation output:`); - console.log(JSON.stringify(result, null, 2)); - -} catch (error) { - console.error(`\nโŒ Error setting up worktree:`, error.message); - process.exit(1); -} \ No newline at end of file diff --git a/scripts/create-stackblitz-examples.js b/scripts/build/create-stackblitz-examples.js similarity index 100% rename from scripts/create-stackblitz-examples.js rename to scripts/build/create-stackblitz-examples.js diff --git a/scripts/build/generate-og-image.js b/scripts/build/generate-og-image.js old mode 100755 new mode 100644 diff --git a/scripts/build/generate-version-info.js b/scripts/build/generate-version-info.js old mode 100755 new mode 100644 index ebf6b3a..73de0bd --- a/scripts/build/generate-version-info.js +++ b/scripts/build/generate-version-info.js @@ -58,7 +58,7 @@ function getGitInfo() { function getPackageVersion() { const packageJson = JSON.parse( - fs.readFileSync(path.resolve(__dirname, '../../package.json'), 'utf8') + fs.readFileSync(path.resolve(__dirname, '../package.json'), 'utf8') ); return packageJson.version; } @@ -103,7 +103,7 @@ function generateVersionInfo() { }; // Write to file - const outputPath = path.resolve(__dirname, '../../src/demo/version-info.json'); + const outputPath = path.resolve(__dirname, '../src/demo/version-info.json'); fs.writeFileSync(outputPath, JSON.stringify(versionInfo, null, 2) + '\n'); console.log('โœ… Version info generated:', versionInfo.displayVersion, `(${versionInfo.displayLabel})`); diff --git a/scripts/check-branch-protection.sh b/scripts/check-branch-protection.sh deleted file mode 100755 index 620ced4..0000000 --- a/scripts/check-branch-protection.sh +++ /dev/null @@ -1,34 +0,0 @@ -#!/bin/bash - -echo "๐Ÿ” Checking branch protection for main branch" -echo "" - -# Check if protection exists -protection=$(gh api repos/ryanrozich/ag-grid-react-components/branches/main/protection 2>&1) - -if [[ $protection == *"404"* ]]; then - echo "โŒ No branch protection enabled" - echo "" - echo "๐Ÿ’ก To enable protection, run:" - echo " ./scripts/setup-branch-protection.sh" -else - echo "โœ… Branch protection is enabled" - echo "" - echo "Required status checks:" - gh api repos/ryanrozich/ag-grid-react-components/branches/main/protection \ - --jq '.required_status_checks.checks[] | " - " + .context' 2>/dev/null || echo " None" - - echo "" - echo "Settings:" - gh api repos/ryanrozich/ag-grid-react-components/branches/main/protection --jq ' - " - Enforce for admins: " + (.enforce_admins.enabled | tostring) + "\n" + - " - Require PR reviews: " + (if .required_pull_request_reviews then "Yes" else "No" end) + "\n" + - " - Allow force pushes: " + (.allow_force_pushes.enabled | tostring) + "\n" + - " - Require conversation resolution: " + (.required_conversation_resolution.enabled | tostring) - ' 2>/dev/null || echo " Unable to read settings" -fi - -echo "" -echo "๐Ÿ“ To modify protection:" -echo " - Add checks: Edit scripts/setup-branch-protection.sh" -echo " - Disable: gh api repos/ryanrozich/ag-grid-react-components/branches/main/protection --method DELETE" \ No newline at end of file diff --git a/scripts/check-codeql.js b/scripts/check-codeql.js deleted file mode 100755 index 939f373..0000000 --- a/scripts/check-codeql.js +++ /dev/null @@ -1,71 +0,0 @@ -#!/usr/bin/env node - -/** - * Local CodeQL checker - * Validates common security patterns before pushing - */ - -import { execSync } from 'child_process'; -import fs from 'fs'; -import path from 'path'; - -const securityPatterns = [ - { - name: 'Command Injection', - pattern: /execSync\s*\(\s*`[^`]*\$\{[^}]*\}[^`]*`/g, - message: 'Potential command injection: validate user input before using in shell commands', - fix: 'Use parseInt() or other validation for numeric inputs' - }, - { - name: 'Unescaped RegExp', - pattern: /new\s+RegExp\s*\(\s*[^,)]*\.replace\s*\(\s*\/[^/]+\/[^,)]*,\s*['"\\]+[^)]*\)/g, - message: 'Incomplete regex escaping: use proper escaping function', - fix: 'Use .replace(/[.*+?^${}()|[\\]\\\\]/g, \'\\\\$&\')' - }, - { - name: 'Workflow Inputs', - pattern: /workflow_dispatch:\s*\n\s*inputs:/g, - message: 'Workflow inputs can affect build security (CKV_GHA_7)', - fix: 'Remove inputs or add exemption comment for non-build workflows' - } -]; - -console.log('๐Ÿ” Running local CodeQL checks...\n'); - -const filesToCheck = execSync('git diff --cached --name-only', { encoding: 'utf8' }) - .split('\n') - .filter(f => f && (f.endsWith('.js') || f.endsWith('.ts') || f.endsWith('.yml'))); - -if (filesToCheck.length === 0) { - console.log('No staged files to check'); - process.exit(0); -} - -let issues = 0; - -for (const file of filesToCheck) { - if (!fs.existsSync(file)) continue; - - const content = fs.readFileSync(file, 'utf8'); - const lines = content.split('\n'); - - for (const pattern of securityPatterns) { - let match; - while ((match = pattern.pattern.exec(content)) !== null) { - const lineNum = content.substring(0, match.index).split('\n').length; - console.log(`\nโŒ ${file}:${lineNum}`); - console.log(` ${pattern.name}: ${pattern.message}`); - console.log(` Fix: ${pattern.fix}`); - console.log(` Line: ${lines[lineNum - 1].trim()}`); - issues++; - } - } -} - -if (issues > 0) { - console.log(`\n\nโš ๏ธ Found ${issues} potential security issues`); - console.log('Fix these before pushing to avoid CodeQL failures'); - process.exit(1); -} else { - console.log('โœ… No security issues found!'); -} \ No newline at end of file diff --git a/scripts/check-fonts.js b/scripts/check-fonts.js deleted file mode 100644 index 667f6a7..0000000 --- a/scripts/check-fonts.js +++ /dev/null @@ -1,119 +0,0 @@ -import puppeteer from "puppeteer"; - -const checkFonts = async () => { - console.log("๐Ÿ” Checking font loading..."); - - const browser = await puppeteer.launch({ - headless: false, - defaultViewport: { width: 1280, height: 800 }, - }); - - try { - const page = await browser.newPage(); - - // Listen for console messages - page.on("console", (msg) => { - if (msg.type() === "error" && msg.text().includes("font")) { - console.log("Font loading error:", msg.text()); - } - }); - - // Go to the demo page - await page.goto("http://localhost:5174", { - waitUntil: "networkidle0", - timeout: 30000, - }); - - // Check if Fira Code is loaded - const fontInfo = await page.evaluate(() => { - // Check if Fira Code is in document.fonts - const fonts = Array.from(document.fonts); - const firaCodeFonts = fonts.filter((font) => - font.family.includes("Fira Code"), - ); - - // Try to detect if Fira Code is actually rendering - const testElement = document.createElement("span"); - testElement.style.fontFamily = "Fira Code"; - testElement.style.position = "absolute"; - testElement.style.left = "-9999px"; - testElement.textContent = "Test"; - document.body.appendChild(testElement); - - const testWidth = testElement.offsetWidth; - - testElement.style.fontFamily = "monospace"; - const monoWidth = testElement.offsetWidth; - - document.body.removeChild(testElement); - - return { - fontsLoaded: fonts.length, - firaCodeLoaded: firaCodeFonts.length > 0, - firaCodeFonts: firaCodeFonts.map((f) => ({ - family: f.family, - status: f.status, - style: f.style, - weight: f.weight, - })), - widthTest: { - firaCode: testWidth, - monospace: monoWidth, - different: testWidth !== monoWidth, - }, - }; - }); - - console.log("\n๐Ÿ“Š Font Loading Analysis:"); - console.log(`Total fonts loaded: ${fontInfo.fontsLoaded}`); - console.log( - `Fira Code loaded: ${fontInfo.firaCodeLoaded ? "โœ… Yes" : "โŒ No"}`, - ); - - if (fontInfo.firaCodeFonts.length > 0) { - console.log("\nFira Code fonts:"); - fontInfo.firaCodeFonts.forEach((font) => { - console.log( - ` ${font.family} - ${font.weight} ${font.style} (${font.status})`, - ); - }); - } - - console.log("\nWidth test:"); - console.log(` Fira Code width: ${fontInfo.widthTest.firaCode}px`); - console.log(` Monospace width: ${fontInfo.widthTest.monospace}px`); - console.log( - ` Different: ${fontInfo.widthTest.different ? "โœ… Yes (font is loading)" : "โŒ No (using fallback)"}`, - ); - - // Check network requests for font files - const fontRequests = await page.evaluate(() => { - return performance - .getEntriesByType("resource") - .filter( - (entry) => entry.name.includes("font") || entry.name.includes("woff"), - ) - .map((entry) => ({ - url: entry.name, - duration: entry.duration, - size: entry.transferSize, - })); - }); - - if (fontRequests.length > 0) { - console.log("\nFont network requests:"); - fontRequests.forEach((req) => { - console.log(` ${req.url.substring(0, 80)}...`); - console.log( - ` Duration: ${req.duration.toFixed(2)}ms, Size: ${req.size} bytes`, - ); - }); - } - } catch (error) { - console.error("โŒ Check failed:", error.message); - } finally { - await browser.close(); - } -}; - -checkFonts(); diff --git a/scripts/check-whitespace.sh b/scripts/check-whitespace.sh deleted file mode 100755 index 0884f09..0000000 --- a/scripts/check-whitespace.sh +++ /dev/null @@ -1,44 +0,0 @@ -#!/bin/bash - -# Check for trailing whitespace in all tracked files -echo "Checking for trailing whitespace..." - -# Get all tracked files -files=$(git ls-files) - -# Track if we found any issues -found_issues=false - -# Check each file for trailing whitespace -for file in $files; do - if [[ -f "$file" ]]; then - # Skip binary files - if file -b "$file" | grep -q "text"; then - # Check for trailing whitespace (spaces or tabs at end of line) - if grep -l '[[:space:]]$' "$file" > /dev/null 2>&1; then - echo "โŒ Trailing whitespace found in: $file" - # Show the lines with issues - grep -n '[[:space:]]$' "$file" | head -5 - found_issues=true - fi - - # Check for blank lines with whitespace - if grep -l '^[[:space:]]\+$' "$file" > /dev/null 2>&1; then - echo "โŒ Blank lines with whitespace found in: $file" - grep -n '^[[:space:]]\+$' "$file" | head -5 - found_issues=true - fi - fi - fi -done - -if [ "$found_issues" = true ]; then - echo "" - echo "โŒ Whitespace issues found! Please fix them before committing." - echo "๐Ÿ’ก Tip: You can fix them automatically with:" - echo " npm run fix:whitespace" - exit 1 -else - echo "โœ… No whitespace issues found!" - exit 0 -fi \ No newline at end of file diff --git a/scripts/cleanup-bot-files.sh b/scripts/cleanup-bot-files.sh deleted file mode 100755 index 64e1c7f..0000000 --- a/scripts/cleanup-bot-files.sh +++ /dev/null @@ -1,71 +0,0 @@ -#!/bin/bash - -# Clean up bot workflow files from ag-grid-react-components -# Run AFTER copying to MADF! - -set -e - -echo "๐Ÿงน Cleaning Bot Files from ag-grid-react-components" -echo "===================================================" -echo "" -echo "โš ๏ธ WARNING: This will remove bot workflow files!" -echo "โš ๏ธ Make sure you've run copy-to-madf.sh first!" -echo "" -read -p "Continue? (y/N) " -n 1 -r -echo "" - -if [[ ! $REPLY =~ ^[Yy]$ ]]; then - echo "โŒ Cancelled" - exit 1 -fi - -# Remove from git tracking (but keep local files) -echo "๐Ÿ“ Removing from git tracking..." -git rm -r --cached scripts/bot-workflow/ 2>/dev/null || echo " โš ๏ธ bot-workflow already untracked" -git rm --cached scripts/sync-agent-status-to-project.js 2>/dev/null || true -git rm --cached scripts/*madf*.sh 2>/dev/null || true -git rm --cached MULTI_AGENT_FRAMEWORK_*.md 2>/dev/null || true -git rm --cached MADF_*.md 2>/dev/null || true -git rm --cached COALESCE_LABS_*.md 2>/dev/null || true -git rm --cached *COORDINATOR*.md 2>/dev/null || true -git rm --cached BOT_*.md 2>/dev/null || true -git rm --cached .github/workflows/sync-agent-status.yml 2>/dev/null || true - -# Update .gitignore -echo "๐Ÿ“ Updating .gitignore..." -cat >> .gitignore << 'EOF' - -# Bot orchestration framework (moved to MADF) -scripts/bot-workflow/ -scripts/*madf* -scripts/sync-agent-status-to-project.js -MULTI_AGENT_FRAMEWORK_*.md -MADF_*.md -COALESCE_LABS_*.md -*COORDINATOR*.md -*BOT_*.md -.bot/ -migration/ -EOF - -# Create a placeholder README for the scripts directory -echo "๐Ÿ“„ Creating placeholder..." -cat > scripts/BOT_WORKFLOW_MOVED.md << 'EOF' -# Bot Workflow Moved - -The bot workflow orchestration framework has been moved to its own repository: -`coalesce-labs/multi-agent-dev-framework` - -This is now a standalone product that can be used across multiple projects. -EOF - -echo "" -echo "โœ… Cleanup complete!" -echo "" -echo "๐Ÿ“‹ Next steps:" -echo "1. Review changes: git status" -echo "2. Commit: git add . && git commit -m 'chore: remove bot orchestration (moved to MADF)'" -echo "3. Push: git push" -echo "" -echo "The bot workflow files are still in your local directory but are now" -echo "untracked by git. You can safely delete them after confirming MADF has them." \ No newline at end of file diff --git a/scripts/cleanup-merged-pr-deployments.js b/scripts/cleanup-merged-pr-deployments.js deleted file mode 100755 index 8c0e607..0000000 --- a/scripts/cleanup-merged-pr-deployments.js +++ /dev/null @@ -1,139 +0,0 @@ -#!/usr/bin/env node - -/** - * Script to clean up Cloudflare deployments for merged PRs - * This removes R2 objects, KV entries, and API workers for PRs that have been merged - */ - -import { execSync } from "child_process"; - -// Check required environment variables -const requiredEnvVars = [ - "CLOUDFLARE_API_TOKEN", - "CLOUDFLARE_ACCOUNT_ID", - "CLOUDFLARE_KV_NAMESPACE_ID", -]; - -for (const envVar of requiredEnvVars) { - if (!process.env[envVar]) { - console.error(`โŒ Missing required environment variable: ${envVar}`); - process.exit(1); - } -} - -console.log("๐Ÿ” Finding merged PRs with deployments...\n"); - -// Get all closed PRs -let mergedPRs; -try { - const prsJson = execSync( - 'gh pr list --state closed --limit 100 --json number,mergedAt,title', - { encoding: "utf8" } - ); - const allPRs = JSON.parse(prsJson); - mergedPRs = allPRs.filter((pr) => pr.mergedAt !== null); - console.log(`Found ${mergedPRs.length} merged PRs to check\n`); -} catch (error) { - console.error("โŒ Failed to fetch PRs:", error.message); - process.exit(1); -} - -// List all KV keys to find PR deployments -let prDeployments; -try { - const kvListOutput = execSync( - `wrangler kv key list --namespace-id="${process.env.CLOUDFLARE_KV_NAMESPACE_ID}" --remote`, - { encoding: "utf8" } - ); - const kvKeys = JSON.parse(kvListOutput); - - // Filter for PR deployment keys - prDeployments = kvKeys - .map((key) => key.name) - .filter((name) => name.match(/^ag-grid-react-components-pr-\d+$/)) - .map((name) => { - const prNumber = parseInt(name.match(/pr-(\d+)$/)[1]); - return { name, prNumber }; - }); - - console.log(`Found ${prDeployments.length} PR deployments in KV store\n`); -} catch (error) { - console.error("โŒ Failed to list KV keys:", error.message); - process.exit(1); -} - -// Find deployments for merged PRs -const mergedPRNumbers = new Set(mergedPRs.map((pr) => pr.number)); -const deploymentsToClean = prDeployments.filter((deployment) => - mergedPRNumbers.has(deployment.prNumber) -); - -if (deploymentsToClean.length === 0) { - console.log("โœ… No merged PR deployments found to clean up!"); - process.exit(0); -} - -console.log(`๐Ÿงน Found ${deploymentsToClean.length} merged PR deployments to clean:\n`); -deploymentsToClean.forEach((d) => { - const pr = mergedPRs.find((pr) => pr.number === d.prNumber); - console.log(` - PR #${d.prNumber}: ${pr.title}`); -}); - -// Ask for confirmation -console.log("\nโš ๏ธ This will permanently delete these deployments!"); -console.log("Press Ctrl+C to cancel, or wait 5 seconds to continue...\n"); - -// Wait 5 seconds -execSync("sleep 5"); - -// Clean up each deployment -let cleaned = 0; -let failed = 0; - -for (const deployment of deploymentsToClean) { - const { name: deployPath, prNumber } = deployment; - console.log(`\n๐Ÿ—‘๏ธ Cleaning PR #${prNumber} deployment...`); - - try { - // 1. Delete R2 objects - console.log(" - Deleting R2 objects..."); - try { - execSync( - `wrangler r2 object delete "rozich-demos/${deployPath}/" --remote --recursive`, - { stdio: "pipe" } - ); - console.log(" โœ… R2 objects deleted"); - } catch (error) { - console.log(" โš ๏ธ No R2 objects found or already deleted"); - } - - // 2. Delete KV entry - console.log(" - Deleting KV entry..."); - execSync( - `wrangler kv key delete "${deployPath}" --namespace-id="${process.env.CLOUDFLARE_KV_NAMESPACE_ID}" --remote`, - { stdio: "pipe" } - ); - console.log(" โœ… KV entry deleted"); - - // 3. Delete API worker - const workerName = `ag-grid-demo-api-pr-${prNumber}`; - console.log(` - Deleting API worker: ${workerName}...`); - try { - execSync(`wrangler delete "${workerName}" --remote`, { stdio: "pipe" }); - console.log(" โœ… API worker deleted"); - } catch (error) { - console.log(" โš ๏ธ API worker not found or already deleted"); - } - - cleaned++; - console.log(`โœ… PR #${prNumber} deployment cleaned up!`); - } catch (error) { - failed++; - console.error(`โŒ Failed to clean PR #${prNumber}:`, error.message); - } -} - -console.log(`\n๐Ÿ“Š Summary:`); -console.log(` - ${cleaned} deployments successfully cleaned`); -console.log(` - ${failed} deployments failed`); -console.log(`\nโœจ Cleanup complete!`); \ No newline at end of file diff --git a/scripts/cleanup-pr-status-labels.js b/scripts/cleanup-pr-status-labels.js deleted file mode 100755 index 77b148d..0000000 --- a/scripts/cleanup-pr-status-labels.js +++ /dev/null @@ -1,56 +0,0 @@ -#!/usr/bin/env node - -/** - * One-time cleanup script to remove status labels from PRs - * PRs should get their status from the project automation, not from linked issues - */ - -import { execSync } from 'child_process'; - -async function main() { - console.log('๐Ÿงน Cleaning up PR status labels\n'); - - // Get all PRs - const openPRsJson = execSync('gh pr list --state open --limit 100 --json number,title,labels', { - encoding: 'utf8' - }); - const openPRs = JSON.parse(openPRsJson); - - console.log(`Found ${openPRs.length} open PRs\n`); - - let cleanedCount = 0; - - for (const pr of openPRs) { - const statusLabels = pr.labels.filter(l => l.name.startsWith('status:')); - - if (statusLabels.length > 0) { - console.log(`PR #${pr.number}: ${pr.title}`); - console.log(` Found status labels: ${statusLabels.map(l => l.name).join(', ')}`); - - // Remove all status labels - for (const label of statusLabels) { - try { - execSync(`gh pr edit ${pr.number} --remove-label "${label.name}"`, { - stdio: 'pipe', - encoding: 'utf8' - }); - console.log(` โœ“ Removed: ${label.name}`); - } catch (error) { - console.error(` โœ— Failed to remove ${label.name}: ${error.message}`); - } - } - - cleanedCount++; - console.log(''); - } - } - - console.log('โ”€'.repeat(50)); - console.log(`โœ… Cleanup complete! Cleaned ${cleanedCount} PRs.`); - console.log('\n๐Ÿ’ก PRs will now get their status from project automation:'); - console.log(' - New PRs โ†’ "In Code Review" status'); - console.log(' - Code approved โ†’ "In Product Review" status'); - console.log(' - Merged PRs โ†’ "Done" status'); -} - -main().catch(console.error); \ No newline at end of file diff --git a/scripts/create-all-labels.js b/scripts/create-all-labels.js deleted file mode 100755 index d832511..0000000 --- a/scripts/create-all-labels.js +++ /dev/null @@ -1,121 +0,0 @@ -#!/usr/bin/env node - -/** - * Creates all required labels in the repository - * This ensures all labels referenced in the automation exist - */ - -import { execSync } from 'child_process'; - -// All labels that should exist -const LABELS = [ - // Type labels - { name: 'bug', color: 'd73a4a', description: 'Something isn\'t working' }, - { name: 'enhancement', color: 'a2eeef', description: 'New feature or request' }, - { name: 'documentation', color: '0075ca', description: 'Improvements or additions to documentation' }, - { name: 'question', color: 'd876e3', description: 'Further information is requested' }, - { name: 'good first issue', color: '7057ff', description: 'Good for newcomers' }, - { name: 'help wanted', color: '008672', description: 'Extra attention is needed' }, - - // Priority labels - { name: 'priority: critical', color: 'b60205', description: 'Must fix ASAP, blocking usage' }, - { name: 'priority: high', color: 'ff6666', description: 'Important, should be fixed soon' }, - { name: 'priority: medium', color: 'ffcc00', description: 'Normal priority' }, - { name: 'priority: low', color: '98fb98', description: 'Nice to have, can wait' }, - - // Area labels - { name: 'area: components', color: '1f77b4', description: 'Related to the React components' }, - { name: 'area: demo', color: 'ff7f0e', description: 'Related to the demo/showcase application' }, - { name: 'area: build', color: '2ca02c', description: 'Build tools, bundling, TypeScript config' }, - { name: 'area: ci/cd', color: 'd62728', description: 'GitHub Actions, deployment, automation' }, - { name: 'area: testing', color: '9467bd', description: 'Test suite, coverage, test infrastructure' }, - { name: 'area: docs', color: '8c564b', description: 'Documentation (README, API docs, guides)' }, - - // Status labels - { name: 'status: needs-triage', color: 'e99695', description: 'New issue awaiting evaluation' }, - { name: 'status: triaging', color: 'ffd93d', description: 'Currently being evaluated and labeled' }, - { name: 'status: backlog', color: 'c5def5', description: 'Prioritized and ready for development' }, - { name: 'status: in-progress', color: '0e8a16', description: 'Actively being worked on' }, - { name: 'status: in-code-review', color: '5319e7', description: 'PR submitted, under code review' }, - { name: 'status: in-product-review', color: '9f7efe', description: 'Code approved, under product review' }, - { name: 'status: done', color: '0e8a16', description: 'Completed and merged' }, - - // Component labels - { name: 'component: date-filter', color: 'bfd4f2', description: 'DateFilter/RelativeDateFilter components' }, - { name: 'component: quick-filter-dropdown', color: 'bfd4f2', description: 'QuickFilterDropdown component' }, - { name: 'component: active-filters', color: 'bfd4f2', description: 'ActiveFilters component' }, - { name: 'component: relative-date-filter', color: 'bfd4f2', description: 'RelativeDateFilter component' }, - { name: 'component: grid-state-utils', color: 'bfd4f2', description: 'Grid state persistence utilities' }, - { name: 'component: demo-app', color: 'bfd4f2', description: 'Demo application specific' }, - - // Effort labels (matching project field values) - { name: 'effort: xs', color: 'c2e0c6', description: 'Extra small (< 1 hour)' }, - { name: 'effort: s', color: 'd4e7c5', description: 'Small (1-4 hours)' }, - { name: 'effort: m', color: 'fef2c0', description: 'Medium (1-2 days)' }, - { name: 'effort: l', color: 'ffd8a8', description: 'Large (3-5 days)' }, - { name: 'effort: xl', color: 'f9d0c4', description: 'Extra large (1+ week)' }, -]; - -function labelExists(labelName) { - try { - execSync(`gh label view "${labelName}"`, { stdio: 'pipe' }); - return true; - } catch { - return false; - } -} - -function createLabel(label) { - try { - const cmd = `gh label create "${label.name}" --color "${label.color}" --description "${label.description}"`; - execSync(cmd, { stdio: 'pipe' }); - console.log(`โœ… Created label: ${label.name}`); - } catch (error) { - console.error(`โŒ Failed to create label ${label.name}: ${error.message}`); - } -} - -function updateLabel(label) { - try { - const cmd = `gh label edit "${label.name}" --color "${label.color}" --description "${label.description}"`; - execSync(cmd, { stdio: 'pipe' }); - console.log(`๐Ÿ“ Updated label: ${label.name}`); - } catch (error) { - console.error(`โŒ Failed to update label ${label.name}: ${error.message}`); - } -} - -async function main() { - console.log('๐Ÿท๏ธ Creating/Updating Repository Labels\n'); - - let created = 0; - let updated = 0; - let skipped = 0; - - for (const label of LABELS) { - if (labelExists(label.name)) { - // Update existing label to ensure color and description match - updateLabel(label); - updated++; - } else { - createLabel(label); - created++; - } - } - - console.log('\n' + 'โ”€'.repeat(50)); - console.log('โœ… Complete!'); - console.log(` Created: ${created} labels`); - console.log(` Updated: ${updated} labels`); - console.log(` Total: ${LABELS.length} labels`); - - console.log('\n๐Ÿ“‹ Label categories now available:'); - console.log(' - Type (bug, enhancement, etc.)'); - console.log(' - Priority (critical, high, medium, low)'); - console.log(' - Area (components, demo, build, etc.)'); - console.log(' - Status (needs-triage through done)'); - console.log(' - Component (specific component labels)'); - console.log(' - Size (small, medium, large)'); -} - -main().catch(console.error); \ No newline at end of file diff --git a/scripts/create-milestone.js b/scripts/create-milestone.js deleted file mode 100755 index d62fb2d..0000000 --- a/scripts/create-milestone.js +++ /dev/null @@ -1,86 +0,0 @@ -#!/usr/bin/env node - -/** - * Create a new milestone with semantic versioning - * Usage: node scripts/create-milestone.js [description] - * Example: node scripts/create-milestone.js v0.1.0 "First Public Release" "Initial stable release with core components" - */ - -import { execSync } from 'child_process'; - -const [,, version, title, description] = process.argv; - -if (!version || !title) { - console.error('Usage: node scripts/create-milestone.js <version> <title> [description]'); - console.error('Example: node scripts/create-milestone.js v0.1.0 "First Public Release"'); - process.exit(1); -} - -// Ensure version starts with 'v' -const versionTag = version.startsWith('v') ? version : `v${version}`; - -// Parse semantic version -const versionMatch = versionTag.match(/v(\d+)\.(\d+)\.(\d+)(-.*)?/); -if (!versionMatch) { - console.error('Invalid version format. Use semantic versioning: v0.1.0, v1.0.0, etc.'); - process.exit(1); -} - -const [, major, minor, patch, prerelease] = versionMatch; -const versionType = major === '0' && minor === '0' ? 'patch' : - major === '0' ? 'minor' : - 'major'; - -// Build full title -const fullTitle = `${versionTag}: ${title}`; - -// Build description -let fullDescription = description || ''; - -// Add version type guidance -const guidance = { - patch: '\n\n**Patch Release Guidelines:**\n- Bug fixes only\n- No new features\n- No breaking changes\n- Update patch version (0.0.X)', - minor: '\n\n**Minor Release Guidelines:**\n- New features allowed\n- Backward compatible changes\n- Bug fixes included\n- Update minor version (0.X.0)', - major: '\n\n**Major Release Guidelines:**\n- Breaking changes allowed\n- Major new features\n- API changes\n- Update major version (X.0.0)' -}; - -fullDescription += guidance[versionType]; - -// Add checklist -fullDescription += '\n\n**Release Checklist:**\n'; -fullDescription += '- [ ] All issues/PRs completed\n'; -fullDescription += '- [ ] Tests passing\n'; -fullDescription += '- [ ] Documentation updated\n'; -fullDescription += '- [ ] CHANGELOG.md updated\n'; -fullDescription += '- [ ] Version bumped in package.json\n'; -fullDescription += '- [ ] Release notes drafted\n'; - -try { - // Get repo info - const repoInfo = execSync('gh repo view --json nameWithOwner', { encoding: 'utf8' }); - const { nameWithOwner } = JSON.parse(repoInfo); - const [owner, repo] = nameWithOwner.split('/'); - - // Create milestone using API - const milestoneData = { - title: fullTitle, - description: fullDescription - }; - - const output = execSync( - `gh api repos/${owner}/${repo}/milestones --method POST --field title="${fullTitle}" --field description="${fullDescription}"`, - { encoding: 'utf8' } - ); - - const createdMilestone = JSON.parse(output); - - console.log(`โœ… Created milestone: ${fullTitle}`); - console.log(` Version: ${versionTag} (${versionType} release)`); - console.log(` Number: #${createdMilestone.number}`); - console.log(`\n๐Ÿ’ก To assign issues: gh issue edit <number> --milestone ${createdMilestone.number}`); - console.log(` To view: ${createdMilestone.html_url}`); - -} catch (error) { - console.error('Failed to create milestone:', error.message); - process.exit(1); -} \ No newline at end of file diff --git a/scripts/create-preview-labels.js b/scripts/create-preview-labels.js deleted file mode 100755 index 16af2cb..0000000 --- a/scripts/create-preview-labels.js +++ /dev/null @@ -1,55 +0,0 @@ -#!/usr/bin/env node - -/** - * Script to create labels used by the smart preview deployment system - */ - -const { execSync } = require("child_process"); - -const labels = [ - { - name: "deploy-preview", - description: "Force deployment of PR preview", - color: "0E8A16" // Green - }, - { - name: "skip-preview", - description: "Skip PR preview deployment", - color: "FBCA04" // Yellow - }, - { - name: "has-preview", - description: "PR has a deployed preview", - color: "1D76DB" // Blue - } -]; - -async function createLabels() { - console.log("๐Ÿท๏ธ Creating smart preview deployment labels...\n"); - - for (const label of labels) { - try { - // Check if label exists - try { - execSync(`gh label view "${label.name}"`, { stdio: 'pipe' }); - console.log(`โœ“ Label "${label.name}" already exists`); - continue; - } catch (e) { - // Label doesn't exist, create it - } - - // Create label - execSync( - `gh label create "${label.name}" --description "${label.description}" --color "${label.color}"`, - { stdio: 'inherit' } - ); - console.log(`โœ… Created label "${label.name}"`); - } catch (error) { - console.error(`โŒ Failed to create label "${label.name}":`, error.message); - } - } - - console.log("\nโœ… All labels processed!"); -} - -createLabels(); \ No newline at end of file diff --git a/scripts/deployment/cleanup-merged-pr-deployments.js b/scripts/deployment/cleanup-merged-pr-deployments.js deleted file mode 100755 index 8c0e607..0000000 --- a/scripts/deployment/cleanup-merged-pr-deployments.js +++ /dev/null @@ -1,139 +0,0 @@ -#!/usr/bin/env node - -/** - * Script to clean up Cloudflare deployments for merged PRs - * This removes R2 objects, KV entries, and API workers for PRs that have been merged - */ - -import { execSync } from "child_process"; - -// Check required environment variables -const requiredEnvVars = [ - "CLOUDFLARE_API_TOKEN", - "CLOUDFLARE_ACCOUNT_ID", - "CLOUDFLARE_KV_NAMESPACE_ID", -]; - -for (const envVar of requiredEnvVars) { - if (!process.env[envVar]) { - console.error(`โŒ Missing required environment variable: ${envVar}`); - process.exit(1); - } -} - -console.log("๐Ÿ” Finding merged PRs with deployments...\n"); - -// Get all closed PRs -let mergedPRs; -try { - const prsJson = execSync( - 'gh pr list --state closed --limit 100 --json number,mergedAt,title', - { encoding: "utf8" } - ); - const allPRs = JSON.parse(prsJson); - mergedPRs = allPRs.filter((pr) => pr.mergedAt !== null); - console.log(`Found ${mergedPRs.length} merged PRs to check\n`); -} catch (error) { - console.error("โŒ Failed to fetch PRs:", error.message); - process.exit(1); -} - -// List all KV keys to find PR deployments -let prDeployments; -try { - const kvListOutput = execSync( - `wrangler kv key list --namespace-id="${process.env.CLOUDFLARE_KV_NAMESPACE_ID}" --remote`, - { encoding: "utf8" } - ); - const kvKeys = JSON.parse(kvListOutput); - - // Filter for PR deployment keys - prDeployments = kvKeys - .map((key) => key.name) - .filter((name) => name.match(/^ag-grid-react-components-pr-\d+$/)) - .map((name) => { - const prNumber = parseInt(name.match(/pr-(\d+)$/)[1]); - return { name, prNumber }; - }); - - console.log(`Found ${prDeployments.length} PR deployments in KV store\n`); -} catch (error) { - console.error("โŒ Failed to list KV keys:", error.message); - process.exit(1); -} - -// Find deployments for merged PRs -const mergedPRNumbers = new Set(mergedPRs.map((pr) => pr.number)); -const deploymentsToClean = prDeployments.filter((deployment) => - mergedPRNumbers.has(deployment.prNumber) -); - -if (deploymentsToClean.length === 0) { - console.log("โœ… No merged PR deployments found to clean up!"); - process.exit(0); -} - -console.log(`๐Ÿงน Found ${deploymentsToClean.length} merged PR deployments to clean:\n`); -deploymentsToClean.forEach((d) => { - const pr = mergedPRs.find((pr) => pr.number === d.prNumber); - console.log(` - PR #${d.prNumber}: ${pr.title}`); -}); - -// Ask for confirmation -console.log("\nโš ๏ธ This will permanently delete these deployments!"); -console.log("Press Ctrl+C to cancel, or wait 5 seconds to continue...\n"); - -// Wait 5 seconds -execSync("sleep 5"); - -// Clean up each deployment -let cleaned = 0; -let failed = 0; - -for (const deployment of deploymentsToClean) { - const { name: deployPath, prNumber } = deployment; - console.log(`\n๐Ÿ—‘๏ธ Cleaning PR #${prNumber} deployment...`); - - try { - // 1. Delete R2 objects - console.log(" - Deleting R2 objects..."); - try { - execSync( - `wrangler r2 object delete "rozich-demos/${deployPath}/" --remote --recursive`, - { stdio: "pipe" } - ); - console.log(" โœ… R2 objects deleted"); - } catch (error) { - console.log(" โš ๏ธ No R2 objects found or already deleted"); - } - - // 2. Delete KV entry - console.log(" - Deleting KV entry..."); - execSync( - `wrangler kv key delete "${deployPath}" --namespace-id="${process.env.CLOUDFLARE_KV_NAMESPACE_ID}" --remote`, - { stdio: "pipe" } - ); - console.log(" โœ… KV entry deleted"); - - // 3. Delete API worker - const workerName = `ag-grid-demo-api-pr-${prNumber}`; - console.log(` - Deleting API worker: ${workerName}...`); - try { - execSync(`wrangler delete "${workerName}" --remote`, { stdio: "pipe" }); - console.log(" โœ… API worker deleted"); - } catch (error) { - console.log(" โš ๏ธ API worker not found or already deleted"); - } - - cleaned++; - console.log(`โœ… PR #${prNumber} deployment cleaned up!`); - } catch (error) { - failed++; - console.error(`โŒ Failed to clean PR #${prNumber}:`, error.message); - } -} - -console.log(`\n๐Ÿ“Š Summary:`); -console.log(` - ${cleaned} deployments successfully cleaned`); -console.log(` - ${failed} deployments failed`); -console.log(`\nโœจ Cleanup complete!`); \ No newline at end of file diff --git a/scripts/deployment/diagnose-404.js b/scripts/deployment/diagnose-404.js deleted file mode 100755 index f6c10f5..0000000 --- a/scripts/deployment/diagnose-404.js +++ /dev/null @@ -1,148 +0,0 @@ -#!/usr/bin/env node - -/** - * This script helps diagnose 404 errors in the Vite development server - * by providing more detailed error logging. - */ - -import { spawn } from 'child_process'; -import puppeteer from 'puppeteer'; -import path from 'path'; -import fs from 'fs'; - -// Create logs directory if it doesn't exist -const logsDir = path.join(process.cwd(), 'logs'); -if (!fs.existsSync(logsDir)) { - fs.mkdirSync(logsDir); -} - -// Log file for detailed console output -const logFile = path.join(logsDir, 'detailed-404-logs.log'); -const logStream = fs.createWriteStream(logFile, { flags: 'w' }); - -// Start the dev server -console.log('Starting dev server...'); -const devServer = spawn('npm', ['run', 'dev'], { - stdio: ['ignore', 'pipe', 'pipe'], - shell: true -}); - -// Track 404 errors -let notFoundErrors = []; - -// Listen for page load -devServer.stdout.on('data', async (data) => { - const output = data.toString(); - - // Wait for the server to be ready - if (output.includes('Local:') && output.includes('http://localhost:')) { - console.log('Dev server started'); - - // Extract the URL from the output - const match = output.match(/http:\/\/localhost:\d+/); - if (!match) { - console.error('Could not determine dev server URL'); - process.exit(1); - } - - const url = match[0]; - console.log(`Testing app at ${url}`); - - try { - // Launch browser with detailed network logging - const browser = await puppeteer.launch({ - headless: 'new', - args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage'] - }); - const page = await browser.newPage(); - - // Enable detailed request logging - await page.setRequestInterception(true); - - // Log all requests and responses - page.on('request', request => { - logStream.write(`[REQUEST] ${request.method()} ${request.url()}\n`); - request.continue(); - }); - - page.on('response', response => { - const status = response.status(); - const url = response.url(); - logStream.write(`[RESPONSE] ${status} ${url}\n`); - - if (status === 404) { - notFoundErrors.push(url); - console.error(`404 Not Found: ${url}`); - } - }); - - // Log all console messages - page.on('console', (msg) => { - const type = msg.type(); - const text = msg.text(); - logStream.write(`[CONSOLE:${type}] ${text}\n`); - }); - - // Navigate to dev server - console.log(`Navigating to ${url}...`); - await page.goto(url, { waitUntil: 'networkidle2', timeout: 60000 }); - - // Wait for potential delayed requests - console.log('Waiting for any delayed network activity...'); - await new Promise(resolve => setTimeout(resolve, 5000)); - - // Take a screenshot - await page.screenshot({ path: './diagnostic-screenshot.png', fullPage: true }); - console.log('Screenshot saved to ./diagnostic-screenshot.png'); - - // Check if page rendered - const pageContent = await page.content(); - logStream.write(`\n\n[PAGE HTML]\n${pageContent}\n\n`); - - // Log errors found - console.log('\n==== Diagnostic Results ===='); - console.log(`Total 404 errors: ${notFoundErrors.length}`); - - if (notFoundErrors.length > 0) { - console.log('\nResources not found:'); - notFoundErrors.forEach((url, index) => { - console.log(`${index + 1}. ${url}`); - }); - } - - console.log(`\nDetailed logs written to: ${logFile}`); - console.log('==========================\n'); - - // Close browser - await browser.close(); - - // Close log file - logStream.end(); - - // Kill dev server - devServer.kill(); - - process.exit(0); - } catch (error) { - console.error('Error running diagnostic:', error); - logStream.end(); - devServer.kill(); - process.exit(1); - } - } -}); - -// Set a timeout -setTimeout(() => { - console.error('Timeout waiting for dev server'); - logStream.end(); - devServer.kill(); - process.exit(1); -}, 60000); - -// Log server errors -devServer.stderr.on('data', (data) => { - const error = data.toString(); - console.error(`Server error: ${error}`); - logStream.write(`[SERVER ERROR] ${error}\n`); -}); \ No newline at end of file diff --git a/scripts/pre-push-quick.sh b/scripts/dev/pre-push-quick.sh similarity index 100% rename from scripts/pre-push-quick.sh rename to scripts/dev/pre-push-quick.sh diff --git a/scripts/pre-push.sh b/scripts/dev/pre-push.sh similarity index 100% rename from scripts/pre-push.sh rename to scripts/dev/pre-push.sh diff --git a/scripts/quality/test-filter-click.js b/scripts/dev/test-filter-click.js similarity index 100% rename from scripts/quality/test-filter-click.js rename to scripts/dev/test-filter-click.js diff --git a/scripts/quality/thorough-demo-check.js b/scripts/dev/thorough-demo-check.js similarity index 100% rename from scripts/quality/thorough-demo-check.js rename to scripts/dev/thorough-demo-check.js diff --git a/scripts/validate-demo.js b/scripts/dev/validate-demo.js similarity index 100% rename from scripts/validate-demo.js rename to scripts/dev/validate-demo.js diff --git a/scripts/fix-all-pr-statuses.js b/scripts/fix-all-pr-statuses.js deleted file mode 100755 index d1ec173..0000000 --- a/scripts/fix-all-pr-statuses.js +++ /dev/null @@ -1,104 +0,0 @@ -#!/usr/bin/env node - -/** - * Comprehensive script to fix ALL PR statuses (open and closed) - */ - -import { execSync } from 'child_process'; - -async function main() { - console.log('๐Ÿ”ง Fixing ALL PR statuses\n'); - - // Get all PRs (open and closed) - const allPRsJson = execSync('gh pr list --state all --limit 100 --json number,title,state,isDraft,labels,mergedAt', { - encoding: 'utf8' - }); - const allPRs = JSON.parse(allPRsJson); - - console.log(`Found ${allPRs.length} total PRs\n`); - - // All possible status labels - const allStatusLabels = [ - 'status: needs-triage', - 'status: triaging', - 'status: backlog', - 'status: in-progress', - 'status: in-product-review', - 'status: done', - 'status: pr-in-progress', - 'status: in-code-review', - 'status: code-review-complete', - 'status: merged', - 'status: in-review' // old label - ]; - - let fixedCount = 0; - - for (const pr of allPRs) { - const currentLabels = pr.labels.map(l => l.name); - const currentStatusLabels = currentLabels.filter(l => allStatusLabels.includes(l)); - - let correctStatus; - - // Determine correct status - if (pr.state === 'MERGED' || pr.mergedAt) { - correctStatus = 'status: merged'; - } else if (pr.state === 'CLOSED') { - // Closed but not merged - remove all status labels - correctStatus = null; - } else { - // Open PR - correctStatus = pr.isDraft ? 'status: pr-in-progress' : 'status: in-code-review'; - } - - // Check if we need to fix this PR - const hasCorrectStatus = correctStatus ? currentStatusLabels.includes(correctStatus) : currentStatusLabels.length === 0; - const hasOnlyCorrectStatus = hasCorrectStatus && currentStatusLabels.length === (correctStatus ? 1 : 0); - - if (!hasOnlyCorrectStatus) { - console.log(`\nPR #${pr.number}: ${pr.title}`); - console.log(` State: ${pr.state}${pr.mergedAt ? ' (merged)' : ''}${pr.isDraft ? ' (draft)' : ''}`); - console.log(` Current status labels: ${currentStatusLabels.join(', ') || 'none'}`); - console.log(` Should have: ${correctStatus || 'no status labels'}`); - - // Remove all status labels - for (const label of allStatusLabels) { - if (currentLabels.includes(label)) { - try { - execSync(`gh pr edit ${pr.number} --remove-label "${label}"`, { - stdio: 'pipe', - encoding: 'utf8' - }); - console.log(` โœ“ Removed: ${label}`); - } catch (e) { - // Ignore errors - label might not exist - } - } - } - - // Add correct status - if (correctStatus) { - try { - execSync(`gh pr edit ${pr.number} --add-label "${correctStatus}"`, { - stdio: 'pipe', - encoding: 'utf8' - }); - console.log(` โœ“ Added: ${correctStatus}`); - } catch (error) { - console.error(` โœ— Failed to add ${correctStatus}: ${error.message}`); - } - } - - fixedCount++; - } - } - - console.log('\n' + 'โ•'.repeat(50)); - console.log(`โœ… Fixed ${fixedCount} PRs!`); - - if (fixedCount === 0) { - console.log(' All PRs already have correct status labels.'); - } -} - -main().catch(console.error); \ No newline at end of file diff --git a/scripts/fix-pr-project-status.js b/scripts/fix-pr-project-status.js deleted file mode 100755 index fde664f..0000000 --- a/scripts/fix-pr-project-status.js +++ /dev/null @@ -1,76 +0,0 @@ -#!/usr/bin/env node - -/** - * Script to fix PR project status for existing PRs - * This ensures all PRs in the project have the correct status based on their state - */ - -const { execSync } = require("child_process"); - -async function fixPRProjectStatus() { - console.log("๐Ÿ”ง Fixing PR project status...\n"); - - try { - // Get all open PRs - const prsJson = execSync( - `gh pr list --json number,isDraft,state --limit 100`, - { encoding: "utf8" } - ); - const prs = JSON.parse(prsJson); - - console.log(`Found ${prs.length} open PRs\n`); - - for (const pr of prs) { - console.log(`\nProcessing PR #${pr.number}...`); - - // Get current labels - const labelsJson = execSync( - `gh pr view ${pr.number} --json labels`, - { encoding: "utf8" } - ); - const { labels } = JSON.parse(labelsJson); - const labelNames = labels.map(l => l.name); - - // Remove existing status labels - const statusLabels = labelNames.filter(l => l.startsWith('status:')); - for (const label of statusLabels) { - console.log(` Removing label: ${label}`); - execSync(`gh pr edit ${pr.number} --remove-label "${label}"`, { stdio: 'pipe' }); - } - - // Add appropriate status label - let newStatus; - if (pr.isDraft) { - newStatus = 'status: pr-in-progress'; - } else { - // Check if it has approvals - const reviewsJson = execSync( - `gh pr view ${pr.number} --json reviews`, - { encoding: "utf8" } - ); - const { reviews } = JSON.parse(reviewsJson); - const hasApproval = reviews.some(r => r.state === 'APPROVED'); - - if (hasApproval) { - newStatus = 'status: code-review-complete'; - } else { - newStatus = 'status: in-code-review'; - } - } - - console.log(` Adding label: ${newStatus}`); - execSync(`gh pr edit ${pr.number} --add-label "${newStatus}"`, { stdio: 'pipe' }); - - console.log(` โœ… Fixed PR #${pr.number}`); - } - - console.log("\nโœ… All PRs have been updated!"); - console.log("\nThe sync-labels-to-project workflow will now sync these labels to the project fields."); - - } catch (error) { - console.error("โŒ Error:", error.message); - process.exit(1); - } -} - -fixPRProjectStatus(); \ No newline at end of file diff --git a/scripts/fix-pr-statuses.js b/scripts/fix-pr-statuses.js deleted file mode 100755 index fcb27d7..0000000 --- a/scripts/fix-pr-statuses.js +++ /dev/null @@ -1,71 +0,0 @@ -#!/usr/bin/env node - -/** - * Script to fix PR statuses - removes issue statuses and adds appropriate PR statuses - */ - -import { execSync } from 'child_process'; - -async function main() { - console.log('๐Ÿ”ง Fixing PR statuses\n'); - - // Get all open PRs - const openPRsJson = execSync('gh pr list --state open --limit 100 --json number,title,labels,isDraft', { - encoding: 'utf8' - }); - const openPRs = JSON.parse(openPRsJson); - - console.log(`Found ${openPRs.length} open PRs\n`); - - const issueStatusLabels = [ - 'status: needs-triage', - 'status: triaging', - 'status: backlog', - 'status: in-progress', - 'status: in-product-review', - 'status: done' - ]; - - for (const pr of openPRs) { - const currentLabels = pr.labels.map(l => l.name); - - // Check if PR has any issue status labels - const hasIssueStatus = currentLabels.some(label => issueStatusLabels.includes(label)); - - if (hasIssueStatus) { - console.log(`PR #${pr.number}: ${pr.title}`); - - // Remove all issue status labels - for (const label of issueStatusLabels) { - if (currentLabels.includes(label)) { - try { - execSync(`gh pr edit ${pr.number} --remove-label "${label}"`, { - stdio: 'pipe', - encoding: 'utf8' - }); - console.log(` โœ“ Removed: ${label}`); - } catch (e) {} - } - } - - // Add appropriate PR status - const newStatus = pr.isDraft ? 'status: pr-in-progress' : 'status: in-code-review'; - - try { - execSync(`gh pr edit ${pr.number} --add-label "${newStatus}"`, { - stdio: 'pipe', - encoding: 'utf8' - }); - console.log(` โœ“ Added: ${newStatus}`); - } catch (error) { - console.error(` โœ— Failed to add ${newStatus}: ${error.message}`); - } - - console.log(''); - } - } - - console.log('โœ… PR statuses fixed!'); -} - -main().catch(console.error); \ No newline at end of file diff --git a/scripts/fix-whitespace.sh b/scripts/fix-whitespace.sh deleted file mode 100755 index 61f0972..0000000 --- a/scripts/fix-whitespace.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash -# Fix whitespace issues in all text files - -# Find all text files and remove trailing whitespace -find . -type f \( -name "*.ts" -o -name "*.tsx" -o -name "*.js" -o -name "*.jsx" -o -name "*.json" -o -name "*.md" -o -name "*.css" -o -name "*.yml" -o -name "*.yaml" \) \ - -not -path "./node_modules/*" \ - -not -path "./dist/*" \ - -not -path "./.trunk/*" \ - -not -path "./coverage/*" \ - -not -path "./.git/*" \ - -exec sed -i '' 's/[[:space:]]*$//' {} \; - -echo "Whitespace fixed in all text files" \ No newline at end of file diff --git a/scripts/generate-og-image.js b/scripts/generate-og-image.js deleted file mode 100644 index f228771..0000000 --- a/scripts/generate-og-image.js +++ /dev/null @@ -1,102 +0,0 @@ -import { createCanvas } from 'canvas'; -import fs from 'fs'; -import path from 'path'; -import { fileURLToPath } from 'url'; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); - -// Create a 1200x630 canvas (Open Graph recommended size) -const canvas = createCanvas(1200, 630); -const ctx = canvas.getContext('2d'); - -// Background gradient -const gradient = ctx.createLinearGradient(0, 0, 1200, 630); -gradient.addColorStop(0, '#1e1b4b'); -gradient.addColorStop(0.5, '#0f172a'); -gradient.addColorStop(1, '#312e81'); -ctx.fillStyle = gradient; -ctx.fillRect(0, 0, 1200, 630); - -// Grid pattern -ctx.strokeStyle = 'rgba(30, 41, 59, 0.3)'; -ctx.lineWidth = 1; -for (let x = 0; x <= 1200; x += 40) { - ctx.beginPath(); - ctx.moveTo(x, 0); - ctx.lineTo(x, 630); - ctx.stroke(); -} -for (let y = 0; y <= 630; y += 40) { - ctx.beginPath(); - ctx.moveTo(0, y); - ctx.lineTo(1200, y); - ctx.stroke(); -} - -// Title -ctx.font = 'bold 64px Inter, Arial, sans-serif'; -ctx.fillStyle = 'white'; -ctx.fillText('AG Grid React Components', 100, 150); - -// Subtitle -ctx.font = '32px Inter, Arial, sans-serif'; -ctx.fillStyle = '#94a3b8'; -ctx.fillText('Give your users the date filtering they deserve', 100, 210); - -// Feature pills -const pills = [ - { text: 'Relative Dates', color: '#4f46e5', textColor: '#818cf8', x: 100 }, - { text: 'Quick Filters', color: '#10b981', textColor: '#34d399', x: 360 }, - { text: 'URL Persistence', color: '#f59e0b', textColor: '#fbbf24', x: 620 } -]; - -pills.forEach(pill => { - // Pill background - ctx.fillStyle = pill.color + '33'; // 20% opacity - ctx.beginPath(); - ctx.roundRect(pill.x, 260, 240, 50, 25); - ctx.fill(); - - // Pill text - ctx.font = '20px Inter, Arial, sans-serif'; - ctx.fillStyle = pill.textColor; - ctx.fillText(pill.text, pill.x + 30, 290); -}); - -// Code example background -ctx.fillStyle = 'rgba(30, 41, 59, 0.8)'; -ctx.beginPath(); -ctx.roundRect(100, 350, 800, 140, 8); -ctx.fill(); - -// Code example text -ctx.font = '18px "Fira Code", monospace'; -ctx.fillStyle = '#60a5fa'; -ctx.fillText('// Enable relative date queries like "Today-7d"', 120, 380); -ctx.fillStyle = '#e5e7eb'; -ctx.fillText('const DateFilter = createDateFilter();', 120, 410); -ctx.fillText('filterParams: { defaultMode: \'relative\' }', 120, 440); -ctx.fillStyle = '#60a5fa'; -ctx.fillText('// "Last 30 days" stays last 30 days', 120, 470); - -// Bundle size badge -ctx.fillStyle = '#10b98133'; // 20% opacity -ctx.beginPath(); -ctx.roundRect(950, 480, 150, 60, 8); -ctx.fill(); - -ctx.font = '16px Inter, Arial, sans-serif'; -ctx.fillStyle = '#34d399'; -ctx.textAlign = 'center'; -ctx.fillText('Starting at', 1025, 505); -ctx.font = 'bold 24px Inter, Arial, sans-serif'; -ctx.fillText('25KB', 1025, 530); - -// Save the image -const buffer = canvas.toBuffer('image/png'); -const outputPath = path.join(__dirname, '..', 'public', 'og-image.png'); -fs.writeFileSync(outputPath, buffer); - -console.log('Open Graph image generated successfully!'); -console.log('Saved to:', outputPath); \ No newline at end of file diff --git a/scripts/generate-version-info.js b/scripts/generate-version-info.js deleted file mode 100644 index 73de0bd..0000000 --- a/scripts/generate-version-info.js +++ /dev/null @@ -1,119 +0,0 @@ -#!/usr/bin/env node - -import { execSync } from 'child_process'; -import fs from 'fs'; -import path from 'path'; -import { fileURLToPath } from 'url'; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); - -function getGitInfo() { - try { - // Get current commit hash - const commitHash = execSync('git rev-parse HEAD', { encoding: 'utf8' }).trim(); - const shortHash = execSync('git rev-parse --short HEAD', { encoding: 'utf8' }).trim(); - - // Get current branch - const branch = execSync('git rev-parse --abbrev-ref HEAD', { encoding: 'utf8' }).trim(); - - // Get commit date - const commitDate = execSync('git log -1 --format=%cd --date=iso', { encoding: 'utf8' }).trim(); - - // Get latest tag - let latestTag = 'v0.0.0'; - try { - latestTag = execSync('git describe --tags --abbrev=0', { encoding: 'utf8' }).trim(); - } catch (e) { - // No tags found - } - - // Count commits since last tag - let commitsSinceTag = 0; - try { - commitsSinceTag = parseInt( - execSync(`git rev-list ${latestTag}..HEAD --count`, { encoding: 'utf8' }).trim() - ); - } catch (e) { - // Error counting commits - } - - // Check if working directory is clean - const isDirty = execSync('git status --porcelain', { encoding: 'utf8' }).trim() !== ''; - - return { - commitHash, - shortHash, - branch, - commitDate, - latestTag, - commitsSinceTag, - isDirty - }; - } catch (error) { - console.error('Error getting git info:', error.message); - return null; - } -} - -function getPackageVersion() { - const packageJson = JSON.parse( - fs.readFileSync(path.resolve(__dirname, '../package.json'), 'utf8') - ); - return packageJson.version; -} - -function getDeploymentContext() { - // Check environment variables to determine context - const isPR = process.env.GITHUB_EVENT_NAME === 'pull_request'; - const prNumber = process.env.GITHUB_PR_NUMBER || null; - const isMainBranch = process.env.GITHUB_REF === 'refs/heads/main'; - const deployPath = process.env.DEPLOY_PATH || 'ag-grid-react-components'; - - return { - isPR, - prNumber, - isMainBranch, - deployPath - }; -} - -function generateVersionInfo() { - const gitInfo = getGitInfo(); - const packageVersion = getPackageVersion(); - const deploymentContext = getDeploymentContext(); - const buildTime = new Date().toISOString(); - - const versionInfo = { - version: packageVersion, - git: gitInfo, - deployment: deploymentContext, - buildTime, - // Generate display strings - displayVersion: gitInfo && gitInfo.commitsSinceTag > 0 - ? `v${packageVersion}+${gitInfo.commitsSinceTag}` - : `v${packageVersion}`, - displayLabel: deploymentContext.isPR - ? `PR #${deploymentContext.prNumber}` - : gitInfo?.branch !== 'main' - ? gitInfo?.branch - : gitInfo?.commitsSinceTag > 0 - ? 'unreleased' - : 'latest' - }; - - // Write to file - const outputPath = path.resolve(__dirname, '../src/demo/version-info.json'); - fs.writeFileSync(outputPath, JSON.stringify(versionInfo, null, 2) + '\n'); - - console.log('โœ… Version info generated:', versionInfo.displayVersion, `(${versionInfo.displayLabel})`); - - return versionInfo; -} - -// Run if called directly -if (process.argv[1] === __filename) { - generateVersionInfo(); -} - -export { generateVersionInfo }; \ No newline at end of file diff --git a/scripts/manual-project-sync.js b/scripts/manual-project-sync.js deleted file mode 100755 index 0ba06d3..0000000 --- a/scripts/manual-project-sync.js +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env node - -/** - * Manual sync script that can run without GitHub Actions - * Use this when workflows aren't available on main branch - * - * Usage: GITHUB_TOKEN=ghp_xxx node scripts/manual-project-sync.js - * Note: Token needs 'repo' and 'read:project' scopes - */ - -import { execSync } from 'child_process'; - -// Check for token -const GITHUB_TOKEN = process.env.GITHUB_TOKEN || execSync('gh auth token').toString().trim(); - -if (!GITHUB_TOKEN) { - console.error('โŒ No GitHub token found. Please set GITHUB_TOKEN or authenticate with gh CLI'); - process.exit(1); -} - -console.log('๐Ÿ”„ Manual Project Sync\n'); -console.log('This script requires a GitHub token with these scopes:'); -console.log(' โœ“ repo'); -console.log(' โœ“ read:project'); -console.log('\nIf you get permission errors, update your token at:'); -console.log('https://github.com/settings/tokens\n'); -console.log('โ”€'.repeat(50) + '\n'); - -// Run the bootstrap sync with the token -try { - execSync('node scripts/bootstrap-project-sync.js', { - stdio: 'inherit', - env: { ...process.env, GITHUB_TOKEN } - }); -} catch (error) { - console.error('\nโŒ Sync failed. Please check your token permissions.'); - process.exit(1); -} \ No newline at end of file diff --git a/scripts/milestone-overview.js b/scripts/milestone-overview.js deleted file mode 100755 index c0b7cbb..0000000 --- a/scripts/milestone-overview.js +++ /dev/null @@ -1,113 +0,0 @@ -#!/usr/bin/env node - -/** - * Show overview of all milestones and their progress - */ - -import { execSync } from 'child_process'; - -// Get all milestones using API -const repoInfo = execSync('gh repo view --json nameWithOwner', { encoding: 'utf8' }); -const { nameWithOwner } = JSON.parse(repoInfo); -const [owner, repo] = nameWithOwner.split('/'); - -const milestonesJson = execSync(`gh api repos/${owner}/${repo}/milestones?state=all&per_page=100`, { encoding: 'utf8' }); -const milestones = JSON.parse(milestonesJson).map(m => ({ - number: m.number, - title: m.title, - state: m.state.toUpperCase(), - description: m.description, - dueOn: m.due_on, - closedAt: m.closed_at, - openIssues: m.open_issues, - closedIssues: m.closed_issues -})); - -// Get all issues with milestones -const issuesJson = execSync('gh issue list --state all --limit 200 --json number,state,milestone,labels', { encoding: 'utf8' }); -const issues = JSON.parse(issuesJson); - -// Get all PRs with milestones -const prsJson = execSync('gh pr list --state all --limit 200 --json number,state,milestone,labels', { encoding: 'utf8' }); -const prs = JSON.parse(prsJson); - -console.log('๐ŸŽฏ Milestone Overview\n'); - -// Group milestones by state -const openMilestones = milestones.filter(m => m.state === 'OPEN').sort((a, b) => { - const aVersion = a.title.match(/v(\d+)\.(\d+)\.(\d+)/); - const bVersion = b.title.match(/v(\d+)\.(\d+)\.(\d+)/); - if (!aVersion || !bVersion) return 0; - - // Compare versions - for (let i = 1; i <= 3; i++) { - if (parseInt(aVersion[i]) !== parseInt(bVersion[i])) { - return parseInt(aVersion[i]) - parseInt(bVersion[i]); - } - } - return 0; -}); - -const closedMilestones = milestones.filter(m => m.state === 'CLOSED'); - -// Show open milestones -if (openMilestones.length > 0) { - console.log('๐Ÿ“‚ Open Milestones:\n'); - - for (const milestone of openMilestones) { - // Count items in this milestone - const milestoneIssues = issues.filter(i => i.milestone?.number === milestone.number); - const milestonePRs = prs.filter(p => p.milestone?.number === milestone.number); - - const openItems = [...milestoneIssues, ...milestonePRs].filter(item => - item.state === 'OPEN' || item.state === 'open' - ); - const closedItems = [...milestoneIssues, ...milestonePRs].filter(item => - item.state === 'CLOSED' || item.state === 'closed' || item.state === 'MERGED' || item.state === 'merged' - ); - - const total = openItems.length + closedItems.length; - const progress = total > 0 ? Math.round((closedItems.length / total) * 100) : 0; - - console.log(` #${milestone.number}: ${milestone.title}`); - console.log(` Progress: ${progress}% (${closedItems.length}/${total} completed)`); - console.log(` ๐Ÿ“‹ Issues: ${milestoneIssues.length} | ๐Ÿ”„ PRs: ${milestonePRs.length}`); - - if (milestone.dueOn) { - const dueDate = new Date(milestone.dueOn); - const today = new Date(); - const daysLeft = Math.ceil((dueDate - today) / (1000 * 60 * 60 * 24)); - console.log(` ๐Ÿ“… Due: ${dueDate.toLocaleDateString()} (${daysLeft} days)`); - } - - // Show what's left - if (openItems.length > 0) { - console.log(` โณ Remaining:`); - const bugs = openItems.filter(item => item.labels.some(l => l.name === 'bug')); - const features = openItems.filter(item => item.labels.some(l => l.name === 'enhancement')); - const other = openItems.length - bugs.length - features.length; - - if (bugs.length > 0) console.log(` ๐Ÿ› ${bugs.length} bugs`); - if (features.length > 0) console.log(` โœจ ${features.length} features`); - if (other > 0) console.log(` ๐Ÿ“„ ${other} other items`); - } - - console.log(''); - } -} - -// Show closed milestones summary -if (closedMilestones.length > 0) { - console.log('\nโœ… Completed Milestones:\n'); - - for (const milestone of closedMilestones.slice(0, 5)) { - const closedDate = milestone.closedAt ? new Date(milestone.closedAt).toLocaleDateString() : 'Unknown'; - console.log(` ${milestone.title} (closed ${closedDate})`); - } -} - -// Suggest next actions -console.log('\n๐Ÿ’ก Quick Actions:'); -console.log(' Create milestone: node scripts/create-milestone.js v0.1.0 "First Public Release"'); -console.log(' Assign items: node scripts/assign-to-milestone.js <milestone-number>'); -console.log(' View in GitHub: gh browse issues'); \ No newline at end of file diff --git a/scripts/publish-local.sh b/scripts/publish-local.sh deleted file mode 100755 index 1edaa5b..0000000 --- a/scripts/publish-local.sh +++ /dev/null @@ -1,47 +0,0 @@ -#!/bin/bash - -# Script for local testing of npm publishing -# This does a dry run to verify everything is set up correctly - -set -e - -echo "๐Ÿ” Checking npm login status..." -npm whoami || (echo "โŒ Not logged in to npm. Run 'npm login' first." && exit 1) - -echo "๐Ÿ“ฆ Building all packages..." -npx turbo run build - -echo "๐Ÿงช Running tests..." -npm run test:unit - -echo "โœ… Running type checks..." -npm run typecheck - -echo "๐Ÿ“‹ Package versions:" -echo "Core: $(cat packages/core/package.json | grep '"version"' | cut -d'"' -f4)" -echo "Adapters: $(cat packages/adapters/package.json | grep '"version"' | cut -d'"' -f4)" -echo "Styles: $(cat packages/styles/package.json | grep '"version"' | cut -d'"' -f4)" -echo "Compat: $(cat packages/compat/package.json | grep '"version"' | cut -d'"' -f4)" - -echo "" -echo "๐Ÿ“ Dry run results:" -echo "-------------------" - -# Dry run for each package -for package in core adapters styles compat; do - echo "" - echo "Package: @agrc/$package" - cd packages/$package - npm pack --dry-run - cd ../.. -done - -echo "" -echo "โœ… Dry run complete!" -echo "" -echo "To publish for real, run:" -echo " npm run publish:beta # Publish with beta tag" -echo " npm run publish:latest # Publish as latest" -echo "" -echo "Or publish manually:" -echo " cd packages/core && npm publish --access public --tag beta" \ No newline at end of file diff --git a/scripts/quality/check-codeql.js b/scripts/quality/check-codeql.js index 5b331ee..939f373 100755 --- a/scripts/quality/check-codeql.js +++ b/scripts/quality/check-codeql.js @@ -8,10 +8,6 @@ import { execSync } from 'child_process'; import fs from 'fs'; import path from 'path'; -import { ensureProjectRoot } from '../utils/ensure-project-root.mjs'; - -// Ensure we're in the project root -ensureProjectRoot('check-codeql.js'); const securityPatterns = [ { diff --git a/scripts/quality/fix-whitespace.sh b/scripts/quality/fix-whitespace.sh index cf09348..61f0972 100755 --- a/scripts/quality/fix-whitespace.sh +++ b/scripts/quality/fix-whitespace.sh @@ -1,48 +1,13 @@ #!/bin/bash - -# Fix trailing whitespace in all tracked files -echo "Fixing trailing whitespace in all tracked files..." - -# Get all tracked files -files=$(git ls-files) - -# Track how many files we fixed -fixed_count=0 - -# Fix each file -for file in $files; do - if [[ -f "$file" ]]; then - # Create a temporary backup - cp "$file" "$file.bak" - - # Skip binary files - if file -b "$file" | grep -q "text"; then - # Remove trailing whitespace - # This works on macOS and Linux - if sed -i '' 's/[[:space:]]*$//' "$file" 2>/dev/null; then - # Check if file actually changed - if ! cmp -s "$file" "$file.bak"; then - echo "โœ… Fixed: $file" - ((fixed_count++)) - fi - else - # Fallback for systems where sed -i '' doesn't work - sed 's/[[:space:]]*$//' "$file" > "$file.tmp" && mv "$file.tmp" "$file" - if ! cmp -s "$file" "$file.bak"; then - echo "โœ… Fixed: $file" - ((fixed_count++)) - fi - fi - fi - - # Remove backup - rm -f "$file.bak" - fi -done - -if [ $fixed_count -gt 0 ]; then - echo "" - echo "โœ… Fixed whitespace issues in $fixed_count files!" -else - echo "โœ… No whitespace issues found!" -fi \ No newline at end of file +# Fix whitespace issues in all text files + +# Find all text files and remove trailing whitespace +find . -type f \( -name "*.ts" -o -name "*.tsx" -o -name "*.js" -o -name "*.jsx" -o -name "*.json" -o -name "*.md" -o -name "*.css" -o -name "*.yml" -o -name "*.yaml" \) \ + -not -path "./node_modules/*" \ + -not -path "./dist/*" \ + -not -path "./.trunk/*" \ + -not -path "./coverage/*" \ + -not -path "./.git/*" \ + -exec sed -i '' 's/[[:space:]]*$//' {} \; + +echo "Whitespace fixed in all text files" \ No newline at end of file diff --git a/scripts/quality/pre-push-quick.sh b/scripts/quality/pre-push-quick.sh deleted file mode 100755 index 747adb3..0000000 --- a/scripts/quality/pre-push-quick.sh +++ /dev/null @@ -1,48 +0,0 @@ -#!/bin/bash - -# Quick pre-push script for non-UI changes -# Skips E2E tests for faster feedback - -set -e - -echo "๐Ÿš€ Running quick pre-push checks (no E2E)..." -echo "" - -# Color codes for output -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' # No Color - -# Function to handle errors -handle_error() { - echo -e "${RED}โŒ Pre-push checks failed!${NC}" - echo -e "${YELLOW}Fix the issues above before pushing.${NC}" - exit 1 -} - -# Trap errors -trap handle_error ERR - -# 1. Run format and lint checks -echo -e "${BLUE}๐Ÿ“ Running format and lint checks...${NC}" -npm run lint:fix -npm run check:whitespace - -# 2. Run TypeScript checks -echo -e "${BLUE}๐Ÿ” Running TypeScript checks...${NC}" -npm run typecheck - -# 3. Run unit tests -echo -e "${BLUE}๐Ÿงช Running unit tests...${NC}" -npm run test:unit - -# 4. Build check -echo -e "${BLUE}๐Ÿ“ฆ Running build check...${NC}" -npm run build - -echo "" -echo -e "${GREEN}โœ… Quick pre-push checks passed!${NC}" -echo -e "${YELLOW}โš ๏ธ Warning: E2E tests were skipped!${NC}" -echo -e "${YELLOW}Only use this for non-UI changes. Run 'npm run pre-push' for full checks.${NC}" \ No newline at end of file diff --git a/scripts/quality/pre-push.sh b/scripts/quality/pre-push.sh deleted file mode 100755 index 2b6c996..0000000 --- a/scripts/quality/pre-push.sh +++ /dev/null @@ -1,55 +0,0 @@ -#!/bin/bash - -# Pre-push script to run essential tests before pushing to GitHub -# This ensures code quality without slowing down CI - -set -e - -echo "๐Ÿš€ Running pre-push checks..." -echo "" - -# Color codes for output -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' # No Color - -# Function to handle errors -handle_error() { - echo -e "${RED}โŒ Pre-push checks failed!${NC}" - echo -e "${YELLOW}Fix the issues above before pushing.${NC}" - exit 1 -} - -# Trap errors -trap handle_error ERR - -# 1. Run format and lint checks -echo -e "${BLUE}๐Ÿ“ Running format and lint checks...${NC}" -npm run lint:fix -npm run check:whitespace - -# 2. Run TypeScript checks -echo -e "${BLUE}๐Ÿ” Running TypeScript checks...${NC}" -npm run typecheck - -# 3. Run unit tests -echo -e "${BLUE}๐Ÿงช Running unit tests...${NC}" -npm run test:unit - -# 4. Run E2E tests -echo -e "${BLUE}๐ŸŽญ Running E2E tests (locally)...${NC}" -echo -e "${YELLOW}This may take a few minutes...${NC}" -npm run test:e2e - -# 5. Build check -echo -e "${BLUE}๐Ÿ“ฆ Running build check...${NC}" -npm run build - -echo "" -echo -e "${GREEN}โœ… All pre-push checks passed!${NC}" -echo -e "${GREEN}You're ready to push to GitHub.${NC}" -echo "" -echo -e "${YELLOW}Note: E2E tests are disabled in CI for performance.${NC}" -echo -e "${YELLOW}Always run 'npm run pre-push' before pushing important changes.${NC}" \ No newline at end of file diff --git a/scripts/quality/test-ultra-minimal.js b/scripts/quality/test-ultra-minimal.js deleted file mode 100755 index 78339e5..0000000 --- a/scripts/quality/test-ultra-minimal.js +++ /dev/null @@ -1,181 +0,0 @@ -#!/usr/bin/env node - -/** - * This script tests the ultra-minimal demo with enhanced error tracking - */ - -import { spawn } from 'child_process'; -import puppeteer from 'puppeteer'; -import path from 'path'; -import fs from 'fs'; -import { ensureProjectRoot } from '../utils/ensure-project-root.mjs'; - -// Ensure we're in the project root -ensureProjectRoot('test-ultra-minimal.js'); - -// Create logs directory if it doesn't exist -const logsDir = path.join(process.cwd(), 'logs'); -if (!fs.existsSync(logsDir)) { - fs.mkdirSync(logsDir); -} - -// Log file for detailed console output -const logFile = path.join(logsDir, 'ultra-minimal-logs.log'); -const logStream = fs.createWriteStream(logFile, { flags: 'w' }); - -// Start the dev server -console.log('Starting dev server...'); -const devServer = spawn('npm', ['run', 'dev'], { - stdio: ['ignore', 'pipe', 'pipe'], - shell: true -}); - -// Log server output -devServer.stdout.on('data', (data) => { - const output = data.toString(); - process.stdout.write(output); - logStream.write(`[SERVER OUT] ${output}`); - - // Wait for the server to be ready - if (output.includes('Local:') && output.includes('http://localhost:')) { - console.log('Dev server started'); - - // Extract the URL from the output - const match = output.match(/http:\/\/localhost:\d+/); - if (!match) { - console.error('Could not determine dev server URL'); - process.exit(1); - } - - const url = match[0]; - console.log(`Testing app at ${url}`); - - // Wait a bit to make sure server is fully ready - setTimeout(async () => { - try { - // Launch browser with dev tools open for better error reporting - const browser = await puppeteer.launch({ - headless: 'new', - devtools: true, - args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage'] - }); - - const page = await browser.newPage(); - - // Capture all console messages in detail - page.on('console', msg => { - const type = msg.type(); - console.log(`BROWSER [${type}]: ${msg.text()}`); - logStream.write(`[BROWSER:${type}] ${msg.text()}\n`); - - // Log argument details for errors - if (type === 'error' || type === 'warning') { - msg.args().then(args => { - args.forEach((arg, i) => { - arg.jsonValue().then(val => { - try { - const detail = JSON.stringify(val, null, 2); - logStream.write(`[ARG ${i}] ${detail}\n`); - } catch (e) { - logStream.write(`[ARG ${i}] [Cannot stringify: ${e.message}]\n`); - } - }).catch(e => { - logStream.write(`[ARG ${i}] [Error getting value: ${e.message}]\n`); - }); - }); - }).catch(e => { - logStream.write(`[ERROR ARGS] [Error getting args: ${e.message}]\n`); - }); - } - }); - - // Capture uncaught exceptions - page.on('pageerror', err => { - console.error(`BROWSER EXCEPTION: ${err.message}`); - logStream.write(`[PAGE ERROR] ${err.message}\n${err.stack || ''}\n`); - }); - - // Navigate to the app with increased timeout - try { - await page.goto(url, { - waitUntil: 'networkidle2', - timeout: 60000 - }); - - console.log('Page loaded, waiting for potential delayed errors...'); - - // Wait longer to ensure all potential errors are captured - await new Promise(resolve => setTimeout(resolve, 10000)); - - // Take a screenshot - await page.screenshot({ path: './ultra-minimal-screenshot.png', fullPage: true }); - console.log('Screenshot saved to ./ultra-minimal-screenshot.png'); - - // Check what was rendered - const renderedContent = await page.evaluate(() => { - const rootElement = document.getElementById('root'); - const gridElement = document.querySelector('.ag-root-wrapper'); - - return { - rootHasChildren: rootElement && rootElement.children.length > 0, - gridRendered: !!gridElement, - bodyHTML: document.body.innerHTML.substring(0, 500) + '...' // First 500 chars - }; - }); - - console.log('\n===== Render Results ====='); - console.log(`Root has children: ${renderedContent.rootHasChildren}`); - console.log(`AG Grid rendered: ${renderedContent.gridRendered}`); - console.log('===========================\n'); - - logStream.write('\n[RENDER RESULTS]\n'); - logStream.write(`Root has children: ${renderedContent.rootHasChildren}\n`); - logStream.write(`AG Grid rendered: ${renderedContent.gridRendered}\n`); - logStream.write(`Body HTML snippet: ${renderedContent.bodyHTML}\n`); - - // All done - await browser.close(); - console.log('Test completed.'); - console.log(`Full logs written to: ${logFile}`); - - devServer.kill(); - process.exit(0); - } catch (error) { - console.error(`Navigation error: ${error.message}`); - logStream.write(`[NAVIGATION ERROR] ${error.message}\n${error.stack}\n`); - - await browser.close(); - devServer.kill(); - process.exit(1); - } - } catch (error) { - console.error(`Browser launch error: ${error.message}`); - logStream.write(`[BROWSER LAUNCH ERROR] ${error.message}\n${error.stack}\n`); - - devServer.kill(); - process.exit(1); - } - }, 3000); // Wait 3 seconds before starting browser - } -}); - -// Capture and log server errors -devServer.stderr.on('data', (data) => { - const error = data.toString(); - console.error(`SERVER ERROR: ${error}`); - logStream.write(`[SERVER ERROR] ${error}\n`); -}); - -// Kill everything if it takes too long -setTimeout(() => { - console.error('Timeout waiting for test to complete'); - logStream.end(); - - try { - devServer.kill(); - } catch (e) { - console.error(`Error killing dev server: ${e.message}`); - } - - process.exit(1); -}, 60000); \ No newline at end of file diff --git a/scripts/quality/validate-demo.js b/scripts/quality/validate-demo.js deleted file mode 100755 index 67d3053..0000000 --- a/scripts/quality/validate-demo.js +++ /dev/null @@ -1,133 +0,0 @@ -#!/usr/bin/env node - -/** - * This script runs a quick validation to ensure the demo works correctly. - * It starts the dev server, uses Puppeteer to check the page, and looks for errors. - */ - -import { spawn } from "child_process"; -import puppeteer from "puppeteer"; -import { ensureProjectRoot } from '../utils/ensure-project-root.mjs'; - -// Ensure we're in the project root -ensureProjectRoot('validate-demo.js'); - -// Flag to check if we found errors -let hasErrors = false; - -// Start the dev server -console.log("Starting dev server..."); -const devServer = spawn("npm", ["run", "dev"], { - stdio: ["ignore", "pipe", "pipe"], - shell: true, -}); - -// Store any errors that happen in the console -let consoleErrors = []; - -// Listen for page load -devServer.stdout.on("data", async (data) => { - const output = data.toString(); - - // Wait for the server to be ready - if (output.includes("Local:") && output.includes("http://localhost:")) { - console.log("Dev server started"); - - // Extract the URL from the output - const match = output.match(/http:\/\/localhost:\d+/); - if (!match) { - console.error("Could not determine dev server URL"); - process.exit(1); - } - - const url = match[0]; - console.log(`Testing demo at ${url}`); - - try { - // Launch browser and navigate to the page - const browser = await puppeteer.launch(); - const page = await browser.newPage(); - - // Log errors and warnings from the page - page.on("console", (msg) => { - const errorText = msg.text(); - - // Log all console messages for debugging - console.log(`Browser console [${msg.type()}]: ${errorText}`); - - if ( - msg.type() === "error" || - errorText.includes("Uncaught") || - errorText.includes("does not provide an export") || - errorText.includes("Cannot find") || - errorText.includes("is not defined") - ) { - consoleErrors.push(errorText); - console.error(`Browser console error: ${errorText}`); - hasErrors = true; - } - }); - - // Navigate to dev server - await page.goto(url, { waitUntil: "networkidle2" }); - - // Wait a bit longer for the grid to render - await new Promise((resolve) => setTimeout(resolve, 2000)); - - // Take a screenshot for debugging - await page.screenshot({ path: "./demo-screenshot.png" }); - - // Skip page rendering check in headless browser - // Instead, just check for critical errors - const pageRendered = true; - console.log( - "Skipping detailed page rendering check - please verify manually", - ); - console.log("Success: Page should load correctly in a real browser"); - - // Take a screenshot for manual inspection - await page.screenshot({ path: "./demo-screenshot.png", fullPage: true }); - console.log( - "Screenshot saved to ./demo-screenshot.png for manual inspection", - ); - - // Check if specific AG Grid errors appeared - const hasAgGridErrors = consoleErrors.some( - (error) => - error.includes("AG Grid: error") || - error.includes("does not provide an export named"), - ); - - if (hasAgGridErrors) { - console.error("Error: AG Grid errors detected in console"); - hasErrors = true; - } else { - console.log("Success: No AG Grid errors in console"); - } - - // Close browser - await browser.close(); - - // Kill dev server and exit - devServer.kill(); - - if (hasErrors) { - process.exit(1); - } else { - console.log("Demo validation successful"); - process.exit(0); - } - } catch (error) { - console.error("Error running browser tests:", error); - devServer.kill(); - process.exit(1); - } - } -}); - -// Set a timeout to kill the server after a while -setTimeout(() => { - console.error("Timeout waiting for dev server"); - devServer.kill(); - process.exit(1); -}, 60000); diff --git a/scripts/setup-branch-protection.sh b/scripts/setup-branch-protection.sh deleted file mode 100755 index 2b2f78b..0000000 --- a/scripts/setup-branch-protection.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/bin/bash - -echo "๐Ÿ”’ Setting up branch protection for main branch" - -# Set up branch protection with minimal required checks -gh api repos/ryanrozich/ag-grid-react-components/branches/main/protection \ - --method PUT \ - --field required_status_checks[strict]=false \ - --field required_status_checks[checks][][context]="Tests" \ - --field required_status_checks[checks][][context]="Build" \ - --field required_status_checks[checks][][context]="Code Quality" \ - --field enforce_admins=false \ - --field required_pull_request_reviews=null \ - --field restrictions=null \ - --field allow_force_pushes=false \ - --field allow_deletions=false \ - --field required_conversation_resolution=true \ - --field lock_branch=false \ - --field allow_fork_syncing=true - -echo "โœ… Branch protection enabled with:" -echo " - Required: Tests, Build, Code Quality" -echo " - Enforce for admins: No (you can override if needed)" -echo " - Require conversation resolution: Yes" -echo " - Allow force pushes: No" - -echo "" -echo "๐Ÿ’ก Optional checks that won't block:" -echo " - E2E Tests" -echo " - CodeQL" -echo " - Deploy Preview" -echo " - Publish Preview" \ No newline at end of file diff --git a/scripts/setup/bootstrap-all.sh b/scripts/setup/bootstrap-all.sh deleted file mode 100755 index 89de289..0000000 --- a/scripts/setup/bootstrap-all.sh +++ /dev/null @@ -1,44 +0,0 @@ -#!/bin/bash - -echo "๐Ÿš€ Complete Project Bootstrap" -echo "============================" -echo "" -echo "This will:" -echo "1. Add missing required labels to all issues" -echo "2. Sync project field values to issue labels" -echo "3. Trigger the GitHub Actions for final sync" -echo "" -echo "Press Ctrl+C to cancel, or Enter to continue..." -read - -# Step 1: Add missing labels -echo "" -echo "๐Ÿ“ Step 1: Adding missing required labels..." -echo "โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€" -node scripts/automation/add-missing-labels.js - -# Step 2: Sync from project to labels -echo "" -echo "๐Ÿ”„ Step 2: Syncing project fields to labels..." -echo "โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€" -node scripts/automation/bootstrap-project-sync.js - -# Step 3: Trigger GitHub Action -echo "" -echo "โš™๏ธ Step 3: Triggering labels-to-project sync..." -echo "โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€" -gh workflow run sync-labels-to-project.yml || echo "Note: Workflow may already be running" - -# Step 4: Also trigger the reverse sync -echo "" -echo "โš™๏ธ Step 4: Triggering project-to-labels sync..." -echo "โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€" -gh workflow run sync-project-to-labels.yml || echo "Note: Workflow may already be running" - -echo "" -echo "โœ… Bootstrap complete!" -echo "" -echo "๐Ÿ“Š You can monitor the workflows at:" -echo " https://github.com/ryanrozich/ag-grid-react-components/actions" -echo "" -echo "๐Ÿ”„ The automated sync will continue running every 5 minutes." \ No newline at end of file diff --git a/scripts/setup/check-branch-protection.sh b/scripts/setup/check-branch-protection.sh deleted file mode 100755 index 620ced4..0000000 --- a/scripts/setup/check-branch-protection.sh +++ /dev/null @@ -1,34 +0,0 @@ -#!/bin/bash - -echo "๐Ÿ” Checking branch protection for main branch" -echo "" - -# Check if protection exists -protection=$(gh api repos/ryanrozich/ag-grid-react-components/branches/main/protection 2>&1) - -if [[ $protection == *"404"* ]]; then - echo "โŒ No branch protection enabled" - echo "" - echo "๐Ÿ’ก To enable protection, run:" - echo " ./scripts/setup-branch-protection.sh" -else - echo "โœ… Branch protection is enabled" - echo "" - echo "Required status checks:" - gh api repos/ryanrozich/ag-grid-react-components/branches/main/protection \ - --jq '.required_status_checks.checks[] | " - " + .context' 2>/dev/null || echo " None" - - echo "" - echo "Settings:" - gh api repos/ryanrozich/ag-grid-react-components/branches/main/protection --jq ' - " - Enforce for admins: " + (.enforce_admins.enabled | tostring) + "\n" + - " - Require PR reviews: " + (if .required_pull_request_reviews then "Yes" else "No" end) + "\n" + - " - Allow force pushes: " + (.allow_force_pushes.enabled | tostring) + "\n" + - " - Require conversation resolution: " + (.required_conversation_resolution.enabled | tostring) - ' 2>/dev/null || echo " Unable to read settings" -fi - -echo "" -echo "๐Ÿ“ To modify protection:" -echo " - Add checks: Edit scripts/setup-branch-protection.sh" -echo " - Disable: gh api repos/ryanrozich/ag-grid-react-components/branches/main/protection --method DELETE" \ No newline at end of file diff --git a/scripts/setup/setup-branch-protection.sh b/scripts/setup/setup-branch-protection.sh deleted file mode 100755 index 2b2f78b..0000000 --- a/scripts/setup/setup-branch-protection.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/bin/bash - -echo "๐Ÿ”’ Setting up branch protection for main branch" - -# Set up branch protection with minimal required checks -gh api repos/ryanrozich/ag-grid-react-components/branches/main/protection \ - --method PUT \ - --field required_status_checks[strict]=false \ - --field required_status_checks[checks][][context]="Tests" \ - --field required_status_checks[checks][][context]="Build" \ - --field required_status_checks[checks][][context]="Code Quality" \ - --field enforce_admins=false \ - --field required_pull_request_reviews=null \ - --field restrictions=null \ - --field allow_force_pushes=false \ - --field allow_deletions=false \ - --field required_conversation_resolution=true \ - --field lock_branch=false \ - --field allow_fork_syncing=true - -echo "โœ… Branch protection enabled with:" -echo " - Required: Tests, Build, Code Quality" -echo " - Enforce for admins: No (you can override if needed)" -echo " - Require conversation resolution: Yes" -echo " - Allow force pushes: No" - -echo "" -echo "๐Ÿ’ก Optional checks that won't block:" -echo " - E2E Tests" -echo " - CodeQL" -echo " - Deploy Preview" -echo " - Publish Preview" \ No newline at end of file diff --git a/scripts/sync-agent-status-to-project.js b/scripts/sync-agent-status-to-project.js deleted file mode 100755 index 155dcfb..0000000 --- a/scripts/sync-agent-status-to-project.js +++ /dev/null @@ -1,206 +0,0 @@ -#!/usr/bin/env node - -/** - * Syncs agent status labels to GitHub Project fields - * This script maps agent:* labels to a custom "Agent Status" field in the project - */ - -import { execSync } from 'child_process'; - -// Configuration -const PROJECT_NUMBER = 1; // Your project number -const FIELD_NAME = 'Agent Status'; - -// Agent status mapping -const AGENT_STATUS_MAP = { - 'agent:todo': 'Todo', - 'agent:wip': 'In Progress', - 'agent:needs-review': 'Needs Review', - 'agent:done': 'Done', - 'agent:error': 'Error' -}; - -async function syncAgentStatus() { - try { - console.log('๐Ÿ”„ Syncing agent status to GitHub Project...\n'); - - // Get project ID - const projectData = JSON.parse( - execSync(`gh api graphql -f query=' - query { - viewer { - projectV2(number: ${PROJECT_NUMBER}) { - id - fields(first: 20) { - nodes { - ... on ProjectV2SingleSelectField { - id - name - options { - id - name - } - } - } - } - } - } - } - '`, { encoding: 'utf8' }) - ); - - const project = projectData.data.viewer.projectV2; - if (!project) { - throw new Error(`Project #${PROJECT_NUMBER} not found`); - } - - // Find the Agent Status field - let agentStatusField = project.fields.nodes.find(field => field.name === FIELD_NAME); - - if (!agentStatusField) { - console.log(`๐Ÿ“ Creating "${FIELD_NAME}" field...`); - - // Create the field with options - const createFieldResult = JSON.parse( - execSync(`gh api graphql -f query=' - mutation { - createProjectV2Field(input: { - projectId: "${project.id}" - dataType: SINGLE_SELECT - name: "${FIELD_NAME}" - singleSelectOptions: [ - { name: "Todo", color: GRAY, description: "Ready for bot work" } - { name: "In Progress", color: YELLOW, description: "Bot actively working" } - { name: "Needs Review", color: BLUE, description: "PR ready for review" } - { name: "Done", color: GREEN, description: "Work completed" } - { name: "Error", color: RED, description: "Bot encountered issues" } - ] - }) { - projectV2Field { - ... on ProjectV2SingleSelectField { - id - name - options { - id - name - } - } - } - } - } - '`, { encoding: 'utf8' }) - ); - - agentStatusField = createFieldResult.data.createProjectV2Field.projectV2Field; - console.log(`โœ… Created "${FIELD_NAME}" field\n`); - } - - // Get all open issues with agent labels - const issues = JSON.parse( - execSync(`gh issue list --json number,labels,projectItems --limit 100`, { encoding: 'utf8' }) - ); - - let updatedCount = 0; - let skippedCount = 0; - - for (const issue of issues) { - // Find agent label - const agentLabel = issue.labels.find(label => label.name.startsWith('agent:')); - - if (!agentLabel) { - continue; // No agent label, skip - } - - const statusValue = AGENT_STATUS_MAP[agentLabel.name]; - if (!statusValue) { - console.warn(`โš ๏ธ Unknown agent label: ${agentLabel.name}`); - continue; - } - - // Find the option ID for this status - const option = agentStatusField.options.find(opt => opt.name === statusValue); - if (!option) { - console.error(`โŒ Option "${statusValue}" not found in field`); - continue; - } - - // Check if issue is in the project - const projectItem = issue.projectItems?.find(item => item.project.number === PROJECT_NUMBER); - - if (!projectItem) { - console.log(`โž• Adding issue #${issue.number} to project...`); - - // Add to project first - const addResult = JSON.parse( - execSync(`gh api graphql -f query=' - mutation { - addProjectV2ItemById(input: { - projectId: "${project.id}" - contentId: "${issue.id}" - }) { - item { - id - } - } - } - '`, { encoding: 'utf8' }) - ); - - const itemId = addResult.data.addProjectV2ItemById.item.id; - - // Update the field - execSync(`gh api graphql -f query=' - mutation { - updateProjectV2ItemFieldValue(input: { - projectId: "${project.id}" - itemId: "${itemId}" - fieldId: "${agentStatusField.id}" - value: { singleSelectOptionId: "${option.id}" } - }) { - projectV2Item { - id - } - } - } - '`); - - console.log(`โœ… Issue #${issue.number}: Added to project with status "${statusValue}"`); - updatedCount++; - } else { - // Update existing item - execSync(`gh api graphql -f query=' - mutation { - updateProjectV2ItemFieldValue(input: { - projectId: "${project.id}" - itemId: "${projectItem.id}" - fieldId: "${agentStatusField.id}" - value: { singleSelectOptionId: "${option.id}" } - }) { - projectV2Item { - id - } - } - } - '`); - - console.log(`โœ… Issue #${issue.number}: Updated agent status to "${statusValue}"`); - updatedCount++; - } - } - - console.log(`\n๐Ÿ“Š Summary:`); - console.log(` Updated: ${updatedCount} issues`); - console.log(` Skipped: ${skippedCount} issues`); - console.log(`\nโœ… Agent status sync complete!`); - - } catch (error) { - console.error('โŒ Error:', error.message); - if (error.message.includes('GraphQL')) { - console.error('\nMake sure you have the required permissions to manage projects.'); - } - process.exit(1); - } -} - -// Run the sync -syncAgentStatus(); \ No newline at end of file diff --git a/scripts/sync-issue-status.js b/scripts/sync-issue-status.js deleted file mode 100755 index 4f3a239..0000000 --- a/scripts/sync-issue-status.js +++ /dev/null @@ -1,147 +0,0 @@ -#!/usr/bin/env node - -/** - * Sync issue status based on their state and associated PRs - * - All issues should be at least in backlog (not needs-triage) - * - Issues with PRs should reflect PR status - */ - -import { execSync } from 'child_process'; - -console.log('๐Ÿ”„ Syncing issue statuses...\n'); - -// Get all open issues -const issuesJson = execSync('gh issue list --state all --limit 200 --json number,state,labels', { encoding: 'utf8' }); -const issues = JSON.parse(issuesJson); - -// Get all PRs -const prsJson = execSync('gh pr list --state all --limit 200 --json number,state,labels,body,title', { encoding: 'utf8' }); -const prs = JSON.parse(prsJson); - -// Create map of issue -> PRs -const issueToPRs = new Map(); - -// Find PRs that reference issues -prs.forEach(pr => { - const references = []; - - // Check PR body for issue references - const body = pr.body || ''; - const title = pr.title || ''; - const fullText = `${title} ${body}`; - - // Match issue references - const patterns = [ - /(?:fixes?|closes?|resolves?|fix|close|resolve)\s+#(\d+)/gi, - /(?:fixes?|closes?|resolves?|fix|close|resolve)\s+(?:https?:\/\/github\.com\/[^\/]+\/[^\/]+\/issues\/)(\d+)/gi - ]; - - patterns.forEach(pattern => { - let match; - while ((match = pattern.exec(fullText)) !== null) { - references.push(parseInt(match[1])); - } - }); - - // Add PR to each referenced issue - references.forEach(issueNum => { - if (!issueToPRs.has(issueNum)) { - issueToPRs.set(issueNum, []); - } - issueToPRs.get(issueNum).push(pr); - }); -}); - -// Helper to get current status label -function getCurrentStatus(labels) { - const statusLabel = labels.find(l => l.name.startsWith('status:')); - return statusLabel ? statusLabel.name : null; -} - -// Helper to update status -async function updateStatus(issueNumber, oldStatus, newStatus) { - try { - // Remove old status if exists - if (oldStatus) { - execSync(`gh issue edit ${issueNumber} --remove-label "${oldStatus}"`, { stdio: 'pipe' }); - } - - // Add new status - execSync(`gh issue edit ${issueNumber} --add-label "${newStatus}"`, { stdio: 'pipe' }); - - console.log(` โœ“ #${issueNumber}: ${oldStatus || 'no status'} โ†’ ${newStatus}`); - return true; - } catch (error) { - console.error(` โœ— #${issueNumber}: Failed to update status`); - return false; - } -} - -let updated = 0; - -// Process each issue -for (const issue of issues) { - // Skip closed/done issues - if (issue.state === 'CLOSED' || issue.labels.some(l => l.name === 'status: done')) { - continue; - } - - const currentStatus = getCurrentStatus(issue.labels); - let targetStatus = null; - - // Check if issue has associated PRs - const associatedPRs = issueToPRs.get(issue.number) || []; - - if (associatedPRs.length > 0) { - // Determine status based on PR states - const openPRs = associatedPRs.filter(pr => pr.state === 'OPEN'); - const mergedPRs = associatedPRs.filter(pr => pr.state === 'MERGED'); - - if (mergedPRs.length > 0) { - // If any PR is merged, issue should be done - targetStatus = 'status: done'; - } else if (openPRs.length > 0) { - // Check PR status labels - const prStatuses = openPRs.map(pr => getCurrentStatus(pr.labels)).filter(Boolean); - - if (prStatuses.includes('status: code-review-complete')) { - targetStatus = 'status: in-product-review'; - } else if (prStatuses.includes('status: in-code-review')) { - targetStatus = 'status: in-product-review'; - } else if (prStatuses.includes('status: pr-in-progress')) { - targetStatus = 'status: in-progress'; - } else { - // PR exists but no status, assume in progress - targetStatus = 'status: in-progress'; - } - } - } else { - // No PRs - ensure at least backlog status - if (currentStatus === 'status: needs-triage' || !currentStatus) { - targetStatus = 'status: backlog'; - } - } - - // Update if needed - if (targetStatus && targetStatus !== currentStatus) { - if (await updateStatus(issue.number, currentStatus, targetStatus)) { - updated++; - } - } -} - -console.log('\n' + 'โ”€'.repeat(50)); -console.log(`โœ… Updated ${updated} issue statuses`); - -// Show summary -console.log('\n๐Ÿ“Š Status Summary:'); -const statusCounts = {}; -const updatedIssues = JSON.parse(execSync('gh issue list --state all --limit 200 --json labels', { encoding: 'utf8' })); -updatedIssues.forEach(issue => { - const status = getCurrentStatus(issue.labels) || 'no status'; - statusCounts[status] = (statusCounts[status] || 0) + 1; -}); - -Object.entries(statusCounts).sort().forEach(([status, count]) => { - console.log(` ${status}: ${count}`); -}); \ No newline at end of file diff --git a/scripts/sync-labels-to-project.js b/scripts/sync-labels-to-project.js deleted file mode 100755 index f8b4eb0..0000000 --- a/scripts/sync-labels-to-project.js +++ /dev/null @@ -1,265 +0,0 @@ -#!/usr/bin/env node - -/** - * Manual sync script to update project fields based on issue labels - * This does what the GitHub Action will do once it's on main branch - * - * Usage: GITHUB_TOKEN=ghp_xxx node scripts/sync-labels-to-project.js - */ - -import { execSync } from 'child_process'; - -const GITHUB_TOKEN = process.env.GITHUB_TOKEN || execSync('gh auth token').toString().trim(); -const OWNER = 'ryanrozich'; -const REPO = 'ag-grid-react-components'; - -// Label to field mappings (reverse of what bootstrap does) -const labelMappings = { - // Priority - 'priority: critical': { field: 'Priority', value: '๐Ÿ”ด Critical' }, - 'priority: high': { field: 'Priority', value: '๐ŸŸ  High' }, - 'priority: medium': { field: 'Priority', value: '๐ŸŸก Medium' }, - 'priority: low': { field: 'Priority', value: '๐ŸŸข Low' }, - - // Area - 'area: components': { field: 'Area', value: '๐Ÿงฉ Components' }, - 'area: demo': { field: 'Area', value: '๐ŸŽจ Demo' }, - 'area: build': { field: 'Area', value: '๐Ÿ”จ Build' }, - 'area: ci/cd': { field: 'Area', value: '๐Ÿค– CI/CD' }, - 'area: testing': { field: 'Area', value: '๐Ÿงช Testing' }, - 'area: docs': { field: 'Area', value: '๐Ÿ“š Docs' }, - - // Type - 'bug': { field: 'Type', value: '๐Ÿ› Bug' }, - 'enhancement': { field: 'Type', value: 'โœจ Feature' }, - 'documentation': { field: 'Type', value: '๐Ÿ“š Documentation' }, - - // Status - 'status: needs-triage': { field: 'Status', value: '๐Ÿ“ฅ Needs Triage' }, - 'status: triaging': { field: 'Status', value: '๐Ÿ” Triaging' }, - 'status: backlog': { field: 'Status', value: '๐Ÿ“‹ Backlog' }, - 'status: in-progress': { field: 'Status', value: '๐Ÿšง In Progress' }, - 'status: in-product-review': { field: 'Status', value: '๐Ÿ‘€ In Product Review' }, - 'status: done': { field: 'Status', value: 'โœ… Done' }, - // PR statuses - 'status: pr-in-progress': { field: 'Status', value: '๐Ÿ”จ PR In Progress' }, - 'status: in-code-review': { field: 'Status', value: '๐Ÿ‘จโ€๐Ÿ’ป In Code Review' }, - 'status: code-review-complete': { field: 'Status', value: '๐ŸŽ‰ Code Review Complete' }, - 'status: merged': { field: 'Status', value: '๐Ÿš€ Merged' }, - - // Effort - 'effort: xs': { field: 'Effort', value: 'XS (< 1 hour)' }, - 'effort: s': { field: 'Effort', value: 'S (1-4 hours)' }, - 'effort: m': { field: 'Effort', value: 'M (1-2 days)' }, - 'effort: l': { field: 'Effort', value: 'L (3-5 days)' }, - 'effort: xl': { field: 'Effort', value: 'XL (1+ week)' }, - - // Components - 'component: date-filter': { field: 'Component', value: 'DateFilter' }, - 'component: quick-filter-dropdown': { field: 'Component', value: 'QuickFilterDropdown' }, - 'component: active-filters': { field: 'Component', value: 'ActiveFilters' }, - 'component: relative-date-filter': { field: 'Component', value: 'RelativeDateFilter' }, - 'component: grid-state-utils': { field: 'Component', value: 'Grid State Utils' }, - 'component: demo-app': { field: 'Component', value: 'Demo App' } -}; - -async function graphqlRequest(query, variables = {}) { - const response = await fetch('https://api.github.com/graphql', { - method: 'POST', - headers: { - 'Authorization': `Bearer ${GITHUB_TOKEN}`, - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ query, variables }) - }); - - const data = await response.json(); - if (data.errors) { - console.error('GraphQL errors:', data.errors); - throw new Error('GraphQL request failed'); - } - return data.data; -} - -async function main() { - console.log('๐Ÿ”„ Syncing Labels to Project Fields\n'); - - // Get all issues with their labels - const issuesQuery = ` - query($owner: String!, $repo: String!) { - repository(owner: $owner, name: $repo) { - issues(first: 100, states: OPEN) { - nodes { - id - number - title - labels(first: 100) { - nodes { - name - } - } - } - } - } - } - `; - - const issuesData = await graphqlRequest(issuesQuery, { owner: OWNER, repo: REPO }); - const issues = issuesData.repository.issues.nodes; - - // Get project info - const projectQuery = ` - query($owner: String!, $repo: String!) { - repository(owner: $owner, name: $repo) { - projectsV2(first: 10) { - nodes { - id - title - field(name: "Status") { - ... on ProjectV2SingleSelectField { - id - options { - id - name - } - } - } - items(first: 100) { - nodes { - id - content { - ... on Issue { - number - } - } - } - } - } - } - } - } - `; - - const projectData = await graphqlRequest(projectQuery, { owner: OWNER, repo: REPO }); - const project = projectData.repository.projectsV2.nodes[0]; - - if (!project) { - console.error('No project found!'); - return; - } - - console.log(`Found project: ${project.title}\n`); - - // Create a map of issue number to project item ID - const issueToItemMap = {}; - project.items.nodes.forEach(item => { - if (item.content && item.content.number) { - issueToItemMap[item.content.number] = item.id; - } - }); - - // Get all field IDs - const fieldsQuery = ` - query($projectId: ID!) { - node(id: $projectId) { - ... on ProjectV2 { - fields(first: 20) { - nodes { - ... on ProjectV2Field { - id - name - } - ... on ProjectV2SingleSelectField { - id - name - options { - id - name - } - } - } - } - } - } - } - `; - - const fieldsData = await graphqlRequest(fieldsQuery, { projectId: project.id }); - const fields = fieldsData.node.fields.nodes; - - // Create field maps - const fieldMap = {}; - const optionMap = {}; - - fields.forEach(field => { - fieldMap[field.name] = field.id; - if (field.options) { - optionMap[field.name] = {}; - field.options.forEach(option => { - optionMap[field.name][option.name] = option.id; - }); - } - }); - - // Process each issue - let updatedCount = 0; - - for (const issue of issues) { - const itemId = issueToItemMap[issue.number]; - if (!itemId) continue; - - console.log(`\n#${issue.number}: ${issue.title}`); - - const labels = issue.labels.nodes.map(l => l.name); - - // Check each label - for (const label of labels) { - const mapping = labelMappings[label]; - if (!mapping) continue; - - const fieldId = fieldMap[mapping.field]; - const optionId = optionMap[mapping.field]?.[mapping.value]; - - if (!fieldId || !optionId) { - console.log(` โš ๏ธ Cannot map ${label} - field or option not found`); - continue; - } - - // Update the field - const updateMutation = ` - mutation($projectId: ID!, $itemId: ID!, $fieldId: ID!, $optionId: String!) { - updateProjectV2ItemFieldValue(input: { - projectId: $projectId - itemId: $itemId - fieldId: $fieldId - value: { - singleSelectOptionId: $optionId - } - }) { - projectV2Item { - id - } - } - } - `; - - try { - await graphqlRequest(updateMutation, { - projectId: project.id, - itemId: itemId, - fieldId: fieldId, - optionId: optionId - }); - console.log(` โœ“ Updated ${mapping.field} โ†’ ${mapping.value}`); - updatedCount++; - } catch (error) { - console.error(` โœ— Failed to update ${mapping.field}: ${error.message}`); - } - } - } - - console.log('\n' + 'โ•'.repeat(50)); - console.log(`โœ… Sync Complete! Updated ${updatedCount} field values.`); -} - -main().catch(console.error); \ No newline at end of file diff --git a/scripts/sync-pr-labels-from-issues.js b/scripts/sync-pr-labels-from-issues.js deleted file mode 100755 index 2804dc2..0000000 --- a/scripts/sync-pr-labels-from-issues.js +++ /dev/null @@ -1,133 +0,0 @@ -#!/usr/bin/env node - -/** - * Script to sync PR labels from their linked issues - * This copies labels from issues to PRs that reference them - * - * Usage: node scripts/sync-pr-labels-from-issues.js - */ - -import { execSync } from 'child_process'; - -// Helper function to extract issue numbers from PR body -function extractIssueNumbers(body) { - if (!body) return []; - - // Match various linking patterns - const patterns = [ - /(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)\s*#(\d+)/gi, - /(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)\s*https:\/\/github\.com\/[^\/]+\/[^\/]+\/issues\/(\d+)/gi - ]; - - const numbers = new Set(); - for (const pattern of patterns) { - const matches = body.matchAll(pattern); - for (const match of matches) { - numbers.add(parseInt(match[1])); - } - } - - return Array.from(numbers); -} - -async function main() { - console.log('๐Ÿ”„ Syncing PR Labels from Linked Issues\n'); - - // Get all PRs (open and closed) - let allPRs = []; - - try { - // Get open PRs - const openPRsJson = execSync('gh pr list --state open --limit 100 --json number,title,body,labels', { - encoding: 'utf8' - }); - const openPRs = JSON.parse(openPRsJson); - - // Get closed PRs - const closedPRsJson = execSync('gh pr list --state closed --limit 100 --json number,title,body,labels', { - encoding: 'utf8' - }); - const closedPRs = JSON.parse(closedPRsJson); - - allPRs = [...openPRs, ...closedPRs]; - } catch (error) { - console.error('Failed to fetch PRs:', error.message); - process.exit(1); - } - - console.log(`Found ${allPRs.length} PRs to process\n`); - - let updatedCount = 0; - - for (const pr of allPRs) { - const linkedIssues = extractIssueNumbers(pr.body); - - if (linkedIssues.length === 0) { - continue; // Skip PRs without linked issues - } - - console.log(`\nPR #${pr.number}: ${pr.title}`); - console.log(` Linked issues: ${linkedIssues.join(', ')}`); - - // Get current PR labels - const currentLabels = new Set(pr.labels.map(l => l.name)); - - // Collect all labels from linked issues (excluding status labels) - const allLabels = new Set(); - - for (const issueNum of linkedIssues) { - try { - const issueJson = execSync(`gh issue view ${issueNum} --json labels`, { - encoding: 'utf8' - }); - const issue = JSON.parse(issueJson); - - issue.labels.forEach(label => { - // Skip status labels - PRs have their own lifecycle - if (!label.name.startsWith('status:')) { - allLabels.add(label.name); - } - }); - - console.log(` Issue #${issueNum} labels: ${issue.labels.map(l => l.name).join(', ')}`); - } catch (error) { - console.error(` โœ— Failed to fetch issue #${issueNum}: ${error.message}`); - } - } - - // Find labels to add (ones from issues that PR doesn't have) - const labelsToAdd = []; - for (const label of allLabels) { - if (!currentLabels.has(label)) { - labelsToAdd.push(label); - } - } - - // Apply new labels - if (labelsToAdd.length > 0) { - const addCommand = labelsToAdd.map(l => `--add-label "${l}"`).join(' '); - - try { - execSync(`gh pr edit ${pr.number} ${addCommand}`, { - stdio: 'pipe', - encoding: 'utf8' - }); - console.log(` โœ“ Added labels: ${labelsToAdd.join(', ')}`); - updatedCount++; - } catch (error) { - console.error(` โœ— Failed to update PR: ${error.message}`); - } - } else { - console.log(' โœ“ Already has all issue labels'); - } - } - - console.log('\n' + 'โ”€'.repeat(50)); - console.log(`โœ… Complete! Updated ${updatedCount} PRs.`); - - if (updatedCount === 0) { - console.log(' All PRs already have labels from their linked issues.'); - } -} - -main().catch(console.error); \ No newline at end of file diff --git a/scripts/test-code-block-fonts.js b/scripts/test-code-block-fonts.js deleted file mode 100644 index 222a9a0..0000000 --- a/scripts/test-code-block-fonts.js +++ /dev/null @@ -1,128 +0,0 @@ -import puppeteer from "puppeteer"; - -const testCodeBlockFonts = async () => { - console.log("๐Ÿ” Testing code block font rendering..."); - - const browser = await puppeteer.launch({ - headless: false, // Set to true for CI - defaultViewport: { width: 1280, height: 800 }, - }); - - try { - const page = await browser.newPage(); - - // Go to the demo page - await page.goto("http://localhost:5173", { - waitUntil: "networkidle0", - timeout: 30000, - }); - - // Wait for code blocks to render - await page.waitForSelector("pre code", { timeout: 10000 }); - - // Get computed styles for all code elements - const codeBlockStyles = await page.evaluate(() => { - const codeElements = document.querySelectorAll("pre code"); - const results = []; - - codeElements.forEach((element, index) => { - const computedStyle = window.getComputedStyle(element); - const preStyle = window.getComputedStyle(element.closest("pre")); - - results.push({ - index, - code: { - fontFamily: computedStyle.fontFamily, - fontSize: computedStyle.fontSize, - fontWeight: computedStyle.fontWeight, - display: computedStyle.display, - }, - pre: { - fontFamily: preStyle.fontFamily, - fontSize: preStyle.fontSize, - fontWeight: preStyle.fontWeight, - }, - // Get the actual rendered font - actualFont: (() => { - // This is a trick to detect the actual rendered font - const canvas = document.createElement("canvas"); - const context = canvas.getContext("2d"); - context.font = computedStyle.font; - return context.font; - })(), - // Check if any parent has overriding styles - parentStyles: (() => { - let parent = element.parentElement; - const overrides = []; - while (parent && parent !== document.body) { - const style = window.getComputedStyle(parent); - if (style.fontFamily && style.fontFamily !== "inherit") { - overrides.push({ - element: parent.tagName, - className: parent.className, - fontFamily: style.fontFamily, - }); - } - parent = parent.parentElement; - } - return overrides; - })(), - }); - }); - - return results; - }); - - console.log("\n๐Ÿ“Š Code Block Font Analysis:"); - codeBlockStyles.forEach((style, index) => { - console.log(`\n๐Ÿ“ฆ Code Block ${index + 1}:`); - console.log(" Code element:"); - console.log(` Font Family: ${style.code.fontFamily}`); - console.log(` Font Size: ${style.code.fontSize}`); - console.log(` Font Weight: ${style.code.fontWeight}`); - console.log(` Display: ${style.code.display}`); - console.log(" Pre element:"); - console.log(` Font Family: ${style.pre.fontFamily}`); - console.log(` Font Size: ${style.pre.fontSize}`); - console.log(" Actual rendered font:", style.actualFont); - if (style.parentStyles.length > 0) { - console.log(" โš ๏ธ Parent style overrides found:"); - style.parentStyles.forEach((parent) => { - console.log( - ` ${parent.element}.${parent.className}: ${parent.fontFamily}`, - ); - }); - } - }); - - // Check if monospace fonts are being used - const nonMonospaceBlocks = codeBlockStyles.filter((style) => { - const fontFamily = style.code.fontFamily.toLowerCase(); - return ( - !fontFamily.includes("monospace") && - !fontFamily.includes("monaco") && - !fontFamily.includes("consolas") && - !fontFamily.includes("courier") && - !fontFamily.includes("fira code") - ); - }); - - if (nonMonospaceBlocks.length > 0) { - console.log("\nโŒ Found code blocks without monospace fonts!"); - process.exit(1); - } else { - console.log("\nโœ… All code blocks are using monospace fonts"); - } - } catch (error) { - console.error("โŒ Test failed:", error.message); - process.exit(1); - } finally { - await browser.close(); - } -}; - -// Make sure dev server is running -console.log("โš ๏ธ Make sure the dev server is running on http://localhost:5173"); -console.log(" Run 'npm run dev' in another terminal\n"); - -testCodeBlockFonts(); diff --git a/scripts/update-all-status-labels.js b/scripts/update-all-status-labels.js deleted file mode 100755 index 37b5732..0000000 --- a/scripts/update-all-status-labels.js +++ /dev/null @@ -1,62 +0,0 @@ -#!/usr/bin/env node - -/** - * Script to create/update all status labels for the new workflow - */ - -import { execSync } from 'child_process'; - -const statusLabels = [ - // Issue statuses - { name: 'status: needs-triage', color: 'e99695', description: 'New issue awaiting evaluation' }, - { name: 'status: triaging', color: 'ffd93d', description: 'Currently being evaluated and labeled' }, - { name: 'status: backlog', color: 'c5def5', description: 'Prioritized and ready for development' }, - { name: 'status: in-progress', color: '0e8a16', description: 'Issue actively being worked on' }, - { name: 'status: in-product-review', color: '9f7efe', description: 'Feature deployed, awaiting product review' }, - { name: 'status: done', color: '0e8a16', description: 'Issue completed and verified' }, - - // PR statuses - { name: 'status: pr-in-progress', color: 'fbca04', description: 'PR draft/WIP, not ready for review' }, - { name: 'status: in-code-review', color: '5319e7', description: 'PR ready, awaiting code review' }, - { name: 'status: code-review-complete', color: '6f42c1', description: 'Code approved, ready to merge' }, - { name: 'status: merged', color: '8250df', description: 'PR merged to main branch' } -]; - -console.log('๐Ÿท๏ธ Creating/updating all status labels\n'); - -for (const label of statusLabels) { - try { - // Try to create first - execSync(`gh label create "${label.name}" --color "${label.color}" --description "${label.description}"`, { - stdio: 'pipe' - }); - console.log(`โœ… Created: ${label.name}`); - } catch (error) { - // If it exists, update it - try { - execSync(`gh label edit "${label.name}" --color "${label.color}" --description "${label.description}"`, { - stdio: 'pipe' - }); - console.log(`๐Ÿ“ Updated: ${label.name}`); - } catch (updateError) { - console.error(`โŒ Failed: ${label.name}`); - } - } -} - -// Remove old labels that are no longer needed -const oldLabels = ['status: in-review']; -console.log('\n๐Ÿงน Removing old labels...'); - -for (const label of oldLabels) { - try { - execSync(`gh label delete "${label}" --yes`, { - stdio: 'pipe' - }); - console.log(`๐Ÿ—‘๏ธ Removed: ${label}`); - } catch (error) { - console.log(`โš ๏ธ Label ${label} doesn't exist or couldn't be removed`); - } -} - -console.log('\nโœ… Status labels updated!'); \ No newline at end of file diff --git a/scripts/utils/add-root-validation.js b/scripts/utils/add-root-validation.js deleted file mode 100755 index d79a882..0000000 --- a/scripts/utils/add-root-validation.js +++ /dev/null @@ -1,95 +0,0 @@ -#!/usr/bin/env node - -/** - * Add project root validation to scripts - */ - -import fs from 'fs'; -import path from 'path'; -import { ensureProjectRoot } from './ensure-project-root.mjs'; - -// Ensure we're in the project root -ensureProjectRoot('add-root-validation.js'); - -// Scripts to update -const scriptsToUpdate = [ - // Bot workflow scripts - 'scripts/bot-workflow/core/bot-checkpoint.js', - 'scripts/bot-workflow/core/bot-handoff.js', - 'scripts/bot-workflow/core/bot-resume-work.js', - 'scripts/bot-workflow/core/bot-status-all.js', - 'scripts/bot-workflow/coordinator/plan-feature.js', - 'scripts/bot-workflow/coordinator/assign-bot.js', - 'scripts/bot-workflow/coordinator/monitor-progress.js', - 'scripts/bot-workflow/worktree/setup-worktree.js', - 'scripts/bot-workflow/worktree/cleanup-worktree.js', - - // Release scripts - 'scripts/release/generate-changelog.js', - 'scripts/release/bump-version.js', - 'scripts/release/prepare-release.js', - - // Automation scripts - 'scripts/automation/monitoring/health-check.js', - 'scripts/automation/monitoring/workflow-performance.js', - - // Quality scripts (CommonJS) - 'scripts/quality/check-fonts.js', - 'scripts/quality/check-codeql.js', - 'scripts/quality/thorough-demo-check.js', - 'scripts/quality/validate-demo.js' -]; - -const importStatementCJS = `const { ensureProjectRoot } = require('${path.relative(process.cwd(), 'scripts/utils/ensure-project-root.cjs')}');`; -const importStatementESM = `import { ensureProjectRoot } from '${path.relative(process.cwd(), 'scripts/utils/ensure-project-root.mjs')}';`; - -scriptsToUpdate.forEach(scriptPath => { - if (!fs.existsSync(scriptPath)) { - console.log(`โš ๏ธ Skipping ${scriptPath} - file not found`); - return; - } - - let content = fs.readFileSync(scriptPath, 'utf8'); - const scriptName = path.basename(scriptPath); - - // Skip if already has the import - if (content.includes('ensureProjectRoot')) { - console.log(`โœ“ ${scriptPath} - already has project root validation`); - return; - } - - // Determine if ES module or CommonJS - const isESM = content.includes('import ') && content.includes('from '); - - // Calculate relative path for import - const relativePath = path.relative(path.dirname(scriptPath), 'scripts/utils'); - const importPath = isESM - ? `import { ensureProjectRoot } from '${relativePath}/ensure-project-root.mjs';` - : `const { ensureProjectRoot } = require('${relativePath}/ensure-project-root.cjs');`; - - const ensureStatement = `\n// Ensure we're in the project root\nensureProjectRoot('${scriptName}');\n`; - - if (isESM) { - // For ES modules, add after last import - const lastImportMatch = content.match(/import[^;]+;[^\n]*\n/g); - if (lastImportMatch) { - const lastImport = lastImportMatch[lastImportMatch.length - 1]; - const insertPos = content.lastIndexOf(lastImport) + lastImport.length; - content = content.slice(0, insertPos) + importPath + '\n' + ensureStatement + content.slice(insertPos); - } - } else { - // For CommonJS, add after last require - const requireMatches = [...content.matchAll(/const [^=]+ = require\([^)]+\);/g)]; - if (requireMatches.length > 0) { - const lastRequire = requireMatches[requireMatches.length - 1]; - const insertPos = lastRequire.index + lastRequire[0].length; - content = content.slice(0, insertPos) + '\n' + importPath + ensureStatement + content.slice(insertPos); - } - } - - fs.writeFileSync(scriptPath, content); - console.log(`โœ… Updated ${scriptPath}`); -}); - -console.log('\nโœ… Project root validation added to scripts'); -console.log('Scripts will now work correctly from any directory within the project'); \ No newline at end of file diff --git a/scripts/utils/convert-to-esm.js b/scripts/utils/convert-to-esm.js deleted file mode 100755 index 838f912..0000000 --- a/scripts/utils/convert-to-esm.js +++ /dev/null @@ -1,132 +0,0 @@ -#!/usr/bin/env node - -/** - * Convert CommonJS scripts to ES Modules - */ - -import fs from 'fs'; -import path from 'path'; -import { glob } from 'glob'; -import { ensureProjectRoot } from './ensure-project-root.mjs'; - -// Ensure we're in the project root -ensureProjectRoot('convert-to-esm.js'); - -// Find all .js files in scripts directory (excluding node_modules and already ESM files) -const scriptFiles = glob.sync('scripts/**/*.js', { - ignore: [ - 'scripts/utils/ensure-project-root.cjs', - 'scripts/utils/convert-to-esm.js', - 'scripts/quality/*.js', // These are already ESM - 'scripts/build/*.js', // These are already ESM - '**/node_modules/**' - ] -}); - -console.log(`Found ${scriptFiles.length} scripts to convert to ESM\n`); - -scriptFiles.forEach(filePath => { - let content = fs.readFileSync(filePath, 'utf8'); - const originalContent = content; - - // Skip if already ESM - if (content.includes('import ') && content.includes(' from ')) { - console.log(`โœ“ ${filePath} - already ESM`); - return; - } - - // Convert require statements to import - content = content.replace( - /const\s+{\s*([^}]+)\s*}\s*=\s*require\(['"]([^'"]+)['"]\);?/g, - 'import { $1 } from \'$2\';' - ); - - content = content.replace( - /const\s+(\w+)\s*=\s*require\(['"]([^'"]+)['"]\);?/g, - 'import $1 from \'$2\';' - ); - - // Fix path and fs imports (they need specific syntax) - content = content.replace( - /import path from 'path';/g, - 'import path from \'path\';' - ); - - content = content.replace( - /import fs from 'fs';/g, - 'import fs from \'fs\';' - ); - - // Update ensure-project-root import - content = content.replace( - /from\s+['"]([^'"]*\/)?ensure-project-root\.cjs['"]/g, - 'from \'$1ensure-project-root.mjs\'' - ); - - // Convert module.exports to export - content = content.replace( - /module\.exports\s*=\s*{([^}]+)}/g, - 'export {$1}' - ); - - content = content.replace( - /module\.exports\s*=\s*(\w+);?/g, - 'export default $1;' - ); - - // Add file extension to local imports - content = content.replace( - /from\s+['"](\.\.?\/[^'"]+)(?<!\.m?js)['"]/g, - (match, importPath) => { - // Don't add extension if it's a directory with index.js - if (fs.existsSync(path.join(path.dirname(filePath), importPath, 'index.js'))) { - return `from '${importPath}'`; - } - // Add .js extension - return `from '${importPath}.js'`; - } - ); - - // Handle __dirname usage - if (content.includes('__dirname')) { - // Add import for fileURLToPath if not present - if (!content.includes('fileURLToPath')) { - const importInsertPoint = content.match(/import[^;]+;\n/g); - if (importInsertPoint) { - const lastImport = importInsertPoint[importInsertPoint.length - 1]; - const insertPos = content.lastIndexOf(lastImport) + lastImport.length; - content = content.slice(0, insertPos) + - `import { fileURLToPath } from 'url';\n` + - content.slice(insertPos); - } - } - - // Add __dirname definition after imports - const importsEnd = content.lastIndexOf('import'); - if (importsEnd !== -1) { - const lineEnd = content.indexOf('\n', importsEnd); - if (!content.includes('const __dirname')) { - content = content.slice(0, lineEnd + 1) + - '\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\n' + - content.slice(lineEnd + 1); - } - } - } - - // Only write if content changed - if (content !== originalContent) { - fs.writeFileSync(filePath, content); - console.log(`โœ… Converted ${filePath} to ESM`); - } else { - console.log(`โš ๏ธ ${filePath} - no changes needed`); - } -}); - -// Also delete the .cjs file since we won't need it -if (fs.existsSync('scripts/utils/ensure-project-root.cjs')) { - fs.unlinkSync('scripts/utils/ensure-project-root.cjs'); - console.log('\n๐Ÿ—‘๏ธ Removed scripts/utils/ensure-project-root.cjs (no longer needed)'); -} - -console.log('\nโœ… Conversion to ESM complete!'); -console.log('All scripts now use modern ES modules.'); \ No newline at end of file