@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.
Files changed (295) hide show
  1. package/.cursorrules +32 -0
  2. package/.github/CODEOWNERS +2 -0
  3. package/.github/actions/setup-node-pnpm/action.yml +47 -0
  4. package/.github/workflow-templates/ci.yml +13 -0
  5. package/.github/workflow-templates/drift-check.yml +10 -0
  6. package/.github/workflow-templates/profiles-validate.yml +16 -0
  7. package/.github/workflow-templates/release.yml +8 -0
  8. package/.github/workflows/ci-reusable.yml +131 -0
  9. package/.github/workflows/drift-check-reusable.yml +23 -0
  10. package/.github/workflows/fixtures.yml +74 -0
  11. package/.github/workflows/profiles-validate-reusable.yml +67 -0
  12. package/.github/workflows/release-reusable.yml +50 -0
  13. package/.vscode/settings.json +23 -0
  14. package/AGENTS.md +130 -0
  15. package/LICENSE +21 -0
  16. package/README.md +1542 -0
  17. package/agents/devkit-maintainer/context.globs +15 -0
  18. package/agents/devkit-maintainer/permissions.yml +17 -0
  19. package/agents/devkit-maintainer/prompt.md +28 -0
  20. package/agents/devkit-maintainer/runbook.md +31 -0
  21. package/agents/docs-crafter/prompt.md +24 -0
  22. package/agents/docs-crafter/runbook.md +18 -0
  23. package/agents/release-manager/context.globs +7 -0
  24. package/agents/release-manager/prompt.md +27 -0
  25. package/agents/release-manager/runbook.md +17 -0
  26. package/agents/test-generator/context.globs +7 -0
  27. package/agents/test-generator/prompt.md +27 -0
  28. package/agents/test-generator/runbook.md +18 -0
  29. package/bin/devkit-architecture.mjs +1225 -0
  30. package/bin/devkit-build-order.mjs +500 -0
  31. package/bin/devkit-check-build-readiness.mjs +222 -0
  32. package/bin/devkit-check-commands.mjs +394 -0
  33. package/bin/devkit-check-configs.mjs +461 -0
  34. package/bin/devkit-check-deprecated.mjs +532 -0
  35. package/bin/devkit-check-duplicates.mjs +431 -0
  36. package/bin/devkit-check-exports.mjs +561 -0
  37. package/bin/devkit-check-imports.mjs +712 -0
  38. package/bin/devkit-check-paths.mjs +670 -0
  39. package/bin/devkit-check-scripts.mjs +335 -0
  40. package/bin/devkit-check-structure.mjs +495 -0
  41. package/bin/devkit-check-types.mjs +450 -0
  42. package/bin/devkit-ci.mjs +261 -0
  43. package/bin/devkit-core-gate.mjs +225 -0
  44. package/bin/devkit-fix-deps.mjs +1169 -0
  45. package/bin/devkit-freshness.mjs +199 -0
  46. package/bin/devkit-health.mjs +489 -0
  47. package/bin/devkit-migrate-configs.mjs +370 -0
  48. package/bin/devkit-paths.mjs +192 -0
  49. package/bin/devkit-stats.mjs +453 -0
  50. package/bin/devkit-sync.mjs +12 -0
  51. package/bin/devkit-tsup-external.mjs +148 -0
  52. package/bin/devkit-types-audit.mjs +615 -0
  53. package/bin/devkit-types-order.mjs +637 -0
  54. package/bin/devkit-validate-naming.mjs +203 -0
  55. package/bin/devkit-visualize.mjs +452 -0
  56. package/bin/kb-devkit-qa-history.mjs +453 -0
  57. package/bin/kb-devkit-qa.mjs +915 -0
  58. package/eslint/node.js +134 -0
  59. package/eslint/react.js +260 -0
  60. package/package.json +186 -0
  61. package/prettier/index.json +10 -0
  62. package/sync/index.mjs +693 -0
  63. package/templates/configs/README.md +306 -0
  64. package/templates/configs/eslint.config.js +27 -0
  65. package/templates/configs/package.json.bin +25 -0
  66. package/templates/configs/package.json.lib +30 -0
  67. package/templates/configs/tsconfig.build.json +15 -0
  68. package/templates/configs/tsconfig.json +9 -0
  69. package/templates/configs/tsup.config.bin.ts +34 -0
  70. package/templates/configs/tsup.config.cli.ts +41 -0
  71. package/templates/configs/tsup.config.dual.ts +46 -0
  72. package/templates/configs/tsup.config.ts +36 -0
  73. package/templates/package-json/README.md +290 -0
  74. package/templates/package-json/package.json.bin.template +40 -0
  75. package/templates/package-json/package.json.template +45 -0
  76. package/tsconfig/base.json +21 -0
  77. package/tsconfig/cli.json +12 -0
  78. package/tsconfig/dist/__tests__/cache.spec.d.ts +6 -0
  79. package/tsconfig/dist/__tests__/cache.spec.d.ts.map +1 -0
  80. package/tsconfig/dist/__tests__/cache.spec.js +85 -0
  81. package/tsconfig/dist/__tests__/cache.spec.js.map +1 -0
  82. package/tsconfig/dist/__tests__/fs-atomic.spec.d.ts +6 -0
  83. package/tsconfig/dist/__tests__/fs-atomic.spec.d.ts.map +1 -0
  84. package/tsconfig/dist/__tests__/fs-atomic.spec.js +153 -0
  85. package/tsconfig/dist/__tests__/fs-atomic.spec.js.map +1 -0
  86. package/tsconfig/dist/__tests__/init-workspace.spec.d.ts +6 -0
  87. package/tsconfig/dist/__tests__/init-workspace.spec.d.ts.map +1 -0
  88. package/tsconfig/dist/__tests__/init-workspace.spec.js +99 -0
  89. package/tsconfig/dist/__tests__/init-workspace.spec.js.map +1 -0
  90. package/tsconfig/dist/__tests__/kb-error.spec.d.ts +6 -0
  91. package/tsconfig/dist/__tests__/kb-error.spec.d.ts.map +1 -0
  92. package/tsconfig/dist/__tests__/kb-error.spec.js +190 -0
  93. package/tsconfig/dist/__tests__/kb-error.spec.js.map +1 -0
  94. package/tsconfig/dist/__tests__/preset-lockfile.spec.d.ts +6 -0
  95. package/tsconfig/dist/__tests__/preset-lockfile.spec.d.ts.map +1 -0
  96. package/tsconfig/dist/__tests__/preset-lockfile.spec.js +142 -0
  97. package/tsconfig/dist/__tests__/preset-lockfile.spec.js.map +1 -0
  98. package/tsconfig/dist/__tests__/product-config-profiles.spec.d.ts +6 -0
  99. package/tsconfig/dist/__tests__/product-config-profiles.spec.d.ts.map +1 -0
  100. package/tsconfig/dist/__tests__/product-config-profiles.spec.js +100 -0
  101. package/tsconfig/dist/__tests__/product-config-profiles.spec.js.map +1 -0
  102. package/tsconfig/dist/__tests__/product-config.spec.d.ts +6 -0
  103. package/tsconfig/dist/__tests__/product-config.spec.d.ts.map +1 -0
  104. package/tsconfig/dist/__tests__/product-config.spec.js +298 -0
  105. package/tsconfig/dist/__tests__/product-config.spec.js.map +1 -0
  106. package/tsconfig/dist/__tests__/runtime.spec.d.ts +2 -0
  107. package/tsconfig/dist/__tests__/runtime.spec.d.ts.map +1 -0
  108. package/tsconfig/dist/__tests__/runtime.spec.js +127 -0
  109. package/tsconfig/dist/__tests__/runtime.spec.js.map +1 -0
  110. package/tsconfig/dist/__tests__/upsert-lockfile.spec.d.ts +6 -0
  111. package/tsconfig/dist/__tests__/upsert-lockfile.spec.d.ts.map +1 -0
  112. package/tsconfig/dist/__tests__/upsert-lockfile.spec.js +251 -0
  113. package/tsconfig/dist/__tests__/upsert-lockfile.spec.js.map +1 -0
  114. package/tsconfig/dist/__tests__/validate-config.spec.d.ts +2 -0
  115. package/tsconfig/dist/__tests__/validate-config.spec.d.ts.map +1 -0
  116. package/tsconfig/dist/__tests__/validate-config.spec.js +14 -0
  117. package/tsconfig/dist/__tests__/validate-config.spec.js.map +1 -0
  118. package/tsconfig/dist/api/init-workspace.d.ts +10 -0
  119. package/tsconfig/dist/api/init-workspace.d.ts.map +1 -0
  120. package/tsconfig/dist/api/init-workspace.js +191 -0
  121. package/tsconfig/dist/api/init-workspace.js.map +1 -0
  122. package/tsconfig/dist/api/product-config.d.ts +21 -0
  123. package/tsconfig/dist/api/product-config.d.ts.map +1 -0
  124. package/tsconfig/dist/api/product-config.js +192 -0
  125. package/tsconfig/dist/api/product-config.js.map +1 -0
  126. package/tsconfig/dist/api/read-config.d.ts +22 -0
  127. package/tsconfig/dist/api/read-config.d.ts.map +1 -0
  128. package/tsconfig/dist/api/read-config.js +105 -0
  129. package/tsconfig/dist/api/read-config.js.map +1 -0
  130. package/tsconfig/dist/api/upsert-lockfile.d.ts +10 -0
  131. package/tsconfig/dist/api/upsert-lockfile.d.ts.map +1 -0
  132. package/tsconfig/dist/api/upsert-lockfile.js +63 -0
  133. package/tsconfig/dist/api/upsert-lockfile.js.map +1 -0
  134. package/tsconfig/dist/cache/fs-cache.d.ts +38 -0
  135. package/tsconfig/dist/cache/fs-cache.d.ts.map +1 -0
  136. package/tsconfig/dist/cache/fs-cache.js +142 -0
  137. package/tsconfig/dist/cache/fs-cache.js.map +1 -0
  138. package/tsconfig/dist/errors/kb-error.d.ts +32 -0
  139. package/tsconfig/dist/errors/kb-error.d.ts.map +1 -0
  140. package/tsconfig/dist/errors/kb-error.js +54 -0
  141. package/tsconfig/dist/errors/kb-error.js.map +1 -0
  142. package/tsconfig/dist/fs/__tests__/fs.spec.d.ts +2 -0
  143. package/tsconfig/dist/fs/__tests__/fs.spec.d.ts.map +1 -0
  144. package/tsconfig/dist/fs/__tests__/fs.spec.js +22 -0
  145. package/tsconfig/dist/fs/__tests__/fs.spec.js.map +1 -0
  146. package/tsconfig/dist/fs/fs.d.ts +6 -0
  147. package/tsconfig/dist/fs/fs.d.ts.map +1 -0
  148. package/tsconfig/dist/fs/fs.js +12 -0
  149. package/tsconfig/dist/fs/fs.js.map +1 -0
  150. package/tsconfig/dist/fs/index.d.ts +2 -0
  151. package/tsconfig/dist/fs/index.d.ts.map +1 -0
  152. package/tsconfig/dist/fs/index.js +2 -0
  153. package/tsconfig/dist/fs/index.js.map +1 -0
  154. package/tsconfig/dist/hash/config-hash.d.ts +17 -0
  155. package/tsconfig/dist/hash/config-hash.d.ts.map +1 -0
  156. package/tsconfig/dist/hash/config-hash.js +55 -0
  157. package/tsconfig/dist/hash/config-hash.js.map +1 -0
  158. package/tsconfig/dist/index.d.ts +5 -0
  159. package/tsconfig/dist/index.d.ts.map +1 -0
  160. package/tsconfig/dist/index.js +5 -0
  161. package/tsconfig/dist/index.js.map +1 -0
  162. package/tsconfig/dist/lockfile/lockfile.d.ts +54 -0
  163. package/tsconfig/dist/lockfile/lockfile.d.ts.map +1 -0
  164. package/tsconfig/dist/lockfile/lockfile.js +141 -0
  165. package/tsconfig/dist/lockfile/lockfile.js.map +1 -0
  166. package/tsconfig/dist/logging/__tests__/logger.spec.d.ts +2 -0
  167. package/tsconfig/dist/logging/__tests__/logger.spec.d.ts.map +1 -0
  168. package/tsconfig/dist/logging/__tests__/logger.spec.js +65 -0
  169. package/tsconfig/dist/logging/__tests__/logger.spec.js.map +1 -0
  170. package/tsconfig/dist/logging/__tests__/redaction.spec.d.ts +2 -0
  171. package/tsconfig/dist/logging/__tests__/redaction.spec.d.ts.map +1 -0
  172. package/tsconfig/dist/logging/__tests__/redaction.spec.js +34 -0
  173. package/tsconfig/dist/logging/__tests__/redaction.spec.js.map +1 -0
  174. package/tsconfig/dist/logging/__tests__/sinks-and-env.spec.d.ts +2 -0
  175. package/tsconfig/dist/logging/__tests__/sinks-and-env.spec.d.ts.map +1 -0
  176. package/tsconfig/dist/logging/__tests__/sinks-and-env.spec.js +90 -0
  177. package/tsconfig/dist/logging/__tests__/sinks-and-env.spec.js.map +1 -0
  178. package/tsconfig/dist/logging/index.d.ts +6 -0
  179. package/tsconfig/dist/logging/index.d.ts.map +1 -0
  180. package/tsconfig/dist/logging/index.js +6 -0
  181. package/tsconfig/dist/logging/index.js.map +1 -0
  182. package/tsconfig/dist/logging/logger.d.ts +9 -0
  183. package/tsconfig/dist/logging/logger.d.ts.map +1 -0
  184. package/tsconfig/dist/logging/logger.js +101 -0
  185. package/tsconfig/dist/logging/logger.js.map +1 -0
  186. package/tsconfig/dist/logging/redaction.d.ts +7 -0
  187. package/tsconfig/dist/logging/redaction.d.ts.map +1 -0
  188. package/tsconfig/dist/logging/redaction.js +23 -0
  189. package/tsconfig/dist/logging/redaction.js.map +1 -0
  190. package/tsconfig/dist/logging/sinks/json.d.ts +4 -0
  191. package/tsconfig/dist/logging/sinks/json.d.ts.map +1 -0
  192. package/tsconfig/dist/logging/sinks/json.js +23 -0
  193. package/tsconfig/dist/logging/sinks/json.js.map +1 -0
  194. package/tsconfig/dist/logging/sinks/stdout.d.ts +3 -0
  195. package/tsconfig/dist/logging/sinks/stdout.d.ts.map +1 -0
  196. package/tsconfig/dist/logging/sinks/stdout.js +24 -0
  197. package/tsconfig/dist/logging/sinks/stdout.js.map +1 -0
  198. package/tsconfig/dist/logging/types/index.d.ts +2 -0
  199. package/tsconfig/dist/logging/types/index.d.ts.map +1 -0
  200. package/tsconfig/dist/logging/types/index.js +2 -0
  201. package/tsconfig/dist/logging/types/index.js.map +1 -0
  202. package/tsconfig/dist/logging/types/types.d.ts +37 -0
  203. package/tsconfig/dist/logging/types/types.d.ts.map +1 -0
  204. package/tsconfig/dist/logging/types/types.js +2 -0
  205. package/tsconfig/dist/logging/types/types.js.map +1 -0
  206. package/tsconfig/dist/merge/layered-merge.d.ts +16 -0
  207. package/tsconfig/dist/merge/layered-merge.d.ts.map +1 -0
  208. package/tsconfig/dist/merge/layered-merge.js +97 -0
  209. package/tsconfig/dist/merge/layered-merge.js.map +1 -0
  210. package/tsconfig/dist/preset/resolve-preset.d.ts +29 -0
  211. package/tsconfig/dist/preset/resolve-preset.d.ts.map +1 -0
  212. package/tsconfig/dist/preset/resolve-preset.js +104 -0
  213. package/tsconfig/dist/preset/resolve-preset.js.map +1 -0
  214. package/tsconfig/dist/repo/__tests__/repo.spec.d.ts +2 -0
  215. package/tsconfig/dist/repo/__tests__/repo.spec.d.ts.map +1 -0
  216. package/tsconfig/dist/repo/__tests__/repo.spec.js +25 -0
  217. package/tsconfig/dist/repo/__tests__/repo.spec.js.map +1 -0
  218. package/tsconfig/dist/repo/index.d.ts +2 -0
  219. package/tsconfig/dist/repo/index.d.ts.map +1 -0
  220. package/tsconfig/dist/repo/index.js +2 -0
  221. package/tsconfig/dist/repo/index.js.map +1 -0
  222. package/tsconfig/dist/repo/repo.d.ts +6 -0
  223. package/tsconfig/dist/repo/repo.d.ts.map +1 -0
  224. package/tsconfig/dist/repo/repo.js +25 -0
  225. package/tsconfig/dist/repo/repo.js.map +1 -0
  226. package/tsconfig/dist/runtime/index.d.ts +2 -0
  227. package/tsconfig/dist/runtime/index.d.ts.map +1 -0
  228. package/tsconfig/dist/runtime/index.js +2 -0
  229. package/tsconfig/dist/runtime/index.js.map +1 -0
  230. package/tsconfig/dist/runtime/runtime.d.ts +46 -0
  231. package/tsconfig/dist/runtime/runtime.d.ts.map +1 -0
  232. package/tsconfig/dist/runtime/runtime.js +126 -0
  233. package/tsconfig/dist/runtime/runtime.js.map +1 -0
  234. package/tsconfig/dist/tsconfig.tools.tsbuildinfo +1 -0
  235. package/tsconfig/dist/tsconfig.tsbuildinfo +1 -0
  236. package/tsconfig/dist/types/index.d.ts +2 -0
  237. package/tsconfig/dist/types/index.d.ts.map +1 -0
  238. package/tsconfig/dist/types/index.js +2 -0
  239. package/tsconfig/dist/types/index.js.map +1 -0
  240. package/tsconfig/dist/types/init.d.ts +34 -0
  241. package/tsconfig/dist/types/init.d.ts.map +1 -0
  242. package/tsconfig/dist/types/init.js +6 -0
  243. package/tsconfig/dist/types/init.js.map +1 -0
  244. package/tsconfig/dist/types/preset.d.ts +27 -0
  245. package/tsconfig/dist/types/preset.d.ts.map +1 -0
  246. package/tsconfig/dist/types/preset.js +6 -0
  247. package/tsconfig/dist/types/preset.js.map +1 -0
  248. package/tsconfig/dist/types/types.d.ts +6 -0
  249. package/tsconfig/dist/types/types.d.ts.map +1 -0
  250. package/tsconfig/dist/types/types.js +2 -0
  251. package/tsconfig/dist/types/types.js.map +1 -0
  252. package/tsconfig/dist/utils/__tests__/env.spec.d.ts +2 -0
  253. package/tsconfig/dist/utils/__tests__/env.spec.d.ts.map +1 -0
  254. package/tsconfig/dist/utils/__tests__/env.spec.js +33 -0
  255. package/tsconfig/dist/utils/__tests__/env.spec.js.map +1 -0
  256. package/tsconfig/dist/utils/env.d.ts +7 -0
  257. package/tsconfig/dist/utils/env.d.ts.map +1 -0
  258. package/tsconfig/dist/utils/env.js +25 -0
  259. package/tsconfig/dist/utils/env.js.map +1 -0
  260. package/tsconfig/dist/utils/fs-atomic.d.ts +15 -0
  261. package/tsconfig/dist/utils/fs-atomic.d.ts.map +1 -0
  262. package/tsconfig/dist/utils/fs-atomic.js +45 -0
  263. package/tsconfig/dist/utils/fs-atomic.js.map +1 -0
  264. package/tsconfig/dist/utils/index.d.ts +3 -0
  265. package/tsconfig/dist/utils/index.d.ts.map +1 -0
  266. package/tsconfig/dist/utils/index.js +3 -0
  267. package/tsconfig/dist/utils/index.js.map +1 -0
  268. package/tsconfig/dist/utils/paths.d.ts +21 -0
  269. package/tsconfig/dist/utils/paths.d.ts.map +1 -0
  270. package/tsconfig/dist/utils/paths.js +32 -0
  271. package/tsconfig/dist/utils/paths.js.map +1 -0
  272. package/tsconfig/dist/utils/product-normalize.d.ts +27 -0
  273. package/tsconfig/dist/utils/product-normalize.d.ts.map +1 -0
  274. package/tsconfig/dist/utils/product-normalize.js +45 -0
  275. package/tsconfig/dist/utils/product-normalize.js.map +1 -0
  276. package/tsconfig/dist/validation/validate-config.d.ts +7 -0
  277. package/tsconfig/dist/validation/validate-config.d.ts.map +1 -0
  278. package/tsconfig/dist/validation/validate-config.js +22 -0
  279. package/tsconfig/dist/validation/validate-config.js.map +1 -0
  280. package/tsconfig/lib.json +13 -0
  281. package/tsconfig/node.json +12 -0
  282. package/tsconfig/react-app.json +8 -0
  283. package/tsconfig/react-lib.json +8 -0
  284. package/tsconfig/test.json +15 -0
  285. package/tsup/bin.js +172 -0
  286. package/tsup/dual.js +155 -0
  287. package/tsup/external-sync.mjs +41 -0
  288. package/tsup/external.mjs +109 -0
  289. package/tsup/node.js +65 -0
  290. package/tsup/react-lib.js +18 -0
  291. package/tsup/sdk.js +118 -0
  292. package/vite/react-app.js +50 -0
  293. package/vitest/node.js +37 -0
  294. package/vitest/react.js +39 -0
  295. package/vitest/vitest-setup.ts +9 -0
@@ -0,0 +1,712 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * @kb-labs/devkit - Import Checker
5
+ *
6
+ * Checks for:
7
+ * 1. Broken imports (files that don't exist)
8
+ * 2. Unused dependencies in package.json
9
+ * 3. Imports from node_modules that should be workspace imports
10
+ * 4. Circular dependencies between packages
11
+ *
12
+ * Usage:
13
+ * kb-devkit-check-imports # Check all packages
14
+ * kb-devkit-check-imports --package cli-core # Check specific package
15
+ * kb-devkit-check-imports --fix # Auto-fix unused deps
16
+ */
17
+
18
+ import fs from 'node:fs';
19
+ import path from 'node:path';
20
+ import { fileURLToPath } from 'node:url';
21
+
22
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
23
+
24
+ /**
25
+ * Parse .devkitignore file
26
+ * Returns rules grouped by type
27
+ */
28
+ function parseDevkitIgnore(rootDir) {
29
+ const ignorePath = path.join(rootDir, '.devkitignore');
30
+ const rules = {
31
+ 'missing-dep': [],
32
+ 'unused-dep': [],
33
+ 'broken-import': [],
34
+ 'circular-dep': [],
35
+ };
36
+
37
+ if (!fs.existsSync(ignorePath)) {
38
+ return rules;
39
+ }
40
+
41
+ const content = fs.readFileSync(ignorePath, 'utf-8');
42
+ const lines = content.split('\n');
43
+
44
+ for (const line of lines) {
45
+ const trimmed = line.trim();
46
+
47
+ // Skip empty lines and comments
48
+ if (!trimmed || trimmed.startsWith('#')) {continue;}
49
+
50
+ // Parse rule: "rule-type: pattern # reason"
51
+ const match = trimmed.match(/^(missing-dep|unused-dep|broken-import|circular-dep):\s*(.+?)(?:\s*#\s*(.*))?$/);
52
+ if (!match) {continue;}
53
+
54
+ const [, ruleType, pattern, reason] = match;
55
+
56
+ // Parse pattern: "package -> target"
57
+ // Simpler regex: match @scope/package or package, then ->, then target
58
+ const patternMatch = pattern.trim().match(/^(@[\w-]+\/[\w-]+|[\w-]+)\s*->\s*(.+)$/);
59
+ if (!patternMatch) {continue;}
60
+
61
+ const [, packageName, target] = patternMatch;
62
+
63
+ rules[ruleType].push({
64
+ package: packageName.trim(),
65
+ target: target.trim(),
66
+ reason: reason?.trim() || 'No reason provided',
67
+ });
68
+ }
69
+
70
+ return rules;
71
+ }
72
+
73
+ /**
74
+ * Check if an issue should be ignored based on rules
75
+ */
76
+ function shouldIgnore(rules, ruleType, packageName, target) {
77
+ const typeRules = rules[ruleType] || [];
78
+
79
+ for (const rule of typeRules) {
80
+ if (rule.package !== packageName) {continue;}
81
+
82
+ // Handle wildcard patterns
83
+ if (rule.target.includes('*')) {
84
+ const regex = new RegExp('^' + rule.target.replace(/\./g, '\\.').replace(/\*/g, '.*') + '$');
85
+ if (regex.test(target)) {
86
+ return rule;
87
+ }
88
+ } else if (rule.target === target) {
89
+ return rule;
90
+ }
91
+ }
92
+
93
+ return null;
94
+ }
95
+
96
+ // ANSI colors
97
+ const colors = {
98
+ reset: '\x1b[0m',
99
+ red: '\x1b[31m',
100
+ green: '\x1b[32m',
101
+ yellow: '\x1b[33m',
102
+ blue: '\x1b[34m',
103
+ magenta: '\x1b[35m',
104
+ cyan: '\x1b[36m',
105
+ gray: '\x1b[90m',
106
+ };
107
+
108
+ function log(message, color = 'reset') {
109
+ console.log(`${colors[color]}${message}${colors.reset}`);
110
+ }
111
+
112
+ // Parse CLI arguments
113
+ const args = process.argv.slice(2);
114
+ const options = {
115
+ package: args.find((arg) => arg.startsWith('--package='))?.split('=')[1],
116
+ fix: args.includes('--fix'),
117
+ verbose: args.includes('--verbose') || args.includes('-v'),
118
+ };
119
+
120
+ /**
121
+ * Extract imports from TypeScript/JavaScript file
122
+ */
123
+ function extractImports(filePath, content) {
124
+ const imports = [];
125
+
126
+ // Match: import ... from 'module'
127
+ // Match: import('module')
128
+ // Match: require('module')
129
+ const patterns = [
130
+ /import\s+(?:[\w*{}\n\r\t, ]+\s+from\s+)?['"]([^'"]+)['"]/g,
131
+ /import\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
132
+ /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
133
+ ];
134
+
135
+ for (const pattern of patterns) {
136
+ let match;
137
+ while ((match = pattern.exec(content)) !== null) {
138
+ const importPath = match[1];
139
+ imports.push({
140
+ path: importPath,
141
+ line: content.substring(0, match.index).split('\n').length,
142
+ });
143
+ }
144
+ }
145
+
146
+ return imports;
147
+ }
148
+
149
+ /**
150
+ * Check if import path is a local file import
151
+ */
152
+ function isLocalImport(importPath) {
153
+ return importPath.startsWith('.') || importPath.startsWith('/');
154
+ }
155
+
156
+ /**
157
+ * Check if import path is a node built-in
158
+ */
159
+ function isNodeBuiltin(importPath) {
160
+ // If it starts with node:, it's definitely a builtin
161
+ if (importPath.startsWith('node:')) {
162
+ return true;
163
+ }
164
+
165
+ const builtins = [
166
+ 'assert', 'buffer', 'child_process', 'cluster', 'crypto', 'dgram',
167
+ 'dns', 'domain', 'events', 'fs', 'http', 'https', 'net', 'os',
168
+ 'path', 'punycode', 'querystring', 'readline', 'repl', 'stream',
169
+ 'string_decoder', 'timers', 'tls', 'tty', 'url', 'util', 'v8',
170
+ 'vm', 'zlib', 'module', 'inspector', 'perf_hooks', 'worker_threads',
171
+ 'async_hooks', 'trace_events', 'diagnostics_channel',
172
+ ];
173
+
174
+ // Also check for subpaths like fs/promises
175
+ const baseModule = importPath.split('/')[0];
176
+ return builtins.includes(baseModule);
177
+ }
178
+
179
+ /**
180
+ * Resolve import path to actual file
181
+ */
182
+ function resolveImport(importPath, sourceFile, packageRoot) {
183
+ if (!isLocalImport(importPath)) {
184
+ return null; // External dependency
185
+ }
186
+
187
+ const sourceDir = path.dirname(sourceFile);
188
+ const extensions = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.json'];
189
+
190
+ // Try exact path
191
+ const resolved = path.resolve(sourceDir, importPath);
192
+
193
+ // Try with extensions
194
+ for (const ext of extensions) {
195
+ const withExt = resolved + ext;
196
+ if (fs.existsSync(withExt) && fs.statSync(withExt).isFile()) {
197
+ return withExt;
198
+ }
199
+ }
200
+
201
+ // Try as directory with index file
202
+ if (fs.existsSync(resolved) && fs.statSync(resolved).isDirectory()) {
203
+ for (const ext of extensions) {
204
+ const indexFile = path.join(resolved, `index${ext}`);
205
+ if (fs.existsSync(indexFile)) {
206
+ return indexFile;
207
+ }
208
+ }
209
+ }
210
+
211
+ return null; // Could not resolve
212
+ }
213
+
214
+ /**
215
+ * Find all source files in package
216
+ */
217
+ function findSourceFiles(packageDir) {
218
+ const files = [];
219
+ const srcDir = path.join(packageDir, 'src');
220
+
221
+ if (!fs.existsSync(srcDir)) {
222
+ return files;
223
+ }
224
+
225
+ function walk(dir) {
226
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
227
+
228
+ for (const entry of entries) {
229
+ const fullPath = path.join(dir, entry.name);
230
+
231
+ if (entry.isDirectory()) {
232
+ walk(fullPath);
233
+ } else if (entry.isFile() && /\.(ts|tsx|js|jsx|mjs|cjs)$/.test(entry.name)) {
234
+ files.push(fullPath);
235
+ }
236
+ }
237
+ }
238
+
239
+ walk(srcDir);
240
+ return files;
241
+ }
242
+
243
+ /**
244
+ * Check package for import issues
245
+ */
246
+ function checkPackage(packageJsonPath) {
247
+ const packageDir = path.dirname(packageJsonPath);
248
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
249
+ const packageName = packageJson.name;
250
+
251
+ if (!packageName || !packageName.startsWith('@kb-labs/')) {
252
+ return null; // Skip non-KB Labs packages
253
+ }
254
+
255
+ const issues = {
256
+ brokenImports: [],
257
+ unusedDeps: [],
258
+ missingWorkspaceDeps: [],
259
+ missingNpmDeps: [],
260
+ usedDeps: new Set(),
261
+ };
262
+
263
+ const dependencies = {
264
+ ...packageJson.dependencies,
265
+ ...packageJson.devDependencies,
266
+ };
267
+
268
+ const sourceFiles = findSourceFiles(packageDir);
269
+
270
+ if (sourceFiles.length === 0) {
271
+ return null; // No source files
272
+ }
273
+
274
+ // Check each source file
275
+ for (const sourceFile of sourceFiles) {
276
+ const content = fs.readFileSync(sourceFile, 'utf-8');
277
+ const imports = extractImports(sourceFile, content);
278
+
279
+ for (const imp of imports) {
280
+ const { path: importPath, line } = imp;
281
+
282
+ // Check local imports
283
+ if (isLocalImport(importPath)) {
284
+ const resolved = resolveImport(importPath, sourceFile, packageDir);
285
+
286
+ if (!resolved) {
287
+ issues.brokenImports.push({
288
+ file: path.relative(packageDir, sourceFile),
289
+ line,
290
+ import: importPath,
291
+ });
292
+ }
293
+ }
294
+ // Check external dependencies
295
+ else if (!isNodeBuiltin(importPath)) {
296
+ // Extract package name (handle scoped packages and subpaths)
297
+ const match = importPath.match(/^(@[^/]+\/[^/]+|[^/]+)/);
298
+ const depName = match ? match[1] : importPath;
299
+
300
+ issues.usedDeps.add(depName);
301
+
302
+ // Check if it's a workspace package that should be in dependencies
303
+ if (depName.startsWith('@kb-labs/')) {
304
+ if (!dependencies[depName]) {
305
+ issues.missingWorkspaceDeps.push({
306
+ file: path.relative(packageDir, sourceFile),
307
+ line,
308
+ package: depName,
309
+ });
310
+ }
311
+ }
312
+ // Check if it's a regular npm package that should be in dependencies
313
+ else {
314
+ if (!dependencies[depName]) {
315
+ issues.missingNpmDeps.push({
316
+ file: path.relative(packageDir, sourceFile),
317
+ line,
318
+ package: depName,
319
+ });
320
+ }
321
+ }
322
+ }
323
+ }
324
+ }
325
+
326
+ // Find unused dependencies
327
+ const allDeps = Object.keys(dependencies);
328
+ for (const dep of allDeps) {
329
+ // Skip type-only packages and build tools
330
+ if (
331
+ dep.startsWith('@types/') ||
332
+ dep === 'typescript' ||
333
+ dep === 'tsup' ||
334
+ dep === 'vitest' ||
335
+ dep === 'rimraf' ||
336
+ dep === 'eslint' ||
337
+ dep === 'prettier' ||
338
+ dep === '@kb-labs/devkit' ||
339
+ dep.startsWith('eslint-') ||
340
+ dep.startsWith('@vitest/') ||
341
+ dep.startsWith('@testing-library/')
342
+ ) {
343
+ continue;
344
+ }
345
+
346
+ if (!issues.usedDeps.has(dep)) {
347
+ issues.unusedDeps.push(dep);
348
+ }
349
+ }
350
+
351
+ return {
352
+ packageName,
353
+ packageDir,
354
+ packageJsonPath,
355
+ ...issues,
356
+ };
357
+ }
358
+
359
+ /**
360
+ * Find all packages in monorepo
361
+ */
362
+ function findPackages(rootDir, filterPackage) {
363
+ const packages = [];
364
+ const entries = fs.readdirSync(rootDir, { withFileTypes: true });
365
+
366
+ for (const entry of entries) {
367
+ if (!entry.isDirectory() || !entry.name.startsWith('kb-labs-')) {continue;}
368
+
369
+ const repoPath = path.join(rootDir, entry.name);
370
+ const packagesDir = path.join(repoPath, 'packages');
371
+
372
+ if (!fs.existsSync(packagesDir)) {continue;}
373
+
374
+ const packageDirs = fs.readdirSync(packagesDir, { withFileTypes: true });
375
+
376
+ for (const pkgDir of packageDirs) {
377
+ if (!pkgDir.isDirectory()) {continue;}
378
+
379
+ // Filter by package name if specified
380
+ if (filterPackage && pkgDir.name !== filterPackage) {continue;}
381
+
382
+ const packageJsonPath = path.join(packagesDir, pkgDir.name, 'package.json');
383
+
384
+ if (fs.existsSync(packageJsonPath)) {
385
+ packages.push(packageJsonPath);
386
+ }
387
+ }
388
+ }
389
+
390
+ return packages;
391
+ }
392
+
393
+ /**
394
+ * Detect circular dependencies between packages
395
+ */
396
+ function detectCircularDeps(allResults) {
397
+ const graph = new Map();
398
+ const packagePaths = new Map();
399
+
400
+ // Build dependency graph
401
+ for (const result of allResults) {
402
+ if (!result) {continue;}
403
+
404
+ const deps = [];
405
+ const packageJson = JSON.parse(fs.readFileSync(result.packageJsonPath, 'utf-8'));
406
+ const allDeps = {
407
+ ...packageJson.dependencies,
408
+ ...packageJson.devDependencies,
409
+ };
410
+
411
+ for (const dep of Object.keys(allDeps)) {
412
+ if (dep.startsWith('@kb-labs/')) {
413
+ deps.push(dep);
414
+ }
415
+ }
416
+
417
+ graph.set(result.packageName, deps);
418
+ packagePaths.set(result.packageName, result.packageDir);
419
+ }
420
+
421
+ // Find cycles using DFS
422
+ const cycles = [];
423
+ const visited = new Set();
424
+ const recStack = new Set();
425
+
426
+ function dfs(node, path = []) {
427
+ if (recStack.has(node)) {
428
+ // Found a cycle
429
+ const cycleStart = path.indexOf(node);
430
+ const cycle = [...path.slice(cycleStart), node];
431
+ cycles.push(cycle);
432
+ return;
433
+ }
434
+
435
+ if (visited.has(node)) {return;}
436
+
437
+ visited.add(node);
438
+ recStack.add(node);
439
+ path.push(node);
440
+
441
+ const neighbors = graph.get(node) || [];
442
+ for (const neighbor of neighbors) {
443
+ if (graph.has(neighbor)) {
444
+ dfs(neighbor, [...path]);
445
+ }
446
+ }
447
+
448
+ recStack.delete(node);
449
+ }
450
+
451
+ for (const node of graph.keys()) {
452
+ dfs(node);
453
+ }
454
+
455
+ // Remove duplicate cycles
456
+ const uniqueCycles = [];
457
+ const seen = new Set();
458
+
459
+ for (const cycle of cycles) {
460
+ const normalized = [...cycle].sort().join('→');
461
+ if (!seen.has(normalized)) {
462
+ seen.add(normalized);
463
+ uniqueCycles.push(cycle);
464
+ }
465
+ }
466
+
467
+ return uniqueCycles;
468
+ }
469
+
470
+ /**
471
+ * Main function
472
+ */
473
+ function main() {
474
+ const rootDir = process.cwd();
475
+
476
+ log('\nšŸ” KB Labs Import Checker\n', 'blue');
477
+
478
+ // Load ignore rules
479
+ const ignoreRules = parseDevkitIgnore(rootDir);
480
+ const hasIgnoreFile = fs.existsSync(path.join(rootDir, '.devkitignore'));
481
+ const totalIgnoreRules = Object.values(ignoreRules).reduce((sum, arr) => sum + arr.length, 0);
482
+
483
+ if (hasIgnoreFile && totalIgnoreRules > 0) {
484
+ log(`Loaded ${totalIgnoreRules} ignore rule(s) from .devkitignore\n`, 'gray');
485
+ }
486
+
487
+ if (options.package) {
488
+ log(`Checking package: ${options.package}\n`, 'gray');
489
+ } else {
490
+ log('Checking all packages...\n', 'gray');
491
+ }
492
+
493
+ const packages = findPackages(rootDir, options.package);
494
+
495
+ if (packages.length === 0) {
496
+ log('āš ļø No KB Labs packages found', 'yellow');
497
+ log(' Run this command from the monorepo root\n', 'gray');
498
+ process.exit(0);
499
+ }
500
+
501
+ log(`Found ${packages.length} package(s) to check\n`, 'gray');
502
+
503
+ const results = [];
504
+ let totalIssues = 0;
505
+
506
+ // Check each package
507
+ for (const packagePath of packages) {
508
+ const result = checkPackage(packagePath);
509
+ if (result) {
510
+ results.push(result);
511
+
512
+ const issueCount =
513
+ result.brokenImports.length +
514
+ result.unusedDeps.length +
515
+ result.missingWorkspaceDeps.length;
516
+
517
+ if (issueCount > 0) {
518
+ totalIssues += issueCount;
519
+ }
520
+ }
521
+ }
522
+
523
+ // Detect circular dependencies
524
+ const cycles = detectCircularDeps(results);
525
+
526
+ // Track ignored items for summary
527
+ let ignoredBroken = 0;
528
+ let ignoredMissing = 0;
529
+ let ignoredUnused = 0;
530
+
531
+ // Print results
532
+ let hasIssues = false;
533
+
534
+ for (const result of results) {
535
+ // Filter issues based on ignore rules
536
+ const filteredBroken = result.brokenImports.filter((issue) => {
537
+ const ignored = shouldIgnore(ignoreRules, 'broken-import', result.packageName, issue.import);
538
+ if (ignored) {ignoredBroken++;}
539
+ return !ignored;
540
+ });
541
+
542
+ const uniqueMissingDeps = [...new Set(result.missingWorkspaceDeps.map((d) => d.package))];
543
+ const filteredMissingDeps = uniqueMissingDeps.filter((dep) => {
544
+ const ignored = shouldIgnore(ignoreRules, 'missing-dep', result.packageName, dep);
545
+ if (ignored) {ignoredMissing++;}
546
+ return !ignored;
547
+ });
548
+
549
+ const uniqueMissingNpmDeps = [...new Set(result.missingNpmDeps.map((d) => d.package))];
550
+ const filteredMissingNpmDeps = uniqueMissingNpmDeps.filter((dep) => {
551
+ const ignored = shouldIgnore(ignoreRules, 'missing-dep', result.packageName, dep);
552
+ if (ignored) {ignoredMissing++;}
553
+ return !ignored;
554
+ });
555
+
556
+ const filteredUnused = result.unusedDeps.filter((dep) => {
557
+ const ignored = shouldIgnore(ignoreRules, 'unused-dep', result.packageName, dep);
558
+ if (ignored) {ignoredUnused++;}
559
+ return !ignored;
560
+ });
561
+
562
+ const issueCount =
563
+ filteredBroken.length +
564
+ filteredMissingDeps.length +
565
+ filteredMissingNpmDeps.length +
566
+ filteredUnused.length;
567
+
568
+ if (issueCount === 0 && !options.verbose) {continue;}
569
+
570
+ hasIssues = hasIssues || issueCount > 0;
571
+
572
+ if (issueCount > 0) {
573
+ log(`\nāŒ ${result.packageName}`, 'red');
574
+ log(` ${path.relative(rootDir, result.packageDir)}`, 'gray');
575
+ } else if (options.verbose) {
576
+ log(`\nāœ… ${result.packageName}`, 'green');
577
+ }
578
+
579
+ // Broken imports
580
+ if (filteredBroken.length > 0) {
581
+ log(`\n šŸ”“ Broken imports (${filteredBroken.length}):`, 'red');
582
+ for (const issue of filteredBroken) {
583
+ log(` ${issue.file}:${issue.line}`, 'yellow');
584
+ log(` └─ Cannot resolve: ${issue.import}`, 'gray');
585
+ }
586
+ }
587
+
588
+ // Missing workspace dependencies
589
+ if (filteredMissingDeps.length > 0) {
590
+ log(`\n 🟔 Missing workspace dependencies (${filteredMissingDeps.length}):`, 'yellow');
591
+ for (const dep of filteredMissingDeps) {
592
+ const usages = result.missingWorkspaceDeps.filter((d) => d.package === dep);
593
+ log(` ${dep}`, 'cyan');
594
+ log(` └─ Used in ${usages.length} file(s)`, 'gray');
595
+ if (options.verbose) {
596
+ for (const usage of usages.slice(0, 3)) {
597
+ log(` - ${usage.file}:${usage.line}`, 'gray');
598
+ }
599
+ if (usages.length > 3) {
600
+ log(` ... and ${usages.length - 3} more`, 'gray');
601
+ }
602
+ }
603
+ }
604
+ }
605
+
606
+ // Missing npm dependencies
607
+ if (filteredMissingNpmDeps.length > 0) {
608
+ log(`\n 🟔 Missing npm dependencies (${filteredMissingNpmDeps.length}):`, 'yellow');
609
+ for (const dep of filteredMissingNpmDeps) {
610
+ const usages = result.missingNpmDeps.filter((d) => d.package === dep);
611
+ log(` ${dep}`, 'cyan');
612
+ log(` └─ Used in ${usages.length} file(s)`, 'gray');
613
+ if (options.verbose) {
614
+ for (const usage of usages.slice(0, 3)) {
615
+ log(` - ${usage.file}:${usage.line}`, 'gray');
616
+ }
617
+ if (usages.length > 3) {
618
+ log(` ... and ${usages.length - 3} more`, 'gray');
619
+ }
620
+ }
621
+ }
622
+ }
623
+
624
+ // Unused dependencies
625
+ if (filteredUnused.length > 0) {
626
+ log(`\n 🟠 Unused dependencies (${filteredUnused.length}):`, 'yellow');
627
+ for (const dep of filteredUnused) {
628
+ log(` ${dep}`, 'gray');
629
+ }
630
+ if (options.fix) {
631
+ log(` šŸ’” Run with --fix to remove unused dependencies`, 'blue');
632
+ }
633
+ }
634
+ }
635
+
636
+ // Print circular dependencies
637
+ if (cycles.length > 0) {
638
+ hasIssues = true;
639
+ log(`\n\nšŸ”„ Circular Dependencies (${cycles.length}):\n`, 'magenta');
640
+
641
+ for (let i = 0; i < cycles.length; i++) {
642
+ const cycle = cycles[i];
643
+ log(`${i + 1}. ${cycle.join(' → ')}`, 'yellow');
644
+ }
645
+
646
+ log('\n āš ļø Circular dependencies can cause build and runtime issues', 'gray');
647
+ log(' šŸ’” Consider refactoring to extract shared code into a new package\n', 'gray');
648
+ }
649
+
650
+ // Summary
651
+ log('\n' + '─'.repeat(60) + '\n', 'gray');
652
+
653
+ if (!hasIssues && cycles.length === 0) {
654
+ log('āœ… No import issues found!\n', 'green');
655
+ if (ignoredBroken + ignoredMissing + ignoredUnused > 0) {
656
+ log(` ā„¹ļø ${ignoredBroken + ignoredMissing + ignoredUnused} issue(s) ignored via .devkitignore\n`, 'gray');
657
+ }
658
+ process.exit(0);
659
+ }
660
+
661
+ log('šŸ“Š Summary:\n', 'blue');
662
+
663
+ // Calculate filtered counts
664
+ let brokenCount = 0;
665
+ let missingCount = 0;
666
+ let unusedCount = 0;
667
+
668
+ for (const result of results) {
669
+ brokenCount += result.brokenImports.filter(
670
+ (issue) => !shouldIgnore(ignoreRules, 'broken-import', result.packageName, issue.import)
671
+ ).length;
672
+
673
+ const uniqueDeps = [...new Set(result.missingWorkspaceDeps.map((d) => d.package))];
674
+ missingCount += uniqueDeps.filter(
675
+ (dep) => !shouldIgnore(ignoreRules, 'missing-dep', result.packageName, dep)
676
+ ).length;
677
+
678
+ unusedCount += result.unusedDeps.filter(
679
+ (dep) => !shouldIgnore(ignoreRules, 'unused-dep', result.packageName, dep)
680
+ ).length;
681
+ }
682
+
683
+ if (brokenCount > 0) {
684
+ log(` šŸ”“ ${brokenCount} broken import(s)`, 'red');
685
+ }
686
+ if (missingCount > 0) {
687
+ log(` 🟔 ${missingCount} missing workspace dep(s)`, 'yellow');
688
+ }
689
+ if (unusedCount > 0) {
690
+ log(` 🟠 ${unusedCount} unused dependency(ies)`, 'yellow');
691
+ }
692
+ if (cycles.length > 0) {
693
+ log(` šŸ”„ ${cycles.length} circular dependency cycle(s)`, 'magenta');
694
+ }
695
+
696
+ // Show ignored count
697
+ const totalIgnored = ignoredBroken + ignoredMissing + ignoredUnused;
698
+ if (totalIgnored > 0) {
699
+ log(`\n ā„¹ļø ${totalIgnored} issue(s) ignored via .devkitignore`, 'gray');
700
+ }
701
+
702
+ log('\nšŸ’” Tips:', 'blue');
703
+ log(' • Use --verbose to see all packages (including clean ones)', 'gray');
704
+ log(' • Use --package=<name> to check a specific package', 'gray');
705
+ log(' • Use --fix to auto-remove unused dependencies (coming soon)', 'gray');
706
+ log(' • Add rules to .devkitignore to suppress known issues', 'gray');
707
+ log('');
708
+
709
+ process.exit(1);
710
+ }
711
+
712
+ main();