@proteinjs/reflection-build 2.0.2 → 2.1.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 (246) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/src/ReflectionDoctor.d.ts +6 -1
  3. package/dist/src/ReflectionDoctor.js +53 -51
  4. package/dist/src/ReflectionDoctor.js.map +1 -1
  5. package/dist/src/codegen/CanonicalSourceGraph.d.ts +22 -0
  6. package/dist/src/codegen/CanonicalSourceGraph.js +60 -0
  7. package/dist/src/codegen/CanonicalSourceGraph.js.map +1 -0
  8. package/dist/src/codegen/writeGeneratedIndex.d.ts +12 -3
  9. package/dist/src/codegen/writeGeneratedIndex.js +21 -13
  10. package/dist/src/codegen/writeGeneratedIndex.js.map +1 -1
  11. package/dist/src/parser/PackageSourceFiles.d.ts +30 -0
  12. package/dist/src/parser/PackageSourceFiles.js +151 -0
  13. package/dist/src/parser/PackageSourceFiles.js.map +1 -0
  14. package/dist/src/parser/SharedQualifiedNames.d.ts +43 -0
  15. package/dist/src/parser/SharedQualifiedNames.js +81 -0
  16. package/dist/src/parser/SharedQualifiedNames.js.map +1 -0
  17. package/dist/src/parser/createGraphBuilder.d.ts +6 -1
  18. package/dist/src/parser/createGraphBuilder.js +16 -31
  19. package/dist/src/parser/createGraphBuilder.js.map +1 -1
  20. package/dist/src/parser/createSourceGraph.d.ts +6 -1
  21. package/dist/src/parser/createSourceGraph.js +23 -38
  22. package/dist/src/parser/createSourceGraph.js.map +1 -1
  23. package/dist/test/Class.test.js +1 -1
  24. package/dist/test/Class.test.js.map +1 -1
  25. package/dist/test/ExamplePackageFixture.d.ts +47 -0
  26. package/dist/test/ExamplePackageFixture.js +207 -0
  27. package/dist/test/ExamplePackageFixture.js.map +1 -0
  28. package/dist/test/GraphEmitRelativePaths.test.js +1 -1
  29. package/dist/test/GraphEmitRelativePaths.test.js.map +1 -1
  30. package/dist/test/PackageSourceFiles.test.d.ts +1 -0
  31. package/dist/test/PackageSourceFiles.test.js +57 -0
  32. package/dist/test/PackageSourceFiles.test.js.map +1 -0
  33. package/dist/test/ReflectionDoctor.test.js +55 -18
  34. package/dist/test/ReflectionDoctor.test.js.map +1 -1
  35. package/dist/test/ReproducibleGeneratedIndex.test.d.ts +1 -0
  36. package/dist/test/ReproducibleGeneratedIndex.test.js +224 -0
  37. package/dist/test/ReproducibleGeneratedIndex.test.js.map +1 -0
  38. package/dist/test/SharedQualifiedNames.test.d.ts +1 -0
  39. package/dist/test/SharedQualifiedNames.test.js +186 -0
  40. package/dist/test/SharedQualifiedNames.test.js.map +1 -0
  41. package/dist/test/examples/reproducible/index.d.ts +4 -0
  42. package/dist/test/examples/reproducible/index.js +21 -0
  43. package/dist/test/examples/reproducible/index.js.map +1 -0
  44. package/dist/test/examples/reproducible/src/alpha/Alpha.d.ts +10 -0
  45. package/dist/test/examples/reproducible/src/alpha/Alpha.js +18 -0
  46. package/dist/test/examples/reproducible/src/alpha/Alpha.js.map +1 -0
  47. package/dist/test/examples/reproducible/src/alpha/Defaults.d.ts +2 -0
  48. package/dist/test/examples/reproducible/src/alpha/Defaults.js +6 -0
  49. package/dist/test/examples/reproducible/src/alpha/Defaults.js.map +1 -0
  50. package/dist/test/examples/reproducible/src/beta/Beta.d.ts +5 -0
  51. package/dist/test/examples/reproducible/src/beta/Beta.js +12 -0
  52. package/dist/test/examples/reproducible/src/beta/Beta.js.map +1 -0
  53. package/dist/test/examples/reproducible/src/beta/deep/Gamma.d.ts +4 -0
  54. package/dist/test/examples/reproducible/src/beta/deep/Gamma.js +11 -0
  55. package/dist/test/examples/reproducible/src/beta/deep/Gamma.js.map +1 -0
  56. package/dist/test/examples/reproducible/src/zeta/Defaults.d.ts +2 -0
  57. package/dist/test/examples/reproducible/src/zeta/Defaults.js +6 -0
  58. package/dist/test/examples/reproducible/src/zeta/Defaults.js.map +1 -0
  59. package/dist/test/examples/reproducible/src/zeta/Zeta.d.ts +7 -0
  60. package/dist/test/examples/reproducible/src/zeta/Zeta.js +18 -0
  61. package/dist/test/examples/reproducible/src/zeta/Zeta.js.map +1 -0
  62. package/dist/test/examples/shared-name/index.d.ts +1 -0
  63. package/dist/test/examples/shared-name/index.js +18 -0
  64. package/dist/test/examples/shared-name/index.js.map +1 -0
  65. package/dist/test/examples/shared-name/src/alpha/Widget.d.ts +4 -0
  66. package/dist/test/examples/shared-name/src/alpha/Widget.js +3 -0
  67. package/dist/test/examples/shared-name/src/alpha/Widget.js.map +1 -0
  68. package/dist/test/examples/shared-name/src/beta/Widget.d.ts +3 -0
  69. package/dist/test/examples/shared-name/src/beta/Widget.js +13 -0
  70. package/dist/test/examples/shared-name/src/beta/Widget.js.map +1 -0
  71. package/dist/test/examples/shared-name/src/gamma/Gear.d.ts +7 -0
  72. package/dist/test/examples/shared-name/src/gamma/Gear.js +5 -0
  73. package/dist/test/examples/shared-name/src/gamma/Gear.js.map +1 -0
  74. package/dist/test/examples/shared-name-pruned/index.d.ts +1 -0
  75. package/dist/test/examples/shared-name-pruned/index.js +18 -0
  76. package/dist/test/examples/shared-name-pruned/index.js.map +1 -0
  77. package/dist/test/examples/shared-name-pruned/src/alpha/Limits.d.ts +3 -0
  78. package/dist/test/examples/shared-name-pruned/src/alpha/Limits.js +6 -0
  79. package/dist/test/examples/shared-name-pruned/src/alpha/Limits.js.map +1 -0
  80. package/dist/test/examples/shared-name-pruned/src/alpha/Shape.d.ts +3 -0
  81. package/dist/test/examples/shared-name-pruned/src/alpha/Shape.js +3 -0
  82. package/dist/test/examples/shared-name-pruned/src/alpha/Shape.js.map +1 -0
  83. package/dist/test/examples/shared-name-pruned/src/alpha/Tile.d.ts +7 -0
  84. package/dist/test/examples/shared-name-pruned/src/alpha/Tile.js +11 -0
  85. package/dist/test/examples/shared-name-pruned/src/alpha/Tile.js.map +1 -0
  86. package/dist/test/examples/shared-name-pruned/src/beta/Limits.d.ts +3 -0
  87. package/dist/test/examples/shared-name-pruned/src/beta/Limits.js +6 -0
  88. package/dist/test/examples/shared-name-pruned/src/beta/Limits.js.map +1 -0
  89. package/dist/test/examples/shared-name-pruned/src/beta/Shape.d.ts +3 -0
  90. package/dist/test/examples/shared-name-pruned/src/beta/Shape.js +3 -0
  91. package/dist/test/examples/shared-name-pruned/src/beta/Shape.js.map +1 -0
  92. package/dist/test/examples/source-repository/a/generated/index.js +28 -28
  93. package/dist/test/examples/source-repository/a/generated/index.js.map +1 -1
  94. package/dist/test/examples/source-repository/a/src/ExtendsForeignType.d.ts +1 -1
  95. package/dist/test/examples/source-repository/a/src/ExtendsForeignType.js +5 -5
  96. package/dist/test/examples/source-repository/a/src/ExtendsForeignType.js.map +1 -1
  97. package/dist/test/examples/source-repository/b/generated/index.js +3 -3
  98. package/dist/test/examples/source-repository/b/generated/index.js.map +1 -1
  99. package/package.json +5 -5
  100. package/src/ReflectionDoctor.ts +37 -17
  101. package/src/codegen/CanonicalSourceGraph.ts +50 -0
  102. package/src/codegen/writeGeneratedIndex.ts +35 -9
  103. package/src/parser/PackageSourceFiles.ts +66 -0
  104. package/src/parser/SharedQualifiedNames.ts +76 -0
  105. package/src/parser/createGraphBuilder.ts +25 -10
  106. package/src/parser/createSourceGraph.ts +14 -16
  107. package/test/Class.test.ts +1 -1
  108. package/test/ExamplePackageFixture.ts +142 -0
  109. package/test/GraphEmitRelativePaths.test.ts +1 -1
  110. package/test/PackageSourceFiles.test.ts +42 -0
  111. package/test/ReflectionDoctor.test.ts +50 -12
  112. package/test/ReproducibleGeneratedIndex.test.ts +114 -0
  113. package/test/SharedQualifiedNames.test.ts +89 -0
  114. package/test/examples/reproducible/index.ts +4 -0
  115. package/test/examples/reproducible/src/alpha/Alpha.ts +13 -0
  116. package/test/examples/reproducible/src/alpha/Defaults.ts +4 -0
  117. package/test/examples/reproducible/src/beta/Beta.ts +7 -0
  118. package/test/examples/reproducible/src/beta/deep/Gamma.ts +5 -0
  119. package/test/examples/reproducible/src/zeta/Defaults.ts +4 -0
  120. package/test/examples/reproducible/src/zeta/Zeta.ts +9 -0
  121. package/test/examples/shared-name/index.ts +1 -0
  122. package/test/examples/shared-name/src/alpha/Widget.ts +6 -0
  123. package/test/examples/shared-name/src/beta/Widget.ts +5 -0
  124. package/test/examples/shared-name/src/gamma/Gear.ts +9 -0
  125. package/test/examples/shared-name-pruned/index.ts +1 -0
  126. package/test/examples/shared-name-pruned/src/alpha/Limits.ts +2 -0
  127. package/test/examples/shared-name-pruned/src/alpha/Shape.ts +3 -0
  128. package/test/examples/shared-name-pruned/src/alpha/Tile.ts +9 -0
  129. package/test/examples/shared-name-pruned/src/beta/Limits.ts +2 -0
  130. package/test/examples/shared-name-pruned/src/beta/Shape.ts +4 -0
  131. package/test/examples/source-repository/a/dist/codegen/CanonicalSourceGraph.d.ts +22 -0
  132. package/test/examples/source-repository/a/dist/codegen/CanonicalSourceGraph.js +60 -0
  133. package/test/examples/source-repository/a/dist/codegen/CanonicalSourceGraph.js.map +1 -0
  134. package/test/examples/source-repository/a/dist/codegen/writeGeneratedIndex.d.ts +12 -3
  135. package/test/examples/source-repository/a/dist/codegen/writeGeneratedIndex.js +21 -13
  136. package/test/examples/source-repository/a/dist/codegen/writeGeneratedIndex.js.map +1 -1
  137. package/test/examples/source-repository/a/dist/generated/index.js +28 -28
  138. package/test/examples/source-repository/a/dist/generated/index.js.map +1 -1
  139. package/test/examples/source-repository/a/dist/parser/PackageSourceFiles.d.ts +30 -0
  140. package/test/examples/source-repository/a/dist/parser/PackageSourceFiles.js +151 -0
  141. package/test/examples/source-repository/a/dist/parser/PackageSourceFiles.js.map +1 -0
  142. package/test/examples/source-repository/a/dist/parser/SharedQualifiedNames.d.ts +43 -0
  143. package/test/examples/source-repository/a/dist/parser/SharedQualifiedNames.js +81 -0
  144. package/test/examples/source-repository/a/dist/parser/SharedQualifiedNames.js.map +1 -0
  145. package/test/examples/source-repository/a/dist/parser/createGraphBuilder.d.ts +6 -1
  146. package/test/examples/source-repository/a/dist/parser/createGraphBuilder.js +16 -31
  147. package/test/examples/source-repository/a/dist/parser/createGraphBuilder.js.map +1 -1
  148. package/test/examples/source-repository/a/dist/parser/createSourceGraph.d.ts +6 -1
  149. package/test/examples/source-repository/a/dist/parser/createSourceGraph.js +23 -38
  150. package/test/examples/source-repository/a/dist/parser/createSourceGraph.js.map +1 -1
  151. package/test/examples/source-repository/a/dist/src/ExtendsForeignType.d.ts +1 -1
  152. package/test/examples/source-repository/a/dist/src/ExtendsForeignType.js +5 -5
  153. package/test/examples/source-repository/a/dist/src/ExtendsForeignType.js.map +1 -1
  154. package/test/examples/source-repository/a/generated/index.ts +44 -44
  155. package/test/examples/source-repository/a/node_modules/.package-lock.json +13 -13
  156. package/test/examples/source-repository/a/node_modules/@proteinjs/util/CHANGELOG.md +8 -0
  157. package/test/examples/source-repository/a/node_modules/@proteinjs/util/LICENSE +21 -0
  158. package/test/examples/source-repository/a/node_modules/@proteinjs/util/package.json +3 -2
  159. package/test/examples/source-repository/a/node_modules/@proteinjs/util/tsconfig.json +1 -0
  160. package/test/examples/source-repository/a/node_modules/@proteinjs/util-node/CHANGELOG.md +63 -0
  161. package/test/examples/source-repository/a/node_modules/@proteinjs/util-node/LICENSE +21 -0
  162. package/test/examples/source-repository/a/node_modules/@proteinjs/util-node/dist/index.d.ts +1 -0
  163. package/test/examples/source-repository/a/node_modules/@proteinjs/util-node/dist/index.js +1 -0
  164. package/test/examples/source-repository/a/node_modules/@proteinjs/util-node/dist/index.js.map +1 -1
  165. package/test/examples/source-repository/a/node_modules/@proteinjs/util-node/dist/src/ArgsMap.js +7 -4
  166. package/test/examples/source-repository/a/node_modules/@proteinjs/util-node/dist/src/ArgsMap.js.map +1 -1
  167. package/test/examples/source-repository/a/node_modules/@proteinjs/util-node/dist/src/Fs.js +16 -12
  168. package/test/examples/source-repository/a/node_modules/@proteinjs/util-node/dist/src/Fs.js.map +1 -1
  169. package/test/examples/source-repository/a/node_modules/@proteinjs/util-node/dist/src/PackageUtil.d.ts +59 -13
  170. package/test/examples/source-repository/a/node_modules/@proteinjs/util-node/dist/src/PackageUtil.js +119 -80
  171. package/test/examples/source-repository/a/node_modules/@proteinjs/util-node/dist/src/PackageUtil.js.map +1 -1
  172. package/test/examples/source-repository/a/node_modules/@proteinjs/util-node/dist/src/WorkspaceDeclaration.d.ts +91 -0
  173. package/test/examples/source-repository/a/node_modules/@proteinjs/util-node/dist/src/WorkspaceDeclaration.js +608 -0
  174. package/test/examples/source-repository/a/node_modules/@proteinjs/util-node/dist/src/WorkspaceDeclaration.js.map +1 -0
  175. package/test/examples/source-repository/a/node_modules/@proteinjs/util-node/dist/src/promisifiedFs.d.ts +0 -1
  176. package/test/examples/source-repository/a/node_modules/@proteinjs/util-node/index.ts +1 -0
  177. package/test/examples/source-repository/a/node_modules/@proteinjs/util-node/package.json +4 -3
  178. package/test/examples/source-repository/a/node_modules/@proteinjs/util-node/src/ArgsMap.ts +7 -4
  179. package/test/examples/source-repository/a/node_modules/@proteinjs/util-node/src/Fs.ts +12 -9
  180. package/test/examples/source-repository/a/node_modules/@proteinjs/util-node/src/PackageUtil.ts +92 -38
  181. package/test/examples/source-repository/a/node_modules/@proteinjs/util-node/src/WorkspaceDeclaration.ts +340 -0
  182. package/test/examples/source-repository/a/node_modules/@proteinjs/util-node/test/ArgsMap.test.ts +35 -0
  183. package/test/examples/source-repository/a/node_modules/@proteinjs/util-node/test/Fs.getFilePathsMatchingGlob.test.ts +70 -0
  184. package/test/examples/source-repository/a/node_modules/@proteinjs/util-node/test/PackageUtil.getLocalPackageMap.test.ts +107 -0
  185. package/test/examples/source-repository/a/node_modules/@proteinjs/util-node/test/PackageUtil.pathKeyedIdentity.test.ts +169 -0
  186. package/test/examples/source-repository/a/node_modules/@proteinjs/util-node/test/WorkspaceDeclaration.test.ts +174 -0
  187. package/test/examples/source-repository/a/node_modules/@proteinjs/util-node/tsconfig.json +1 -0
  188. package/test/examples/source-repository/a/package-lock.json +16 -16
  189. package/test/examples/source-repository/a/package.json +3 -3
  190. package/test/examples/source-repository/a/src/ExtendsForeignType.ts +1 -1
  191. package/test/examples/source-repository/b/dist/codegen/CanonicalSourceGraph.d.ts +22 -0
  192. package/test/examples/source-repository/b/dist/codegen/CanonicalSourceGraph.js +60 -0
  193. package/test/examples/source-repository/b/dist/codegen/CanonicalSourceGraph.js.map +1 -0
  194. package/test/examples/source-repository/b/dist/codegen/writeGeneratedIndex.d.ts +12 -3
  195. package/test/examples/source-repository/b/dist/codegen/writeGeneratedIndex.js +21 -13
  196. package/test/examples/source-repository/b/dist/codegen/writeGeneratedIndex.js.map +1 -1
  197. package/test/examples/source-repository/b/dist/generated/index.js +3 -3
  198. package/test/examples/source-repository/b/dist/generated/index.js.map +1 -1
  199. package/test/examples/source-repository/b/dist/parser/PackageSourceFiles.d.ts +30 -0
  200. package/test/examples/source-repository/b/dist/parser/PackageSourceFiles.js +151 -0
  201. package/test/examples/source-repository/b/dist/parser/PackageSourceFiles.js.map +1 -0
  202. package/test/examples/source-repository/b/dist/parser/SharedQualifiedNames.d.ts +43 -0
  203. package/test/examples/source-repository/b/dist/parser/SharedQualifiedNames.js +81 -0
  204. package/test/examples/source-repository/b/dist/parser/SharedQualifiedNames.js.map +1 -0
  205. package/test/examples/source-repository/b/dist/parser/createGraphBuilder.d.ts +6 -1
  206. package/test/examples/source-repository/b/dist/parser/createGraphBuilder.js +16 -31
  207. package/test/examples/source-repository/b/dist/parser/createGraphBuilder.js.map +1 -1
  208. package/test/examples/source-repository/b/dist/parser/createSourceGraph.d.ts +6 -1
  209. package/test/examples/source-repository/b/dist/parser/createSourceGraph.js +23 -38
  210. package/test/examples/source-repository/b/dist/parser/createSourceGraph.js.map +1 -1
  211. package/test/examples/source-repository/b/generated/index.ts +6 -6
  212. package/test/examples/source-repository/b/node_modules/.package-lock.json +10 -10
  213. package/test/examples/source-repository/b/node_modules/@proteinjs/util/CHANGELOG.md +8 -0
  214. package/test/examples/source-repository/b/node_modules/@proteinjs/util/LICENSE +21 -0
  215. package/test/examples/source-repository/b/node_modules/@proteinjs/util/package.json +3 -2
  216. package/test/examples/source-repository/b/node_modules/@proteinjs/util/tsconfig.json +1 -0
  217. package/test/examples/source-repository/b/node_modules/@proteinjs/util-node/CHANGELOG.md +63 -0
  218. package/test/examples/source-repository/b/node_modules/@proteinjs/util-node/LICENSE +21 -0
  219. package/test/examples/source-repository/b/node_modules/@proteinjs/util-node/dist/index.d.ts +1 -0
  220. package/test/examples/source-repository/b/node_modules/@proteinjs/util-node/dist/index.js +1 -0
  221. package/test/examples/source-repository/b/node_modules/@proteinjs/util-node/dist/index.js.map +1 -1
  222. package/test/examples/source-repository/b/node_modules/@proteinjs/util-node/dist/src/ArgsMap.js +7 -4
  223. package/test/examples/source-repository/b/node_modules/@proteinjs/util-node/dist/src/ArgsMap.js.map +1 -1
  224. package/test/examples/source-repository/b/node_modules/@proteinjs/util-node/dist/src/Fs.js +16 -12
  225. package/test/examples/source-repository/b/node_modules/@proteinjs/util-node/dist/src/Fs.js.map +1 -1
  226. package/test/examples/source-repository/b/node_modules/@proteinjs/util-node/dist/src/PackageUtil.d.ts +59 -13
  227. package/test/examples/source-repository/b/node_modules/@proteinjs/util-node/dist/src/PackageUtil.js +119 -80
  228. package/test/examples/source-repository/b/node_modules/@proteinjs/util-node/dist/src/PackageUtil.js.map +1 -1
  229. package/test/examples/source-repository/b/node_modules/@proteinjs/util-node/dist/src/WorkspaceDeclaration.d.ts +91 -0
  230. package/test/examples/source-repository/b/node_modules/@proteinjs/util-node/dist/src/WorkspaceDeclaration.js +608 -0
  231. package/test/examples/source-repository/b/node_modules/@proteinjs/util-node/dist/src/WorkspaceDeclaration.js.map +1 -0
  232. package/test/examples/source-repository/b/node_modules/@proteinjs/util-node/dist/src/promisifiedFs.d.ts +0 -1
  233. package/test/examples/source-repository/b/node_modules/@proteinjs/util-node/index.ts +1 -0
  234. package/test/examples/source-repository/b/node_modules/@proteinjs/util-node/package.json +4 -3
  235. package/test/examples/source-repository/b/node_modules/@proteinjs/util-node/src/ArgsMap.ts +7 -4
  236. package/test/examples/source-repository/b/node_modules/@proteinjs/util-node/src/Fs.ts +12 -9
  237. package/test/examples/source-repository/b/node_modules/@proteinjs/util-node/src/PackageUtil.ts +92 -38
  238. package/test/examples/source-repository/b/node_modules/@proteinjs/util-node/src/WorkspaceDeclaration.ts +340 -0
  239. package/test/examples/source-repository/b/node_modules/@proteinjs/util-node/test/ArgsMap.test.ts +35 -0
  240. package/test/examples/source-repository/b/node_modules/@proteinjs/util-node/test/Fs.getFilePathsMatchingGlob.test.ts +70 -0
  241. package/test/examples/source-repository/b/node_modules/@proteinjs/util-node/test/PackageUtil.getLocalPackageMap.test.ts +107 -0
  242. package/test/examples/source-repository/b/node_modules/@proteinjs/util-node/test/PackageUtil.pathKeyedIdentity.test.ts +169 -0
  243. package/test/examples/source-repository/b/node_modules/@proteinjs/util-node/test/WorkspaceDeclaration.test.ts +174 -0
  244. package/test/examples/source-repository/b/node_modules/@proteinjs/util-node/tsconfig.json +1 -0
  245. package/test/examples/source-repository/b/package-lock.json +13 -13
  246. package/test/examples/source-repository/b/package.json +3 -3
@@ -0,0 +1,70 @@
1
+ import * as fs from 'fs/promises';
2
+ import * as os from 'os';
3
+ import * as path from 'path';
4
+ import { Fs } from '../src/Fs';
5
+
6
+ /**
7
+ * Regression tests for the `Fs` glob helpers: the ignore patterns must PRUNE excluded
8
+ * directories, not merely filter them out of the results.
9
+ *
10
+ * `Fs` used to fold the base directory into one absolute pattern (`<dir>/<glob>`) and hand
11
+ * fast-glob the ignore list, which fast-glob then matched against the ABSOLUTE entry path. A
12
+ * globstar does not cross a dot-segment, so whenever the base path itself contained a
13
+ * dot-directory (`~/.n3xa/workspaces/<name>` — the default local workspaces root — or a
14
+ * `.scratch` estate) the ignore list matched nothing: the walk entered every node_modules/dist
15
+ * and followed the workspace symlinks inside them. On a materialized metarepo (70+
16
+ * node_modules trees) that is millions of entries held in fast-glob's unique-index, and the
17
+ * dev-skill Glob of every package.json exhausted a 12G heap three times running (2026-09-02).
18
+ * Matching relative to `cwd` keeps the ignore list and the entry paths in the same frame, so
19
+ * the prune holds under any base path.
20
+ *
21
+ * The oracle: an unreadable directory inside node_modules, under a dot-directory base path.
22
+ * A walk that enters node_modules hits EACCES, which fast-glob treats as fatal, and the call
23
+ * rejects; a pruned walk never sees it and resolves.
24
+ */
25
+ describe('Fs glob helpers prune ignored directories under a dot-directory base path', () => {
26
+ const IGNORE = ['**/node_modules/**', '**/dist/**'];
27
+ let root: string;
28
+ let locked: string;
29
+
30
+ beforeEach(async () => {
31
+ root = await fs.mkdtemp(path.join(os.tmpdir(), '.fs-glob-prune-'));
32
+ locked = path.join(root, 'node_modules', 'dep', 'locked');
33
+ await fs.mkdir(locked, { recursive: true });
34
+ await fs.mkdir(path.join(root, 'packages', 'pkg', 'src'), { recursive: true });
35
+ await fs.mkdir(path.join(root, 'dist'), { recursive: true });
36
+ await fs.writeFile(path.join(root, 'package.json'), '{}');
37
+ await fs.writeFile(path.join(root, 'packages', 'pkg', 'package.json'), '{}');
38
+ await fs.writeFile(path.join(root, 'packages', 'pkg', 'src', 'b.ts'), '');
39
+ await fs.writeFile(path.join(root, 'node_modules', 'dep', 'package.json'), '{}');
40
+ await fs.writeFile(path.join(root, 'dist', 'out.js'), '');
41
+ await fs.chmod(locked, 0o000);
42
+ });
43
+
44
+ afterEach(async () => {
45
+ await fs.chmod(locked, 0o755).catch(() => undefined);
46
+ await fs.rm(root, { recursive: true, force: true });
47
+ });
48
+
49
+ const relative = (paths: string[]) => paths.map((p) => path.relative(root, p)).sort();
50
+
51
+ // chmod 000 does not stop root; the oracle is meaningless there.
52
+ const asRoot = typeof process.getuid === 'function' && process.getuid() === 0;
53
+ const it = asRoot ? test.skip : test;
54
+
55
+ it('getFilePathsMatchingGlob never enters node_modules, returns absolute paths', async () => {
56
+ const out = await Fs.getFilePathsMatchingGlob(root, '**/package.json', IGNORE);
57
+ expect(out.every((p) => path.isAbsolute(p))).toBe(true);
58
+ expect(relative(out)).toEqual(['package.json', 'packages/pkg/package.json']);
59
+ });
60
+
61
+ it('getFilePathsMatchingGlob: a trailing separator on the directory changes nothing', async () => {
62
+ const out = await Fs.getFilePathsMatchingGlob(root + path.sep, '**/package.json', IGNORE);
63
+ expect(relative(out)).toEqual(['package.json', 'packages/pkg/package.json']);
64
+ });
65
+
66
+ it('getFilePaths walks the whole tree except the ignored directories', async () => {
67
+ const out = await Fs.getFilePaths(root, IGNORE);
68
+ expect(relative(out)).toEqual(['package.json', 'packages/pkg/package.json', 'packages/pkg/src/b.ts']);
69
+ });
70
+ });
@@ -0,0 +1,107 @@
1
+ import * as path from 'path';
2
+ import * as fs from 'fs/promises';
3
+ import * as os from 'os';
4
+ import { PackageUtil } from '../src/PackageUtil';
5
+
6
+ /**
7
+ * Regression tests for PackageUtil.getLocalPackageMap — workspace package discovery.
8
+ *
9
+ * The important property: discovery is a BOUNDED walk. It must not descend into
10
+ * hidden (dot-prefixed) directories, `node_modules`, or `dist`, and must never
11
+ * follow symlinks. The prior glob-based implementation violated all three at
12
+ * once in one spot: micromatch's default `dot: false` makes a leading `**` in
13
+ * an ignore pattern refuse to cross dot-segments, so beneath a hidden directory
14
+ * (e.g. a `.scratch/` full of throwaway checkouts) the
15
+ * `['**\/node_modules/**', '**\/dist/**']` ignores matched nothing while the
16
+ * walker still descended and followed symlinks — an unbounded traversal that
17
+ * OOMed every workspace command regardless of heap size.
18
+ */
19
+ describe('PackageUtil.getLocalPackageMap — bounded discovery', () => {
20
+ let workspaceRoot: string;
21
+
22
+ beforeEach(async () => {
23
+ workspaceRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'pkg-discovery-'));
24
+ });
25
+
26
+ afterEach(async () => {
27
+ if (workspaceRoot) {
28
+ await fs.rm(workspaceRoot, { recursive: true, force: true }).catch(() => {
29
+ /* ignore */
30
+ });
31
+ }
32
+ });
33
+
34
+ const writePackageJson = async (relDir: string, name: string) => {
35
+ const dir = path.join(workspaceRoot, relDir);
36
+ await fs.mkdir(dir, { recursive: true });
37
+ await fs.writeFile(path.join(dir, 'package.json'), JSON.stringify({ name, version: '1.0.0' }, null, 2));
38
+ };
39
+
40
+ /**
41
+ * The incident signature, compressed: a hidden directory containing a
42
+ * checkout whose `node_modules` holds symlinks back to the checkout root.
43
+ * The old glob walker descended into `.scratch` (hidden dirs were not
44
+ * pruned), its node_modules ignore was dead under the dot-segment
45
+ * (micromatch `dot: false`), and it followed the symlinks — several cycle
46
+ * links give the expansion a branching factor, so the path space grows
47
+ * combinatorially (`loopN/node_modules/loopM/...`) and the walk allocates
48
+ * unboundedly, exactly the OOM every workspace command hit. Bounded
49
+ * discovery never enters `.scratch` at all, so this returns instantly.
50
+ */
51
+ it('does not descend into hidden directories (unbounded-traversal incident repro)', async () => {
52
+ await writePackageJson('.', 'root');
53
+ await writePackageJson('packages/a', '@test/a');
54
+ await writePackageJson('.scratch/checkout/packages/b', '@test/b');
55
+ const checkoutDir = path.join(workspaceRoot, '.scratch', 'checkout');
56
+ await fs.mkdir(path.join(checkoutDir, 'node_modules'), { recursive: true });
57
+ for (let i = 0; i < 4; i++) {
58
+ await fs.symlink(checkoutDir, path.join(checkoutDir, 'node_modules', `loop${i}`));
59
+ }
60
+
61
+ const packageMap = await PackageUtil.getLocalPackageMap(workspaceRoot);
62
+
63
+ expect(Object.keys(packageMap).sort()).toEqual(['@test/a', 'root']);
64
+ }, 10000);
65
+
66
+ it('does not follow symlinked directories', async () => {
67
+ await writePackageJson('packages/a', '@test/a');
68
+ // A package OUTSIDE the workspace, reachable only via symlink — the old
69
+ // walker followed the link and admitted @test/outside as a member.
70
+ const outsideDir = await fs.mkdtemp(path.join(os.tmpdir(), 'pkg-outside-'));
71
+ try {
72
+ await fs.mkdir(path.join(outsideDir, 'c'), { recursive: true });
73
+ await fs.writeFile(
74
+ path.join(outsideDir, 'c', 'package.json'),
75
+ JSON.stringify({ name: '@test/outside', version: '1.0.0' })
76
+ );
77
+ await fs.symlink(path.join(outsideDir, 'c'), path.join(workspaceRoot, 'packages', 'linked'));
78
+
79
+ const packageMap = await PackageUtil.getLocalPackageMap(workspaceRoot);
80
+
81
+ expect(Object.keys(packageMap)).toEqual(['@test/a']);
82
+ } finally {
83
+ await fs.rm(outsideDir, { recursive: true, force: true });
84
+ }
85
+ });
86
+
87
+ it('does not descend into node_modules or dist', async () => {
88
+ await writePackageJson('packages/a', '@test/a');
89
+ await writePackageJson('packages/a/node_modules/dep', '@test/dep');
90
+ await writePackageJson('packages/a/dist', '@test/dist-artifact');
91
+
92
+ const packageMap = await PackageUtil.getLocalPackageMap(workspaceRoot);
93
+
94
+ expect(Object.keys(packageMap)).toEqual(['@test/a']);
95
+ });
96
+
97
+ it('survives a symlink cycle in the open (non-hidden) tree', async () => {
98
+ await writePackageJson('packages/a', '@test/a');
99
+ // Symlink from inside a package back to the workspace root — a cycle the
100
+ // old walker expanded path-by-path until the kernel refused to resolve it.
101
+ await fs.symlink(workspaceRoot, path.join(workspaceRoot, 'packages', 'a', 'back-to-root'));
102
+
103
+ const packageMap = await PackageUtil.getLocalPackageMap(workspaceRoot);
104
+
105
+ expect(Object.keys(packageMap)).toEqual(['@test/a']);
106
+ });
107
+ });
@@ -0,0 +1,169 @@
1
+ import * as path from 'path';
2
+ import * as fs from 'fs/promises';
3
+ import * as os from 'os';
4
+ import { PackageUtil } from '../src/PackageUtil';
5
+
6
+ /**
7
+ * Regression tests for path-keyed package identity (metarepo issue #75).
8
+ *
9
+ * Every workspace root in the metarepo is named `root` — the metarepo root, the app root, and
10
+ * each nested lerna root (proteinjs, util, build, ...). Package IDENTITY must therefore be the
11
+ * package.json path, never the name: a name-keyed discovery map silently drops all but one of
12
+ * the same-named packages, and closure computation seeded from a shared name-keyed graph node
13
+ * conflates their dependencies.
14
+ *
15
+ * Live failure this reproduces: the app root (named `root`, declaring a registry dep on
16
+ * @proteinjs/build) collided with the metarepo root in the name-keyed packageMap and was
17
+ * additionally skipped by symlink-workspace's `root` skip — so its node_modules kept a STALE
18
+ * REGISTRY copy of @proteinjs/build (carrying an old @proteinjs/util-node) indefinitely, and
19
+ * the dev-server OOM persisted after the workspace fix had shipped.
20
+ *
21
+ * Fixture shape (mirrors the metarepo): two packages BOTH named `root` with DIFFERENT deps,
22
+ * plus regular workspace packages. Each root's closure and symlinks must derive from its OWN
23
+ * declarations.
24
+ */
25
+ describe('PackageUtil — path-keyed package identity (same-named packages)', () => {
26
+ let workspaceRoot: string;
27
+
28
+ beforeEach(async () => {
29
+ workspaceRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'pkg-identity-'));
30
+ });
31
+
32
+ afterEach(async () => {
33
+ if (workspaceRoot) {
34
+ await fs.rm(workspaceRoot, { recursive: true, force: true }).catch(() => {
35
+ /* ignore */
36
+ });
37
+ }
38
+ });
39
+
40
+ /** Write a package.json and return its absolute file path. */
41
+ const writePackageJson = async (
42
+ relDir: string,
43
+ name: string,
44
+ deps: Record<string, string> = {},
45
+ devDeps: Record<string, string> = {}
46
+ ): Promise<string> => {
47
+ const dir = path.join(workspaceRoot, relDir);
48
+ await fs.mkdir(dir, { recursive: true });
49
+ const pkgPath = path.join(dir, 'package.json');
50
+ await fs.writeFile(
51
+ pkgPath,
52
+ JSON.stringify({ name, version: '1.0.0', dependencies: deps, devDependencies: devDeps }, null, 2)
53
+ );
54
+ return pkgPath;
55
+ };
56
+
57
+ /**
58
+ * The metarepo shape: metarepo root and app root are BOTH named `root`, with different
59
+ * workspace deps. `@test/build` itself depends on `@test/util-node` (the live transitive
60
+ * chain that carried the stale code).
61
+ */
62
+ const writeMetarepoFixture = async () => {
63
+ const metarepoRootPath = await writePackageJson('.', 'root', {}, { '@test/build': '^1.0.0' });
64
+ const appRootPath = await writePackageJson('packages/app', 'root', {}, { '@test/app-lib': '^1.0.0' });
65
+ const buildPath = await writePackageJson('packages/tools/build', '@test/build', {
66
+ '@test/util-node': '^1.0.0',
67
+ });
68
+ const utilNodePath = await writePackageJson('packages/tools/util-node', '@test/util-node');
69
+ const appLibPath = await writePackageJson('packages/app/packages/app-lib', '@test/app-lib');
70
+ return { metarepoRootPath, appRootPath, buildPath, utilNodePath, appLibPath };
71
+ };
72
+
73
+ test('discovery retains every same-named package, path-keyed', async () => {
74
+ const { metarepoRootPath, appRootPath } = await writeMetarepoFixture();
75
+
76
+ const packagePathMap = await PackageUtil.getLocalPackagePathMap(workspaceRoot);
77
+
78
+ // Both `root`-named packages are distinct entries under their own package.json paths.
79
+ expect(packagePathMap[metarepoRootPath]?.name).toBe('root');
80
+ expect(packagePathMap[appRootPath]?.name).toBe('root');
81
+ expect(Object.keys(packagePathMap)).toHaveLength(5);
82
+
83
+ // Workspace metadata carries the same collision-free map; the name-keyed index still
84
+ // resolves real (unique) dependency names.
85
+ const metadata = await PackageUtil.getWorkspaceMetadata(workspaceRoot);
86
+ expect(Object.keys(metadata.packagePathMap)).toHaveLength(5);
87
+ expect(metadata.packageMap['@test/build'].filePath).toBe(
88
+ path.join(workspaceRoot, 'packages/tools/build/package.json')
89
+ );
90
+ });
91
+
92
+ test('transitive closure derives from the given package instance, not a shared name-keyed node', async () => {
93
+ const { metarepoRootPath, appRootPath } = await writeMetarepoFixture();
94
+ const packageMap = await PackageUtil.getLocalPackageMap(workspaceRoot);
95
+
96
+ // Construct the LocalPackage records from disk truth (discovery may or may not retain
97
+ // both same-named packages in a name-keyed map — closure must be correct regardless of
98
+ // which instance we hand it).
99
+ const metarepoRoot = {
100
+ name: 'root',
101
+ filePath: metarepoRootPath,
102
+ packageJson: JSON.parse(await fs.readFile(metarepoRootPath, 'utf-8')),
103
+ };
104
+ const appRoot = {
105
+ name: 'root',
106
+ filePath: appRootPath,
107
+ packageJson: JSON.parse(await fs.readFile(appRootPath, 'utf-8')),
108
+ };
109
+
110
+ const metarepoClosure = await PackageUtil.getTransitiveWorkspaceDependencies(metarepoRoot, packageMap);
111
+ const appClosure = await PackageUtil.getTransitiveWorkspaceDependencies(appRoot, packageMap);
112
+
113
+ // The metarepo root declares @test/build (which pulls @test/util-node) — and nothing else.
114
+ expect(metarepoClosure.sort()).toEqual(['@test/build', '@test/util-node']);
115
+ // The app root declares @test/app-lib — and nothing else. A name-keyed traversal seeded
116
+ // from the shared `root` node returns the OTHER root's deps here.
117
+ expect(appClosure.sort()).toEqual(['@test/app-lib']);
118
+ });
119
+
120
+ test('symlinkDependencies links each same-named consumer to ITS OWN workspace closure', async () => {
121
+ const { metarepoRootPath, appRootPath } = await writeMetarepoFixture();
122
+ const packageMap = await PackageUtil.getLocalPackageMap(workspaceRoot);
123
+ const metarepoRoot = {
124
+ name: 'root',
125
+ filePath: metarepoRootPath,
126
+ packageJson: JSON.parse(await fs.readFile(metarepoRootPath, 'utf-8')),
127
+ };
128
+ const appRoot = {
129
+ name: 'root',
130
+ filePath: appRootPath,
131
+ packageJson: JSON.parse(await fs.readFile(appRootPath, 'utf-8')),
132
+ };
133
+
134
+ await PackageUtil.symlinkDependencies(metarepoRoot, packageMap);
135
+ await PackageUtil.symlinkDependencies(appRoot, packageMap);
136
+
137
+ // Metarepo root gets its own closure: build + util-node, NOT app-lib.
138
+ const metarepoNodeModules = path.join(workspaceRoot, 'node_modules');
139
+ await expectSymlinkTo(
140
+ path.join(metarepoNodeModules, '@test/build'),
141
+ path.join(workspaceRoot, 'packages/tools/build')
142
+ );
143
+ await expectSymlinkTo(
144
+ path.join(metarepoNodeModules, '@test/util-node'),
145
+ path.join(workspaceRoot, 'packages/tools/util-node')
146
+ );
147
+ await expectAbsent(path.join(metarepoNodeModules, '@test/app-lib'));
148
+
149
+ // App root gets ITS closure: app-lib only, NOT the metarepo root's build chain.
150
+ const appNodeModules = path.join(workspaceRoot, 'packages/app/node_modules');
151
+ await expectSymlinkTo(
152
+ path.join(appNodeModules, '@test/app-lib'),
153
+ path.join(workspaceRoot, 'packages/app/packages/app-lib')
154
+ );
155
+ await expectAbsent(path.join(appNodeModules, '@test/build'));
156
+ });
157
+
158
+ const expectSymlinkTo = async (linkPath: string, expectedTargetDir: string) => {
159
+ const stat = await fs.lstat(linkPath).catch(() => undefined);
160
+ expect(stat?.isSymbolicLink() ?? false).toBe(true);
161
+ const resolved = await fs.realpath(linkPath);
162
+ expect(resolved).toBe(await fs.realpath(expectedTargetDir));
163
+ };
164
+
165
+ const expectAbsent = async (linkPath: string) => {
166
+ const stat = await fs.lstat(linkPath).catch(() => undefined);
167
+ expect(stat).toBeUndefined();
168
+ };
169
+ });
@@ -0,0 +1,174 @@
1
+ import * as path from 'path';
2
+ import * as fs from 'fs/promises';
3
+ import * as os from 'os';
4
+ import { PackageUtil } from '../src/PackageUtil';
5
+ import { WorkspaceDeclaration, WorkspaceDeclarationError } from '../src/WorkspaceDeclaration';
6
+
7
+ /**
8
+ * Workspace membership comes from the DECLARATION, never from a crawl (n3xah/app Deploy to Test
9
+ * run 33747781291, 2026-09-03): the scanner crawled every package.json under the app and keyed
10
+ * them by name, so CI fixture trees whose package.json files reused the app's own package names
11
+ * (`@n3xa/app-common/-server/-ui` under scripts/ci/fixtures) shadowed the real packages — walk
12
+ * order put the fixtures last, they had no build script, and build-workspace reported "1 package
13
+ * in workspace" (ops/error-bridge), shipping an image with no packages/server/dist.
14
+ *
15
+ * The rules pinned here:
16
+ * - a directory holding lerna.json `packages` (or a root package.json `workspacePackages`) is a
17
+ * workspace ROOT; its members are exactly what it declares, and it OWNS its subtree — nothing
18
+ * undeclared beneath it is a member, however the tree is walked from above;
19
+ * - a declared literal path with no package.json is a hard error naming the declaration;
20
+ * - two leaf packages sharing a name is a hard error naming both paths (roots — the containers
21
+ * every metarepo names `root` — are identified by path and exempt);
22
+ * - an undeclared tree still crawls (bounded: no hidden dirs, node_modules, dist, symlinks), and
23
+ * hands every declared root it meets to that root's declaration.
24
+ */
25
+ describe('WorkspaceDeclaration — membership is declared, never crawled', () => {
26
+ let root: string;
27
+
28
+ beforeEach(async () => {
29
+ root = await fs.realpath(await fs.mkdtemp(path.join(os.tmpdir(), 'ws-decl-')));
30
+ });
31
+
32
+ afterEach(async () => {
33
+ await fs.rm(root, { recursive: true, force: true }).catch(() => undefined);
34
+ });
35
+
36
+ const writeJson = async (relPath: string, json: unknown) => {
37
+ const filePath = path.join(root, relPath);
38
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
39
+ await fs.writeFile(filePath, JSON.stringify(json, null, 2));
40
+ return filePath;
41
+ };
42
+ const writePackage = (relDir: string, name: string, extra: Record<string, unknown> = {}) =>
43
+ writeJson(path.join(relDir, 'package.json'), { name, version: '1.0.0', ...extra });
44
+ const buildable = { scripts: { build: 'tsc' } };
45
+ const rel = (filePaths: string[]) => filePaths.map((p) => path.relative(root, p)).sort();
46
+
47
+ it('resolves a declared package to ITS OWN package.json — a same-named fixture beneath the root is not a member (the 33747781291 shadowing repro)', async () => {
48
+ await writeJson('lerna.json', { packages: ['packages/common', 'packages/server'] });
49
+ const rootPkg = await writePackage('.', 'root');
50
+ const common = await writePackage('packages/common', '@t/common', buildable);
51
+ const server = await writePackage('packages/server', '@t/server', buildable);
52
+ // The impostor: same name, sorts AFTER the real one (walk order made it "win"), no build script.
53
+ await writePackage('scripts/ci/fixtures/floor-sync/repo/packages/server', '@t/server');
54
+ await writePackage('scripts/ci/fixtures/floor-sync/repo', 'root');
55
+
56
+ const { packageMap, packagePathMap, sortedPackageNames } = await PackageUtil.getWorkspaceMetadata(root);
57
+
58
+ expect(packageMap['@t/server'].filePath).toBe(server);
59
+ expect(packageMap['@t/server'].packageJson.scripts?.build).toBe('tsc');
60
+ expect(rel(Object.keys(packagePathMap))).toEqual(rel([rootPkg, common, server]));
61
+ expect(sortedPackageNames.filter((n) => n !== 'root').sort()).toEqual(['@t/common', '@t/server']);
62
+ });
63
+
64
+ it('expands lerna globs (packages/*, packages/**) and the root package.json `workspacePackages` extra roots', async () => {
65
+ await writeJson('lerna.json', { packages: ['packages/*'] });
66
+ const rootPkg = await writeJson('package.json', {
67
+ name: 'root',
68
+ version: '1.0.0',
69
+ [WorkspaceDeclaration.EXTRA_PACKAGES_FIELD]: ['ops/*'],
70
+ });
71
+ const a = await writePackage('packages/a', '@t/a', buildable);
72
+ const b = await writePackage('packages/b', '@t/b', buildable);
73
+ const bridge = await writePackage('ops/error-bridge', '@t/error-bridge', buildable);
74
+ await fs.mkdir(path.join(root, 'ops/notes'), { recursive: true }); // a glob match without package.json: skipped
75
+ await writePackage('packages/a/test/fixtures/x', '@t/a-fixture'); // not matched by packages/* — not a member
76
+
77
+ const { packagePathMap } = await PackageUtil.getWorkspaceMetadata(root);
78
+ expect(rel(Object.keys(packagePathMap))).toEqual(rel([rootPkg, a, b, bridge]));
79
+
80
+ // packages/** reaches nested dirs but still never enters hidden dirs, node_modules, dist, or symlinks.
81
+ await writeJson('lerna.json', { packages: ['packages/**'] });
82
+ const deep = await writePackage('packages/group/c', '@t/c', buildable);
83
+ await writePackage('packages/.hidden/h', '@t/hidden');
84
+ await writePackage('packages/a/node_modules/dep', '@t/dep');
85
+ await writePackage('packages/a/dist', '@t/dist-artifact');
86
+ const outside = await fs.mkdtemp(path.join(os.tmpdir(), 'ws-outside-'));
87
+ try {
88
+ await fs.mkdir(path.join(outside, 'o'));
89
+ await fs.writeFile(path.join(outside, 'o', 'package.json'), JSON.stringify({ name: '@t/outside' }));
90
+ await fs.symlink(path.join(outside, 'o'), path.join(root, 'packages', 'linked'));
91
+ const nested = await writePackage('packages/a/test/fixtures/x', '@t/a-fixture'); // packages/** DOES reach it
92
+ const { packagePathMap: deepMap } = await PackageUtil.getWorkspaceMetadata(root);
93
+ expect(rel(Object.keys(deepMap))).toEqual(rel([rootPkg, a, b, bridge, deep, nested]));
94
+ } finally {
95
+ await fs.rm(outside, { recursive: true, force: true });
96
+ }
97
+ });
98
+
99
+ it('a declared literal path with no package.json is a hard error naming the declaration and the path', async () => {
100
+ await writeJson('lerna.json', { packages: ['packages/common', 'packages/ui'] });
101
+ await writePackage('.', 'root');
102
+ await writePackage('packages/common', '@t/common', buildable);
103
+ await fs.mkdir(path.join(root, 'packages/ui'), { recursive: true }); // declared, but empty
104
+
105
+ await expect(PackageUtil.getWorkspaceMetadata(root)).rejects.toThrow(WorkspaceDeclarationError);
106
+ await expect(PackageUtil.getWorkspaceMetadata(root)).rejects.toThrow(
107
+ /lerna\.json declares "packages\/ui" but .*packages\/ui\/package\.json does not exist/
108
+ );
109
+ });
110
+
111
+ it('an undeclared tree crawls, and every declared root it meets owns its own subtree (the metarepo shape)', async () => {
112
+ // Metarepo root: no lerna.json, no workspacePackages — a crawl root, named `root` like every root.
113
+ const meta = await writePackage('.', 'root');
114
+ // packages/app is a declared root; its fixtures are NOT members even though the crawl passes above them.
115
+ await writeJson('packages/app/lerna.json', { packages: ['packages/common'] });
116
+ const app = await writePackage('packages/app', 'root');
117
+ const appCommon = await writePackage('packages/app/packages/common', '@t/app-common', buildable);
118
+ await writePackage('packages/app/scripts/ci/fixtures/f/packages/common', '@t/app-common');
119
+ await writePackage('packages/app/packages/undeclared', '@t/undeclared', buildable);
120
+ // A plain undeclared package elsewhere in the crawl stays a member (today's behavior for loose trees).
121
+ const loose = await writePackage('packages/loose', '@t/loose', buildable);
122
+
123
+ const { packageMap, packagePathMap } = await PackageUtil.getWorkspaceMetadata(root);
124
+ expect(rel(Object.keys(packagePathMap))).toEqual(rel([meta, app, appCommon, loose]));
125
+ expect(packageMap['@t/app-common'].filePath).toBe(appCommon);
126
+ expect(packageMap['@t/undeclared']).toBeUndefined();
127
+ });
128
+
129
+ it('two leaf packages sharing a name is a hard error naming both paths and which one is declared', async () => {
130
+ await writePackage('.', 'root');
131
+ await writeJson('packages/app/lerna.json', { packages: ['packages/common'] });
132
+ await writePackage('packages/app', 'root');
133
+ const declared = await writePackage('packages/app/packages/common', '@t/app-common', buildable);
134
+ const crawled = await writePackage('tools/impostor', '@t/app-common');
135
+
136
+ let error: unknown;
137
+ try {
138
+ await WorkspaceDeclaration.discover(root);
139
+ } catch (e) {
140
+ error = e;
141
+ }
142
+ expect(error).toBeInstanceOf(WorkspaceDeclarationError);
143
+ const message = String((error as Error).message);
144
+ expect(message).toContain('@t/app-common');
145
+ expect(message).toContain(declared);
146
+ expect(message).toContain(crawled);
147
+ expect(message).toMatch(/declared by .*packages\/app\/lerna\.json/);
148
+ expect(message).toMatch(/crawled/);
149
+
150
+ // Declared vs declared collides the same way.
151
+ await fs.rm(path.join(root, 'tools'), { recursive: true });
152
+ await writeJson('packages/other/lerna.json', { packages: ['packages/*'] });
153
+ await writePackage('packages/other', 'root');
154
+ const otherCommon = await writePackage('packages/other/packages/common', '@t/app-common', buildable);
155
+ await expect(WorkspaceDeclaration.discover(root)).rejects.toThrow(otherCommon);
156
+ });
157
+
158
+ it('containers named `root` at every level stay legal — roots are identified by path, not name', async () => {
159
+ const meta = await writePackage('.', 'root');
160
+ await writeJson('packages/app/lerna.json', { packages: ['packages/*'] });
161
+ const app = await writePackage('packages/app', 'root');
162
+ const common = await writePackage('packages/app/packages/common', '@t/common', buildable);
163
+ await writeJson('packages/lib/lerna.json', {}); // lerna's default packages/* when the field is absent
164
+ const lib = await writePackage('packages/lib', 'root');
165
+ const libA = await writePackage('packages/lib/packages/a', '@t/lib-a', buildable);
166
+
167
+ const discovered = await WorkspaceDeclaration.discover(root);
168
+ expect(rel(discovered.map((d) => d.filePath))).toEqual(rel([meta, app, common, lib, libA]));
169
+ const byPath = new Map(discovered.map((d) => [d.filePath, d]));
170
+ expect(byPath.get(common)?.origin).toBe('declared');
171
+ expect(byPath.get(common)?.declaredBy).toBe(path.join(root, 'packages/app/lerna.json'));
172
+ expect(byPath.get(meta)?.origin).toBe('crawled');
173
+ });
174
+ });
@@ -1,5 +1,6 @@
1
1
  {
2
2
  "compilerOptions": {
3
+ "noEmitOnError": true,
3
4
  "target": "es5",
4
5
  "module": "commonjs",
5
6
  "declaration": true,
@@ -10,10 +10,10 @@
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
12
  "@dagrejs/graphlib": "2.1.4",
13
- "@proteinjs/reflection": "^1.1.12",
13
+ "@proteinjs/reflection": "^1.2.0",
14
14
  "@proteinjs/reflection-build-test-b": "file:../b",
15
- "@proteinjs/util": "^1.6.1",
16
- "@proteinjs/util-node": "^1.10.1",
15
+ "@proteinjs/util": "^1.6.2",
16
+ "@proteinjs/util-node": "^1.11.0",
17
17
  "globby": "11.0.1",
18
18
  "gulp": "4.0.2",
19
19
  "gulp-shell": "0.8.0",
@@ -36,9 +36,9 @@
36
36
  "license": "ISC",
37
37
  "dependencies": {
38
38
  "@dagrejs/graphlib": "2.1.4",
39
- "@proteinjs/reflection": "^1.1.12",
40
- "@proteinjs/util": "^1.6.1",
41
- "@proteinjs/util-node": "^1.10.1",
39
+ "@proteinjs/reflection": "^1.2.0",
40
+ "@proteinjs/util": "^1.6.2",
41
+ "@proteinjs/util-node": "^1.11.0",
42
42
  "globby": "11.0.1",
43
43
  "gulp": "4.0.2",
44
44
  "gulp-shell": "0.8.0",
@@ -1104,9 +1104,9 @@
1104
1104
  }
1105
1105
  },
1106
1106
  "node_modules/@proteinjs/reflection": {
1107
- "version": "1.1.12",
1108
- "resolved": "https://registry.npmjs.org/@proteinjs/reflection/-/reflection-1.1.12.tgz",
1109
- "integrity": "sha512-w+sL7Nqppk4DXdFxrfwjEGHSPykCwB3GWaiQXcP6X+8Qtj32BI0wmTvAt6hXvtbXPhvIHPFnTfSs9qlW0FYf9Q==",
1107
+ "version": "1.2.0",
1108
+ "resolved": "https://registry.npmjs.org/@proteinjs/reflection/-/reflection-1.2.0.tgz",
1109
+ "integrity": "sha512-AsoipvjpRD0WA1VC4770D5NsN0BDdli9UipdzHHPxcA+BJYcxwPLPLA7h+AAXTNMNfpr/BbEFkxHC0oSCiTkNg==",
1110
1110
  "license": "ISC",
1111
1111
  "dependencies": {
1112
1112
  "@dagrejs/graphlib": "2.1.4",
@@ -1118,9 +1118,9 @@
1118
1118
  "link": true
1119
1119
  },
1120
1120
  "node_modules/@proteinjs/util": {
1121
- "version": "1.6.1",
1122
- "resolved": "https://registry.npmjs.org/@proteinjs/util/-/util-1.6.1.tgz",
1123
- "integrity": "sha512-bFy6++l1u6RtMMeZEDYvMQWg14SAG2gCgUrcPy9M0Ctl0ATVMdQbU3h4IUQOlxwW32mWoni0qDTt9BkmHwAQjg==",
1121
+ "version": "1.6.2",
1122
+ "resolved": "https://registry.npmjs.org/@proteinjs/util/-/util-1.6.2.tgz",
1123
+ "integrity": "sha512-ZY7rMImFNnGV7x/Bqkkav1/gy9IvRR823JcnRJ4SoWXSlA29kuPdaHfnBay6eTQ7+kjiGGtJ+tMWM6NvZW9rUw==",
1124
1124
  "license": "ISC",
1125
1125
  "dependencies": {
1126
1126
  "@dagrejs/graphlib": "2.1.4",
@@ -1128,13 +1128,13 @@
1128
1128
  }
1129
1129
  },
1130
1130
  "node_modules/@proteinjs/util-node": {
1131
- "version": "1.10.1",
1132
- "resolved": "https://registry.npmjs.org/@proteinjs/util-node/-/util-node-1.10.1.tgz",
1133
- "integrity": "sha512-950BkiO4ccf9Bbt0CpvPj5pUalNH9dV1zqr7Cw1Dh3tg9xny6R02p1QrKvPU7anoZcnSp2Us6lBv0rlutYoSWw==",
1131
+ "version": "1.11.0",
1132
+ "resolved": "https://registry.npmjs.org/@proteinjs/util-node/-/util-node-1.11.0.tgz",
1133
+ "integrity": "sha512-+G6Q2w/F5NqmB/3wpNlhj6anpFs503xeKGiec4Hh0+WAsN1JkZCMmEQsHrrzaLGide0b6LyY3Kdo1eycFWRwGw==",
1134
1134
  "license": "ISC",
1135
1135
  "dependencies": {
1136
1136
  "@dagrejs/graphlib": "2.1.4",
1137
- "@proteinjs/util": "^1.6.1",
1137
+ "@proteinjs/util": "^1.6.2",
1138
1138
  "fs-extra": "11.1.1",
1139
1139
  "globby": "11.0.1",
1140
1140
  "typescript": "5.2.2"
@@ -19,10 +19,10 @@
19
19
  },
20
20
  "dependencies": {
21
21
  "@dagrejs/graphlib": "2.1.4",
22
- "@proteinjs/reflection": "^1.1.12",
22
+ "@proteinjs/reflection": "^1.2.0",
23
23
  "@proteinjs/reflection-build-test-b": "file:../b",
24
- "@proteinjs/util": "^1.6.1",
25
- "@proteinjs/util-node": "^1.10.1",
24
+ "@proteinjs/util": "^1.6.2",
25
+ "@proteinjs/util-node": "^1.11.0",
26
26
  "globby": "11.0.1",
27
27
  "gulp": "4.0.2",
28
28
  "gulp-shell": "0.8.0",
@@ -57,7 +57,7 @@ export interface ExtendsNotLoadableForeignInterface extends NotLoadableForeignIn
57
57
  b: number;
58
58
  }
59
59
 
60
- export class ImplementsExtendsLocalNotLoadableInterface implements ExtendsNotLoadableForeignInterface {
60
+ export class ImplementsExtendsNotLoadableForeignInterface implements ExtendsNotLoadableForeignInterface {
61
61
  z = 1;
62
62
  b = 2;
63
63
  }
@@ -0,0 +1,22 @@
1
+ import { Graph } from '@dagrejs/graphlib';
2
+ /**
3
+ * The emitted form of a source graph: the same nodes, values and edges, in an order that is a
4
+ * function of the graph's content alone — never of the order the builder happened to visit the
5
+ * sources or insert placeholder parents. The generated index serializes nodes and edges in
6
+ * insertion order, so this order is the artifact's byte order (and the runtime's `objects()`
7
+ * order within one package).
8
+ *
9
+ * - Nodes by the declaring file's package-relative path, then by qualified name. A placeholder
10
+ * (a parent referenced here but declared elsewhere) has no file and sorts by name ahead of
11
+ * the declared nodes.
12
+ * - Edges by child, then parent (a source graph is never a multigraph: one edge per pair).
13
+ *
14
+ * Both keys are package-relative strings compared by code unit, so they are the same on every
15
+ * machine and in every checkout location.
16
+ */
17
+ export declare class CanonicalSourceGraph {
18
+ /** A copy of `graph` whose node and edge order is canonical. */
19
+ static of(graph: Graph): Graph;
20
+ private static nodeOrder;
21
+ private static edgeOrder;
22
+ }