Improve knip reports by using a single config with workspaces

Also move the dependency from the workspace into repo-tools.

The run with a configured workspace is slower, so I had to bump the timeout.

Signed-off-by: Axel Hecht <axel@pike.org>
This commit is contained in:
Axel Hecht
2025-01-15 13:16:59 +01:00
parent a3e29fffd6
commit 474b748a51
6 changed files with 51 additions and 38 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/repo-tools': patch
---
Improve knip reports by using a single workspace-based config
-1
View File
@@ -126,7 +126,6 @@
"eslint-plugin-testing-library": "^6.0.0",
"fs-extra": "^11.2.0",
"husky": "^9.0.0",
"knip": "^5.0.0",
"lint-staged": "^15.0.0",
"minimist": "^1.2.5",
"node-gyp": "^10.0.0",
+1
View File
@@ -74,6 +74,7 @@
"just-diff": "^6.0.2",
"knex": "^3.0.0",
"knex-pglite": "^0.11.0",
"knip": "^5.42.0",
"lodash": "^4.17.21",
"minimatch": "^9.0.0",
"p-limit": "^3.0.2",
@@ -18,6 +18,7 @@ import pLimit from 'p-limit';
import os from 'os';
import { relative as relativePath, resolve as resolvePath } from 'path';
import fs from 'fs-extra';
import type { KnipConfig } from 'knip';
import { createBinRunner } from '../util';
// Ignore this due to Knip error: Error: ENAMETOOLONG: name too long, scandir
@@ -29,7 +30,7 @@ interface KnipExtractionOptions {
}
interface KnipConfigOptions {
packageDir: string;
knipConfigPath: string;
}
interface KnipPackageOptions {
@@ -55,15 +56,20 @@ function logKnipReportInstructions() {
console.log('');
}
async function generateKnipConfig({ packageDir }: KnipConfigOptions) {
const knipConfig = {
entry: [
'dev/index.{ts,tsx}',
'src/index.{ts,tsx}',
'src/alpha.{ts,tsx}',
'src/routes.ts',
'src/run.ts',
],
async function generateKnipConfig({ knipConfigPath }: KnipConfigOptions) {
const knipConfig: KnipConfig = {
workspaces: {
'.': {},
'{packages,plugins}/*': {
entry: [
'dev/index.{ts,tsx}',
'src/index.{ts,tsx}',
'src/alpha.{ts,tsx}',
'src/routes.ts',
'src/run.ts',
],
},
},
jest: {
entry: ['src/setupTests.ts', '**/*.test.{ts,tsx}'],
},
@@ -81,15 +87,12 @@ async function generateKnipConfig({ packageDir }: KnipConfigOptions) {
'@backstage/theme', // this uses `declare module` in .d.ts so is implicitly used whenever extensions are needed
],
};
await fs.writeFile(
`${packageDir}/knip.json`,
JSON.stringify(knipConfig, null, 2),
);
await fs.writeFile(knipConfigPath, JSON.stringify(knipConfig, null, 2));
}
function cleanKnipConfig({ packageDir }: KnipConfigOptions) {
if (fs.existsSync(`${packageDir}/knip.json`)) {
fs.rmSync(`${packageDir}/knip.json`);
function cleanKnipConfig({ knipConfigPath }: KnipConfigOptions) {
if (fs.existsSync(knipConfigPath)) {
fs.rmSync(knipConfigPath);
}
}
@@ -105,13 +108,11 @@ async function handlePackage({
}
const fullDir = cliPaths.resolveTargetRoot(packageDir);
const reportPath = resolvePath(fullDir, 'knip-report.md');
const run = createBinRunner(fullDir, '');
const run = createBinRunner(cliPaths.targetRoot, '');
await generateKnipConfig({ packageDir: fullDir });
const report = await run(
let report = await run(
`${knipDir}/knip.js`,
`--directory ${fullDir}`, // Run in the package directory
`-W ${packageDir}`, // Run the desired workspace
'--config knip.json',
'--no-exit-code', // Removing this will end the process in case there are findings by knip
'--no-progress', // Remove unnecessary debugging from output
@@ -120,7 +121,17 @@ async function handlePackage({
'--reporter markdown',
);
cleanKnipConfig({ packageDir: fullDir });
// Adjust report paths to be relative to workspace
report = report.replaceAll(`| ${packageDir}/`, '| ');
// Adjust table separators
report = report.replaceAll(
new RegExp(`(\\| :-+ \\| :)-{${packageDir.length + 1}}`, 'g'),
(_, p1) => p1,
);
report = report.replaceAll(
new RegExp(` \\| Location {1,${packageDir.length + 2}}`, 'g'),
' | Location ',
);
const existingReport = await fs.readFile(reportPath, 'utf8').catch(error => {
if (error.code === 'ENOENT') {
@@ -160,8 +171,10 @@ export async function runKnipReports({
isLocalBuild,
}: KnipExtractionOptions) {
const knipDir = cliPaths.resolveTargetRoot('./node_modules/knip/bin/');
const knipConfigPath = cliPaths.resolveTargetRoot('./knip.json');
const limiter = pLimit(os.cpus().length);
await generateKnipConfig({ knipConfigPath });
try {
await Promise.all(
packageDirs.map(packageDir =>
@@ -170,15 +183,9 @@ export async function runKnipReports({
),
),
);
await cleanKnipConfig({ knipConfigPath });
} catch (e) {
console.log(
`Error occurred during knip reporting: ${e}, cleaning knip configs`,
);
packageDirs.map(packageDir => {
const fullDir = cliPaths.resolveTargetRoot(packageDir);
cleanKnipConfig({ packageDir: fullDir });
});
console.log(`Error occurred during knip reporting: ${e}`);
throw e;
}
}
+2 -1
View File
@@ -35,11 +35,12 @@ export function createBinRunner(cwd: string, path: string) {
{
cwd,
shell: true,
timeout: 60000,
timeout: 3 * 60 * 1000,
maxBuffer: 1024 * 1024,
},
(err, stdout, stderr) => {
if (err) {
console.log('err', err);
reject(new Error(`${err.message}\n${stderr}`));
} else if (stderr) {
reject(new Error(`Command printed error output: ${stderr}`));
+5 -5
View File
@@ -8329,6 +8329,7 @@ __metadata:
just-diff: ^6.0.2
knex: ^3.0.0
knex-pglite: ^0.11.0
knip: ^5.42.0
lodash: ^4.17.21
minimatch: ^9.0.0
p-limit: ^3.0.2
@@ -34104,7 +34105,7 @@ __metadata:
languageName: node
linkType: hard
"knip@npm:^5.0.0":
"knip@npm:^5.42.0":
version: 5.42.2
resolution: "knip@npm:5.42.2"
dependencies:
@@ -39801,11 +39802,11 @@ __metadata:
linkType: hard
"pretty-ms@npm:^9.0.0":
version: 9.0.0
resolution: "pretty-ms@npm:9.0.0"
version: 9.2.0
resolution: "pretty-ms@npm:9.2.0"
dependencies:
parse-ms: ^4.0.0
checksum: 072b17547e09cb232e8e4c7be0281e256b6d8acd18dfb2fdd715d50330d1689fdaa877f53cf90c62ed419ef842f0f5fb94a2cd8ed1aa6d7608ad48834219435d
checksum: d3a5a5b1c8a3417f64a877dba5ee2bacee404b59bc12083466e5e6dce2745e4bd716e1f9860624c7dceb1b4a532e808e4f2a7a03903a132344b3818951e2d125
languageName: node
linkType: hard
@@ -42217,7 +42218,6 @@ __metadata:
eslint-plugin-testing-library: ^6.0.0
fs-extra: ^11.2.0
husky: ^9.0.0
knip: ^5.0.0
lint-staged: ^15.0.0
minimist: ^1.2.5
node-gyp: ^10.0.0