@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,561 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * @kb-labs/devkit - Export Checker
5
+ *
6
+ * Checks for:
7
+ * 1. Unused exports (exported but never imported by other packages)
8
+ * 2. Dead code in public APIs (exports without consumers)
9
+ * 3. Missing barrel exports (files not exported from index.ts)
10
+ * 4. Inconsistent exports (package.json exports vs actual files)
11
+ *
12
+ * Usage:
13
+ * kb-devkit-check-exports # Check all packages
14
+ * kb-devkit-check-exports --package cli-core # Check specific package
15
+ * kb-devkit-check-exports --strict # Include internal-only exports
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
+ // ANSI colors
25
+ const colors = {
26
+ reset: '\x1b[0m',
27
+ red: '\x1b[31m',
28
+ green: '\x1b[32m',
29
+ yellow: '\x1b[33m',
30
+ blue: '\x1b[34m',
31
+ magenta: '\x1b[35m',
32
+ cyan: '\x1b[36m',
33
+ gray: '\x1b[90m',
34
+ };
35
+
36
+ function log(message, color = 'reset') {
37
+ console.log(`${colors[color]}${message}${colors.reset}`);
38
+ }
39
+
40
+ // Parse CLI arguments
41
+ const args = process.argv.slice(2);
42
+ const options = {
43
+ package: args.find((arg) => arg.startsWith('--package='))?.split('=')[1],
44
+ strict: args.includes('--strict'),
45
+ verbose: args.includes('--verbose') || args.includes('-v'),
46
+ };
47
+
48
+ /**
49
+ * Extract exports from TypeScript/JavaScript file
50
+ */
51
+ function extractExports(filePath, content) {
52
+ const exports = [];
53
+
54
+ // Match: export const/let/var/function/class/interface/type/enum name
55
+ // Match: export { name1, name2 }
56
+ // Match: export * from 'module'
57
+ const patterns = [
58
+ // export const foo = ...
59
+ /export\s+(?:const|let|var)\s+(\w+)/g,
60
+ // export function foo() {}
61
+ /export\s+function\s+(\w+)/g,
62
+ // export class Foo {}
63
+ /export\s+class\s+(\w+)/g,
64
+ // export interface Foo {}
65
+ /export\s+interface\s+(\w+)/g,
66
+ // export type Foo = ...
67
+ /export\s+type\s+(\w+)/g,
68
+ // export enum Foo {}
69
+ /export\s+enum\s+(\w+)/g,
70
+ // export { foo, bar }
71
+ /export\s*{([^}]+)}/g,
72
+ ];
73
+
74
+ for (const pattern of patterns) {
75
+ let match;
76
+ while ((match = pattern.exec(content)) !== null) {
77
+ const exportName = match[1];
78
+ const line = content.substring(0, match.index).split('\n').length;
79
+
80
+ // Handle export { foo, bar } - multiple exports
81
+ if (exportName.includes(',')) {
82
+ const names = exportName
83
+ .split(',')
84
+ .map((n) => n.trim())
85
+ .filter((n) => n && !n.includes('as')); // Skip "as" renames for now
86
+
87
+ for (const name of names) {
88
+ exports.push({ name, line, type: 'named' });
89
+ }
90
+ } else {
91
+ exports.push({ name: exportName, line, type: 'named' });
92
+ }
93
+ }
94
+ }
95
+
96
+ // Check for default export
97
+ if (/export\s+default/.test(content)) {
98
+ const line = content.substring(0, content.indexOf('export default')).split('\n').length;
99
+ exports.push({ name: 'default', line, type: 'default' });
100
+ }
101
+
102
+ // Check for re-exports (export * from 'module')
103
+ const reExportPattern = /export\s+\*\s+from\s+['"]([^'"]+)['"]/g;
104
+ let reExportMatch;
105
+ while ((reExportMatch = reExportPattern.exec(content)) !== null) {
106
+ const line = content.substring(0, reExportMatch.index).split('\n').length;
107
+ exports.push({
108
+ name: `* from "${reExportMatch[1]}"`,
109
+ line,
110
+ type: 're-export',
111
+ });
112
+ }
113
+
114
+ return exports;
115
+ }
116
+
117
+ /**
118
+ * Extract imports from TypeScript/JavaScript file
119
+ */
120
+ function extractImports(filePath, content) {
121
+ const imports = [];
122
+
123
+ // Match: import { name1, name2 } from 'module'
124
+ // Match: import name from 'module'
125
+ const namedImportPattern = /import\s+{([^}]+)}\s+from\s+['"]([^'"]+)['"]/g;
126
+ const defaultImportPattern = /import\s+(\w+)\s+from\s+['"]([^'"]+)['"]/g;
127
+
128
+ let match;
129
+
130
+ // Named imports
131
+ while ((match = namedImportPattern.exec(content)) !== null) {
132
+ const names = match[1]
133
+ .split(',')
134
+ .map((n) => n.trim())
135
+ .filter((n) => n && !n.includes('as')); // Skip "as" renames
136
+
137
+ const module = match[2];
138
+
139
+ for (const name of names) {
140
+ imports.push({ name, module, type: 'named' });
141
+ }
142
+ }
143
+
144
+ // Default imports
145
+ while ((match = defaultImportPattern.exec(content)) !== null) {
146
+ const name = match[1];
147
+ const module = match[2];
148
+
149
+ // Skip if this is actually a named import (has { after import)
150
+ if (!content.substring(match.index, match.index + 50).includes('{')) {
151
+ imports.push({ name, module, type: 'default' });
152
+ }
153
+ }
154
+
155
+ return imports;
156
+ }
157
+
158
+ /**
159
+ * Find all source files in package
160
+ */
161
+ function findSourceFiles(packageDir) {
162
+ const files = [];
163
+ const srcDir = path.join(packageDir, 'src');
164
+
165
+ if (!fs.existsSync(srcDir)) {
166
+ return files;
167
+ }
168
+
169
+ function walk(dir) {
170
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
171
+
172
+ for (const entry of entries) {
173
+ const fullPath = path.join(dir, entry.name);
174
+
175
+ if (entry.isDirectory()) {
176
+ walk(fullPath);
177
+ } else if (entry.isFile() && /\.(ts|tsx|js|jsx|mjs|cjs)$/.test(entry.name)) {
178
+ files.push(fullPath);
179
+ }
180
+ }
181
+ }
182
+
183
+ walk(srcDir);
184
+ return files;
185
+ }
186
+
187
+ /**
188
+ * Check if export is used in package.json exports field
189
+ */
190
+ function isUsedInPackageExports(exportName, packageJson) {
191
+ if (!packageJson.exports) {return false;}
192
+
193
+ const exportsStr = JSON.stringify(packageJson.exports);
194
+ return exportsStr.includes(exportName);
195
+ }
196
+
197
+ /**
198
+ * Check package for export issues
199
+ */
200
+ function checkPackage(packageJsonPath, allPackages) {
201
+ const packageDir = path.dirname(packageJsonPath);
202
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
203
+ const packageName = packageJson.name;
204
+
205
+ if (!packageName || !packageName.startsWith('@kb-labs/')) {
206
+ return null; // Skip non-KB Labs packages
207
+ }
208
+
209
+ const issues = {
210
+ unusedExports: [],
211
+ missingBarrelExports: [],
212
+ inconsistentExports: [],
213
+ };
214
+
215
+ const sourceFiles = findSourceFiles(packageDir);
216
+
217
+ if (sourceFiles.length === 0) {
218
+ return null; // No source files
219
+ }
220
+
221
+ // Step 1: Collect all exports from all files
222
+ const allExports = new Map(); // file -> exports[]
223
+
224
+ for (const sourceFile of sourceFiles) {
225
+ const content = fs.readFileSync(sourceFile, 'utf-8');
226
+ const exports = extractExports(sourceFile, content);
227
+
228
+ if (exports.length > 0) {
229
+ allExports.set(sourceFile, exports);
230
+ }
231
+ }
232
+
233
+ // Step 2: Collect all imports from other packages
234
+ const externalImports = new Map(); // packageName -> imports[]
235
+
236
+ for (const otherPackagePath of allPackages) {
237
+ if (otherPackagePath === packageJsonPath) {continue;} // Skip self
238
+
239
+ const otherPackageDir = path.dirname(otherPackagePath);
240
+ const otherSourceFiles = findSourceFiles(otherPackageDir);
241
+
242
+ for (const otherSourceFile of otherSourceFiles) {
243
+ const content = fs.readFileSync(otherSourceFile, 'utf-8');
244
+ const imports = extractImports(otherSourceFile, content);
245
+
246
+ for (const imp of imports) {
247
+ // Check if importing from our package
248
+ if (imp.module === packageName || imp.module.startsWith(`${packageName}/`)) {
249
+ if (!externalImports.has(imp.module)) {
250
+ externalImports.set(imp.module, []);
251
+ }
252
+ externalImports.get(imp.module).push(imp.name);
253
+ }
254
+ }
255
+ }
256
+ }
257
+
258
+ // Step 3: Check for unused exports
259
+ const indexFile = sourceFiles.find((f) => /\/index\.(ts|tsx|js|jsx|mjs|cjs)$/.test(f));
260
+ const indexExports = indexFile ? allExports.get(indexFile) || [] : [];
261
+ const importedNames = new Set();
262
+
263
+ for (const imports of externalImports.values()) {
264
+ for (const name of imports) {
265
+ importedNames.add(name);
266
+ }
267
+ }
268
+
269
+ for (const [file, exports] of allExports.entries()) {
270
+ const relativeFile = path.relative(packageDir, file);
271
+
272
+ // Skip test files
273
+ if (relativeFile.includes('.test.') || relativeFile.includes('.spec.')) {
274
+ continue;
275
+ }
276
+
277
+ for (const exp of exports) {
278
+ // Skip re-exports and default exports (harder to track)
279
+ if (exp.type === 're-export' || exp.type === 'default') {
280
+ continue;
281
+ }
282
+
283
+ // Skip if used in package.json exports
284
+ if (isUsedInPackageExports(exp.name, packageJson)) {
285
+ continue;
286
+ }
287
+
288
+ // Check if exported from index (public API)
289
+ const isInIndex = indexExports.some((e) => e.name === exp.name);
290
+
291
+ // If not exported from index and not imported externally
292
+ if (!isInIndex && !importedNames.has(exp.name)) {
293
+ // In strict mode, report all unused exports
294
+ // In normal mode, only report exports from index.ts
295
+ if (options.strict || file === indexFile) {
296
+ issues.unusedExports.push({
297
+ file: relativeFile,
298
+ line: exp.line,
299
+ name: exp.name,
300
+ type: exp.type,
301
+ isPublic: file === indexFile,
302
+ });
303
+ }
304
+ }
305
+ }
306
+ }
307
+
308
+ // Step 4: Check for missing barrel exports (files not exported from index.ts)
309
+ if (indexFile && sourceFiles.length > 1) {
310
+ const indexContent = fs.readFileSync(indexFile, 'utf-8');
311
+
312
+ for (const file of sourceFiles) {
313
+ if (file === indexFile) {continue;}
314
+
315
+ // Skip test files, internal files
316
+ const relativeFile = path.relative(path.dirname(indexFile), file);
317
+ if (
318
+ relativeFile.includes('.test.') ||
319
+ relativeFile.includes('.spec.') ||
320
+ relativeFile.includes('internal/') ||
321
+ relativeFile.includes('__tests__/') ||
322
+ relativeFile.includes('__mocks__/')
323
+ ) {
324
+ continue;
325
+ }
326
+
327
+ // Check if file is re-exported from index
328
+ const fileExports = allExports.get(file) || [];
329
+ if (fileExports.length === 0) {continue;}
330
+
331
+ const fileBasename = path.basename(file).replace(/\.(ts|tsx|js|jsx|mjs|cjs)$/, '');
332
+ const isReExported =
333
+ indexContent.includes(`from './${relativeFile.replace(/\.(ts|tsx|js|jsx|mjs|cjs)$/, '')}`) ||
334
+ indexContent.includes(`from "./${relativeFile.replace(/\.(ts|tsx|js|jsx|mjs|cjs)$/, '')}`) ||
335
+ indexContent.includes(`from './${fileBasename}'`) ||
336
+ indexContent.includes(`from "./${fileBasename}"`);
337
+
338
+ if (!isReExported && !options.strict) {
339
+ // Only report in strict mode for now (too noisy otherwise)
340
+ continue;
341
+ }
342
+
343
+ if (!isReExported) {
344
+ issues.missingBarrelExports.push({
345
+ file: relativeFile,
346
+ exportsCount: fileExports.length,
347
+ });
348
+ }
349
+ }
350
+ }
351
+
352
+ // Step 5: Check package.json exports consistency
353
+ if (packageJson.exports && typeof packageJson.exports === 'object') {
354
+ for (const [exportPath, exportConfig] of Object.entries(packageJson.exports)) {
355
+ if (exportPath === '.') {continue;} // Main export
356
+
357
+ // Get the actual file path
358
+ const actualPath =
359
+ typeof exportConfig === 'string'
360
+ ? exportConfig
361
+ : exportConfig.import || exportConfig.require || exportConfig.default;
362
+
363
+ if (!actualPath || typeof actualPath !== 'string') {continue;}
364
+
365
+ // Check if file exists
366
+ const fullPath = path.join(packageDir, actualPath);
367
+ if (!fs.existsSync(fullPath)) {
368
+ issues.inconsistentExports.push({
369
+ exportPath,
370
+ actualPath,
371
+ issue: 'File does not exist',
372
+ });
373
+ }
374
+ }
375
+ }
376
+
377
+ return {
378
+ packageName,
379
+ packageDir,
380
+ packageJsonPath,
381
+ ...issues,
382
+ };
383
+ }
384
+
385
+ /**
386
+ * Find all packages in monorepo
387
+ */
388
+ function findPackages(rootDir, filterPackage) {
389
+ const packages = [];
390
+ const entries = fs.readdirSync(rootDir, { withFileTypes: true });
391
+
392
+ for (const entry of entries) {
393
+ if (!entry.isDirectory() || !entry.name.startsWith('kb-labs-')) {continue;}
394
+
395
+ const repoPath = path.join(rootDir, entry.name);
396
+ const packagesDir = path.join(repoPath, 'packages');
397
+
398
+ if (!fs.existsSync(packagesDir)) {continue;}
399
+
400
+ const packageDirs = fs.readdirSync(packagesDir, { withFileTypes: true });
401
+
402
+ for (const pkgDir of packageDirs) {
403
+ if (!pkgDir.isDirectory()) {continue;}
404
+
405
+ // Filter by package name if specified
406
+ if (filterPackage && pkgDir.name !== filterPackage) {continue;}
407
+
408
+ const packageJsonPath = path.join(packagesDir, pkgDir.name, 'package.json');
409
+
410
+ if (fs.existsSync(packageJsonPath)) {
411
+ packages.push(packageJsonPath);
412
+ }
413
+ }
414
+ }
415
+
416
+ return packages;
417
+ }
418
+
419
+ /**
420
+ * Main function
421
+ */
422
+ function main() {
423
+ const rootDir = process.cwd();
424
+
425
+ log('\nšŸ“¤ KB Labs Export Checker\n', 'blue');
426
+
427
+ if (options.package) {
428
+ log(`Checking package: ${options.package}\n`, 'gray');
429
+ } else {
430
+ log('Checking all packages...\n', 'gray');
431
+ }
432
+
433
+ if (options.strict) {
434
+ log('āš ļø Strict mode enabled (includes internal exports)\n', 'yellow');
435
+ }
436
+
437
+ const packages = findPackages(rootDir, options.package);
438
+
439
+ if (packages.length === 0) {
440
+ log('āš ļø No KB Labs packages found', 'yellow');
441
+ log(' Run this command from the monorepo root\n', 'gray');
442
+ process.exit(0);
443
+ }
444
+
445
+ log(`Found ${packages.length} package(s) to check\n`, 'gray');
446
+
447
+ const results = [];
448
+
449
+ // Check each package
450
+ for (const packagePath of packages) {
451
+ const result = checkPackage(packagePath, packages);
452
+ if (result) {
453
+ results.push(result);
454
+ }
455
+ }
456
+
457
+ // Print results
458
+ let hasIssues = false;
459
+
460
+ for (const result of results) {
461
+ const issueCount =
462
+ result.unusedExports.length +
463
+ result.missingBarrelExports.length +
464
+ result.inconsistentExports.length;
465
+
466
+ if (issueCount === 0 && !options.verbose) {continue;}
467
+
468
+ hasIssues = issueCount > 0;
469
+
470
+ if (issueCount > 0) {
471
+ log(`\nāŒ ${result.packageName}`, 'red');
472
+ log(` ${path.relative(rootDir, result.packageDir)}`, 'gray');
473
+ } else if (options.verbose) {
474
+ log(`\nāœ… ${result.packageName}`, 'green');
475
+ }
476
+
477
+ // Unused exports
478
+ if (result.unusedExports.length > 0) {
479
+ log(`\n 🟠 Unused exports (${result.unusedExports.length}):`, 'yellow');
480
+
481
+ // Group by file
482
+ const byFile = new Map();
483
+ for (const exp of result.unusedExports) {
484
+ if (!byFile.has(exp.file)) {
485
+ byFile.set(exp.file, []);
486
+ }
487
+ byFile.get(exp.file).push(exp);
488
+ }
489
+
490
+ for (const [file, exports] of byFile.entries()) {
491
+ log(` ${file}`, 'cyan');
492
+ for (const exp of exports) {
493
+ const visibility = exp.isPublic ? '(public API)' : '(internal)';
494
+ log(` └─ ${exp.name} ${visibility}`, 'gray');
495
+ }
496
+ }
497
+
498
+ log(`\n šŸ’” These exports are never imported by other packages`, 'blue');
499
+ log(` šŸ’” Consider removing them to reduce API surface`, 'blue');
500
+ }
501
+
502
+ // Missing barrel exports
503
+ if (result.missingBarrelExports.length > 0 && options.strict) {
504
+ log(`\n 🟔 Files not exported from index.ts (${result.missingBarrelExports.length}):`, 'yellow');
505
+
506
+ for (const file of result.missingBarrelExports) {
507
+ log(` ${file.file}`, 'gray');
508
+ log(` └─ ${file.exportsCount} export(s)`, 'gray');
509
+ }
510
+
511
+ log(`\n šŸ’” These files have exports but aren't re-exported from index.ts`, 'blue');
512
+ log(` šŸ’” Consider adding them to the barrel export or marking as internal`, 'blue');
513
+ }
514
+
515
+ // Inconsistent exports
516
+ if (result.inconsistentExports.length > 0) {
517
+ log(`\n šŸ”“ Inconsistent package.json exports (${result.inconsistentExports.length}):`, 'red');
518
+
519
+ for (const exp of result.inconsistentExports) {
520
+ log(` "${exp.exportPath}" → ${exp.actualPath}`, 'yellow');
521
+ log(` └─ ${exp.issue}`, 'gray');
522
+ }
523
+
524
+ log(`\n šŸ’” Update package.json exports field to match actual files`, 'blue');
525
+ }
526
+ }
527
+
528
+ // Summary
529
+ log('\n' + '─'.repeat(60) + '\n', 'gray');
530
+
531
+ if (!hasIssues) {
532
+ log('āœ… No export issues found!\n', 'green');
533
+ process.exit(0);
534
+ }
535
+
536
+ log('šŸ“Š Summary:\n', 'blue');
537
+
538
+ const unusedCount = results.reduce((sum, r) => sum + r.unusedExports.length, 0);
539
+ const missingCount = results.reduce((sum, r) => sum + r.missingBarrelExports.length, 0);
540
+ const inconsistentCount = results.reduce((sum, r) => sum + r.inconsistentExports.length, 0);
541
+
542
+ if (unusedCount > 0) {
543
+ log(` 🟠 ${unusedCount} unused export(s)`, 'yellow');
544
+ }
545
+ if (missingCount > 0 && options.strict) {
546
+ log(` 🟔 ${missingCount} file(s) not in barrel exports`, 'yellow');
547
+ }
548
+ if (inconsistentCount > 0) {
549
+ log(` šŸ”“ ${inconsistentCount} inconsistent package.json export(s)`, 'red');
550
+ }
551
+
552
+ log('\nšŸ’” Tips:', 'blue');
553
+ log(' • Use --verbose to see all packages (including clean ones)', 'gray');
554
+ log(' • Use --package=<name> to check a specific package', 'gray');
555
+ log(' • Use --strict to include internal exports (more thorough)', 'gray');
556
+ log('');
557
+
558
+ process.exit(1);
559
+ }
560
+
561
+ main();