@nifrajs/cli 3.5.0 → 4.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 (403) hide show
  1. package/README.md +42 -3
  2. package/dist/app-files.d.ts +11 -0
  3. package/dist/app-files.d.ts.map +1 -0
  4. package/dist/app-files.js +34 -0
  5. package/dist/app-files.js.map +1 -0
  6. package/dist/assure-hydration.d.ts +7 -1
  7. package/dist/assure-hydration.d.ts.map +1 -1
  8. package/dist/assure-hydration.js +128 -43
  9. package/dist/assure-hydration.js.map +1 -1
  10. package/dist/assure.d.ts.map +1 -1
  11. package/dist/assure.js +16 -2
  12. package/dist/assure.js.map +1 -1
  13. package/dist/capabilities-tool.d.ts +5 -1
  14. package/dist/capabilities-tool.d.ts.map +1 -1
  15. package/dist/capabilities-tool.js +32 -10
  16. package/dist/capabilities-tool.js.map +1 -1
  17. package/dist/cdn-check.d.ts +41 -0
  18. package/dist/cdn-check.d.ts.map +1 -0
  19. package/dist/cdn-check.js +207 -0
  20. package/dist/cdn-check.js.map +1 -0
  21. package/dist/check-diagnostics.d.ts +4 -0
  22. package/dist/check-diagnostics.d.ts.map +1 -1
  23. package/dist/check-diagnostics.js +13 -0
  24. package/dist/check-diagnostics.js.map +1 -1
  25. package/dist/check-scan.d.ts +49 -7
  26. package/dist/check-scan.d.ts.map +1 -1
  27. package/dist/check-scan.js +291 -62
  28. package/dist/check-scan.js.map +1 -1
  29. package/dist/check.d.ts.map +1 -1
  30. package/dist/check.js +99 -63
  31. package/dist/check.js.map +1 -1
  32. package/dist/cli.d.ts +42 -16
  33. package/dist/cli.d.ts.map +1 -1
  34. package/dist/cli.js +262 -82
  35. package/dist/cli.js.map +1 -1
  36. package/dist/command-catalog.d.ts +57 -5
  37. package/dist/command-catalog.d.ts.map +1 -1
  38. package/dist/command-catalog.js +270 -26
  39. package/dist/command-catalog.js.map +1 -1
  40. package/dist/config-target.d.ts +8 -0
  41. package/dist/config-target.d.ts.map +1 -0
  42. package/dist/config-target.js +45 -0
  43. package/dist/config-target.js.map +1 -0
  44. package/dist/contracts.d.ts.map +1 -1
  45. package/dist/contracts.js +52 -13
  46. package/dist/contracts.js.map +1 -1
  47. package/dist/db-audit.d.ts +37 -0
  48. package/dist/db-audit.d.ts.map +1 -0
  49. package/dist/db-audit.js +99 -0
  50. package/dist/db-audit.js.map +1 -0
  51. package/dist/db-child.d.ts +64 -0
  52. package/dist/db-child.d.ts.map +1 -0
  53. package/dist/db-child.js +213 -0
  54. package/dist/db-child.js.map +1 -0
  55. package/dist/db-config.d.ts +60 -0
  56. package/dist/db-config.d.ts.map +1 -0
  57. package/dist/db-config.js +176 -0
  58. package/dist/db-config.js.map +1 -0
  59. package/dist/db-tool.d.ts +84 -0
  60. package/dist/db-tool.d.ts.map +1 -0
  61. package/dist/db-tool.js +537 -0
  62. package/dist/db-tool.js.map +1 -0
  63. package/dist/dev-bun-config.d.ts +20 -13
  64. package/dist/dev-bun-config.d.ts.map +1 -1
  65. package/dist/dev-bun-config.js +98 -41
  66. package/dist/dev-bun-config.js.map +1 -1
  67. package/dist/dev-feed-tool.d.ts +106 -0
  68. package/dist/dev-feed-tool.d.ts.map +1 -0
  69. package/dist/dev-feed-tool.js +713 -0
  70. package/dist/dev-feed-tool.js.map +1 -0
  71. package/dist/dev-server-client.d.ts +67 -0
  72. package/dist/dev-server-client.d.ts.map +1 -0
  73. package/dist/dev-server-client.js +195 -0
  74. package/dist/dev-server-client.js.map +1 -0
  75. package/dist/diff-tool.d.ts +3 -3
  76. package/dist/diff-tool.d.ts.map +1 -1
  77. package/dist/diff-tool.js +6 -5
  78. package/dist/diff-tool.js.map +1 -1
  79. package/dist/docs-search.d.ts.map +1 -1
  80. package/dist/docs-search.js +12 -4
  81. package/dist/docs-search.js.map +1 -1
  82. package/dist/doctor.d.ts +17 -0
  83. package/dist/doctor.d.ts.map +1 -1
  84. package/dist/doctor.js +76 -15
  85. package/dist/doctor.js.map +1 -1
  86. package/dist/env-file.d.ts +15 -0
  87. package/dist/env-file.d.ts.map +1 -1
  88. package/dist/env-file.js +73 -1
  89. package/dist/env-file.js.map +1 -1
  90. package/dist/fix-recipes.d.ts.map +1 -1
  91. package/dist/fix-recipes.js +41 -21
  92. package/dist/fix-recipes.js.map +1 -1
  93. package/dist/frontend-guidance.js +5 -5
  94. package/dist/frontend-guidance.js.map +1 -1
  95. package/dist/i18n-check.d.ts +16 -0
  96. package/dist/i18n-check.d.ts.map +1 -0
  97. package/dist/i18n-check.js +154 -0
  98. package/dist/i18n-check.js.map +1 -0
  99. package/dist/init-agents.d.ts +49 -13
  100. package/dist/init-agents.d.ts.map +1 -1
  101. package/dist/init-agents.js +239 -34
  102. package/dist/init-agents.js.map +1 -1
  103. package/dist/internal/code-unit-order.d.ts +4 -0
  104. package/dist/internal/code-unit-order.d.ts.map +1 -0
  105. package/dist/internal/code-unit-order.js +6 -0
  106. package/dist/internal/code-unit-order.js.map +1 -0
  107. package/dist/internal/script-kind.d.ts +7 -0
  108. package/dist/internal/script-kind.d.ts.map +1 -0
  109. package/dist/internal/script-kind.js +12 -0
  110. package/dist/internal/script-kind.js.map +1 -0
  111. package/dist/internal/source-facts.d.ts +5 -0
  112. package/dist/internal/source-facts.d.ts.map +1 -1
  113. package/dist/internal/source-facts.js +61 -5
  114. package/dist/internal/source-facts.js.map +1 -1
  115. package/dist/internal/typescript-import.d.ts +49 -24
  116. package/dist/internal/typescript-import.d.ts.map +1 -1
  117. package/dist/internal/typescript-import.js +507 -67
  118. package/dist/internal/typescript-import.js.map +1 -1
  119. package/dist/introspect.d.ts +5 -1
  120. package/dist/introspect.d.ts.map +1 -1
  121. package/dist/introspect.js +92 -27
  122. package/dist/introspect.js.map +1 -1
  123. package/dist/learn.js +3 -3
  124. package/dist/learn.js.map +1 -1
  125. package/dist/levels-tool.d.ts +10 -0
  126. package/dist/levels-tool.d.ts.map +1 -1
  127. package/dist/levels-tool.js +33 -3
  128. package/dist/levels-tool.js.map +1 -1
  129. package/dist/load.d.ts +67 -17
  130. package/dist/load.d.ts.map +1 -1
  131. package/dist/load.js +78 -18
  132. package/dist/load.js.map +1 -1
  133. package/dist/manifest-tool.d.ts.map +1 -1
  134. package/dist/manifest-tool.js +3 -2
  135. package/dist/manifest-tool.js.map +1 -1
  136. package/dist/mcp-app-tool.d.ts.map +1 -1
  137. package/dist/mcp-app-tool.js +2 -0
  138. package/dist/mcp-app-tool.js.map +1 -1
  139. package/dist/mcp-context.js +1 -1
  140. package/dist/mcp-context.js.map +1 -1
  141. package/dist/mcp-delegate.d.ts +57 -0
  142. package/dist/mcp-delegate.d.ts.map +1 -0
  143. package/dist/mcp-delegate.js +130 -0
  144. package/dist/mcp-delegate.js.map +1 -0
  145. package/dist/mcp-docs-tools.d.ts.map +1 -1
  146. package/dist/mcp-docs-tools.js +23 -5
  147. package/dist/mcp-docs-tools.js.map +1 -1
  148. package/dist/mcp-exec.d.ts +2 -0
  149. package/dist/mcp-exec.d.ts.map +1 -1
  150. package/dist/mcp-exec.js +132 -108
  151. package/dist/mcp-exec.js.map +1 -1
  152. package/dist/mcp-http.d.ts.map +1 -1
  153. package/dist/mcp-http.js +17 -5
  154. package/dist/mcp-http.js.map +1 -1
  155. package/dist/mcp-io.d.ts +14 -0
  156. package/dist/mcp-io.d.ts.map +1 -1
  157. package/dist/mcp-io.js +45 -0
  158. package/dist/mcp-io.js.map +1 -1
  159. package/dist/mcp-isolate.d.ts +48 -0
  160. package/dist/mcp-isolate.d.ts.map +1 -0
  161. package/dist/mcp-isolate.js +222 -0
  162. package/dist/mcp-isolate.js.map +1 -0
  163. package/dist/mcp-project-child.d.ts +74 -0
  164. package/dist/mcp-project-child.d.ts.map +1 -0
  165. package/dist/mcp-project-child.js +130 -0
  166. package/dist/mcp-project-child.js.map +1 -0
  167. package/dist/mcp-render.js +2 -2
  168. package/dist/mcp-render.js.map +1 -1
  169. package/dist/mcp-root.d.ts +37 -9
  170. package/dist/mcp-root.d.ts.map +1 -1
  171. package/dist/mcp-root.js +110 -23
  172. package/dist/mcp-root.js.map +1 -1
  173. package/dist/mcp-run.d.ts +18 -1
  174. package/dist/mcp-run.d.ts.map +1 -1
  175. package/dist/mcp-run.js +82 -9
  176. package/dist/mcp-run.js.map +1 -1
  177. package/dist/mcp-ws.js +4 -1
  178. package/dist/mcp-ws.js.map +1 -1
  179. package/dist/mcp.d.ts +16 -3
  180. package/dist/mcp.d.ts.map +1 -1
  181. package/dist/mcp.js +81 -53
  182. package/dist/mcp.js.map +1 -1
  183. package/dist/migrate-layout.d.ts +44 -0
  184. package/dist/migrate-layout.d.ts.map +1 -0
  185. package/dist/migrate-layout.js +982 -0
  186. package/dist/migrate-layout.js.map +1 -0
  187. package/dist/openapi-tool.d.ts +2 -2
  188. package/dist/openapi-tool.d.ts.map +1 -1
  189. package/dist/openapi-tool.js.map +1 -1
  190. package/dist/openapi-types.d.ts +2 -1
  191. package/dist/openapi-types.d.ts.map +1 -1
  192. package/dist/openapi-types.js +240 -12
  193. package/dist/openapi-types.js.map +1 -1
  194. package/dist/pipeline-report.d.ts +3 -2
  195. package/dist/pipeline-report.d.ts.map +1 -1
  196. package/dist/pipeline-report.js +19 -16
  197. package/dist/pipeline-report.js.map +1 -1
  198. package/dist/port.d.ts +5 -4
  199. package/dist/port.d.ts.map +1 -1
  200. package/dist/port.js +46 -29
  201. package/dist/port.js.map +1 -1
  202. package/dist/project-facts.d.ts +5 -0
  203. package/dist/project-facts.d.ts.map +1 -1
  204. package/dist/project-facts.js.map +1 -1
  205. package/dist/recipes/4.0.0.d.ts +9 -0
  206. package/dist/recipes/4.0.0.d.ts.map +1 -0
  207. package/dist/recipes/4.0.0.js +34 -0
  208. package/dist/recipes/4.0.0.js.map +1 -0
  209. package/dist/recipes/index.d.ts.map +1 -1
  210. package/dist/recipes/index.js +2 -0
  211. package/dist/recipes/index.js.map +1 -1
  212. package/dist/release-verification.d.ts +11 -1
  213. package/dist/release-verification.d.ts.map +1 -1
  214. package/dist/release-verification.js +47 -4
  215. package/dist/release-verification.js.map +1 -1
  216. package/dist/review.d.ts.map +1 -1
  217. package/dist/review.js +12 -8
  218. package/dist/review.js.map +1 -1
  219. package/dist/route-types.d.ts +28 -0
  220. package/dist/route-types.d.ts.map +1 -0
  221. package/dist/route-types.js +106 -0
  222. package/dist/route-types.js.map +1 -0
  223. package/dist/rules/codes.d.ts +9 -0
  224. package/dist/rules/codes.d.ts.map +1 -1
  225. package/dist/rules/codes.js +9 -0
  226. package/dist/rules/codes.js.map +1 -1
  227. package/dist/rules/data-guard.d.ts +12 -0
  228. package/dist/rules/data-guard.d.ts.map +1 -0
  229. package/dist/rules/data-guard.js +213 -0
  230. package/dist/rules/data-guard.js.map +1 -0
  231. package/dist/rules/index.d.ts +3 -0
  232. package/dist/rules/index.d.ts.map +1 -1
  233. package/dist/rules/index.js +17 -5
  234. package/dist/rules/index.js.map +1 -1
  235. package/dist/rules/islands.d.ts.map +1 -1
  236. package/dist/rules/islands.js +7 -4
  237. package/dist/rules/islands.js.map +1 -1
  238. package/dist/rules/legacy.d.ts.map +1 -1
  239. package/dist/rules/legacy.js +71 -38
  240. package/dist/rules/legacy.js.map +1 -1
  241. package/dist/rules/nano.d.ts.map +1 -1
  242. package/dist/rules/nano.js +15 -6
  243. package/dist/rules/nano.js.map +1 -1
  244. package/dist/rules/pages.d.ts +15 -0
  245. package/dist/rules/pages.d.ts.map +1 -0
  246. package/dist/rules/pages.js +88 -0
  247. package/dist/rules/pages.js.map +1 -0
  248. package/dist/rules/routes.d.ts +11 -0
  249. package/dist/rules/routes.d.ts.map +1 -1
  250. package/dist/rules/routes.js +180 -2
  251. package/dist/rules/routes.js.map +1 -1
  252. package/dist/rules/secrets.d.ts +10 -0
  253. package/dist/rules/secrets.d.ts.map +1 -0
  254. package/dist/rules/secrets.js +169 -0
  255. package/dist/rules/secrets.js.map +1 -0
  256. package/dist/rules/security.d.ts +7 -0
  257. package/dist/rules/security.d.ts.map +1 -1
  258. package/dist/rules/security.js +171 -65
  259. package/dist/rules/security.js.map +1 -1
  260. package/dist/rules/typescript.d.ts +16 -0
  261. package/dist/rules/typescript.d.ts.map +1 -0
  262. package/dist/rules/typescript.js +59 -0
  263. package/dist/rules/typescript.js.map +1 -0
  264. package/dist/rules/zones.d.ts +17 -0
  265. package/dist/rules/zones.d.ts.map +1 -0
  266. package/dist/rules/zones.js +174 -0
  267. package/dist/rules/zones.js.map +1 -0
  268. package/dist/scaffold.d.ts +25 -12
  269. package/dist/scaffold.d.ts.map +1 -1
  270. package/dist/scaffold.js +132 -87
  271. package/dist/scaffold.js.map +1 -1
  272. package/dist/sdk.d.ts +17 -3
  273. package/dist/sdk.d.ts.map +1 -1
  274. package/dist/sdk.js +941 -153
  275. package/dist/sdk.js.map +1 -1
  276. package/dist/search-terms.d.ts +2 -0
  277. package/dist/search-terms.d.ts.map +1 -1
  278. package/dist/search-terms.js +23 -8
  279. package/dist/search-terms.js.map +1 -1
  280. package/dist/smoke.d.ts +79 -0
  281. package/dist/smoke.d.ts.map +1 -0
  282. package/dist/smoke.js +494 -0
  283. package/dist/smoke.js.map +1 -0
  284. package/dist/sync-manifest.d.ts.map +1 -1
  285. package/dist/sync-manifest.js +2 -1
  286. package/dist/sync-manifest.js.map +1 -1
  287. package/dist/sync-routes.d.ts +4 -3
  288. package/dist/sync-routes.d.ts.map +1 -1
  289. package/dist/sync-routes.js +4 -3
  290. package/dist/sync-routes.js.map +1 -1
  291. package/dist/test-tool.d.ts.map +1 -1
  292. package/dist/test-tool.js +30 -11
  293. package/dist/test-tool.js.map +1 -1
  294. package/dist/types-search.d.ts.map +1 -1
  295. package/dist/types-search.js +11 -5
  296. package/dist/types-search.js.map +1 -1
  297. package/dist/upgrade.d.ts +36 -6
  298. package/dist/upgrade.d.ts.map +1 -1
  299. package/dist/upgrade.js +167 -29
  300. package/dist/upgrade.js.map +1 -1
  301. package/dist/verification-plan.d.ts.map +1 -1
  302. package/dist/verification-plan.js +4 -0
  303. package/dist/verification-plan.js.map +1 -1
  304. package/dist/verification.d.ts.map +1 -1
  305. package/dist/verification.js +45 -12
  306. package/dist/verification.js.map +1 -1
  307. package/dist/web-app-options.d.ts +19 -0
  308. package/dist/web-app-options.d.ts.map +1 -0
  309. package/dist/web-app-options.js +49 -0
  310. package/dist/web-app-options.js.map +1 -0
  311. package/dist/work-graph.d.ts.map +1 -1
  312. package/dist/work-graph.js +29 -13
  313. package/dist/work-graph.js.map +1 -1
  314. package/docs/examples.json +185 -29
  315. package/docs/llms-full.txt +3807 -580
  316. package/docs/types.json +14229 -10939
  317. package/package.json +19 -12
  318. package/src/app-files.ts +40 -0
  319. package/src/assure-hydration.ts +139 -46
  320. package/src/assure.ts +17 -2
  321. package/src/capabilities-tool.ts +35 -9
  322. package/src/cdn-check.ts +268 -0
  323. package/src/check-diagnostics.ts +17 -0
  324. package/src/check-scan.ts +358 -63
  325. package/src/check.ts +97 -62
  326. package/src/cli.ts +296 -87
  327. package/src/command-catalog.ts +374 -38
  328. package/src/config-target.ts +47 -0
  329. package/src/contracts.ts +54 -12
  330. package/src/db-audit.ts +128 -0
  331. package/src/db-child.ts +285 -0
  332. package/src/db-config.ts +224 -0
  333. package/src/db-tool.ts +690 -0
  334. package/src/dev-bun-config.ts +116 -40
  335. package/src/dev-feed-tool.ts +912 -0
  336. package/src/dev-server-client.ts +267 -0
  337. package/src/diff-tool.ts +6 -5
  338. package/src/docs-search.ts +10 -3
  339. package/src/doctor.ts +98 -15
  340. package/src/env-file.ts +78 -1
  341. package/src/fix-recipes.ts +44 -31
  342. package/src/frontend-guidance.ts +5 -5
  343. package/src/i18n-check.ts +179 -0
  344. package/src/init-agents.ts +301 -38
  345. package/src/internal/code-unit-order.ts +5 -0
  346. package/src/internal/script-kind.ts +11 -0
  347. package/src/internal/source-facts.ts +96 -22
  348. package/src/internal/typescript-import.ts +663 -68
  349. package/src/introspect.ts +90 -27
  350. package/src/learn.ts +3 -3
  351. package/src/levels-tool.ts +47 -2
  352. package/src/load.ts +140 -33
  353. package/src/manifest-tool.ts +3 -2
  354. package/src/mcp-app-tool.ts +2 -0
  355. package/src/mcp-context.ts +1 -1
  356. package/src/mcp-delegate.ts +162 -0
  357. package/src/mcp-docs-tools.ts +23 -5
  358. package/src/mcp-exec.ts +160 -132
  359. package/src/mcp-http.ts +19 -5
  360. package/src/mcp-io.ts +57 -0
  361. package/src/mcp-isolate.ts +287 -0
  362. package/src/mcp-project-child.ts +182 -0
  363. package/src/mcp-render.ts +2 -2
  364. package/src/mcp-root.ts +117 -22
  365. package/src/mcp-run.ts +104 -10
  366. package/src/mcp-ws.ts +4 -1
  367. package/src/mcp.ts +106 -64
  368. package/src/migrate-layout.ts +1121 -0
  369. package/src/openapi-tool.ts +7 -3
  370. package/src/openapi-types.ts +315 -13
  371. package/src/pipeline-report.ts +22 -18
  372. package/src/port.ts +53 -30
  373. package/src/project-facts.ts +5 -0
  374. package/src/recipes/4.0.0.ts +35 -0
  375. package/src/recipes/index.ts +2 -0
  376. package/src/release-verification.ts +74 -5
  377. package/src/review.ts +13 -8
  378. package/src/route-types.ts +133 -0
  379. package/src/rules/codes.ts +9 -0
  380. package/src/rules/data-guard.ts +230 -0
  381. package/src/rules/index.ts +18 -5
  382. package/src/rules/islands.ts +12 -4
  383. package/src/rules/legacy.ts +71 -39
  384. package/src/rules/nano.ts +18 -6
  385. package/src/rules/pages.ts +99 -0
  386. package/src/rules/routes.ts +181 -2
  387. package/src/rules/secrets.ts +174 -0
  388. package/src/rules/security.ts +198 -70
  389. package/src/rules/typescript.ts +77 -0
  390. package/src/rules/zones.ts +195 -0
  391. package/src/scaffold.ts +147 -95
  392. package/src/sdk.ts +1215 -158
  393. package/src/search-terms.ts +24 -7
  394. package/src/smoke.ts +607 -0
  395. package/src/sync-manifest.ts +2 -1
  396. package/src/sync-routes.ts +4 -3
  397. package/src/test-tool.ts +32 -11
  398. package/src/types-search.ts +11 -4
  399. package/src/upgrade.ts +205 -25
  400. package/src/verification-plan.ts +13 -0
  401. package/src/verification.ts +50 -12
  402. package/src/web-app-options.ts +57 -0
  403. package/src/work-graph.ts +27 -13
@@ -13,16 +13,23 @@ import type {
13
13
  CapabilityExplainCommandResult,
14
14
  CapabilitySnapshotCommandResult,
15
15
  } from "./capabilities-tool.ts"
16
+ import { cdnCheckSpec } from "./cdn-check.ts"
16
17
  import { type CheckResult, renderCheckReport } from "./check.ts"
17
18
  import type { ContractsLock } from "./contracts.ts"
19
+ import { dbAuditSpec, dbQuerySpec, dbRoleSpec, dbSchemaSpec } from "./db-tool.ts"
20
+ import { errorsSpec, logsSpec } from "./dev-feed-tool.ts"
18
21
  import { type Diagnostic, diagnostic, normalizeSeverity, toSarifLog } from "./diagnostics.ts"
19
22
  import type { DoctorResult } from "./doctor.ts"
23
+ import { type I18nCheckOutput, i18nCheckPassed, renderI18nCheck } from "./i18n-check.ts"
20
24
  import type { VerificationLevelsResult } from "./levels-tool.ts"
21
25
  import type { LoadedApp } from "./load.ts"
22
26
  import type { ManifestEmitCommandResult } from "./manifest-tool.ts"
27
+ import type { LayoutMigrationResult } from "./migrate-layout.ts"
23
28
  import { collectPortResult, type PortResult, renderReport } from "./port.ts"
24
29
  import type { ReplayResult } from "./replay.ts"
25
30
  import { reviewSpec } from "./review.ts"
31
+ import type { RouteTypesReport } from "./route-types.ts"
32
+ import type { SmokeReport } from "./smoke.ts"
26
33
  import type { StylexMigrationResult } from "./stylex-migrate.ts"
27
34
  import {
28
35
  collectProjectWorkGraph,
@@ -79,6 +86,11 @@ export interface CommandSpec<Input, Output> {
79
86
  readonly transports: readonly CommandTransport[]
80
87
  readonly stability: CommandStability
81
88
  readonly argv?: CommandArgvBinding<Input>
89
+ /**
90
+ * Input fields only the CLI may set: MCP neither advertises nor accepts them. For a field that acts
91
+ * with the operator's own credentials, such as a signing key, which an agent must not invoke.
92
+ */
93
+ readonly cliOnlyFields?: readonly string[]
82
94
  readonly run: (input: Input, ctx: CommandCtx) => Promise<Output>
83
95
  readonly render: (out: Output, input?: Input) => readonly string[]
84
96
  readonly success?: (out: Output, input: Input) => boolean
@@ -95,6 +107,8 @@ export interface CommandCatalogEntry {
95
107
  readonly outputVersion: number
96
108
  readonly transports: readonly CommandTransport[]
97
109
  readonly stability: CommandStability
110
+ /** {@link CommandSpec.cliOnlyFields}: present in `inputSchema`, absent from the MCP tool's. */
111
+ readonly cliOnlyFields: readonly string[]
98
112
  }
99
113
 
100
114
  function freezeJson<T>(value: T): T {
@@ -116,6 +130,20 @@ export function toCommandCatalogEntry<Input, Output>(
116
130
  outputVersion: spec.output.version,
117
131
  transports: Object.freeze([...spec.transports]),
118
132
  stability: spec.stability,
133
+ cliOnlyFields: Object.freeze([...(spec.cliOnlyFields ?? [])]),
134
+ })
135
+ }
136
+
137
+ /** The input schema a command's MCP tool advertises: its catalog schema without CLI-only fields. */
138
+ export function commandMcpInputSchema(entry: CommandCatalogEntry): CommandJsonSchema {
139
+ const properties = entry.inputSchema.properties
140
+ if (entry.cliOnlyFields.length === 0 || typeof properties !== "object" || properties === null)
141
+ return entry.inputSchema
142
+ return freezeJson({
143
+ ...entry.inputSchema,
144
+ properties: Object.fromEntries(
145
+ Object.entries(properties).filter(([key]) => !entry.cliOnlyFields.includes(key)),
146
+ ),
119
147
  })
120
148
  }
121
149
 
@@ -324,14 +352,38 @@ interface FixInput {
324
352
  readonly dir?: string | undefined
325
353
  }
326
354
 
355
+ /** `migrate layout`, or `migrate --from tailwind --to stylex`; the parser admits only those two. */
327
356
  interface MigrateInput {
328
- readonly from: "tailwind"
329
- readonly to: "stylex"
357
+ readonly kind?: "layout" | undefined
358
+ readonly from?: "tailwind" | undefined
359
+ readonly to?: "stylex" | undefined
330
360
  readonly write?: boolean | undefined
331
361
  readonly json?: boolean | undefined
332
362
  readonly dir?: string | undefined
333
363
  }
334
364
 
365
+ interface TargetInput {
366
+ readonly target?: string | undefined
367
+ readonly json?: boolean | undefined
368
+ readonly dir?: string | undefined
369
+ }
370
+
371
+ interface TargetOutput {
372
+ readonly ok: boolean
373
+ readonly target: string
374
+ /** The target before this run, when it switched one; `undefined` when none was declared. */
375
+ readonly previous?: string | undefined
376
+ readonly changed: boolean
377
+ /** What `nifra build` now emits, and how it deploys. */
378
+ readonly run: string
379
+ }
380
+
381
+ interface TypesInput {
382
+ readonly check?: boolean | undefined
383
+ readonly json?: boolean | undefined
384
+ readonly dir?: string | undefined
385
+ }
386
+
335
387
  interface SnapshotInput {
336
388
  readonly out?: string | undefined
337
389
  readonly json?: boolean | undefined
@@ -376,6 +428,21 @@ interface PortInput {
376
428
  readonly dir?: string | undefined
377
429
  }
378
430
 
431
+ interface SmokeInput {
432
+ readonly fixture?: string | undefined
433
+ readonly inProcess?: boolean | undefined
434
+ readonly json?: boolean | undefined
435
+ readonly dir?: string | undefined
436
+ }
437
+
438
+ interface I18nInput {
439
+ readonly action: "check"
440
+ readonly entry?: string | undefined
441
+ readonly json?: boolean | undefined
442
+ readonly strict?: boolean | undefined
443
+ readonly dir?: string | undefined
444
+ }
445
+
379
446
  interface CheckCommandOutput extends CheckResult {}
380
447
  interface AssureCommandOutput {
381
448
  readonly report?: unknown
@@ -406,7 +473,9 @@ interface FixCommandOutput {
406
473
  readonly failed: readonly { readonly code: string; readonly reason: string }[]
407
474
  readonly diagnostics: readonly unknown[]
408
475
  }
409
- interface MigrateCommandOutput extends StylexMigrationResult {}
476
+ type MigrateCommandOutput =
477
+ | (StylexMigrationResult & { readonly kind?: undefined })
478
+ | (LayoutMigrationResult & { readonly kind: "layout" })
410
479
  interface SnapshotCommandOutput {
411
480
  readonly ok: true
412
481
  readonly file: string
@@ -430,6 +499,8 @@ interface SyncCommandOutput {
430
499
  readonly results: readonly unknown[]
431
500
  }
432
501
 
502
+ interface SmokeCommandOutput extends SmokeReport {}
503
+
433
504
  const CHECK_SCHEMA = input<CheckInput>(
434
505
  objectSchema({
435
506
  lintsOnly: { type: "boolean" },
@@ -648,26 +719,53 @@ const FIX_SCHEMA = input<FixInput>(
648
719
  )
649
720
 
650
721
  const MIGRATE_SCHEMA = input<MigrateInput>(
651
- objectSchema(
652
- {
653
- from: { type: "string", enum: ["tailwind"] },
654
- to: { type: "string", enum: ["stylex"] },
655
- write: { type: "boolean" },
656
- json: { type: "boolean" },
657
- dir: { type: "string" },
658
- },
659
- ["from", "to"],
660
- ),
722
+ objectSchema({
723
+ kind: { type: "string", enum: ["layout"] },
724
+ from: { type: "string", enum: ["tailwind"] },
725
+ to: { type: "string", enum: ["stylex"] },
726
+ write: { type: "boolean" },
727
+ json: { type: "boolean" },
728
+ dir: { type: "string" },
729
+ }),
661
730
  (value) => {
662
731
  const raw = withDir(record(value))
732
+ const flags = {
733
+ ...parseBooleanFlags(raw, ["write", "json"]),
734
+ ...(raw.dir === undefined ? {} : { dir: raw.dir }),
735
+ }
736
+ const kind = optionalString(raw.kind, "kind")
737
+ if (kind !== undefined) {
738
+ if (kind !== "layout")
739
+ throw new TypeError("the migration must be `layout` (or --from tailwind --to stylex)")
740
+ return { kind, ...flags }
741
+ }
663
742
  const from = optionalString(raw.from, "from")
664
743
  const to = optionalString(raw.to, "to")
665
744
  if (from !== "tailwind") throw new TypeError("from must be tailwind")
666
745
  if (to !== "stylex") throw new TypeError("to must be stylex")
746
+ return { from, to, ...flags }
747
+ },
748
+ )
749
+
750
+ const TARGET_SCHEMA = input<TargetInput>(
751
+ objectSchema({ target: { type: "string" }, json: { type: "boolean" }, dir: { type: "string" } }),
752
+ (value) => {
753
+ const raw = withDir(record(value))
754
+ const target = optionalString(raw.target, "target")
667
755
  return {
668
- from,
669
- to,
670
- ...parseBooleanFlags(raw, ["write", "json"]),
756
+ ...parseBooleanFlags(raw, ["json"]),
757
+ ...(raw.dir === undefined ? {} : { dir: raw.dir }),
758
+ ...(target === undefined ? {} : { target }),
759
+ }
760
+ },
761
+ )
762
+
763
+ const TYPES_SCHEMA = input<TypesInput>(
764
+ objectSchema({ check: { type: "boolean" }, json: { type: "boolean" }, dir: { type: "string" } }),
765
+ (value) => {
766
+ const raw = withDir(record(value))
767
+ return {
768
+ ...parseBooleanFlags(raw, ["check", "json"]),
671
769
  ...(raw.dir === undefined ? {} : { dir: raw.dir }),
672
770
  }
673
771
  },
@@ -790,6 +888,52 @@ const PORT_SCHEMA = input<PortInput>(
790
888
  },
791
889
  )
792
890
 
891
+ const SMOKE_SCHEMA = input<SmokeInput>(
892
+ objectSchema({
893
+ fixture: { type: "string" },
894
+ inProcess: { type: "boolean" },
895
+ json: { type: "boolean" },
896
+ dir: { type: "string" },
897
+ }),
898
+ (value) => {
899
+ const raw = withDir(record(value))
900
+ return {
901
+ fixture: optionalString(raw.fixture, "fixture"),
902
+ ...parseBooleanFlags(raw, ["inProcess", "json"]),
903
+ ...(raw.dir === undefined ? {} : { dir: raw.dir }),
904
+ }
905
+ },
906
+ )
907
+
908
+ const I18N_SCHEMA = input<I18nInput>(
909
+ objectSchema(
910
+ {
911
+ action: { type: "string", enum: ["check"] },
912
+ entry: { type: "string" },
913
+ json: { type: "boolean" },
914
+ strict: { type: "boolean" },
915
+ dir: { type: "string" },
916
+ },
917
+ ["action"],
918
+ ),
919
+ (value) => {
920
+ const raw = withDir(record(value))
921
+ if (raw.action !== "check") {
922
+ throw new TypeError(
923
+ raw.action === undefined
924
+ ? "i18n needs an action: nifra i18n check [entry]"
925
+ : `unknown i18n action: ${String(raw.action)} (the one action is check)`,
926
+ )
927
+ }
928
+ return {
929
+ action: "check",
930
+ entry: optionalString(raw.entry, "entry"),
931
+ ...parseBooleanFlags(raw, ["json", "strict"]),
932
+ ...(raw.dir === undefined ? {} : { dir: raw.dir }),
933
+ }
934
+ },
935
+ )
936
+
793
937
  function loadAppFor(ctx: CommandCtx): Promise<LoadedApp> {
794
938
  if (ctx.loadApp !== undefined) return ctx.loadApp()
795
939
  return import("./load.ts").then(({ loadApp }) => loadApp(ctx.cwd))
@@ -995,6 +1139,8 @@ const manifestSpec: CommandSpec<ManifestInput, ManifestEmitCommandResult | DiffC
995
1139
  output: output({ type: "object" }),
996
1140
  transports: ["cli", "mcp"],
997
1141
  stability: "stable",
1142
+ // The signer is the operator's KMS/HSM callback; an agent signing its own manifest defeats it.
1143
+ cliOnlyFields: ["sign"],
998
1144
  argv: {
999
1145
  positionals: ["action", "before", "after"],
1000
1146
  flags: [
@@ -1136,7 +1282,10 @@ const openApiSpec: CommandSpec<OpenApiInput, OpenApiCommandOutput> = {
1136
1282
  ],
1137
1283
  },
1138
1284
  async run(value, ctx) {
1139
- const app = await loadAppFor(ctx)
1285
+ const app =
1286
+ ctx.loadApp !== undefined
1287
+ ? await ctx.loadApp()
1288
+ : await import("./load.ts").then(({ loadBackendApp }) => loadBackendApp(ctx.cwd))
1140
1289
  const { renderOpenApiWithTypes } = await import("./openapi-tool.ts")
1141
1290
  const format = value.format ?? "json"
1142
1291
  return {
@@ -1252,12 +1401,14 @@ const fixSpec: CommandSpec<FixInput, FixCommandOutput> = {
1252
1401
 
1253
1402
  const migrateSpec: CommandSpec<MigrateInput, MigrateCommandOutput> = {
1254
1403
  name: "migrate",
1255
- summary: "Migrate safe static Tailwind className utilities to native StyleX props.",
1404
+ summary:
1405
+ "Move an app onto the frontend/backend split (`migrate layout`), or migrate static Tailwind utilities to StyleX.",
1256
1406
  input: MIGRATE_SCHEMA,
1257
1407
  output: output({
1258
1408
  type: "object",
1259
1409
  properties: {
1260
1410
  ok: { type: "boolean" },
1411
+ kind: { type: "string" },
1261
1412
  from: { type: "string" },
1262
1413
  to: { type: "string" },
1263
1414
  write: { type: "boolean" },
@@ -1266,12 +1417,16 @@ const migrateSpec: CommandSpec<MigrateInput, MigrateCommandOutput> = {
1266
1417
  written: { type: "array" },
1267
1418
  issues: { type: "array" },
1268
1419
  files: { type: "array" },
1420
+ splits: { type: "array" },
1421
+ moves: { type: "array" },
1422
+ rewritten: { type: "array" },
1269
1423
  },
1270
- required: ["ok", "from", "to", "write", "scanned", "changed", "written", "issues", "files"],
1424
+ required: ["ok", "write", "issues"],
1271
1425
  }),
1272
1426
  transports: ["cli"],
1273
1427
  stability: "stable",
1274
1428
  argv: {
1429
+ positionals: ["kind"],
1275
1430
  flags: [
1276
1431
  { name: "from", field: "from", type: "string" },
1277
1432
  { name: "to", field: "to", type: "string" },
@@ -1281,27 +1436,142 @@ const migrateSpec: CommandSpec<MigrateInput, MigrateCommandOutput> = {
1281
1436
  ],
1282
1437
  },
1283
1438
  async run(value, ctx) {
1439
+ const dir = resolve(ctx.cwd, value.dir ?? ".")
1440
+ const write = value.write === undefined ? {} : { write: value.write }
1441
+ if (value.kind === "layout") {
1442
+ const { migrateLayout } = await import("./migrate-layout.ts")
1443
+ return { kind: "layout" as const, ...(await migrateLayout(dir, write)) }
1444
+ }
1284
1445
  const { migrateTailwindToStylex } = await import("./stylex-migrate.ts")
1285
- return migrateTailwindToStylex(resolve(ctx.cwd, value.dir ?? "."), {
1286
- ...(value.write === undefined ? {} : { write: value.write }),
1287
- })
1446
+ return migrateTailwindToStylex(dir, write)
1288
1447
  },
1289
- render: (out) => [
1290
- out.ok
1291
- ? `✓ migrated ${out.changed.length} file${out.changed.length === 1 ? "" : "s"}`
1292
- : `⚠ migrated ${out.changed.length} safe file${out.changed.length === 1 ? "" : "s"}; ${out.issues.length} manual issue${out.issues.length === 1 ? "" : "s"} remain`,
1293
- ` scanned ${out.scanned} source file${out.scanned === 1 ? "" : "s"}`,
1294
- ...(out.write
1295
- ? [` wrote ${out.written.length} file${out.written.length === 1 ? "" : "s"}`]
1296
- : out.changed.length > 0
1297
- ? [" dry run: pass --write to apply the safe changes"]
1448
+ render: (out) => {
1449
+ if (out.kind === "layout") {
1450
+ return [
1451
+ out.ok
1452
+ ? `✓ ${out.splits.length} route file${out.splits.length === 1 ? "" : "s"} split, ${out.moves.length} file${out.moves.length === 1 ? "" : "s"} moved`
1453
+ : `⚠ ${out.issues.length} issue${out.issues.length === 1 ? "" : "s"} need a decision; everything else is planned`,
1454
+ ...out.splits.map(
1455
+ (split) => ` split ${split.file} → ${split.backend} (${split.moved.join(", ")})`,
1456
+ ),
1457
+ ...out.moves.map((move) => ` move ${move.from} → ${move.to}`),
1458
+ ...out.rewritten.map((file) => ` edit ${file} (imports)`),
1459
+ ...out.issues.map((issue) => ` ⚠ ${issue.file}: ${issue.reason}`),
1460
+ ...(out.write ? [] : [" dry run: pass --write to apply"]),
1461
+ ]
1462
+ }
1463
+ return [
1464
+ out.ok
1465
+ ? `✓ migrated ${out.changed.length} file${out.changed.length === 1 ? "" : "s"}`
1466
+ : `⚠ migrated ${out.changed.length} safe file${out.changed.length === 1 ? "" : "s"}; ${out.issues.length} manual issue${out.issues.length === 1 ? "" : "s"} remain`,
1467
+ ` scanned ${out.scanned} source file${out.scanned === 1 ? "" : "s"}`,
1468
+ ...(out.write
1469
+ ? [` wrote ${out.written.length} file${out.written.length === 1 ? "" : "s"}`]
1470
+ : out.changed.length > 0
1471
+ ? [" dry run: pass --write to apply the safe changes"]
1472
+ : []),
1473
+ ...out.issues
1474
+ .slice(0, 30)
1475
+ .map((issue) => ` ${issue.file}:${issue.line} ${issue.token} — ${issue.reason}`),
1476
+ ...(out.issues.length > 30
1477
+ ? [` … ${out.issues.length - 30} more issue${out.issues.length - 30 === 1 ? "" : "s"}`]
1298
1478
  : []),
1299
- ...out.issues
1300
- .slice(0, 30)
1301
- .map((issue) => ` ${issue.file}:${issue.line} ${issue.token} — ${issue.reason}`),
1302
- ...(out.issues.length > 30
1303
- ? [` … ${out.issues.length - 30} more issue${out.issues.length - 30 === 1 ? "" : "s"}`]
1304
- : []),
1479
+ ]
1480
+ },
1481
+ success: (out) => out.ok,
1482
+ }
1483
+
1484
+ const targetSpec: CommandSpec<TargetInput, TargetOutput> = {
1485
+ name: "target",
1486
+ summary:
1487
+ "Show the app's deploy target, or switch it (`nifra target cloudflare`): the `target` `nifra build` emits, kept in nifra.config.ts.",
1488
+ input: TARGET_SCHEMA,
1489
+ output: output({
1490
+ type: "object",
1491
+ properties: {
1492
+ ok: { type: "boolean" },
1493
+ target: { type: "string" },
1494
+ previous: { type: "string" },
1495
+ changed: { type: "boolean" },
1496
+ run: { type: "string" },
1497
+ },
1498
+ required: ["ok", "target", "changed", "run"],
1499
+ }),
1500
+ transports: ["cli"],
1501
+ stability: "stable",
1502
+ argv: {
1503
+ positionals: ["target"],
1504
+ flags: [
1505
+ { name: "json", field: "json", type: "boolean" },
1506
+ { name: "dir", field: "dir", type: "string" },
1507
+ ],
1508
+ },
1509
+ async run(value, ctx) {
1510
+ const dir = resolve(ctx.cwd, value.dir ?? ".")
1511
+ const { parseBuildTarget, planBuildTarget } = await import("@nifrajs/web/build")
1512
+ const { readConfigTarget, writeConfigTarget } = await import("./config-target.ts")
1513
+ const current = readConfigTarget(dir)
1514
+ const target = parseBuildTarget(
1515
+ value.target ?? current ?? "bun",
1516
+ value.target === undefined ? "target in nifra.config.ts" : "target",
1517
+ )
1518
+ const changed = value.target !== undefined && value.target !== current
1519
+ if (changed) writeConfigTarget(dir, target)
1520
+ return {
1521
+ ok: true,
1522
+ target,
1523
+ ...(changed && current !== undefined ? { previous: current } : {}),
1524
+ changed,
1525
+ run: planBuildTarget(target, "dist").run,
1526
+ }
1527
+ },
1528
+ render: (out) => [
1529
+ out.changed
1530
+ ? `✓ target: ${out.target}${out.previous === undefined ? "" : ` (was ${out.previous})`}`
1531
+ : `target: ${out.target}`,
1532
+ ` nifra build → ${out.run}`,
1533
+ ],
1534
+ success: (out) => out.ok,
1535
+ }
1536
+
1537
+ const typesSpec: CommandSpec<TypesInput, RouteTypesReport> = {
1538
+ name: "types",
1539
+ summary:
1540
+ "Generate each route's `./+types` module (params, schema-typed data, typed `api`); `--check` fails when one is stale.",
1541
+ input: TYPES_SCHEMA,
1542
+ output: output({
1543
+ type: "object",
1544
+ properties: {
1545
+ ok: { type: "boolean" },
1546
+ check: { type: "boolean" },
1547
+ written: { type: "array" },
1548
+ removed: { type: "array" },
1549
+ stale: { type: "array" },
1550
+ tsconfig: { type: "string" },
1551
+ },
1552
+ required: ["ok", "check", "written", "removed", "stale"],
1553
+ }),
1554
+ transports: ["cli"],
1555
+ stability: "stable",
1556
+ argv: {
1557
+ flags: [
1558
+ { name: "check", field: "check", type: "boolean" },
1559
+ { name: "json", field: "json", type: "boolean" },
1560
+ { name: "dir", field: "dir", type: "string" },
1561
+ ],
1562
+ },
1563
+ async run(value, ctx) {
1564
+ const { routeTypes } = await import("./route-types.ts")
1565
+ return routeTypes(resolve(ctx.cwd, value.dir ?? "."), { check: value.check === true })
1566
+ },
1567
+ render: (out) => [
1568
+ out.check
1569
+ ? out.ok
1570
+ ? "✓ route types are up to date"
1571
+ : `✖ ${out.stale.length} route type file${out.stale.length === 1 ? " is" : "s are"} stale - run \`nifra types\``
1572
+ : `✓ route types: ${out.written.length} written, ${out.removed.length} removed`,
1573
+ ...out.stale.map((file) => ` stale ${file}`),
1574
+ ...(out.tsconfig === undefined ? [] : [` ⚠ ${out.tsconfig}`]),
1305
1575
  ],
1306
1576
  success: (out) => out.ok,
1307
1577
  }
@@ -1493,6 +1763,34 @@ const replaySpec: CommandSpec<ReplayInput, ReplayResult> = {
1493
1763
  success: (out) => out.ok,
1494
1764
  }
1495
1765
 
1766
+ const smokeSpec: CommandSpec<SmokeInput, SmokeCommandOutput> = {
1767
+ name: "smoke",
1768
+ summary:
1769
+ "Run the declared production SSR, mounted-API, 404, header, auth, contract, and hydration smoke checks.",
1770
+ input: SMOKE_SCHEMA,
1771
+ output: output({ type: "object" }),
1772
+ transports: ["cli", "mcp"],
1773
+ stability: "stable",
1774
+ argv: {
1775
+ flags: [
1776
+ { name: "fixture", field: "fixture", type: "string" },
1777
+ { name: "in-process", field: "inProcess", type: "boolean" },
1778
+ { name: "json", field: "json", type: "boolean" },
1779
+ ],
1780
+ },
1781
+ async run(value, ctx) {
1782
+ const { loadSmokeFixture, runSmoke } = await import("./smoke.ts")
1783
+ const fixture = await loadSmokeFixture(ctx.cwd, value.fixture)
1784
+ return runSmoke(fixture, { inProcess: value.inProcess === true })
1785
+ },
1786
+ render: (out) =>
1787
+ out.checks.map(
1788
+ (check) =>
1789
+ `${check.status === "pass" ? "✓" : check.status === "skip" ? "•" : "✖"} ${check.id}${check.message === undefined ? "" : ` - ${check.message}`}`,
1790
+ ),
1791
+ success: (out) => out.ok,
1792
+ }
1793
+
1496
1794
  const portSpec: CommandSpec<PortInput, PortResult> = {
1497
1795
  name: "port",
1498
1796
  summary:
@@ -1522,7 +1820,7 @@ const portSpec: CommandSpec<PortInput, PortResult> = {
1522
1820
  return [
1523
1821
  report,
1524
1822
  "",
1525
- "[nifra] --ci needs a deploy target to gate against, and none was detected. Pass --target <bun|node|deno|cf-pages|vercel>.",
1823
+ "[nifra] --ci needs a deploy target to gate against, and none was detected. Pass --target <bun|node|deno|cloudflare|vercel>.",
1526
1824
  ]
1527
1825
  return [report]
1528
1826
  },
@@ -1535,6 +1833,33 @@ const portSpec: CommandSpec<PortInput, PortResult> = {
1535
1833
  json: (out) => out.json,
1536
1834
  }
1537
1835
 
1836
+ // CLI only: it imports app code (the catalogs), which a long-lived MCP server process would cache.
1837
+ const i18nSpec: CommandSpec<I18nInput, I18nCheckOutput> = {
1838
+ name: "i18n",
1839
+ summary:
1840
+ "Check i18n catalogs (imports the module exporting `locales` and `catalogs`): coverage, missing and unused keys, ICU syntax, placeholder and tag parity, plural cases, script purity, untranslated messages.",
1841
+ input: I18N_SCHEMA,
1842
+ output: output({ type: "object" }),
1843
+ transports: ["cli"],
1844
+ stability: "stable",
1845
+ argv: {
1846
+ positionals: ["action", "entry"],
1847
+ flags: [
1848
+ { name: "json", field: "json", type: "boolean" },
1849
+ { name: "strict", field: "strict", type: "boolean" },
1850
+ ],
1851
+ },
1852
+ async run(value, ctx) {
1853
+ const { runI18nCheck } = await import("./i18n-check.ts")
1854
+ return runI18nCheck(value.dir === undefined ? ctx.cwd : resolve(ctx.cwd, value.dir), {
1855
+ entry: value.entry,
1856
+ })
1857
+ },
1858
+ render: (out) => renderI18nCheck(out),
1859
+ success: (out, input) => i18nCheckPassed(out, input.strict === true),
1860
+ json: (out) => out,
1861
+ }
1862
+
1538
1863
  export const commandSpecs = Object.freeze([
1539
1864
  checkSpec,
1540
1865
  reviewSpec,
@@ -1548,6 +1873,8 @@ export const commandSpecs = Object.freeze([
1548
1873
  doctorSpec,
1549
1874
  fixSpec,
1550
1875
  migrateSpec,
1876
+ targetSpec,
1877
+ typesSpec,
1551
1878
  snapshotSpec,
1552
1879
  diffSpec,
1553
1880
  contractsSpec,
@@ -1555,7 +1882,16 @@ export const commandSpecs = Object.freeze([
1555
1882
  syncRoutesSpec,
1556
1883
  proveSpec,
1557
1884
  replaySpec,
1885
+ smokeSpec,
1558
1886
  portSpec,
1887
+ i18nSpec,
1888
+ errorsSpec,
1889
+ logsSpec,
1890
+ cdnCheckSpec,
1891
+ dbSchemaSpec,
1892
+ dbQuerySpec,
1893
+ dbRoleSpec,
1894
+ dbAuditSpec,
1559
1895
  ] as const)
1560
1896
 
1561
1897
  const commandByName = new Map(commandSpecs.map((spec) => [spec.name, spec]))
@@ -0,0 +1,47 @@
1
+ /**
2
+ * The app's deploy target as `nifra.config.ts` declares it (`export const target = "bun"`), read and
3
+ * rewritten as text: `nifra port` and `nifra doctor` never run app code, and `nifra target <t>` edits
4
+ * one line of a file the developer owns.
5
+ */
6
+ import { existsSync, readFileSync, writeFileSync } from "node:fs"
7
+ import { join } from "node:path"
8
+ import { CONFIG_FILE } from "./app-files.ts"
9
+ import { stripComments } from "./check-scan.ts"
10
+
11
+ const TARGET_EXPORT = /\bexport\s+const\s+target\b[^=\n]*=\s*(["'])([^"'\n]*)\1/
12
+
13
+ /** The `target` string `nifra.config.ts` exports, or `undefined` when it exports none. */
14
+ export function readConfigTarget(appRoot: string): string | undefined {
15
+ const path = join(appRoot, CONFIG_FILE)
16
+ if (!existsSync(path)) return undefined
17
+ return TARGET_EXPORT.exec(stripComments(readFileSync(path, "utf8")))?.[2]
18
+ }
19
+
20
+ /**
21
+ * Point `nifra.config.ts` at `target`: rewrite its `target` export, or append one. Returns the previous
22
+ * value. Throws when the app has no `nifra.config.ts`.
23
+ */
24
+ export function writeConfigTarget(appRoot: string, target: string): string | undefined {
25
+ const path = join(appRoot, CONFIG_FILE)
26
+ if (!existsSync(path)) {
27
+ throw new Error(
28
+ `[nifra] no ${CONFIG_FILE} in ${appRoot} - \`nifra target\` sets its \`target\``,
29
+ )
30
+ }
31
+ const text = readFileSync(path, "utf8")
32
+ // Matched on the comment-blanked text, whose offsets are the original's, so a commented-out
33
+ // `target` line is never the one rewritten.
34
+ const found = TARGET_EXPORT.exec(stripComments(text))
35
+ const previous = found?.[2]
36
+ let next: string
37
+ if (found === null) {
38
+ next = `${text.replace(/\n*$/, "\n")}\n// The deploy target \`nifra build\` emits; \`nifra target <t>\` switches it.\nexport const target = "${target}"\n`
39
+ } else {
40
+ // Only the quoted value changes, so a type annotation on the export survives.
41
+ const end = found.index + found[0].length
42
+ const start = end - (previous ?? "").length - 2
43
+ next = `${text.slice(0, start)}"${target}"${text.slice(end)}`
44
+ }
45
+ writeFileSync(path, next)
46
+ return previous
47
+ }