@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.
- package/README.md +42 -3
- package/dist/app-files.d.ts +11 -0
- package/dist/app-files.d.ts.map +1 -0
- package/dist/app-files.js +34 -0
- package/dist/app-files.js.map +1 -0
- package/dist/assure-hydration.d.ts +7 -1
- package/dist/assure-hydration.d.ts.map +1 -1
- package/dist/assure-hydration.js +128 -43
- package/dist/assure-hydration.js.map +1 -1
- package/dist/assure.d.ts.map +1 -1
- package/dist/assure.js +16 -2
- package/dist/assure.js.map +1 -1
- package/dist/capabilities-tool.d.ts +5 -1
- package/dist/capabilities-tool.d.ts.map +1 -1
- package/dist/capabilities-tool.js +32 -10
- package/dist/capabilities-tool.js.map +1 -1
- package/dist/cdn-check.d.ts +41 -0
- package/dist/cdn-check.d.ts.map +1 -0
- package/dist/cdn-check.js +207 -0
- package/dist/cdn-check.js.map +1 -0
- package/dist/check-diagnostics.d.ts +7 -15
- package/dist/check-diagnostics.d.ts.map +1 -1
- package/dist/check-diagnostics.js +162 -713
- package/dist/check-diagnostics.js.map +1 -1
- package/dist/check-scan.d.ts +58 -7
- package/dist/check-scan.d.ts.map +1 -1
- package/dist/check-scan.js +323 -62
- package/dist/check-scan.js.map +1 -1
- package/dist/check.d.ts +1 -0
- package/dist/check.d.ts.map +1 -1
- package/dist/check.js +145 -54
- package/dist/check.js.map +1 -1
- package/dist/cli.d.ts +42 -16
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +306 -87
- package/dist/cli.js.map +1 -1
- package/dist/command-catalog.d.ts +60 -5
- package/dist/command-catalog.d.ts.map +1 -1
- package/dist/command-catalog.js +298 -33
- package/dist/command-catalog.js.map +1 -1
- package/dist/config-target.d.ts +8 -0
- package/dist/config-target.d.ts.map +1 -0
- package/dist/config-target.js +45 -0
- package/dist/config-target.js.map +1 -0
- package/dist/contracts.d.ts +3 -2
- package/dist/contracts.d.ts.map +1 -1
- package/dist/contracts.js +56 -16
- package/dist/contracts.js.map +1 -1
- package/dist/db-audit.d.ts +37 -0
- package/dist/db-audit.d.ts.map +1 -0
- package/dist/db-audit.js +99 -0
- package/dist/db-audit.js.map +1 -0
- package/dist/db-child.d.ts +64 -0
- package/dist/db-child.d.ts.map +1 -0
- package/dist/db-child.js +213 -0
- package/dist/db-child.js.map +1 -0
- package/dist/db-config.d.ts +60 -0
- package/dist/db-config.d.ts.map +1 -0
- package/dist/db-config.js +176 -0
- package/dist/db-config.js.map +1 -0
- package/dist/db-tool.d.ts +84 -0
- package/dist/db-tool.d.ts.map +1 -0
- package/dist/db-tool.js +537 -0
- package/dist/db-tool.js.map +1 -0
- package/dist/dev-bun-config.d.ts +20 -13
- package/dist/dev-bun-config.d.ts.map +1 -1
- package/dist/dev-bun-config.js +98 -41
- package/dist/dev-bun-config.js.map +1 -1
- package/dist/dev-feed-tool.d.ts +106 -0
- package/dist/dev-feed-tool.d.ts.map +1 -0
- package/dist/dev-feed-tool.js +713 -0
- package/dist/dev-feed-tool.js.map +1 -0
- package/dist/dev-server-client.d.ts +67 -0
- package/dist/dev-server-client.d.ts.map +1 -0
- package/dist/dev-server-client.js +195 -0
- package/dist/dev-server-client.js.map +1 -0
- package/dist/diagnostics.d.ts +86 -0
- package/dist/diagnostics.d.ts.map +1 -1
- package/dist/diagnostics.js +85 -0
- package/dist/diagnostics.js.map +1 -1
- package/dist/diff-tool.d.ts +3 -3
- package/dist/diff-tool.d.ts.map +1 -1
- package/dist/diff-tool.js +6 -5
- package/dist/diff-tool.js.map +1 -1
- package/dist/docs-search.d.ts.map +1 -1
- package/dist/docs-search.js +12 -4
- package/dist/docs-search.js.map +1 -1
- package/dist/doctor.d.ts +17 -0
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +76 -15
- package/dist/doctor.js.map +1 -1
- package/dist/env-file.d.ts +15 -0
- package/dist/env-file.d.ts.map +1 -1
- package/dist/env-file.js +73 -1
- package/dist/env-file.js.map +1 -1
- package/dist/fix-recipes.d.ts.map +1 -1
- package/dist/fix-recipes.js +41 -21
- package/dist/fix-recipes.js.map +1 -1
- package/dist/frontend-guidance.js +5 -5
- package/dist/frontend-guidance.js.map +1 -1
- package/dist/i18n-check.d.ts +16 -0
- package/dist/i18n-check.d.ts.map +1 -0
- package/dist/i18n-check.js +154 -0
- package/dist/i18n-check.js.map +1 -0
- package/dist/init-agents.d.ts +49 -13
- package/dist/init-agents.d.ts.map +1 -1
- package/dist/init-agents.js +239 -34
- package/dist/init-agents.js.map +1 -1
- package/dist/internal/code-unit-order.d.ts +4 -0
- package/dist/internal/code-unit-order.d.ts.map +1 -0
- package/dist/internal/code-unit-order.js +6 -0
- package/dist/internal/code-unit-order.js.map +1 -0
- package/dist/internal/script-kind.d.ts +7 -0
- package/dist/internal/script-kind.d.ts.map +1 -0
- package/dist/internal/script-kind.js +12 -0
- package/dist/internal/script-kind.js.map +1 -0
- package/dist/internal/source-facts.d.ts +5 -0
- package/dist/internal/source-facts.d.ts.map +1 -1
- package/dist/internal/source-facts.js +61 -5
- package/dist/internal/source-facts.js.map +1 -1
- package/dist/internal/typescript-import.d.ts +49 -24
- package/dist/internal/typescript-import.d.ts.map +1 -1
- package/dist/internal/typescript-import.js +507 -67
- package/dist/internal/typescript-import.js.map +1 -1
- package/dist/introspect.d.ts +6 -4
- package/dist/introspect.d.ts.map +1 -1
- package/dist/introspect.js +93 -74
- package/dist/introspect.js.map +1 -1
- package/dist/learn.js +3 -3
- package/dist/learn.js.map +1 -1
- package/dist/levels-tool.d.ts +10 -0
- package/dist/levels-tool.d.ts.map +1 -1
- package/dist/levels-tool.js +33 -3
- package/dist/levels-tool.js.map +1 -1
- package/dist/load.d.ts +71 -16
- package/dist/load.d.ts.map +1 -1
- package/dist/load.js +78 -18
- package/dist/load.js.map +1 -1
- package/dist/manifest-tool.d.ts.map +1 -1
- package/dist/manifest-tool.js +3 -2
- package/dist/manifest-tool.js.map +1 -1
- package/dist/mcp-app-tool.d.ts.map +1 -1
- package/dist/mcp-app-tool.js +2 -0
- package/dist/mcp-app-tool.js.map +1 -1
- package/dist/mcp-context.d.ts.map +1 -1
- package/dist/mcp-context.js +5 -6
- package/dist/mcp-context.js.map +1 -1
- package/dist/mcp-delegate.d.ts +57 -0
- package/dist/mcp-delegate.d.ts.map +1 -0
- package/dist/mcp-delegate.js +130 -0
- package/dist/mcp-delegate.js.map +1 -0
- package/dist/mcp-docs-tools.d.ts.map +1 -1
- package/dist/mcp-docs-tools.js +23 -5
- package/dist/mcp-docs-tools.js.map +1 -1
- package/dist/mcp-exec.d.ts +3 -0
- package/dist/mcp-exec.d.ts.map +1 -1
- package/dist/mcp-exec.js +263 -131
- package/dist/mcp-exec.js.map +1 -1
- package/dist/mcp-http.d.ts.map +1 -1
- package/dist/mcp-http.js +20 -5
- package/dist/mcp-http.js.map +1 -1
- package/dist/mcp-io.d.ts +26 -0
- package/dist/mcp-io.d.ts.map +1 -1
- package/dist/mcp-io.js +113 -0
- package/dist/mcp-io.js.map +1 -1
- package/dist/mcp-isolate.d.ts +48 -0
- package/dist/mcp-isolate.d.ts.map +1 -0
- package/dist/mcp-isolate.js +222 -0
- package/dist/mcp-isolate.js.map +1 -0
- package/dist/mcp-path.d.ts +10 -0
- package/dist/mcp-path.d.ts.map +1 -0
- package/dist/mcp-path.js +51 -0
- package/dist/mcp-path.js.map +1 -0
- package/dist/mcp-project-child.d.ts +74 -0
- package/dist/mcp-project-child.d.ts.map +1 -0
- package/dist/mcp-project-child.js +130 -0
- package/dist/mcp-project-child.js.map +1 -0
- package/dist/mcp-reflect.d.ts.map +1 -1
- package/dist/mcp-reflect.js +2 -2
- package/dist/mcp-reflect.js.map +1 -1
- package/dist/mcp-render.d.ts.map +1 -1
- package/dist/mcp-render.js +64 -45
- package/dist/mcp-render.js.map +1 -1
- package/dist/mcp-root.d.ts +37 -9
- package/dist/mcp-root.d.ts.map +1 -1
- package/dist/mcp-root.js +124 -25
- package/dist/mcp-root.js.map +1 -1
- package/dist/mcp-run.d.ts +18 -1
- package/dist/mcp-run.d.ts.map +1 -1
- package/dist/mcp-run.js +159 -55
- package/dist/mcp-run.js.map +1 -1
- package/dist/mcp-ws.js +4 -1
- package/dist/mcp-ws.js.map +1 -1
- package/dist/mcp.d.ts +18 -5
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +165 -82
- package/dist/mcp.js.map +1 -1
- package/dist/migrate-layout.d.ts +44 -0
- package/dist/migrate-layout.d.ts.map +1 -0
- package/dist/migrate-layout.js +982 -0
- package/dist/migrate-layout.js.map +1 -0
- package/dist/openapi-tool.d.ts +2 -2
- package/dist/openapi-tool.d.ts.map +1 -1
- package/dist/openapi-tool.js.map +1 -1
- package/dist/openapi-types.d.ts +2 -1
- package/dist/openapi-types.d.ts.map +1 -1
- package/dist/openapi-types.js +240 -12
- package/dist/openapi-types.js.map +1 -1
- package/dist/pipeline-report.d.ts +3 -2
- package/dist/pipeline-report.d.ts.map +1 -1
- package/dist/pipeline-report.js +19 -16
- package/dist/pipeline-report.js.map +1 -1
- package/dist/port.d.ts +5 -4
- package/dist/port.d.ts.map +1 -1
- package/dist/port.js +46 -29
- package/dist/port.js.map +1 -1
- package/dist/project-facts.d.ts +28 -4
- package/dist/project-facts.d.ts.map +1 -1
- package/dist/project-facts.js +12 -2
- package/dist/project-facts.js.map +1 -1
- package/dist/recipes/4.0.0.d.ts +9 -0
- package/dist/recipes/4.0.0.d.ts.map +1 -0
- package/dist/recipes/4.0.0.js +34 -0
- package/dist/recipes/4.0.0.js.map +1 -0
- package/dist/recipes/index.d.ts.map +1 -1
- package/dist/recipes/index.js +2 -0
- package/dist/recipes/index.js.map +1 -1
- package/dist/release-verification.d.ts +11 -1
- package/dist/release-verification.d.ts.map +1 -1
- package/dist/release-verification.js +47 -4
- package/dist/release-verification.js.map +1 -1
- package/dist/review.d.ts +21 -0
- package/dist/review.d.ts.map +1 -0
- package/dist/review.js +823 -0
- package/dist/review.js.map +1 -0
- package/dist/route-types.d.ts +28 -0
- package/dist/route-types.d.ts.map +1 -0
- package/dist/route-types.js +106 -0
- package/dist/route-types.js.map +1 -0
- package/dist/rules/codes.d.ts +9 -0
- package/dist/rules/codes.d.ts.map +1 -1
- package/dist/rules/codes.js +9 -0
- package/dist/rules/codes.js.map +1 -1
- package/dist/rules/data-guard.d.ts +12 -0
- package/dist/rules/data-guard.d.ts.map +1 -0
- package/dist/rules/data-guard.js +213 -0
- package/dist/rules/data-guard.js.map +1 -0
- package/dist/rules/index.d.ts +3 -0
- package/dist/rules/index.d.ts.map +1 -1
- package/dist/rules/index.js +17 -5
- package/dist/rules/index.js.map +1 -1
- package/dist/rules/islands.d.ts.map +1 -1
- package/dist/rules/islands.js +7 -4
- package/dist/rules/islands.js.map +1 -1
- package/dist/rules/legacy.d.ts +3 -1
- package/dist/rules/legacy.d.ts.map +1 -1
- package/dist/rules/legacy.js +829 -34
- package/dist/rules/legacy.js.map +1 -1
- package/dist/rules/nano.d.ts.map +1 -1
- package/dist/rules/nano.js +15 -6
- package/dist/rules/nano.js.map +1 -1
- package/dist/rules/pages.d.ts +15 -0
- package/dist/rules/pages.d.ts.map +1 -0
- package/dist/rules/pages.js +88 -0
- package/dist/rules/pages.js.map +1 -0
- package/dist/rules/routes.d.ts +11 -0
- package/dist/rules/routes.d.ts.map +1 -1
- package/dist/rules/routes.js +180 -2
- package/dist/rules/routes.js.map +1 -1
- package/dist/rules/secrets.d.ts +10 -0
- package/dist/rules/secrets.d.ts.map +1 -0
- package/dist/rules/secrets.js +169 -0
- package/dist/rules/secrets.js.map +1 -0
- package/dist/rules/security.d.ts +7 -0
- package/dist/rules/security.d.ts.map +1 -1
- package/dist/rules/security.js +171 -65
- package/dist/rules/security.js.map +1 -1
- package/dist/rules/typescript.d.ts +16 -0
- package/dist/rules/typescript.d.ts.map +1 -0
- package/dist/rules/typescript.js +59 -0
- package/dist/rules/typescript.js.map +1 -0
- package/dist/rules/zones.d.ts +17 -0
- package/dist/rules/zones.d.ts.map +1 -0
- package/dist/rules/zones.js +174 -0
- package/dist/rules/zones.js.map +1 -0
- package/dist/scaffold.d.ts +25 -12
- package/dist/scaffold.d.ts.map +1 -1
- package/dist/scaffold.js +154 -90
- package/dist/scaffold.js.map +1 -1
- package/dist/sdk.d.ts +17 -3
- package/dist/sdk.d.ts.map +1 -1
- package/dist/sdk.js +941 -153
- package/dist/sdk.js.map +1 -1
- package/dist/search-terms.d.ts +2 -0
- package/dist/search-terms.d.ts.map +1 -1
- package/dist/search-terms.js +23 -8
- package/dist/search-terms.js.map +1 -1
- package/dist/smoke.d.ts +79 -0
- package/dist/smoke.d.ts.map +1 -0
- package/dist/smoke.js +494 -0
- package/dist/smoke.js.map +1 -0
- package/dist/sync-manifest.d.ts.map +1 -1
- package/dist/sync-manifest.js +2 -1
- package/dist/sync-manifest.js.map +1 -1
- package/dist/sync-routes.d.ts +4 -3
- package/dist/sync-routes.d.ts.map +1 -1
- package/dist/sync-routes.js +4 -3
- package/dist/sync-routes.js.map +1 -1
- package/dist/test-tool.d.ts.map +1 -1
- package/dist/test-tool.js +30 -11
- package/dist/test-tool.js.map +1 -1
- package/dist/types-search.d.ts.map +1 -1
- package/dist/types-search.js +11 -5
- package/dist/types-search.js.map +1 -1
- package/dist/upgrade.d.ts +36 -6
- package/dist/upgrade.d.ts.map +1 -1
- package/dist/upgrade.js +167 -29
- package/dist/upgrade.js.map +1 -1
- package/dist/verification-plan.d.ts.map +1 -1
- package/dist/verification-plan.js +8 -0
- package/dist/verification-plan.js.map +1 -1
- package/dist/verification.d.ts.map +1 -1
- package/dist/verification.js +45 -12
- package/dist/verification.js.map +1 -1
- package/dist/web-app-options.d.ts +19 -0
- package/dist/web-app-options.d.ts.map +1 -0
- package/dist/web-app-options.js +49 -0
- package/dist/web-app-options.js.map +1 -0
- package/dist/work-graph.d.ts +3 -0
- package/dist/work-graph.d.ts.map +1 -1
- package/dist/work-graph.js +33 -16
- package/dist/work-graph.js.map +1 -1
- package/docs/examples.json +185 -29
- package/docs/llms-full.txt +4123 -548
- package/docs/types.json +14391 -10060
- package/package.json +19 -11
- package/src/app-files.ts +40 -0
- package/src/assure-hydration.ts +139 -46
- package/src/assure.ts +17 -2
- package/src/capabilities-tool.ts +35 -9
- package/src/cdn-check.ts +268 -0
- package/src/check-diagnostics.ts +199 -797
- package/src/check-scan.ts +394 -63
- package/src/check.ts +146 -58
- package/src/cli.ts +347 -92
- package/src/command-catalog.ts +407 -45
- package/src/config-target.ts +47 -0
- package/src/contracts.ts +66 -15
- package/src/db-audit.ts +128 -0
- package/src/db-child.ts +285 -0
- package/src/db-config.ts +224 -0
- package/src/db-tool.ts +690 -0
- package/src/dev-bun-config.ts +116 -40
- package/src/dev-feed-tool.ts +912 -0
- package/src/dev-server-client.ts +267 -0
- package/src/diagnostics.ts +179 -0
- package/src/diff-tool.ts +6 -5
- package/src/docs-search.ts +10 -3
- package/src/doctor.ts +98 -15
- package/src/env-file.ts +78 -1
- package/src/fix-recipes.ts +44 -31
- package/src/frontend-guidance.ts +5 -5
- package/src/i18n-check.ts +179 -0
- package/src/init-agents.ts +301 -38
- package/src/internal/code-unit-order.ts +5 -0
- package/src/internal/script-kind.ts +11 -0
- package/src/internal/source-facts.ts +96 -22
- package/src/internal/typescript-import.ts +663 -68
- package/src/introspect.ts +93 -82
- package/src/learn.ts +3 -3
- package/src/levels-tool.ts +47 -2
- package/src/load.ts +144 -32
- package/src/manifest-tool.ts +3 -2
- package/src/mcp-app-tool.ts +2 -0
- package/src/mcp-context.ts +5 -7
- package/src/mcp-delegate.ts +162 -0
- package/src/mcp-docs-tools.ts +23 -5
- package/src/mcp-exec.ts +301 -161
- package/src/mcp-http.ts +22 -5
- package/src/mcp-io.ts +130 -0
- package/src/mcp-isolate.ts +287 -0
- package/src/mcp-path.ts +50 -0
- package/src/mcp-project-child.ts +182 -0
- package/src/mcp-reflect.ts +3 -2
- package/src/mcp-render.ts +72 -42
- package/src/mcp-root.ts +130 -24
- package/src/mcp-run.ts +190 -56
- package/src/mcp-ws.ts +4 -1
- package/src/mcp.ts +193 -91
- package/src/migrate-layout.ts +1121 -0
- package/src/openapi-tool.ts +7 -3
- package/src/openapi-types.ts +315 -13
- package/src/pipeline-report.ts +22 -18
- package/src/port.ts +53 -30
- package/src/project-facts.ts +42 -8
- package/src/recipes/4.0.0.ts +35 -0
- package/src/recipes/index.ts +2 -0
- package/src/release-verification.ts +74 -5
- package/src/review.ts +988 -0
- package/src/route-types.ts +133 -0
- package/src/rules/codes.ts +9 -0
- package/src/rules/data-guard.ts +230 -0
- package/src/rules/index.ts +18 -5
- package/src/rules/islands.ts +12 -4
- package/src/rules/legacy.ts +947 -35
- package/src/rules/nano.ts +18 -6
- package/src/rules/pages.ts +99 -0
- package/src/rules/routes.ts +181 -2
- package/src/rules/secrets.ts +174 -0
- package/src/rules/security.ts +198 -70
- package/src/rules/typescript.ts +77 -0
- package/src/rules/zones.ts +195 -0
- package/src/scaffold.ts +175 -98
- package/src/sdk.ts +1215 -158
- package/src/search-terms.ts +24 -7
- package/src/smoke.ts +607 -0
- package/src/sync-manifest.ts +2 -1
- package/src/sync-routes.ts +4 -3
- package/src/test-tool.ts +32 -11
- package/src/types-search.ts +11 -4
- package/src/upgrade.ts +205 -25
- package/src/verification-plan.ts +27 -0
- package/src/verification.ts +50 -12
- package/src/web-app-options.ts +57 -0
- package/src/work-graph.ts +40 -16
package/src/cli.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
/**
|
|
3
|
-
* `nifra` - the zero-config CLI for a nifra app. Reads `framework.ts` + `backend.ts` +
|
|
4
|
-
* the project root (see {@link loadApp}) and wires the right `@nifrajs/web`
|
|
3
|
+
* `nifra` - the zero-config CLI for a nifra app. Reads `backend/framework.ts` + `backend/app.ts` +
|
|
4
|
+
* `routes/` from the project root (see {@link loadApp}) and wires the right `@nifrajs/web`
|
|
5
|
+
* entrypoint:
|
|
5
6
|
*
|
|
6
7
|
* nifra dev true-HMR dev server (Bun native HMR + nifra SSR) - @nifrajs/web/dev
|
|
7
8
|
* nifra build emit a complete target-specific deploy directory - @nifrajs/web/build
|
|
@@ -12,26 +13,34 @@
|
|
|
12
13
|
import { existsSync, readFileSync, realpathSync } from "node:fs"
|
|
13
14
|
import { resolve } from "node:path"
|
|
14
15
|
import { fileURLToPath } from "node:url"
|
|
15
|
-
import { inProcessClient } from "@nifrajs/client"
|
|
16
16
|
import {
|
|
17
|
-
type
|
|
17
|
+
type CssLoadingMode,
|
|
18
18
|
createWebApp,
|
|
19
19
|
DEFAULT_DEV_PORT,
|
|
20
20
|
type RenderAdapter,
|
|
21
21
|
} from "@nifrajs/web"
|
|
22
22
|
import { discoverRoutes } from "@nifrajs/web/fs"
|
|
23
|
+
import { formatShadowedPages, shadowedPages } from "@nifrajs/web/route-manifest"
|
|
23
24
|
import type { BunPlugin } from "bun"
|
|
25
|
+
import { adapterFile, BACKEND_APP_FILE } from "./app-files.ts"
|
|
24
26
|
import { bindCommandArgv, findCommandSpec, renderCommandCatalogHelp } from "./command-catalog.ts"
|
|
25
|
-
import { applyEnvFiles, takeEnvFileFlags } from "./env-file.ts"
|
|
26
|
-
import { type LoadedApp, loadApp, type NifraFramework } from "./load.ts"
|
|
27
|
+
import { applyEnvFiles, forgetAutoLoadedEnv, takeEnvFileFlags } from "./env-file.ts"
|
|
28
|
+
import { FRAMEWORK_WEB_OPTIONS, type LoadedApp, loadApp, type NifraFramework } from "./load.ts"
|
|
27
29
|
import { chooseBuildPipeline, describePipeline } from "./pipeline-guard.ts"
|
|
30
|
+
import {
|
|
31
|
+
frameworkMountPaths,
|
|
32
|
+
frameworkOptionImports,
|
|
33
|
+
frameworkWebAppOptions,
|
|
34
|
+
} from "./web-app-options.ts"
|
|
28
35
|
|
|
29
36
|
export interface Flags {
|
|
30
37
|
readonly port: number
|
|
31
|
-
|
|
38
|
+
/** `--out <dir>`; without it `dist`, or `.vercel/output` for a Vercel build. */
|
|
39
|
+
readonly out: string | undefined
|
|
32
40
|
readonly poll: boolean
|
|
33
|
-
/** `nifra build --target <t>`: emit a full deploy dir for this target.
|
|
34
|
-
|
|
41
|
+
/** `nifra build --target <t>`: emit a full deploy dir for this target. Without it, the `target` that
|
|
42
|
+
* `nifra.config.ts` exports, else `bun`. */
|
|
43
|
+
readonly target: string | undefined
|
|
35
44
|
/** `nifra build --report`: print a per-chunk size + gzip table after the build. */
|
|
36
45
|
readonly report: boolean
|
|
37
46
|
/** `nifra build --vite`: force the client + server through Vite/Rollup. Without a flag the pipeline is
|
|
@@ -46,18 +55,22 @@ export interface Flags {
|
|
|
46
55
|
* a hard failure to a loud warning so a duplicate coming from a linked sibling repo doesn't take dev
|
|
47
56
|
* down while you fix the resolution. Dev only - `nifra build` always fails hard on a duplicate. */
|
|
48
57
|
readonly allowDuplicateIdentity: boolean
|
|
58
|
+
/** `nifra dev --no-indicator`: no in-page issues badge; browser errors still reach the dev feed. */
|
|
59
|
+
readonly noIndicator: boolean
|
|
49
60
|
}
|
|
50
61
|
|
|
51
62
|
/** Forward a re-exec'd Bun child's output without relying on Windows inheriting a pipe-of-a-pipe. */
|
|
52
63
|
async function forwardChildOutput(
|
|
53
64
|
stream: ReadableStream<Uint8Array>,
|
|
54
65
|
sink: { write(chunk: Uint8Array): unknown },
|
|
66
|
+
observe?: (chunk: Uint8Array) => void,
|
|
55
67
|
): Promise<void> {
|
|
56
68
|
const reader = stream.getReader()
|
|
57
69
|
try {
|
|
58
70
|
for (;;) {
|
|
59
71
|
const { done, value } = await reader.read()
|
|
60
72
|
if (done) return
|
|
73
|
+
observe?.(value)
|
|
61
74
|
await sink.write(value)
|
|
62
75
|
}
|
|
63
76
|
} finally {
|
|
@@ -65,6 +78,31 @@ async function forwardChildOutput(
|
|
|
65
78
|
}
|
|
66
79
|
}
|
|
67
80
|
|
|
81
|
+
/** The last `max` bytes of a stream, decoded: what a crashed child printed last. */
|
|
82
|
+
function streamTail(max: number): { push(chunk: Uint8Array): void; text(): string } {
|
|
83
|
+
let chunks: Uint8Array[] = []
|
|
84
|
+
let size = 0
|
|
85
|
+
return {
|
|
86
|
+
push(chunk) {
|
|
87
|
+
chunks.push(chunk)
|
|
88
|
+
size += chunk.length
|
|
89
|
+
while (size - (chunks[0]?.length ?? 0) >= max && chunks.length > 1) {
|
|
90
|
+
size -= chunks.shift()?.length ?? 0
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
text() {
|
|
94
|
+
const all = new Uint8Array(size)
|
|
95
|
+
let offset = 0
|
|
96
|
+
for (const chunk of chunks) {
|
|
97
|
+
all.set(chunk, offset)
|
|
98
|
+
offset += chunk.length
|
|
99
|
+
}
|
|
100
|
+
chunks = [all]
|
|
101
|
+
return new TextDecoder().decode(all.subarray(Math.max(0, size - max)))
|
|
102
|
+
},
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
68
106
|
const HELP = `nifra - zero-config dev/build/start for a nifra app
|
|
69
107
|
|
|
70
108
|
Usage:
|
|
@@ -80,14 +118,33 @@ Usage:
|
|
|
80
118
|
loud warning instead of a hard failure, so a duplicate React/
|
|
81
119
|
adapter copy from a linked sibling repo doesn't take dev down
|
|
82
120
|
while you fix the resolution. Dev only - \`nifra build\` still fails.
|
|
121
|
+
[--no-indicator] No in-page issues badge. Browser errors still reach
|
|
122
|
+
\`nifra errors\` and the agent tools.
|
|
123
|
+
nifra errors [--since <n>] [--category <c>] [--request <id>] What the running dev server caught:
|
|
124
|
+
SSR/loader/API/build/browser/hydration errors, each a structured
|
|
125
|
+
diagnostic. Finds the server itself; reads the log a crashed
|
|
126
|
+
one left. Exits 1 while current-code errors are open.
|
|
127
|
+
nifra logs [--since <n>] [--level <l>] [--grep <s>] The dev server's console, server and
|
|
128
|
+
browser, each line tagged with its request.
|
|
129
|
+
nifra cdn-check <url> Check a deployed page behind a CDN: served from cache, no
|
|
130
|
+
internal or CDN-only headers reaching the visitor, and soft
|
|
131
|
+
navigations get page data, not the cached document. Exits 1
|
|
132
|
+
on a failure.
|
|
133
|
+
nifra db schema [<table>] The development database declared as \`devDatabase\` in
|
|
134
|
+
nifra.config.ts: tables, columns, keys, indexes.
|
|
135
|
+
nifra db query "<sql>" [--explain [--analyze]] One read-only SELECT (or its plan), in a fresh
|
|
136
|
+
process killed at devDatabase.timeoutMs. Rows capped, secrets
|
|
137
|
+
masked. Postgres: a superuser connection is refused.
|
|
138
|
+
nifra db role Print the SQL for a read-only Postgres role. Runs nothing.
|
|
139
|
+
nifra db audit [--limit <n>] What nifra db ran (.nifra/db-audit.jsonl), never the rows.
|
|
83
140
|
nifra build [--out <dir>] [--report] Emit a complete deploy directory.
|
|
84
141
|
[--target <t>] Target a FULL deploy dir for <t>:
|
|
85
|
-
bun | node | deno |
|
|
142
|
+
bun | node | deno | cloudflare | vercel | static. Packages
|
|
86
143
|
buildClient + buildServer (+ prerender for static) so an app
|
|
87
144
|
no longer hand-writes build-<target>.ts + _worker.ts +
|
|
88
145
|
_routes.json. The server entry is generated from your
|
|
89
|
-
framework.ts (adapter) + backend.ts + routes/.
|
|
90
|
-
a per-chunk size + gzip table (biggest first).
|
|
146
|
+
backend/framework.ts (adapter) + backend/app.ts + routes/.
|
|
147
|
+
--report prints a per-chunk size + gzip table (biggest first).
|
|
91
148
|
[--vite | --bun] Force the bundler. Without a flag it follows your config: Bun
|
|
92
149
|
(faster, Bun-native) unless your ONLY transforms are
|
|
93
150
|
\`vitePlugins\`, which the Bun build cannot run - then Vite,
|
|
@@ -108,12 +165,20 @@ Usage:
|
|
|
108
165
|
against it: a route the target can't honour (an ssr route on a
|
|
109
166
|
static build, ISR where there's no revalidation) exits nonzero
|
|
110
167
|
with the consequence - run in CI so it fails the build, not prod.
|
|
111
|
-
nifra init-agents [--force] [--json] Retrofit an EXISTING app with the agent
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
168
|
+
nifra init-agents [--force] [--json] Retrofit an EXISTING app with the agent files a new app ships:
|
|
169
|
+
.mcp.json + .cursor/mcp.json (register this project's nifra MCP),
|
|
170
|
+
each agent's pointer to AGENTS.md (CLAUDE.md, GEMINI.md,
|
|
171
|
+
.cursor/rules/nifra.mdc, .github/copilot-instructions.md), and
|
|
172
|
+
AGENTS.md's "## MCP server" and (with routes/) "## Project
|
|
173
|
+
structure" sections. No-clobber by default (skips a file you've
|
|
174
|
+
customized); --force overwrites the owned files. AGENTS.md is
|
|
175
|
+
only appended to, never overwritten.
|
|
176
|
+
nifra init-agents --sync-mcp [--json] Re-pin an existing app's MCP launch to the nifra the project
|
|
177
|
+
installs: rewrites only the @nifrajs/cli@x.y.z version in
|
|
178
|
+
.mcp.json, .cursor/mcp.json, CLAUDE.md and AGENTS.md's MCP
|
|
179
|
+
section - every other byte stays as it is. Creates nothing.
|
|
180
|
+
At a workspace root with one nifra member, the two registries'
|
|
181
|
+
launch also names that member (\`mcp app\`).
|
|
117
182
|
nifra mcp [dir] Start an MCP server (stdio) exposing this project to a coding
|
|
118
183
|
agent. The project root is [dir] when given, else resolved from
|
|
119
184
|
cwd (marker walk-up + the client's MCP roots); tools refuse
|
|
@@ -122,15 +187,20 @@ Usage:
|
|
|
122
187
|
nifra_run (backend), nifra_render (SSR a page), nifra_docs,
|
|
123
188
|
nifra_example (verified snippets), nifra_scaffold (route→file),
|
|
124
189
|
nifra_check (drift gate + fixes), nifra_levels (verification
|
|
125
|
-
ladder), nifra_doctor (deps),
|
|
126
|
-
|
|
190
|
+
ladder), nifra_doctor (deps), nifra_errors + nifra_logs (what
|
|
191
|
+
the running dev server saw), nifra_db_schema + nifra_db_query +
|
|
192
|
+
nifra_db_role (the declared dev database, read-only),
|
|
193
|
+
nifra_explain (structured errors), nifra_inspect (request
|
|
194
|
+
traces), nifra_learn (guided build path).
|
|
127
195
|
nifra docs-mcp [--port <n>] Serve the PUBLIC docs MCP over HTTP (nifra_docs + nifra_example) -
|
|
128
196
|
self-host on a VPS so any remote agent can learn nifra. Default :8787.
|
|
129
197
|
nifra learn [<step>] Print the guided build-an-app path (the human view of nifra_learn):
|
|
130
198
|
no arg for the step index, a number for one step's goal/do/verify.
|
|
131
|
-
nifra check [--json] [--lints-only]
|
|
132
|
-
|
|
133
|
-
|
|
199
|
+
nifra check [--json] [--sarif] [--lints-only]
|
|
200
|
+
Gate: typecheck + lints (hand-rolled fetch(), untyped client("…"),
|
|
201
|
+
backend code in browser code). Run as "done"; --json for agents;
|
|
202
|
+
--sarif emits the same stable diagnostics as SARIF 2.1.0 for external
|
|
203
|
+
review tools; --lints-only skips tsc for a near-instant inner-loop pass.
|
|
134
204
|
nifra verify [--release] [--json] Run the shared repository verification gate. --release runs the
|
|
135
205
|
full build, test, coverage, corpus, consumer, and cross-runtime gate.
|
|
136
206
|
nifra fix [--code <NF-code>] Apply a registered diagnostic recipe, then print the remaining
|
|
@@ -148,8 +218,8 @@ Usage:
|
|
|
148
218
|
request field or a removed response field breaks; widening a
|
|
149
219
|
request enum or adding a response field doesn't) and fails
|
|
150
220
|
closed. Exits non-zero on any breaking change - run it in CI.
|
|
151
|
-
nifra sdk --lang <python|go> [--out <file>]
|
|
152
|
-
Generate a deterministic non-TypeScript SDK from backend.ts.
|
|
221
|
+
nifra sdk --lang <python|go> [--out <file>] [--strict]
|
|
222
|
+
Generate a deterministic non-TypeScript SDK from backend/app.ts.
|
|
153
223
|
nifra assure [--config <file>] [--json] Route-assurance report. Human table by default; --json emits
|
|
154
224
|
the {ok, routes, findings} report for agents.
|
|
155
225
|
nifra assure --bundle [--json] [--strict] [--out <file>] [--hydration] [--interact]
|
|
@@ -187,22 +257,40 @@ Usage:
|
|
|
187
257
|
print production readiness, with --strict making absent
|
|
188
258
|
applicable guarantees fail. --auto-fix writes safe local-
|
|
189
259
|
version dependency fixes.
|
|
190
|
-
nifra upgrade <version>
|
|
191
|
-
[--write] [--no-verify]
|
|
192
|
-
[--list] [--json]
|
|
260
|
+
nifra upgrade <version> Upgrade to <version>, running every release recipe between the
|
|
261
|
+
[--write] [--no-verify] installed version and it, oldest first: move removed packages,
|
|
262
|
+
[--list] [--json] apply exact imports, pin every matching dependency (keeping
|
|
263
|
+
[--exact] ^/~, or exact with --exact), then verify with nifra check.
|
|
193
264
|
[--allow-downgrade] Dry-run by default; --write applies then verifies (--no-verify
|
|
194
|
-
to skip). --list shows available targets.
|
|
195
|
-
|
|
196
|
-
|
|
265
|
+
to skip). --list shows available targets. A version newer than
|
|
266
|
+
this CLI prints the command for that release's CLI. Fail-closed
|
|
267
|
+
on an unknown version or a rollback (--allow-downgrade
|
|
268
|
+
overrides). Deterministic + idempotent.
|
|
269
|
+
nifra migrate layout [--write] [--json]
|
|
270
|
+
Move an app onto the frontend/backend split: split each route
|
|
271
|
+
into x.tsx + x.backend.ts, fold _middleware.ts into
|
|
272
|
+
_layout.backend.ts, move backend.ts/framework.ts under backend/,
|
|
273
|
+
and zone the other modules into frontend/, backend/ or shared/.
|
|
274
|
+
Dry-run by default; --write applies.
|
|
275
|
+
nifra types [--check] [--json] Generate each route's ./+types module under .nifra/types: its
|
|
276
|
+
params, the data its output schemas let through, and api typed
|
|
277
|
+
by backend/app.ts. dev, build and check refresh them too;
|
|
278
|
+
--check fails when one is stale.
|
|
197
279
|
nifra port [--target <t>] [--json] Portability linter: print a feature × deploy-target capability
|
|
198
280
|
[--ci] [--strict] matrix (in-memory stores, in-process cron/WebSocket, Bun/Deno
|
|
199
281
|
globals, node: builtins) with file:line evidence. --target auto-
|
|
200
282
|
detected from build/deploy scripts, wrangler.toml, or vercel config;
|
|
201
283
|
--ci (or any --target) exits nonzero when a used feature is
|
|
202
284
|
unsupported on the target; --strict also fails on caveats.
|
|
285
|
+
nifra i18n check [entry] Check i18n catalogs: coverage, missing + unused keys, ICU syntax,
|
|
286
|
+
[--json] [--strict] placeholder + tag parity, plural cases, script purity,
|
|
287
|
+
untranslated messages. IMPORTS the entry (default: the first
|
|
288
|
+
i18n.ts in ., lib/, src/, src/lib/, app/), which exports
|
|
289
|
+
\`locales\` + \`catalogs\` (+ optional \`ignore\`). Exits 1 on
|
|
290
|
+
errors; --strict also on warnings.
|
|
203
291
|
|
|
204
|
-
Reads nifra.config.ts (adapter + clientModule + plugins; or framework.ts), backend.ts
|
|
205
|
-
routes/ from the current directory. Run from your project root.
|
|
292
|
+
Reads nifra.config.ts (adapter + clientModule + plugins; or backend/framework.ts), backend/app.ts
|
|
293
|
+
(optional), and routes/ from the current directory. Run from your project root.
|
|
206
294
|
|
|
207
295
|
Port: \`dev\` and \`start\` share the default ${DEFAULT_DEV_PORT}. Override with \`--port <n>\` (alias \`-p\`) or the
|
|
208
296
|
\`PORT\` env var (\`--port\` wins over \`PORT\`, which wins over the default).
|
|
@@ -216,15 +304,11 @@ set in the process environment is never overwritten by a file.
|
|
|
216
304
|
${renderCommandCatalogHelp()}`
|
|
217
305
|
|
|
218
306
|
// Kept in lockstep with packages/cli/package.json by check:publish's version-consistency gate.
|
|
219
|
-
const CLI_VERSION = "
|
|
307
|
+
const CLI_VERSION = "4.0.0"
|
|
220
308
|
|
|
221
309
|
// A render adapter + nifra server are opaque to the CLI (it just forwards them); cast at the seam.
|
|
222
310
|
const asAdapter = (v: unknown): RenderAdapter => v as RenderAdapter
|
|
223
311
|
const asBunPlugins = (v: readonly unknown[]): BunPlugin[] => v as BunPlugin[]
|
|
224
|
-
const asUse = (v: (app: never) => void): NonNullable<CreateWebAppOptions["use"]> =>
|
|
225
|
-
v as NonNullable<CreateWebAppOptions["use"]>
|
|
226
|
-
const apiOf = (backend: unknown): { api?: unknown } =>
|
|
227
|
-
backend === undefined ? {} : { api: inProcessClient(backend as never) }
|
|
228
312
|
|
|
229
313
|
/**
|
|
230
314
|
* Render an error for the CLI, unwrapping the detail a bare `.message` drops.
|
|
@@ -281,7 +365,7 @@ async function dev(app: LoadedApp, flags: Flags): Promise<void> {
|
|
|
281
365
|
)
|
|
282
366
|
if (decision.pipeline === "bun") {
|
|
283
367
|
// Bun's dev-server bundler takes plugins only via bunfig `[serve.static]`, read at process
|
|
284
|
-
// start - so the boundary plugins (
|
|
368
|
+
// start - so the boundary plugins (zone guard, server-fn stubs) are delivered by
|
|
285
369
|
// generating a config and re-execing this same command once with `--config=`. The child proves
|
|
286
370
|
// it IS the configured child with a per-launch random token (matched against the file the
|
|
287
371
|
// parent just wrote, consumed on first read). A fixed sentinel here would be a secret-leak
|
|
@@ -299,7 +383,11 @@ async function dev(app: LoadedApp, flags: Flags): Promise<void> {
|
|
|
299
383
|
"launches; refusing to serve without the client-boundary plugins.",
|
|
300
384
|
)
|
|
301
385
|
}
|
|
302
|
-
const { bunfigPath, launchToken } = await writeBunDevConfig(
|
|
386
|
+
const { bunfigPath, launchToken } = await writeBunDevConfig(
|
|
387
|
+
app.cwd,
|
|
388
|
+
app.configPath,
|
|
389
|
+
app.framework.define,
|
|
390
|
+
)
|
|
303
391
|
// Bun 1.4 on Windows may discard the script-relative portion of argv when `--config` is
|
|
304
392
|
// present. Keep a second, authenticated copy of the user vector in the child's environment so
|
|
305
393
|
// the configured process cannot silently fall back to `nifra`'s help and exit 0. The launch token
|
|
@@ -350,16 +438,30 @@ async function dev(app: LoadedApp, flags: Flags): Promise<void> {
|
|
|
350
438
|
},
|
|
351
439
|
},
|
|
352
440
|
)
|
|
441
|
+
// The child's last words, kept so a crash leaves a record `nifra errors` can read after the fact.
|
|
442
|
+
const stderrTail = streamTail(16 * 1024)
|
|
353
443
|
const forwarded = Promise.all([
|
|
354
444
|
forwardChildOutput(child.stdout as ReadableStream<Uint8Array>, Bun.stdout),
|
|
355
|
-
forwardChildOutput(child.stderr as ReadableStream<Uint8Array>, Bun.stderr),
|
|
445
|
+
forwardChildOutput(child.stderr as ReadableStream<Uint8Array>, Bun.stderr, stderrTail.push),
|
|
356
446
|
])
|
|
357
|
-
|
|
447
|
+
let interrupted = false
|
|
448
|
+
const forward = (): void => {
|
|
449
|
+
interrupted = true
|
|
450
|
+
child.kill("SIGINT")
|
|
451
|
+
}
|
|
358
452
|
process.on("SIGINT", forward)
|
|
359
453
|
process.on("SIGTERM", forward)
|
|
360
454
|
try {
|
|
361
455
|
const [code] = await Promise.all([child.exited, forwarded])
|
|
362
456
|
process.exitCode = code
|
|
457
|
+
if (code !== 0 && !interrupted) {
|
|
458
|
+
try {
|
|
459
|
+
const { recordDevCrash } = await import("@nifrajs/web/dev-feed")
|
|
460
|
+
recordDevCrash(app.cwd, code, stderrTail.text())
|
|
461
|
+
} catch {
|
|
462
|
+
// The crash is already on the terminal; failing to persist it must not mask the exit code.
|
|
463
|
+
}
|
|
464
|
+
}
|
|
363
465
|
} finally {
|
|
364
466
|
process.off("SIGINT", forward)
|
|
365
467
|
process.off("SIGTERM", forward)
|
|
@@ -408,13 +510,14 @@ async function dev(app: LoadedApp, flags: Flags): Promise<void> {
|
|
|
408
510
|
...(fw.publicDir !== undefined ? { publicDir: fw.publicDir } : {}),
|
|
409
511
|
...(fw.conditions ? { conditions: fw.conditions } : {}),
|
|
410
512
|
...(fw.define ? { define: fw.define } : {}),
|
|
411
|
-
|
|
513
|
+
indicator: showsIndicator(flags, fw),
|
|
514
|
+
createApp: (clientEntry, importQuery, dev) =>
|
|
412
515
|
createWebApp({
|
|
413
516
|
adapter: asAdapter(fw.adapter),
|
|
414
517
|
manifest: discoverRoutes(routesDir, { importQuery }),
|
|
415
518
|
clientEntry,
|
|
416
|
-
...(fw
|
|
417
|
-
|
|
519
|
+
...frameworkWebAppOptions(fw, backend),
|
|
520
|
+
onLoaderError: dev.onLoaderError,
|
|
418
521
|
}),
|
|
419
522
|
})
|
|
420
523
|
console.log(`nifra dev (bun) → http://localhost:${server.port}`)
|
|
@@ -464,18 +567,19 @@ async function dev(app: LoadedApp, flags: Flags): Promise<void> {
|
|
|
464
567
|
poll: flags.poll,
|
|
465
568
|
port: flags.port,
|
|
466
569
|
...(flags.allowDuplicateIdentity ? { allowDuplicateIdentity: true } : {}),
|
|
570
|
+
indicator: showsIndicator(flags, fw),
|
|
467
571
|
...(fw.conditions ? { conditions: fw.conditions } : {}),
|
|
468
572
|
...(fw.define ? { define: fw.define } : {}),
|
|
469
573
|
// `load` resolves route modules through VITE, not through Bun. That is what makes the Vite
|
|
470
574
|
// pipeline own the whole phase: the client and the server now agree on every specifier, so
|
|
471
575
|
// `resolve.dedupe` finally governs SSR and the dual-React crash cannot occur.
|
|
472
|
-
createApp: (clientEntry, load) =>
|
|
576
|
+
createApp: (clientEntry, load, dev) =>
|
|
473
577
|
createWebApp({
|
|
474
578
|
adapter: asAdapter(fw.adapter),
|
|
475
579
|
manifest: discoverRoutes(routesDir, { load }),
|
|
476
580
|
clientEntry,
|
|
477
|
-
...(fw
|
|
478
|
-
|
|
581
|
+
...frameworkWebAppOptions(fw, backend),
|
|
582
|
+
onLoaderError: dev.onLoaderError,
|
|
479
583
|
}),
|
|
480
584
|
})
|
|
481
585
|
console.log(`nifra dev (vite) → http://localhost:${server.port}`)
|
|
@@ -485,23 +589,26 @@ async function dev(app: LoadedApp, flags: Flags): Promise<void> {
|
|
|
485
589
|
/**
|
|
486
590
|
* `nifra build --target <t>` - package the engine (buildClient + buildServer + prerender) into one
|
|
487
591
|
* command that emits a full deploy dir, so an app no longer hand-writes build-bun.ts + _worker.ts +
|
|
488
|
-
* _routes.json per target. The adapter is imported from `framework.ts` (the edge-bundlable
|
|
489
|
-
* `nifra.config.ts`, which pulls in Vite plugins), and the backend from
|
|
490
|
-
* `buildTarget` generates the per-target server entry from those +
|
|
592
|
+
* _routes.json per target. The adapter is imported from `backend/framework.ts` (the edge-bundlable
|
|
593
|
+
* file - never `nifra.config.ts`, which pulls in Vite plugins), and the backend from
|
|
594
|
+
* `backend/app.ts` when present; `buildTarget` generates the per-target server entry from those +
|
|
595
|
+
* the app's `routes/`.
|
|
491
596
|
*/
|
|
492
597
|
/**
|
|
493
598
|
* Refuse a `use` the generated server entry cannot import.
|
|
494
599
|
*
|
|
495
|
-
* `nifra build` emits `import { use } from <frameworkFile>` (framework.ts, the
|
|
496
|
-
* but `loadApp` prefers `nifra.config.ts` - so an app with BOTH files whose
|
|
497
|
-
* `nifra.config.ts` makes `fw.use` defined while `framework.ts` exports
|
|
498
|
-
* build dies later with an opaque bundler error pointing at generated
|
|
499
|
-
* exact move instead, in the spirit of `assertPipelineSeparation`
|
|
600
|
+
* `nifra build` emits `import { use } from <frameworkFile>` (backend/framework.ts, the
|
|
601
|
+
* edge-bundlable file), but `loadApp` prefers `nifra.config.ts` - so an app with BOTH files whose
|
|
602
|
+
* `use` lives only in `nifra.config.ts` makes `fw.use` defined while `backend/framework.ts` exports
|
|
603
|
+
* nothing of that name, and the build dies later with an opaque bundler error pointing at generated
|
|
604
|
+
* code. Refuse it here with the exact move instead, in the spirit of `assertPipelineSeparation`
|
|
605
|
+
* (load.ts).
|
|
500
606
|
*
|
|
501
|
-
* Detected by importing `frameworkFile` and checking the named export, not by comparing paths
|
|
502
|
-
* a split app legitimately DEFINES `use` in framework.ts and re-exports it from
|
|
503
|
-
* (exactly how `adapter` reaches both readers), and a path compare would refuse
|
|
504
|
-
* The import is cheap - framework.ts is already in the loaded config's
|
|
607
|
+
* Detected by importing `frameworkFile` and checking the named export, not by comparing paths
|
|
608
|
+
* alone: a split app legitimately DEFINES `use` in backend/framework.ts and re-exports it from
|
|
609
|
+
* nifra.config.ts (exactly how `adapter` reaches both readers), and a path compare would refuse
|
|
610
|
+
* that correct layout. The import is cheap - backend/framework.ts is already in the loaded config's
|
|
611
|
+
* module graph in that layout.
|
|
505
612
|
*/
|
|
506
613
|
export async function assertUseIsEdgeExported(
|
|
507
614
|
use: NifraFramework["use"],
|
|
@@ -513,13 +620,65 @@ export async function assertUseIsEdgeExported(
|
|
|
513
620
|
if (typeof mod.use === "function") return
|
|
514
621
|
throw new Error(
|
|
515
622
|
`[nifra] \`use\` is exported from ${configPath} but not from ${frameworkFile}. ` +
|
|
516
|
-
"`nifra build` generates a server entry that imports `use` from framework.ts (the
|
|
517
|
-
"file), so this build would fail later with an opaque bundler error inside generated code.\n\n" +
|
|
518
|
-
" - Define `use` in framework.ts and re-export it from nifra.config.ts " +
|
|
519
|
-
'(`export { use } from "./framework.ts"`) so `nifra dev` sees it too.',
|
|
623
|
+
"`nifra build` generates a server entry that imports `use` from backend/framework.ts (the " +
|
|
624
|
+
"edge-bundled file), so this build would fail later with an opaque bundler error inside generated code.\n\n" +
|
|
625
|
+
" - Define `use` in backend/framework.ts and re-export it from nifra.config.ts " +
|
|
626
|
+
'(`export { use } from "./backend/framework.ts"`) so `nifra dev` sees it too.',
|
|
520
627
|
)
|
|
521
628
|
}
|
|
522
629
|
|
|
630
|
+
/**
|
|
631
|
+
* Refuse a forwarded framework field (`apiPrefix`, `mounts`, `csp`, ...) the generated server entry
|
|
632
|
+
* would not see, or would see with a different value.
|
|
633
|
+
*
|
|
634
|
+
* `nifra dev` reads these from the loaded config (`nifra.config.ts` when it exists) while the
|
|
635
|
+
* server entry imports them from backend/framework.ts. A field set only in nifra.config.ts would be
|
|
636
|
+
* missing from production, and one defined separately in each file could differ - dev mounting the
|
|
637
|
+
* backend at one path while production mounts it at another. Identity is required, which a
|
|
638
|
+
* re-export satisfies.
|
|
639
|
+
*/
|
|
640
|
+
export async function assertFrameworkOptionsEdgeExported(
|
|
641
|
+
fw: NifraFramework,
|
|
642
|
+
configPath: string,
|
|
643
|
+
frameworkFile: string,
|
|
644
|
+
): Promise<void> {
|
|
645
|
+
if (configPath === frameworkFile) return
|
|
646
|
+
const set = FRAMEWORK_WEB_OPTIONS.filter((name) => fw[name] !== undefined)
|
|
647
|
+
if (set.length === 0) return
|
|
648
|
+
const mod = (await import(frameworkFile).catch(() => ({}))) as Record<string, unknown>
|
|
649
|
+
const drifted = set.filter((name) => mod[name] !== fw[name])
|
|
650
|
+
if (drifted.length === 0) return
|
|
651
|
+
const names = drifted.map((name) => `\`${name}\``).join(", ")
|
|
652
|
+
throw new Error(
|
|
653
|
+
`[nifra] ${names} ${drifted.length === 1 ? "is" : "are"} exported from ${configPath} but not, or not as the same value, from ${frameworkFile}. ` +
|
|
654
|
+
"`nifra build` generates a server entry that imports these from backend/framework.ts, so production " +
|
|
655
|
+
"would serve a different app than `nifra dev`.\n\n" +
|
|
656
|
+
` - Define ${drifted.length === 1 ? "it" : "them"} in backend/framework.ts and re-export from nifra.config.ts ` +
|
|
657
|
+
`(\`export { ${drifted.join(", ")} } from "./backend/framework.ts"\`).`,
|
|
658
|
+
)
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
/**
|
|
662
|
+
* Refuse a build whose page routes sit under a mount: each would ship as a route that can never render.
|
|
663
|
+
* Read from the config and `routes/` alone; the server also checks its own mount table at startup,
|
|
664
|
+
* which covers a mount added inside `use`.
|
|
665
|
+
*/
|
|
666
|
+
/** `nifra dev` shows the in-page issues badge unless `--no-indicator` or `dev.indicator: false` turns it off. */
|
|
667
|
+
export function showsIndicator(
|
|
668
|
+
flags: Pick<Flags, "noIndicator">,
|
|
669
|
+
fw: Pick<NifraFramework, "dev">,
|
|
670
|
+
): boolean {
|
|
671
|
+
return !flags.noIndicator && fw.dev?.indicator !== false
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
export function assertNoShadowedPages(app: LoadedApp): void {
|
|
675
|
+
const paths = frameworkMountPaths(app.framework, app.backend !== undefined)
|
|
676
|
+
if (paths.length === 0) return
|
|
677
|
+
const shadowed = shadowedPages(discoverRoutes(app.routesDir), paths)
|
|
678
|
+
if (shadowed.length === 0) return
|
|
679
|
+
throw new Error(`[nifra] build blocked: ${formatShadowedPages(shadowed)}`)
|
|
680
|
+
}
|
|
681
|
+
|
|
523
682
|
/**
|
|
524
683
|
* A production build must not ship a workspace-linked package whose `default`-condition artifact is
|
|
525
684
|
* missing or older than its source. The `"bun": "./src"` / `"default": "./dist"` split means Bun (this
|
|
@@ -554,38 +713,71 @@ async function assertFreshWorkspaceDists(cwd: string): Promise<void> {
|
|
|
554
713
|
)
|
|
555
714
|
}
|
|
556
715
|
|
|
557
|
-
async function buildForTarget(
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
716
|
+
async function buildForTarget(
|
|
717
|
+
app: LoadedApp,
|
|
718
|
+
flag: string | undefined,
|
|
719
|
+
flags: Flags,
|
|
720
|
+
): Promise<void> {
|
|
721
|
+
const { parseBuildTarget, renderSizeReport } = await import("@nifrajs/web/build")
|
|
722
|
+
const target = parseBuildTarget(
|
|
723
|
+
flag ?? app.framework.target ?? "bun",
|
|
724
|
+
flag === undefined ? "target in nifra.config.ts" : "--target",
|
|
725
|
+
)
|
|
562
726
|
if (flags.vite && flags.bun) {
|
|
563
727
|
throw new Error("[nifra] `nifra build` takes `--vite` or `--bun`, not both.")
|
|
564
728
|
}
|
|
729
|
+
const { framework: fw } = app
|
|
730
|
+
const cssCodeSplitRequested = fw.cssCodeSplit === false
|
|
731
|
+
const hasBunTransforms =
|
|
732
|
+
app.resolvedPlugins.clientPlugins.length > 0 || app.resolvedPlugins.serverPlugins.length > 0
|
|
733
|
+
if (cssCodeSplitRequested && flags.bun) {
|
|
734
|
+
throw new Error(
|
|
735
|
+
"[nifra] `cssCodeSplit: false` is a Vite-only production option, but `nifra build --bun` was requested. " +
|
|
736
|
+
"Drop `--bun` (Nifra will select Vite automatically) or remove the aggregate CSS setting.",
|
|
737
|
+
)
|
|
738
|
+
}
|
|
739
|
+
if (cssCodeSplitRequested && hasBunTransforms) {
|
|
740
|
+
throw new Error(
|
|
741
|
+
"[nifra] `cssCodeSplit: false` requires the Vite production pipeline, but this app also declares " +
|
|
742
|
+
"Bun client/server plugins. Use `--vite` with Vite equivalents, or remove the aggregate CSS setting; " +
|
|
743
|
+
"switching pipelines would silently drop the Bun transforms.",
|
|
744
|
+
)
|
|
745
|
+
}
|
|
565
746
|
// Which bundler builds this app. Default is Bun; an app whose ONLY transforms are `vitePlugins` gets
|
|
566
747
|
// Vite instead, because the Bun build cannot run them and would silently omit the work. `--vite`/`--bun`
|
|
567
748
|
// force it, and the one forced combination that would drop transforms throws.
|
|
568
|
-
|
|
749
|
+
let decision = chooseBuildPipeline(
|
|
569
750
|
app.resolvedPlugins,
|
|
570
751
|
flags.vite ? "vite" : flags.bun ? "bun" : undefined,
|
|
571
752
|
)
|
|
753
|
+
if (cssCodeSplitRequested && decision.pipeline === "bun") {
|
|
754
|
+
// With no Bun transforms, aggregate CSS is a Vite-only request that can be honored safely by
|
|
755
|
+
// selecting the same pipeline the user would otherwise have to force explicitly.
|
|
756
|
+
decision = {
|
|
757
|
+
pipeline: "vite",
|
|
758
|
+
why: "auto",
|
|
759
|
+
reason: "auto: this app requested Vite's aggregate CSS output (`cssCodeSplit: false`)",
|
|
760
|
+
}
|
|
761
|
+
}
|
|
572
762
|
const useVite = decision.pipeline === "vite"
|
|
573
763
|
// Same deploy-dir output - buildTargetVite delegates to the same orchestrator as buildTarget - so only
|
|
574
764
|
// the bundler and the plugin FORMAT (Vite plugins, not Bun) differ.
|
|
575
765
|
const buildTarget = useVite
|
|
576
766
|
? (await import("@nifrajs/web/build-vite")).buildTargetVite
|
|
577
767
|
: (await import("@nifrajs/web/build")).buildTarget
|
|
578
|
-
const {
|
|
579
|
-
//
|
|
580
|
-
//
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
const backendFile = resolve(cwd,
|
|
768
|
+
const { routesDir, cwd, backend } = app
|
|
769
|
+
// `vercel deploy --prebuilt` uploads `.vercel/output` from the project root, so that is where a
|
|
770
|
+
// Vercel build goes unless `--out` says otherwise.
|
|
771
|
+
const outDir =
|
|
772
|
+
target === "vercel" && flags.out === undefined ? resolve(cwd, ".vercel/output") : app.outDir
|
|
773
|
+
// The server entry must import the adapter from `backend/framework.ts` (edge-safe), not the
|
|
774
|
+
// loaded config, so a multi-target app's Vite-plugin config never reaches the edge bundle
|
|
775
|
+
// (load.ts header).
|
|
776
|
+
const frameworkFile = adapterFile(cwd)
|
|
777
|
+
const backendFile = resolve(cwd, BACKEND_APP_FILE)
|
|
588
778
|
await assertUseIsEdgeExported(fw.use, app.configPath, frameworkFile)
|
|
779
|
+
await assertFrameworkOptionsEdgeExported(fw, app.configPath, frameworkFile)
|
|
780
|
+
assertNoShadowedPages(app)
|
|
589
781
|
// Plugin FORMAT differs by pipeline: the Bun build takes Bun plugins (clientPlugins/serverPlugins); the
|
|
590
782
|
// Vite build takes the app's Vite plugins (fw.vitePlugins) for BOTH halves. buildTargetWith forwards
|
|
591
783
|
// whatever it's given straight to the chosen bundler, which casts to its own plugin type.
|
|
@@ -605,12 +797,17 @@ async function buildForTarget(app: LoadedApp, target: string, flags: Flags): Pro
|
|
|
605
797
|
clientModule: fw.clientModule,
|
|
606
798
|
adapterImport: frameworkFile,
|
|
607
799
|
...(fw.use ? { useImport: frameworkFile } : {}),
|
|
800
|
+
...frameworkOptionImports(fw, frameworkFile),
|
|
608
801
|
...(backend !== undefined && existsSync(backendFile) ? { backendImport: backendFile } : {}),
|
|
609
802
|
...plugins,
|
|
610
803
|
...(fw.conditions ? { conditions: fw.conditions } : {}),
|
|
611
804
|
...(fw.define ? { define: fw.define } : {}),
|
|
612
805
|
...(fw.publicDir !== undefined ? { publicDir: fw.publicDir } : {}),
|
|
613
806
|
...(fw.publicEnvPrefix !== undefined ? { publicEnvPrefix: fw.publicEnvPrefix } : {}),
|
|
807
|
+
...(fw.secretExemptions !== undefined ? { secretExemptions: fw.secretExemptions } : {}),
|
|
808
|
+
...(fw.cssCodeSplit !== undefined ? { cssCodeSplit: fw.cssCodeSplit } : {}),
|
|
809
|
+
...(fw.cssLoading !== undefined ? { cssLoading: fw.cssLoading } : {}),
|
|
810
|
+
...(fw.clientIp !== undefined ? { clientIp: fw.clientIp } : {}),
|
|
614
811
|
// The static target needs a built app to drive prerendering - only build it when targeting static.
|
|
615
812
|
...(target === "static" ? { prerenderApp: await buildPrerenderApp(app) } : {}),
|
|
616
813
|
})
|
|
@@ -643,11 +840,11 @@ async function buildPrerenderApp(
|
|
|
643
840
|
adapter: asAdapter(fw.adapter),
|
|
644
841
|
manifest: discoverRoutes(routesDir),
|
|
645
842
|
clientEntry: client.entry,
|
|
646
|
-
...(fw
|
|
843
|
+
...frameworkWebAppOptions(fw, backend),
|
|
647
844
|
...(client.routes ? { routePreload: client.routes } : {}),
|
|
648
845
|
...(client.css ? { styles: client.css } : {}),
|
|
649
846
|
...(client.routeStyles ? { routeStyles: client.routeStyles } : {}),
|
|
650
|
-
...
|
|
847
|
+
...(client.cssLoading !== undefined ? { cssLoading: client.cssLoading } : {}),
|
|
651
848
|
})
|
|
652
849
|
}
|
|
653
850
|
|
|
@@ -656,13 +853,14 @@ interface BuiltManifest {
|
|
|
656
853
|
readonly routes?: Readonly<Record<string, readonly string[]>>
|
|
657
854
|
readonly css?: readonly string[]
|
|
658
855
|
readonly routeStyles?: Readonly<Record<string, readonly string[]>>
|
|
856
|
+
readonly cssLoading?: CssLoadingMode
|
|
659
857
|
}
|
|
660
858
|
|
|
661
859
|
async function start(app: LoadedApp, flags: Flags): Promise<void> {
|
|
662
860
|
const serverFile = resolve(app.outDir, "server.js")
|
|
663
861
|
if (!existsSync(serverFile)) {
|
|
664
862
|
// A Cloudflare Pages build emits `_worker.js` (a Workers bundle), not a self-hosting `server.js` - so
|
|
665
|
-
// `nifra start` on a dir built for
|
|
863
|
+
// `nifra start` on a dir built for cloudflare would otherwise fail with a bare "no server.js". Name the
|
|
666
864
|
// actual mismatch and the fix.
|
|
667
865
|
if (existsSync(resolve(app.outDir, "_worker.js"))) {
|
|
668
866
|
throw new Error(
|
|
@@ -683,13 +881,14 @@ export function parseFlags(args: readonly string[]): Flags {
|
|
|
683
881
|
// the SAME uncommon port for `nifra dev` and `nifra start` (DEFAULT_DEV_PORT) so a project's URL is
|
|
684
882
|
// stable across commands and doesn't collide with the usual 3000/5173/8080 crowd.
|
|
685
883
|
let port = Number(Bun.env.PORT ?? DEFAULT_DEV_PORT)
|
|
686
|
-
let out
|
|
884
|
+
let out: string | undefined
|
|
687
885
|
let poll = Bun.env.CHOKIDAR_USEPOLLING === "1"
|
|
688
|
-
let target
|
|
886
|
+
let target: string | undefined
|
|
689
887
|
let report = false
|
|
690
888
|
let vite = false
|
|
691
889
|
let bun = false
|
|
692
890
|
let allowDuplicateIdentity = false
|
|
891
|
+
let noIndicator = false
|
|
693
892
|
for (let i = 0; i < args.length; i++) {
|
|
694
893
|
const a = args[i]
|
|
695
894
|
if ((a === "--port" || a === "-p") && args[i + 1]) port = Number(args[++i])
|
|
@@ -700,11 +899,12 @@ export function parseFlags(args: readonly string[]): Flags {
|
|
|
700
899
|
else if (a === "--vite") vite = true
|
|
701
900
|
else if (a === "--bun") bun = true
|
|
702
901
|
else if (a === "--allow-duplicate-identity") allowDuplicateIdentity = true
|
|
902
|
+
else if (a === "--no-indicator") noIndicator = true
|
|
703
903
|
}
|
|
704
904
|
if (!Number.isFinite(port) || port < 0 || port > 65535) {
|
|
705
905
|
throw new Error(`[nifra] invalid --port: ${port}`)
|
|
706
906
|
}
|
|
707
|
-
return { port, out, poll, target, report, vite, bun, allowDuplicateIdentity }
|
|
907
|
+
return { port, out, poll, target, report, vite, bun, allowDuplicateIdentity, noIndicator }
|
|
708
908
|
}
|
|
709
909
|
|
|
710
910
|
/**
|
|
@@ -745,8 +945,17 @@ function installReflectionExitHint(): (command: string | undefined) => void {
|
|
|
745
945
|
}
|
|
746
946
|
}
|
|
747
947
|
|
|
948
|
+
/** Set on the copy of `nifra mcp` that serves without `.env` files, so it does not restart again. */
|
|
949
|
+
const MCP_WITHOUT_ENV_FILES = "NIFRA_MCP_WITHOUT_ENV_FILES"
|
|
950
|
+
|
|
748
951
|
async function main(): Promise<void> {
|
|
749
952
|
const { argv: rawArgv, files: envFiles } = takeEnvFileFlags(cliArgv())
|
|
953
|
+
// `nifra mcp` keeps none of the `.env` values Bun loaded at startup. Decided before the
|
|
954
|
+
// `--env-file`s below, which the caller names explicitly and which stay.
|
|
955
|
+
const forgotten =
|
|
956
|
+
rawArgv[0] === "mcp" && process.env[MCP_WITHOUT_ENV_FILES] !== "1"
|
|
957
|
+
? await forgetAutoLoadedEnv()
|
|
958
|
+
: []
|
|
750
959
|
// Applied before any command runs: a reflecting command imports the app on its first await, and the
|
|
751
960
|
// app reads its environment at module scope, so the variables must already be in place by then.
|
|
752
961
|
if (envFiles.length > 0) await applyEnvFiles(process.cwd(), envFiles)
|
|
@@ -769,6 +978,19 @@ async function main(): Promise<void> {
|
|
|
769
978
|
// `mcp` runs a long-lived stdio server and loads the project lazily per-tool - it must not go through
|
|
770
979
|
// the eager `loadApp` below (which would fail fast on a project that's API-only / not yet built).
|
|
771
980
|
if (command === "mcp") {
|
|
981
|
+
// Bun passes what it loaded to every subprocess whatever `process.env` says now, so the session
|
|
982
|
+
// moves to a copy of this process that never loads `.env` files.
|
|
983
|
+
if (forgotten.length > 0) {
|
|
984
|
+
const { handOffSession } = await import("./mcp-delegate.ts")
|
|
985
|
+
const exitCode = await handOffSession(
|
|
986
|
+
[process.execPath, "--no-env-file", ...process.argv.slice(1)],
|
|
987
|
+
{ cwd: process.cwd(), env: { ...process.env, [MCP_WITHOUT_ENV_FILES]: "1" } },
|
|
988
|
+
)
|
|
989
|
+
if (exitCode !== undefined) {
|
|
990
|
+
process.exitCode = exitCode
|
|
991
|
+
return
|
|
992
|
+
}
|
|
993
|
+
}
|
|
772
994
|
const { runMcpServer } = await import("./mcp.ts")
|
|
773
995
|
// `nifra mcp [dir]` - an explicit project directory pins the root (for clients configured outside
|
|
774
996
|
// the project); otherwise the server resolves it from cwd + the client's MCP roots.
|
|
@@ -820,11 +1042,17 @@ async function main(): Promise<void> {
|
|
|
820
1042
|
)
|
|
821
1043
|
return
|
|
822
1044
|
}
|
|
823
|
-
|
|
1045
|
+
// `nifra db <sub>` is the catalog's `db-<sub>` command, projected to MCP as `nifra_db_<sub>`.
|
|
1046
|
+
const dbSub =
|
|
1047
|
+
command === "db" && argv[1] !== undefined && !argv[1].startsWith("-") ? argv[1] : undefined
|
|
1048
|
+
if (command === "db" && (dbSub === undefined || findCommandSpec(`db-${dbSub}`) === undefined)) {
|
|
1049
|
+
throw new Error("[nifra] usage: nifra db schema|query|role|audit - see `nifra help`")
|
|
1050
|
+
}
|
|
1051
|
+
const catalogSpec = findCommandSpec(dbSub === undefined ? command : `db-${dbSub}`)
|
|
824
1052
|
if (catalogSpec?.transports.includes("cli")) {
|
|
825
1053
|
const markReflecting = installReflectionExitHint()
|
|
826
1054
|
try {
|
|
827
|
-
const input = bindCommandArgv(catalogSpec, argv.slice(1))
|
|
1055
|
+
const input = bindCommandArgv(catalogSpec, argv.slice(dbSub === undefined ? 1 : 2))
|
|
828
1056
|
markReflecting(command)
|
|
829
1057
|
const output = await catalogSpec.run(input, { cwd: process.cwd(), cliVersion: CLI_VERSION })
|
|
830
1058
|
markReflecting(undefined)
|
|
@@ -836,14 +1064,27 @@ async function main(): Promise<void> {
|
|
|
836
1064
|
} else {
|
|
837
1065
|
console.log(catalogSpec.render(output, input).join("\n"))
|
|
838
1066
|
}
|
|
839
|
-
if (catalogSpec.
|
|
1067
|
+
if (catalogSpec.exitCode !== undefined) {
|
|
1068
|
+
process.exitCode = catalogSpec.exitCode(output, input)
|
|
1069
|
+
} else if (catalogSpec.success !== undefined && !catalogSpec.success(output, input)) {
|
|
840
1070
|
process.exitCode = 1
|
|
1071
|
+
}
|
|
841
1072
|
} catch (err) {
|
|
842
1073
|
// A thrown error is nifra's own reporting path, not a silent abort: clear the sentinel so the
|
|
843
1074
|
// hint stays reserved for the case where the process died inside the app import.
|
|
844
1075
|
markReflecting(undefined)
|
|
845
1076
|
console.error(formatCliError(err))
|
|
846
|
-
|
|
1077
|
+
const candidate =
|
|
1078
|
+
typeof err === "object" && err !== null && "exitCode" in err
|
|
1079
|
+
? (err as { readonly exitCode?: unknown }).exitCode
|
|
1080
|
+
: undefined
|
|
1081
|
+
process.exitCode =
|
|
1082
|
+
typeof candidate === "number" &&
|
|
1083
|
+
Number.isInteger(candidate) &&
|
|
1084
|
+
candidate >= 0 &&
|
|
1085
|
+
candidate <= 255
|
|
1086
|
+
? candidate
|
|
1087
|
+
: 1
|
|
847
1088
|
}
|
|
848
1089
|
return
|
|
849
1090
|
}
|
|
@@ -856,7 +1097,7 @@ async function main(): Promise<void> {
|
|
|
856
1097
|
if (!ok) process.exitCode = 1
|
|
857
1098
|
return
|
|
858
1099
|
}
|
|
859
|
-
// `init-agents` retrofits the agent
|
|
1100
|
+
// `init-agents` retrofits the agent files (.mcp.json, CLAUDE.md, AGENTS.md, …) into the cwd. It's a
|
|
860
1101
|
// pure file-writing command independent of the app loading, so dispatch it before the eager `loadApp`
|
|
861
1102
|
// (an existing app might be API-only or not yet built). It always succeeds unless a write throws.
|
|
862
1103
|
if (command === "init-agents") {
|
|
@@ -864,6 +1105,7 @@ async function main(): Promise<void> {
|
|
|
864
1105
|
await runInitAgents(process.cwd(), {
|
|
865
1106
|
json: argv.includes("--json"),
|
|
866
1107
|
force: argv.includes("--force"),
|
|
1108
|
+
syncMcp: argv.includes("--sync-mcp"),
|
|
867
1109
|
})
|
|
868
1110
|
return
|
|
869
1111
|
}
|
|
@@ -884,7 +1126,11 @@ async function main(): Promise<void> {
|
|
|
884
1126
|
}
|
|
885
1127
|
try {
|
|
886
1128
|
const { runSdk } = await import("./sdk.ts")
|
|
887
|
-
await runSdk(process.cwd(), {
|
|
1129
|
+
await runSdk(process.cwd(), {
|
|
1130
|
+
language,
|
|
1131
|
+
...(out !== undefined ? { out } : {}),
|
|
1132
|
+
...(argv.includes("--strict") ? { strict: true } : {}),
|
|
1133
|
+
})
|
|
888
1134
|
} catch (err) {
|
|
889
1135
|
console.error(formatCliError(err))
|
|
890
1136
|
process.exitCode = 1
|
|
@@ -905,6 +1151,8 @@ async function main(): Promise<void> {
|
|
|
905
1151
|
list: argv.includes("--list"),
|
|
906
1152
|
verify: !argv.includes("--no-verify"),
|
|
907
1153
|
allowDowngrade: argv.includes("--allow-downgrade"),
|
|
1154
|
+
exact: argv.includes("--exact"),
|
|
1155
|
+
cliVersion: CLI_VERSION,
|
|
908
1156
|
})
|
|
909
1157
|
if (!ok) process.exitCode = 1
|
|
910
1158
|
} catch (err) {
|
|
@@ -923,6 +1171,12 @@ async function main(): Promise<void> {
|
|
|
923
1171
|
}
|
|
924
1172
|
const flags = parseFlags(argv.slice(1))
|
|
925
1173
|
const app = await loadApp(process.cwd(), flags.out)
|
|
1174
|
+
if (command !== "start") {
|
|
1175
|
+
const { refreshRouteTypes, watchRouteTypes } = await import("./route-types.ts")
|
|
1176
|
+
refreshRouteTypes(app.cwd)
|
|
1177
|
+
// A relaunched `nifra dev --bun` child runs beside its parent, which already watches.
|
|
1178
|
+
if (command === "dev" && process.env.NIFRA_BUN_DEV_TOKEN === undefined) watchRouteTypes(app.cwd)
|
|
1179
|
+
}
|
|
926
1180
|
if (command === "dev") await dev(app, flags)
|
|
927
1181
|
else if (command === "build") await buildForTarget(app, flags.target, flags)
|
|
928
1182
|
else await start(app, flags)
|
|
@@ -1007,6 +1261,7 @@ function isCliCommandToken(value: string): boolean {
|
|
|
1007
1261
|
value === "mcp" ||
|
|
1008
1262
|
value === "init-agents" ||
|
|
1009
1263
|
value === "upgrade" ||
|
|
1264
|
+
value === "db" ||
|
|
1010
1265
|
value === "help" ||
|
|
1011
1266
|
value === "--help" ||
|
|
1012
1267
|
value === "-h" ||
|