@nifrajs/cli 3.5.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +4 -0
- package/dist/check-diagnostics.d.ts.map +1 -1
- package/dist/check-diagnostics.js +13 -0
- package/dist/check-diagnostics.js.map +1 -1
- package/dist/check-scan.d.ts +49 -7
- package/dist/check-scan.d.ts.map +1 -1
- package/dist/check-scan.js +291 -62
- package/dist/check-scan.js.map +1 -1
- package/dist/check.d.ts.map +1 -1
- package/dist/check.js +99 -63
- 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 +262 -82
- package/dist/cli.js.map +1 -1
- package/dist/command-catalog.d.ts +57 -5
- package/dist/command-catalog.d.ts.map +1 -1
- package/dist/command-catalog.js +270 -26
- 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.map +1 -1
- package/dist/contracts.js +52 -13
- 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/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 +5 -1
- package/dist/introspect.d.ts.map +1 -1
- package/dist/introspect.js +92 -27
- 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 +67 -17
- 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.js +1 -1
- 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 +2 -0
- package/dist/mcp-exec.d.ts.map +1 -1
- package/dist/mcp-exec.js +132 -108
- package/dist/mcp-exec.js.map +1 -1
- package/dist/mcp-http.d.ts.map +1 -1
- package/dist/mcp-http.js +17 -5
- package/dist/mcp-http.js.map +1 -1
- package/dist/mcp-io.d.ts +14 -0
- package/dist/mcp-io.d.ts.map +1 -1
- package/dist/mcp-io.js +45 -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-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-render.js +2 -2
- 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 +110 -23
- 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 +82 -9
- 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 +16 -3
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +81 -53
- 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 +5 -0
- package/dist/project-facts.d.ts.map +1 -1
- 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.map +1 -1
- package/dist/review.js +12 -8
- package/dist/review.js.map +1 -1
- 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.map +1 -1
- package/dist/rules/legacy.js +71 -38
- 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 +132 -87
- 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 +4 -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.map +1 -1
- package/dist/work-graph.js +29 -13
- package/dist/work-graph.js.map +1 -1
- package/docs/examples.json +185 -29
- package/docs/llms-full.txt +3807 -580
- package/docs/types.json +14229 -10939
- package/package.json +19 -12
- 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 +17 -0
- package/src/check-scan.ts +358 -63
- package/src/check.ts +97 -62
- package/src/cli.ts +296 -87
- package/src/command-catalog.ts +374 -38
- package/src/config-target.ts +47 -0
- package/src/contracts.ts +54 -12
- 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/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 +90 -27
- package/src/learn.ts +3 -3
- package/src/levels-tool.ts +47 -2
- package/src/load.ts +140 -33
- package/src/manifest-tool.ts +3 -2
- package/src/mcp-app-tool.ts +2 -0
- package/src/mcp-context.ts +1 -1
- package/src/mcp-delegate.ts +162 -0
- package/src/mcp-docs-tools.ts +23 -5
- package/src/mcp-exec.ts +160 -132
- package/src/mcp-http.ts +19 -5
- package/src/mcp-io.ts +57 -0
- package/src/mcp-isolate.ts +287 -0
- package/src/mcp-project-child.ts +182 -0
- package/src/mcp-render.ts +2 -2
- package/src/mcp-root.ts +117 -22
- package/src/mcp-run.ts +104 -10
- package/src/mcp-ws.ts +4 -1
- package/src/mcp.ts +106 -64
- 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 +5 -0
- 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 +13 -8
- 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 +71 -39
- 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 +147 -95
- 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 +13 -0
- package/src/verification.ts +50 -12
- package/src/web-app-options.ts +57 -0
- package/src/work-graph.ts +27 -13
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,27 +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 CreateWebAppOptions,
|
|
18
17
|
type CssLoadingMode,
|
|
19
18
|
createWebApp,
|
|
20
19
|
DEFAULT_DEV_PORT,
|
|
21
20
|
type RenderAdapter,
|
|
22
21
|
} from "@nifrajs/web"
|
|
23
22
|
import { discoverRoutes } from "@nifrajs/web/fs"
|
|
23
|
+
import { formatShadowedPages, shadowedPages } from "@nifrajs/web/route-manifest"
|
|
24
24
|
import type { BunPlugin } from "bun"
|
|
25
|
+
import { adapterFile, BACKEND_APP_FILE } from "./app-files.ts"
|
|
25
26
|
import { bindCommandArgv, findCommandSpec, renderCommandCatalogHelp } from "./command-catalog.ts"
|
|
26
|
-
import { applyEnvFiles, takeEnvFileFlags } from "./env-file.ts"
|
|
27
|
-
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"
|
|
28
29
|
import { chooseBuildPipeline, describePipeline } from "./pipeline-guard.ts"
|
|
30
|
+
import {
|
|
31
|
+
frameworkMountPaths,
|
|
32
|
+
frameworkOptionImports,
|
|
33
|
+
frameworkWebAppOptions,
|
|
34
|
+
} from "./web-app-options.ts"
|
|
29
35
|
|
|
30
36
|
export interface Flags {
|
|
31
37
|
readonly port: number
|
|
32
|
-
|
|
38
|
+
/** `--out <dir>`; without it `dist`, or `.vercel/output` for a Vercel build. */
|
|
39
|
+
readonly out: string | undefined
|
|
33
40
|
readonly poll: boolean
|
|
34
|
-
/** `nifra build --target <t>`: emit a full deploy dir for this target.
|
|
35
|
-
|
|
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
|
|
36
44
|
/** `nifra build --report`: print a per-chunk size + gzip table after the build. */
|
|
37
45
|
readonly report: boolean
|
|
38
46
|
/** `nifra build --vite`: force the client + server through Vite/Rollup. Without a flag the pipeline is
|
|
@@ -47,18 +55,22 @@ export interface Flags {
|
|
|
47
55
|
* a hard failure to a loud warning so a duplicate coming from a linked sibling repo doesn't take dev
|
|
48
56
|
* down while you fix the resolution. Dev only - `nifra build` always fails hard on a duplicate. */
|
|
49
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
|
|
50
60
|
}
|
|
51
61
|
|
|
52
62
|
/** Forward a re-exec'd Bun child's output without relying on Windows inheriting a pipe-of-a-pipe. */
|
|
53
63
|
async function forwardChildOutput(
|
|
54
64
|
stream: ReadableStream<Uint8Array>,
|
|
55
65
|
sink: { write(chunk: Uint8Array): unknown },
|
|
66
|
+
observe?: (chunk: Uint8Array) => void,
|
|
56
67
|
): Promise<void> {
|
|
57
68
|
const reader = stream.getReader()
|
|
58
69
|
try {
|
|
59
70
|
for (;;) {
|
|
60
71
|
const { done, value } = await reader.read()
|
|
61
72
|
if (done) return
|
|
73
|
+
observe?.(value)
|
|
62
74
|
await sink.write(value)
|
|
63
75
|
}
|
|
64
76
|
} finally {
|
|
@@ -66,6 +78,31 @@ async function forwardChildOutput(
|
|
|
66
78
|
}
|
|
67
79
|
}
|
|
68
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
|
+
|
|
69
106
|
const HELP = `nifra - zero-config dev/build/start for a nifra app
|
|
70
107
|
|
|
71
108
|
Usage:
|
|
@@ -81,14 +118,33 @@ Usage:
|
|
|
81
118
|
loud warning instead of a hard failure, so a duplicate React/
|
|
82
119
|
adapter copy from a linked sibling repo doesn't take dev down
|
|
83
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.
|
|
84
140
|
nifra build [--out <dir>] [--report] Emit a complete deploy directory.
|
|
85
141
|
[--target <t>] Target a FULL deploy dir for <t>:
|
|
86
|
-
bun | node | deno |
|
|
142
|
+
bun | node | deno | cloudflare | vercel | static. Packages
|
|
87
143
|
buildClient + buildServer (+ prerender for static) so an app
|
|
88
144
|
no longer hand-writes build-<target>.ts + _worker.ts +
|
|
89
145
|
_routes.json. The server entry is generated from your
|
|
90
|
-
framework.ts (adapter) + backend.ts + routes/.
|
|
91
|
-
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).
|
|
92
148
|
[--vite | --bun] Force the bundler. Without a flag it follows your config: Bun
|
|
93
149
|
(faster, Bun-native) unless your ONLY transforms are
|
|
94
150
|
\`vitePlugins\`, which the Bun build cannot run - then Vite,
|
|
@@ -109,12 +165,20 @@ Usage:
|
|
|
109
165
|
against it: a route the target can't honour (an ssr route on a
|
|
110
166
|
static build, ISR where there's no revalidation) exits nonzero
|
|
111
167
|
with the consequence - run in CI so it fails the build, not prod.
|
|
112
|
-
nifra init-agents [--force] [--json] Retrofit an EXISTING app with the agent
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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\`).
|
|
118
182
|
nifra mcp [dir] Start an MCP server (stdio) exposing this project to a coding
|
|
119
183
|
agent. The project root is [dir] when given, else resolved from
|
|
120
184
|
cwd (marker walk-up + the client's MCP roots); tools refuse
|
|
@@ -123,15 +187,18 @@ Usage:
|
|
|
123
187
|
nifra_run (backend), nifra_render (SSR a page), nifra_docs,
|
|
124
188
|
nifra_example (verified snippets), nifra_scaffold (route→file),
|
|
125
189
|
nifra_check (drift gate + fixes), nifra_levels (verification
|
|
126
|
-
ladder), nifra_doctor (deps),
|
|
127
|
-
|
|
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).
|
|
128
195
|
nifra docs-mcp [--port <n>] Serve the PUBLIC docs MCP over HTTP (nifra_docs + nifra_example) -
|
|
129
196
|
self-host on a VPS so any remote agent can learn nifra. Default :8787.
|
|
130
197
|
nifra learn [<step>] Print the guided build-an-app path (the human view of nifra_learn):
|
|
131
198
|
no arg for the step index, a number for one step's goal/do/verify.
|
|
132
199
|
nifra check [--json] [--sarif] [--lints-only]
|
|
133
200
|
Gate: typecheck + lints (hand-rolled fetch(), untyped client("…"),
|
|
134
|
-
|
|
201
|
+
backend code in browser code). Run as "done"; --json for agents;
|
|
135
202
|
--sarif emits the same stable diagnostics as SARIF 2.1.0 for external
|
|
136
203
|
review tools; --lints-only skips tsc for a near-instant inner-loop pass.
|
|
137
204
|
nifra verify [--release] [--json] Run the shared repository verification gate. --release runs the
|
|
@@ -151,8 +218,8 @@ Usage:
|
|
|
151
218
|
request field or a removed response field breaks; widening a
|
|
152
219
|
request enum or adding a response field doesn't) and fails
|
|
153
220
|
closed. Exits non-zero on any breaking change - run it in CI.
|
|
154
|
-
nifra sdk --lang <python|go> [--out <file>]
|
|
155
|
-
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.
|
|
156
223
|
nifra assure [--config <file>] [--json] Route-assurance report. Human table by default; --json emits
|
|
157
224
|
the {ok, routes, findings} report for agents.
|
|
158
225
|
nifra assure --bundle [--json] [--strict] [--out <file>] [--hydration] [--interact]
|
|
@@ -190,22 +257,40 @@ Usage:
|
|
|
190
257
|
print production readiness, with --strict making absent
|
|
191
258
|
applicable guarantees fail. --auto-fix writes safe local-
|
|
192
259
|
version dependency fixes.
|
|
193
|
-
nifra upgrade <version>
|
|
194
|
-
[--write] [--no-verify]
|
|
195
|
-
[--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.
|
|
196
264
|
[--allow-downgrade] Dry-run by default; --write applies then verifies (--no-verify
|
|
197
|
-
to skip). --list shows available targets.
|
|
198
|
-
|
|
199
|
-
|
|
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.
|
|
200
279
|
nifra port [--target <t>] [--json] Portability linter: print a feature × deploy-target capability
|
|
201
280
|
[--ci] [--strict] matrix (in-memory stores, in-process cron/WebSocket, Bun/Deno
|
|
202
281
|
globals, node: builtins) with file:line evidence. --target auto-
|
|
203
282
|
detected from build/deploy scripts, wrangler.toml, or vercel config;
|
|
204
283
|
--ci (or any --target) exits nonzero when a used feature is
|
|
205
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.
|
|
206
291
|
|
|
207
|
-
Reads nifra.config.ts (adapter + clientModule + plugins; or framework.ts), backend.ts
|
|
208
|
-
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.
|
|
209
294
|
|
|
210
295
|
Port: \`dev\` and \`start\` share the default ${DEFAULT_DEV_PORT}. Override with \`--port <n>\` (alias \`-p\`) or the
|
|
211
296
|
\`PORT\` env var (\`--port\` wins over \`PORT\`, which wins over the default).
|
|
@@ -219,15 +304,11 @@ set in the process environment is never overwritten by a file.
|
|
|
219
304
|
${renderCommandCatalogHelp()}`
|
|
220
305
|
|
|
221
306
|
// Kept in lockstep with packages/cli/package.json by check:publish's version-consistency gate.
|
|
222
|
-
const CLI_VERSION = "
|
|
307
|
+
const CLI_VERSION = "4.0.0"
|
|
223
308
|
|
|
224
309
|
// A render adapter + nifra server are opaque to the CLI (it just forwards them); cast at the seam.
|
|
225
310
|
const asAdapter = (v: unknown): RenderAdapter => v as RenderAdapter
|
|
226
311
|
const asBunPlugins = (v: readonly unknown[]): BunPlugin[] => v as BunPlugin[]
|
|
227
|
-
const asUse = (v: (app: never) => void): NonNullable<CreateWebAppOptions["use"]> =>
|
|
228
|
-
v as NonNullable<CreateWebAppOptions["use"]>
|
|
229
|
-
const apiOf = (backend: unknown): { api?: unknown } =>
|
|
230
|
-
backend === undefined ? {} : { api: inProcessClient(backend as never) }
|
|
231
312
|
|
|
232
313
|
/**
|
|
233
314
|
* Render an error for the CLI, unwrapping the detail a bare `.message` drops.
|
|
@@ -284,7 +365,7 @@ async function dev(app: LoadedApp, flags: Flags): Promise<void> {
|
|
|
284
365
|
)
|
|
285
366
|
if (decision.pipeline === "bun") {
|
|
286
367
|
// Bun's dev-server bundler takes plugins only via bunfig `[serve.static]`, read at process
|
|
287
|
-
// start - so the boundary plugins (
|
|
368
|
+
// start - so the boundary plugins (zone guard, server-fn stubs) are delivered by
|
|
288
369
|
// generating a config and re-execing this same command once with `--config=`. The child proves
|
|
289
370
|
// it IS the configured child with a per-launch random token (matched against the file the
|
|
290
371
|
// parent just wrote, consumed on first read). A fixed sentinel here would be a secret-leak
|
|
@@ -302,7 +383,11 @@ async function dev(app: LoadedApp, flags: Flags): Promise<void> {
|
|
|
302
383
|
"launches; refusing to serve without the client-boundary plugins.",
|
|
303
384
|
)
|
|
304
385
|
}
|
|
305
|
-
const { bunfigPath, launchToken } = await writeBunDevConfig(
|
|
386
|
+
const { bunfigPath, launchToken } = await writeBunDevConfig(
|
|
387
|
+
app.cwd,
|
|
388
|
+
app.configPath,
|
|
389
|
+
app.framework.define,
|
|
390
|
+
)
|
|
306
391
|
// Bun 1.4 on Windows may discard the script-relative portion of argv when `--config` is
|
|
307
392
|
// present. Keep a second, authenticated copy of the user vector in the child's environment so
|
|
308
393
|
// the configured process cannot silently fall back to `nifra`'s help and exit 0. The launch token
|
|
@@ -353,16 +438,30 @@ async function dev(app: LoadedApp, flags: Flags): Promise<void> {
|
|
|
353
438
|
},
|
|
354
439
|
},
|
|
355
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)
|
|
356
443
|
const forwarded = Promise.all([
|
|
357
444
|
forwardChildOutput(child.stdout as ReadableStream<Uint8Array>, Bun.stdout),
|
|
358
|
-
forwardChildOutput(child.stderr as ReadableStream<Uint8Array>, Bun.stderr),
|
|
445
|
+
forwardChildOutput(child.stderr as ReadableStream<Uint8Array>, Bun.stderr, stderrTail.push),
|
|
359
446
|
])
|
|
360
|
-
|
|
447
|
+
let interrupted = false
|
|
448
|
+
const forward = (): void => {
|
|
449
|
+
interrupted = true
|
|
450
|
+
child.kill("SIGINT")
|
|
451
|
+
}
|
|
361
452
|
process.on("SIGINT", forward)
|
|
362
453
|
process.on("SIGTERM", forward)
|
|
363
454
|
try {
|
|
364
455
|
const [code] = await Promise.all([child.exited, forwarded])
|
|
365
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
|
+
}
|
|
366
465
|
} finally {
|
|
367
466
|
process.off("SIGINT", forward)
|
|
368
467
|
process.off("SIGTERM", forward)
|
|
@@ -411,13 +510,14 @@ async function dev(app: LoadedApp, flags: Flags): Promise<void> {
|
|
|
411
510
|
...(fw.publicDir !== undefined ? { publicDir: fw.publicDir } : {}),
|
|
412
511
|
...(fw.conditions ? { conditions: fw.conditions } : {}),
|
|
413
512
|
...(fw.define ? { define: fw.define } : {}),
|
|
414
|
-
|
|
513
|
+
indicator: showsIndicator(flags, fw),
|
|
514
|
+
createApp: (clientEntry, importQuery, dev) =>
|
|
415
515
|
createWebApp({
|
|
416
516
|
adapter: asAdapter(fw.adapter),
|
|
417
517
|
manifest: discoverRoutes(routesDir, { importQuery }),
|
|
418
518
|
clientEntry,
|
|
419
|
-
...(fw
|
|
420
|
-
|
|
519
|
+
...frameworkWebAppOptions(fw, backend),
|
|
520
|
+
onLoaderError: dev.onLoaderError,
|
|
421
521
|
}),
|
|
422
522
|
})
|
|
423
523
|
console.log(`nifra dev (bun) → http://localhost:${server.port}`)
|
|
@@ -467,18 +567,19 @@ async function dev(app: LoadedApp, flags: Flags): Promise<void> {
|
|
|
467
567
|
poll: flags.poll,
|
|
468
568
|
port: flags.port,
|
|
469
569
|
...(flags.allowDuplicateIdentity ? { allowDuplicateIdentity: true } : {}),
|
|
570
|
+
indicator: showsIndicator(flags, fw),
|
|
470
571
|
...(fw.conditions ? { conditions: fw.conditions } : {}),
|
|
471
572
|
...(fw.define ? { define: fw.define } : {}),
|
|
472
573
|
// `load` resolves route modules through VITE, not through Bun. That is what makes the Vite
|
|
473
574
|
// pipeline own the whole phase: the client and the server now agree on every specifier, so
|
|
474
575
|
// `resolve.dedupe` finally governs SSR and the dual-React crash cannot occur.
|
|
475
|
-
createApp: (clientEntry, load) =>
|
|
576
|
+
createApp: (clientEntry, load, dev) =>
|
|
476
577
|
createWebApp({
|
|
477
578
|
adapter: asAdapter(fw.adapter),
|
|
478
579
|
manifest: discoverRoutes(routesDir, { load }),
|
|
479
580
|
clientEntry,
|
|
480
|
-
...(fw
|
|
481
|
-
|
|
581
|
+
...frameworkWebAppOptions(fw, backend),
|
|
582
|
+
onLoaderError: dev.onLoaderError,
|
|
482
583
|
}),
|
|
483
584
|
})
|
|
484
585
|
console.log(`nifra dev (vite) → http://localhost:${server.port}`)
|
|
@@ -488,23 +589,26 @@ async function dev(app: LoadedApp, flags: Flags): Promise<void> {
|
|
|
488
589
|
/**
|
|
489
590
|
* `nifra build --target <t>` - package the engine (buildClient + buildServer + prerender) into one
|
|
490
591
|
* command that emits a full deploy dir, so an app no longer hand-writes build-bun.ts + _worker.ts +
|
|
491
|
-
* _routes.json per target. The adapter is imported from `framework.ts` (the edge-bundlable
|
|
492
|
-
* `nifra.config.ts`, which pulls in Vite plugins), and the backend from
|
|
493
|
-
* `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/`.
|
|
494
596
|
*/
|
|
495
597
|
/**
|
|
496
598
|
* Refuse a `use` the generated server entry cannot import.
|
|
497
599
|
*
|
|
498
|
-
* `nifra build` emits `import { use } from <frameworkFile>` (framework.ts, the
|
|
499
|
-
* but `loadApp` prefers `nifra.config.ts` - so an app with BOTH files whose
|
|
500
|
-
* `nifra.config.ts` makes `fw.use` defined while `framework.ts` exports
|
|
501
|
-
* build dies later with an opaque bundler error pointing at generated
|
|
502
|
-
* 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).
|
|
503
606
|
*
|
|
504
|
-
* Detected by importing `frameworkFile` and checking the named export, not by comparing paths
|
|
505
|
-
* a split app legitimately DEFINES `use` in framework.ts and re-exports it from
|
|
506
|
-
* (exactly how `adapter` reaches both readers), and a path compare would refuse
|
|
507
|
-
* 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.
|
|
508
612
|
*/
|
|
509
613
|
export async function assertUseIsEdgeExported(
|
|
510
614
|
use: NifraFramework["use"],
|
|
@@ -516,13 +620,65 @@ export async function assertUseIsEdgeExported(
|
|
|
516
620
|
if (typeof mod.use === "function") return
|
|
517
621
|
throw new Error(
|
|
518
622
|
`[nifra] \`use\` is exported from ${configPath} but not from ${frameworkFile}. ` +
|
|
519
|
-
"`nifra build` generates a server entry that imports `use` from framework.ts (the
|
|
520
|
-
"file), so this build would fail later with an opaque bundler error inside generated code.\n\n" +
|
|
521
|
-
" - Define `use` in framework.ts and re-export it from nifra.config.ts " +
|
|
522
|
-
'(`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.',
|
|
627
|
+
)
|
|
628
|
+
}
|
|
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"\`).`,
|
|
523
658
|
)
|
|
524
659
|
}
|
|
525
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
|
+
|
|
526
682
|
/**
|
|
527
683
|
* A production build must not ship a workspace-linked package whose `default`-condition artifact is
|
|
528
684
|
* missing or older than its source. The `"bun": "./src"` / `"default": "./dist"` split means Bun (this
|
|
@@ -557,11 +713,16 @@ async function assertFreshWorkspaceDists(cwd: string): Promise<void> {
|
|
|
557
713
|
)
|
|
558
714
|
}
|
|
559
715
|
|
|
560
|
-
async function buildForTarget(
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
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
|
+
)
|
|
565
726
|
if (flags.vite && flags.bun) {
|
|
566
727
|
throw new Error("[nifra] `nifra build` takes `--vite` or `--bun`, not both.")
|
|
567
728
|
}
|
|
@@ -604,17 +765,19 @@ async function buildForTarget(app: LoadedApp, target: string, flags: Flags): Pro
|
|
|
604
765
|
const buildTarget = useVite
|
|
605
766
|
? (await import("@nifrajs/web/build-vite")).buildTargetVite
|
|
606
767
|
: (await import("@nifrajs/web/build")).buildTarget
|
|
607
|
-
const { routesDir,
|
|
608
|
-
//
|
|
609
|
-
//
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
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)
|
|
617
778
|
await assertUseIsEdgeExported(fw.use, app.configPath, frameworkFile)
|
|
779
|
+
await assertFrameworkOptionsEdgeExported(fw, app.configPath, frameworkFile)
|
|
780
|
+
assertNoShadowedPages(app)
|
|
618
781
|
// Plugin FORMAT differs by pipeline: the Bun build takes Bun plugins (clientPlugins/serverPlugins); the
|
|
619
782
|
// Vite build takes the app's Vite plugins (fw.vitePlugins) for BOTH halves. buildTargetWith forwards
|
|
620
783
|
// whatever it's given straight to the chosen bundler, which casts to its own plugin type.
|
|
@@ -634,14 +797,17 @@ async function buildForTarget(app: LoadedApp, target: string, flags: Flags): Pro
|
|
|
634
797
|
clientModule: fw.clientModule,
|
|
635
798
|
adapterImport: frameworkFile,
|
|
636
799
|
...(fw.use ? { useImport: frameworkFile } : {}),
|
|
800
|
+
...frameworkOptionImports(fw, frameworkFile),
|
|
637
801
|
...(backend !== undefined && existsSync(backendFile) ? { backendImport: backendFile } : {}),
|
|
638
802
|
...plugins,
|
|
639
803
|
...(fw.conditions ? { conditions: fw.conditions } : {}),
|
|
640
804
|
...(fw.define ? { define: fw.define } : {}),
|
|
641
805
|
...(fw.publicDir !== undefined ? { publicDir: fw.publicDir } : {}),
|
|
642
806
|
...(fw.publicEnvPrefix !== undefined ? { publicEnvPrefix: fw.publicEnvPrefix } : {}),
|
|
807
|
+
...(fw.secretExemptions !== undefined ? { secretExemptions: fw.secretExemptions } : {}),
|
|
643
808
|
...(fw.cssCodeSplit !== undefined ? { cssCodeSplit: fw.cssCodeSplit } : {}),
|
|
644
809
|
...(fw.cssLoading !== undefined ? { cssLoading: fw.cssLoading } : {}),
|
|
810
|
+
...(fw.clientIp !== undefined ? { clientIp: fw.clientIp } : {}),
|
|
645
811
|
// The static target needs a built app to drive prerendering - only build it when targeting static.
|
|
646
812
|
...(target === "static" ? { prerenderApp: await buildPrerenderApp(app) } : {}),
|
|
647
813
|
})
|
|
@@ -674,12 +840,11 @@ async function buildPrerenderApp(
|
|
|
674
840
|
adapter: asAdapter(fw.adapter),
|
|
675
841
|
manifest: discoverRoutes(routesDir),
|
|
676
842
|
clientEntry: client.entry,
|
|
677
|
-
...(fw
|
|
843
|
+
...frameworkWebAppOptions(fw, backend),
|
|
678
844
|
...(client.routes ? { routePreload: client.routes } : {}),
|
|
679
845
|
...(client.css ? { styles: client.css } : {}),
|
|
680
846
|
...(client.routeStyles ? { routeStyles: client.routeStyles } : {}),
|
|
681
847
|
...(client.cssLoading !== undefined ? { cssLoading: client.cssLoading } : {}),
|
|
682
|
-
...apiOf(backend),
|
|
683
848
|
})
|
|
684
849
|
}
|
|
685
850
|
|
|
@@ -695,7 +860,7 @@ async function start(app: LoadedApp, flags: Flags): Promise<void> {
|
|
|
695
860
|
const serverFile = resolve(app.outDir, "server.js")
|
|
696
861
|
if (!existsSync(serverFile)) {
|
|
697
862
|
// A Cloudflare Pages build emits `_worker.js` (a Workers bundle), not a self-hosting `server.js` - so
|
|
698
|
-
// `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
|
|
699
864
|
// actual mismatch and the fix.
|
|
700
865
|
if (existsSync(resolve(app.outDir, "_worker.js"))) {
|
|
701
866
|
throw new Error(
|
|
@@ -716,13 +881,14 @@ export function parseFlags(args: readonly string[]): Flags {
|
|
|
716
881
|
// the SAME uncommon port for `nifra dev` and `nifra start` (DEFAULT_DEV_PORT) so a project's URL is
|
|
717
882
|
// stable across commands and doesn't collide with the usual 3000/5173/8080 crowd.
|
|
718
883
|
let port = Number(Bun.env.PORT ?? DEFAULT_DEV_PORT)
|
|
719
|
-
let out
|
|
884
|
+
let out: string | undefined
|
|
720
885
|
let poll = Bun.env.CHOKIDAR_USEPOLLING === "1"
|
|
721
|
-
let target
|
|
886
|
+
let target: string | undefined
|
|
722
887
|
let report = false
|
|
723
888
|
let vite = false
|
|
724
889
|
let bun = false
|
|
725
890
|
let allowDuplicateIdentity = false
|
|
891
|
+
let noIndicator = false
|
|
726
892
|
for (let i = 0; i < args.length; i++) {
|
|
727
893
|
const a = args[i]
|
|
728
894
|
if ((a === "--port" || a === "-p") && args[i + 1]) port = Number(args[++i])
|
|
@@ -733,11 +899,12 @@ export function parseFlags(args: readonly string[]): Flags {
|
|
|
733
899
|
else if (a === "--vite") vite = true
|
|
734
900
|
else if (a === "--bun") bun = true
|
|
735
901
|
else if (a === "--allow-duplicate-identity") allowDuplicateIdentity = true
|
|
902
|
+
else if (a === "--no-indicator") noIndicator = true
|
|
736
903
|
}
|
|
737
904
|
if (!Number.isFinite(port) || port < 0 || port > 65535) {
|
|
738
905
|
throw new Error(`[nifra] invalid --port: ${port}`)
|
|
739
906
|
}
|
|
740
|
-
return { port, out, poll, target, report, vite, bun, allowDuplicateIdentity }
|
|
907
|
+
return { port, out, poll, target, report, vite, bun, allowDuplicateIdentity, noIndicator }
|
|
741
908
|
}
|
|
742
909
|
|
|
743
910
|
/**
|
|
@@ -778,8 +945,17 @@ function installReflectionExitHint(): (command: string | undefined) => void {
|
|
|
778
945
|
}
|
|
779
946
|
}
|
|
780
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
|
+
|
|
781
951
|
async function main(): Promise<void> {
|
|
782
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
|
+
: []
|
|
783
959
|
// Applied before any command runs: a reflecting command imports the app on its first await, and the
|
|
784
960
|
// app reads its environment at module scope, so the variables must already be in place by then.
|
|
785
961
|
if (envFiles.length > 0) await applyEnvFiles(process.cwd(), envFiles)
|
|
@@ -802,6 +978,19 @@ async function main(): Promise<void> {
|
|
|
802
978
|
// `mcp` runs a long-lived stdio server and loads the project lazily per-tool - it must not go through
|
|
803
979
|
// the eager `loadApp` below (which would fail fast on a project that's API-only / not yet built).
|
|
804
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
|
+
}
|
|
805
994
|
const { runMcpServer } = await import("./mcp.ts")
|
|
806
995
|
// `nifra mcp [dir]` - an explicit project directory pins the root (for clients configured outside
|
|
807
996
|
// the project); otherwise the server resolves it from cwd + the client's MCP roots.
|
|
@@ -853,11 +1042,17 @@ async function main(): Promise<void> {
|
|
|
853
1042
|
)
|
|
854
1043
|
return
|
|
855
1044
|
}
|
|
856
|
-
|
|
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}`)
|
|
857
1052
|
if (catalogSpec?.transports.includes("cli")) {
|
|
858
1053
|
const markReflecting = installReflectionExitHint()
|
|
859
1054
|
try {
|
|
860
|
-
const input = bindCommandArgv(catalogSpec, argv.slice(1))
|
|
1055
|
+
const input = bindCommandArgv(catalogSpec, argv.slice(dbSub === undefined ? 1 : 2))
|
|
861
1056
|
markReflecting(command)
|
|
862
1057
|
const output = await catalogSpec.run(input, { cwd: process.cwd(), cliVersion: CLI_VERSION })
|
|
863
1058
|
markReflecting(undefined)
|
|
@@ -902,7 +1097,7 @@ async function main(): Promise<void> {
|
|
|
902
1097
|
if (!ok) process.exitCode = 1
|
|
903
1098
|
return
|
|
904
1099
|
}
|
|
905
|
-
// `init-agents` retrofits the agent
|
|
1100
|
+
// `init-agents` retrofits the agent files (.mcp.json, CLAUDE.md, AGENTS.md, …) into the cwd. It's a
|
|
906
1101
|
// pure file-writing command independent of the app loading, so dispatch it before the eager `loadApp`
|
|
907
1102
|
// (an existing app might be API-only or not yet built). It always succeeds unless a write throws.
|
|
908
1103
|
if (command === "init-agents") {
|
|
@@ -910,6 +1105,7 @@ async function main(): Promise<void> {
|
|
|
910
1105
|
await runInitAgents(process.cwd(), {
|
|
911
1106
|
json: argv.includes("--json"),
|
|
912
1107
|
force: argv.includes("--force"),
|
|
1108
|
+
syncMcp: argv.includes("--sync-mcp"),
|
|
913
1109
|
})
|
|
914
1110
|
return
|
|
915
1111
|
}
|
|
@@ -930,7 +1126,11 @@ async function main(): Promise<void> {
|
|
|
930
1126
|
}
|
|
931
1127
|
try {
|
|
932
1128
|
const { runSdk } = await import("./sdk.ts")
|
|
933
|
-
await runSdk(process.cwd(), {
|
|
1129
|
+
await runSdk(process.cwd(), {
|
|
1130
|
+
language,
|
|
1131
|
+
...(out !== undefined ? { out } : {}),
|
|
1132
|
+
...(argv.includes("--strict") ? { strict: true } : {}),
|
|
1133
|
+
})
|
|
934
1134
|
} catch (err) {
|
|
935
1135
|
console.error(formatCliError(err))
|
|
936
1136
|
process.exitCode = 1
|
|
@@ -951,6 +1151,8 @@ async function main(): Promise<void> {
|
|
|
951
1151
|
list: argv.includes("--list"),
|
|
952
1152
|
verify: !argv.includes("--no-verify"),
|
|
953
1153
|
allowDowngrade: argv.includes("--allow-downgrade"),
|
|
1154
|
+
exact: argv.includes("--exact"),
|
|
1155
|
+
cliVersion: CLI_VERSION,
|
|
954
1156
|
})
|
|
955
1157
|
if (!ok) process.exitCode = 1
|
|
956
1158
|
} catch (err) {
|
|
@@ -969,6 +1171,12 @@ async function main(): Promise<void> {
|
|
|
969
1171
|
}
|
|
970
1172
|
const flags = parseFlags(argv.slice(1))
|
|
971
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
|
+
}
|
|
972
1180
|
if (command === "dev") await dev(app, flags)
|
|
973
1181
|
else if (command === "build") await buildForTarget(app, flags.target, flags)
|
|
974
1182
|
else await start(app, flags)
|
|
@@ -1053,6 +1261,7 @@ function isCliCommandToken(value: string): boolean {
|
|
|
1053
1261
|
value === "mcp" ||
|
|
1054
1262
|
value === "init-agents" ||
|
|
1055
1263
|
value === "upgrade" ||
|
|
1264
|
+
value === "db" ||
|
|
1056
1265
|
value === "help" ||
|
|
1057
1266
|
value === "--help" ||
|
|
1058
1267
|
value === "-h" ||
|