@nifrajs/cli 4.0.0 → 5.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 (332) hide show
  1. package/README.md +3 -0
  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 +7 -2
  12. package/dist/assure-hydration.js.map +1 -1
  13. package/dist/capabilities-tool.d.ts.map +1 -1
  14. package/dist/capabilities-tool.js +13 -3
  15. package/dist/capabilities-tool.js.map +1 -1
  16. package/dist/check-diagnostics.d.ts +20 -2
  17. package/dist/check-diagnostics.d.ts.map +1 -1
  18. package/dist/check-diagnostics.js +54 -7
  19. package/dist/check-diagnostics.js.map +1 -1
  20. package/dist/check-scan.d.ts +75 -22
  21. package/dist/check-scan.d.ts.map +1 -1
  22. package/dist/check-scan.js +1475 -310
  23. package/dist/check-scan.js.map +1 -1
  24. package/dist/check.d.ts +2 -2
  25. package/dist/check.d.ts.map +1 -1
  26. package/dist/check.js +192 -65
  27. package/dist/check.js.map +1 -1
  28. package/dist/cli.d.ts +7 -0
  29. package/dist/cli.d.ts.map +1 -1
  30. package/dist/cli.js +112 -5
  31. package/dist/cli.js.map +1 -1
  32. package/dist/client-module.d.ts +18 -0
  33. package/dist/client-module.d.ts.map +1 -0
  34. package/dist/client-module.js +49 -0
  35. package/dist/client-module.js.map +1 -0
  36. package/dist/command-catalog.d.ts +21 -5
  37. package/dist/command-catalog.d.ts.map +1 -1
  38. package/dist/command-catalog.js +138 -38
  39. package/dist/command-catalog.js.map +1 -1
  40. package/dist/component-check.d.ts +13 -0
  41. package/dist/component-check.d.ts.map +1 -0
  42. package/dist/component-check.js +59 -0
  43. package/dist/component-check.js.map +1 -0
  44. package/dist/contract-proof.d.ts +1 -1
  45. package/dist/contract-proof.d.ts.map +1 -1
  46. package/dist/contract-proof.js +2 -24
  47. package/dist/contract-proof.js.map +1 -1
  48. package/dist/contracts.d.ts +19 -0
  49. package/dist/contracts.d.ts.map +1 -1
  50. package/dist/contracts.js +50 -3
  51. package/dist/contracts.js.map +1 -1
  52. package/dist/db-tool.d.ts.map +1 -1
  53. package/dist/db-tool.js +4 -3
  54. package/dist/db-tool.js.map +1 -1
  55. package/dist/dev-bun-config.d.ts +5 -1
  56. package/dist/dev-bun-config.d.ts.map +1 -1
  57. package/dist/dev-bun-config.js +44 -11
  58. package/dist/dev-bun-config.js.map +1 -1
  59. package/dist/diagnostics.d.ts +3 -0
  60. package/dist/diagnostics.d.ts.map +1 -1
  61. package/dist/diagnostics.js.map +1 -1
  62. package/dist/diff-tool.d.ts +9 -8
  63. package/dist/diff-tool.d.ts.map +1 -1
  64. package/dist/diff-tool.js +28 -24
  65. package/dist/diff-tool.js.map +1 -1
  66. package/dist/doctor.d.ts +13 -0
  67. package/dist/doctor.d.ts.map +1 -1
  68. package/dist/doctor.js +23 -15
  69. package/dist/doctor.js.map +1 -1
  70. package/dist/env-file.d.ts +6 -0
  71. package/dist/env-file.d.ts.map +1 -1
  72. package/dist/env-file.js +6 -0
  73. package/dist/env-file.js.map +1 -1
  74. package/dist/fix-recipes.d.ts.map +1 -1
  75. package/dist/fix-recipes.js +28 -3
  76. package/dist/fix-recipes.js.map +1 -1
  77. package/dist/hand-built-manifest.d.ts +27 -0
  78. package/dist/hand-built-manifest.d.ts.map +1 -0
  79. package/dist/hand-built-manifest.js +124 -0
  80. package/dist/hand-built-manifest.js.map +1 -0
  81. package/dist/hooks-report.d.ts +23 -0
  82. package/dist/hooks-report.d.ts.map +1 -0
  83. package/dist/hooks-report.js +104 -0
  84. package/dist/hooks-report.js.map +1 -0
  85. package/dist/init-agents.d.ts +2 -2
  86. package/dist/init-agents.d.ts.map +1 -1
  87. package/dist/init-agents.js +17 -11
  88. package/dist/init-agents.js.map +1 -1
  89. package/dist/internal/nifra-project.d.ts +41 -0
  90. package/dist/internal/nifra-project.d.ts.map +1 -0
  91. package/dist/internal/nifra-project.js +183 -0
  92. package/dist/internal/nifra-project.js.map +1 -0
  93. package/dist/internal/project-specifiers.d.ts +16 -0
  94. package/dist/internal/project-specifiers.d.ts.map +1 -0
  95. package/dist/internal/project-specifiers.js +396 -0
  96. package/dist/internal/project-specifiers.js.map +1 -0
  97. package/dist/internal/script-kind.d.ts.map +1 -1
  98. package/dist/internal/script-kind.js +2 -1
  99. package/dist/internal/script-kind.js.map +1 -1
  100. package/dist/internal/source-facts.d.ts +2 -0
  101. package/dist/internal/source-facts.d.ts.map +1 -1
  102. package/dist/internal/source-facts.js +11 -0
  103. package/dist/internal/source-facts.js.map +1 -1
  104. package/dist/internal/typescript-import.d.ts +1 -1
  105. package/dist/internal/typescript-import.d.ts.map +1 -1
  106. package/dist/internal/typescript-import.js +5 -3
  107. package/dist/internal/typescript-import.js.map +1 -1
  108. package/dist/introspect.d.ts +6 -6
  109. package/dist/introspect.d.ts.map +1 -1
  110. package/dist/introspect.js +55 -24
  111. package/dist/introspect.js.map +1 -1
  112. package/dist/levels-tool.d.ts.map +1 -1
  113. package/dist/levels-tool.js +93 -15
  114. package/dist/levels-tool.js.map +1 -1
  115. package/dist/load.d.ts +31 -8
  116. package/dist/load.d.ts.map +1 -1
  117. package/dist/load.js +37 -7
  118. package/dist/load.js.map +1 -1
  119. package/dist/mcp-context.d.ts +5 -5
  120. package/dist/mcp-context.d.ts.map +1 -1
  121. package/dist/mcp-context.js +2 -2
  122. package/dist/mcp-context.js.map +1 -1
  123. package/dist/mcp-exec.d.ts +12 -4
  124. package/dist/mcp-exec.d.ts.map +1 -1
  125. package/dist/mcp-exec.js +50 -13
  126. package/dist/mcp-exec.js.map +1 -1
  127. package/dist/mcp-http.js +1 -1
  128. package/dist/mcp-io.d.ts +17 -0
  129. package/dist/mcp-io.d.ts.map +1 -1
  130. package/dist/mcp-io.js +40 -0
  131. package/dist/mcp-io.js.map +1 -1
  132. package/dist/mcp-isolate.d.ts.map +1 -1
  133. package/dist/mcp-isolate.js +3 -2
  134. package/dist/mcp-isolate.js.map +1 -1
  135. package/dist/mcp-project-child.js +1 -1
  136. package/dist/mcp-project-child.js.map +1 -1
  137. package/dist/mcp-root.d.ts.map +1 -1
  138. package/dist/mcp-root.js +50 -10
  139. package/dist/mcp-root.js.map +1 -1
  140. package/dist/mcp-ws.d.ts.map +1 -1
  141. package/dist/mcp-ws.js +3 -1
  142. package/dist/mcp-ws.js.map +1 -1
  143. package/dist/mcp.d.ts.map +1 -1
  144. package/dist/mcp.js +8 -2
  145. package/dist/mcp.js.map +1 -1
  146. package/dist/migrate-layout.d.ts +7 -0
  147. package/dist/migrate-layout.d.ts.map +1 -1
  148. package/dist/migrate-layout.js +958 -71
  149. package/dist/migrate-layout.js.map +1 -1
  150. package/dist/output-schemas.d.ts +94 -0
  151. package/dist/output-schemas.d.ts.map +1 -0
  152. package/dist/output-schemas.js +864 -0
  153. package/dist/output-schemas.js.map +1 -0
  154. package/dist/port.d.ts.map +1 -1
  155. package/dist/port.js +3 -2
  156. package/dist/port.js.map +1 -1
  157. package/dist/project-facts.d.ts +15 -4
  158. package/dist/project-facts.d.ts.map +1 -1
  159. package/dist/project-facts.js +1 -0
  160. package/dist/project-facts.js.map +1 -1
  161. package/dist/recipes/4.0.0.d.ts.map +1 -1
  162. package/dist/recipes/4.0.0.js +2 -0
  163. package/dist/recipes/4.0.0.js.map +1 -1
  164. package/dist/recipes/5.0.0.d.ts +11 -0
  165. package/dist/recipes/5.0.0.d.ts.map +1 -0
  166. package/dist/recipes/5.0.0.js +34 -0
  167. package/dist/recipes/5.0.0.js.map +1 -0
  168. package/dist/recipes/index.d.ts +7 -0
  169. package/dist/recipes/index.d.ts.map +1 -1
  170. package/dist/recipes/index.js +2 -0
  171. package/dist/recipes/index.js.map +1 -1
  172. package/dist/replay.d.ts.map +1 -1
  173. package/dist/replay.js +12 -3
  174. package/dist/replay.js.map +1 -1
  175. package/dist/review.d.ts +3 -1
  176. package/dist/review.d.ts.map +1 -1
  177. package/dist/review.js +38 -7
  178. package/dist/review.js.map +1 -1
  179. package/dist/rules/codes.d.ts +10 -0
  180. package/dist/rules/codes.d.ts.map +1 -1
  181. package/dist/rules/codes.js +10 -0
  182. package/dist/rules/codes.js.map +1 -1
  183. package/dist/rules/comment-markers.d.ts +16 -7
  184. package/dist/rules/comment-markers.d.ts.map +1 -1
  185. package/dist/rules/comment-markers.js +58 -19
  186. package/dist/rules/comment-markers.js.map +1 -1
  187. package/dist/rules/contract-visibility.d.ts +3 -0
  188. package/dist/rules/contract-visibility.d.ts.map +1 -0
  189. package/dist/rules/contract-visibility.js +337 -0
  190. package/dist/rules/contract-visibility.js.map +1 -0
  191. package/dist/rules/data-guard.d.ts +7 -0
  192. package/dist/rules/data-guard.d.ts.map +1 -1
  193. package/dist/rules/data-guard.js +319 -37
  194. package/dist/rules/data-guard.js.map +1 -1
  195. package/dist/rules/layout.d.ts +4 -0
  196. package/dist/rules/layout.d.ts.map +1 -0
  197. package/dist/rules/layout.js +19 -0
  198. package/dist/rules/layout.js.map +1 -0
  199. package/dist/rules/legacy.d.ts.map +1 -1
  200. package/dist/rules/legacy.js +158 -43
  201. package/dist/rules/legacy.js.map +1 -1
  202. package/dist/rules/pages.d.ts +24 -0
  203. package/dist/rules/pages.d.ts.map +1 -1
  204. package/dist/rules/pages.js +221 -3
  205. package/dist/rules/pages.js.map +1 -1
  206. package/dist/rules/routes.d.ts +6 -0
  207. package/dist/rules/routes.d.ts.map +1 -1
  208. package/dist/rules/routes.js +111 -16
  209. package/dist/rules/routes.js.map +1 -1
  210. package/dist/rules/secrets.js +2 -2
  211. package/dist/rules/secrets.js.map +1 -1
  212. package/dist/rules/security.d.ts +12 -1
  213. package/dist/rules/security.d.ts.map +1 -1
  214. package/dist/rules/security.js +281 -68
  215. package/dist/rules/security.js.map +1 -1
  216. package/dist/rules/zones.d.ts +13 -0
  217. package/dist/rules/zones.d.ts.map +1 -1
  218. package/dist/rules/zones.js +192 -40
  219. package/dist/rules/zones.js.map +1 -1
  220. package/dist/scaffold.d.ts.map +1 -1
  221. package/dist/scaffold.js +6 -4
  222. package/dist/scaffold.js.map +1 -1
  223. package/dist/sdk.d.ts.map +1 -1
  224. package/dist/sdk.js +14 -5
  225. package/dist/sdk.js.map +1 -1
  226. package/dist/stylex-migrate.d.ts.map +1 -1
  227. package/dist/stylex-migrate.js +163 -7
  228. package/dist/stylex-migrate.js.map +1 -1
  229. package/dist/sync-manifest.d.ts +10 -2
  230. package/dist/sync-manifest.d.ts.map +1 -1
  231. package/dist/sync-manifest.js +42 -18
  232. package/dist/sync-manifest.js.map +1 -1
  233. package/dist/test-tool.d.ts.map +1 -1
  234. package/dist/test-tool.js +12 -3
  235. package/dist/test-tool.js.map +1 -1
  236. package/dist/typecheck-coverage.d.ts +20 -0
  237. package/dist/typecheck-coverage.d.ts.map +1 -0
  238. package/dist/typecheck-coverage.js +129 -0
  239. package/dist/typecheck-coverage.js.map +1 -0
  240. package/dist/upgrade.d.ts +69 -0
  241. package/dist/upgrade.d.ts.map +1 -1
  242. package/dist/upgrade.js +420 -77
  243. package/dist/upgrade.js.map +1 -1
  244. package/dist/verification-plan.d.ts.map +1 -1
  245. package/dist/verification-plan.js +11 -0
  246. package/dist/verification-plan.js.map +1 -1
  247. package/dist/version-drift.d.ts +22 -0
  248. package/dist/version-drift.d.ts.map +1 -0
  249. package/dist/version-drift.js +42 -0
  250. package/dist/version-drift.js.map +1 -0
  251. package/dist/web-app-options.d.ts +1 -1
  252. package/dist/web-app-options.d.ts.map +1 -1
  253. package/dist/web-app-options.js +1 -1
  254. package/dist/web-app-options.js.map +1 -1
  255. package/dist/work-graph.d.ts.map +1 -1
  256. package/dist/work-graph.js +20 -8
  257. package/dist/work-graph.js.map +1 -1
  258. package/docs/examples.json +54 -6
  259. package/docs/llms-full.txt +1539 -138
  260. package/docs/types.json +1456 -206
  261. package/package.json +11 -12
  262. package/src/agent-files.ts +203 -0
  263. package/src/app-files.ts +34 -4
  264. package/src/assure-hydration.ts +15 -2
  265. package/src/capabilities-tool.ts +15 -3
  266. package/src/check-diagnostics.ts +73 -9
  267. package/src/check-scan.ts +1773 -381
  268. package/src/check.ts +237 -76
  269. package/src/cli.ts +141 -5
  270. package/src/client-module.ts +61 -0
  271. package/src/command-catalog.ts +181 -47
  272. package/src/component-check.ts +79 -0
  273. package/src/contract-proof.ts +3 -25
  274. package/src/contracts.ts +60 -3
  275. package/src/db-tool.ts +5 -2
  276. package/src/dev-bun-config.ts +51 -10
  277. package/src/diagnostics.ts +3 -0
  278. package/src/diff-tool.ts +33 -26
  279. package/src/doctor.ts +43 -21
  280. package/src/env-file.ts +7 -0
  281. package/src/fix-recipes.ts +34 -3
  282. package/src/hand-built-manifest.ts +144 -0
  283. package/src/hooks-report.ts +135 -0
  284. package/src/init-agents.ts +17 -11
  285. package/src/internal/nifra-project.ts +191 -0
  286. package/src/internal/project-specifiers.ts +424 -0
  287. package/src/internal/script-kind.ts +2 -1
  288. package/src/internal/source-facts.ts +25 -0
  289. package/src/internal/typescript-import.ts +10 -3
  290. package/src/introspect.ts +65 -27
  291. package/src/levels-tool.ts +117 -20
  292. package/src/load.ts +78 -14
  293. package/src/mcp-context.ts +7 -7
  294. package/src/mcp-exec.ts +60 -17
  295. package/src/mcp-http.ts +1 -1
  296. package/src/mcp-io.ts +41 -0
  297. package/src/mcp-isolate.ts +9 -2
  298. package/src/mcp-project-child.ts +1 -1
  299. package/src/mcp-root.ts +58 -8
  300. package/src/mcp-ws.ts +4 -2
  301. package/src/mcp.ts +8 -3
  302. package/src/migrate-layout.ts +1109 -70
  303. package/src/output-schemas.ts +1084 -0
  304. package/src/port.ts +3 -2
  305. package/src/project-facts.ts +17 -3
  306. package/src/recipes/4.0.0.ts +2 -0
  307. package/src/recipes/5.0.0.ts +35 -0
  308. package/src/recipes/index.ts +10 -0
  309. package/src/replay.ts +14 -3
  310. package/src/review.ts +39 -10
  311. package/src/rules/codes.ts +10 -0
  312. package/src/rules/comment-markers.ts +57 -21
  313. package/src/rules/contract-visibility.ts +383 -0
  314. package/src/rules/data-guard.ts +351 -42
  315. package/src/rules/layout.ts +22 -0
  316. package/src/rules/legacy.ts +184 -51
  317. package/src/rules/pages.ts +238 -3
  318. package/src/rules/routes.ts +124 -16
  319. package/src/rules/secrets.ts +2 -2
  320. package/src/rules/security.ts +352 -71
  321. package/src/rules/zones.ts +205 -38
  322. package/src/scaffold.ts +6 -4
  323. package/src/sdk.ts +14 -5
  324. package/src/stylex-migrate.ts +134 -7
  325. package/src/sync-manifest.ts +48 -18
  326. package/src/test-tool.ts +18 -3
  327. package/src/typecheck-coverage.ts +134 -0
  328. package/src/upgrade.ts +487 -71
  329. package/src/verification-plan.ts +24 -0
  330. package/src/version-drift.ts +67 -0
  331. package/src/web-app-options.ts +2 -2
  332. package/src/work-graph.ts +17 -12
@@ -178,12 +178,32 @@ const GATES = Object.freeze([
178
178
  [["run", "check:verification-parity"]],
179
179
  "Run `bun run check:verification-parity` and reconcile the release plan with CI.",
180
180
  ),
181
+ gate(
182
+ "server-components-contracts",
183
+ [["run", "check:server-components:contracts"]],
184
+ "Run `bun run check:server-components:contracts` and fix the component admission, transport or lifecycle regression.",
185
+ ),
186
+ gate(
187
+ "server-components",
188
+ [
189
+ ["run", "check:server-components"],
190
+ ["run", "check:server-components:consumers"],
191
+ ],
192
+ "Run the server-components matrix and packaged-consumer commands; every adapter and required row must pass.",
193
+ ),
194
+ gate(
195
+ "server-components-performance",
196
+ [["run", "check:server-components:performance"]],
197
+ "Run the five-run component performance comparison and investigate the measured regression.",
198
+ { workflowRequired: false },
199
+ ),
181
200
  ])
182
201
 
183
202
  const DEFAULT_PLAN = Object.freeze([
184
203
  "lint",
185
204
  "typecheck",
186
205
  "tests",
206
+ "server-components-contracts",
187
207
  "docs",
188
208
  "api-corpus",
189
209
  "cards-corpus",
@@ -204,6 +224,7 @@ const RELEASE_PLAN = Object.freeze([
204
224
  "cli-isolation",
205
225
  "coverage",
206
226
  "corpus",
227
+ "sitemap",
207
228
  "docs",
208
229
  "public-boundary",
209
230
  "public-manifest",
@@ -222,6 +243,9 @@ const RELEASE_PLAN = Object.freeze([
222
243
  "pipeline-parity",
223
244
  "leak-matrix",
224
245
  "verification-parity",
246
+ "server-components",
247
+ "server-components-performance",
248
+ "server-components-contracts",
225
249
  "changesets",
226
250
  ] as const)
227
251
 
@@ -0,0 +1,67 @@
1
+ import {
2
+ compareSemverSpec,
3
+ groupPinVersions,
4
+ installedGroupPackages,
5
+ type LinkedGroupInstall,
6
+ } from "./upgrade.ts"
7
+
8
+ /** One version of the fixed release group, and where the workspace carries it. */
9
+ export interface GroupVersion {
10
+ readonly version: string
11
+ readonly sources: readonly string[]
12
+ }
13
+
14
+ /**
15
+ * More than one version of the fixed release group (`@nifrajs/*`, `nifra`, `create-nifra`) in one
16
+ * workspace. Read only: package.json pins, installed packages (followed to their real location, so a
17
+ * linked repo reports its own version) and the running CLI. Nothing is spawned.
18
+ */
19
+ export interface VersionDrift {
20
+ /** Oldest first. */
21
+ readonly versions: readonly GroupVersion[]
22
+ readonly highest: string
23
+ /** `nifra upgrade` edits the workspace only; these are listed, never edited. */
24
+ readonly linkedOutside: readonly LinkedGroupInstall[]
25
+ }
26
+
27
+ export function collectVersionDrift(cwd: string, cliVersion?: string): VersionDrift | undefined {
28
+ const sources = new Map<string, string[]>()
29
+ const add = (version: string, source: string): void => {
30
+ const list = sources.get(version) ?? []
31
+ list.push(source)
32
+ sources.set(version, list)
33
+ }
34
+ for (const [version, files] of groupPinVersions(cwd)) {
35
+ for (const file of files) add(version, `pinned in ${file}`)
36
+ }
37
+ const linkedOutside: LinkedGroupInstall[] = []
38
+ for (const { package: name, path, version, inside } of installedGroupPackages(cwd)) {
39
+ add(version, `installed ${name}${inside ? "" : ` (linked from ${path})`}`)
40
+ if (!inside) linkedOutside.push({ package: name, path, version })
41
+ }
42
+ if (cliVersion !== undefined) add(cliVersion, "the running nifra CLI")
43
+ if (sources.size < 2) return undefined
44
+ const versions = [...sources]
45
+ .sort(([a], [b]) => compareSemverSpec(a, b))
46
+ .map(([version, list]) => ({ version, sources: list }))
47
+ return {
48
+ versions,
49
+ highest: versions[versions.length - 1]!.version,
50
+ linkedOutside,
51
+ }
52
+ }
53
+
54
+ /** The advisory both `nifra doctor` and `nifra check` print. */
55
+ export function versionDriftMessage(drift: VersionDrift): string {
56
+ const listed = drift.versions
57
+ .map(
58
+ ({ version, sources }) =>
59
+ `${version} (${sources.slice(0, 3).join("; ")}${sources.length > 3 ? `; and ${sources.length - 3} more` : ""})`,
60
+ )
61
+ .join(", ")
62
+ const linked =
63
+ drift.linkedOutside.length === 0
64
+ ? ""
65
+ : ` It does not edit ${drift.linkedOutside.map((entry) => `${entry.package} ${entry.version} at ${entry.path}`).join(", ")}, linked from outside the workspace root; upgrade that repository on its own.`
66
+ return `mixed nifra versions in this workspace: ${listed}. Run \`nifra upgrade ${drift.highest}\` (it runs each release's migration steps, then re-pins).${linked}`
67
+ }
@@ -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