chore(deps): update turbo monorepo to v2.9.18 #155
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CNC Sync Policy | |
| on: | |
| workflow_dispatch: | |
| pull_request: | |
| branches: | |
| - master | |
| types: | |
| - opened | |
| - edited | |
| - synchronize | |
| - reopened | |
| - ready_for_review | |
| concurrency: | |
| group: cnc-sync-policy-${{ github.event.pull_request.number || github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| policy-check: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| permissions: | |
| contents: read | |
| pull-requests: read | |
| issues: read | |
| steps: | |
| - name: Validate CNC sync policy | |
| uses: actions/github-script@v8 | |
| with: | |
| script: | | |
| const pr = context.payload.pull_request; | |
| if (!pr) return; | |
| const body = pr.body || ''; | |
| const title = pr.title || ''; | |
| const combined = `${title}\n${body}`; | |
| const errors = []; | |
| const files = await github.paginate(github.rest.pulls.listFiles, { | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| pull_number: pr.number, | |
| per_page: 100, | |
| }); | |
| const filenames = files.map((file) => file.filename); | |
| const touchesCncPaths = filenames.some((name) => | |
| /^apps\/cnc\/src\//.test(name) || /^packages\/protocol\/src\//.test(name) | |
| ); | |
| const isCncFeature = /\[(x|X)\]\s+This PR is a CNC feature change\./.test(body); | |
| const requiredReviewPassChecks = [ | |
| /\[(x|X)\]\s+I completed a final review pass after my latest implementation commit\b/, | |
| /\[(x|X)\]\s+I reviewed the final diff for correctness, scope control, and regression risk\./, | |
| /\[(x|X)\]\s+I addressed all review comments\/threads with follow-up commits or explicit rationale\./, | |
| /\[(x|X)\]\s+I re-reviewed the updated diff after applying review feedback\./, | |
| ]; | |
| for (const check of requiredReviewPassChecks) { | |
| if (!check.test(body)) { | |
| errors.push(`Missing required checked review-pass checklist item matching: ${check}`); | |
| } | |
| } | |
| // Low-cost CI parity for commit-msg hook: validate PR commit subjects. | |
| const conventionalCommitRegex = | |
| /^(revert: )?(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)(\([^)]+\))?!?: .+/; | |
| const commits = await github.paginate(github.rest.pulls.listCommits, { | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| pull_number: pr.number, | |
| per_page: 100, | |
| }); | |
| const invalidCommitSubjects = []; | |
| for (const commit of commits) { | |
| const subject = (commit.commit?.message || '').split('\n')[0].trim(); | |
| if (!subject || /^Merge\b/.test(subject)) { | |
| continue; | |
| } | |
| if (!conventionalCommitRegex.test(subject)) { | |
| invalidCommitSubjects.push(`${commit.sha.slice(0, 7)}: ${subject}`); | |
| } | |
| } | |
| if (invalidCommitSubjects.length > 0) { | |
| errors.push( | |
| `Non-conventional commit subjects detected: ${invalidCommitSubjects.slice(0, 5).join(' | ')}` | |
| ); | |
| } | |
| // Low-cost CI parity for pre-commit secret scanning: scan added diff lines for common secret patterns. | |
| const secretPatterns = [ | |
| { | |
| name: 'Private key', | |
| regex: /-----BEGIN(?: RSA| EC| OPENSSH| DSA)? PRIVATE KEY-----/, | |
| }, | |
| { | |
| name: 'GitHub token', | |
| regex: /\bgh[pousr]_[A-Za-z0-9_]{20,}\b/, | |
| }, | |
| { | |
| name: 'AWS access key', | |
| regex: /\bAKIA[0-9A-Z]{16}\b/, | |
| }, | |
| { | |
| name: 'Google API key', | |
| regex: /\bAIza[0-9A-Za-z\-_]{35}\b/, | |
| }, | |
| { | |
| name: 'Slack token', | |
| regex: /\bxox[baprs]-[A-Za-z0-9-]{10,}\b/, | |
| }, | |
| { | |
| name: 'Generic credential assignment', | |
| regex: /(api[_-]?key|token|secret|password)\s*[:=]\s*["'][^"'\s]{16,}["']/i, | |
| }, | |
| ]; | |
| const placeholderRegex = | |
| /(example|sample|dummy|fake|test|changeme|placeholder|redacted|your[_-]?token|your[_-]?key)/i; | |
| const secretHits = []; | |
| for (const file of files) { | |
| if (!file.patch) { | |
| continue; | |
| } | |
| const addedLines = file.patch | |
| .split('\n') | |
| .filter((line) => line.startsWith('+') && !line.startsWith('+++')) | |
| .map((line) => line.slice(1)); | |
| for (const line of addedLines) { | |
| if (placeholderRegex.test(line)) { | |
| continue; | |
| } | |
| const match = secretPatterns.find((pattern) => pattern.regex.test(line)); | |
| if (match) { | |
| secretHits.push(`${file.filename}: ${match.name}`); | |
| break; | |
| } | |
| } | |
| } | |
| if (secretHits.length > 0) { | |
| errors.push( | |
| `Potential secrets detected in added lines: ${secretHits.slice(0, 5).join(' | ')}` | |
| ); | |
| } | |
| if (touchesCncPaths && !isCncFeature) { | |
| errors.push('This PR touches CNC/protocol source paths. Check "This PR is a CNC feature change." in the PR template.'); | |
| } | |
| if (isCncFeature) { | |
| const requiredFields = [ | |
| { | |
| name: 'Protocol issue', | |
| regex: /Protocol issue:\s*(kaonis\/woly-server#\d+|https:\/\/github\.com\/kaonis\/woly-server\/issues\/\d+)/i, | |
| }, | |
| { | |
| name: 'Backend issue', | |
| regex: /Backend issue:\s*(kaonis\/woly-server#\d+|https:\/\/github\.com\/kaonis\/woly-server\/issues\/\d+)/i, | |
| }, | |
| { | |
| name: 'Frontend issue', | |
| regex: /Frontend issue:\s*(kaonis\/woly#\d+|https:\/\/github\.com\/kaonis\/woly\/issues\/\d+)/i, | |
| }, | |
| ]; | |
| for (const field of requiredFields) { | |
| if (!field.regex.test(body)) { | |
| errors.push(`Missing or invalid ${field.name}. Use explicit cross-repo reference format.`); | |
| } | |
| } | |
| const requiredChecks = [ | |
| /\[(x|X)\]\s+Protocol contract updated or verified\./, | |
| /\[(x|X)\]\s+Backend endpoint\/command implemented or explicitly unchanged\./, | |
| /\[(x|X)\]\s+Frontend integration implemented or tracked in linked issue\./, | |
| /\[(x|X)\]\s+Local validation passed/, | |
| ]; | |
| for (const check of requiredChecks) { | |
| if (!check.test(body)) { | |
| errors.push(`Missing required checked checklist item matching: ${check}`); | |
| } | |
| } | |
| const capabilityIssue = await github.rest.issues.get({ | |
| owner: 'kaonis', | |
| repo: 'woly-server', | |
| issue_number: 254, | |
| }); | |
| const capabilityMentioned = | |
| /kaonis\/woly-server#254/.test(combined) || | |
| /https:\/\/github\.com\/kaonis\/woly-server\/issues\/254/.test(combined) || | |
| /\b#254\b/.test(combined); | |
| if (capabilityIssue.data.state !== 'closed' && !capabilityMentioned) { | |
| errors.push('Issue kaonis/woly-server#254 is still open. CNC feature PRs must implement/link this capability work first.'); | |
| } | |
| } | |
| if (errors.length > 0) { | |
| core.setFailed(`CNC sync policy check failed:\n- ${errors.join('\n- ')}`); | |
| } |