* esm * wip * wip * wip * wip * wip * wip * subscription notice * wip * wip * wip * fix envs * fix: update docker build * fix * esm/types * delete dashboard :D * add patches to dockerfiles * update packages + catalogs + ts * wip * remove native libs * ts * improvements * fix redirects and fetching session * try fix favicon * fixes * fix * order and resize reportds within a dashboard * improvements * wip * added userjot to dashboard * fix * add op * wip * different cache key * improve date picker * fix table * event details loading * redo onboarding completely * fix login * fix * fix * extend session, billing and improve bars * fix * reduce price on 10M
202 lines
5.2 KiB
JavaScript
202 lines
5.2 KiB
JavaScript
#!/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;
|
|
});
|