@nifrajs/cli 4.0.2 → 5.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 (364) hide show
  1. package/README.md +10 -2
  2. package/dist/agent-files.d.ts +107 -0
  3. package/dist/agent-files.d.ts.map +1 -0
  4. package/dist/agent-files.js +162 -0
  5. package/dist/agent-files.js.map +1 -0
  6. package/dist/app-files.d.ts +6 -0
  7. package/dist/app-files.d.ts.map +1 -1
  8. package/dist/app-files.js +27 -2
  9. package/dist/app-files.js.map +1 -1
  10. package/dist/assure-hydration.d.ts.map +1 -1
  11. package/dist/assure-hydration.js +9 -2
  12. package/dist/assure-hydration.js.map +1 -1
  13. package/dist/assure.d.ts +12 -2
  14. package/dist/assure.d.ts.map +1 -1
  15. package/dist/assure.js +59 -8
  16. package/dist/assure.js.map +1 -1
  17. package/dist/capabilities-tool.d.ts.map +1 -1
  18. package/dist/capabilities-tool.js +36 -9
  19. package/dist/capabilities-tool.js.map +1 -1
  20. package/dist/check-diagnostics.d.ts +22 -2
  21. package/dist/check-diagnostics.d.ts.map +1 -1
  22. package/dist/check-diagnostics.js +54 -7
  23. package/dist/check-diagnostics.js.map +1 -1
  24. package/dist/check-scan.d.ts +83 -24
  25. package/dist/check-scan.d.ts.map +1 -1
  26. package/dist/check-scan.js +1516 -318
  27. package/dist/check-scan.js.map +1 -1
  28. package/dist/check.d.ts +2 -2
  29. package/dist/check.d.ts.map +1 -1
  30. package/dist/check.js +203 -70
  31. package/dist/check.js.map +1 -1
  32. package/dist/cli.d.ts +9 -0
  33. package/dist/cli.d.ts.map +1 -1
  34. package/dist/cli.js +215 -50
  35. package/dist/cli.js.map +1 -1
  36. package/dist/client-module.d.ts +20 -0
  37. package/dist/client-module.d.ts.map +1 -0
  38. package/dist/client-module.js +73 -0
  39. package/dist/client-module.js.map +1 -0
  40. package/dist/command-catalog.d.ts +24 -5
  41. package/dist/command-catalog.d.ts.map +1 -1
  42. package/dist/command-catalog.js +194 -45
  43. package/dist/command-catalog.js.map +1 -1
  44. package/dist/component-check.d.ts +13 -0
  45. package/dist/component-check.d.ts.map +1 -0
  46. package/dist/component-check.js +59 -0
  47. package/dist/component-check.js.map +1 -0
  48. package/dist/contract-proof.d.ts +1 -1
  49. package/dist/contract-proof.d.ts.map +1 -1
  50. package/dist/contract-proof.js +2 -24
  51. package/dist/contract-proof.js.map +1 -1
  52. package/dist/contracts.d.ts +19 -0
  53. package/dist/contracts.d.ts.map +1 -1
  54. package/dist/contracts.js +50 -3
  55. package/dist/contracts.js.map +1 -1
  56. package/dist/db-tool.d.ts.map +1 -1
  57. package/dist/db-tool.js +4 -3
  58. package/dist/db-tool.js.map +1 -1
  59. package/dist/dev-bun-config.d.ts +5 -1
  60. package/dist/dev-bun-config.d.ts.map +1 -1
  61. package/dist/dev-bun-config.js +44 -11
  62. package/dist/dev-bun-config.js.map +1 -1
  63. package/dist/dev-feed-tool.d.ts +18 -1
  64. package/dist/dev-feed-tool.d.ts.map +1 -1
  65. package/dist/dev-feed-tool.js +93 -0
  66. package/dist/dev-feed-tool.js.map +1 -1
  67. package/dist/diagnostics.d.ts +3 -0
  68. package/dist/diagnostics.d.ts.map +1 -1
  69. package/dist/diagnostics.js.map +1 -1
  70. package/dist/diff-tool.d.ts +9 -8
  71. package/dist/diff-tool.d.ts.map +1 -1
  72. package/dist/diff-tool.js +28 -24
  73. package/dist/diff-tool.js.map +1 -1
  74. package/dist/doctor.d.ts +13 -0
  75. package/dist/doctor.d.ts.map +1 -1
  76. package/dist/doctor.js +23 -15
  77. package/dist/doctor.js.map +1 -1
  78. package/dist/env-file.d.ts +6 -0
  79. package/dist/env-file.d.ts.map +1 -1
  80. package/dist/env-file.js +6 -0
  81. package/dist/env-file.js.map +1 -1
  82. package/dist/fix-recipes.d.ts +2 -0
  83. package/dist/fix-recipes.d.ts.map +1 -1
  84. package/dist/fix-recipes.js +102 -2
  85. package/dist/fix-recipes.js.map +1 -1
  86. package/dist/hand-built-manifest.d.ts +27 -0
  87. package/dist/hand-built-manifest.d.ts.map +1 -0
  88. package/dist/hand-built-manifest.js +124 -0
  89. package/dist/hand-built-manifest.js.map +1 -0
  90. package/dist/hooks-report.d.ts +23 -0
  91. package/dist/hooks-report.d.ts.map +1 -0
  92. package/dist/hooks-report.js +104 -0
  93. package/dist/hooks-report.js.map +1 -0
  94. package/dist/init-agents.d.ts +18 -2
  95. package/dist/init-agents.d.ts.map +1 -1
  96. package/dist/init-agents.js +20 -13
  97. package/dist/init-agents.js.map +1 -1
  98. package/dist/internal/config-paths.d.ts +16 -0
  99. package/dist/internal/config-paths.d.ts.map +1 -0
  100. package/dist/internal/config-paths.js +124 -0
  101. package/dist/internal/config-paths.js.map +1 -0
  102. package/dist/internal/installed-package.d.ts +4 -0
  103. package/dist/internal/installed-package.d.ts.map +1 -0
  104. package/dist/internal/installed-package.js +17 -0
  105. package/dist/internal/installed-package.js.map +1 -0
  106. package/dist/internal/nifra-project.d.ts +41 -0
  107. package/dist/internal/nifra-project.d.ts.map +1 -0
  108. package/dist/internal/nifra-project.js +183 -0
  109. package/dist/internal/nifra-project.js.map +1 -0
  110. package/dist/internal/project-specifiers.d.ts +29 -0
  111. package/dist/internal/project-specifiers.d.ts.map +1 -0
  112. package/dist/internal/project-specifiers.js +435 -0
  113. package/dist/internal/project-specifiers.js.map +1 -0
  114. package/dist/internal/script-kind.d.ts.map +1 -1
  115. package/dist/internal/script-kind.js +2 -1
  116. package/dist/internal/script-kind.js.map +1 -1
  117. package/dist/internal/source-facts.d.ts +8 -1
  118. package/dist/internal/source-facts.d.ts.map +1 -1
  119. package/dist/internal/source-facts.js +22 -5
  120. package/dist/internal/source-facts.js.map +1 -1
  121. package/dist/internal/typescript-import.d.ts +1 -1
  122. package/dist/internal/typescript-import.d.ts.map +1 -1
  123. package/dist/internal/typescript-import.js +10 -6
  124. package/dist/internal/typescript-import.js.map +1 -1
  125. package/dist/introspect.d.ts +15 -6
  126. package/dist/introspect.d.ts.map +1 -1
  127. package/dist/introspect.js +87 -25
  128. package/dist/introspect.js.map +1 -1
  129. package/dist/levels-tool.d.ts.map +1 -1
  130. package/dist/levels-tool.js +98 -23
  131. package/dist/levels-tool.js.map +1 -1
  132. package/dist/load.d.ts +33 -10
  133. package/dist/load.d.ts.map +1 -1
  134. package/dist/load.js +60 -10
  135. package/dist/load.js.map +1 -1
  136. package/dist/mcp-context.d.ts +5 -5
  137. package/dist/mcp-context.d.ts.map +1 -1
  138. package/dist/mcp-context.js +8 -23
  139. package/dist/mcp-context.js.map +1 -1
  140. package/dist/mcp-delegate.d.ts +2 -2
  141. package/dist/mcp-delegate.d.ts.map +1 -1
  142. package/dist/mcp-delegate.js +19 -7
  143. package/dist/mcp-delegate.js.map +1 -1
  144. package/dist/mcp-exec.d.ts +13 -4
  145. package/dist/mcp-exec.d.ts.map +1 -1
  146. package/dist/mcp-exec.js +56 -14
  147. package/dist/mcp-exec.js.map +1 -1
  148. package/dist/mcp-http.js +1 -1
  149. package/dist/mcp-io.d.ts +30 -0
  150. package/dist/mcp-io.d.ts.map +1 -1
  151. package/dist/mcp-io.js +63 -0
  152. package/dist/mcp-io.js.map +1 -1
  153. package/dist/mcp-isolate.d.ts.map +1 -1
  154. package/dist/mcp-isolate.js +3 -2
  155. package/dist/mcp-isolate.js.map +1 -1
  156. package/dist/mcp-path.d.ts +2 -1
  157. package/dist/mcp-path.d.ts.map +1 -1
  158. package/dist/mcp-path.js +9 -24
  159. package/dist/mcp-path.js.map +1 -1
  160. package/dist/mcp-project-child.js +1 -1
  161. package/dist/mcp-project-child.js.map +1 -1
  162. package/dist/mcp-root.d.ts.map +1 -1
  163. package/dist/mcp-root.js +50 -10
  164. package/dist/mcp-root.js.map +1 -1
  165. package/dist/mcp-ws.d.ts.map +1 -1
  166. package/dist/mcp-ws.js +3 -1
  167. package/dist/mcp-ws.js.map +1 -1
  168. package/dist/mcp.d.ts.map +1 -1
  169. package/dist/mcp.js +48 -19
  170. package/dist/mcp.js.map +1 -1
  171. package/dist/migrate-layout.d.ts +47 -1
  172. package/dist/migrate-layout.d.ts.map +1 -1
  173. package/dist/migrate-layout.js +1972 -170
  174. package/dist/migrate-layout.js.map +1 -1
  175. package/dist/output-schemas.d.ts +128 -0
  176. package/dist/output-schemas.d.ts.map +1 -0
  177. package/dist/output-schemas.js +975 -0
  178. package/dist/output-schemas.js.map +1 -0
  179. package/dist/port.d.ts.map +1 -1
  180. package/dist/port.js +3 -2
  181. package/dist/port.js.map +1 -1
  182. package/dist/project-facts.d.ts +15 -4
  183. package/dist/project-facts.d.ts.map +1 -1
  184. package/dist/project-facts.js +1 -0
  185. package/dist/project-facts.js.map +1 -1
  186. package/dist/project-path.d.ts +6 -0
  187. package/dist/project-path.d.ts.map +1 -1
  188. package/dist/project-path.js +48 -14
  189. package/dist/project-path.js.map +1 -1
  190. package/dist/recipes/5.0.0.d.ts +11 -0
  191. package/dist/recipes/5.0.0.d.ts.map +1 -0
  192. package/dist/recipes/5.0.0.js +34 -0
  193. package/dist/recipes/5.0.0.js.map +1 -0
  194. package/dist/recipes/index.d.ts +7 -0
  195. package/dist/recipes/index.d.ts.map +1 -1
  196. package/dist/recipes/index.js +2 -0
  197. package/dist/recipes/index.js.map +1 -1
  198. package/dist/release-verification.d.ts +5 -0
  199. package/dist/release-verification.d.ts.map +1 -1
  200. package/dist/release-verification.js +14 -0
  201. package/dist/release-verification.js.map +1 -1
  202. package/dist/replay.d.ts.map +1 -1
  203. package/dist/replay.js +12 -3
  204. package/dist/replay.js.map +1 -1
  205. package/dist/review.d.ts +3 -1
  206. package/dist/review.d.ts.map +1 -1
  207. package/dist/review.js +43 -7
  208. package/dist/review.js.map +1 -1
  209. package/dist/rules/codes.d.ts +12 -0
  210. package/dist/rules/codes.d.ts.map +1 -1
  211. package/dist/rules/codes.js +12 -0
  212. package/dist/rules/codes.js.map +1 -1
  213. package/dist/rules/comment-markers.d.ts +16 -7
  214. package/dist/rules/comment-markers.d.ts.map +1 -1
  215. package/dist/rules/comment-markers.js +58 -19
  216. package/dist/rules/comment-markers.js.map +1 -1
  217. package/dist/rules/contract-visibility.d.ts +3 -0
  218. package/dist/rules/contract-visibility.d.ts.map +1 -0
  219. package/dist/rules/contract-visibility.js +337 -0
  220. package/dist/rules/contract-visibility.js.map +1 -0
  221. package/dist/rules/data-guard.d.ts +13 -0
  222. package/dist/rules/data-guard.d.ts.map +1 -1
  223. package/dist/rules/data-guard.js +356 -37
  224. package/dist/rules/data-guard.js.map +1 -1
  225. package/dist/rules/layout.d.ts +4 -0
  226. package/dist/rules/layout.d.ts.map +1 -0
  227. package/dist/rules/layout.js +19 -0
  228. package/dist/rules/layout.js.map +1 -0
  229. package/dist/rules/legacy.d.ts.map +1 -1
  230. package/dist/rules/legacy.js +178 -44
  231. package/dist/rules/legacy.js.map +1 -1
  232. package/dist/rules/pages.d.ts +50 -0
  233. package/dist/rules/pages.d.ts.map +1 -1
  234. package/dist/rules/pages.js +436 -4
  235. package/dist/rules/pages.js.map +1 -1
  236. package/dist/rules/routes.d.ts +6 -0
  237. package/dist/rules/routes.d.ts.map +1 -1
  238. package/dist/rules/routes.js +111 -16
  239. package/dist/rules/routes.js.map +1 -1
  240. package/dist/rules/secrets.js +2 -2
  241. package/dist/rules/secrets.js.map +1 -1
  242. package/dist/rules/security.d.ts +13 -1
  243. package/dist/rules/security.d.ts.map +1 -1
  244. package/dist/rules/security.js +313 -69
  245. package/dist/rules/security.js.map +1 -1
  246. package/dist/rules/zones.d.ts +13 -0
  247. package/dist/rules/zones.d.ts.map +1 -1
  248. package/dist/rules/zones.js +218 -44
  249. package/dist/rules/zones.js.map +1 -1
  250. package/dist/scaffold.d.ts.map +1 -1
  251. package/dist/scaffold.js +6 -4
  252. package/dist/scaffold.js.map +1 -1
  253. package/dist/sdk.d.ts.map +1 -1
  254. package/dist/sdk.js +14 -5
  255. package/dist/sdk.js.map +1 -1
  256. package/dist/stylex-migrate.d.ts.map +1 -1
  257. package/dist/stylex-migrate.js +290 -24
  258. package/dist/stylex-migrate.js.map +1 -1
  259. package/dist/test-tool.d.ts.map +1 -1
  260. package/dist/test-tool.js +6 -3
  261. package/dist/test-tool.js.map +1 -1
  262. package/dist/typecheck-coverage.d.ts +20 -0
  263. package/dist/typecheck-coverage.d.ts.map +1 -0
  264. package/dist/typecheck-coverage.js +129 -0
  265. package/dist/typecheck-coverage.js.map +1 -0
  266. package/dist/upgrade.d.ts +84 -0
  267. package/dist/upgrade.d.ts.map +1 -1
  268. package/dist/upgrade.js +498 -78
  269. package/dist/upgrade.js.map +1 -1
  270. package/dist/verification-plan.d.ts.map +1 -1
  271. package/dist/verification-plan.js +16 -2
  272. package/dist/verification-plan.js.map +1 -1
  273. package/dist/version-drift.d.ts +22 -0
  274. package/dist/version-drift.d.ts.map +1 -0
  275. package/dist/version-drift.js +94 -0
  276. package/dist/version-drift.js.map +1 -0
  277. package/dist/web-app-options.d.ts +1 -1
  278. package/dist/web-app-options.d.ts.map +1 -1
  279. package/dist/web-app-options.js +1 -1
  280. package/dist/web-app-options.js.map +1 -1
  281. package/dist/work-graph.d.ts.map +1 -1
  282. package/dist/work-graph.js +20 -8
  283. package/dist/work-graph.js.map +1 -1
  284. package/docs/examples.json +55 -7
  285. package/docs/llms-full.txt +2043 -222
  286. package/docs/types.json +1573 -246
  287. package/package.json +11 -12
  288. package/src/agent-files.ts +203 -0
  289. package/src/app-files.ts +34 -4
  290. package/src/assure-hydration.ts +18 -2
  291. package/src/assure.ts +88 -10
  292. package/src/capabilities-tool.ts +47 -9
  293. package/src/check-diagnostics.ts +75 -9
  294. package/src/check-scan.ts +1816 -377
  295. package/src/check.ts +258 -81
  296. package/src/cli.ts +242 -37
  297. package/src/client-module.ts +97 -0
  298. package/src/command-catalog.ts +237 -52
  299. package/src/component-check.ts +79 -0
  300. package/src/contract-proof.ts +3 -25
  301. package/src/contracts.ts +60 -3
  302. package/src/db-tool.ts +5 -2
  303. package/src/dev-bun-config.ts +51 -10
  304. package/src/dev-feed-tool.ts +112 -1
  305. package/src/diagnostics.ts +3 -0
  306. package/src/diff-tool.ts +33 -26
  307. package/src/doctor.ts +43 -21
  308. package/src/env-file.ts +7 -0
  309. package/src/fix-recipes.ts +116 -2
  310. package/src/hand-built-manifest.ts +144 -0
  311. package/src/hooks-report.ts +135 -0
  312. package/src/init-agents.ts +21 -14
  313. package/src/internal/config-paths.ts +154 -0
  314. package/src/internal/installed-package.ts +15 -0
  315. package/src/internal/nifra-project.ts +191 -0
  316. package/src/internal/project-specifiers.ts +477 -0
  317. package/src/internal/script-kind.ts +2 -1
  318. package/src/internal/source-facts.ts +48 -5
  319. package/src/internal/typescript-import.ts +15 -6
  320. package/src/introspect.ts +104 -28
  321. package/src/levels-tool.ts +124 -29
  322. package/src/load.ts +107 -17
  323. package/src/mcp-context.ts +12 -25
  324. package/src/mcp-delegate.ts +22 -10
  325. package/src/mcp-exec.ts +68 -18
  326. package/src/mcp-http.ts +1 -1
  327. package/src/mcp-io.ts +74 -0
  328. package/src/mcp-isolate.ts +9 -2
  329. package/src/mcp-path.ts +8 -22
  330. package/src/mcp-project-child.ts +1 -1
  331. package/src/mcp-root.ts +58 -8
  332. package/src/mcp-ws.ts +4 -2
  333. package/src/mcp.ts +56 -22
  334. package/src/migrate-layout.ts +2257 -176
  335. package/src/output-schemas.ts +1240 -0
  336. package/src/port.ts +3 -2
  337. package/src/project-facts.ts +17 -3
  338. package/src/project-path.ts +44 -13
  339. package/src/recipes/5.0.0.ts +35 -0
  340. package/src/recipes/index.ts +10 -0
  341. package/src/release-verification.ts +20 -0
  342. package/src/replay.ts +14 -3
  343. package/src/review.ts +44 -10
  344. package/src/rules/codes.ts +12 -0
  345. package/src/rules/comment-markers.ts +57 -21
  346. package/src/rules/contract-visibility.ts +383 -0
  347. package/src/rules/data-guard.ts +391 -42
  348. package/src/rules/layout.ts +22 -0
  349. package/src/rules/legacy.ts +206 -52
  350. package/src/rules/pages.ts +481 -4
  351. package/src/rules/routes.ts +124 -16
  352. package/src/rules/secrets.ts +2 -2
  353. package/src/rules/security.ts +391 -72
  354. package/src/rules/zones.ts +232 -42
  355. package/src/scaffold.ts +6 -4
  356. package/src/sdk.ts +14 -5
  357. package/src/stylex-migrate.ts +251 -24
  358. package/src/test-tool.ts +12 -3
  359. package/src/typecheck-coverage.ts +134 -0
  360. package/src/upgrade.ts +576 -72
  361. package/src/verification-plan.ts +33 -2
  362. package/src/version-drift.ts +118 -0
  363. package/src/web-app-options.ts +2 -2
  364. package/src/work-graph.ts +17 -12
@@ -37,6 +37,11 @@ const GATES = Object.freeze([
37
37
  "Run `bun run typecheck` and fix the reported TypeScript errors.",
38
38
  ),
39
39
  gate("tests", [["run", "test"]], "Run `bun run test` and fix the first failing test."),
40
+ gate(
41
+ "rest-tests",
42
+ [["run", "test:rest"]],
43
+ "Run `bun run test:rest` and fix the first failing test.",
44
+ ),
40
45
  gate(
41
46
  "docs",
42
47
  [["run", "check:docs"]],
@@ -178,12 +183,32 @@ const GATES = Object.freeze([
178
183
  [["run", "check:verification-parity"]],
179
184
  "Run `bun run check:verification-parity` and reconcile the release plan with CI.",
180
185
  ),
186
+ gate(
187
+ "server-components-contracts",
188
+ [["run", "check:server-components:contracts"]],
189
+ "Run `bun run check:server-components:contracts` and fix the component admission, transport or lifecycle regression.",
190
+ ),
191
+ gate(
192
+ "server-components",
193
+ [
194
+ ["run", "check:server-components"],
195
+ ["run", "check:server-components:consumers"],
196
+ ],
197
+ "Run the server-components matrix and packaged-consumer commands; every adapter and required row must pass.",
198
+ ),
199
+ gate(
200
+ "server-components-performance",
201
+ [["run", "check:server-components:performance"]],
202
+ "Run the five-run component performance comparison and investigate the measured regression.",
203
+ { workflowRequired: false },
204
+ ),
181
205
  ])
182
206
 
183
207
  const DEFAULT_PLAN = Object.freeze([
184
208
  "lint",
185
209
  "typecheck",
186
210
  "tests",
211
+ "server-components-contracts",
187
212
  "docs",
188
213
  "api-corpus",
189
214
  "cards-corpus",
@@ -196,14 +221,17 @@ const DEFAULT_PLAN = Object.freeze([
196
221
  "changesets",
197
222
  ] as const)
198
223
 
224
+ // `coverage` fails on any failing test, so it is the release plan's run of the library suites;
225
+ // `rest-tests` runs the suites it leaves out, and `tests` would only repeat the two.
199
226
  const RELEASE_PLAN = Object.freeze([
200
227
  "build",
201
228
  "lint",
202
229
  "typecheck",
203
- "tests",
204
- "cli-isolation",
205
230
  "coverage",
231
+ "rest-tests",
232
+ "cli-isolation",
206
233
  "corpus",
234
+ "sitemap",
207
235
  "docs",
208
236
  "public-boundary",
209
237
  "public-manifest",
@@ -222,6 +250,9 @@ const RELEASE_PLAN = Object.freeze([
222
250
  "pipeline-parity",
223
251
  "leak-matrix",
224
252
  "verification-parity",
253
+ "server-components",
254
+ "server-components-performance",
255
+ "server-components-contracts",
225
256
  "changesets",
226
257
  ] as const)
227
258
 
@@ -0,0 +1,118 @@
1
+ import { readFileSync } from "node:fs"
2
+ import { join, sep } from "node:path"
3
+ import {
4
+ compareSemverSpec,
5
+ groupDeclaredNames,
6
+ groupPinVersions,
7
+ installedGroupPackages,
8
+ isFixedGroup,
9
+ type LinkedGroupInstall,
10
+ } from "./upgrade.ts"
11
+
12
+ /** The fixed-group packages the package installed at `dir` depends on. */
13
+ function groupDependenciesAt(dir: string): string[] {
14
+ try {
15
+ const manifest: unknown = JSON.parse(readFileSync(join(dir, "package.json"), "utf8"))
16
+ if (typeof manifest !== "object" || manifest === null) return []
17
+ return ["dependencies", "peerDependencies", "optionalDependencies"].flatMap((field) => {
18
+ const deps: unknown = Reflect.get(manifest, field)
19
+ return typeof deps === "object" && deps !== null ? Object.keys(deps).filter(isFixedGroup) : []
20
+ })
21
+ } catch {
22
+ return []
23
+ }
24
+ }
25
+
26
+ /** One version of the fixed release group, and where the workspace carries it. */
27
+ export interface GroupVersion {
28
+ readonly version: string
29
+ readonly sources: readonly string[]
30
+ }
31
+
32
+ /**
33
+ * More than one version of the fixed release group (`@nifrajs/*`, `nifra`, `create-nifra`) in one
34
+ * workspace. Read only: package.json pins, installed packages (followed to their real location, so a
35
+ * linked repo reports its own version) and the running CLI. Nothing is spawned.
36
+ */
37
+ export interface VersionDrift {
38
+ /** Oldest first. */
39
+ readonly versions: readonly GroupVersion[]
40
+ readonly highest: string
41
+ /** `nifra upgrade` edits the workspace only; these are listed, never edited. */
42
+ readonly linkedOutside: readonly LinkedGroupInstall[]
43
+ }
44
+
45
+ export function collectVersionDrift(cwd: string, cliVersion?: string): VersionDrift | undefined {
46
+ const sources = new Map<string, string[]>()
47
+ const add = (version: string, source: string): void => {
48
+ const list = sources.get(version) ?? []
49
+ list.push(source)
50
+ sources.set(version, list)
51
+ }
52
+ for (const [version, files] of groupPinVersions(cwd)) {
53
+ for (const file of files) add(version, `pinned in ${file}`)
54
+ }
55
+ const linkedOutside: LinkedGroupInstall[] = []
56
+ const installs = installedGroupPackages(cwd)
57
+ // An install nothing declares, such as one an older install left behind, loads nowhere, and no
58
+ // upgrade re-pins it: counting it would report drift that never clears.
59
+ const declared = new Set(groupDeclaredNames(cwd))
60
+ for (const { path } of installs) for (const name of groupDependenciesAt(path)) declared.add(name)
61
+ for (const { package: name, path, version, inside } of installs) {
62
+ if (!declared.has(name)) continue
63
+ add(version, `installed ${name}${inside ? "" : ` (linked from ${path})`}`)
64
+ if (!inside) linkedOutside.push({ package: name, path, version })
65
+ }
66
+ if (cliVersion !== undefined) add(cliVersion, "the running nifra CLI")
67
+ if (sources.size < 2) return undefined
68
+ const versions = [...sources]
69
+ .sort(([a], [b]) => compareSemverSpec(a, b))
70
+ .map(([version, list]) => ({ version, sources: list }))
71
+ return {
72
+ versions,
73
+ highest: versions[versions.length - 1]!.version,
74
+ linkedOutside,
75
+ }
76
+ }
77
+
78
+ /** The install a package belongs to: the folder above its last `node_modules`, or the package itself. */
79
+ const installRoot = (path: string): string => {
80
+ const parts = path.split(sep)
81
+ const last = parts.lastIndexOf("node_modules")
82
+ return last <= 0 ? path : parts.slice(0, last).join(sep)
83
+ }
84
+
85
+ /** Linked installs, one phrase per install and version: a linked repository brings its whole group. */
86
+ function linkedGroups(installs: readonly LinkedGroupInstall[]): string[] {
87
+ const groups = new Map<string, LinkedGroupInstall[]>()
88
+ for (const install of installs) {
89
+ const key = `${installRoot(install.path)}\0${install.version}`
90
+ groups.set(key, [...(groups.get(key) ?? []), install])
91
+ }
92
+ return [...groups.values()].map((group) => {
93
+ const [first] = group
94
+ if (first === undefined) return ""
95
+ if (group.length === 1) return `${first.package} ${first.version} at ${first.path}`
96
+ const names = group.map((install) => install.package).sort()
97
+ const listed =
98
+ names.length > 3
99
+ ? `${names.slice(0, 3).join(", ")} and ${names.length - 3} more`
100
+ : names.join(", ")
101
+ return `${group.length} packages at ${first.version} under ${installRoot(first.path)} (${listed})`
102
+ })
103
+ }
104
+
105
+ /** The advisory both `nifra doctor` and `nifra check` print. */
106
+ export function versionDriftMessage(drift: VersionDrift): string {
107
+ const listed = drift.versions
108
+ .map(
109
+ ({ version, sources }) =>
110
+ `${version} (${sources.slice(0, 3).join("; ")}${sources.length > 3 ? `; and ${sources.length - 3} more` : ""})`,
111
+ )
112
+ .join(", ")
113
+ const linked =
114
+ drift.linkedOutside.length === 0
115
+ ? ""
116
+ : ` It does not edit ${linkedGroups(drift.linkedOutside).join(", ")}, linked from outside the workspace root; upgrade that repository on its own.`
117
+ return `mixed nifra versions in this workspace: ${listed}. Run \`nifra upgrade ${drift.highest}\` (it runs each release's migration steps, then re-pins).${linked}`
118
+ }
@@ -11,7 +11,7 @@ import { FRAMEWORK_WEB_OPTIONS, type FrameworkWebOption, type NifraFramework } f
11
11
 
12
12
  type FrameworkWebAppOptions = Pick<
13
13
  CreateWebAppOptions,
14
- "use" | "api" | "apiPrefix" | "apiStrip" | "mounts" | "csp" | "nonce"
14
+ "use" | "api" | "apiPrefix" | "apiStrip" | "mounts" | "csp" | "nonce" | "serverComponents"
15
15
  >
16
16
 
17
17
  /** `use`, the backend as `api`, and every forwarded framework field the config sets. */
@@ -39,7 +39,7 @@ export function frameworkMountPaths(fw: NifraFramework, hasBackend: boolean): st
39
39
  for (const mount of fw.mounts ?? []) {
40
40
  if (typeof mount?.path === "string") paths.push(mount.path)
41
41
  }
42
- const apiPrefix = fw.apiPrefix ?? "/api"
42
+ const apiPrefix = fw.apiPrefix ?? ""
43
43
  if (hasBackend && apiPrefix !== "") paths.push(apiPrefix)
44
44
  return paths
45
45
  }
package/src/work-graph.ts CHANGED
@@ -251,16 +251,15 @@ export async function buildWorkGraph(input: WorkGraphBuildInput): Promise<WorkGr
251
251
  const files = input.files ?? []
252
252
  const nodes = new Map<string, WorkGraphNode>()
253
253
  const edges: WorkGraphEdge[] = []
254
+ const edgeKeys = new Set<string>()
254
255
  const addNode = (node: WorkGraphNode): void => {
255
256
  if (!nodes.has(node.id)) nodes.set(node.id, node)
256
257
  }
257
258
  const addEdge = (edge: WorkGraphEdge): void => {
258
- if (
259
- !edges.some(
260
- (item) => item.from === edge.from && item.to === edge.to && item.relation === edge.relation,
261
- )
262
- )
263
- edges.push(edge)
259
+ const key = `${edge.from}\n${edge.to}\n${edge.relation}`
260
+ if (edgeKeys.has(key)) return
261
+ edgeKeys.add(key)
262
+ edges.push(edge)
264
263
  }
265
264
  for (const file of files) {
266
265
  const kind =
@@ -381,14 +380,20 @@ export function queryImpact(graph: WorkGraph, changedFiles: readonly string[]):
381
380
  // Any backend or shared module can reach every handler through backend/app.ts.
382
381
  if (unmodeled.length > 0 || normalized.some((file) => BACKEND_SOURCE_DIR.test(file)))
383
382
  for (const node of graph.nodes) if (node.kind === "route") seeds.add(node.id)
383
+ const targets = new Map<string, string[]>()
384
+ for (const edge of graph.edges) {
385
+ const from = targets.get(edge.from)
386
+ if (from === undefined) targets.set(edge.from, [edge.to])
387
+ else from.push(edge.to)
388
+ }
384
389
  const impacted = new Set(seeds)
385
390
  const queue = [...seeds]
386
- while (queue.length > 0) {
387
- const current = queue.shift()!
388
- for (const edge of graph.edges) {
389
- if (edge.from !== current || impacted.has(edge.to)) continue
390
- impacted.add(edge.to)
391
- queue.push(edge.to)
391
+ // Iterating an array visits what is pushed onto it meanwhile, so this is the breadth-first walk.
392
+ for (const current of queue) {
393
+ for (const to of targets.get(current) ?? []) {
394
+ if (impacted.has(to)) continue
395
+ impacted.add(to)
396
+ queue.push(to)
392
397
  }
393
398
  }
394
399
  const impactedRoutes = graph.nodes