Switch to OpenAI Responses API #101
Workflow file for this run
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: Knip - Unused Code Analysis | |
| on: | |
| pull_request: | |
| branches: [main] | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| knip: | |
| timeout-minutes: 10 | |
| runs-on: ubuntu-24.04 | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| steps: | |
| - name: Checkout PR branch | |
| uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 | |
| - name: Setup node | |
| uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 | |
| with: | |
| node-version-file: '.nvmrc' | |
| cache-dependency-path: 'yarn.lock' | |
| cache: 'yarn' | |
| - name: Install dependencies | |
| run: yarn install | |
| - name: Run Knip on PR branch | |
| run: yarn knip --reporter json > /tmp/knip-pr.json 2>/dev/null || true | |
| - name: Checkout main branch | |
| uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 | |
| with: | |
| ref: main | |
| clean: false | |
| - name: Install dependencies (main) | |
| run: yarn install | |
| - name: Run Knip on main branch | |
| run: yarn knip --reporter json > /tmp/knip-main.json 2>/dev/null || true | |
| - name: Compare results and comment | |
| uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 | |
| with: | |
| script: | | |
| const fs = require('fs'); | |
| const issueCategories = [ | |
| 'files', 'dependencies', 'devDependencies', 'unlisted', | |
| 'unresolved', 'binaries', 'exports', 'types', | |
| 'enumMembers', 'duplicates' | |
| ]; | |
| function countIssues(filePath) { | |
| try { | |
| const raw = fs.readFileSync(filePath, 'utf8'); | |
| const data = JSON.parse(raw); | |
| const counts = {}; | |
| // items maps category -> Set of "file:name" identifiers | |
| const items = {}; | |
| for (const cat of issueCategories) items[cat] = new Set(); | |
| // Top-level "files" are unused files (just strings) | |
| for (const f of data.files || []) { | |
| counts.files = (counts.files || 0) + 1; | |
| items.files.add(f); | |
| } | |
| for (const issue of data.issues || []) { | |
| for (const cat of issueCategories) { | |
| if (cat === 'files') continue; | |
| if (Array.isArray(issue[cat]) && issue[cat].length > 0) { | |
| counts[cat] = (counts[cat] || 0) + issue[cat].length; | |
| for (const item of issue[cat]) { | |
| items[cat].add(`${issue.file}:${item.name}`); | |
| } | |
| } | |
| } | |
| } | |
| let total = 0; | |
| for (const v of Object.values(counts)) total += v; | |
| return { counts, total, items }; | |
| } catch (e) { | |
| console.log(`Failed to parse ${filePath}: ${e.message}`); | |
| const items = {}; | |
| for (const cat of issueCategories) items[cat] = new Set(); | |
| return { counts: {}, total: 0, items }; | |
| } | |
| } | |
| const pr = countIssues('/tmp/knip-pr.json'); | |
| const main = countIssues('/tmp/knip-main.json'); | |
| const categories = [ | |
| ['files', 'Unused files'], | |
| ['dependencies', 'Unused dependencies'], | |
| ['devDependencies', 'Unused devDependencies'], | |
| ['unlisted', 'Unlisted dependencies'], | |
| ['unresolved', 'Unresolved imports'], | |
| ['binaries', 'Unlisted binaries'], | |
| ['exports', 'Unused exports'], | |
| ['types', 'Unused exported types'], | |
| ['enumMembers', 'Unused enum members'], | |
| ['duplicates', 'Duplicate exports'], | |
| ]; | |
| const diff = pr.total - main.total; | |
| const emoji = diff > 0 ? '🔴' : diff < 0 ? '🟢' : '⚪'; | |
| const sign = diff > 0 ? '+' : ''; | |
| let body = `## Knip - Unused Code Analysis\n\n`; | |
| if (diff === 0) { | |
| body += `${emoji} No changes detected (${main.total} issues on both main and PR)\n`; | |
| } else { | |
| body += `${emoji} **${sign}${diff}** change in total issues (${main.total} on main → ${pr.total} on PR)\n\n`; | |
| body += `| Category | main | PR | Diff |\n`; | |
| body += `|----------|-----:|---:|-----:|\n`; | |
| const detailLines = []; | |
| for (const [key, label] of categories) { | |
| const mainCount = main.counts[key] || 0; | |
| const prCount = pr.counts[key] || 0; | |
| const catDiff = prCount - mainCount; | |
| if (mainCount === 0 && prCount === 0) continue; | |
| const catSign = catDiff > 0 ? '+' : ''; | |
| const catEmoji = catDiff > 0 ? ' 🔴' : catDiff < 0 ? ' 🟢' : ''; | |
| body += `| ${label} | ${mainCount} | ${prCount} | ${catSign}${catDiff}${catEmoji} |\n`; | |
| if (catDiff !== 0) { | |
| const added = [...pr.items[key]].filter(x => !main.items[key].has(x)); | |
| const removed = [...main.items[key]].filter(x => !pr.items[key].has(x)); | |
| if (added.length || removed.length) { | |
| detailLines.push(`**${label}**`); | |
| for (const a of added) detailLines.push(`- 🔴 \`${a}\``); | |
| for (const r of removed) detailLines.push(`- 🟢 ~\`${r}\`~`); | |
| } | |
| } | |
| } | |
| if (detailLines.length) { | |
| body += `\n<details><summary>Details</summary>\n\n`; | |
| body += detailLines.join('\n') + '\n'; | |
| body += `</details>\n`; | |
| } | |
| } | |
| body += `\n<details><summary>What is this?</summary>\n\n`; | |
| body += `[Knip](https://knip.dev) finds unused files, dependencies, and exports in your codebase.\n`; | |
| body += `This comment compares the PR branch against \`main\` to detect regressions.\n\n`; | |
| body += `Run \`yarn knip\` locally to see full details.\n`; | |
| body += `</details>\n`; | |
| // Find existing comment | |
| const { data: comments } = await github.rest.issues.listComments({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| }); | |
| const existing = comments.find(c => | |
| c.user.type === 'Bot' && c.body.includes('Knip - Unused Code Analysis') | |
| ); | |
| if (existing) { | |
| await github.rest.issues.updateComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| comment_id: existing.id, | |
| body, | |
| }); | |
| } else { | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| body, | |
| }); | |
| } |