fix: remove unused

This commit is contained in:
Carl-Gerhard Lindesvärd
2025-10-16 13:14:43 +02:00
parent 4d060cb7d2
commit 7a63885d38
2 changed files with 1 additions and 203 deletions

View File

@@ -26,8 +26,7 @@
"lint:fix": "biome check --write .",
"lint:workspace": "pnpm dlx sherif@latest",
"typecheck": "pnpm -r typecheck",
"update-simple-git-hooks": "npx simple-git-hooks",
"deps:unused": "node tooling/unused-deps.mjs"
"update-simple-git-hooks": "npx simple-git-hooks"
},
"simple-git-hooks": {
"pre-push": "pnpm typecheck && pnpm test"

View File

@@ -1,201 +0,0 @@
#!/usr/bin/env node
import { spawn } from 'node:child_process';
import { promises as fs } from 'node:fs';
import path from 'node:path';
import url from 'node:url';
// Lazy import depcheck to avoid hard crash if not installed
async function loadDepcheck() {
try {
const mod = await import('depcheck');
return mod.default ?? mod;
} catch (err) {
console.error(
'depcheck is not installed. Install it with: pnpm -w add -D depcheck',
);
process.exitCode = 1;
process.exit(1);
}
}
const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
const repoRoot = path.resolve(__dirname, '..');
const DEFAULT_IGNORE_DIRS = [
'node_modules',
'dist',
'build',
'.next',
'.nuxt',
'.svelte-kit',
'.output',
'.turbo',
'coverage',
'.vercel',
'.cache',
'.astro',
'.pnpm',
];
function parseArgs() {
const args = new Set(process.argv.slice(2));
return {
json: args.has('--json') || args.has('-j'),
};
}
async function fileExists(p) {
try {
await fs.access(p);
return true;
} catch {
return false;
}
}
async function readWorkspacePatterns() {
const workspaceFile = path.join(repoRoot, 'pnpm-workspace.yaml');
if (!(await fileExists(workspaceFile))) return [];
const content = await fs.readFile(workspaceFile, 'utf8');
const patterns = [];
for (const line of content.split(/\r?\n/)) {
const m = line.match(/^\s*-\s*"?([^"#]+)"?\s*(?:#.*)?$/);
if (m) {
patterns.push(m[1].trim());
}
}
return patterns.filter(Boolean);
}
async function isPackageDir(dir) {
const pkgPath = path.join(dir, 'package.json');
return fileExists(pkgPath);
}
async function listSubdirs(dir) {
const entries = await fs.readdir(dir, { withFileTypes: true });
return entries
.filter((e) => e.isDirectory())
.map((e) => path.join(dir, e.name));
}
async function findPackagesFromPattern(pattern) {
// Normalize pattern relative to repo root
const absolutePattern = path.resolve(repoRoot, pattern);
if (pattern.endsWith('/**')) {
const baseDir = absolutePattern.slice(0, -3); // remove /**
return await findPackagesRecursively(baseDir);
}
if (pattern.endsWith('/*')) {
const baseDir = absolutePattern.slice(0, -2); // remove /*
return (await listSubdirs(baseDir)).filterAsync(isPackageDir);
}
// Direct path
return (await isPackageDir(absolutePattern)) ? [absolutePattern] : [];
}
async function findPackagesRecursively(startDir) {
const results = [];
async function walk(dir) {
if (!(await fileExists(dir))) return;
if (await isPackageDir(dir)) results.push(dir);
const subdirs = await listSubdirs(dir);
for (const sub of subdirs) {
await walk(sub);
}
}
await walk(startDir);
return results;
}
// Add filterAsync utility on arrays
Object.defineProperty(Array.prototype, 'filterAsync', {
value: async function (predicate) {
const results = await Promise.all(this.map(predicate));
return this.filter((_, i) => results[i]);
},
enumerable: false,
});
async function discoverWorkspacePackageDirs() {
const patterns = await readWorkspacePatterns();
const discovered = new Set();
for (const pattern of patterns) {
const pkgs = await findPackagesFromPattern(pattern);
for (const p of pkgs) discovered.add(path.resolve(p));
}
// Always include repo root as well
discovered.add(repoRoot);
return Array.from(discovered);
}
async function runDepcheckOnDir(depcheck, dir) {
const result = await depcheck(dir, {
ignoreBinPackage: false,
ignoreDirs: DEFAULT_IGNORE_DIRS,
ignoreMatches: [],
specials: [
depcheck.special.eslint,
depcheck.special.typescript,
depcheck.special.webpack,
depcheck.special.rollup,
depcheck.special.babel,
depcheck.special.postcss,
],
});
return {
dir,
unused: {
dependencies: result.dependencies ?? [],
devDependencies: result.devDependencies ?? [],
},
};
}
function relative(p) {
return path.relative(repoRoot, p) || '.';
}
function printHuman(results) {
let any = false;
for (const { dir, unused } of results) {
if (unused.dependencies.length + unused.devDependencies.length === 0)
continue;
any = true;
console.log(`\n${relative(dir)}:`);
if (unused.dependencies.length) {
console.log(` unused dependencies (${unused.dependencies.length}):`);
for (const d of unused.dependencies) console.log(` - ${d}`);
}
if (unused.devDependencies.length) {
console.log(
` unused devDependencies (${unused.devDependencies.length}):`,
);
for (const d of unused.devDependencies) console.log(` - ${d}`);
}
}
if (!any) console.log('No unused dependencies found.');
}
async function main() {
const args = parseArgs();
const depcheck = await loadDepcheck();
const packageDirs = await discoverWorkspacePackageDirs();
const checks = await Promise.all(
packageDirs.map((dir) => runDepcheckOnDir(depcheck, dir)),
);
if (args.json) {
const compact = checks
.map(({ dir, unused }) => ({ dir: relative(dir), ...unused }))
.filter((r) => r.dependencies.length || r.devDependencies.length);
console.log(JSON.stringify(compact, null, 2));
} else {
printHuman(checks);
}
}
main().catch((err) => {
console.error(err);
process.exitCode = 1;
});