Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/tests-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ jobs:
with:
node-version: ${{ env.DEFAULT_NODE_VERSION }}
- name: Run knip
run: pnpm knip
run: node bin/run-knip-ci.js

graphql-schema:
if: ${{ github.event.pull_request.user.login != 'dependabot[bot]' }}
Expand Down
41 changes: 41 additions & 0 deletions bin/run-knip-ci.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import {spawnSync} from 'node:child_process'

// Run knip with the JSON reporter so a successful run still emits structured
// output (e.g. {"files":[],"issues":[]}). An empty stdout therefore indicates
// knip did not actually analyze the codebase — a silent-pass mode previously
// observed — and must be treated as a failure to avoid false-green builds.
const knip = spawnSync('pnpm', ['--silent', 'knip', '--reporter', 'json'], {
encoding: 'utf8',
})

if (knip.error) {
console.error(`::error::failed to spawn knip: ${knip.error.message}`)
process.exit(1)
}

let report
try {
report = JSON.parse(knip.stdout)
} catch {
console.error(
`::error::knip exited ${knip.status} but produced no parseable JSON output. Failing CI to avoid a false green.`,
)
console.error('Raw stdout:')
console.error(knip.stdout)
console.error('Raw stderr:')
console.error(knip.stderr)
process.exit(1)
}

if (!Array.isArray(report.files) || !Array.isArray(report.issues)) {
console.error('::error::knip output had unexpected shape')
console.error(JSON.stringify(report))
process.exit(1)
}

if (knip.status !== 0) {
// Findings: re-run with the default reporter so the log is human-readable.
spawnSync('pnpm', ['knip'], {stdio: 'inherit'})
}

process.exit(knip.status ?? 1)
Loading