@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/dist/cli.js
CHANGED
|
@@ -8,8 +8,9 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
|
|
|
8
8
|
return path;
|
|
9
9
|
};
|
|
10
10
|
/**
|
|
11
|
-
* `nifra` - the zero-config CLI for a nifra app. Reads `framework.ts` + `backend.ts` +
|
|
12
|
-
* the project root (see {@link loadApp}) and wires the right `@nifrajs/web`
|
|
11
|
+
* `nifra` - the zero-config CLI for a nifra app. Reads `backend/framework.ts` + `backend/app.ts` +
|
|
12
|
+
* `routes/` from the project root (see {@link loadApp}) and wires the right `@nifrajs/web`
|
|
13
|
+
* entrypoint:
|
|
13
14
|
*
|
|
14
15
|
* nifra dev true-HMR dev server (Bun native HMR + nifra SSR) - @nifrajs/web/dev
|
|
15
16
|
* nifra build emit a complete target-specific deploy directory - @nifrajs/web/build
|
|
@@ -20,21 +21,24 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
|
|
|
20
21
|
import { existsSync, readFileSync, realpathSync } from "node:fs";
|
|
21
22
|
import { resolve } from "node:path";
|
|
22
23
|
import { fileURLToPath } from "node:url";
|
|
23
|
-
import { inProcessClient } from "@nifrajs/client";
|
|
24
24
|
import { createWebApp, DEFAULT_DEV_PORT, } from "@nifrajs/web";
|
|
25
25
|
import { discoverRoutes } from "@nifrajs/web/fs";
|
|
26
|
+
import { formatShadowedPages, shadowedPages } from "@nifrajs/web/route-manifest";
|
|
27
|
+
import { adapterFile, BACKEND_APP_FILE } from "./app-files.js";
|
|
26
28
|
import { bindCommandArgv, findCommandSpec, renderCommandCatalogHelp } from "./command-catalog.js";
|
|
27
|
-
import { applyEnvFiles, takeEnvFileFlags } from "./env-file.js";
|
|
28
|
-
import { loadApp } from "./load.js";
|
|
29
|
+
import { applyEnvFiles, forgetAutoLoadedEnv, takeEnvFileFlags } from "./env-file.js";
|
|
30
|
+
import { FRAMEWORK_WEB_OPTIONS, loadApp } from "./load.js";
|
|
29
31
|
import { chooseBuildPipeline, describePipeline } from "./pipeline-guard.js";
|
|
32
|
+
import { frameworkMountPaths, frameworkOptionImports, frameworkWebAppOptions, } from "./web-app-options.js";
|
|
30
33
|
/** Forward a re-exec'd Bun child's output without relying on Windows inheriting a pipe-of-a-pipe. */
|
|
31
|
-
async function forwardChildOutput(stream, sink) {
|
|
34
|
+
async function forwardChildOutput(stream, sink, observe) {
|
|
32
35
|
const reader = stream.getReader();
|
|
33
36
|
try {
|
|
34
37
|
for (;;) {
|
|
35
38
|
const { done, value } = await reader.read();
|
|
36
39
|
if (done)
|
|
37
40
|
return;
|
|
41
|
+
observe?.(value);
|
|
38
42
|
await sink.write(value);
|
|
39
43
|
}
|
|
40
44
|
}
|
|
@@ -42,6 +46,30 @@ async function forwardChildOutput(stream, sink) {
|
|
|
42
46
|
reader.releaseLock();
|
|
43
47
|
}
|
|
44
48
|
}
|
|
49
|
+
/** The last `max` bytes of a stream, decoded: what a crashed child printed last. */
|
|
50
|
+
function streamTail(max) {
|
|
51
|
+
let chunks = [];
|
|
52
|
+
let size = 0;
|
|
53
|
+
return {
|
|
54
|
+
push(chunk) {
|
|
55
|
+
chunks.push(chunk);
|
|
56
|
+
size += chunk.length;
|
|
57
|
+
while (size - (chunks[0]?.length ?? 0) >= max && chunks.length > 1) {
|
|
58
|
+
size -= chunks.shift()?.length ?? 0;
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
text() {
|
|
62
|
+
const all = new Uint8Array(size);
|
|
63
|
+
let offset = 0;
|
|
64
|
+
for (const chunk of chunks) {
|
|
65
|
+
all.set(chunk, offset);
|
|
66
|
+
offset += chunk.length;
|
|
67
|
+
}
|
|
68
|
+
chunks = [all];
|
|
69
|
+
return new TextDecoder().decode(all.subarray(Math.max(0, size - max)));
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
}
|
|
45
73
|
const HELP = `nifra - zero-config dev/build/start for a nifra app
|
|
46
74
|
|
|
47
75
|
Usage:
|
|
@@ -57,14 +85,33 @@ Usage:
|
|
|
57
85
|
loud warning instead of a hard failure, so a duplicate React/
|
|
58
86
|
adapter copy from a linked sibling repo doesn't take dev down
|
|
59
87
|
while you fix the resolution. Dev only - \`nifra build\` still fails.
|
|
88
|
+
[--no-indicator] No in-page issues badge. Browser errors still reach
|
|
89
|
+
\`nifra errors\` and the agent tools.
|
|
90
|
+
nifra errors [--since <n>] [--category <c>] [--request <id>] What the running dev server caught:
|
|
91
|
+
SSR/loader/API/build/browser/hydration errors, each a structured
|
|
92
|
+
diagnostic. Finds the server itself; reads the log a crashed
|
|
93
|
+
one left. Exits 1 while current-code errors are open.
|
|
94
|
+
nifra logs [--since <n>] [--level <l>] [--grep <s>] The dev server's console, server and
|
|
95
|
+
browser, each line tagged with its request.
|
|
96
|
+
nifra cdn-check <url> Check a deployed page behind a CDN: served from cache, no
|
|
97
|
+
internal or CDN-only headers reaching the visitor, and soft
|
|
98
|
+
navigations get page data, not the cached document. Exits 1
|
|
99
|
+
on a failure.
|
|
100
|
+
nifra db schema [<table>] The development database declared as \`devDatabase\` in
|
|
101
|
+
nifra.config.ts: tables, columns, keys, indexes.
|
|
102
|
+
nifra db query "<sql>" [--explain [--analyze]] One read-only SELECT (or its plan), in a fresh
|
|
103
|
+
process killed at devDatabase.timeoutMs. Rows capped, secrets
|
|
104
|
+
masked. Postgres: a superuser connection is refused.
|
|
105
|
+
nifra db role Print the SQL for a read-only Postgres role. Runs nothing.
|
|
106
|
+
nifra db audit [--limit <n>] What nifra db ran (.nifra/db-audit.jsonl), never the rows.
|
|
60
107
|
nifra build [--out <dir>] [--report] Emit a complete deploy directory.
|
|
61
108
|
[--target <t>] Target a FULL deploy dir for <t>:
|
|
62
|
-
bun | node | deno |
|
|
109
|
+
bun | node | deno | cloudflare | vercel | static. Packages
|
|
63
110
|
buildClient + buildServer (+ prerender for static) so an app
|
|
64
111
|
no longer hand-writes build-<target>.ts + _worker.ts +
|
|
65
112
|
_routes.json. The server entry is generated from your
|
|
66
|
-
framework.ts (adapter) + backend.ts + routes/.
|
|
67
|
-
a per-chunk size + gzip table (biggest first).
|
|
113
|
+
backend/framework.ts (adapter) + backend/app.ts + routes/.
|
|
114
|
+
--report prints a per-chunk size + gzip table (biggest first).
|
|
68
115
|
[--vite | --bun] Force the bundler. Without a flag it follows your config: Bun
|
|
69
116
|
(faster, Bun-native) unless your ONLY transforms are
|
|
70
117
|
\`vitePlugins\`, which the Bun build cannot run - then Vite,
|
|
@@ -85,12 +132,20 @@ Usage:
|
|
|
85
132
|
against it: a route the target can't honour (an ssr route on a
|
|
86
133
|
static build, ISR where there's no revalidation) exits nonzero
|
|
87
134
|
with the consequence - run in CI so it fails the build, not prod.
|
|
88
|
-
nifra init-agents [--force] [--json] Retrofit an EXISTING app with the agent
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
135
|
+
nifra init-agents [--force] [--json] Retrofit an EXISTING app with the agent files a new app ships:
|
|
136
|
+
.mcp.json + .cursor/mcp.json (register this project's nifra MCP),
|
|
137
|
+
each agent's pointer to AGENTS.md (CLAUDE.md, GEMINI.md,
|
|
138
|
+
.cursor/rules/nifra.mdc, .github/copilot-instructions.md), and
|
|
139
|
+
AGENTS.md's "## MCP server" and (with routes/) "## Project
|
|
140
|
+
structure" sections. No-clobber by default (skips a file you've
|
|
141
|
+
customized); --force overwrites the owned files. AGENTS.md is
|
|
142
|
+
only appended to, never overwritten.
|
|
143
|
+
nifra init-agents --sync-mcp [--json] Re-pin an existing app's MCP launch to the nifra the project
|
|
144
|
+
installs: rewrites only the @nifrajs/cli@x.y.z version in
|
|
145
|
+
.mcp.json, .cursor/mcp.json, CLAUDE.md and AGENTS.md's MCP
|
|
146
|
+
section - every other byte stays as it is. Creates nothing.
|
|
147
|
+
At a workspace root with one nifra member, the two registries'
|
|
148
|
+
launch also names that member (\`mcp app\`).
|
|
94
149
|
nifra mcp [dir] Start an MCP server (stdio) exposing this project to a coding
|
|
95
150
|
agent. The project root is [dir] when given, else resolved from
|
|
96
151
|
cwd (marker walk-up + the client's MCP roots); tools refuse
|
|
@@ -99,15 +154,18 @@ Usage:
|
|
|
99
154
|
nifra_run (backend), nifra_render (SSR a page), nifra_docs,
|
|
100
155
|
nifra_example (verified snippets), nifra_scaffold (route→file),
|
|
101
156
|
nifra_check (drift gate + fixes), nifra_levels (verification
|
|
102
|
-
ladder), nifra_doctor (deps),
|
|
103
|
-
|
|
157
|
+
ladder), nifra_doctor (deps), nifra_errors + nifra_logs (what
|
|
158
|
+
the running dev server saw), nifra_db_schema + nifra_db_query +
|
|
159
|
+
nifra_db_role (the declared dev database, read-only),
|
|
160
|
+
nifra_explain (structured errors), nifra_inspect (request
|
|
161
|
+
traces), nifra_learn (guided build path).
|
|
104
162
|
nifra docs-mcp [--port <n>] Serve the PUBLIC docs MCP over HTTP (nifra_docs + nifra_example) -
|
|
105
163
|
self-host on a VPS so any remote agent can learn nifra. Default :8787.
|
|
106
164
|
nifra learn [<step>] Print the guided build-an-app path (the human view of nifra_learn):
|
|
107
165
|
no arg for the step index, a number for one step's goal/do/verify.
|
|
108
166
|
nifra check [--json] [--sarif] [--lints-only]
|
|
109
167
|
Gate: typecheck + lints (hand-rolled fetch(), untyped client("…"),
|
|
110
|
-
|
|
168
|
+
backend code in browser code). Run as "done"; --json for agents;
|
|
111
169
|
--sarif emits the same stable diagnostics as SARIF 2.1.0 for external
|
|
112
170
|
review tools; --lints-only skips tsc for a near-instant inner-loop pass.
|
|
113
171
|
nifra verify [--release] [--json] Run the shared repository verification gate. --release runs the
|
|
@@ -127,8 +185,8 @@ Usage:
|
|
|
127
185
|
request field or a removed response field breaks; widening a
|
|
128
186
|
request enum or adding a response field doesn't) and fails
|
|
129
187
|
closed. Exits non-zero on any breaking change - run it in CI.
|
|
130
|
-
nifra sdk --lang <python|go> [--out <file>]
|
|
131
|
-
Generate a deterministic non-TypeScript SDK from backend.ts.
|
|
188
|
+
nifra sdk --lang <python|go> [--out <file>] [--strict]
|
|
189
|
+
Generate a deterministic non-TypeScript SDK from backend/app.ts.
|
|
132
190
|
nifra assure [--config <file>] [--json] Route-assurance report. Human table by default; --json emits
|
|
133
191
|
the {ok, routes, findings} report for agents.
|
|
134
192
|
nifra assure --bundle [--json] [--strict] [--out <file>] [--hydration] [--interact]
|
|
@@ -166,22 +224,40 @@ Usage:
|
|
|
166
224
|
print production readiness, with --strict making absent
|
|
167
225
|
applicable guarantees fail. --auto-fix writes safe local-
|
|
168
226
|
version dependency fixes.
|
|
169
|
-
nifra upgrade <version>
|
|
170
|
-
[--write] [--no-verify]
|
|
171
|
-
[--list] [--json]
|
|
227
|
+
nifra upgrade <version> Upgrade to <version>, running every release recipe between the
|
|
228
|
+
[--write] [--no-verify] installed version and it, oldest first: move removed packages,
|
|
229
|
+
[--list] [--json] apply exact imports, pin every matching dependency (keeping
|
|
230
|
+
[--exact] ^/~, or exact with --exact), then verify with nifra check.
|
|
172
231
|
[--allow-downgrade] Dry-run by default; --write applies then verifies (--no-verify
|
|
173
|
-
to skip). --list shows available targets.
|
|
174
|
-
|
|
175
|
-
|
|
232
|
+
to skip). --list shows available targets. A version newer than
|
|
233
|
+
this CLI prints the command for that release's CLI. Fail-closed
|
|
234
|
+
on an unknown version or a rollback (--allow-downgrade
|
|
235
|
+
overrides). Deterministic + idempotent.
|
|
236
|
+
nifra migrate layout [--write] [--json]
|
|
237
|
+
Move an app onto the frontend/backend split: split each route
|
|
238
|
+
into x.tsx + x.backend.ts, fold _middleware.ts into
|
|
239
|
+
_layout.backend.ts, move backend.ts/framework.ts under backend/,
|
|
240
|
+
and zone the other modules into frontend/, backend/ or shared/.
|
|
241
|
+
Dry-run by default; --write applies.
|
|
242
|
+
nifra types [--check] [--json] Generate each route's ./+types module under .nifra/types: its
|
|
243
|
+
params, the data its output schemas let through, and api typed
|
|
244
|
+
by backend/app.ts. dev, build and check refresh them too;
|
|
245
|
+
--check fails when one is stale.
|
|
176
246
|
nifra port [--target <t>] [--json] Portability linter: print a feature × deploy-target capability
|
|
177
247
|
[--ci] [--strict] matrix (in-memory stores, in-process cron/WebSocket, Bun/Deno
|
|
178
248
|
globals, node: builtins) with file:line evidence. --target auto-
|
|
179
249
|
detected from build/deploy scripts, wrangler.toml, or vercel config;
|
|
180
250
|
--ci (or any --target) exits nonzero when a used feature is
|
|
181
251
|
unsupported on the target; --strict also fails on caveats.
|
|
252
|
+
nifra i18n check [entry] Check i18n catalogs: coverage, missing + unused keys, ICU syntax,
|
|
253
|
+
[--json] [--strict] placeholder + tag parity, plural cases, script purity,
|
|
254
|
+
untranslated messages. IMPORTS the entry (default: the first
|
|
255
|
+
i18n.ts in ., lib/, src/, src/lib/, app/), which exports
|
|
256
|
+
\`locales\` + \`catalogs\` (+ optional \`ignore\`). Exits 1 on
|
|
257
|
+
errors; --strict also on warnings.
|
|
182
258
|
|
|
183
|
-
Reads nifra.config.ts (adapter + clientModule + plugins; or framework.ts), backend.ts
|
|
184
|
-
routes/ from the current directory. Run from your project root.
|
|
259
|
+
Reads nifra.config.ts (adapter + clientModule + plugins; or backend/framework.ts), backend/app.ts
|
|
260
|
+
(optional), and routes/ from the current directory. Run from your project root.
|
|
185
261
|
|
|
186
262
|
Port: \`dev\` and \`start\` share the default ${DEFAULT_DEV_PORT}. Override with \`--port <n>\` (alias \`-p\`) or the
|
|
187
263
|
\`PORT\` env var (\`--port\` wins over \`PORT\`, which wins over the default).
|
|
@@ -194,12 +270,10 @@ set in the process environment is never overwritten by a file.
|
|
|
194
270
|
|
|
195
271
|
${renderCommandCatalogHelp()}`;
|
|
196
272
|
// Kept in lockstep with packages/cli/package.json by check:publish's version-consistency gate.
|
|
197
|
-
const CLI_VERSION = "
|
|
273
|
+
const CLI_VERSION = "4.0.0";
|
|
198
274
|
// A render adapter + nifra server are opaque to the CLI (it just forwards them); cast at the seam.
|
|
199
275
|
const asAdapter = (v) => v;
|
|
200
276
|
const asBunPlugins = (v) => v;
|
|
201
|
-
const asUse = (v) => v;
|
|
202
|
-
const apiOf = (backend) => backend === undefined ? {} : { api: inProcessClient(backend) };
|
|
203
277
|
/**
|
|
204
278
|
* Render an error for the CLI, unwrapping the detail a bare `.message` drops.
|
|
205
279
|
*
|
|
@@ -248,7 +322,7 @@ async function dev(app, flags) {
|
|
|
248
322
|
const decision = chooseBuildPipeline(app.resolvedPlugins, flags.vite ? "vite" : flags.bun ? "bun" : undefined, "dev");
|
|
249
323
|
if (decision.pipeline === "bun") {
|
|
250
324
|
// Bun's dev-server bundler takes plugins only via bunfig `[serve.static]`, read at process
|
|
251
|
-
// start - so the boundary plugins (
|
|
325
|
+
// start - so the boundary plugins (zone guard, server-fn stubs) are delivered by
|
|
252
326
|
// generating a config and re-execing this same command once with `--config=`. The child proves
|
|
253
327
|
// it IS the configured child with a per-launch random token (matched against the file the
|
|
254
328
|
// parent just wrote, consumed on first read). A fixed sentinel here would be a secret-leak
|
|
@@ -264,7 +338,7 @@ async function dev(app, flags) {
|
|
|
264
338
|
"relaunches. Something is rewriting `.nifra/dev-bun/` or the environment between " +
|
|
265
339
|
"launches; refusing to serve without the client-boundary plugins.");
|
|
266
340
|
}
|
|
267
|
-
const { bunfigPath, launchToken } = await writeBunDevConfig(app.cwd, app.configPath);
|
|
341
|
+
const { bunfigPath, launchToken } = await writeBunDevConfig(app.cwd, app.configPath, app.framework.define);
|
|
268
342
|
// Bun 1.4 on Windows may discard the script-relative portion of argv when `--config` is
|
|
269
343
|
// present. Keep a second, authenticated copy of the user vector in the child's environment so
|
|
270
344
|
// the configured process cannot silently fall back to `nifra`'s help and exit 0. The launch token
|
|
@@ -312,16 +386,31 @@ async function dev(app, flags) {
|
|
|
312
386
|
NODE_ENV: process.env.NODE_ENV ?? "development",
|
|
313
387
|
},
|
|
314
388
|
});
|
|
389
|
+
// The child's last words, kept so a crash leaves a record `nifra errors` can read after the fact.
|
|
390
|
+
const stderrTail = streamTail(16 * 1024);
|
|
315
391
|
const forwarded = Promise.all([
|
|
316
392
|
forwardChildOutput(child.stdout, Bun.stdout),
|
|
317
|
-
forwardChildOutput(child.stderr, Bun.stderr),
|
|
393
|
+
forwardChildOutput(child.stderr, Bun.stderr, stderrTail.push),
|
|
318
394
|
]);
|
|
319
|
-
|
|
395
|
+
let interrupted = false;
|
|
396
|
+
const forward = () => {
|
|
397
|
+
interrupted = true;
|
|
398
|
+
child.kill("SIGINT");
|
|
399
|
+
};
|
|
320
400
|
process.on("SIGINT", forward);
|
|
321
401
|
process.on("SIGTERM", forward);
|
|
322
402
|
try {
|
|
323
403
|
const [code] = await Promise.all([child.exited, forwarded]);
|
|
324
404
|
process.exitCode = code;
|
|
405
|
+
if (code !== 0 && !interrupted) {
|
|
406
|
+
try {
|
|
407
|
+
const { recordDevCrash } = await import("@nifrajs/web/dev-feed");
|
|
408
|
+
recordDevCrash(app.cwd, code, stderrTail.text());
|
|
409
|
+
}
|
|
410
|
+
catch {
|
|
411
|
+
// The crash is already on the terminal; failing to persist it must not mask the exit code.
|
|
412
|
+
}
|
|
413
|
+
}
|
|
325
414
|
}
|
|
326
415
|
finally {
|
|
327
416
|
process.off("SIGINT", forward);
|
|
@@ -372,12 +461,13 @@ async function dev(app, flags) {
|
|
|
372
461
|
...(fw.publicDir !== undefined ? { publicDir: fw.publicDir } : {}),
|
|
373
462
|
...(fw.conditions ? { conditions: fw.conditions } : {}),
|
|
374
463
|
...(fw.define ? { define: fw.define } : {}),
|
|
375
|
-
|
|
464
|
+
indicator: showsIndicator(flags, fw),
|
|
465
|
+
createApp: (clientEntry, importQuery, dev) => createWebApp({
|
|
376
466
|
adapter: asAdapter(fw.adapter),
|
|
377
467
|
manifest: discoverRoutes(routesDir, { importQuery }),
|
|
378
468
|
clientEntry,
|
|
379
|
-
...(fw
|
|
380
|
-
|
|
469
|
+
...frameworkWebAppOptions(fw, backend),
|
|
470
|
+
onLoaderError: dev.onLoaderError,
|
|
381
471
|
}),
|
|
382
472
|
});
|
|
383
473
|
console.log(`nifra dev (bun) → http://localhost:${server.port}`);
|
|
@@ -426,17 +516,18 @@ async function dev(app, flags) {
|
|
|
426
516
|
poll: flags.poll,
|
|
427
517
|
port: flags.port,
|
|
428
518
|
...(flags.allowDuplicateIdentity ? { allowDuplicateIdentity: true } : {}),
|
|
519
|
+
indicator: showsIndicator(flags, fw),
|
|
429
520
|
...(fw.conditions ? { conditions: fw.conditions } : {}),
|
|
430
521
|
...(fw.define ? { define: fw.define } : {}),
|
|
431
522
|
// `load` resolves route modules through VITE, not through Bun. That is what makes the Vite
|
|
432
523
|
// pipeline own the whole phase: the client and the server now agree on every specifier, so
|
|
433
524
|
// `resolve.dedupe` finally governs SSR and the dual-React crash cannot occur.
|
|
434
|
-
createApp: (clientEntry, load) => createWebApp({
|
|
525
|
+
createApp: (clientEntry, load, dev) => createWebApp({
|
|
435
526
|
adapter: asAdapter(fw.adapter),
|
|
436
527
|
manifest: discoverRoutes(routesDir, { load }),
|
|
437
528
|
clientEntry,
|
|
438
|
-
...(fw
|
|
439
|
-
|
|
529
|
+
...frameworkWebAppOptions(fw, backend),
|
|
530
|
+
onLoaderError: dev.onLoaderError,
|
|
440
531
|
}),
|
|
441
532
|
});
|
|
442
533
|
console.log(`nifra dev (vite) → http://localhost:${server.port}`);
|
|
@@ -445,23 +536,26 @@ async function dev(app, flags) {
|
|
|
445
536
|
/**
|
|
446
537
|
* `nifra build --target <t>` - package the engine (buildClient + buildServer + prerender) into one
|
|
447
538
|
* command that emits a full deploy dir, so an app no longer hand-writes build-bun.ts + _worker.ts +
|
|
448
|
-
* _routes.json per target. The adapter is imported from `framework.ts` (the edge-bundlable
|
|
449
|
-
* `nifra.config.ts`, which pulls in Vite plugins), and the backend from
|
|
450
|
-
* `buildTarget` generates the per-target server entry from those +
|
|
539
|
+
* _routes.json per target. The adapter is imported from `backend/framework.ts` (the edge-bundlable
|
|
540
|
+
* file - never `nifra.config.ts`, which pulls in Vite plugins), and the backend from
|
|
541
|
+
* `backend/app.ts` when present; `buildTarget` generates the per-target server entry from those +
|
|
542
|
+
* the app's `routes/`.
|
|
451
543
|
*/
|
|
452
544
|
/**
|
|
453
545
|
* Refuse a `use` the generated server entry cannot import.
|
|
454
546
|
*
|
|
455
|
-
* `nifra build` emits `import { use } from <frameworkFile>` (framework.ts, the
|
|
456
|
-
* but `loadApp` prefers `nifra.config.ts` - so an app with BOTH files whose
|
|
457
|
-
* `nifra.config.ts` makes `fw.use` defined while `framework.ts` exports
|
|
458
|
-
* build dies later with an opaque bundler error pointing at generated
|
|
459
|
-
* exact move instead, in the spirit of `assertPipelineSeparation`
|
|
547
|
+
* `nifra build` emits `import { use } from <frameworkFile>` (backend/framework.ts, the
|
|
548
|
+
* edge-bundlable file), but `loadApp` prefers `nifra.config.ts` - so an app with BOTH files whose
|
|
549
|
+
* `use` lives only in `nifra.config.ts` makes `fw.use` defined while `backend/framework.ts` exports
|
|
550
|
+
* nothing of that name, and the build dies later with an opaque bundler error pointing at generated
|
|
551
|
+
* code. Refuse it here with the exact move instead, in the spirit of `assertPipelineSeparation`
|
|
552
|
+
* (load.ts).
|
|
460
553
|
*
|
|
461
|
-
* Detected by importing `frameworkFile` and checking the named export, not by comparing paths
|
|
462
|
-
* a split app legitimately DEFINES `use` in framework.ts and re-exports it from
|
|
463
|
-
* (exactly how `adapter` reaches both readers), and a path compare would refuse
|
|
464
|
-
* The import is cheap - framework.ts is already in the loaded config's
|
|
554
|
+
* Detected by importing `frameworkFile` and checking the named export, not by comparing paths
|
|
555
|
+
* alone: a split app legitimately DEFINES `use` in backend/framework.ts and re-exports it from
|
|
556
|
+
* nifra.config.ts (exactly how `adapter` reaches both readers), and a path compare would refuse
|
|
557
|
+
* that correct layout. The import is cheap - backend/framework.ts is already in the loaded config's
|
|
558
|
+
* module graph in that layout.
|
|
465
559
|
*/
|
|
466
560
|
export async function assertUseIsEdgeExported(use, configPath, frameworkFile) {
|
|
467
561
|
if (use === undefined || configPath === frameworkFile)
|
|
@@ -470,10 +564,55 @@ export async function assertUseIsEdgeExported(use, configPath, frameworkFile) {
|
|
|
470
564
|
if (typeof mod.use === "function")
|
|
471
565
|
return;
|
|
472
566
|
throw new Error(`[nifra] \`use\` is exported from ${configPath} but not from ${frameworkFile}. ` +
|
|
473
|
-
"`nifra build` generates a server entry that imports `use` from framework.ts (the
|
|
474
|
-
"file), so this build would fail later with an opaque bundler error inside generated code.\n\n" +
|
|
475
|
-
" - Define `use` in framework.ts and re-export it from nifra.config.ts " +
|
|
476
|
-
'(`export { use } from "./framework.ts"`) so `nifra dev` sees it too.');
|
|
567
|
+
"`nifra build` generates a server entry that imports `use` from backend/framework.ts (the " +
|
|
568
|
+
"edge-bundled file), so this build would fail later with an opaque bundler error inside generated code.\n\n" +
|
|
569
|
+
" - Define `use` in backend/framework.ts and re-export it from nifra.config.ts " +
|
|
570
|
+
'(`export { use } from "./backend/framework.ts"`) so `nifra dev` sees it too.');
|
|
571
|
+
}
|
|
572
|
+
/**
|
|
573
|
+
* Refuse a forwarded framework field (`apiPrefix`, `mounts`, `csp`, ...) the generated server entry
|
|
574
|
+
* would not see, or would see with a different value.
|
|
575
|
+
*
|
|
576
|
+
* `nifra dev` reads these from the loaded config (`nifra.config.ts` when it exists) while the
|
|
577
|
+
* server entry imports them from backend/framework.ts. A field set only in nifra.config.ts would be
|
|
578
|
+
* missing from production, and one defined separately in each file could differ - dev mounting the
|
|
579
|
+
* backend at one path while production mounts it at another. Identity is required, which a
|
|
580
|
+
* re-export satisfies.
|
|
581
|
+
*/
|
|
582
|
+
export async function assertFrameworkOptionsEdgeExported(fw, configPath, frameworkFile) {
|
|
583
|
+
if (configPath === frameworkFile)
|
|
584
|
+
return;
|
|
585
|
+
const set = FRAMEWORK_WEB_OPTIONS.filter((name) => fw[name] !== undefined);
|
|
586
|
+
if (set.length === 0)
|
|
587
|
+
return;
|
|
588
|
+
const mod = (await import(__rewriteRelativeImportExtension(frameworkFile)).catch(() => ({})));
|
|
589
|
+
const drifted = set.filter((name) => mod[name] !== fw[name]);
|
|
590
|
+
if (drifted.length === 0)
|
|
591
|
+
return;
|
|
592
|
+
const names = drifted.map((name) => `\`${name}\``).join(", ");
|
|
593
|
+
throw new Error(`[nifra] ${names} ${drifted.length === 1 ? "is" : "are"} exported from ${configPath} but not, or not as the same value, from ${frameworkFile}. ` +
|
|
594
|
+
"`nifra build` generates a server entry that imports these from backend/framework.ts, so production " +
|
|
595
|
+
"would serve a different app than `nifra dev`.\n\n" +
|
|
596
|
+
` - Define ${drifted.length === 1 ? "it" : "them"} in backend/framework.ts and re-export from nifra.config.ts ` +
|
|
597
|
+
`(\`export { ${drifted.join(", ")} } from "./backend/framework.ts"\`).`);
|
|
598
|
+
}
|
|
599
|
+
/**
|
|
600
|
+
* Refuse a build whose page routes sit under a mount: each would ship as a route that can never render.
|
|
601
|
+
* Read from the config and `routes/` alone; the server also checks its own mount table at startup,
|
|
602
|
+
* which covers a mount added inside `use`.
|
|
603
|
+
*/
|
|
604
|
+
/** `nifra dev` shows the in-page issues badge unless `--no-indicator` or `dev.indicator: false` turns it off. */
|
|
605
|
+
export function showsIndicator(flags, fw) {
|
|
606
|
+
return !flags.noIndicator && fw.dev?.indicator !== false;
|
|
607
|
+
}
|
|
608
|
+
export function assertNoShadowedPages(app) {
|
|
609
|
+
const paths = frameworkMountPaths(app.framework, app.backend !== undefined);
|
|
610
|
+
if (paths.length === 0)
|
|
611
|
+
return;
|
|
612
|
+
const shadowed = shadowedPages(discoverRoutes(app.routesDir), paths);
|
|
613
|
+
if (shadowed.length === 0)
|
|
614
|
+
return;
|
|
615
|
+
throw new Error(`[nifra] build blocked: ${formatShadowedPages(shadowed)}`);
|
|
477
616
|
}
|
|
478
617
|
/**
|
|
479
618
|
* A production build must not ship a workspace-linked package whose `default`-condition artifact is
|
|
@@ -505,11 +644,9 @@ async function assertFreshWorkspaceDists(cwd) {
|
|
|
505
644
|
: ` ${f.package}: ${f.distFile} is ${f.behindSeconds}s behind ${f.sourceFile}`);
|
|
506
645
|
throw new Error(`[nifra] build blocked: ${stale.length} workspace-linked package(s) ship a stale or missing dist artifact, so the deployed app would serve code this build did not produce:\n${lines.join("\n")}\nBuild the offending package(s) (\`bun run build\` in each) so every export target exists and is at least as new as its source, then rebuild.`);
|
|
507
646
|
}
|
|
508
|
-
async function buildForTarget(app,
|
|
509
|
-
const {
|
|
510
|
-
|
|
511
|
-
throw new Error(`[nifra] unknown --target "${target}". Valid: ${BUILD_TARGETS.join(", ")}.`);
|
|
512
|
-
}
|
|
647
|
+
async function buildForTarget(app, flag, flags) {
|
|
648
|
+
const { parseBuildTarget, renderSizeReport } = await import("@nifrajs/web/build");
|
|
649
|
+
const target = parseBuildTarget(flag ?? app.framework.target ?? "bun", flag === undefined ? "target in nifra.config.ts" : "--target");
|
|
513
650
|
if (flags.vite && flags.bun) {
|
|
514
651
|
throw new Error("[nifra] `nifra build` takes `--vite` or `--bun`, not both.");
|
|
515
652
|
}
|
|
@@ -544,17 +681,18 @@ async function buildForTarget(app, target, flags) {
|
|
|
544
681
|
const buildTarget = useVite
|
|
545
682
|
? (await import("@nifrajs/web/build-vite")).buildTargetVite
|
|
546
683
|
: (await import("@nifrajs/web/build")).buildTarget;
|
|
547
|
-
const { routesDir,
|
|
548
|
-
//
|
|
549
|
-
//
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
const backendFile = resolve(cwd, "backend.ts");
|
|
684
|
+
const { routesDir, cwd, backend } = app;
|
|
685
|
+
// `vercel deploy --prebuilt` uploads `.vercel/output` from the project root, so that is where a
|
|
686
|
+
// Vercel build goes unless `--out` says otherwise.
|
|
687
|
+
const outDir = target === "vercel" && flags.out === undefined ? resolve(cwd, ".vercel/output") : app.outDir;
|
|
688
|
+
// The server entry must import the adapter from `backend/framework.ts` (edge-safe), not the
|
|
689
|
+
// loaded config, so a multi-target app's Vite-plugin config never reaches the edge bundle
|
|
690
|
+
// (load.ts header).
|
|
691
|
+
const frameworkFile = adapterFile(cwd);
|
|
692
|
+
const backendFile = resolve(cwd, BACKEND_APP_FILE);
|
|
557
693
|
await assertUseIsEdgeExported(fw.use, app.configPath, frameworkFile);
|
|
694
|
+
await assertFrameworkOptionsEdgeExported(fw, app.configPath, frameworkFile);
|
|
695
|
+
assertNoShadowedPages(app);
|
|
558
696
|
// Plugin FORMAT differs by pipeline: the Bun build takes Bun plugins (clientPlugins/serverPlugins); the
|
|
559
697
|
// Vite build takes the app's Vite plugins (fw.vitePlugins) for BOTH halves. buildTargetWith forwards
|
|
560
698
|
// whatever it's given straight to the chosen bundler, which casts to its own plugin type.
|
|
@@ -574,14 +712,17 @@ async function buildForTarget(app, target, flags) {
|
|
|
574
712
|
clientModule: fw.clientModule,
|
|
575
713
|
adapterImport: frameworkFile,
|
|
576
714
|
...(fw.use ? { useImport: frameworkFile } : {}),
|
|
715
|
+
...frameworkOptionImports(fw, frameworkFile),
|
|
577
716
|
...(backend !== undefined && existsSync(backendFile) ? { backendImport: backendFile } : {}),
|
|
578
717
|
...plugins,
|
|
579
718
|
...(fw.conditions ? { conditions: fw.conditions } : {}),
|
|
580
719
|
...(fw.define ? { define: fw.define } : {}),
|
|
581
720
|
...(fw.publicDir !== undefined ? { publicDir: fw.publicDir } : {}),
|
|
582
721
|
...(fw.publicEnvPrefix !== undefined ? { publicEnvPrefix: fw.publicEnvPrefix } : {}),
|
|
722
|
+
...(fw.secretExemptions !== undefined ? { secretExemptions: fw.secretExemptions } : {}),
|
|
583
723
|
...(fw.cssCodeSplit !== undefined ? { cssCodeSplit: fw.cssCodeSplit } : {}),
|
|
584
724
|
...(fw.cssLoading !== undefined ? { cssLoading: fw.cssLoading } : {}),
|
|
725
|
+
...(fw.clientIp !== undefined ? { clientIp: fw.clientIp } : {}),
|
|
585
726
|
// The static target needs a built app to drive prerendering - only build it when targeting static.
|
|
586
727
|
...(target === "static" ? { prerenderApp: await buildPrerenderApp(app) } : {}),
|
|
587
728
|
});
|
|
@@ -612,19 +753,18 @@ async function buildPrerenderApp(app) {
|
|
|
612
753
|
adapter: asAdapter(fw.adapter),
|
|
613
754
|
manifest: discoverRoutes(routesDir),
|
|
614
755
|
clientEntry: client.entry,
|
|
615
|
-
...(fw
|
|
756
|
+
...frameworkWebAppOptions(fw, backend),
|
|
616
757
|
...(client.routes ? { routePreload: client.routes } : {}),
|
|
617
758
|
...(client.css ? { styles: client.css } : {}),
|
|
618
759
|
...(client.routeStyles ? { routeStyles: client.routeStyles } : {}),
|
|
619
760
|
...(client.cssLoading !== undefined ? { cssLoading: client.cssLoading } : {}),
|
|
620
|
-
...apiOf(backend),
|
|
621
761
|
});
|
|
622
762
|
}
|
|
623
763
|
async function start(app, flags) {
|
|
624
764
|
const serverFile = resolve(app.outDir, "server.js");
|
|
625
765
|
if (!existsSync(serverFile)) {
|
|
626
766
|
// A Cloudflare Pages build emits `_worker.js` (a Workers bundle), not a self-hosting `server.js` - so
|
|
627
|
-
// `nifra start` on a dir built for
|
|
767
|
+
// `nifra start` on a dir built for cloudflare would otherwise fail with a bare "no server.js". Name the
|
|
628
768
|
// actual mismatch and the fix.
|
|
629
769
|
if (existsSync(resolve(app.outDir, "_worker.js"))) {
|
|
630
770
|
throw new Error(`[nifra] ${app.outDir} holds a Cloudflare Pages worker bundle (_worker.js), not a self-hosting ` +
|
|
@@ -640,13 +780,14 @@ export function parseFlags(args) {
|
|
|
640
780
|
// the SAME uncommon port for `nifra dev` and `nifra start` (DEFAULT_DEV_PORT) so a project's URL is
|
|
641
781
|
// stable across commands and doesn't collide with the usual 3000/5173/8080 crowd.
|
|
642
782
|
let port = Number(Bun.env.PORT ?? DEFAULT_DEV_PORT);
|
|
643
|
-
let out
|
|
783
|
+
let out;
|
|
644
784
|
let poll = Bun.env.CHOKIDAR_USEPOLLING === "1";
|
|
645
|
-
let target
|
|
785
|
+
let target;
|
|
646
786
|
let report = false;
|
|
647
787
|
let vite = false;
|
|
648
788
|
let bun = false;
|
|
649
789
|
let allowDuplicateIdentity = false;
|
|
790
|
+
let noIndicator = false;
|
|
650
791
|
for (let i = 0; i < args.length; i++) {
|
|
651
792
|
const a = args[i];
|
|
652
793
|
if ((a === "--port" || a === "-p") && args[i + 1])
|
|
@@ -665,11 +806,13 @@ export function parseFlags(args) {
|
|
|
665
806
|
bun = true;
|
|
666
807
|
else if (a === "--allow-duplicate-identity")
|
|
667
808
|
allowDuplicateIdentity = true;
|
|
809
|
+
else if (a === "--no-indicator")
|
|
810
|
+
noIndicator = true;
|
|
668
811
|
}
|
|
669
812
|
if (!Number.isFinite(port) || port < 0 || port > 65535) {
|
|
670
813
|
throw new Error(`[nifra] invalid --port: ${port}`);
|
|
671
814
|
}
|
|
672
|
-
return { port, out, poll, target, report, vite, bun, allowDuplicateIdentity };
|
|
815
|
+
return { port, out, poll, target, report, vite, bun, allowDuplicateIdentity, noIndicator };
|
|
673
816
|
}
|
|
674
817
|
/**
|
|
675
818
|
* `assure` uses the structured-bundle lane only when explicitly asked. A bare `--json` is NOT a
|
|
@@ -704,8 +847,15 @@ function installReflectionExitHint() {
|
|
|
704
847
|
inFlight = command;
|
|
705
848
|
};
|
|
706
849
|
}
|
|
850
|
+
/** Set on the copy of `nifra mcp` that serves without `.env` files, so it does not restart again. */
|
|
851
|
+
const MCP_WITHOUT_ENV_FILES = "NIFRA_MCP_WITHOUT_ENV_FILES";
|
|
707
852
|
async function main() {
|
|
708
853
|
const { argv: rawArgv, files: envFiles } = takeEnvFileFlags(cliArgv());
|
|
854
|
+
// `nifra mcp` keeps none of the `.env` values Bun loaded at startup. Decided before the
|
|
855
|
+
// `--env-file`s below, which the caller names explicitly and which stay.
|
|
856
|
+
const forgotten = rawArgv[0] === "mcp" && process.env[MCP_WITHOUT_ENV_FILES] !== "1"
|
|
857
|
+
? await forgetAutoLoadedEnv()
|
|
858
|
+
: [];
|
|
709
859
|
// Applied before any command runs: a reflecting command imports the app on its first await, and the
|
|
710
860
|
// app reads its environment at module scope, so the variables must already be in place by then.
|
|
711
861
|
if (envFiles.length > 0)
|
|
@@ -727,6 +877,16 @@ async function main() {
|
|
|
727
877
|
// `mcp` runs a long-lived stdio server and loads the project lazily per-tool - it must not go through
|
|
728
878
|
// the eager `loadApp` below (which would fail fast on a project that's API-only / not yet built).
|
|
729
879
|
if (command === "mcp") {
|
|
880
|
+
// Bun passes what it loaded to every subprocess whatever `process.env` says now, so the session
|
|
881
|
+
// moves to a copy of this process that never loads `.env` files.
|
|
882
|
+
if (forgotten.length > 0) {
|
|
883
|
+
const { handOffSession } = await import("./mcp-delegate.js");
|
|
884
|
+
const exitCode = await handOffSession([process.execPath, "--no-env-file", ...process.argv.slice(1)], { cwd: process.cwd(), env: { ...process.env, [MCP_WITHOUT_ENV_FILES]: "1" } });
|
|
885
|
+
if (exitCode !== undefined) {
|
|
886
|
+
process.exitCode = exitCode;
|
|
887
|
+
return;
|
|
888
|
+
}
|
|
889
|
+
}
|
|
730
890
|
const { runMcpServer } = await import("./mcp.js");
|
|
731
891
|
// `nifra mcp [dir]` - an explicit project directory pins the root (for clients configured outside
|
|
732
892
|
// the project); otherwise the server resolves it from cwd + the client's MCP roots.
|
|
@@ -772,11 +932,16 @@ async function main() {
|
|
|
772
932
|
}));
|
|
773
933
|
return;
|
|
774
934
|
}
|
|
775
|
-
|
|
935
|
+
// `nifra db <sub>` is the catalog's `db-<sub>` command, projected to MCP as `nifra_db_<sub>`.
|
|
936
|
+
const dbSub = command === "db" && argv[1] !== undefined && !argv[1].startsWith("-") ? argv[1] : undefined;
|
|
937
|
+
if (command === "db" && (dbSub === undefined || findCommandSpec(`db-${dbSub}`) === undefined)) {
|
|
938
|
+
throw new Error("[nifra] usage: nifra db schema|query|role|audit - see `nifra help`");
|
|
939
|
+
}
|
|
940
|
+
const catalogSpec = findCommandSpec(dbSub === undefined ? command : `db-${dbSub}`);
|
|
776
941
|
if (catalogSpec?.transports.includes("cli")) {
|
|
777
942
|
const markReflecting = installReflectionExitHint();
|
|
778
943
|
try {
|
|
779
|
-
const input = bindCommandArgv(catalogSpec, argv.slice(1));
|
|
944
|
+
const input = bindCommandArgv(catalogSpec, argv.slice(dbSub === undefined ? 1 : 2));
|
|
780
945
|
markReflecting(command);
|
|
781
946
|
const output = await catalogSpec.run(input, { cwd: process.cwd(), cliVersion: CLI_VERSION });
|
|
782
947
|
markReflecting(undefined);
|
|
@@ -824,7 +989,7 @@ async function main() {
|
|
|
824
989
|
process.exitCode = 1;
|
|
825
990
|
return;
|
|
826
991
|
}
|
|
827
|
-
// `init-agents` retrofits the agent
|
|
992
|
+
// `init-agents` retrofits the agent files (.mcp.json, CLAUDE.md, AGENTS.md, …) into the cwd. It's a
|
|
828
993
|
// pure file-writing command independent of the app loading, so dispatch it before the eager `loadApp`
|
|
829
994
|
// (an existing app might be API-only or not yet built). It always succeeds unless a write throws.
|
|
830
995
|
if (command === "init-agents") {
|
|
@@ -832,6 +997,7 @@ async function main() {
|
|
|
832
997
|
await runInitAgents(process.cwd(), {
|
|
833
998
|
json: argv.includes("--json"),
|
|
834
999
|
force: argv.includes("--force"),
|
|
1000
|
+
syncMcp: argv.includes("--sync-mcp"),
|
|
835
1001
|
});
|
|
836
1002
|
return;
|
|
837
1003
|
}
|
|
@@ -852,7 +1018,11 @@ async function main() {
|
|
|
852
1018
|
}
|
|
853
1019
|
try {
|
|
854
1020
|
const { runSdk } = await import("./sdk.js");
|
|
855
|
-
await runSdk(process.cwd(), {
|
|
1021
|
+
await runSdk(process.cwd(), {
|
|
1022
|
+
language,
|
|
1023
|
+
...(out !== undefined ? { out } : {}),
|
|
1024
|
+
...(argv.includes("--strict") ? { strict: true } : {}),
|
|
1025
|
+
});
|
|
856
1026
|
}
|
|
857
1027
|
catch (err) {
|
|
858
1028
|
console.error(formatCliError(err));
|
|
@@ -874,6 +1044,8 @@ async function main() {
|
|
|
874
1044
|
list: argv.includes("--list"),
|
|
875
1045
|
verify: !argv.includes("--no-verify"),
|
|
876
1046
|
allowDowngrade: argv.includes("--allow-downgrade"),
|
|
1047
|
+
exact: argv.includes("--exact"),
|
|
1048
|
+
cliVersion: CLI_VERSION,
|
|
877
1049
|
});
|
|
878
1050
|
if (!ok)
|
|
879
1051
|
process.exitCode = 1;
|
|
@@ -894,6 +1066,13 @@ async function main() {
|
|
|
894
1066
|
}
|
|
895
1067
|
const flags = parseFlags(argv.slice(1));
|
|
896
1068
|
const app = await loadApp(process.cwd(), flags.out);
|
|
1069
|
+
if (command !== "start") {
|
|
1070
|
+
const { refreshRouteTypes, watchRouteTypes } = await import("./route-types.js");
|
|
1071
|
+
refreshRouteTypes(app.cwd);
|
|
1072
|
+
// A relaunched `nifra dev --bun` child runs beside its parent, which already watches.
|
|
1073
|
+
if (command === "dev" && process.env.NIFRA_BUN_DEV_TOKEN === undefined)
|
|
1074
|
+
watchRouteTypes(app.cwd);
|
|
1075
|
+
}
|
|
897
1076
|
if (command === "dev")
|
|
898
1077
|
await dev(app, flags);
|
|
899
1078
|
else if (command === "build")
|
|
@@ -985,6 +1164,7 @@ function isCliCommandToken(value) {
|
|
|
985
1164
|
value === "mcp" ||
|
|
986
1165
|
value === "init-agents" ||
|
|
987
1166
|
value === "upgrade" ||
|
|
1167
|
+
value === "db" ||
|
|
988
1168
|
value === "help" ||
|
|
989
1169
|
value === "--help" ||
|
|
990
1170
|
value === "-h" ||
|