@nifrajs/cli 3.3.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 (425) 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 +7 -15
  22. package/dist/check-diagnostics.d.ts.map +1 -1
  23. package/dist/check-diagnostics.js +162 -713
  24. package/dist/check-diagnostics.js.map +1 -1
  25. package/dist/check-scan.d.ts +58 -7
  26. package/dist/check-scan.d.ts.map +1 -1
  27. package/dist/check-scan.js +323 -62
  28. package/dist/check-scan.js.map +1 -1
  29. package/dist/check.d.ts +1 -0
  30. package/dist/check.d.ts.map +1 -1
  31. package/dist/check.js +145 -54
  32. package/dist/check.js.map +1 -1
  33. package/dist/cli.d.ts +42 -16
  34. package/dist/cli.d.ts.map +1 -1
  35. package/dist/cli.js +306 -87
  36. package/dist/cli.js.map +1 -1
  37. package/dist/command-catalog.d.ts +60 -5
  38. package/dist/command-catalog.d.ts.map +1 -1
  39. package/dist/command-catalog.js +298 -33
  40. package/dist/command-catalog.js.map +1 -1
  41. package/dist/config-target.d.ts +8 -0
  42. package/dist/config-target.d.ts.map +1 -0
  43. package/dist/config-target.js +45 -0
  44. package/dist/config-target.js.map +1 -0
  45. package/dist/contracts.d.ts +3 -2
  46. package/dist/contracts.d.ts.map +1 -1
  47. package/dist/contracts.js +56 -16
  48. package/dist/contracts.js.map +1 -1
  49. package/dist/db-audit.d.ts +37 -0
  50. package/dist/db-audit.d.ts.map +1 -0
  51. package/dist/db-audit.js +99 -0
  52. package/dist/db-audit.js.map +1 -0
  53. package/dist/db-child.d.ts +64 -0
  54. package/dist/db-child.d.ts.map +1 -0
  55. package/dist/db-child.js +213 -0
  56. package/dist/db-child.js.map +1 -0
  57. package/dist/db-config.d.ts +60 -0
  58. package/dist/db-config.d.ts.map +1 -0
  59. package/dist/db-config.js +176 -0
  60. package/dist/db-config.js.map +1 -0
  61. package/dist/db-tool.d.ts +84 -0
  62. package/dist/db-tool.d.ts.map +1 -0
  63. package/dist/db-tool.js +537 -0
  64. package/dist/db-tool.js.map +1 -0
  65. package/dist/dev-bun-config.d.ts +20 -13
  66. package/dist/dev-bun-config.d.ts.map +1 -1
  67. package/dist/dev-bun-config.js +98 -41
  68. package/dist/dev-bun-config.js.map +1 -1
  69. package/dist/dev-feed-tool.d.ts +106 -0
  70. package/dist/dev-feed-tool.d.ts.map +1 -0
  71. package/dist/dev-feed-tool.js +713 -0
  72. package/dist/dev-feed-tool.js.map +1 -0
  73. package/dist/dev-server-client.d.ts +67 -0
  74. package/dist/dev-server-client.d.ts.map +1 -0
  75. package/dist/dev-server-client.js +195 -0
  76. package/dist/dev-server-client.js.map +1 -0
  77. package/dist/diagnostics.d.ts +86 -0
  78. package/dist/diagnostics.d.ts.map +1 -1
  79. package/dist/diagnostics.js +85 -0
  80. package/dist/diagnostics.js.map +1 -1
  81. package/dist/diff-tool.d.ts +3 -3
  82. package/dist/diff-tool.d.ts.map +1 -1
  83. package/dist/diff-tool.js +6 -5
  84. package/dist/diff-tool.js.map +1 -1
  85. package/dist/docs-search.d.ts.map +1 -1
  86. package/dist/docs-search.js +12 -4
  87. package/dist/docs-search.js.map +1 -1
  88. package/dist/doctor.d.ts +17 -0
  89. package/dist/doctor.d.ts.map +1 -1
  90. package/dist/doctor.js +76 -15
  91. package/dist/doctor.js.map +1 -1
  92. package/dist/env-file.d.ts +15 -0
  93. package/dist/env-file.d.ts.map +1 -1
  94. package/dist/env-file.js +73 -1
  95. package/dist/env-file.js.map +1 -1
  96. package/dist/fix-recipes.d.ts.map +1 -1
  97. package/dist/fix-recipes.js +41 -21
  98. package/dist/fix-recipes.js.map +1 -1
  99. package/dist/frontend-guidance.js +5 -5
  100. package/dist/frontend-guidance.js.map +1 -1
  101. package/dist/i18n-check.d.ts +16 -0
  102. package/dist/i18n-check.d.ts.map +1 -0
  103. package/dist/i18n-check.js +154 -0
  104. package/dist/i18n-check.js.map +1 -0
  105. package/dist/init-agents.d.ts +49 -13
  106. package/dist/init-agents.d.ts.map +1 -1
  107. package/dist/init-agents.js +239 -34
  108. package/dist/init-agents.js.map +1 -1
  109. package/dist/internal/code-unit-order.d.ts +4 -0
  110. package/dist/internal/code-unit-order.d.ts.map +1 -0
  111. package/dist/internal/code-unit-order.js +6 -0
  112. package/dist/internal/code-unit-order.js.map +1 -0
  113. package/dist/internal/script-kind.d.ts +7 -0
  114. package/dist/internal/script-kind.d.ts.map +1 -0
  115. package/dist/internal/script-kind.js +12 -0
  116. package/dist/internal/script-kind.js.map +1 -0
  117. package/dist/internal/source-facts.d.ts +5 -0
  118. package/dist/internal/source-facts.d.ts.map +1 -1
  119. package/dist/internal/source-facts.js +61 -5
  120. package/dist/internal/source-facts.js.map +1 -1
  121. package/dist/internal/typescript-import.d.ts +49 -24
  122. package/dist/internal/typescript-import.d.ts.map +1 -1
  123. package/dist/internal/typescript-import.js +507 -67
  124. package/dist/internal/typescript-import.js.map +1 -1
  125. package/dist/introspect.d.ts +6 -4
  126. package/dist/introspect.d.ts.map +1 -1
  127. package/dist/introspect.js +93 -74
  128. package/dist/introspect.js.map +1 -1
  129. package/dist/learn.js +3 -3
  130. package/dist/learn.js.map +1 -1
  131. package/dist/levels-tool.d.ts +10 -0
  132. package/dist/levels-tool.d.ts.map +1 -1
  133. package/dist/levels-tool.js +33 -3
  134. package/dist/levels-tool.js.map +1 -1
  135. package/dist/load.d.ts +71 -16
  136. package/dist/load.d.ts.map +1 -1
  137. package/dist/load.js +78 -18
  138. package/dist/load.js.map +1 -1
  139. package/dist/manifest-tool.d.ts.map +1 -1
  140. package/dist/manifest-tool.js +3 -2
  141. package/dist/manifest-tool.js.map +1 -1
  142. package/dist/mcp-app-tool.d.ts.map +1 -1
  143. package/dist/mcp-app-tool.js +2 -0
  144. package/dist/mcp-app-tool.js.map +1 -1
  145. package/dist/mcp-context.d.ts.map +1 -1
  146. package/dist/mcp-context.js +5 -6
  147. package/dist/mcp-context.js.map +1 -1
  148. package/dist/mcp-delegate.d.ts +57 -0
  149. package/dist/mcp-delegate.d.ts.map +1 -0
  150. package/dist/mcp-delegate.js +130 -0
  151. package/dist/mcp-delegate.js.map +1 -0
  152. package/dist/mcp-docs-tools.d.ts.map +1 -1
  153. package/dist/mcp-docs-tools.js +23 -5
  154. package/dist/mcp-docs-tools.js.map +1 -1
  155. package/dist/mcp-exec.d.ts +3 -0
  156. package/dist/mcp-exec.d.ts.map +1 -1
  157. package/dist/mcp-exec.js +263 -131
  158. package/dist/mcp-exec.js.map +1 -1
  159. package/dist/mcp-http.d.ts.map +1 -1
  160. package/dist/mcp-http.js +20 -5
  161. package/dist/mcp-http.js.map +1 -1
  162. package/dist/mcp-io.d.ts +26 -0
  163. package/dist/mcp-io.d.ts.map +1 -1
  164. package/dist/mcp-io.js +113 -0
  165. package/dist/mcp-io.js.map +1 -1
  166. package/dist/mcp-isolate.d.ts +48 -0
  167. package/dist/mcp-isolate.d.ts.map +1 -0
  168. package/dist/mcp-isolate.js +222 -0
  169. package/dist/mcp-isolate.js.map +1 -0
  170. package/dist/mcp-path.d.ts +10 -0
  171. package/dist/mcp-path.d.ts.map +1 -0
  172. package/dist/mcp-path.js +51 -0
  173. package/dist/mcp-path.js.map +1 -0
  174. package/dist/mcp-project-child.d.ts +74 -0
  175. package/dist/mcp-project-child.d.ts.map +1 -0
  176. package/dist/mcp-project-child.js +130 -0
  177. package/dist/mcp-project-child.js.map +1 -0
  178. package/dist/mcp-reflect.d.ts.map +1 -1
  179. package/dist/mcp-reflect.js +2 -2
  180. package/dist/mcp-reflect.js.map +1 -1
  181. package/dist/mcp-render.d.ts.map +1 -1
  182. package/dist/mcp-render.js +64 -45
  183. package/dist/mcp-render.js.map +1 -1
  184. package/dist/mcp-root.d.ts +37 -9
  185. package/dist/mcp-root.d.ts.map +1 -1
  186. package/dist/mcp-root.js +124 -25
  187. package/dist/mcp-root.js.map +1 -1
  188. package/dist/mcp-run.d.ts +18 -1
  189. package/dist/mcp-run.d.ts.map +1 -1
  190. package/dist/mcp-run.js +159 -55
  191. package/dist/mcp-run.js.map +1 -1
  192. package/dist/mcp-ws.js +4 -1
  193. package/dist/mcp-ws.js.map +1 -1
  194. package/dist/mcp.d.ts +18 -5
  195. package/dist/mcp.d.ts.map +1 -1
  196. package/dist/mcp.js +165 -82
  197. package/dist/mcp.js.map +1 -1
  198. package/dist/migrate-layout.d.ts +44 -0
  199. package/dist/migrate-layout.d.ts.map +1 -0
  200. package/dist/migrate-layout.js +982 -0
  201. package/dist/migrate-layout.js.map +1 -0
  202. package/dist/openapi-tool.d.ts +2 -2
  203. package/dist/openapi-tool.d.ts.map +1 -1
  204. package/dist/openapi-tool.js.map +1 -1
  205. package/dist/openapi-types.d.ts +2 -1
  206. package/dist/openapi-types.d.ts.map +1 -1
  207. package/dist/openapi-types.js +240 -12
  208. package/dist/openapi-types.js.map +1 -1
  209. package/dist/pipeline-report.d.ts +3 -2
  210. package/dist/pipeline-report.d.ts.map +1 -1
  211. package/dist/pipeline-report.js +19 -16
  212. package/dist/pipeline-report.js.map +1 -1
  213. package/dist/port.d.ts +5 -4
  214. package/dist/port.d.ts.map +1 -1
  215. package/dist/port.js +46 -29
  216. package/dist/port.js.map +1 -1
  217. package/dist/project-facts.d.ts +28 -4
  218. package/dist/project-facts.d.ts.map +1 -1
  219. package/dist/project-facts.js +12 -2
  220. package/dist/project-facts.js.map +1 -1
  221. package/dist/recipes/4.0.0.d.ts +9 -0
  222. package/dist/recipes/4.0.0.d.ts.map +1 -0
  223. package/dist/recipes/4.0.0.js +34 -0
  224. package/dist/recipes/4.0.0.js.map +1 -0
  225. package/dist/recipes/index.d.ts.map +1 -1
  226. package/dist/recipes/index.js +2 -0
  227. package/dist/recipes/index.js.map +1 -1
  228. package/dist/release-verification.d.ts +11 -1
  229. package/dist/release-verification.d.ts.map +1 -1
  230. package/dist/release-verification.js +47 -4
  231. package/dist/release-verification.js.map +1 -1
  232. package/dist/review.d.ts +21 -0
  233. package/dist/review.d.ts.map +1 -0
  234. package/dist/review.js +823 -0
  235. package/dist/review.js.map +1 -0
  236. package/dist/route-types.d.ts +28 -0
  237. package/dist/route-types.d.ts.map +1 -0
  238. package/dist/route-types.js +106 -0
  239. package/dist/route-types.js.map +1 -0
  240. package/dist/rules/codes.d.ts +9 -0
  241. package/dist/rules/codes.d.ts.map +1 -1
  242. package/dist/rules/codes.js +9 -0
  243. package/dist/rules/codes.js.map +1 -1
  244. package/dist/rules/data-guard.d.ts +12 -0
  245. package/dist/rules/data-guard.d.ts.map +1 -0
  246. package/dist/rules/data-guard.js +213 -0
  247. package/dist/rules/data-guard.js.map +1 -0
  248. package/dist/rules/index.d.ts +3 -0
  249. package/dist/rules/index.d.ts.map +1 -1
  250. package/dist/rules/index.js +17 -5
  251. package/dist/rules/index.js.map +1 -1
  252. package/dist/rules/islands.d.ts.map +1 -1
  253. package/dist/rules/islands.js +7 -4
  254. package/dist/rules/islands.js.map +1 -1
  255. package/dist/rules/legacy.d.ts +3 -1
  256. package/dist/rules/legacy.d.ts.map +1 -1
  257. package/dist/rules/legacy.js +829 -34
  258. package/dist/rules/legacy.js.map +1 -1
  259. package/dist/rules/nano.d.ts.map +1 -1
  260. package/dist/rules/nano.js +15 -6
  261. package/dist/rules/nano.js.map +1 -1
  262. package/dist/rules/pages.d.ts +15 -0
  263. package/dist/rules/pages.d.ts.map +1 -0
  264. package/dist/rules/pages.js +88 -0
  265. package/dist/rules/pages.js.map +1 -0
  266. package/dist/rules/routes.d.ts +11 -0
  267. package/dist/rules/routes.d.ts.map +1 -1
  268. package/dist/rules/routes.js +180 -2
  269. package/dist/rules/routes.js.map +1 -1
  270. package/dist/rules/secrets.d.ts +10 -0
  271. package/dist/rules/secrets.d.ts.map +1 -0
  272. package/dist/rules/secrets.js +169 -0
  273. package/dist/rules/secrets.js.map +1 -0
  274. package/dist/rules/security.d.ts +7 -0
  275. package/dist/rules/security.d.ts.map +1 -1
  276. package/dist/rules/security.js +171 -65
  277. package/dist/rules/security.js.map +1 -1
  278. package/dist/rules/typescript.d.ts +16 -0
  279. package/dist/rules/typescript.d.ts.map +1 -0
  280. package/dist/rules/typescript.js +59 -0
  281. package/dist/rules/typescript.js.map +1 -0
  282. package/dist/rules/zones.d.ts +17 -0
  283. package/dist/rules/zones.d.ts.map +1 -0
  284. package/dist/rules/zones.js +174 -0
  285. package/dist/rules/zones.js.map +1 -0
  286. package/dist/scaffold.d.ts +25 -12
  287. package/dist/scaffold.d.ts.map +1 -1
  288. package/dist/scaffold.js +154 -90
  289. package/dist/scaffold.js.map +1 -1
  290. package/dist/sdk.d.ts +17 -3
  291. package/dist/sdk.d.ts.map +1 -1
  292. package/dist/sdk.js +941 -153
  293. package/dist/sdk.js.map +1 -1
  294. package/dist/search-terms.d.ts +2 -0
  295. package/dist/search-terms.d.ts.map +1 -1
  296. package/dist/search-terms.js +23 -8
  297. package/dist/search-terms.js.map +1 -1
  298. package/dist/smoke.d.ts +79 -0
  299. package/dist/smoke.d.ts.map +1 -0
  300. package/dist/smoke.js +494 -0
  301. package/dist/smoke.js.map +1 -0
  302. package/dist/sync-manifest.d.ts.map +1 -1
  303. package/dist/sync-manifest.js +2 -1
  304. package/dist/sync-manifest.js.map +1 -1
  305. package/dist/sync-routes.d.ts +4 -3
  306. package/dist/sync-routes.d.ts.map +1 -1
  307. package/dist/sync-routes.js +4 -3
  308. package/dist/sync-routes.js.map +1 -1
  309. package/dist/test-tool.d.ts.map +1 -1
  310. package/dist/test-tool.js +30 -11
  311. package/dist/test-tool.js.map +1 -1
  312. package/dist/types-search.d.ts.map +1 -1
  313. package/dist/types-search.js +11 -5
  314. package/dist/types-search.js.map +1 -1
  315. package/dist/upgrade.d.ts +36 -6
  316. package/dist/upgrade.d.ts.map +1 -1
  317. package/dist/upgrade.js +167 -29
  318. package/dist/upgrade.js.map +1 -1
  319. package/dist/verification-plan.d.ts.map +1 -1
  320. package/dist/verification-plan.js +8 -0
  321. package/dist/verification-plan.js.map +1 -1
  322. package/dist/verification.d.ts.map +1 -1
  323. package/dist/verification.js +45 -12
  324. package/dist/verification.js.map +1 -1
  325. package/dist/web-app-options.d.ts +19 -0
  326. package/dist/web-app-options.d.ts.map +1 -0
  327. package/dist/web-app-options.js +49 -0
  328. package/dist/web-app-options.js.map +1 -0
  329. package/dist/work-graph.d.ts +3 -0
  330. package/dist/work-graph.d.ts.map +1 -1
  331. package/dist/work-graph.js +33 -16
  332. package/dist/work-graph.js.map +1 -1
  333. package/docs/examples.json +185 -29
  334. package/docs/llms-full.txt +4123 -548
  335. package/docs/types.json +14391 -10060
  336. package/package.json +19 -11
  337. package/src/app-files.ts +40 -0
  338. package/src/assure-hydration.ts +139 -46
  339. package/src/assure.ts +17 -2
  340. package/src/capabilities-tool.ts +35 -9
  341. package/src/cdn-check.ts +268 -0
  342. package/src/check-diagnostics.ts +199 -797
  343. package/src/check-scan.ts +394 -63
  344. package/src/check.ts +146 -58
  345. package/src/cli.ts +347 -92
  346. package/src/command-catalog.ts +407 -45
  347. package/src/config-target.ts +47 -0
  348. package/src/contracts.ts +66 -15
  349. package/src/db-audit.ts +128 -0
  350. package/src/db-child.ts +285 -0
  351. package/src/db-config.ts +224 -0
  352. package/src/db-tool.ts +690 -0
  353. package/src/dev-bun-config.ts +116 -40
  354. package/src/dev-feed-tool.ts +912 -0
  355. package/src/dev-server-client.ts +267 -0
  356. package/src/diagnostics.ts +179 -0
  357. package/src/diff-tool.ts +6 -5
  358. package/src/docs-search.ts +10 -3
  359. package/src/doctor.ts +98 -15
  360. package/src/env-file.ts +78 -1
  361. package/src/fix-recipes.ts +44 -31
  362. package/src/frontend-guidance.ts +5 -5
  363. package/src/i18n-check.ts +179 -0
  364. package/src/init-agents.ts +301 -38
  365. package/src/internal/code-unit-order.ts +5 -0
  366. package/src/internal/script-kind.ts +11 -0
  367. package/src/internal/source-facts.ts +96 -22
  368. package/src/internal/typescript-import.ts +663 -68
  369. package/src/introspect.ts +93 -82
  370. package/src/learn.ts +3 -3
  371. package/src/levels-tool.ts +47 -2
  372. package/src/load.ts +144 -32
  373. package/src/manifest-tool.ts +3 -2
  374. package/src/mcp-app-tool.ts +2 -0
  375. package/src/mcp-context.ts +5 -7
  376. package/src/mcp-delegate.ts +162 -0
  377. package/src/mcp-docs-tools.ts +23 -5
  378. package/src/mcp-exec.ts +301 -161
  379. package/src/mcp-http.ts +22 -5
  380. package/src/mcp-io.ts +130 -0
  381. package/src/mcp-isolate.ts +287 -0
  382. package/src/mcp-path.ts +50 -0
  383. package/src/mcp-project-child.ts +182 -0
  384. package/src/mcp-reflect.ts +3 -2
  385. package/src/mcp-render.ts +72 -42
  386. package/src/mcp-root.ts +130 -24
  387. package/src/mcp-run.ts +190 -56
  388. package/src/mcp-ws.ts +4 -1
  389. package/src/mcp.ts +193 -91
  390. package/src/migrate-layout.ts +1121 -0
  391. package/src/openapi-tool.ts +7 -3
  392. package/src/openapi-types.ts +315 -13
  393. package/src/pipeline-report.ts +22 -18
  394. package/src/port.ts +53 -30
  395. package/src/project-facts.ts +42 -8
  396. package/src/recipes/4.0.0.ts +35 -0
  397. package/src/recipes/index.ts +2 -0
  398. package/src/release-verification.ts +74 -5
  399. package/src/review.ts +988 -0
  400. package/src/route-types.ts +133 -0
  401. package/src/rules/codes.ts +9 -0
  402. package/src/rules/data-guard.ts +230 -0
  403. package/src/rules/index.ts +18 -5
  404. package/src/rules/islands.ts +12 -4
  405. package/src/rules/legacy.ts +947 -35
  406. package/src/rules/nano.ts +18 -6
  407. package/src/rules/pages.ts +99 -0
  408. package/src/rules/routes.ts +181 -2
  409. package/src/rules/secrets.ts +174 -0
  410. package/src/rules/security.ts +198 -70
  411. package/src/rules/typescript.ts +77 -0
  412. package/src/rules/zones.ts +195 -0
  413. package/src/scaffold.ts +175 -98
  414. package/src/sdk.ts +1215 -158
  415. package/src/search-terms.ts +24 -7
  416. package/src/smoke.ts +607 -0
  417. package/src/sync-manifest.ts +2 -1
  418. package/src/sync-routes.ts +4 -3
  419. package/src/test-tool.ts +32 -11
  420. package/src/types-search.ts +11 -4
  421. package/src/upgrade.ts +205 -25
  422. package/src/verification-plan.ts +27 -0
  423. package/src/verification.ts +50 -12
  424. package/src/web-app-options.ts +57 -0
  425. package/src/work-graph.ts +40 -16
@@ -54,7 +54,7 @@ function toYaml(value: unknown, depth = 0): string {
54
54
  * worth the risk of emitting a `$ref` with no target.
55
55
  */
56
56
  function renderOpenApiWithResponses(
57
- app: LoadedApp,
57
+ app: Pick<LoadedApp, "cwd" | "backend">,
58
58
  format: OpenApiFormat,
59
59
  pathPrefix: string | undefined,
60
60
  inferredResponses: InferredOpenAPIResponses | undefined,
@@ -81,7 +81,11 @@ function renderOpenApiWithResponses(
81
81
  }
82
82
 
83
83
  /** Render the current runtime route table without loading the TypeScript compiler. */
84
- export function renderOpenApi(app: LoadedApp, format: OpenApiFormat, pathPrefix?: string): string {
84
+ export function renderOpenApi(
85
+ app: Pick<LoadedApp, "cwd" | "backend">,
86
+ format: OpenApiFormat,
87
+ pathPrefix?: string,
88
+ ): string {
85
89
  return renderOpenApiWithResponses(app, format, pathPrefix, undefined)
86
90
  }
87
91
 
@@ -90,7 +94,7 @@ export function renderOpenApi(app: LoadedApp, format: OpenApiFormat, pathPrefix?
90
94
  * only in this explicit CLI path; the server and schema runtime packages remain compiler-free.
91
95
  */
92
96
  export async function renderOpenApiWithTypes(
93
- app: LoadedApp,
97
+ app: Pick<LoadedApp, "cwd" | "backend">,
94
98
  format: OpenApiFormat,
95
99
  pathPrefix?: string,
96
100
  ): Promise<string> {
@@ -8,11 +8,16 @@
8
8
 
9
9
  import { existsSync } from "node:fs"
10
10
  import { mkdtemp, rm, writeFile } from "node:fs/promises"
11
- import { tmpdir } from "node:os"
12
- import { dirname, join, resolve } from "node:path"
11
+ import { basename, dirname, join, resolve } from "node:path"
13
12
  import type { JsonSchema } from "@nifrajs/core/reflection"
14
13
  import type * as TSApi from "typescript"
15
- import { importProjectTypeScript, type TypeScriptApi } from "./internal/typescript-import.ts"
14
+ import { BACKEND_APP_FILE } from "./app-files.ts"
15
+ import {
16
+ loadProjectTypeScript,
17
+ type TypeScriptApi,
18
+ type TypeScriptSemanticSession,
19
+ type TypeScriptSession,
20
+ } from "./internal/typescript-import.ts"
16
21
 
17
22
  export interface InferredOpenAPIResponse {
18
23
  readonly description?: string
@@ -137,6 +142,254 @@ function propertyType(checker: TypeChecker, type: Type, name: string): Type | un
137
142
  : checker.getTypeOfSymbolAtLocation(property, declaration)
138
143
  }
139
144
 
145
+ interface TypeScript7Node {
146
+ readonly kind: number
147
+ readonly pos: number
148
+ readonly end: number
149
+ getStart(sourceFile?: TypeScript7SourceFile): number
150
+ getText(sourceFile?: TypeScript7SourceFile): string
151
+ }
152
+
153
+ interface TypeScript7SourceFile extends TypeScript7Node {
154
+ readonly fileName: string
155
+ }
156
+
157
+ interface TypeScript7Symbol {
158
+ readonly name: string
159
+ readonly flags: number
160
+ }
161
+
162
+ interface TypeScript7Type {
163
+ readonly flags: number
164
+ readonly value?: string | number | boolean | bigint
165
+ getTypes(): Promise<readonly TypeScript7Type[] | undefined>
166
+ getSymbol(): Promise<TypeScript7Symbol | undefined>
167
+ }
168
+
169
+ interface TypeScript7Checker {
170
+ getTypeAtPosition(file: string, position: number): Promise<TypeScript7Type | undefined>
171
+ getTypeOfSymbol(symbol: TypeScript7Symbol): Promise<TypeScript7Type | undefined>
172
+ getTypeOfSymbolAtLocation(
173
+ symbol: TypeScript7Symbol,
174
+ location: TypeScript7Node,
175
+ ): Promise<TypeScript7Type>
176
+ getPropertiesOfType(type: TypeScript7Type): Promise<readonly TypeScript7Symbol[]>
177
+ getPropertyOfType(type: TypeScript7Type, name: string): Promise<TypeScript7Symbol | undefined>
178
+ getDeclaredTypeOfSymbol(symbol: TypeScript7Symbol): Promise<TypeScript7Type>
179
+ getApparentType(type: TypeScript7Type): Promise<TypeScript7Type | undefined>
180
+ getSignaturesOfType(type: TypeScript7Type, kind: number): Promise<readonly unknown[]>
181
+ getTypeArguments(type: TypeScript7Type): Promise<readonly TypeScript7Type[]>
182
+ getNonNullableType(type: TypeScript7Type): Promise<TypeScript7Type | undefined>
183
+ typeToString(type: TypeScript7Type): Promise<string>
184
+ isTupleType(type: TypeScript7Type): Promise<boolean>
185
+ isArrayType(type: TypeScript7Type): Promise<boolean>
186
+ }
187
+
188
+ const semanticChecker = async (
189
+ semantic: TypeScriptSemanticSession,
190
+ file: string,
191
+ ): Promise<TypeScript7Checker> =>
192
+ ((await semantic.getCheckerForFile(file)) ?? semantic.checker) as TypeScript7Checker
193
+
194
+ const semanticSource = async (
195
+ semantic: TypeScriptSemanticSession,
196
+ file: string,
197
+ ): Promise<TypeScript7SourceFile | undefined> =>
198
+ (await semantic.getSourceFile(file)) as TypeScript7SourceFile | undefined
199
+
200
+ function semanticFlag(semantic: TypeScriptSemanticSession, name: string): number {
201
+ const value = semantic.typeFlags[name]
202
+ if (typeof value !== "number") throw new Error(`TypeScript 7 TypeFlags.${name} is unavailable`)
203
+ return value
204
+ }
205
+
206
+ function semanticSymbolFlag(semantic: TypeScriptSemanticSession, name: string): number {
207
+ const value = semantic.symbolFlags[name]
208
+ if (typeof value !== "number") throw new Error(`TypeScript 7 SymbolFlags.${name} is unavailable`)
209
+ return value
210
+ }
211
+
212
+ function semanticSignatureKind(semantic: TypeScriptSemanticSession, name: string): number {
213
+ const value = semantic.signatureKind[name]
214
+ if (typeof value !== "number")
215
+ throw new Error(`TypeScript 7 SignatureKind.${name} is unavailable`)
216
+ return value
217
+ }
218
+
219
+ async function schemaForType7(
220
+ semantic: TypeScriptSemanticSession,
221
+ checker: TypeScript7Checker,
222
+ input: TypeScript7Type,
223
+ seen: Set<TypeScript7Type>,
224
+ depth: number,
225
+ ): Promise<JsonSchema | undefined> {
226
+ if (depth > 20 || seen.has(input)) return undefined
227
+ const anyFlag = semanticFlag(semantic, "Any")
228
+ const unknownFlag = semanticFlag(semantic, "Unknown")
229
+ const neverFlag = semanticFlag(semantic, "Never")
230
+ const undefinedFlag = semanticFlag(semantic, "Undefined")
231
+ const voidFlag = semanticFlag(semantic, "Void")
232
+ const stringLiteralFlag = semanticFlag(semantic, "StringLiteral")
233
+ const numberLiteralFlag = semanticFlag(semantic, "NumberLiteral")
234
+ const booleanLiteralFlag = semanticFlag(semantic, "BooleanLiteral")
235
+ const stringLikeFlag = semanticFlag(semantic, "StringLike")
236
+ const numberLikeFlag = semanticFlag(semantic, "NumberLike")
237
+ const booleanLikeFlag = semanticFlag(semantic, "BooleanLike")
238
+ const bigintLikeFlag = semanticFlag(semantic, "BigIntLike")
239
+ const nullFlag = semanticFlag(semantic, "Null")
240
+ const unionFlag = semanticFlag(semantic, "Union")
241
+ const objectFlag = semanticFlag(semantic, "Object")
242
+
243
+ if ((input.flags & (anyFlag | unknownFlag)) !== 0) return undefined
244
+ if ((input.flags & neverFlag) !== 0) return undefined
245
+ if ((input.flags & (undefinedFlag | voidFlag)) !== 0) return undefined
246
+ if ((input.flags & stringLiteralFlag) !== 0 && typeof input.value === "string") {
247
+ return { const: input.value }
248
+ }
249
+ if ((input.flags & numberLiteralFlag) !== 0 && typeof input.value === "number") {
250
+ return { const: input.value }
251
+ }
252
+ if ((input.flags & booleanLiteralFlag) !== 0) {
253
+ return { const: (await checker.typeToString(input)) === "true" }
254
+ }
255
+ if ((input.flags & stringLikeFlag) !== 0) return { type: "string" }
256
+ if ((input.flags & numberLikeFlag) !== 0) return { type: "number" }
257
+ if ((input.flags & booleanLikeFlag) !== 0) return { type: "boolean" }
258
+ if ((input.flags & bigintLikeFlag) !== 0) return { type: "integer" }
259
+ if ((input.flags & nullFlag) !== 0) return { type: "null" }
260
+
261
+ if ((input.flags & unionFlag) !== 0) {
262
+ const members = (await input.getTypes()) ?? []
263
+ const schemas = (
264
+ await Promise.all(
265
+ members.map((member) => schemaForType7(semantic, checker, member, seen, depth + 1)),
266
+ )
267
+ ).filter((schema): schema is JsonSchema => schema !== undefined)
268
+ if (schemas.length === 0) return undefined
269
+ if (schemas.length === 1) return schemas[0]
270
+ return { anyOf: schemas }
271
+ }
272
+
273
+ if (await checker.isTupleType(input)) {
274
+ const elements = (
275
+ await Promise.all(
276
+ (
277
+ await checker.getTypeArguments(input)
278
+ ).map((member) => schemaForType7(semantic, checker, member, seen, depth + 1)),
279
+ )
280
+ ).filter((schema): schema is JsonSchema => schema !== undefined)
281
+ return elements.length === 0 ? undefined : { type: "array", prefixItems: elements }
282
+ }
283
+ if (await checker.isArrayType(input)) {
284
+ const [element] = await checker.getTypeArguments(input)
285
+ const items =
286
+ element === undefined
287
+ ? undefined
288
+ : await schemaForType7(semantic, checker, element, seen, depth + 1)
289
+ return items === undefined ? { type: "array" } : { type: "array", items }
290
+ }
291
+
292
+ const symbol = await input.getSymbol()
293
+ if (symbol?.name === "Date") return { type: "string", format: "date-time" }
294
+ if ((input.flags & objectFlag) === 0) return undefined
295
+
296
+ seen.add(input)
297
+ try {
298
+ const properties: Record<string, JsonSchema> = {}
299
+ const required: string[] = []
300
+ for (const property of await checker.getPropertiesOfType(input)) {
301
+ const propertyType = await checker.getTypeOfSymbol(property)
302
+ if (propertyType === undefined) return undefined
303
+ if (
304
+ (await checker.getSignaturesOfType(propertyType, semanticSignatureKind(semantic, "Call")))
305
+ .length > 0
306
+ )
307
+ continue
308
+ const propertySchema = await schemaForType7(semantic, checker, propertyType, seen, depth + 1)
309
+ if (propertySchema === undefined) return undefined
310
+ properties[property.name] = propertySchema
311
+ if ((property.flags & semanticSymbolFlag(semantic, "Optional")) === 0) {
312
+ required.push(property.name)
313
+ }
314
+ }
315
+ if (Object.keys(properties).length === 0) return { type: "object" }
316
+ return {
317
+ type: "object",
318
+ properties,
319
+ ...(required.length > 0 ? { required } : {}),
320
+ }
321
+ } finally {
322
+ seen.delete(input)
323
+ }
324
+ }
325
+
326
+ async function inferOpenAPIResponses7(
327
+ semantic: TypeScriptSemanticSession,
328
+ probePath: string,
329
+ warnings: string[],
330
+ ): Promise<OpenAPITypeInferenceResult> {
331
+ const source = await semanticSource(semantic, probePath)
332
+ if (source === undefined) {
333
+ warnings.push("could not create the TypeScript 7 response-inference probe")
334
+ return { responses: {}, warnings }
335
+ }
336
+ const checker = await semanticChecker(semantic, probePath)
337
+ const registryPosition = source.getText().indexOf("__nifra_registry")
338
+ if (registryPosition < 0) {
339
+ warnings.push("backend does not expose a typed Nifra server registry")
340
+ return { responses: {}, warnings }
341
+ }
342
+ const registry = await checker.getTypeAtPosition(probePath, registryPosition)
343
+ if (registry === undefined) {
344
+ warnings.push("backend does not expose a typed Nifra server registry")
345
+ return { responses: {}, warnings }
346
+ }
347
+ const responses: Record<string, Record<string, InferredOpenAPIResponse>> = {}
348
+ for (const pathSymbol of await checker.getPropertiesOfType(registry)) {
349
+ const methods = await checker.getTypeOfSymbol(pathSymbol)
350
+ if (methods === undefined) continue
351
+ for (const methodSymbol of await checker.getPropertiesOfType(methods)) {
352
+ if (methodSymbol.name === "WS") continue
353
+ const info = await checker.getTypeOfSymbol(methodSymbol)
354
+ if (info === undefined) continue
355
+ const responseSymbol = await checker.getPropertyOfType(info, "responses")
356
+ if (responseSymbol === undefined) continue
357
+ const responseType = await checker.getTypeOfSymbol(responseSymbol)
358
+ if (responseType === undefined) continue
359
+ const nonNullable = await checker.getNonNullableType(responseType)
360
+ if (nonNullable === undefined) continue
361
+ const routeResponses: Record<string, InferredOpenAPIResponse> = {}
362
+ const responseMap = (await checker.getApparentType(nonNullable)) ?? nonNullable
363
+ for (const property of await checker.getPropertiesOfType(responseMap)) {
364
+ if (!/^[1-5][0-9]{2}$/.test(property.name)) continue
365
+ const bodyType = await checker.getTypeOfSymbol(property)
366
+ if (bodyType === undefined) continue
367
+ const schema = await schemaForType7(semantic, checker, bodyType, new Set(), 0)
368
+ if (schema === undefined) {
369
+ const bodylessStatus = ["204", "205", "304"].includes(property.name)
370
+ const bodylessType =
371
+ (bodyType.flags &
372
+ (semanticFlag(semantic, "Undefined") | semanticFlag(semantic, "Void"))) !==
373
+ 0
374
+ if (bodylessStatus && bodylessType) {
375
+ routeResponses[property.name] = {}
376
+ continue
377
+ }
378
+ warnings.push(
379
+ `response ${property.name} has an unsupported or opaque TypeScript body (${await checker.typeToString(bodyType)})`,
380
+ )
381
+ continue
382
+ }
383
+ routeResponses[property.name] = { schema }
384
+ }
385
+ if (Object.keys(routeResponses).length > 0) {
386
+ responses[`${methodSymbol.name.toUpperCase()} ${pathSymbol.name}`] = routeResponses
387
+ }
388
+ }
389
+ }
390
+ return { responses, warnings }
391
+ }
392
+
140
393
  function responseEntries(
141
394
  ts: TypeScriptApi,
142
395
  checker: TypeChecker,
@@ -168,29 +421,75 @@ function responseEntries(
168
421
  return output
169
422
  }
170
423
 
171
- /** Infer response schemas from `<root>/backend.ts` without affecting runtime application loading. */
424
+ /** Infer response schemas from `<root>/backend/app.ts` without affecting runtime application
425
+ * loading. */
172
426
  export async function inferOpenAPIResponses(root: string): Promise<OpenAPITypeInferenceResult> {
173
427
  const warnings: string[] = []
174
- const backendPath = resolve(root, "backend.ts")
428
+ const backendPath = resolve(root, BACKEND_APP_FILE)
175
429
  if (!existsSync(backendPath)) return { responses: {}, warnings }
176
430
 
177
- const ts = await importProjectTypeScript(root)
178
- if (ts === undefined) {
179
- warnings.push("TypeScript is not installed; response inference was skipped")
180
- return { responses: {}, warnings }
181
- }
182
-
183
- const temp = await mkdtemp(join(tmpdir(), "nifra-openapi-"))
184
- const probePath = join(temp, "probe.ts")
431
+ // TypeScript 7's project checker only resolves node handles inside a configured project's root.
432
+ // Keep both the probe and a short-lived extending config at the project root, then remove them.
433
+ const temp = await mkdtemp(join(root, ".nifra-openapi-"))
434
+ const tempName = basename(temp)
435
+ const probePath = join(root, `.${tempName}.ts`)
436
+ const probeConfigPath = join(root, `.${tempName}.tsconfig.json`)
185
437
  const probe = [
186
438
  'import type { Server as __NifraServer } from "@nifrajs/core/server"',
187
439
  `import { backend as __nifra_backend } from ${JSON.stringify(backendPath)}`,
188
440
  "type __NifraRegistry = typeof __nifra_backend extends __NifraServer<infer R, any, any> ? R : never",
189
441
  "declare const __nifra_registry: __NifraRegistry",
190
442
  ].join("\n")
443
+ let session: TypeScriptSession | undefined
191
444
 
192
445
  try {
193
446
  await writeFile(probePath, probe, "utf8")
447
+ await writeFile(
448
+ probeConfigPath,
449
+ JSON.stringify(
450
+ existsSync(join(root, "tsconfig.json"))
451
+ ? {
452
+ extends: "./tsconfig.json",
453
+ compilerOptions: { allowImportingTsExtensions: true },
454
+ include: [BACKEND_APP_FILE, basename(probePath)],
455
+ }
456
+ : {
457
+ compilerOptions: {
458
+ module: "ESNext",
459
+ moduleResolution: "Bundler",
460
+ allowImportingTsExtensions: true,
461
+ skipLibCheck: true,
462
+ },
463
+ include: [BACKEND_APP_FILE, basename(probePath)],
464
+ },
465
+ ),
466
+ "utf8",
467
+ )
468
+ const backendContent = await Bun.file(backendPath).text()
469
+ const contents = new Map<string, string>([
470
+ [backendPath, backendContent],
471
+ [probePath, probe],
472
+ ])
473
+ const loaded = await loadProjectTypeScript(root, undefined, {
474
+ files: [backendPath, probePath],
475
+ read: (file) => contents.get(resolve(file)),
476
+ projectConfig: probeConfigPath,
477
+ })
478
+ session = loaded.session
479
+ if (loaded.unsupported !== undefined) {
480
+ warnings.push(
481
+ `TypeScript ${loaded.unsupported.version} is outside the supported OpenAPI reflection API`,
482
+ )
483
+ return { responses: {}, warnings }
484
+ }
485
+ const ts = loaded.compiler
486
+ if (ts === undefined) {
487
+ warnings.push("TypeScript is not installed; response inference was skipped")
488
+ return { responses: {}, warnings }
489
+ }
490
+ if (loaded.session?.semantic !== undefined) {
491
+ return await inferOpenAPIResponses7(loaded.session.semantic, probePath, warnings)
492
+ }
194
493
  const configPath = ts.findConfigFile(root, ts.sys.fileExists, "tsconfig.json")
195
494
  let options: TSApi.CompilerOptions = {
196
495
  noEmit: true,
@@ -256,6 +555,9 @@ export async function inferOpenAPIResponses(root: string): Promise<OpenAPITypeIn
256
555
  }
257
556
  return { responses, warnings }
258
557
  } finally {
558
+ await session?.close()
559
+ await rm(probePath, { force: true })
560
+ await rm(probeConfigPath, { force: true })
259
561
  await rm(temp, { recursive: true, force: true })
260
562
  }
261
563
  }
@@ -10,15 +10,16 @@
10
10
  * Text, not types, so the rules are chosen for the ones that survive being read rather than run:
11
11
  *
12
12
  * - Which pipeline the app is on. `chooseBuildPipeline` decides from whether the plugin slots are
13
- * empty, and "does this file declare a non-empty `vitePlugins`" is a question the source answers.
14
- * - The adapter-entry hazard. `nifra build` imports the adapter from `framework.ts`, or from
15
- * `nifra.config.ts` when there is no `framework.ts`, and bundles everything that file imports into
16
- * the production server. A Vite plugin or an SFC compiler reached that way builds cleanly and then
17
- * dies at startup on a missing native binding. Nothing runtime-side catches it: by then the build
18
- * has already succeeded.
19
- * - A plugin in the wrong slot, for the subset whose import specifier names its pipeline. The load
20
- * guard classifies by hook shape and is authoritative; this catches the same thing earlier, before
21
- * anything is installed or started.
13
+ * empty, and "does this file declare a non-empty `vitePlugins`" is a question the source
14
+ * answers.
15
+ * - The adapter-entry hazard. `nifra build` imports the adapter from `backend/framework.ts`, or
16
+ * from `nifra.config.ts` when there is no `backend/framework.ts`, and bundles everything that
17
+ * file imports into the production server. A Vite plugin or an SFC compiler reached that way
18
+ * builds cleanly and then dies at startup on a missing native binding. Nothing runtime-side
19
+ * catches it: by then the build has already succeeded.
20
+ * - A plugin in the wrong slot, for the subset whose import specifier names its pipeline. The
21
+ * load guard classifies by hook shape and is authoritative; this catches the same thing
22
+ * earlier, before anything is installed or started.
22
23
  * - `conditions` on the Bun pipeline, which reach SSR and cannot reach the dev client bundle.
23
24
  *
24
25
  * Everything here degrades to silence rather than guessing. A config that computes its plugins, or
@@ -27,6 +28,7 @@
27
28
  */
28
29
  import { existsSync } from "node:fs"
29
30
  import { join } from "node:path"
31
+ import { CONFIG_FILE, FRAMEWORK_FILE } from "./app-files.ts"
30
32
  import { codePositionMask, stripComments } from "./check.ts"
31
33
  import type { BuildPipeline } from "./pipeline-guard.ts"
32
34
 
@@ -52,7 +54,8 @@ export interface PipelineFinding {
52
54
  }
53
55
 
54
56
  export interface PipelineReport {
55
- /** `false` when the directory holds no `nifra.config.ts` or `framework.ts` - nothing to report on. */
57
+ /** `false` when the directory holds no `nifra.config.ts` or `backend/framework.ts` - nothing to
58
+ * report on. */
56
59
  readonly ran: boolean
57
60
  /** The bundler `nifra dev` and `nifra build` will use, or `unknown` when the source can't say. */
58
61
  readonly pipeline: BuildPipeline | "unknown"
@@ -65,7 +68,7 @@ export interface PipelineReport {
65
68
  readonly certain: boolean
66
69
  /** The config file read, project-relative. */
67
70
  readonly configFile?: string
68
- /** The file `nifra build` imports the adapter from - `framework.ts`, else the config. */
71
+ /** The file `nifra build` imports the adapter from - `backend/framework.ts`, else the config. */
69
72
  readonly adapterEntry?: string
70
73
  readonly findings: readonly PipelineFinding[]
71
74
  }
@@ -227,13 +230,13 @@ async function readSource(path: string): Promise<string | undefined> {
227
230
  * Total: a missing or unreadable config yields `ran: false`, never a throw.
228
231
  */
229
232
  export async function collectPipelineReport(cwd: string): Promise<PipelineReport> {
230
- const configFile = existsSync(join(cwd, "nifra.config.ts")) ? "nifra.config.ts" : "framework.ts"
233
+ const configFile = existsSync(join(cwd, CONFIG_FILE)) ? CONFIG_FILE : FRAMEWORK_FILE
231
234
  const source = await readSource(join(cwd, configFile))
232
235
  if (source === undefined) {
233
236
  return {
234
237
  ran: false,
235
238
  pipeline: "unknown",
236
- reason: "no nifra.config.ts or framework.ts here",
239
+ reason: "no nifra.config.ts or backend/framework.ts here",
237
240
  certain: false,
238
241
  findings: [],
239
242
  }
@@ -284,8 +287,9 @@ export async function collectPipelineReport(cwd: string): Promise<PipelineReport
284
287
  ? "default: this app declares Bun plugins, so both phases stay on Bun"
285
288
  : "default: no transforms to place, so both phases stay on Bun"
286
289
 
287
- // The adapter entry is bundled into every `nifra build` server entry. Whatever it imports goes with it.
288
- const adapterEntry = existsSync(join(cwd, "framework.ts")) ? "framework.ts" : configFile
290
+ // The adapter entry is bundled into every `nifra build` server entry. Whatever it imports goes
291
+ // with it.
292
+ const adapterEntry = existsSync(join(cwd, FRAMEWORK_FILE)) ? FRAMEWORK_FILE : configFile
289
293
  const entrySource =
290
294
  adapterEntry === configFile ? source : await readSource(join(cwd, adapterEntry))
291
295
  if (entrySource !== undefined) {
@@ -302,11 +306,11 @@ export async function collectPipelineReport(cwd: string): Promise<PipelineReport
302
306
  `${adapterEntry} imports "${specifier}", and \`nifra build\` imports the adapter from ${adapterEntry} - ` +
303
307
  "so the dev toolchain is bundled into the production server entry. The build succeeds and the server then fails at startup on a dependency of the bundler (a missing native binding, typically), which reads like a broken install rather than a config split.",
304
308
  fix:
305
- adapterEntry === "framework.ts"
309
+ adapterEntry === FRAMEWORK_FILE
306
310
  ? 'move "' +
307
311
  specifier +
308
- '" to `nifra.config.ts`, which only the CLI imports, and keep `framework.ts` to the adapter'
309
- : 'add a `framework.ts` exporting just the adapter, re-export it from `nifra.config.ts` (`export { adapter } from "./framework"`), and leave the toolchain imports in the config',
312
+ '" to `nifra.config.ts`, which only the CLI imports, and keep `backend/framework.ts` to the adapter'
313
+ : 'add a `backend/framework.ts` exporting just the adapter, re-export it from `nifra.config.ts` (`export { adapter } from "./backend/framework"`), and leave the toolchain imports in the config',
310
314
  })
311
315
  }
312
316
  }