@nifrajs/cli 3.3.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -3
- package/dist/app-files.d.ts +11 -0
- package/dist/app-files.d.ts.map +1 -0
- package/dist/app-files.js +34 -0
- package/dist/app-files.js.map +1 -0
- package/dist/assure-hydration.d.ts +7 -1
- package/dist/assure-hydration.d.ts.map +1 -1
- package/dist/assure-hydration.js +128 -43
- package/dist/assure-hydration.js.map +1 -1
- package/dist/assure.d.ts.map +1 -1
- package/dist/assure.js +16 -2
- package/dist/assure.js.map +1 -1
- package/dist/capabilities-tool.d.ts +5 -1
- package/dist/capabilities-tool.d.ts.map +1 -1
- package/dist/capabilities-tool.js +32 -10
- package/dist/capabilities-tool.js.map +1 -1
- package/dist/cdn-check.d.ts +41 -0
- package/dist/cdn-check.d.ts.map +1 -0
- package/dist/cdn-check.js +207 -0
- package/dist/cdn-check.js.map +1 -0
- package/dist/check-diagnostics.d.ts +7 -15
- package/dist/check-diagnostics.d.ts.map +1 -1
- package/dist/check-diagnostics.js +162 -713
- package/dist/check-diagnostics.js.map +1 -1
- package/dist/check-scan.d.ts +58 -7
- package/dist/check-scan.d.ts.map +1 -1
- package/dist/check-scan.js +323 -62
- package/dist/check-scan.js.map +1 -1
- package/dist/check.d.ts +1 -0
- package/dist/check.d.ts.map +1 -1
- package/dist/check.js +145 -54
- package/dist/check.js.map +1 -1
- package/dist/cli.d.ts +42 -16
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +306 -87
- package/dist/cli.js.map +1 -1
- package/dist/command-catalog.d.ts +60 -5
- package/dist/command-catalog.d.ts.map +1 -1
- package/dist/command-catalog.js +298 -33
- package/dist/command-catalog.js.map +1 -1
- package/dist/config-target.d.ts +8 -0
- package/dist/config-target.d.ts.map +1 -0
- package/dist/config-target.js +45 -0
- package/dist/config-target.js.map +1 -0
- package/dist/contracts.d.ts +3 -2
- package/dist/contracts.d.ts.map +1 -1
- package/dist/contracts.js +56 -16
- package/dist/contracts.js.map +1 -1
- package/dist/db-audit.d.ts +37 -0
- package/dist/db-audit.d.ts.map +1 -0
- package/dist/db-audit.js +99 -0
- package/dist/db-audit.js.map +1 -0
- package/dist/db-child.d.ts +64 -0
- package/dist/db-child.d.ts.map +1 -0
- package/dist/db-child.js +213 -0
- package/dist/db-child.js.map +1 -0
- package/dist/db-config.d.ts +60 -0
- package/dist/db-config.d.ts.map +1 -0
- package/dist/db-config.js +176 -0
- package/dist/db-config.js.map +1 -0
- package/dist/db-tool.d.ts +84 -0
- package/dist/db-tool.d.ts.map +1 -0
- package/dist/db-tool.js +537 -0
- package/dist/db-tool.js.map +1 -0
- package/dist/dev-bun-config.d.ts +20 -13
- package/dist/dev-bun-config.d.ts.map +1 -1
- package/dist/dev-bun-config.js +98 -41
- package/dist/dev-bun-config.js.map +1 -1
- package/dist/dev-feed-tool.d.ts +106 -0
- package/dist/dev-feed-tool.d.ts.map +1 -0
- package/dist/dev-feed-tool.js +713 -0
- package/dist/dev-feed-tool.js.map +1 -0
- package/dist/dev-server-client.d.ts +67 -0
- package/dist/dev-server-client.d.ts.map +1 -0
- package/dist/dev-server-client.js +195 -0
- package/dist/dev-server-client.js.map +1 -0
- package/dist/diagnostics.d.ts +86 -0
- package/dist/diagnostics.d.ts.map +1 -1
- package/dist/diagnostics.js +85 -0
- package/dist/diagnostics.js.map +1 -1
- package/dist/diff-tool.d.ts +3 -3
- package/dist/diff-tool.d.ts.map +1 -1
- package/dist/diff-tool.js +6 -5
- package/dist/diff-tool.js.map +1 -1
- package/dist/docs-search.d.ts.map +1 -1
- package/dist/docs-search.js +12 -4
- package/dist/docs-search.js.map +1 -1
- package/dist/doctor.d.ts +17 -0
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +76 -15
- package/dist/doctor.js.map +1 -1
- package/dist/env-file.d.ts +15 -0
- package/dist/env-file.d.ts.map +1 -1
- package/dist/env-file.js +73 -1
- package/dist/env-file.js.map +1 -1
- package/dist/fix-recipes.d.ts.map +1 -1
- package/dist/fix-recipes.js +41 -21
- package/dist/fix-recipes.js.map +1 -1
- package/dist/frontend-guidance.js +5 -5
- package/dist/frontend-guidance.js.map +1 -1
- package/dist/i18n-check.d.ts +16 -0
- package/dist/i18n-check.d.ts.map +1 -0
- package/dist/i18n-check.js +154 -0
- package/dist/i18n-check.js.map +1 -0
- package/dist/init-agents.d.ts +49 -13
- package/dist/init-agents.d.ts.map +1 -1
- package/dist/init-agents.js +239 -34
- package/dist/init-agents.js.map +1 -1
- package/dist/internal/code-unit-order.d.ts +4 -0
- package/dist/internal/code-unit-order.d.ts.map +1 -0
- package/dist/internal/code-unit-order.js +6 -0
- package/dist/internal/code-unit-order.js.map +1 -0
- package/dist/internal/script-kind.d.ts +7 -0
- package/dist/internal/script-kind.d.ts.map +1 -0
- package/dist/internal/script-kind.js +12 -0
- package/dist/internal/script-kind.js.map +1 -0
- package/dist/internal/source-facts.d.ts +5 -0
- package/dist/internal/source-facts.d.ts.map +1 -1
- package/dist/internal/source-facts.js +61 -5
- package/dist/internal/source-facts.js.map +1 -1
- package/dist/internal/typescript-import.d.ts +49 -24
- package/dist/internal/typescript-import.d.ts.map +1 -1
- package/dist/internal/typescript-import.js +507 -67
- package/dist/internal/typescript-import.js.map +1 -1
- package/dist/introspect.d.ts +6 -4
- package/dist/introspect.d.ts.map +1 -1
- package/dist/introspect.js +93 -74
- package/dist/introspect.js.map +1 -1
- package/dist/learn.js +3 -3
- package/dist/learn.js.map +1 -1
- package/dist/levels-tool.d.ts +10 -0
- package/dist/levels-tool.d.ts.map +1 -1
- package/dist/levels-tool.js +33 -3
- package/dist/levels-tool.js.map +1 -1
- package/dist/load.d.ts +71 -16
- package/dist/load.d.ts.map +1 -1
- package/dist/load.js +78 -18
- package/dist/load.js.map +1 -1
- package/dist/manifest-tool.d.ts.map +1 -1
- package/dist/manifest-tool.js +3 -2
- package/dist/manifest-tool.js.map +1 -1
- package/dist/mcp-app-tool.d.ts.map +1 -1
- package/dist/mcp-app-tool.js +2 -0
- package/dist/mcp-app-tool.js.map +1 -1
- package/dist/mcp-context.d.ts.map +1 -1
- package/dist/mcp-context.js +5 -6
- package/dist/mcp-context.js.map +1 -1
- package/dist/mcp-delegate.d.ts +57 -0
- package/dist/mcp-delegate.d.ts.map +1 -0
- package/dist/mcp-delegate.js +130 -0
- package/dist/mcp-delegate.js.map +1 -0
- package/dist/mcp-docs-tools.d.ts.map +1 -1
- package/dist/mcp-docs-tools.js +23 -5
- package/dist/mcp-docs-tools.js.map +1 -1
- package/dist/mcp-exec.d.ts +3 -0
- package/dist/mcp-exec.d.ts.map +1 -1
- package/dist/mcp-exec.js +263 -131
- package/dist/mcp-exec.js.map +1 -1
- package/dist/mcp-http.d.ts.map +1 -1
- package/dist/mcp-http.js +20 -5
- package/dist/mcp-http.js.map +1 -1
- package/dist/mcp-io.d.ts +26 -0
- package/dist/mcp-io.d.ts.map +1 -1
- package/dist/mcp-io.js +113 -0
- package/dist/mcp-io.js.map +1 -1
- package/dist/mcp-isolate.d.ts +48 -0
- package/dist/mcp-isolate.d.ts.map +1 -0
- package/dist/mcp-isolate.js +222 -0
- package/dist/mcp-isolate.js.map +1 -0
- package/dist/mcp-path.d.ts +10 -0
- package/dist/mcp-path.d.ts.map +1 -0
- package/dist/mcp-path.js +51 -0
- package/dist/mcp-path.js.map +1 -0
- package/dist/mcp-project-child.d.ts +74 -0
- package/dist/mcp-project-child.d.ts.map +1 -0
- package/dist/mcp-project-child.js +130 -0
- package/dist/mcp-project-child.js.map +1 -0
- package/dist/mcp-reflect.d.ts.map +1 -1
- package/dist/mcp-reflect.js +2 -2
- package/dist/mcp-reflect.js.map +1 -1
- package/dist/mcp-render.d.ts.map +1 -1
- package/dist/mcp-render.js +64 -45
- package/dist/mcp-render.js.map +1 -1
- package/dist/mcp-root.d.ts +37 -9
- package/dist/mcp-root.d.ts.map +1 -1
- package/dist/mcp-root.js +124 -25
- package/dist/mcp-root.js.map +1 -1
- package/dist/mcp-run.d.ts +18 -1
- package/dist/mcp-run.d.ts.map +1 -1
- package/dist/mcp-run.js +159 -55
- package/dist/mcp-run.js.map +1 -1
- package/dist/mcp-ws.js +4 -1
- package/dist/mcp-ws.js.map +1 -1
- package/dist/mcp.d.ts +18 -5
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +165 -82
- package/dist/mcp.js.map +1 -1
- package/dist/migrate-layout.d.ts +44 -0
- package/dist/migrate-layout.d.ts.map +1 -0
- package/dist/migrate-layout.js +982 -0
- package/dist/migrate-layout.js.map +1 -0
- package/dist/openapi-tool.d.ts +2 -2
- package/dist/openapi-tool.d.ts.map +1 -1
- package/dist/openapi-tool.js.map +1 -1
- package/dist/openapi-types.d.ts +2 -1
- package/dist/openapi-types.d.ts.map +1 -1
- package/dist/openapi-types.js +240 -12
- package/dist/openapi-types.js.map +1 -1
- package/dist/pipeline-report.d.ts +3 -2
- package/dist/pipeline-report.d.ts.map +1 -1
- package/dist/pipeline-report.js +19 -16
- package/dist/pipeline-report.js.map +1 -1
- package/dist/port.d.ts +5 -4
- package/dist/port.d.ts.map +1 -1
- package/dist/port.js +46 -29
- package/dist/port.js.map +1 -1
- package/dist/project-facts.d.ts +28 -4
- package/dist/project-facts.d.ts.map +1 -1
- package/dist/project-facts.js +12 -2
- package/dist/project-facts.js.map +1 -1
- package/dist/recipes/4.0.0.d.ts +9 -0
- package/dist/recipes/4.0.0.d.ts.map +1 -0
- package/dist/recipes/4.0.0.js +34 -0
- package/dist/recipes/4.0.0.js.map +1 -0
- package/dist/recipes/index.d.ts.map +1 -1
- package/dist/recipes/index.js +2 -0
- package/dist/recipes/index.js.map +1 -1
- package/dist/release-verification.d.ts +11 -1
- package/dist/release-verification.d.ts.map +1 -1
- package/dist/release-verification.js +47 -4
- package/dist/release-verification.js.map +1 -1
- package/dist/review.d.ts +21 -0
- package/dist/review.d.ts.map +1 -0
- package/dist/review.js +823 -0
- package/dist/review.js.map +1 -0
- package/dist/route-types.d.ts +28 -0
- package/dist/route-types.d.ts.map +1 -0
- package/dist/route-types.js +106 -0
- package/dist/route-types.js.map +1 -0
- package/dist/rules/codes.d.ts +9 -0
- package/dist/rules/codes.d.ts.map +1 -1
- package/dist/rules/codes.js +9 -0
- package/dist/rules/codes.js.map +1 -1
- package/dist/rules/data-guard.d.ts +12 -0
- package/dist/rules/data-guard.d.ts.map +1 -0
- package/dist/rules/data-guard.js +213 -0
- package/dist/rules/data-guard.js.map +1 -0
- package/dist/rules/index.d.ts +3 -0
- package/dist/rules/index.d.ts.map +1 -1
- package/dist/rules/index.js +17 -5
- package/dist/rules/index.js.map +1 -1
- package/dist/rules/islands.d.ts.map +1 -1
- package/dist/rules/islands.js +7 -4
- package/dist/rules/islands.js.map +1 -1
- package/dist/rules/legacy.d.ts +3 -1
- package/dist/rules/legacy.d.ts.map +1 -1
- package/dist/rules/legacy.js +829 -34
- package/dist/rules/legacy.js.map +1 -1
- package/dist/rules/nano.d.ts.map +1 -1
- package/dist/rules/nano.js +15 -6
- package/dist/rules/nano.js.map +1 -1
- package/dist/rules/pages.d.ts +15 -0
- package/dist/rules/pages.d.ts.map +1 -0
- package/dist/rules/pages.js +88 -0
- package/dist/rules/pages.js.map +1 -0
- package/dist/rules/routes.d.ts +11 -0
- package/dist/rules/routes.d.ts.map +1 -1
- package/dist/rules/routes.js +180 -2
- package/dist/rules/routes.js.map +1 -1
- package/dist/rules/secrets.d.ts +10 -0
- package/dist/rules/secrets.d.ts.map +1 -0
- package/dist/rules/secrets.js +169 -0
- package/dist/rules/secrets.js.map +1 -0
- package/dist/rules/security.d.ts +7 -0
- package/dist/rules/security.d.ts.map +1 -1
- package/dist/rules/security.js +171 -65
- package/dist/rules/security.js.map +1 -1
- package/dist/rules/typescript.d.ts +16 -0
- package/dist/rules/typescript.d.ts.map +1 -0
- package/dist/rules/typescript.js +59 -0
- package/dist/rules/typescript.js.map +1 -0
- package/dist/rules/zones.d.ts +17 -0
- package/dist/rules/zones.d.ts.map +1 -0
- package/dist/rules/zones.js +174 -0
- package/dist/rules/zones.js.map +1 -0
- package/dist/scaffold.d.ts +25 -12
- package/dist/scaffold.d.ts.map +1 -1
- package/dist/scaffold.js +154 -90
- package/dist/scaffold.js.map +1 -1
- package/dist/sdk.d.ts +17 -3
- package/dist/sdk.d.ts.map +1 -1
- package/dist/sdk.js +941 -153
- package/dist/sdk.js.map +1 -1
- package/dist/search-terms.d.ts +2 -0
- package/dist/search-terms.d.ts.map +1 -1
- package/dist/search-terms.js +23 -8
- package/dist/search-terms.js.map +1 -1
- package/dist/smoke.d.ts +79 -0
- package/dist/smoke.d.ts.map +1 -0
- package/dist/smoke.js +494 -0
- package/dist/smoke.js.map +1 -0
- package/dist/sync-manifest.d.ts.map +1 -1
- package/dist/sync-manifest.js +2 -1
- package/dist/sync-manifest.js.map +1 -1
- package/dist/sync-routes.d.ts +4 -3
- package/dist/sync-routes.d.ts.map +1 -1
- package/dist/sync-routes.js +4 -3
- package/dist/sync-routes.js.map +1 -1
- package/dist/test-tool.d.ts.map +1 -1
- package/dist/test-tool.js +30 -11
- package/dist/test-tool.js.map +1 -1
- package/dist/types-search.d.ts.map +1 -1
- package/dist/types-search.js +11 -5
- package/dist/types-search.js.map +1 -1
- package/dist/upgrade.d.ts +36 -6
- package/dist/upgrade.d.ts.map +1 -1
- package/dist/upgrade.js +167 -29
- package/dist/upgrade.js.map +1 -1
- package/dist/verification-plan.d.ts.map +1 -1
- package/dist/verification-plan.js +8 -0
- package/dist/verification-plan.js.map +1 -1
- package/dist/verification.d.ts.map +1 -1
- package/dist/verification.js +45 -12
- package/dist/verification.js.map +1 -1
- package/dist/web-app-options.d.ts +19 -0
- package/dist/web-app-options.d.ts.map +1 -0
- package/dist/web-app-options.js +49 -0
- package/dist/web-app-options.js.map +1 -0
- package/dist/work-graph.d.ts +3 -0
- package/dist/work-graph.d.ts.map +1 -1
- package/dist/work-graph.js +33 -16
- package/dist/work-graph.js.map +1 -1
- package/docs/examples.json +185 -29
- package/docs/llms-full.txt +4123 -548
- package/docs/types.json +14391 -10060
- package/package.json +19 -11
- package/src/app-files.ts +40 -0
- package/src/assure-hydration.ts +139 -46
- package/src/assure.ts +17 -2
- package/src/capabilities-tool.ts +35 -9
- package/src/cdn-check.ts +268 -0
- package/src/check-diagnostics.ts +199 -797
- package/src/check-scan.ts +394 -63
- package/src/check.ts +146 -58
- package/src/cli.ts +347 -92
- package/src/command-catalog.ts +407 -45
- package/src/config-target.ts +47 -0
- package/src/contracts.ts +66 -15
- package/src/db-audit.ts +128 -0
- package/src/db-child.ts +285 -0
- package/src/db-config.ts +224 -0
- package/src/db-tool.ts +690 -0
- package/src/dev-bun-config.ts +116 -40
- package/src/dev-feed-tool.ts +912 -0
- package/src/dev-server-client.ts +267 -0
- package/src/diagnostics.ts +179 -0
- package/src/diff-tool.ts +6 -5
- package/src/docs-search.ts +10 -3
- package/src/doctor.ts +98 -15
- package/src/env-file.ts +78 -1
- package/src/fix-recipes.ts +44 -31
- package/src/frontend-guidance.ts +5 -5
- package/src/i18n-check.ts +179 -0
- package/src/init-agents.ts +301 -38
- package/src/internal/code-unit-order.ts +5 -0
- package/src/internal/script-kind.ts +11 -0
- package/src/internal/source-facts.ts +96 -22
- package/src/internal/typescript-import.ts +663 -68
- package/src/introspect.ts +93 -82
- package/src/learn.ts +3 -3
- package/src/levels-tool.ts +47 -2
- package/src/load.ts +144 -32
- package/src/manifest-tool.ts +3 -2
- package/src/mcp-app-tool.ts +2 -0
- package/src/mcp-context.ts +5 -7
- package/src/mcp-delegate.ts +162 -0
- package/src/mcp-docs-tools.ts +23 -5
- package/src/mcp-exec.ts +301 -161
- package/src/mcp-http.ts +22 -5
- package/src/mcp-io.ts +130 -0
- package/src/mcp-isolate.ts +287 -0
- package/src/mcp-path.ts +50 -0
- package/src/mcp-project-child.ts +182 -0
- package/src/mcp-reflect.ts +3 -2
- package/src/mcp-render.ts +72 -42
- package/src/mcp-root.ts +130 -24
- package/src/mcp-run.ts +190 -56
- package/src/mcp-ws.ts +4 -1
- package/src/mcp.ts +193 -91
- package/src/migrate-layout.ts +1121 -0
- package/src/openapi-tool.ts +7 -3
- package/src/openapi-types.ts +315 -13
- package/src/pipeline-report.ts +22 -18
- package/src/port.ts +53 -30
- package/src/project-facts.ts +42 -8
- package/src/recipes/4.0.0.ts +35 -0
- package/src/recipes/index.ts +2 -0
- package/src/release-verification.ts +74 -5
- package/src/review.ts +988 -0
- package/src/route-types.ts +133 -0
- package/src/rules/codes.ts +9 -0
- package/src/rules/data-guard.ts +230 -0
- package/src/rules/index.ts +18 -5
- package/src/rules/islands.ts +12 -4
- package/src/rules/legacy.ts +947 -35
- package/src/rules/nano.ts +18 -6
- package/src/rules/pages.ts +99 -0
- package/src/rules/routes.ts +181 -2
- package/src/rules/secrets.ts +174 -0
- package/src/rules/security.ts +198 -70
- package/src/rules/typescript.ts +77 -0
- package/src/rules/zones.ts +195 -0
- package/src/scaffold.ts +175 -98
- package/src/sdk.ts +1215 -158
- package/src/search-terms.ts +24 -7
- package/src/smoke.ts +607 -0
- package/src/sync-manifest.ts +2 -1
- package/src/sync-routes.ts +4 -3
- package/src/test-tool.ts +32 -11
- package/src/types-search.ts +11 -4
- package/src/upgrade.ts +205 -25
- package/src/verification-plan.ts +27 -0
- package/src/verification.ts +50 -12
- package/src/web-app-options.ts +57 -0
- package/src/work-graph.ts +40 -16
package/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,20 @@ 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
|
-
nifra check [--json] [--lints-only]
|
|
109
|
-
|
|
110
|
-
|
|
166
|
+
nifra check [--json] [--sarif] [--lints-only]
|
|
167
|
+
Gate: typecheck + lints (hand-rolled fetch(), untyped client("…"),
|
|
168
|
+
backend code in browser code). Run as "done"; --json for agents;
|
|
169
|
+
--sarif emits the same stable diagnostics as SARIF 2.1.0 for external
|
|
170
|
+
review tools; --lints-only skips tsc for a near-instant inner-loop pass.
|
|
111
171
|
nifra verify [--release] [--json] Run the shared repository verification gate. --release runs the
|
|
112
172
|
full build, test, coverage, corpus, consumer, and cross-runtime gate.
|
|
113
173
|
nifra fix [--code <NF-code>] Apply a registered diagnostic recipe, then print the remaining
|
|
@@ -125,8 +185,8 @@ Usage:
|
|
|
125
185
|
request field or a removed response field breaks; widening a
|
|
126
186
|
request enum or adding a response field doesn't) and fails
|
|
127
187
|
closed. Exits non-zero on any breaking change - run it in CI.
|
|
128
|
-
nifra sdk --lang <python|go> [--out <file>]
|
|
129
|
-
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.
|
|
130
190
|
nifra assure [--config <file>] [--json] Route-assurance report. Human table by default; --json emits
|
|
131
191
|
the {ok, routes, findings} report for agents.
|
|
132
192
|
nifra assure --bundle [--json] [--strict] [--out <file>] [--hydration] [--interact]
|
|
@@ -164,22 +224,40 @@ Usage:
|
|
|
164
224
|
print production readiness, with --strict making absent
|
|
165
225
|
applicable guarantees fail. --auto-fix writes safe local-
|
|
166
226
|
version dependency fixes.
|
|
167
|
-
nifra upgrade <version>
|
|
168
|
-
[--write] [--no-verify]
|
|
169
|
-
[--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.
|
|
170
231
|
[--allow-downgrade] Dry-run by default; --write applies then verifies (--no-verify
|
|
171
|
-
to skip). --list shows available targets.
|
|
172
|
-
|
|
173
|
-
|
|
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.
|
|
174
246
|
nifra port [--target <t>] [--json] Portability linter: print a feature × deploy-target capability
|
|
175
247
|
[--ci] [--strict] matrix (in-memory stores, in-process cron/WebSocket, Bun/Deno
|
|
176
248
|
globals, node: builtins) with file:line evidence. --target auto-
|
|
177
249
|
detected from build/deploy scripts, wrangler.toml, or vercel config;
|
|
178
250
|
--ci (or any --target) exits nonzero when a used feature is
|
|
179
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.
|
|
180
258
|
|
|
181
|
-
Reads nifra.config.ts (adapter + clientModule + plugins; or framework.ts), backend.ts
|
|
182
|
-
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.
|
|
183
261
|
|
|
184
262
|
Port: \`dev\` and \`start\` share the default ${DEFAULT_DEV_PORT}. Override with \`--port <n>\` (alias \`-p\`) or the
|
|
185
263
|
\`PORT\` env var (\`--port\` wins over \`PORT\`, which wins over the default).
|
|
@@ -192,12 +270,10 @@ set in the process environment is never overwritten by a file.
|
|
|
192
270
|
|
|
193
271
|
${renderCommandCatalogHelp()}`;
|
|
194
272
|
// Kept in lockstep with packages/cli/package.json by check:publish's version-consistency gate.
|
|
195
|
-
const CLI_VERSION = "
|
|
273
|
+
const CLI_VERSION = "4.0.0";
|
|
196
274
|
// A render adapter + nifra server are opaque to the CLI (it just forwards them); cast at the seam.
|
|
197
275
|
const asAdapter = (v) => v;
|
|
198
276
|
const asBunPlugins = (v) => v;
|
|
199
|
-
const asUse = (v) => v;
|
|
200
|
-
const apiOf = (backend) => backend === undefined ? {} : { api: inProcessClient(backend) };
|
|
201
277
|
/**
|
|
202
278
|
* Render an error for the CLI, unwrapping the detail a bare `.message` drops.
|
|
203
279
|
*
|
|
@@ -246,7 +322,7 @@ async function dev(app, flags) {
|
|
|
246
322
|
const decision = chooseBuildPipeline(app.resolvedPlugins, flags.vite ? "vite" : flags.bun ? "bun" : undefined, "dev");
|
|
247
323
|
if (decision.pipeline === "bun") {
|
|
248
324
|
// Bun's dev-server bundler takes plugins only via bunfig `[serve.static]`, read at process
|
|
249
|
-
// start - so the boundary plugins (
|
|
325
|
+
// start - so the boundary plugins (zone guard, server-fn stubs) are delivered by
|
|
250
326
|
// generating a config and re-execing this same command once with `--config=`. The child proves
|
|
251
327
|
// it IS the configured child with a per-launch random token (matched against the file the
|
|
252
328
|
// parent just wrote, consumed on first read). A fixed sentinel here would be a secret-leak
|
|
@@ -262,7 +338,7 @@ async function dev(app, flags) {
|
|
|
262
338
|
"relaunches. Something is rewriting `.nifra/dev-bun/` or the environment between " +
|
|
263
339
|
"launches; refusing to serve without the client-boundary plugins.");
|
|
264
340
|
}
|
|
265
|
-
const { bunfigPath, launchToken } = await writeBunDevConfig(app.cwd, app.configPath);
|
|
341
|
+
const { bunfigPath, launchToken } = await writeBunDevConfig(app.cwd, app.configPath, app.framework.define);
|
|
266
342
|
// Bun 1.4 on Windows may discard the script-relative portion of argv when `--config` is
|
|
267
343
|
// present. Keep a second, authenticated copy of the user vector in the child's environment so
|
|
268
344
|
// the configured process cannot silently fall back to `nifra`'s help and exit 0. The launch token
|
|
@@ -310,16 +386,31 @@ async function dev(app, flags) {
|
|
|
310
386
|
NODE_ENV: process.env.NODE_ENV ?? "development",
|
|
311
387
|
},
|
|
312
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);
|
|
313
391
|
const forwarded = Promise.all([
|
|
314
392
|
forwardChildOutput(child.stdout, Bun.stdout),
|
|
315
|
-
forwardChildOutput(child.stderr, Bun.stderr),
|
|
393
|
+
forwardChildOutput(child.stderr, Bun.stderr, stderrTail.push),
|
|
316
394
|
]);
|
|
317
|
-
|
|
395
|
+
let interrupted = false;
|
|
396
|
+
const forward = () => {
|
|
397
|
+
interrupted = true;
|
|
398
|
+
child.kill("SIGINT");
|
|
399
|
+
};
|
|
318
400
|
process.on("SIGINT", forward);
|
|
319
401
|
process.on("SIGTERM", forward);
|
|
320
402
|
try {
|
|
321
403
|
const [code] = await Promise.all([child.exited, forwarded]);
|
|
322
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
|
+
}
|
|
323
414
|
}
|
|
324
415
|
finally {
|
|
325
416
|
process.off("SIGINT", forward);
|
|
@@ -370,12 +461,13 @@ async function dev(app, flags) {
|
|
|
370
461
|
...(fw.publicDir !== undefined ? { publicDir: fw.publicDir } : {}),
|
|
371
462
|
...(fw.conditions ? { conditions: fw.conditions } : {}),
|
|
372
463
|
...(fw.define ? { define: fw.define } : {}),
|
|
373
|
-
|
|
464
|
+
indicator: showsIndicator(flags, fw),
|
|
465
|
+
createApp: (clientEntry, importQuery, dev) => createWebApp({
|
|
374
466
|
adapter: asAdapter(fw.adapter),
|
|
375
467
|
manifest: discoverRoutes(routesDir, { importQuery }),
|
|
376
468
|
clientEntry,
|
|
377
|
-
...(fw
|
|
378
|
-
|
|
469
|
+
...frameworkWebAppOptions(fw, backend),
|
|
470
|
+
onLoaderError: dev.onLoaderError,
|
|
379
471
|
}),
|
|
380
472
|
});
|
|
381
473
|
console.log(`nifra dev (bun) → http://localhost:${server.port}`);
|
|
@@ -424,17 +516,18 @@ async function dev(app, flags) {
|
|
|
424
516
|
poll: flags.poll,
|
|
425
517
|
port: flags.port,
|
|
426
518
|
...(flags.allowDuplicateIdentity ? { allowDuplicateIdentity: true } : {}),
|
|
519
|
+
indicator: showsIndicator(flags, fw),
|
|
427
520
|
...(fw.conditions ? { conditions: fw.conditions } : {}),
|
|
428
521
|
...(fw.define ? { define: fw.define } : {}),
|
|
429
522
|
// `load` resolves route modules through VITE, not through Bun. That is what makes the Vite
|
|
430
523
|
// pipeline own the whole phase: the client and the server now agree on every specifier, so
|
|
431
524
|
// `resolve.dedupe` finally governs SSR and the dual-React crash cannot occur.
|
|
432
|
-
createApp: (clientEntry, load) => createWebApp({
|
|
525
|
+
createApp: (clientEntry, load, dev) => createWebApp({
|
|
433
526
|
adapter: asAdapter(fw.adapter),
|
|
434
527
|
manifest: discoverRoutes(routesDir, { load }),
|
|
435
528
|
clientEntry,
|
|
436
|
-
...(fw
|
|
437
|
-
|
|
529
|
+
...frameworkWebAppOptions(fw, backend),
|
|
530
|
+
onLoaderError: dev.onLoaderError,
|
|
438
531
|
}),
|
|
439
532
|
});
|
|
440
533
|
console.log(`nifra dev (vite) → http://localhost:${server.port}`);
|
|
@@ -443,23 +536,26 @@ async function dev(app, flags) {
|
|
|
443
536
|
/**
|
|
444
537
|
* `nifra build --target <t>` - package the engine (buildClient + buildServer + prerender) into one
|
|
445
538
|
* command that emits a full deploy dir, so an app no longer hand-writes build-bun.ts + _worker.ts +
|
|
446
|
-
* _routes.json per target. The adapter is imported from `framework.ts` (the edge-bundlable
|
|
447
|
-
* `nifra.config.ts`, which pulls in Vite plugins), and the backend from
|
|
448
|
-
* `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/`.
|
|
449
543
|
*/
|
|
450
544
|
/**
|
|
451
545
|
* Refuse a `use` the generated server entry cannot import.
|
|
452
546
|
*
|
|
453
|
-
* `nifra build` emits `import { use } from <frameworkFile>` (framework.ts, the
|
|
454
|
-
* but `loadApp` prefers `nifra.config.ts` - so an app with BOTH files whose
|
|
455
|
-
* `nifra.config.ts` makes `fw.use` defined while `framework.ts` exports
|
|
456
|
-
* build dies later with an opaque bundler error pointing at generated
|
|
457
|
-
* 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).
|
|
458
553
|
*
|
|
459
|
-
* Detected by importing `frameworkFile` and checking the named export, not by comparing paths
|
|
460
|
-
* a split app legitimately DEFINES `use` in framework.ts and re-exports it from
|
|
461
|
-
* (exactly how `adapter` reaches both readers), and a path compare would refuse
|
|
462
|
-
* 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.
|
|
463
559
|
*/
|
|
464
560
|
export async function assertUseIsEdgeExported(use, configPath, frameworkFile) {
|
|
465
561
|
if (use === undefined || configPath === frameworkFile)
|
|
@@ -468,10 +564,55 @@ export async function assertUseIsEdgeExported(use, configPath, frameworkFile) {
|
|
|
468
564
|
if (typeof mod.use === "function")
|
|
469
565
|
return;
|
|
470
566
|
throw new Error(`[nifra] \`use\` is exported from ${configPath} but not from ${frameworkFile}. ` +
|
|
471
|
-
"`nifra build` generates a server entry that imports `use` from framework.ts (the
|
|
472
|
-
"file), so this build would fail later with an opaque bundler error inside generated code.\n\n" +
|
|
473
|
-
" - Define `use` in framework.ts and re-export it from nifra.config.ts " +
|
|
474
|
-
'(`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)}`);
|
|
475
616
|
}
|
|
476
617
|
/**
|
|
477
618
|
* A production build must not ship a workspace-linked package whose `default`-condition artifact is
|
|
@@ -503,35 +644,55 @@ async function assertFreshWorkspaceDists(cwd) {
|
|
|
503
644
|
: ` ${f.package}: ${f.distFile} is ${f.behindSeconds}s behind ${f.sourceFile}`);
|
|
504
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.`);
|
|
505
646
|
}
|
|
506
|
-
async function buildForTarget(app,
|
|
507
|
-
const {
|
|
508
|
-
|
|
509
|
-
throw new Error(`[nifra] unknown --target "${target}". Valid: ${BUILD_TARGETS.join(", ")}.`);
|
|
510
|
-
}
|
|
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");
|
|
511
650
|
if (flags.vite && flags.bun) {
|
|
512
651
|
throw new Error("[nifra] `nifra build` takes `--vite` or `--bun`, not both.");
|
|
513
652
|
}
|
|
653
|
+
const { framework: fw } = app;
|
|
654
|
+
const cssCodeSplitRequested = fw.cssCodeSplit === false;
|
|
655
|
+
const hasBunTransforms = app.resolvedPlugins.clientPlugins.length > 0 || app.resolvedPlugins.serverPlugins.length > 0;
|
|
656
|
+
if (cssCodeSplitRequested && flags.bun) {
|
|
657
|
+
throw new Error("[nifra] `cssCodeSplit: false` is a Vite-only production option, but `nifra build --bun` was requested. " +
|
|
658
|
+
"Drop `--bun` (Nifra will select Vite automatically) or remove the aggregate CSS setting.");
|
|
659
|
+
}
|
|
660
|
+
if (cssCodeSplitRequested && hasBunTransforms) {
|
|
661
|
+
throw new Error("[nifra] `cssCodeSplit: false` requires the Vite production pipeline, but this app also declares " +
|
|
662
|
+
"Bun client/server plugins. Use `--vite` with Vite equivalents, or remove the aggregate CSS setting; " +
|
|
663
|
+
"switching pipelines would silently drop the Bun transforms.");
|
|
664
|
+
}
|
|
514
665
|
// Which bundler builds this app. Default is Bun; an app whose ONLY transforms are `vitePlugins` gets
|
|
515
666
|
// Vite instead, because the Bun build cannot run them and would silently omit the work. `--vite`/`--bun`
|
|
516
667
|
// force it, and the one forced combination that would drop transforms throws.
|
|
517
|
-
|
|
668
|
+
let decision = chooseBuildPipeline(app.resolvedPlugins, flags.vite ? "vite" : flags.bun ? "bun" : undefined);
|
|
669
|
+
if (cssCodeSplitRequested && decision.pipeline === "bun") {
|
|
670
|
+
// With no Bun transforms, aggregate CSS is a Vite-only request that can be honored safely by
|
|
671
|
+
// selecting the same pipeline the user would otherwise have to force explicitly.
|
|
672
|
+
decision = {
|
|
673
|
+
pipeline: "vite",
|
|
674
|
+
why: "auto",
|
|
675
|
+
reason: "auto: this app requested Vite's aggregate CSS output (`cssCodeSplit: false`)",
|
|
676
|
+
};
|
|
677
|
+
}
|
|
518
678
|
const useVite = decision.pipeline === "vite";
|
|
519
679
|
// Same deploy-dir output - buildTargetVite delegates to the same orchestrator as buildTarget - so only
|
|
520
680
|
// the bundler and the plugin FORMAT (Vite plugins, not Bun) differ.
|
|
521
681
|
const buildTarget = useVite
|
|
522
682
|
? (await import("@nifrajs/web/build-vite")).buildTargetVite
|
|
523
683
|
: (await import("@nifrajs/web/build")).buildTarget;
|
|
524
|
-
const {
|
|
525
|
-
//
|
|
526
|
-
//
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
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);
|
|
534
693
|
await assertUseIsEdgeExported(fw.use, app.configPath, frameworkFile);
|
|
694
|
+
await assertFrameworkOptionsEdgeExported(fw, app.configPath, frameworkFile);
|
|
695
|
+
assertNoShadowedPages(app);
|
|
535
696
|
// Plugin FORMAT differs by pipeline: the Bun build takes Bun plugins (clientPlugins/serverPlugins); the
|
|
536
697
|
// Vite build takes the app's Vite plugins (fw.vitePlugins) for BOTH halves. buildTargetWith forwards
|
|
537
698
|
// whatever it's given straight to the chosen bundler, which casts to its own plugin type.
|
|
@@ -551,12 +712,17 @@ async function buildForTarget(app, target, flags) {
|
|
|
551
712
|
clientModule: fw.clientModule,
|
|
552
713
|
adapterImport: frameworkFile,
|
|
553
714
|
...(fw.use ? { useImport: frameworkFile } : {}),
|
|
715
|
+
...frameworkOptionImports(fw, frameworkFile),
|
|
554
716
|
...(backend !== undefined && existsSync(backendFile) ? { backendImport: backendFile } : {}),
|
|
555
717
|
...plugins,
|
|
556
718
|
...(fw.conditions ? { conditions: fw.conditions } : {}),
|
|
557
719
|
...(fw.define ? { define: fw.define } : {}),
|
|
558
720
|
...(fw.publicDir !== undefined ? { publicDir: fw.publicDir } : {}),
|
|
559
721
|
...(fw.publicEnvPrefix !== undefined ? { publicEnvPrefix: fw.publicEnvPrefix } : {}),
|
|
722
|
+
...(fw.secretExemptions !== undefined ? { secretExemptions: fw.secretExemptions } : {}),
|
|
723
|
+
...(fw.cssCodeSplit !== undefined ? { cssCodeSplit: fw.cssCodeSplit } : {}),
|
|
724
|
+
...(fw.cssLoading !== undefined ? { cssLoading: fw.cssLoading } : {}),
|
|
725
|
+
...(fw.clientIp !== undefined ? { clientIp: fw.clientIp } : {}),
|
|
560
726
|
// The static target needs a built app to drive prerendering - only build it when targeting static.
|
|
561
727
|
...(target === "static" ? { prerenderApp: await buildPrerenderApp(app) } : {}),
|
|
562
728
|
});
|
|
@@ -587,18 +753,18 @@ async function buildPrerenderApp(app) {
|
|
|
587
753
|
adapter: asAdapter(fw.adapter),
|
|
588
754
|
manifest: discoverRoutes(routesDir),
|
|
589
755
|
clientEntry: client.entry,
|
|
590
|
-
...(fw
|
|
756
|
+
...frameworkWebAppOptions(fw, backend),
|
|
591
757
|
...(client.routes ? { routePreload: client.routes } : {}),
|
|
592
758
|
...(client.css ? { styles: client.css } : {}),
|
|
593
759
|
...(client.routeStyles ? { routeStyles: client.routeStyles } : {}),
|
|
594
|
-
...
|
|
760
|
+
...(client.cssLoading !== undefined ? { cssLoading: client.cssLoading } : {}),
|
|
595
761
|
});
|
|
596
762
|
}
|
|
597
763
|
async function start(app, flags) {
|
|
598
764
|
const serverFile = resolve(app.outDir, "server.js");
|
|
599
765
|
if (!existsSync(serverFile)) {
|
|
600
766
|
// A Cloudflare Pages build emits `_worker.js` (a Workers bundle), not a self-hosting `server.js` - so
|
|
601
|
-
// `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
|
|
602
768
|
// actual mismatch and the fix.
|
|
603
769
|
if (existsSync(resolve(app.outDir, "_worker.js"))) {
|
|
604
770
|
throw new Error(`[nifra] ${app.outDir} holds a Cloudflare Pages worker bundle (_worker.js), not a self-hosting ` +
|
|
@@ -614,13 +780,14 @@ export function parseFlags(args) {
|
|
|
614
780
|
// the SAME uncommon port for `nifra dev` and `nifra start` (DEFAULT_DEV_PORT) so a project's URL is
|
|
615
781
|
// stable across commands and doesn't collide with the usual 3000/5173/8080 crowd.
|
|
616
782
|
let port = Number(Bun.env.PORT ?? DEFAULT_DEV_PORT);
|
|
617
|
-
let out
|
|
783
|
+
let out;
|
|
618
784
|
let poll = Bun.env.CHOKIDAR_USEPOLLING === "1";
|
|
619
|
-
let target
|
|
785
|
+
let target;
|
|
620
786
|
let report = false;
|
|
621
787
|
let vite = false;
|
|
622
788
|
let bun = false;
|
|
623
789
|
let allowDuplicateIdentity = false;
|
|
790
|
+
let noIndicator = false;
|
|
624
791
|
for (let i = 0; i < args.length; i++) {
|
|
625
792
|
const a = args[i];
|
|
626
793
|
if ((a === "--port" || a === "-p") && args[i + 1])
|
|
@@ -639,11 +806,13 @@ export function parseFlags(args) {
|
|
|
639
806
|
bun = true;
|
|
640
807
|
else if (a === "--allow-duplicate-identity")
|
|
641
808
|
allowDuplicateIdentity = true;
|
|
809
|
+
else if (a === "--no-indicator")
|
|
810
|
+
noIndicator = true;
|
|
642
811
|
}
|
|
643
812
|
if (!Number.isFinite(port) || port < 0 || port > 65535) {
|
|
644
813
|
throw new Error(`[nifra] invalid --port: ${port}`);
|
|
645
814
|
}
|
|
646
|
-
return { port, out, poll, target, report, vite, bun, allowDuplicateIdentity };
|
|
815
|
+
return { port, out, poll, target, report, vite, bun, allowDuplicateIdentity, noIndicator };
|
|
647
816
|
}
|
|
648
817
|
/**
|
|
649
818
|
* `assure` uses the structured-bundle lane only when explicitly asked. A bare `--json` is NOT a
|
|
@@ -678,8 +847,15 @@ function installReflectionExitHint() {
|
|
|
678
847
|
inFlight = command;
|
|
679
848
|
};
|
|
680
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";
|
|
681
852
|
async function main() {
|
|
682
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
|
+
: [];
|
|
683
859
|
// Applied before any command runs: a reflecting command imports the app on its first await, and the
|
|
684
860
|
// app reads its environment at module scope, so the variables must already be in place by then.
|
|
685
861
|
if (envFiles.length > 0)
|
|
@@ -701,6 +877,16 @@ async function main() {
|
|
|
701
877
|
// `mcp` runs a long-lived stdio server and loads the project lazily per-tool - it must not go through
|
|
702
878
|
// the eager `loadApp` below (which would fail fast on a project that's API-only / not yet built).
|
|
703
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
|
+
}
|
|
704
890
|
const { runMcpServer } = await import("./mcp.js");
|
|
705
891
|
// `nifra mcp [dir]` - an explicit project directory pins the root (for clients configured outside
|
|
706
892
|
// the project); otherwise the server resolves it from cwd + the client's MCP roots.
|
|
@@ -746,11 +932,16 @@ async function main() {
|
|
|
746
932
|
}));
|
|
747
933
|
return;
|
|
748
934
|
}
|
|
749
|
-
|
|
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}`);
|
|
750
941
|
if (catalogSpec?.transports.includes("cli")) {
|
|
751
942
|
const markReflecting = installReflectionExitHint();
|
|
752
943
|
try {
|
|
753
|
-
const input = bindCommandArgv(catalogSpec, argv.slice(1));
|
|
944
|
+
const input = bindCommandArgv(catalogSpec, argv.slice(dbSub === undefined ? 1 : 2));
|
|
754
945
|
markReflecting(command);
|
|
755
946
|
const output = await catalogSpec.run(input, { cwd: process.cwd(), cliVersion: CLI_VERSION });
|
|
756
947
|
markReflecting(undefined);
|
|
@@ -763,15 +954,28 @@ async function main() {
|
|
|
763
954
|
else {
|
|
764
955
|
console.log(catalogSpec.render(output, input).join("\n"));
|
|
765
956
|
}
|
|
766
|
-
if (catalogSpec.
|
|
957
|
+
if (catalogSpec.exitCode !== undefined) {
|
|
958
|
+
process.exitCode = catalogSpec.exitCode(output, input);
|
|
959
|
+
}
|
|
960
|
+
else if (catalogSpec.success !== undefined && !catalogSpec.success(output, input)) {
|
|
767
961
|
process.exitCode = 1;
|
|
962
|
+
}
|
|
768
963
|
}
|
|
769
964
|
catch (err) {
|
|
770
965
|
// A thrown error is nifra's own reporting path, not a silent abort: clear the sentinel so the
|
|
771
966
|
// hint stays reserved for the case where the process died inside the app import.
|
|
772
967
|
markReflecting(undefined);
|
|
773
968
|
console.error(formatCliError(err));
|
|
774
|
-
|
|
969
|
+
const candidate = typeof err === "object" && err !== null && "exitCode" in err
|
|
970
|
+
? err.exitCode
|
|
971
|
+
: undefined;
|
|
972
|
+
process.exitCode =
|
|
973
|
+
typeof candidate === "number" &&
|
|
974
|
+
Number.isInteger(candidate) &&
|
|
975
|
+
candidate >= 0 &&
|
|
976
|
+
candidate <= 255
|
|
977
|
+
? candidate
|
|
978
|
+
: 1;
|
|
775
979
|
}
|
|
776
980
|
return;
|
|
777
981
|
}
|
|
@@ -785,7 +989,7 @@ async function main() {
|
|
|
785
989
|
process.exitCode = 1;
|
|
786
990
|
return;
|
|
787
991
|
}
|
|
788
|
-
// `init-agents` retrofits the agent
|
|
992
|
+
// `init-agents` retrofits the agent files (.mcp.json, CLAUDE.md, AGENTS.md, …) into the cwd. It's a
|
|
789
993
|
// pure file-writing command independent of the app loading, so dispatch it before the eager `loadApp`
|
|
790
994
|
// (an existing app might be API-only or not yet built). It always succeeds unless a write throws.
|
|
791
995
|
if (command === "init-agents") {
|
|
@@ -793,6 +997,7 @@ async function main() {
|
|
|
793
997
|
await runInitAgents(process.cwd(), {
|
|
794
998
|
json: argv.includes("--json"),
|
|
795
999
|
force: argv.includes("--force"),
|
|
1000
|
+
syncMcp: argv.includes("--sync-mcp"),
|
|
796
1001
|
});
|
|
797
1002
|
return;
|
|
798
1003
|
}
|
|
@@ -813,7 +1018,11 @@ async function main() {
|
|
|
813
1018
|
}
|
|
814
1019
|
try {
|
|
815
1020
|
const { runSdk } = await import("./sdk.js");
|
|
816
|
-
await runSdk(process.cwd(), {
|
|
1021
|
+
await runSdk(process.cwd(), {
|
|
1022
|
+
language,
|
|
1023
|
+
...(out !== undefined ? { out } : {}),
|
|
1024
|
+
...(argv.includes("--strict") ? { strict: true } : {}),
|
|
1025
|
+
});
|
|
817
1026
|
}
|
|
818
1027
|
catch (err) {
|
|
819
1028
|
console.error(formatCliError(err));
|
|
@@ -835,6 +1044,8 @@ async function main() {
|
|
|
835
1044
|
list: argv.includes("--list"),
|
|
836
1045
|
verify: !argv.includes("--no-verify"),
|
|
837
1046
|
allowDowngrade: argv.includes("--allow-downgrade"),
|
|
1047
|
+
exact: argv.includes("--exact"),
|
|
1048
|
+
cliVersion: CLI_VERSION,
|
|
838
1049
|
});
|
|
839
1050
|
if (!ok)
|
|
840
1051
|
process.exitCode = 1;
|
|
@@ -855,6 +1066,13 @@ async function main() {
|
|
|
855
1066
|
}
|
|
856
1067
|
const flags = parseFlags(argv.slice(1));
|
|
857
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
|
+
}
|
|
858
1076
|
if (command === "dev")
|
|
859
1077
|
await dev(app, flags);
|
|
860
1078
|
else if (command === "build")
|
|
@@ -946,6 +1164,7 @@ function isCliCommandToken(value) {
|
|
|
946
1164
|
value === "mcp" ||
|
|
947
1165
|
value === "init-agents" ||
|
|
948
1166
|
value === "upgrade" ||
|
|
1167
|
+
value === "db" ||
|
|
949
1168
|
value === "help" ||
|
|
950
1169
|
value === "--help" ||
|
|
951
1170
|
value === "-h" ||
|