@kb-labs/devkit 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.cursorrules +32 -0
- package/.github/CODEOWNERS +2 -0
- package/.github/actions/setup-node-pnpm/action.yml +47 -0
- package/.github/workflow-templates/ci.yml +13 -0
- package/.github/workflow-templates/drift-check.yml +10 -0
- package/.github/workflow-templates/profiles-validate.yml +16 -0
- package/.github/workflow-templates/release.yml +8 -0
- package/.github/workflows/ci-reusable.yml +131 -0
- package/.github/workflows/drift-check-reusable.yml +23 -0
- package/.github/workflows/fixtures.yml +74 -0
- package/.github/workflows/profiles-validate-reusable.yml +67 -0
- package/.github/workflows/release-reusable.yml +50 -0
- package/.vscode/settings.json +23 -0
- package/AGENTS.md +130 -0
- package/LICENSE +21 -0
- package/README.md +1542 -0
- package/agents/devkit-maintainer/context.globs +15 -0
- package/agents/devkit-maintainer/permissions.yml +17 -0
- package/agents/devkit-maintainer/prompt.md +28 -0
- package/agents/devkit-maintainer/runbook.md +31 -0
- package/agents/docs-crafter/prompt.md +24 -0
- package/agents/docs-crafter/runbook.md +18 -0
- package/agents/release-manager/context.globs +7 -0
- package/agents/release-manager/prompt.md +27 -0
- package/agents/release-manager/runbook.md +17 -0
- package/agents/test-generator/context.globs +7 -0
- package/agents/test-generator/prompt.md +27 -0
- package/agents/test-generator/runbook.md +18 -0
- package/bin/devkit-architecture.mjs +1225 -0
- package/bin/devkit-build-order.mjs +500 -0
- package/bin/devkit-check-build-readiness.mjs +222 -0
- package/bin/devkit-check-commands.mjs +394 -0
- package/bin/devkit-check-configs.mjs +461 -0
- package/bin/devkit-check-deprecated.mjs +532 -0
- package/bin/devkit-check-duplicates.mjs +431 -0
- package/bin/devkit-check-exports.mjs +561 -0
- package/bin/devkit-check-imports.mjs +712 -0
- package/bin/devkit-check-paths.mjs +670 -0
- package/bin/devkit-check-scripts.mjs +335 -0
- package/bin/devkit-check-structure.mjs +495 -0
- package/bin/devkit-check-types.mjs +450 -0
- package/bin/devkit-ci.mjs +261 -0
- package/bin/devkit-core-gate.mjs +225 -0
- package/bin/devkit-fix-deps.mjs +1169 -0
- package/bin/devkit-freshness.mjs +199 -0
- package/bin/devkit-health.mjs +489 -0
- package/bin/devkit-migrate-configs.mjs +370 -0
- package/bin/devkit-paths.mjs +192 -0
- package/bin/devkit-stats.mjs +453 -0
- package/bin/devkit-sync.mjs +12 -0
- package/bin/devkit-tsup-external.mjs +148 -0
- package/bin/devkit-types-audit.mjs +615 -0
- package/bin/devkit-types-order.mjs +637 -0
- package/bin/devkit-validate-naming.mjs +203 -0
- package/bin/devkit-visualize.mjs +452 -0
- package/bin/kb-devkit-qa-history.mjs +453 -0
- package/bin/kb-devkit-qa.mjs +915 -0
- package/eslint/node.js +134 -0
- package/eslint/react.js +260 -0
- package/package.json +186 -0
- package/prettier/index.json +10 -0
- package/sync/index.mjs +693 -0
- package/templates/configs/README.md +306 -0
- package/templates/configs/eslint.config.js +27 -0
- package/templates/configs/package.json.bin +25 -0
- package/templates/configs/package.json.lib +30 -0
- package/templates/configs/tsconfig.build.json +15 -0
- package/templates/configs/tsconfig.json +9 -0
- package/templates/configs/tsup.config.bin.ts +34 -0
- package/templates/configs/tsup.config.cli.ts +41 -0
- package/templates/configs/tsup.config.dual.ts +46 -0
- package/templates/configs/tsup.config.ts +36 -0
- package/templates/package-json/README.md +290 -0
- package/templates/package-json/package.json.bin.template +40 -0
- package/templates/package-json/package.json.template +45 -0
- package/tsconfig/base.json +21 -0
- package/tsconfig/cli.json +12 -0
- package/tsconfig/dist/__tests__/cache.spec.d.ts +6 -0
- package/tsconfig/dist/__tests__/cache.spec.d.ts.map +1 -0
- package/tsconfig/dist/__tests__/cache.spec.js +85 -0
- package/tsconfig/dist/__tests__/cache.spec.js.map +1 -0
- package/tsconfig/dist/__tests__/fs-atomic.spec.d.ts +6 -0
- package/tsconfig/dist/__tests__/fs-atomic.spec.d.ts.map +1 -0
- package/tsconfig/dist/__tests__/fs-atomic.spec.js +153 -0
- package/tsconfig/dist/__tests__/fs-atomic.spec.js.map +1 -0
- package/tsconfig/dist/__tests__/init-workspace.spec.d.ts +6 -0
- package/tsconfig/dist/__tests__/init-workspace.spec.d.ts.map +1 -0
- package/tsconfig/dist/__tests__/init-workspace.spec.js +99 -0
- package/tsconfig/dist/__tests__/init-workspace.spec.js.map +1 -0
- package/tsconfig/dist/__tests__/kb-error.spec.d.ts +6 -0
- package/tsconfig/dist/__tests__/kb-error.spec.d.ts.map +1 -0
- package/tsconfig/dist/__tests__/kb-error.spec.js +190 -0
- package/tsconfig/dist/__tests__/kb-error.spec.js.map +1 -0
- package/tsconfig/dist/__tests__/preset-lockfile.spec.d.ts +6 -0
- package/tsconfig/dist/__tests__/preset-lockfile.spec.d.ts.map +1 -0
- package/tsconfig/dist/__tests__/preset-lockfile.spec.js +142 -0
- package/tsconfig/dist/__tests__/preset-lockfile.spec.js.map +1 -0
- package/tsconfig/dist/__tests__/product-config-profiles.spec.d.ts +6 -0
- package/tsconfig/dist/__tests__/product-config-profiles.spec.d.ts.map +1 -0
- package/tsconfig/dist/__tests__/product-config-profiles.spec.js +100 -0
- package/tsconfig/dist/__tests__/product-config-profiles.spec.js.map +1 -0
- package/tsconfig/dist/__tests__/product-config.spec.d.ts +6 -0
- package/tsconfig/dist/__tests__/product-config.spec.d.ts.map +1 -0
- package/tsconfig/dist/__tests__/product-config.spec.js +298 -0
- package/tsconfig/dist/__tests__/product-config.spec.js.map +1 -0
- package/tsconfig/dist/__tests__/runtime.spec.d.ts +2 -0
- package/tsconfig/dist/__tests__/runtime.spec.d.ts.map +1 -0
- package/tsconfig/dist/__tests__/runtime.spec.js +127 -0
- package/tsconfig/dist/__tests__/runtime.spec.js.map +1 -0
- package/tsconfig/dist/__tests__/upsert-lockfile.spec.d.ts +6 -0
- package/tsconfig/dist/__tests__/upsert-lockfile.spec.d.ts.map +1 -0
- package/tsconfig/dist/__tests__/upsert-lockfile.spec.js +251 -0
- package/tsconfig/dist/__tests__/upsert-lockfile.spec.js.map +1 -0
- package/tsconfig/dist/__tests__/validate-config.spec.d.ts +2 -0
- package/tsconfig/dist/__tests__/validate-config.spec.d.ts.map +1 -0
- package/tsconfig/dist/__tests__/validate-config.spec.js +14 -0
- package/tsconfig/dist/__tests__/validate-config.spec.js.map +1 -0
- package/tsconfig/dist/api/init-workspace.d.ts +10 -0
- package/tsconfig/dist/api/init-workspace.d.ts.map +1 -0
- package/tsconfig/dist/api/init-workspace.js +191 -0
- package/tsconfig/dist/api/init-workspace.js.map +1 -0
- package/tsconfig/dist/api/product-config.d.ts +21 -0
- package/tsconfig/dist/api/product-config.d.ts.map +1 -0
- package/tsconfig/dist/api/product-config.js +192 -0
- package/tsconfig/dist/api/product-config.js.map +1 -0
- package/tsconfig/dist/api/read-config.d.ts +22 -0
- package/tsconfig/dist/api/read-config.d.ts.map +1 -0
- package/tsconfig/dist/api/read-config.js +105 -0
- package/tsconfig/dist/api/read-config.js.map +1 -0
- package/tsconfig/dist/api/upsert-lockfile.d.ts +10 -0
- package/tsconfig/dist/api/upsert-lockfile.d.ts.map +1 -0
- package/tsconfig/dist/api/upsert-lockfile.js +63 -0
- package/tsconfig/dist/api/upsert-lockfile.js.map +1 -0
- package/tsconfig/dist/cache/fs-cache.d.ts +38 -0
- package/tsconfig/dist/cache/fs-cache.d.ts.map +1 -0
- package/tsconfig/dist/cache/fs-cache.js +142 -0
- package/tsconfig/dist/cache/fs-cache.js.map +1 -0
- package/tsconfig/dist/errors/kb-error.d.ts +32 -0
- package/tsconfig/dist/errors/kb-error.d.ts.map +1 -0
- package/tsconfig/dist/errors/kb-error.js +54 -0
- package/tsconfig/dist/errors/kb-error.js.map +1 -0
- package/tsconfig/dist/fs/__tests__/fs.spec.d.ts +2 -0
- package/tsconfig/dist/fs/__tests__/fs.spec.d.ts.map +1 -0
- package/tsconfig/dist/fs/__tests__/fs.spec.js +22 -0
- package/tsconfig/dist/fs/__tests__/fs.spec.js.map +1 -0
- package/tsconfig/dist/fs/fs.d.ts +6 -0
- package/tsconfig/dist/fs/fs.d.ts.map +1 -0
- package/tsconfig/dist/fs/fs.js +12 -0
- package/tsconfig/dist/fs/fs.js.map +1 -0
- package/tsconfig/dist/fs/index.d.ts +2 -0
- package/tsconfig/dist/fs/index.d.ts.map +1 -0
- package/tsconfig/dist/fs/index.js +2 -0
- package/tsconfig/dist/fs/index.js.map +1 -0
- package/tsconfig/dist/hash/config-hash.d.ts +17 -0
- package/tsconfig/dist/hash/config-hash.d.ts.map +1 -0
- package/tsconfig/dist/hash/config-hash.js +55 -0
- package/tsconfig/dist/hash/config-hash.js.map +1 -0
- package/tsconfig/dist/index.d.ts +5 -0
- package/tsconfig/dist/index.d.ts.map +1 -0
- package/tsconfig/dist/index.js +5 -0
- package/tsconfig/dist/index.js.map +1 -0
- package/tsconfig/dist/lockfile/lockfile.d.ts +54 -0
- package/tsconfig/dist/lockfile/lockfile.d.ts.map +1 -0
- package/tsconfig/dist/lockfile/lockfile.js +141 -0
- package/tsconfig/dist/lockfile/lockfile.js.map +1 -0
- package/tsconfig/dist/logging/__tests__/logger.spec.d.ts +2 -0
- package/tsconfig/dist/logging/__tests__/logger.spec.d.ts.map +1 -0
- package/tsconfig/dist/logging/__tests__/logger.spec.js +65 -0
- package/tsconfig/dist/logging/__tests__/logger.spec.js.map +1 -0
- package/tsconfig/dist/logging/__tests__/redaction.spec.d.ts +2 -0
- package/tsconfig/dist/logging/__tests__/redaction.spec.d.ts.map +1 -0
- package/tsconfig/dist/logging/__tests__/redaction.spec.js +34 -0
- package/tsconfig/dist/logging/__tests__/redaction.spec.js.map +1 -0
- package/tsconfig/dist/logging/__tests__/sinks-and-env.spec.d.ts +2 -0
- package/tsconfig/dist/logging/__tests__/sinks-and-env.spec.d.ts.map +1 -0
- package/tsconfig/dist/logging/__tests__/sinks-and-env.spec.js +90 -0
- package/tsconfig/dist/logging/__tests__/sinks-and-env.spec.js.map +1 -0
- package/tsconfig/dist/logging/index.d.ts +6 -0
- package/tsconfig/dist/logging/index.d.ts.map +1 -0
- package/tsconfig/dist/logging/index.js +6 -0
- package/tsconfig/dist/logging/index.js.map +1 -0
- package/tsconfig/dist/logging/logger.d.ts +9 -0
- package/tsconfig/dist/logging/logger.d.ts.map +1 -0
- package/tsconfig/dist/logging/logger.js +101 -0
- package/tsconfig/dist/logging/logger.js.map +1 -0
- package/tsconfig/dist/logging/redaction.d.ts +7 -0
- package/tsconfig/dist/logging/redaction.d.ts.map +1 -0
- package/tsconfig/dist/logging/redaction.js +23 -0
- package/tsconfig/dist/logging/redaction.js.map +1 -0
- package/tsconfig/dist/logging/sinks/json.d.ts +4 -0
- package/tsconfig/dist/logging/sinks/json.d.ts.map +1 -0
- package/tsconfig/dist/logging/sinks/json.js +23 -0
- package/tsconfig/dist/logging/sinks/json.js.map +1 -0
- package/tsconfig/dist/logging/sinks/stdout.d.ts +3 -0
- package/tsconfig/dist/logging/sinks/stdout.d.ts.map +1 -0
- package/tsconfig/dist/logging/sinks/stdout.js +24 -0
- package/tsconfig/dist/logging/sinks/stdout.js.map +1 -0
- package/tsconfig/dist/logging/types/index.d.ts +2 -0
- package/tsconfig/dist/logging/types/index.d.ts.map +1 -0
- package/tsconfig/dist/logging/types/index.js +2 -0
- package/tsconfig/dist/logging/types/index.js.map +1 -0
- package/tsconfig/dist/logging/types/types.d.ts +37 -0
- package/tsconfig/dist/logging/types/types.d.ts.map +1 -0
- package/tsconfig/dist/logging/types/types.js +2 -0
- package/tsconfig/dist/logging/types/types.js.map +1 -0
- package/tsconfig/dist/merge/layered-merge.d.ts +16 -0
- package/tsconfig/dist/merge/layered-merge.d.ts.map +1 -0
- package/tsconfig/dist/merge/layered-merge.js +97 -0
- package/tsconfig/dist/merge/layered-merge.js.map +1 -0
- package/tsconfig/dist/preset/resolve-preset.d.ts +29 -0
- package/tsconfig/dist/preset/resolve-preset.d.ts.map +1 -0
- package/tsconfig/dist/preset/resolve-preset.js +104 -0
- package/tsconfig/dist/preset/resolve-preset.js.map +1 -0
- package/tsconfig/dist/repo/__tests__/repo.spec.d.ts +2 -0
- package/tsconfig/dist/repo/__tests__/repo.spec.d.ts.map +1 -0
- package/tsconfig/dist/repo/__tests__/repo.spec.js +25 -0
- package/tsconfig/dist/repo/__tests__/repo.spec.js.map +1 -0
- package/tsconfig/dist/repo/index.d.ts +2 -0
- package/tsconfig/dist/repo/index.d.ts.map +1 -0
- package/tsconfig/dist/repo/index.js +2 -0
- package/tsconfig/dist/repo/index.js.map +1 -0
- package/tsconfig/dist/repo/repo.d.ts +6 -0
- package/tsconfig/dist/repo/repo.d.ts.map +1 -0
- package/tsconfig/dist/repo/repo.js +25 -0
- package/tsconfig/dist/repo/repo.js.map +1 -0
- package/tsconfig/dist/runtime/index.d.ts +2 -0
- package/tsconfig/dist/runtime/index.d.ts.map +1 -0
- package/tsconfig/dist/runtime/index.js +2 -0
- package/tsconfig/dist/runtime/index.js.map +1 -0
- package/tsconfig/dist/runtime/runtime.d.ts +46 -0
- package/tsconfig/dist/runtime/runtime.d.ts.map +1 -0
- package/tsconfig/dist/runtime/runtime.js +126 -0
- package/tsconfig/dist/runtime/runtime.js.map +1 -0
- package/tsconfig/dist/tsconfig.tools.tsbuildinfo +1 -0
- package/tsconfig/dist/tsconfig.tsbuildinfo +1 -0
- package/tsconfig/dist/types/index.d.ts +2 -0
- package/tsconfig/dist/types/index.d.ts.map +1 -0
- package/tsconfig/dist/types/index.js +2 -0
- package/tsconfig/dist/types/index.js.map +1 -0
- package/tsconfig/dist/types/init.d.ts +34 -0
- package/tsconfig/dist/types/init.d.ts.map +1 -0
- package/tsconfig/dist/types/init.js +6 -0
- package/tsconfig/dist/types/init.js.map +1 -0
- package/tsconfig/dist/types/preset.d.ts +27 -0
- package/tsconfig/dist/types/preset.d.ts.map +1 -0
- package/tsconfig/dist/types/preset.js +6 -0
- package/tsconfig/dist/types/preset.js.map +1 -0
- package/tsconfig/dist/types/types.d.ts +6 -0
- package/tsconfig/dist/types/types.d.ts.map +1 -0
- package/tsconfig/dist/types/types.js +2 -0
- package/tsconfig/dist/types/types.js.map +1 -0
- package/tsconfig/dist/utils/__tests__/env.spec.d.ts +2 -0
- package/tsconfig/dist/utils/__tests__/env.spec.d.ts.map +1 -0
- package/tsconfig/dist/utils/__tests__/env.spec.js +33 -0
- package/tsconfig/dist/utils/__tests__/env.spec.js.map +1 -0
- package/tsconfig/dist/utils/env.d.ts +7 -0
- package/tsconfig/dist/utils/env.d.ts.map +1 -0
- package/tsconfig/dist/utils/env.js +25 -0
- package/tsconfig/dist/utils/env.js.map +1 -0
- package/tsconfig/dist/utils/fs-atomic.d.ts +15 -0
- package/tsconfig/dist/utils/fs-atomic.d.ts.map +1 -0
- package/tsconfig/dist/utils/fs-atomic.js +45 -0
- package/tsconfig/dist/utils/fs-atomic.js.map +1 -0
- package/tsconfig/dist/utils/index.d.ts +3 -0
- package/tsconfig/dist/utils/index.d.ts.map +1 -0
- package/tsconfig/dist/utils/index.js +3 -0
- package/tsconfig/dist/utils/index.js.map +1 -0
- package/tsconfig/dist/utils/paths.d.ts +21 -0
- package/tsconfig/dist/utils/paths.d.ts.map +1 -0
- package/tsconfig/dist/utils/paths.js +32 -0
- package/tsconfig/dist/utils/paths.js.map +1 -0
- package/tsconfig/dist/utils/product-normalize.d.ts +27 -0
- package/tsconfig/dist/utils/product-normalize.d.ts.map +1 -0
- package/tsconfig/dist/utils/product-normalize.js +45 -0
- package/tsconfig/dist/utils/product-normalize.js.map +1 -0
- package/tsconfig/dist/validation/validate-config.d.ts +7 -0
- package/tsconfig/dist/validation/validate-config.d.ts.map +1 -0
- package/tsconfig/dist/validation/validate-config.js +22 -0
- package/tsconfig/dist/validation/validate-config.js.map +1 -0
- package/tsconfig/lib.json +13 -0
- package/tsconfig/node.json +12 -0
- package/tsconfig/react-app.json +8 -0
- package/tsconfig/react-lib.json +8 -0
- package/tsconfig/test.json +15 -0
- package/tsup/bin.js +172 -0
- package/tsup/dual.js +155 -0
- package/tsup/external-sync.mjs +41 -0
- package/tsup/external.mjs +109 -0
- package/tsup/node.js +65 -0
- package/tsup/react-lib.js +18 -0
- package/tsup/sdk.js +118 -0
- package/vite/react-app.js +50 -0
- package/vitest/node.js +37 -0
- package/vitest/react.js +39 -0
- package/vitest/vitest-setup.ts +9 -0
|
@@ -0,0 +1,1169 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @kb-labs/devkit - Dependency Auto-Fixer
|
|
5
|
+
*
|
|
6
|
+
* Automatically fixes common dependency issues:
|
|
7
|
+
* 1. Removes unused dependencies
|
|
8
|
+
* 2. Adds missing workspace dependencies
|
|
9
|
+
* 3. Adds missing npm packages
|
|
10
|
+
* 4. Aligns duplicate dependency versions
|
|
11
|
+
*
|
|
12
|
+
* Usage:
|
|
13
|
+
* kb-devkit-fix-deps --remove-unused # Remove unused dependencies
|
|
14
|
+
* kb-devkit-fix-deps --add-missing # Add missing workspace deps
|
|
15
|
+
* kb-devkit-fix-deps --add-missing-npm # Add missing npm packages
|
|
16
|
+
* kb-devkit-fix-deps --align-versions # Align duplicate versions
|
|
17
|
+
* kb-devkit-fix-deps --all # Apply all fixes
|
|
18
|
+
* kb-devkit-fix-deps --dry-run # Show what would be changed
|
|
19
|
+
* kb-devkit-fix-deps --orphans # Find packages not used by anyone
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import fs from 'node:fs';
|
|
23
|
+
import path from 'node:path';
|
|
24
|
+
import { fileURLToPath } from 'node:url';
|
|
25
|
+
import { execSync } from 'node:child_process';
|
|
26
|
+
|
|
27
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
28
|
+
|
|
29
|
+
// ANSI colors
|
|
30
|
+
const colors = {
|
|
31
|
+
reset: '\x1b[0m',
|
|
32
|
+
red: '\x1b[31m',
|
|
33
|
+
green: '\x1b[32m',
|
|
34
|
+
yellow: '\x1b[33m',
|
|
35
|
+
blue: '\x1b[34m',
|
|
36
|
+
magenta: '\x1b[35m',
|
|
37
|
+
cyan: '\x1b[36m',
|
|
38
|
+
gray: '\x1b[90m',
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
function log(message, color = 'reset') {
|
|
42
|
+
console.log(`${colors[color]}${message}${colors.reset}`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Parse CLI arguments
|
|
46
|
+
const args = process.argv.slice(2);
|
|
47
|
+
const options = {
|
|
48
|
+
removeUnused: args.includes('--remove-unused'),
|
|
49
|
+
addMissing: args.includes('--add-missing'),
|
|
50
|
+
addMissingNpm: args.includes('--add-missing-npm'),
|
|
51
|
+
alignVersions: args.includes('--align-versions'),
|
|
52
|
+
all: args.includes('--all'),
|
|
53
|
+
dryRun: args.includes('--dry-run'),
|
|
54
|
+
verbose: args.includes('--verbose') || args.includes('-v'),
|
|
55
|
+
showStats: args.includes('--stats'),
|
|
56
|
+
showOrphans: args.includes('--orphans'),
|
|
57
|
+
json: args.includes('--json'),
|
|
58
|
+
package: args.find((arg) => arg.startsWith('--package='))?.split('=')[1],
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
// If --all, enable all fixes
|
|
62
|
+
if (options.all) {
|
|
63
|
+
options.removeUnused = true;
|
|
64
|
+
options.addMissing = true;
|
|
65
|
+
options.addMissingNpm = true;
|
|
66
|
+
options.alignVersions = true;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Find all packages in monorepo
|
|
71
|
+
*/
|
|
72
|
+
function findPackages(rootDir, filterPackage) {
|
|
73
|
+
const packages = [];
|
|
74
|
+
const entries = fs.readdirSync(rootDir, { withFileTypes: true });
|
|
75
|
+
|
|
76
|
+
for (const entry of entries) {
|
|
77
|
+
if (!entry.isDirectory() || !entry.name.startsWith('kb-labs-')) {continue;}
|
|
78
|
+
|
|
79
|
+
const repoPath = path.join(rootDir, entry.name);
|
|
80
|
+
const packagesDir = path.join(repoPath, 'packages');
|
|
81
|
+
|
|
82
|
+
if (!fs.existsSync(packagesDir)) {continue;}
|
|
83
|
+
|
|
84
|
+
const packageDirs = fs.readdirSync(packagesDir, { withFileTypes: true });
|
|
85
|
+
|
|
86
|
+
for (const pkgDir of packageDirs) {
|
|
87
|
+
if (!pkgDir.isDirectory()) {continue;}
|
|
88
|
+
|
|
89
|
+
if (filterPackage && pkgDir.name !== filterPackage) {continue;}
|
|
90
|
+
|
|
91
|
+
const packageJsonPath = path.join(packagesDir, pkgDir.name, 'package.json');
|
|
92
|
+
|
|
93
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
94
|
+
packages.push(packageJsonPath);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return packages;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Extract imports from source files
|
|
104
|
+
*/
|
|
105
|
+
function extractImportsFromFile(filePath) {
|
|
106
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
107
|
+
const imports = new Set();
|
|
108
|
+
|
|
109
|
+
const patterns = [
|
|
110
|
+
// Static imports: import X from 'pkg'
|
|
111
|
+
/import\s+(?:[\w*{}\n\r\t, ]+\s+from\s+)?['"]([^'"]+)['"]/g,
|
|
112
|
+
// Dynamic imports: import('pkg')
|
|
113
|
+
/import\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
|
|
114
|
+
// Require: require('pkg')
|
|
115
|
+
/require\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
|
|
116
|
+
// Template literal dynamic imports: import(`pkg`)
|
|
117
|
+
/import\s*\(\s*`([^`]+)`\s*\)/g,
|
|
118
|
+
// Template literal require: require(`pkg`)
|
|
119
|
+
/require\s*\(\s*`([^`]+)`\s*\)/g,
|
|
120
|
+
];
|
|
121
|
+
|
|
122
|
+
for (const pattern of patterns) {
|
|
123
|
+
let match;
|
|
124
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
125
|
+
const importPath = match[1];
|
|
126
|
+
|
|
127
|
+
// Skip template variables like ${var} - we can't analyze these statically
|
|
128
|
+
if (importPath.includes('${')) {
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Extract package name (handle scoped packages)
|
|
133
|
+
if (!importPath.startsWith('.') && !importPath.startsWith('/')) {
|
|
134
|
+
const pkgMatch = importPath.match(/^(@[^/]+\/[^/]+|[^/]+)/);
|
|
135
|
+
if (pkgMatch) {
|
|
136
|
+
imports.add(pkgMatch[1]);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return imports;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Find all imports in package
|
|
147
|
+
*/
|
|
148
|
+
function findUsedDependencies(packageDir) {
|
|
149
|
+
const used = new Set();
|
|
150
|
+
|
|
151
|
+
// Directories to scan
|
|
152
|
+
const dirsToScan = ['src', 'test', 'tests', '__tests__', 'scripts', 'bin'];
|
|
153
|
+
|
|
154
|
+
for (const dir of dirsToScan) {
|
|
155
|
+
const dirPath = path.join(packageDir, dir);
|
|
156
|
+
if (fs.existsSync(dirPath)) {
|
|
157
|
+
walkDir(dirPath, used);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Also scan config files in root
|
|
162
|
+
const configPatterns = [
|
|
163
|
+
'tsup.config.ts',
|
|
164
|
+
'tsup.config.js',
|
|
165
|
+
'vitest.config.ts',
|
|
166
|
+
'vitest.config.js',
|
|
167
|
+
'vite.config.ts',
|
|
168
|
+
'vite.config.js',
|
|
169
|
+
'eslint.config.js',
|
|
170
|
+
'eslint.config.mjs',
|
|
171
|
+
'jest.config.ts',
|
|
172
|
+
'jest.config.js',
|
|
173
|
+
'rollup.config.ts',
|
|
174
|
+
'rollup.config.js',
|
|
175
|
+
];
|
|
176
|
+
|
|
177
|
+
for (const configFile of configPatterns) {
|
|
178
|
+
const configPath = path.join(packageDir, configFile);
|
|
179
|
+
if (fs.existsSync(configPath)) {
|
|
180
|
+
const imports = extractImportsFromFile(configPath);
|
|
181
|
+
imports.forEach((imp) => used.add(imp));
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Scan package.json scripts for CLI tool usage
|
|
186
|
+
const packageJsonPath = path.join(packageDir, 'package.json');
|
|
187
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
188
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
189
|
+
if (packageJson.scripts) {
|
|
190
|
+
for (const script of Object.values(packageJson.scripts)) {
|
|
191
|
+
// Extract command names from scripts (first word before space or flags)
|
|
192
|
+
const commands = script.match(/\b([a-z0-9@\-_/.]+)/gi) || [];
|
|
193
|
+
for (const cmd of commands) {
|
|
194
|
+
// Extract package name from command (e.g., "tsx" from "tsx src/index.ts")
|
|
195
|
+
// Handle scoped packages (e.g., "@vitest/ui")
|
|
196
|
+
if (cmd.startsWith('@')) {
|
|
197
|
+
const scopedMatch = cmd.match(/^(@[^/]+\/[^/\s]+)/);
|
|
198
|
+
if (scopedMatch) {
|
|
199
|
+
used.add(scopedMatch[1]);
|
|
200
|
+
}
|
|
201
|
+
} else {
|
|
202
|
+
const pkgMatch = cmd.match(/^([a-z0-9\-_]+)/);
|
|
203
|
+
if (pkgMatch) {
|
|
204
|
+
used.add(pkgMatch[1]);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return used;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Walk directory and collect imports
|
|
217
|
+
*/
|
|
218
|
+
function walkDir(dir, used) {
|
|
219
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
220
|
+
|
|
221
|
+
for (const entry of entries) {
|
|
222
|
+
const fullPath = path.join(dir, entry.name);
|
|
223
|
+
|
|
224
|
+
if (entry.isDirectory() && entry.name !== 'node_modules' && entry.name !== 'dist') {
|
|
225
|
+
walkDir(fullPath, used);
|
|
226
|
+
} else if (entry.isFile() && /\.(ts|tsx|js|jsx|mjs|cjs)$/.test(entry.name)) {
|
|
227
|
+
const imports = extractImportsFromFile(fullPath);
|
|
228
|
+
imports.forEach((imp) => used.add(imp));
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Remove unused dependencies
|
|
235
|
+
*/
|
|
236
|
+
function removeUnusedDependencies(packageJsonPath, dryRun, verbose = false) {
|
|
237
|
+
const packageDir = path.dirname(packageJsonPath);
|
|
238
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
239
|
+
const packageName = packageJson.name;
|
|
240
|
+
|
|
241
|
+
if (!packageName || !packageName.startsWith('@kb-labs/')) {
|
|
242
|
+
return { removed: 0, deps: [], kept: [] };
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const used = findUsedDependencies(packageDir);
|
|
246
|
+
const kept = []; // For verbose output
|
|
247
|
+
const allDeps = { ...packageJson.dependencies, ...packageJson.devDependencies };
|
|
248
|
+
const peerDeps = packageJson.peerDependencies || {};
|
|
249
|
+
const toRemove = [];
|
|
250
|
+
|
|
251
|
+
// Also mark dependencies used in bin scripts as used
|
|
252
|
+
if (packageJson.bin) {
|
|
253
|
+
const binScripts = typeof packageJson.bin === 'string'
|
|
254
|
+
? [packageJson.bin]
|
|
255
|
+
: Object.values(packageJson.bin);
|
|
256
|
+
|
|
257
|
+
for (const binPath of binScripts) {
|
|
258
|
+
const fullBinPath = path.join(packageDir, binPath);
|
|
259
|
+
if (fs.existsSync(fullBinPath)) {
|
|
260
|
+
const imports = extractImportsFromFile(fullBinPath);
|
|
261
|
+
imports.forEach((imp) => used.add(imp));
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Build tools that should not be removed (used via bin/cli, not imports)
|
|
267
|
+
const keepDeps = new Set([
|
|
268
|
+
// TypeScript & build
|
|
269
|
+
'typescript',
|
|
270
|
+
'tsup',
|
|
271
|
+
'esbuild',
|
|
272
|
+
'rollup',
|
|
273
|
+
'vite',
|
|
274
|
+
'rimraf',
|
|
275
|
+
'concurrently',
|
|
276
|
+
'cross-env',
|
|
277
|
+
'tsx',
|
|
278
|
+
'ts-node',
|
|
279
|
+
'npm-run-all',
|
|
280
|
+
'npm-run-all2',
|
|
281
|
+
'wait-on',
|
|
282
|
+
'nodemon',
|
|
283
|
+
'chokidar-cli',
|
|
284
|
+
|
|
285
|
+
// Linting & formatting
|
|
286
|
+
'eslint',
|
|
287
|
+
'prettier',
|
|
288
|
+
'oxlint',
|
|
289
|
+
|
|
290
|
+
// Testing
|
|
291
|
+
'vitest',
|
|
292
|
+
'jest',
|
|
293
|
+
'mocha',
|
|
294
|
+
'nyc',
|
|
295
|
+
'c8',
|
|
296
|
+
'@vitest/coverage-v8',
|
|
297
|
+
'@vitest/ui',
|
|
298
|
+
'playwright',
|
|
299
|
+
'@playwright/test',
|
|
300
|
+
'happy-dom',
|
|
301
|
+
'jsdom',
|
|
302
|
+
|
|
303
|
+
// KB Labs devkit
|
|
304
|
+
'@kb-labs/devkit',
|
|
305
|
+
|
|
306
|
+
// Node types (used implicitly)
|
|
307
|
+
'@types/node',
|
|
308
|
+
|
|
309
|
+
// Package managers & publish tools
|
|
310
|
+
'changesets',
|
|
311
|
+
'@changesets/cli',
|
|
312
|
+
'turbo',
|
|
313
|
+
'lerna',
|
|
314
|
+
]);
|
|
315
|
+
|
|
316
|
+
// Patterns for packages that shouldn't be removed
|
|
317
|
+
const keepPatterns = [
|
|
318
|
+
/^@types\//, // Type definitions
|
|
319
|
+
/^eslint-/, // ESLint plugins/configs
|
|
320
|
+
/^@eslint\//, // ESLint scoped packages
|
|
321
|
+
/^@vitest\//, // Vitest plugins
|
|
322
|
+
/^@testing-library\//, // Testing library
|
|
323
|
+
/^@typescript-eslint/, // TS ESLint
|
|
324
|
+
/^@vitejs\//, // Vite plugins
|
|
325
|
+
/^vite-plugin-/, // Vite plugins
|
|
326
|
+
/^rollup-plugin-/, // Rollup plugins
|
|
327
|
+
/^@rollup\//, // Rollup scoped
|
|
328
|
+
/^prettier-plugin-/, // Prettier plugins
|
|
329
|
+
];
|
|
330
|
+
|
|
331
|
+
for (const dep of Object.keys(allDeps)) {
|
|
332
|
+
// Always keep workspace dependencies (they define the architecture)
|
|
333
|
+
const depValue = allDeps[dep];
|
|
334
|
+
if (typeof depValue === 'string' && (depValue.startsWith('workspace:') || depValue.startsWith('link:'))) {
|
|
335
|
+
if (verbose) {kept.push({ dep, reason: 'workspace/link dependency' });}
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Skip if it's used in code
|
|
340
|
+
if (used.has(dep)) {
|
|
341
|
+
if (verbose) {kept.push({ dep, reason: 'used in code' });}
|
|
342
|
+
continue;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Skip if it's a peer dependency (consumers might need it)
|
|
346
|
+
if (peerDeps[dep]) {
|
|
347
|
+
if (verbose) {kept.push({ dep, reason: 'peer dependency' });}
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Skip if it's in keepDeps
|
|
352
|
+
if (keepDeps.has(dep)) {
|
|
353
|
+
if (verbose) {kept.push({ dep, reason: 'build tool' });}
|
|
354
|
+
continue;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Skip if matches any keep pattern
|
|
358
|
+
const matchedPattern = keepPatterns.find((pattern) => pattern.test(dep));
|
|
359
|
+
if (matchedPattern) {
|
|
360
|
+
if (verbose) {kept.push({ dep, reason: `pattern: ${matchedPattern.source}` });}
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
toRemove.push(dep);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
if (toRemove.length > 0) {
|
|
368
|
+
if (!dryRun) {
|
|
369
|
+
// Remove from both dependencies and devDependencies
|
|
370
|
+
for (const dep of toRemove) {
|
|
371
|
+
delete packageJson.dependencies?.[dep];
|
|
372
|
+
delete packageJson.devDependencies?.[dep];
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n');
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
return { removed: toRemove.length, deps: toRemove, packageName, kept, totalDeps: Object.keys(allDeps).length };
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
return { removed: 0, deps: [], packageName, kept, totalDeps: Object.keys(allDeps).length };
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Add missing workspace dependencies
|
|
386
|
+
*/
|
|
387
|
+
function addMissingDependencies(packageJsonPath, dryRun, allPackages) {
|
|
388
|
+
const packageDir = path.dirname(packageJsonPath);
|
|
389
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
390
|
+
const packageName = packageJson.name;
|
|
391
|
+
|
|
392
|
+
if (!packageName || !packageName.startsWith('@kb-labs/')) {
|
|
393
|
+
return { added: 0, deps: [] };
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const used = findUsedDependencies(packageDir);
|
|
397
|
+
const existing = { ...packageJson.dependencies, ...packageJson.devDependencies };
|
|
398
|
+
const toAdd = [];
|
|
399
|
+
|
|
400
|
+
// Find missing workspace packages
|
|
401
|
+
for (const dep of used) {
|
|
402
|
+
if (dep.startsWith('@kb-labs/') && !existing[dep]) {
|
|
403
|
+
// Check if this package exists in workspace
|
|
404
|
+
const exists = allPackages.some((p) => {
|
|
405
|
+
const json = JSON.parse(fs.readFileSync(p, 'utf-8'));
|
|
406
|
+
return json.name === dep;
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
if (exists) {
|
|
410
|
+
toAdd.push(dep);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
if (toAdd.length > 0) {
|
|
416
|
+
if (!dryRun) {
|
|
417
|
+
if (!packageJson.dependencies) {
|
|
418
|
+
packageJson.dependencies = {};
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
for (const dep of toAdd) {
|
|
422
|
+
packageJson.dependencies[dep] = 'workspace:*';
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// Sort dependencies
|
|
426
|
+
packageJson.dependencies = Object.fromEntries(
|
|
427
|
+
Object.entries(packageJson.dependencies).sort()
|
|
428
|
+
);
|
|
429
|
+
|
|
430
|
+
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n');
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
return { added: toAdd.length, deps: toAdd, packageName };
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
return { added: 0, deps: [], packageName };
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Add missing npm dependencies (non-workspace packages)
|
|
441
|
+
* Uses devkit-check-imports to find missing npm packages
|
|
442
|
+
*/
|
|
443
|
+
function addMissingNpmDependencies(packageJsonPath, dryRun) {
|
|
444
|
+
const packageDir = path.dirname(packageJsonPath);
|
|
445
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
446
|
+
const packageName = packageJson.name;
|
|
447
|
+
|
|
448
|
+
if (!packageName || !packageName.startsWith('@kb-labs/')) {
|
|
449
|
+
return { added: 0, deps: [], packageName };
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// Run devkit-check-imports for this package to get missing npm deps
|
|
453
|
+
const shortName = packageName.replace('@kb-labs/', '');
|
|
454
|
+
const missingNpmDeps = [];
|
|
455
|
+
|
|
456
|
+
try {
|
|
457
|
+
const output = execSync(
|
|
458
|
+
`node ${path.join(__dirname, 'devkit-check-imports.mjs')} --package=${shortName} 2>&1`,
|
|
459
|
+
{ encoding: 'utf-8', stdio: 'pipe' }
|
|
460
|
+
);
|
|
461
|
+
|
|
462
|
+
// Parse output to extract missing npm dependencies
|
|
463
|
+
const lines = output.split('\n');
|
|
464
|
+
let inMissingNpmSection = false;
|
|
465
|
+
|
|
466
|
+
for (let i = 0; i < lines.length; i++) {
|
|
467
|
+
const line = lines[i];
|
|
468
|
+
|
|
469
|
+
// Check if we're in the "Missing npm dependencies" section
|
|
470
|
+
if (line.includes('Missing npm dependencies')) {
|
|
471
|
+
inMissingNpmSection = true;
|
|
472
|
+
continue;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// Exit section when we hit a separator, new section, or empty line after deps
|
|
476
|
+
if (inMissingNpmSection && (line.includes('────') || line.includes('📊') || line.includes('🔴') || line.includes('🟠') || line.includes('🔄') || line.includes('Summary'))) {
|
|
477
|
+
inMissingNpmSection = false;
|
|
478
|
+
break;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// Extract dependency name from lines like "[36m pino[0m"
|
|
482
|
+
if (inMissingNpmSection && line.trim() && !line.includes('└─') && !line.includes('Used in')) {
|
|
483
|
+
// Remove ANSI color codes and whitespace
|
|
484
|
+
const cleaned = line.replace(/\x1b\[[0-9;]*m/g, '').replace(/\[[0-9]+m/g, '').trim();
|
|
485
|
+
if (cleaned && /^[a-z@]/.test(cleaned) && !cleaned.includes('Missing') && !cleaned.includes('dependencies')) {
|
|
486
|
+
missingNpmDeps.push(cleaned);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
} catch (error) {
|
|
491
|
+
// If check-imports fails (non-zero exit), still try to parse output
|
|
492
|
+
if (error.stdout) {
|
|
493
|
+
const lines = error.stdout.toString().split('\n');
|
|
494
|
+
let inMissingNpmSection = false;
|
|
495
|
+
|
|
496
|
+
for (const line of lines) {
|
|
497
|
+
if (line.includes('Missing npm dependencies')) {
|
|
498
|
+
inMissingNpmSection = true;
|
|
499
|
+
continue;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
if (inMissingNpmSection && (line.includes('────') || line.includes('📊') || line.includes('🔴') || line.includes('🟠') || line.includes('🔄'))) {
|
|
503
|
+
inMissingNpmSection = false;
|
|
504
|
+
break;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
if (inMissingNpmSection && line.trim() && !line.includes('└─') && !line.includes('Used in')) {
|
|
508
|
+
const cleaned = line.replace(/\x1b\[[0-9;]*m/g, '').replace(/\[[0-9]+m/g, '').trim();
|
|
509
|
+
if (cleaned && /^[a-z@]/.test(cleaned) && !cleaned.includes('Missing')) {
|
|
510
|
+
missingNpmDeps.push(cleaned);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// If still no deps found, return empty
|
|
517
|
+
if (missingNpmDeps.length === 0) {
|
|
518
|
+
return { added: 0, deps: [], packageName };
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
if (missingNpmDeps.length === 0) {
|
|
523
|
+
return { added: 0, deps: [], packageName };
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
const existing = { ...packageJson.dependencies, ...packageJson.devDependencies };
|
|
527
|
+
const toAdd = [];
|
|
528
|
+
|
|
529
|
+
// Get most common version from other packages in monorepo
|
|
530
|
+
const rootDir = path.resolve(__dirname, '..');
|
|
531
|
+
const allPackages = findPackages(rootDir);
|
|
532
|
+
|
|
533
|
+
for (const npmDep of missingNpmDeps) {
|
|
534
|
+
// Skip if trying to add package to itself
|
|
535
|
+
if (npmDep === packageName) {
|
|
536
|
+
continue;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
if (!existing[npmDep]) {
|
|
540
|
+
// Find version used in other packages
|
|
541
|
+
let version = null;
|
|
542
|
+
for (const pkgPath of allPackages) {
|
|
543
|
+
const pkgJson = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
544
|
+
const allDeps = { ...pkgJson.dependencies, ...pkgJson.devDependencies };
|
|
545
|
+
if (allDeps[npmDep]) {
|
|
546
|
+
version = allDeps[npmDep];
|
|
547
|
+
break;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// If not found in other packages, use 'latest' placeholder
|
|
552
|
+
if (!version) {
|
|
553
|
+
version = 'latest';
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
toAdd.push({ name: npmDep, version });
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
if (toAdd.length > 0) {
|
|
561
|
+
if (!dryRun) {
|
|
562
|
+
if (!packageJson.dependencies) {
|
|
563
|
+
packageJson.dependencies = {};
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
for (const { name, version } of toAdd) {
|
|
567
|
+
packageJson.dependencies[name] = version;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
// Sort dependencies
|
|
571
|
+
packageJson.dependencies = Object.fromEntries(
|
|
572
|
+
Object.entries(packageJson.dependencies).sort()
|
|
573
|
+
);
|
|
574
|
+
|
|
575
|
+
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n');
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
return { added: toAdd.length, deps: toAdd.map((d) => `${d.name}@${d.version}`), packageName };
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
return { added: 0, deps: [], packageName };
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
/**
|
|
585
|
+
* Collect version statistics
|
|
586
|
+
*/
|
|
587
|
+
function collectVersionStats(packages) {
|
|
588
|
+
const versionMap = new Map();
|
|
589
|
+
|
|
590
|
+
for (const packagePath of packages) {
|
|
591
|
+
const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf-8'));
|
|
592
|
+
const allDeps = {
|
|
593
|
+
...packageJson.dependencies,
|
|
594
|
+
...packageJson.devDependencies,
|
|
595
|
+
};
|
|
596
|
+
|
|
597
|
+
for (const [dep, version] of Object.entries(allDeps)) {
|
|
598
|
+
if (version.startsWith('workspace:') || version.startsWith('link:')) {
|
|
599
|
+
continue;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
if (!versionMap.has(dep)) {
|
|
603
|
+
versionMap.set(dep, new Map());
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
const versions = versionMap.get(dep);
|
|
607
|
+
if (!versions.has(version)) {
|
|
608
|
+
versions.set(version, []);
|
|
609
|
+
}
|
|
610
|
+
versions.get(version).push(packagePath);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
return versionMap;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
/**
|
|
618
|
+
* Align dependency versions
|
|
619
|
+
*/
|
|
620
|
+
function alignDependencyVersions(packages, dryRun) {
|
|
621
|
+
const versionMap = collectVersionStats(packages);
|
|
622
|
+
let totalAligned = 0;
|
|
623
|
+
const changes = [];
|
|
624
|
+
|
|
625
|
+
for (const [dep, versions] of versionMap.entries()) {
|
|
626
|
+
if (versions.size <= 1) {continue;}
|
|
627
|
+
|
|
628
|
+
// Pick the most common version (or newest if tie)
|
|
629
|
+
const versionArray = Array.from(versions.entries()).sort(
|
|
630
|
+
(a, b) => b[1].length - a[1].length
|
|
631
|
+
);
|
|
632
|
+
|
|
633
|
+
const targetVersion = versionArray[0][0];
|
|
634
|
+
const targetCount = versionArray[0][1].length;
|
|
635
|
+
|
|
636
|
+
// Align all packages to target version
|
|
637
|
+
for (const [version, packagePaths] of versions.entries()) {
|
|
638
|
+
if (version === targetVersion) {continue;}
|
|
639
|
+
|
|
640
|
+
for (const packagePath of packagePaths) {
|
|
641
|
+
if (!dryRun) {
|
|
642
|
+
const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf-8'));
|
|
643
|
+
|
|
644
|
+
if (packageJson.dependencies?.[dep]) {
|
|
645
|
+
packageJson.dependencies[dep] = targetVersion;
|
|
646
|
+
}
|
|
647
|
+
if (packageJson.devDependencies?.[dep]) {
|
|
648
|
+
packageJson.devDependencies[dep] = targetVersion;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
fs.writeFileSync(packagePath, JSON.stringify(packageJson, null, 2) + '\n');
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
totalAligned++;
|
|
655
|
+
changes.push({
|
|
656
|
+
package: JSON.parse(fs.readFileSync(packagePath, 'utf-8')).name,
|
|
657
|
+
dep,
|
|
658
|
+
from: version,
|
|
659
|
+
to: targetVersion,
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
return { totalAligned, changes };
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
/**
|
|
669
|
+
* Show dependency statistics
|
|
670
|
+
*/
|
|
671
|
+
function showDependencyStats(packages) {
|
|
672
|
+
log('\n📊 Dependency Statistics\n', 'cyan');
|
|
673
|
+
|
|
674
|
+
let totalDeps = 0;
|
|
675
|
+
let totalDevDeps = 0;
|
|
676
|
+
let totalPeerDeps = 0;
|
|
677
|
+
let unusedCount = 0;
|
|
678
|
+
const depUsage = new Map(); // dep -> count of packages using it
|
|
679
|
+
|
|
680
|
+
for (const packagePath of packages) {
|
|
681
|
+
const packageDir = path.dirname(packagePath);
|
|
682
|
+
const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf-8'));
|
|
683
|
+
|
|
684
|
+
const deps = Object.keys(packageJson.dependencies || {});
|
|
685
|
+
const devDeps = Object.keys(packageJson.devDependencies || {});
|
|
686
|
+
const peerDeps = Object.keys(packageJson.peerDependencies || {});
|
|
687
|
+
|
|
688
|
+
totalDeps += deps.length;
|
|
689
|
+
totalDevDeps += devDeps.length;
|
|
690
|
+
totalPeerDeps += peerDeps.length;
|
|
691
|
+
|
|
692
|
+
// Track dep usage across packages
|
|
693
|
+
for (const dep of [...deps, ...devDeps]) {
|
|
694
|
+
depUsage.set(dep, (depUsage.get(dep) || 0) + 1);
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
// Check for unused
|
|
698
|
+
const used = findUsedDependencies(packageDir);
|
|
699
|
+
for (const dep of deps) {
|
|
700
|
+
if (!used.has(dep) && !dep.startsWith('@types/')) {
|
|
701
|
+
unusedCount++;
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
log(`📦 Total packages: ${packages.length}`, 'cyan');
|
|
707
|
+
log(`📚 Total dependencies: ${totalDeps}`, 'cyan');
|
|
708
|
+
log(`🔧 Total devDependencies: ${totalDevDeps}`, 'cyan');
|
|
709
|
+
log(`🔗 Total peerDependencies: ${totalPeerDeps}`, 'cyan');
|
|
710
|
+
log('', 'reset');
|
|
711
|
+
|
|
712
|
+
// Top used dependencies
|
|
713
|
+
const sortedDeps = Array.from(depUsage.entries())
|
|
714
|
+
.sort((a, b) => b[1] - a[1])
|
|
715
|
+
.slice(0, 10);
|
|
716
|
+
|
|
717
|
+
log('🔝 Top 10 Most Used Dependencies:\n', 'blue');
|
|
718
|
+
for (let i = 0; i < sortedDeps.length; i++) {
|
|
719
|
+
const [dep, count] = sortedDeps[i];
|
|
720
|
+
log(` ${(i + 1).toString().padStart(2)}. ${dep} (${count} packages)`, 'gray');
|
|
721
|
+
}
|
|
722
|
+
log('', 'reset');
|
|
723
|
+
|
|
724
|
+
// Potentially unused (very rough estimate)
|
|
725
|
+
if (unusedCount > 0) {
|
|
726
|
+
log(`⚠️ Potentially unused deps: ${unusedCount}`, 'yellow');
|
|
727
|
+
log(' Run --remove-unused --dry-run to see details\n', 'gray');
|
|
728
|
+
} else {
|
|
729
|
+
log('✅ No obviously unused dependencies detected\n', 'green');
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
/**
|
|
734
|
+
* Find orphan packages - packages that no other package depends on
|
|
735
|
+
*/
|
|
736
|
+
function findOrphanPackages(packages) {
|
|
737
|
+
// Build map of package names to their paths
|
|
738
|
+
const packageMap = new Map();
|
|
739
|
+
for (const packagePath of packages) {
|
|
740
|
+
const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf-8'));
|
|
741
|
+
if (packageJson.name?.startsWith('@kb-labs/')) {
|
|
742
|
+
packageMap.set(packageJson.name, {
|
|
743
|
+
path: packagePath,
|
|
744
|
+
dir: path.dirname(packagePath),
|
|
745
|
+
repo: path.basename(path.dirname(path.dirname(path.dirname(packagePath)))),
|
|
746
|
+
});
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
// Collect all dependencies across all packages
|
|
751
|
+
const dependedOn = new Set();
|
|
752
|
+
for (const packagePath of packages) {
|
|
753
|
+
const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf-8'));
|
|
754
|
+
|
|
755
|
+
// Combine all dependency types
|
|
756
|
+
const allDeps = [
|
|
757
|
+
...Object.keys(packageJson.dependencies || {}),
|
|
758
|
+
...Object.keys(packageJson.devDependencies || {}),
|
|
759
|
+
...Object.keys(packageJson.peerDependencies || {}),
|
|
760
|
+
];
|
|
761
|
+
|
|
762
|
+
for (const dep of allDeps) {
|
|
763
|
+
if (dep.startsWith('@kb-labs/')) {
|
|
764
|
+
dependedOn.add(dep);
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
// Find packages that are NOT depended on by anyone
|
|
770
|
+
const orphans = [];
|
|
771
|
+
for (const [name, info] of packageMap.entries()) {
|
|
772
|
+
if (!dependedOn.has(name)) {
|
|
773
|
+
orphans.push({ name, ...info });
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
return { orphans, totalPackages: packageMap.size, dependedOn: dependedOn.size };
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
/**
|
|
781
|
+
* Categorize orphan packages
|
|
782
|
+
*/
|
|
783
|
+
function categorizeOrphans(orphans) {
|
|
784
|
+
const categories = {
|
|
785
|
+
cliEntryPoints: [], // CLI tools (expected to be orphans)
|
|
786
|
+
apps: [], // Application entry points
|
|
787
|
+
plugins: [], // Plugin packages
|
|
788
|
+
templates: [], // Template packages
|
|
789
|
+
external: [], // Packages meant for external consumption
|
|
790
|
+
internal: [], // Internal packages (potential issues)
|
|
791
|
+
};
|
|
792
|
+
|
|
793
|
+
for (const pkg of orphans) {
|
|
794
|
+
const name = pkg.name.replace('@kb-labs/', '');
|
|
795
|
+
|
|
796
|
+
// CLI entry points (expected orphans)
|
|
797
|
+
if (name.endsWith('-cli') || name === 'cli-core' || name === 'cli-bin') {
|
|
798
|
+
categories.cliEntryPoints.push(pkg);
|
|
799
|
+
}
|
|
800
|
+
// Plugin packages (often standalone) - check both name pattern and repo
|
|
801
|
+
else if (name.endsWith('-plugin') || name.includes('plugin-') || pkg.repo === 'kb-labs-plugin') {
|
|
802
|
+
categories.plugins.push(pkg);
|
|
803
|
+
}
|
|
804
|
+
// App entry points
|
|
805
|
+
else if (name.endsWith('-app') || name.endsWith('-api') || name.endsWith('-server')) {
|
|
806
|
+
categories.apps.push(pkg);
|
|
807
|
+
}
|
|
808
|
+
// Template packages
|
|
809
|
+
else if (name.includes('template-') || name.includes('-template')) {
|
|
810
|
+
categories.templates.push(pkg);
|
|
811
|
+
}
|
|
812
|
+
// External-facing packages (libs meant for consumers)
|
|
813
|
+
else if (
|
|
814
|
+
name.endsWith('-core') || // Core packages often consumed externally
|
|
815
|
+
name === 'ui-core' || // UI library
|
|
816
|
+
name === 'ui-react' || // React components
|
|
817
|
+
name === 'data-client' || // Client library
|
|
818
|
+
pkg.repo === 'kb-labs-studio' || // Studio packages are user-facing
|
|
819
|
+
// Core infrastructure packages (meant for external consumption)
|
|
820
|
+
name === 'core-framework' ||
|
|
821
|
+
name === 'core-state-daemon' ||
|
|
822
|
+
name === 'core-tenant' ||
|
|
823
|
+
name === 'core-profile-toolkit'
|
|
824
|
+
) {
|
|
825
|
+
categories.external.push(pkg);
|
|
826
|
+
}
|
|
827
|
+
// Internal packages (potential dead code)
|
|
828
|
+
else {
|
|
829
|
+
categories.internal.push(pkg);
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
return categories;
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
/**
|
|
837
|
+
* Show orphan packages analysis
|
|
838
|
+
*/
|
|
839
|
+
function showOrphanPackages(packages) {
|
|
840
|
+
log('\n👻 Orphan Packages Analysis\n', 'cyan');
|
|
841
|
+
log('Finding packages that no other package depends on...\n', 'gray');
|
|
842
|
+
|
|
843
|
+
const { orphans, totalPackages, dependedOn } = findOrphanPackages(packages);
|
|
844
|
+
const categories = categorizeOrphans(orphans);
|
|
845
|
+
|
|
846
|
+
log(`📦 Total @kb-labs/* packages: ${totalPackages}`, 'cyan');
|
|
847
|
+
log(`🔗 Packages with dependents: ${dependedOn}`, 'cyan');
|
|
848
|
+
log(`👻 Orphan packages: ${orphans.length}`, orphans.length > 0 ? 'yellow' : 'green');
|
|
849
|
+
log('', 'reset');
|
|
850
|
+
|
|
851
|
+
// Show by category
|
|
852
|
+
if (categories.cliEntryPoints.length > 0) {
|
|
853
|
+
log(`✅ CLI Entry Points (${categories.cliEntryPoints.length}) - Expected orphans:`, 'green');
|
|
854
|
+
for (const pkg of categories.cliEntryPoints) {
|
|
855
|
+
log(` ${pkg.name}`, 'gray');
|
|
856
|
+
}
|
|
857
|
+
log('', 'reset');
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
if (categories.apps.length > 0) {
|
|
861
|
+
log(`✅ Application Entry Points (${categories.apps.length}) - Expected orphans:`, 'green');
|
|
862
|
+
for (const pkg of categories.apps) {
|
|
863
|
+
log(` ${pkg.name}`, 'gray');
|
|
864
|
+
}
|
|
865
|
+
log('', 'reset');
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
if (categories.plugins.length > 0) {
|
|
869
|
+
log(`📦 Plugin Packages (${categories.plugins.length}) - Usually standalone:`, 'blue');
|
|
870
|
+
for (const pkg of categories.plugins) {
|
|
871
|
+
log(` ${pkg.name}`, 'gray');
|
|
872
|
+
}
|
|
873
|
+
log('', 'reset');
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
if (categories.templates.length > 0) {
|
|
877
|
+
log(`📄 Template Packages (${categories.templates.length}) - Expected orphans:`, 'blue');
|
|
878
|
+
for (const pkg of categories.templates) {
|
|
879
|
+
log(` ${pkg.name}`, 'gray');
|
|
880
|
+
}
|
|
881
|
+
log('', 'reset');
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
if (categories.external.length > 0) {
|
|
885
|
+
log(`📤 External/Library Packages (${categories.external.length}) - Consumed externally:`, 'blue');
|
|
886
|
+
for (const pkg of categories.external) {
|
|
887
|
+
log(` ${pkg.name}`, 'gray');
|
|
888
|
+
}
|
|
889
|
+
log('', 'reset');
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
// Internal packages - these might be dead code
|
|
893
|
+
if (categories.internal.length > 0) {
|
|
894
|
+
log(`⚠️ Internal Packages Without Dependents (${categories.internal.length}) - Review needed:`, 'yellow');
|
|
895
|
+
log(' These might be dead code or missing from dependency lists:\n', 'gray');
|
|
896
|
+
|
|
897
|
+
// Group by repo
|
|
898
|
+
const byRepo = new Map();
|
|
899
|
+
for (const pkg of categories.internal) {
|
|
900
|
+
if (!byRepo.has(pkg.repo)) {
|
|
901
|
+
byRepo.set(pkg.repo, []);
|
|
902
|
+
}
|
|
903
|
+
byRepo.get(pkg.repo).push(pkg);
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
for (const [repo, pkgs] of byRepo.entries()) {
|
|
907
|
+
log(` ${repo}/`, 'yellow');
|
|
908
|
+
for (const pkg of pkgs) {
|
|
909
|
+
log(` ${pkg.name}`, 'red');
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
log('', 'reset');
|
|
913
|
+
|
|
914
|
+
log('💡 Actions:', 'blue');
|
|
915
|
+
log(' 1. If these packages are used externally, they\'re fine as orphans', 'gray');
|
|
916
|
+
log(' 2. If they should be used internally, add them as dependencies', 'gray');
|
|
917
|
+
log(' 3. If they\'re truly unused, consider removing them', 'gray');
|
|
918
|
+
log('', 'reset');
|
|
919
|
+
} else {
|
|
920
|
+
log('✅ No suspicious internal orphan packages found\n', 'green');
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
// Summary
|
|
924
|
+
const expectedOrphans =
|
|
925
|
+
categories.cliEntryPoints.length +
|
|
926
|
+
categories.apps.length +
|
|
927
|
+
categories.plugins.length +
|
|
928
|
+
categories.templates.length +
|
|
929
|
+
categories.external.length;
|
|
930
|
+
|
|
931
|
+
log('─'.repeat(60), 'gray');
|
|
932
|
+
log('📊 Summary:', 'blue');
|
|
933
|
+
log(` Expected orphans (CLI/Apps/Plugins/Templates/External): ${expectedOrphans}`, 'green');
|
|
934
|
+
log(` Review needed (internal packages): ${categories.internal.length}`, categories.internal.length > 0 ? 'yellow' : 'green');
|
|
935
|
+
log('', 'reset');
|
|
936
|
+
|
|
937
|
+
return { orphans, categories };
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
/**
|
|
941
|
+
* Main function
|
|
942
|
+
*/
|
|
943
|
+
function main() {
|
|
944
|
+
const rootDir = process.cwd();
|
|
945
|
+
|
|
946
|
+
if (!options.json) {
|
|
947
|
+
log('\n🔧 KB Labs Dependency Fixer\n', 'blue');
|
|
948
|
+
|
|
949
|
+
if (options.dryRun) {
|
|
950
|
+
log('🔍 DRY RUN MODE - No changes will be made\n', 'yellow');
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
const packages = findPackages(rootDir, options.package);
|
|
955
|
+
|
|
956
|
+
if (packages.length === 0) {
|
|
957
|
+
if (!options.json) {
|
|
958
|
+
log('⚠️ No KB Labs packages found', 'yellow');
|
|
959
|
+
}
|
|
960
|
+
process.exit(0);
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
if (!options.json) {
|
|
964
|
+
log(`Found ${packages.length} package(s)\n`, 'gray');
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
// Stats mode - just show dependency statistics
|
|
968
|
+
if (options.showStats) {
|
|
969
|
+
showDependencyStats(packages);
|
|
970
|
+
process.exit(0);
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
// Orphan packages mode - find packages not used by anyone
|
|
974
|
+
if (options.showOrphans) {
|
|
975
|
+
if (options.json) {
|
|
976
|
+
const { orphans, totalPackages, dependedOn } = findOrphanPackages(packages);
|
|
977
|
+
const categories = categorizeOrphans(orphans);
|
|
978
|
+
console.log(JSON.stringify({
|
|
979
|
+
totalPackages,
|
|
980
|
+
dependedOn,
|
|
981
|
+
orphanCount: orphans.length,
|
|
982
|
+
categories: {
|
|
983
|
+
cliEntryPoints: categories.cliEntryPoints.map(p => p.name),
|
|
984
|
+
apps: categories.apps.map(p => p.name),
|
|
985
|
+
plugins: categories.plugins.map(p => p.name),
|
|
986
|
+
templates: categories.templates.map(p => p.name),
|
|
987
|
+
external: categories.external.map(p => p.name),
|
|
988
|
+
internal: categories.internal.map(p => p.name),
|
|
989
|
+
},
|
|
990
|
+
reviewNeeded: categories.internal.map(p => ({ name: p.name, repo: p.repo })),
|
|
991
|
+
}, null, 2));
|
|
992
|
+
process.exit(categories.internal.length > 0 ? 1 : 0);
|
|
993
|
+
}
|
|
994
|
+
const { categories } = showOrphanPackages(packages);
|
|
995
|
+
// Exit with error if there are suspicious internal orphans
|
|
996
|
+
process.exit(categories.internal.length > 0 ? 1 : 0);
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
let hasChanges = false;
|
|
1000
|
+
|
|
1001
|
+
// Remove unused dependencies
|
|
1002
|
+
if (options.removeUnused) {
|
|
1003
|
+
log('🗑️ Removing unused dependencies...\n', 'cyan');
|
|
1004
|
+
|
|
1005
|
+
let totalRemoved = 0;
|
|
1006
|
+
let totalScanned = 0;
|
|
1007
|
+
const removals = [];
|
|
1008
|
+
|
|
1009
|
+
for (const packagePath of packages) {
|
|
1010
|
+
const result = removeUnusedDependencies(packagePath, options.dryRun, options.verbose);
|
|
1011
|
+
totalScanned += result.totalDeps || 0;
|
|
1012
|
+
|
|
1013
|
+
if (result.removed > 0) {
|
|
1014
|
+
totalRemoved += result.removed;
|
|
1015
|
+
removals.push(result);
|
|
1016
|
+
|
|
1017
|
+
log(` ${result.packageName}`, 'yellow');
|
|
1018
|
+
for (const dep of result.deps) {
|
|
1019
|
+
log(` - ${dep}`, 'red');
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
// Show kept deps in verbose mode
|
|
1023
|
+
if (options.verbose && result.kept.length > 0) {
|
|
1024
|
+
log(` kept:`, 'gray');
|
|
1025
|
+
for (const { dep, reason } of result.kept.slice(0, 5)) {
|
|
1026
|
+
log(` ✓ ${dep} (${reason})`, 'gray');
|
|
1027
|
+
}
|
|
1028
|
+
if (result.kept.length > 5) {
|
|
1029
|
+
log(` ... and ${result.kept.length - 5} more`, 'gray');
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
if (totalRemoved > 0) {
|
|
1036
|
+
hasChanges = true;
|
|
1037
|
+
log(`\n ✅ Removed ${totalRemoved} unused dependency(ies)`, 'green');
|
|
1038
|
+
log(` 📊 Scanned ${totalScanned} total dependencies across ${packages.length} packages\n`, 'gray');
|
|
1039
|
+
} else {
|
|
1040
|
+
log(' ✅ No unused dependencies found\n', 'green');
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
// Add missing dependencies
|
|
1045
|
+
if (options.addMissing) {
|
|
1046
|
+
log('➕ Adding missing workspace dependencies...\n', 'cyan');
|
|
1047
|
+
|
|
1048
|
+
let totalAdded = 0;
|
|
1049
|
+
const additions = [];
|
|
1050
|
+
|
|
1051
|
+
for (const packagePath of packages) {
|
|
1052
|
+
const result = addMissingDependencies(packagePath, options.dryRun, packages);
|
|
1053
|
+
if (result.added > 0) {
|
|
1054
|
+
totalAdded += result.added;
|
|
1055
|
+
additions.push(result);
|
|
1056
|
+
|
|
1057
|
+
log(` ${result.packageName}`, 'yellow');
|
|
1058
|
+
for (const dep of result.deps) {
|
|
1059
|
+
log(` + ${dep}`, 'gray');
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
if (totalAdded > 0) {
|
|
1065
|
+
hasChanges = true;
|
|
1066
|
+
log(`\n ✅ Added ${totalAdded} missing dependency(ies)\n`, 'green');
|
|
1067
|
+
} else {
|
|
1068
|
+
log(' ✅ No missing dependencies found\n', 'green');
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
// Add missing npm dependencies
|
|
1073
|
+
if (options.addMissingNpm) {
|
|
1074
|
+
log('📦 Adding missing npm dependencies...\n', 'cyan');
|
|
1075
|
+
|
|
1076
|
+
let totalAdded = 0;
|
|
1077
|
+
const additions = [];
|
|
1078
|
+
|
|
1079
|
+
for (const packagePath of packages) {
|
|
1080
|
+
const result = addMissingNpmDependencies(packagePath, options.dryRun);
|
|
1081
|
+
if (result.added > 0) {
|
|
1082
|
+
totalAdded += result.added;
|
|
1083
|
+
additions.push(result);
|
|
1084
|
+
|
|
1085
|
+
log(` ${result.packageName}`, 'yellow');
|
|
1086
|
+
for (const dep of result.deps) {
|
|
1087
|
+
log(` + ${dep}`, 'gray');
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
if (totalAdded > 0) {
|
|
1093
|
+
hasChanges = true;
|
|
1094
|
+
log(`\n ✅ Added ${totalAdded} missing npm package(s)\n`, 'green');
|
|
1095
|
+
log(` 💡 Run 'pnpm install' to install new dependencies\n`, 'cyan');
|
|
1096
|
+
} else {
|
|
1097
|
+
log(' ✅ No missing npm dependencies found\n', 'green');
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
// Align versions
|
|
1102
|
+
if (options.alignVersions) {
|
|
1103
|
+
log('🔄 Aligning dependency versions...\n', 'cyan');
|
|
1104
|
+
|
|
1105
|
+
const { totalAligned, changes } = alignDependencyVersions(packages, options.dryRun);
|
|
1106
|
+
|
|
1107
|
+
if (totalAligned > 0) {
|
|
1108
|
+
hasChanges = true;
|
|
1109
|
+
|
|
1110
|
+
// Group by dependency
|
|
1111
|
+
const byDep = new Map();
|
|
1112
|
+
for (const change of changes) {
|
|
1113
|
+
if (!byDep.has(change.dep)) {
|
|
1114
|
+
byDep.set(change.dep, []);
|
|
1115
|
+
}
|
|
1116
|
+
byDep.get(change.dep).push(change);
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
for (const [dep, depChanges] of byDep.entries()) {
|
|
1120
|
+
const targetVersion = depChanges[0].to;
|
|
1121
|
+
log(` ${dep} → ${targetVersion}`, 'yellow');
|
|
1122
|
+
log(` ${depChanges.length} package(s) updated`, 'gray');
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
log(`\n ✅ Aligned ${totalAligned} dependency version(s)\n`, 'green');
|
|
1126
|
+
} else {
|
|
1127
|
+
log(' ✅ No version conflicts found\n', 'green');
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
// Summary
|
|
1132
|
+
if (!options.removeUnused && !options.addMissing && !options.alignVersions) {
|
|
1133
|
+
log('⚠️ No fix mode specified\n', 'yellow');
|
|
1134
|
+
log('Fix modes:', 'blue');
|
|
1135
|
+
log(' --remove-unused Remove unused dependencies', 'gray');
|
|
1136
|
+
log(' --add-missing Add missing workspace dependencies', 'gray');
|
|
1137
|
+
log(' --align-versions Align duplicate dependency versions', 'gray');
|
|
1138
|
+
log(' --all Apply all fixes', 'gray');
|
|
1139
|
+
log('', 'reset');
|
|
1140
|
+
log('Analysis modes:', 'blue');
|
|
1141
|
+
log(' --stats Show dependency statistics', 'gray');
|
|
1142
|
+
log(' --orphans Find packages not used by anyone', 'gray');
|
|
1143
|
+
log('', 'reset');
|
|
1144
|
+
log('Options:', 'blue');
|
|
1145
|
+
log(' --dry-run Preview changes without applying', 'gray');
|
|
1146
|
+
log(' --verbose, -v Show why dependencies were kept', 'gray');
|
|
1147
|
+
log(' --json Output results in JSON format', 'gray');
|
|
1148
|
+
log(' --package=<name> Process only specific package', 'gray');
|
|
1149
|
+
log('');
|
|
1150
|
+
log('Examples:', 'blue');
|
|
1151
|
+
log(' npx kb-devkit-fix-deps --stats', 'gray');
|
|
1152
|
+
log(' npx kb-devkit-fix-deps --orphans', 'gray');
|
|
1153
|
+
log(' npx kb-devkit-fix-deps --orphans --json', 'gray');
|
|
1154
|
+
log(' npx kb-devkit-fix-deps --remove-unused --dry-run', 'gray');
|
|
1155
|
+
log(' npx kb-devkit-fix-deps --all --package=cli-core', 'gray');
|
|
1156
|
+
log('');
|
|
1157
|
+
process.exit(0);
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
if (options.dryRun && hasChanges) {
|
|
1161
|
+
log('💡 Run without --dry-run to apply changes\n', 'blue');
|
|
1162
|
+
} else if (!options.dryRun && hasChanges) {
|
|
1163
|
+
log('💡 Don\'t forget to run: pnpm install\n', 'blue');
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
process.exit(0);
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
main();
|