@nifrajs/cli 2.10.0 → 2.12.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 +7 -1
- package/dist/assure-hydration.d.ts +14 -0
- package/dist/assure-hydration.d.ts.map +1 -0
- package/dist/assure-hydration.js +483 -0
- package/dist/assure-hydration.js.map +1 -0
- package/dist/assure.d.ts +35 -0
- package/dist/assure.d.ts.map +1 -1
- package/dist/assure.js +222 -1
- package/dist/assure.js.map +1 -1
- package/dist/capabilities-tool.d.ts +13 -0
- package/dist/capabilities-tool.d.ts.map +1 -1
- package/dist/capabilities-tool.js +64 -1
- package/dist/capabilities-tool.js.map +1 -1
- package/dist/check.d.ts +133 -10
- package/dist/check.d.ts.map +1 -1
- package/dist/check.js +741 -148
- package/dist/check.js.map +1 -1
- package/dist/cli.d.ts +21 -14
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +288 -73
- package/dist/cli.js.map +1 -1
- package/dist/contract-proof.d.ts +49 -0
- package/dist/contract-proof.d.ts.map +1 -0
- package/dist/contract-proof.js +128 -0
- package/dist/contract-proof.js.map +1 -0
- package/dist/contracts.d.ts +22 -0
- package/dist/contracts.d.ts.map +1 -0
- package/dist/contracts.js +134 -0
- package/dist/contracts.js.map +1 -0
- package/dist/dev-bun-config.d.ts +17 -6
- package/dist/dev-bun-config.d.ts.map +1 -1
- package/dist/dev-bun-config.js +51 -14
- package/dist/dev-bun-config.js.map +1 -1
- package/dist/diagnostics.d.ts +23 -0
- package/dist/diagnostics.d.ts.map +1 -0
- package/dist/diagnostics.js +9 -0
- package/dist/diagnostics.js.map +1 -0
- package/dist/diff-tool.d.ts +2 -0
- package/dist/diff-tool.d.ts.map +1 -1
- package/dist/diff-tool.js +1 -1
- package/dist/diff-tool.js.map +1 -1
- package/dist/docs-search.d.ts +8 -3
- package/dist/docs-search.d.ts.map +1 -1
- package/dist/docs-search.js +36 -15
- package/dist/docs-search.js.map +1 -1
- package/dist/doctor.d.ts +38 -2
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +189 -7
- package/dist/doctor.js.map +1 -1
- package/dist/examples.d.ts +1 -2
- package/dist/examples.d.ts.map +1 -1
- package/dist/examples.js +37 -16
- package/dist/examples.js.map +1 -1
- package/dist/fix-recipes.d.ts +12 -0
- package/dist/fix-recipes.d.ts.map +1 -0
- package/dist/fix-recipes.js +106 -0
- package/dist/fix-recipes.js.map +1 -0
- package/dist/init-agents.d.ts.map +1 -1
- package/dist/init-agents.js +60 -4
- package/dist/init-agents.js.map +1 -1
- package/dist/internal/source-facts.d.ts +15 -0
- package/dist/internal/source-facts.d.ts.map +1 -0
- package/dist/internal/source-facts.js +82 -0
- package/dist/internal/source-facts.js.map +1 -0
- package/dist/internal/typescript-import.d.ts +9 -0
- package/dist/internal/typescript-import.d.ts.map +1 -1
- package/dist/internal/typescript-import.js +38 -0
- package/dist/internal/typescript-import.js.map +1 -1
- package/dist/introspect.d.ts +6 -2
- package/dist/introspect.d.ts.map +1 -1
- package/dist/introspect.js +60 -5
- package/dist/introspect.js.map +1 -1
- package/dist/load.d.ts +10 -0
- package/dist/load.d.ts.map +1 -1
- package/dist/load.js +1 -0
- package/dist/load.js.map +1 -1
- package/dist/manifest-tool.d.ts.map +1 -1
- package/dist/manifest-tool.js +14 -9
- package/dist/manifest-tool.js.map +1 -1
- package/dist/mcp-docs-tools.d.ts.map +1 -1
- package/dist/mcp-docs-tools.js +3 -0
- package/dist/mcp-docs-tools.js.map +1 -1
- package/dist/mcp-http.js +1 -1
- package/dist/mcp-root.d.ts +83 -0
- package/dist/mcp-root.d.ts.map +1 -0
- package/dist/mcp-root.js +221 -0
- package/dist/mcp-root.js.map +1 -0
- package/dist/mcp-run.d.ts.map +1 -1
- package/dist/mcp-run.js +18 -4
- package/dist/mcp-run.js.map +1 -1
- package/dist/mcp.d.ts +27 -4
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +360 -27
- package/dist/mcp.js.map +1 -1
- package/dist/pipeline-guard.d.ts +25 -10
- package/dist/pipeline-guard.d.ts.map +1 -1
- package/dist/pipeline-guard.js +35 -15
- package/dist/pipeline-guard.js.map +1 -1
- package/dist/pipeline-report.d.ts +34 -0
- package/dist/pipeline-report.d.ts.map +1 -0
- package/dist/pipeline-report.js +282 -0
- package/dist/pipeline-report.js.map +1 -0
- package/dist/project-path.d.ts +3 -0
- package/dist/project-path.d.ts.map +1 -0
- package/dist/project-path.js +36 -0
- package/dist/project-path.js.map +1 -0
- package/dist/replay.d.ts +12 -0
- package/dist/replay.d.ts.map +1 -0
- package/dist/replay.js +36 -0
- package/dist/replay.js.map +1 -0
- package/dist/rules/codes.d.ts +42 -0
- package/dist/rules/codes.d.ts.map +1 -0
- package/dist/rules/codes.js +52 -0
- package/dist/rules/codes.js.map +1 -0
- package/dist/rules/comment-markers.d.ts +10 -0
- package/dist/rules/comment-markers.d.ts.map +1 -0
- package/dist/rules/comment-markers.js +24 -0
- package/dist/rules/comment-markers.js.map +1 -0
- package/dist/rules/index.d.ts +31 -0
- package/dist/rules/index.d.ts.map +1 -0
- package/dist/rules/index.js +133 -0
- package/dist/rules/index.js.map +1 -0
- package/dist/rules/legacy.d.ts +7 -0
- package/dist/rules/legacy.d.ts.map +1 -0
- package/dist/rules/legacy.js +85 -0
- package/dist/rules/legacy.js.map +1 -0
- package/dist/rules/routes.d.ts +12 -0
- package/dist/rules/routes.d.ts.map +1 -0
- package/dist/rules/routes.js +142 -0
- package/dist/rules/routes.js.map +1 -0
- package/dist/rules/security.d.ts +10 -0
- package/dist/rules/security.d.ts.map +1 -0
- package/dist/rules/security.js +471 -0
- package/dist/rules/security.js.map +1 -0
- package/dist/search-terms.d.ts +2 -0
- package/dist/search-terms.d.ts.map +1 -1
- package/dist/search-terms.js +5 -1
- package/dist/search-terms.js.map +1 -1
- package/dist/types-search.d.ts +1 -2
- package/dist/types-search.d.ts.map +1 -1
- package/dist/types-search.js +33 -16
- package/dist/types-search.js.map +1 -1
- package/dist/verification.d.ts +9 -0
- package/dist/verification.d.ts.map +1 -1
- package/dist/verification.js +17 -1
- package/dist/verification.js.map +1 -1
- package/dist/work-graph.d.ts +123 -0
- package/dist/work-graph.d.ts.map +1 -0
- package/dist/work-graph.js +465 -0
- package/dist/work-graph.js.map +1 -0
- package/docs/examples.json +32 -2
- package/docs/llms-full.txt +1105 -27
- package/docs/types.json +1772 -137
- package/package.json +34 -9
- package/src/assure-hydration.ts +606 -0
- package/src/assure.ts +316 -2
- package/src/capabilities-tool.ts +80 -1
- package/src/check.ts +923 -169
- package/src/cli.ts +297 -76
- package/src/contract-proof.ts +194 -0
- package/src/contracts.ts +157 -0
- package/src/dev-bun-config.ts +57 -14
- package/src/diagnostics.ts +32 -0
- package/src/diff-tool.ts +1 -1
- package/src/docs-search.ts +45 -15
- package/src/doctor.ts +286 -8
- package/src/examples.ts +43 -15
- package/src/fix-recipes.ts +126 -0
- package/src/init-agents.ts +61 -4
- package/src/internal/source-facts.ts +133 -0
- package/src/internal/typescript-import.ts +29 -0
- package/src/introspect.ts +81 -6
- package/src/load.ts +11 -0
- package/src/manifest-tool.ts +16 -10
- package/src/mcp-docs-tools.ts +3 -0
- package/src/mcp-http.ts +1 -1
- package/src/mcp-root.ts +271 -0
- package/src/mcp-run.ts +19 -4
- package/src/mcp.ts +440 -34
- package/src/pipeline-guard.ts +44 -14
- package/src/pipeline-report.ts +341 -0
- package/src/project-path.ts +36 -0
- package/src/replay.ts +44 -0
- package/src/rules/codes.ts +57 -0
- package/src/rules/comment-markers.ts +26 -0
- package/src/rules/index.ts +175 -0
- package/src/rules/legacy.ts +93 -0
- package/src/rules/routes.ts +157 -0
- package/src/rules/security.ts +505 -0
- package/src/search-terms.ts +9 -4
- package/src/types-search.ts +39 -15
- package/src/verification.ts +18 -1
- package/src/work-graph.ts +613 -0
package/dist/cli.js
CHANGED
|
@@ -11,7 +11,7 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
|
|
|
11
11
|
* `nifra` - the zero-config CLI for a nifra app. Reads `framework.ts` + `backend.ts` + `routes/` from
|
|
12
12
|
* the project root (see {@link loadApp}) and wires the right `@nifrajs/web` entrypoint:
|
|
13
13
|
*
|
|
14
|
-
* nifra dev true-HMR dev server (
|
|
14
|
+
* nifra dev true-HMR dev server (Bun native HMR + nifra SSR) - @nifrajs/web/dev
|
|
15
15
|
* nifra build emit a complete target-specific deploy directory - @nifrajs/web/build
|
|
16
16
|
* nifra start run the default Bun build - dist/server.js
|
|
17
17
|
*
|
|
@@ -20,19 +20,22 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
|
|
|
20
20
|
import { existsSync } from "node:fs";
|
|
21
21
|
import { resolve } from "node:path";
|
|
22
22
|
import { inProcessClient } from "@nifrajs/client";
|
|
23
|
-
import { createWebApp, DEFAULT_DEV_PORT } from "@nifrajs/web";
|
|
23
|
+
import { createWebApp, DEFAULT_DEV_PORT, } from "@nifrajs/web";
|
|
24
24
|
import { discoverRoutes } from "@nifrajs/web/fs";
|
|
25
25
|
import { describeProject, describeRouteGraph, describeRoutes } from "./introspect.js";
|
|
26
26
|
import { loadApp } from "./load.js";
|
|
27
|
-
import { chooseBuildPipeline } from "./pipeline-guard.js";
|
|
27
|
+
import { chooseBuildPipeline, describePipeline } from "./pipeline-guard.js";
|
|
28
28
|
const HELP = `nifra - zero-config dev/build/start for a nifra app
|
|
29
29
|
|
|
30
30
|
Usage:
|
|
31
|
-
nifra dev [--port <n>] [--poll] Start the true-HMR dev server (
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
31
|
+
nifra dev [--port <n>] [--poll] Start the true-HMR dev server (Bun). Default port ${DEFAULT_DEV_PORT}.
|
|
32
|
+
An app whose ONLY transforms are \`vitePlugins\` uses Vite (the
|
|
33
|
+
Bun pipeline cannot run them); everything else uses Bun.
|
|
34
|
+
[--vite] Force the Vite middleware/HMR pipeline.
|
|
35
|
+
[--bun] Force Bun.serve native HMR (React Fast Refresh included, state
|
|
36
|
+
preserved), no Vite in the process. CSS Modules, plain CSS,
|
|
37
|
+
Tailwind and the app's own \`clientPlugins\`/\`serverPlugins\` run
|
|
38
|
+
through the same Bun transforms production uses.
|
|
36
39
|
nifra build [--out <dir>] [--report] Emit a complete deploy directory.
|
|
37
40
|
[--target <t>] Target a FULL deploy dir for <t>:
|
|
38
41
|
bun | node | deno | cf-pages | vercel | static. Packages
|
|
@@ -67,8 +70,11 @@ Usage:
|
|
|
67
70
|
and a "## MCP server" section in AGENTS.md. No-clobber by
|
|
68
71
|
default (skips a file you've customized); --force overwrites the
|
|
69
72
|
owned files. AGENTS.md is only appended to, never overwritten.
|
|
70
|
-
nifra mcp
|
|
71
|
-
agent
|
|
73
|
+
nifra mcp [dir] Start an MCP server (stdio) exposing this project to a coding
|
|
74
|
+
agent. The project root is [dir] when given, else resolved from
|
|
75
|
+
cwd (marker walk-up + the client's MCP roots); tools refuse
|
|
76
|
+
with a fix when no nifra project is found - never a silent
|
|
77
|
+
wrong-project answer. Tools: nifra_context, nifra_routes,
|
|
72
78
|
nifra_run (backend), nifra_render (SSR a page), nifra_docs,
|
|
73
79
|
nifra_example (verified snippets), nifra_scaffold (route→file),
|
|
74
80
|
nifra_check (drift gate + fixes), nifra_levels (verification
|
|
@@ -81,6 +87,8 @@ Usage:
|
|
|
81
87
|
nifra check [--json] [--lints-only] Gate: typecheck + lints (hand-rolled fetch(), untyped client("…"),
|
|
82
88
|
server-only imports in routes/). Run as "done"; --json for agents;
|
|
83
89
|
--lints-only skips tsc for a near-instant inner-loop pass.
|
|
90
|
+
nifra fix [--code <NF-code>] Apply a registered diagnostic recipe, then print the remaining
|
|
91
|
+
structured diagnostics.
|
|
84
92
|
nifra sync-manifest Regenerate a committed web server-manifest.ts from routes/ WITHOUT
|
|
85
93
|
a full build - clears server-manifest drift after a route add/rename
|
|
86
94
|
(a new hydrating component still needs a full build).
|
|
@@ -96,9 +104,14 @@ Usage:
|
|
|
96
104
|
closed. Exits non-zero on any breaking change - run it in CI.
|
|
97
105
|
nifra sdk --lang <python|go> [--out <file>]
|
|
98
106
|
Generate a deterministic non-TypeScript SDK from backend.ts.
|
|
99
|
-
nifra assure [--config <file>] [--json]
|
|
100
|
-
|
|
101
|
-
|
|
107
|
+
nifra assure [--config <file>] [--json] [--strict] [--out <file>] [--hydration] [--interact]
|
|
108
|
+
Emit one structured assurance bundle. The bundle records every
|
|
109
|
+
applicable gate as pass, fail, or skip and exits non-zero unless
|
|
110
|
+
its verdict is green. Without these bundle flags, the legacy
|
|
111
|
+
route-assurance report remains available.
|
|
112
|
+
nifra contracts snapshot [--out <file>] Write the deterministic route contract lock.
|
|
113
|
+
nifra contracts check [--json] Check the current routes against contracts.lock.json.
|
|
114
|
+
nifra replay <file> Replay a token-only verification metadata file.
|
|
102
115
|
nifra capabilities snapshot [--out <file>] [--config <file>]
|
|
103
116
|
Write the deterministic, token-only capability lockfile, but
|
|
104
117
|
only after provenance + idempotency assurance passes.
|
|
@@ -115,9 +128,15 @@ Usage:
|
|
|
115
128
|
Compute the verification ladder (L0 typed contract → L1 route
|
|
116
129
|
assurance → L2 capability lockfile → L3 route manifest → L4
|
|
117
130
|
invariant-tested); --min <n> fails the exit code below level n.
|
|
131
|
+
nifra prove [--json] [--file <path>] [--min <n>]
|
|
132
|
+
Build the static verification work graph, plan the cheapest
|
|
133
|
+
proofs for changed files, and report a machine-checkable stop
|
|
134
|
+
condition. Requires a fresh build; never probes a running app.
|
|
118
135
|
nifra doctor [--json] [--auto-fix] Flag undeclared imports and multiple physical copies of
|
|
119
|
-
|
|
120
|
-
|
|
136
|
+
[--strict] [--target] @nifrajs/core/React identity-sensitive dependencies;
|
|
137
|
+
print production readiness, with --strict making absent
|
|
138
|
+
applicable guarantees fail. --auto-fix writes safe local-
|
|
139
|
+
version dependency fixes.
|
|
121
140
|
nifra upgrade <version> Run the per-release upgrade recipe for <version>: sweep every
|
|
122
141
|
[--write] [--no-verify] matching dependency pin (preserving ^/~/exact), move removed
|
|
123
142
|
[--list] [--json] packages, apply exact imports, then verify with nifra check.
|
|
@@ -138,10 +157,11 @@ routes/ from the current directory. Run from your project root.
|
|
|
138
157
|
Port: \`dev\` and \`start\` share the default ${DEFAULT_DEV_PORT}. Override with \`--port <n>\` (alias \`-p\`) or the
|
|
139
158
|
\`PORT\` env var (\`--port\` wins over \`PORT\`, which wins over the default).`;
|
|
140
159
|
// Kept in lockstep with packages/cli/package.json by check:publish's version-consistency gate.
|
|
141
|
-
const CLI_VERSION = "2.
|
|
160
|
+
const CLI_VERSION = "2.12.0";
|
|
142
161
|
// A render adapter + nifra server are opaque to the CLI (it just forwards them); cast at the seam.
|
|
143
162
|
const asAdapter = (v) => v;
|
|
144
163
|
const asBunPlugins = (v) => v;
|
|
164
|
+
const asUse = (v) => v;
|
|
145
165
|
const apiOf = (backend) => backend === undefined ? {} : { api: inProcessClient(backend) };
|
|
146
166
|
/**
|
|
147
167
|
* Render an error for the CLI, unwrapping the detail a bare `.message` drops.
|
|
@@ -174,52 +194,22 @@ export function formatCliError(err) {
|
|
|
174
194
|
return err.message;
|
|
175
195
|
return String(err);
|
|
176
196
|
}
|
|
177
|
-
/**
|
|
178
|
-
* Refuse `nifra dev --bun` for an app Bun's DEV-server bundler cannot compile.
|
|
179
|
-
*
|
|
180
|
-
* Bun's dev server and `Bun.build` are not the same bundler. `Bun.build` transforms `*.module.css` into a
|
|
181
|
-
* scoped class map (the Bun production build of a CSS-Modules app works, verified), but the dev server's
|
|
182
|
-
* bundler does not: the import compiles to a dangling reference and the browser throws
|
|
183
|
-
* `ReferenceError: import_X_module is not defined` from inside the component - a message that names
|
|
184
|
-
* neither CSS Modules nor the dev server, so the cause is invisible.
|
|
185
|
-
*
|
|
186
|
-
* A silently-broken client is the one outcome worth failing the command over, so this checks up front and
|
|
187
|
-
* names the trade plus both ways out. Narrow by design: only the transform proven missing is refused, so
|
|
188
|
-
* an app without CSS Modules still gets the Bun dev loop.
|
|
189
|
-
*/
|
|
190
|
-
export async function assertBunDevSupportsApp(app) {
|
|
191
|
-
// Server functions and `*.server` modules are handled: the generated bunfig (dev-bun-config.ts)
|
|
192
|
-
// delivers the SAME serverFnStubPlugin/serverOnlyEmptyPlugin the production build uses to Bun's
|
|
193
|
-
// dev-server bundler via `[serve.static] plugins` - the one channel that bundler accepts plugins
|
|
194
|
-
// through (a runtime `Bun.plugin` never reaches it; programmatic ask: oven-sh/bun#36830). The old
|
|
195
|
-
// fail-closed refusal for those modules is gone because the leak it guarded is gone.
|
|
196
|
-
//
|
|
197
|
-
// CSS Modules remain gated: the `*.module.css` plugin rests on an `onResolve` virtual-module
|
|
198
|
-
// stylesheet emitter that is not yet verified under `[serve.static]`, and its failure mode is a
|
|
199
|
-
// broken client rather than a leak - refuse loudly until it is proven there.
|
|
200
|
-
const { Glob } = await import("bun");
|
|
201
|
-
const offenders = [];
|
|
202
|
-
for await (const file of new Glob("**/*.module.css").scan({ cwd: app.cwd, dot: false })) {
|
|
203
|
-
if (/(^|\/)(node_modules|dist|\.nifra|\.git)\//.test(file))
|
|
204
|
-
continue;
|
|
205
|
-
offenders.push(file);
|
|
206
|
-
if (offenders.length >= 5)
|
|
207
|
-
break;
|
|
208
|
-
}
|
|
209
|
-
if (offenders.length === 0)
|
|
210
|
-
return;
|
|
211
|
-
throw new Error("[nifra] `nifra dev --bun` can't compile CSS Modules: Bun's DEV-server bundler has no `*.module.css` " +
|
|
212
|
-
"transform (its production `Bun.build` does, so `nifra build` is unaffected). The client would load " +
|
|
213
|
-
"with a bare `ReferenceError: import_… is not defined` instead of your component.\n" +
|
|
214
|
-
` CSS Modules found: ${offenders.join(", ")}\n` +
|
|
215
|
-
" Use `nifra dev` (the Vite pipeline handles them), or drop the CSS Modules to use the Bun dev loop.");
|
|
216
|
-
}
|
|
217
197
|
async function dev(app, flags) {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
198
|
+
if (flags.vite && flags.bun) {
|
|
199
|
+
throw new Error("[nifra] `nifra dev` takes `--vite` or `--bun`, not both.");
|
|
200
|
+
}
|
|
201
|
+
// Default to Bun for the dev phase too. An app whose only transforms are Vite plugins stays on Vite so
|
|
202
|
+
// its transform is never silently dropped; `--vite`/`--bun` make that choice explicit.
|
|
203
|
+
// ONE rule for both phases - `chooseBuildPipeline` decides `dev` exactly as it decides `build`, so a
|
|
204
|
+
// project cannot end up bundled by one toolchain in dev and the other in production. An earlier
|
|
205
|
+
// version of this kept apps declaring `vitePlugins` on Vite for dev even when they also shipped Bun
|
|
206
|
+
// equivalents, on the theory that the framework plugins were the safer HMR path. Measurement killed
|
|
207
|
+
// it: Vue, Solid and Svelte all render correctly on the Bun pipeline, so the hedge bought nothing
|
|
208
|
+
// and cost an app the guarantee that dev and production bundle the same way. (Svelte used to render
|
|
209
|
+
// ONLY on Bun, which made the hedge actively harmful; it now renders on either - see the note at the
|
|
210
|
+
// Vite branch for what the adapter had to be given to make that true.)
|
|
211
|
+
const decision = chooseBuildPipeline(app.resolvedPlugins, flags.vite ? "vite" : flags.bun ? "bun" : undefined, "dev");
|
|
212
|
+
if (decision.pipeline === "bun") {
|
|
223
213
|
// Bun's dev-server bundler takes plugins only via bunfig `[serve.static]`, read at process
|
|
224
214
|
// start - so the boundary plugins (server-fn stubs, server-only emptying) are delivered by
|
|
225
215
|
// generating a config and re-execing this same command once with `--config=`. The child proves
|
|
@@ -237,13 +227,34 @@ async function dev(app, flags) {
|
|
|
237
227
|
"relaunches. Something is rewriting `.nifra/dev-bun/` or the environment between " +
|
|
238
228
|
"launches; refusing to serve without the client-boundary plugins.");
|
|
239
229
|
}
|
|
240
|
-
const { bunfigPath, launchToken } = await writeBunDevConfig(app.cwd);
|
|
241
|
-
const child = Bun.spawn([
|
|
230
|
+
const { bunfigPath, launchToken } = await writeBunDevConfig(app.cwd, app.configPath);
|
|
231
|
+
const child = Bun.spawn([
|
|
232
|
+
process.execPath,
|
|
233
|
+
`--config=${bunfigPath}`,
|
|
234
|
+
// The app's `conditions` reach SSR here or nowhere: bunfig has no key for them (a top-level,
|
|
235
|
+
// `[run]`, `[serve.static]` or `[bundle]` `conditions` is parsed and ignored), and the re-exec
|
|
236
|
+
// is the only point where this process's resolver is still configurable. Without it a package
|
|
237
|
+
// with an `exports` map resolves to a DIFFERENT file on the server than in the client bundle,
|
|
238
|
+
// which is the two-toolchains-one-module failure the pipeline guard exists to prevent.
|
|
239
|
+
//
|
|
240
|
+
// One flag per condition. `--conditions=a,b` is accepted and matches nothing: Bun takes the
|
|
241
|
+
// whole string as a single condition name, so the comma form fails silently.
|
|
242
|
+
...(app.framework.conditions ?? []).map((condition) => `--conditions=${condition}`),
|
|
243
|
+
...process.argv.slice(1),
|
|
244
|
+
], {
|
|
242
245
|
stdio: ["inherit", "inherit", "inherit"],
|
|
243
246
|
env: {
|
|
244
247
|
...process.env,
|
|
245
248
|
NIFRA_BUN_DEV_TOKEN: launchToken,
|
|
246
249
|
NIFRA_BUN_DEV_DEPTH: String(depth + 1),
|
|
250
|
+
// The child is the process that renders, so it is the one that needs dev-shaped runtimes.
|
|
251
|
+
// A library ships its dev build behind the `development` export condition with a `NODE_ENV`
|
|
252
|
+
// fallback for resolvers that set no conditions - which is Bun's runtime, where SSR runs,
|
|
253
|
+
// so on the server that fallback is the only signal there is. It has to be in place before
|
|
254
|
+
// the app's config is read, because reading it imports the adapter and through it the
|
|
255
|
+
// framework runtime; setting it inside the dev server would already be a step too late.
|
|
256
|
+
// Preserved if the caller pinned one.
|
|
257
|
+
NODE_ENV: process.env.NODE_ENV ?? "development",
|
|
247
258
|
},
|
|
248
259
|
});
|
|
249
260
|
const forward = () => child.kill("SIGINT");
|
|
@@ -251,6 +262,28 @@ async function dev(app, flags) {
|
|
|
251
262
|
process.on("SIGTERM", forward);
|
|
252
263
|
process.exit(await child.exited);
|
|
253
264
|
}
|
|
265
|
+
// SSR runs in THIS process, on Bun's runtime - a different loader from the dev-server bundler the
|
|
266
|
+
// bunfig above configures. Both halves have to agree, so the runtime gets the same transforms:
|
|
267
|
+
//
|
|
268
|
+
// - CSS Modules. The scoped name is a pure function of file path + class name, so the `"ssr"` form
|
|
269
|
+
// produces the identical map the client bundle emits (and no stylesheet - that ships from the
|
|
270
|
+
// client build). Without it `styles.box` is `undefined` on the server: the SSR markup carries no
|
|
271
|
+
// class, the page paints unstyled, and hydration then reports a className mismatch.
|
|
272
|
+
// - the app's `serverPlugins` - the SSR counterpart of the `clientPlugins` the generated bunfig
|
|
273
|
+
// delivers, exactly as `nifra build`'s prerender pass registers them.
|
|
274
|
+
//
|
|
275
|
+
// Registered BEFORE `createDevServer`, because a Bun runtime plugin only affects modules loaded
|
|
276
|
+
// after it - and `createApp` imports the route modules.
|
|
277
|
+
const { plugin } = await import("bun");
|
|
278
|
+
const { cssModulesBunPlugin } = await import("@nifrajs/web/plugins/css-modules");
|
|
279
|
+
plugin(cssModulesBunPlugin("ssr"));
|
|
280
|
+
for (const p of asBunPlugins(app.resolvedPlugins.serverPlugins))
|
|
281
|
+
plugin(p);
|
|
282
|
+
// "After" has one consequence an adapter has to respect. Reading the app's config is what PRODUCES
|
|
283
|
+
// these plugins, so the config's import graph - which includes the adapter - is already loaded when
|
|
284
|
+
// they register. An adapter must therefore not import an asset its own plugin compiles at module
|
|
285
|
+
// scope; it has to defer to first render, which happens after this point. `@nifrajs/web-svelte`
|
|
286
|
+
// loads its `Chain.svelte` that way for exactly this reason, and says so at the import.
|
|
254
287
|
const { createDevServer } = await import("@nifrajs/web/dev");
|
|
255
288
|
const { framework: fw, routesDir, outDir, backend } = app;
|
|
256
289
|
const server = await createDevServer({
|
|
@@ -258,8 +291,10 @@ async function dev(app, flags) {
|
|
|
258
291
|
outDir,
|
|
259
292
|
clientModule: fw.clientModule,
|
|
260
293
|
port: flags.port,
|
|
261
|
-
//
|
|
262
|
-
//
|
|
294
|
+
// For the background client-leak guard, which re-runs `buildClient` on change - the SERVED client
|
|
295
|
+
// bundle gets these same plugins through the generated bunfig, since Bun's dev-server bundler takes
|
|
296
|
+
// plugins only as module paths. Never `vitePlugins`, which belong to the other pipeline;
|
|
297
|
+
// `assertPipelineSeparation` already refuses a plugin sitting in the wrong slot.
|
|
263
298
|
plugins: asBunPlugins(app.resolvedPlugins.clientPlugins),
|
|
264
299
|
...(fw.publicDir !== undefined ? { publicDir: fw.publicDir } : {}),
|
|
265
300
|
...(fw.conditions ? { conditions: fw.conditions } : {}),
|
|
@@ -268,13 +303,15 @@ async function dev(app, flags) {
|
|
|
268
303
|
adapter: asAdapter(fw.adapter),
|
|
269
304
|
manifest: discoverRoutes(routesDir, { importQuery }),
|
|
270
305
|
clientEntry,
|
|
306
|
+
...(fw.use ? { use: asUse(fw.use) } : {}),
|
|
271
307
|
...apiOf(backend),
|
|
272
308
|
}),
|
|
273
309
|
});
|
|
274
310
|
console.log(`nifra dev (bun) → http://localhost:${server.port}`);
|
|
311
|
+
console.log(` ${describePipeline(decision)}`);
|
|
275
312
|
return;
|
|
276
313
|
}
|
|
277
|
-
// Preflight:
|
|
314
|
+
// Preflight: the Vite fallback needs `vite` resolvable from the project. Run via `bunx @nifrajs/cli dev` the CLI
|
|
278
315
|
// sits in an isolated install where the project's peer deps don't resolve, so the vite import below fails
|
|
279
316
|
// with an opaque ERR_MODULE_NOT_FOUND. Surface the real fix instead.
|
|
280
317
|
try {
|
|
@@ -294,6 +331,18 @@ async function dev(app, flags) {
|
|
|
294
331
|
// same file in one process, with only one of them governed by Vite's `resolve.dedupe`. That is what
|
|
295
332
|
// made an app's hand-written React alias fail to reach SSR, and it is the condition being removed -
|
|
296
333
|
// not a redundancy worth keeping "just in case".
|
|
334
|
+
//
|
|
335
|
+
// Svelte is the case that proves the rule, and the discarded attempts are recorded so they are not
|
|
336
|
+
// retried. The adapter package stays `ssr.external` (its context must be the ONE instance the
|
|
337
|
+
// Bun-imported adapter provides), so BUN loads it - and `@nifrajs/web-svelte` renders through its own
|
|
338
|
+
// `Chain.svelte`, which needs a compiler that this pipeline does not put in the runtime. Registering
|
|
339
|
+
// the app's `serverPlugins` here, scoped to that package's directory, compiles it against a SECOND
|
|
340
|
+
// Svelte runtime and the first `setContext` dies (`context.function` null); externalizing the
|
|
341
|
+
// adapter's framework peer as well gets further and then dies on `context.function[FILENAME]`,
|
|
342
|
+
// because `svelteBunPlugin` and `vite-plugin-svelte` do not emit the same dev-mode component shape.
|
|
343
|
+
// Both attempts add a compiler. The fix removes one instead: `createViteDevServer` publishes its
|
|
344
|
+
// `ssrLoadModule` via `setSsrModuleLoader`, and the adapter loads `Chain.svelte` AND `svelte/server`
|
|
345
|
+
// through it - one toolchain, one runtime, context intact across the whole tree.
|
|
297
346
|
const server = await createViteDevServer({
|
|
298
347
|
root: cwd,
|
|
299
348
|
routesDir,
|
|
@@ -312,10 +361,12 @@ async function dev(app, flags) {
|
|
|
312
361
|
adapter: asAdapter(fw.adapter),
|
|
313
362
|
manifest: discoverRoutes(routesDir, { load }),
|
|
314
363
|
clientEntry,
|
|
364
|
+
...(fw.use ? { use: asUse(fw.use) } : {}),
|
|
315
365
|
...apiOf(backend),
|
|
316
366
|
}),
|
|
317
367
|
});
|
|
318
|
-
console.log(`nifra dev → http://localhost:${server.port}`);
|
|
368
|
+
console.log(`nifra dev (vite) → http://localhost:${server.port}`);
|
|
369
|
+
console.log(` ${describePipeline(decision)}`);
|
|
319
370
|
}
|
|
320
371
|
/**
|
|
321
372
|
* `nifra build --target <t>` - package the engine (buildClient + buildServer + prerender) into one
|
|
@@ -324,6 +375,32 @@ async function dev(app, flags) {
|
|
|
324
375
|
* `nifra.config.ts`, which pulls in Vite plugins), and the backend from `backend.ts` when present;
|
|
325
376
|
* `buildTarget` generates the per-target server entry from those + the app's `routes/`.
|
|
326
377
|
*/
|
|
378
|
+
/**
|
|
379
|
+
* Refuse a `use` the generated server entry cannot import.
|
|
380
|
+
*
|
|
381
|
+
* `nifra build` emits `import { use } from <frameworkFile>` (framework.ts, the edge-bundlable file),
|
|
382
|
+
* but `loadApp` prefers `nifra.config.ts` - so an app with BOTH files whose `use` lives only in
|
|
383
|
+
* `nifra.config.ts` makes `fw.use` defined while `framework.ts` exports nothing of that name, and the
|
|
384
|
+
* build dies later with an opaque bundler error pointing at generated code. Refuse it here with the
|
|
385
|
+
* exact move instead, in the spirit of `assertPipelineSeparation` (load.ts).
|
|
386
|
+
*
|
|
387
|
+
* Detected by importing `frameworkFile` and checking the named export, not by comparing paths alone:
|
|
388
|
+
* a split app legitimately DEFINES `use` in framework.ts and re-exports it from nifra.config.ts
|
|
389
|
+
* (exactly how `adapter` reaches both readers), and a path compare would refuse that correct layout.
|
|
390
|
+
* The import is cheap - framework.ts is already in the loaded config's module graph in that layout.
|
|
391
|
+
*/
|
|
392
|
+
export async function assertUseIsEdgeExported(use, configPath, frameworkFile) {
|
|
393
|
+
if (use === undefined || configPath === frameworkFile)
|
|
394
|
+
return;
|
|
395
|
+
const mod = (await import(__rewriteRelativeImportExtension(frameworkFile)).catch(() => ({})));
|
|
396
|
+
if (typeof mod.use === "function")
|
|
397
|
+
return;
|
|
398
|
+
throw new Error(`[nifra] \`use\` is exported from ${configPath} but not from ${frameworkFile}. ` +
|
|
399
|
+
"`nifra build` generates a server entry that imports `use` from framework.ts (the edge-bundled " +
|
|
400
|
+
"file), so this build would fail later with an opaque bundler error inside generated code.\n\n" +
|
|
401
|
+
" - Define `use` in framework.ts and re-export it from nifra.config.ts " +
|
|
402
|
+
'(`export { use } from "./framework.ts"`) so `nifra dev` sees it too.');
|
|
403
|
+
}
|
|
327
404
|
async function buildForTarget(app, target, flags) {
|
|
328
405
|
const { isBuildTarget, renderSizeReport, BUILD_TARGETS } = await import("@nifrajs/web/build");
|
|
329
406
|
if (!isBuildTarget(target)) {
|
|
@@ -352,6 +429,7 @@ async function buildForTarget(app, target, flags) {
|
|
|
352
429
|
? resolve(cwd, "nifra.config.ts")
|
|
353
430
|
: resolve(cwd, "framework.ts");
|
|
354
431
|
const backendFile = resolve(cwd, "backend.ts");
|
|
432
|
+
await assertUseIsEdgeExported(fw.use, app.configPath, frameworkFile);
|
|
355
433
|
// Plugin FORMAT differs by pipeline: the Bun build takes Bun plugins (clientPlugins/serverPlugins); the
|
|
356
434
|
// Vite build takes the app's Vite plugins (fw.vitePlugins) for BOTH halves. buildTargetWith forwards
|
|
357
435
|
// whatever it's given straight to the chosen bundler, which casts to its own plugin type.
|
|
@@ -370,6 +448,7 @@ async function buildForTarget(app, target, flags) {
|
|
|
370
448
|
workDir: resolve(cwd, ".nifra-build"),
|
|
371
449
|
clientModule: fw.clientModule,
|
|
372
450
|
adapterImport: frameworkFile,
|
|
451
|
+
...(fw.use ? { useImport: frameworkFile } : {}),
|
|
373
452
|
...(backend !== undefined && existsSync(backendFile) ? { backendImport: backendFile } : {}),
|
|
374
453
|
...plugins,
|
|
375
454
|
...(fw.conditions ? { conditions: fw.conditions } : {}),
|
|
@@ -379,11 +458,10 @@ async function buildForTarget(app, target, flags) {
|
|
|
379
458
|
// The static target needs a built app to drive prerendering - only build it when targeting static.
|
|
380
459
|
...(target === "static" ? { prerenderApp: await buildPrerenderApp(app) } : {}),
|
|
381
460
|
});
|
|
382
|
-
console.log(`nifra build (${target}${
|
|
383
|
-
//
|
|
384
|
-
//
|
|
385
|
-
|
|
386
|
-
console.log(` ${decision.reason}`);
|
|
461
|
+
console.log(`nifra build (${target}, ${decision.pipeline}) → ${result.run}`);
|
|
462
|
+
// The pipeline is stated on every build, not only the surprising ones - an auto-selected Vite build
|
|
463
|
+
// must never look like the default, and a default Bun build must not leave the question open either.
|
|
464
|
+
console.log(` ${describePipeline(decision)}`);
|
|
387
465
|
if (flags.report)
|
|
388
466
|
console.log(`\n${renderSizeReport(result.size)}`);
|
|
389
467
|
}
|
|
@@ -402,6 +480,7 @@ async function buildPrerenderApp(app) {
|
|
|
402
480
|
adapter: asAdapter(fw.adapter),
|
|
403
481
|
manifest: discoverRoutes(routesDir),
|
|
404
482
|
clientEntry: client.entry,
|
|
483
|
+
...(fw.use ? { use: asUse(fw.use) } : {}),
|
|
405
484
|
...(client.routes ? { routePreload: client.routes } : {}),
|
|
406
485
|
...(client.css ? { styles: client.css } : {}),
|
|
407
486
|
...(client.routeStyles ? { routeStyles: client.routeStyles } : {}),
|
|
@@ -471,7 +550,10 @@ async function main() {
|
|
|
471
550
|
// the eager `loadApp` below (which would fail fast on a project that's API-only / not yet built).
|
|
472
551
|
if (command === "mcp") {
|
|
473
552
|
const { runMcpServer } = await import("./mcp.js");
|
|
474
|
-
|
|
553
|
+
// `nifra mcp [dir]` - an explicit project directory pins the root (for clients configured outside
|
|
554
|
+
// the project); otherwise the server resolves it from cwd + the client's MCP roots.
|
|
555
|
+
const dirArg = argv[1] !== undefined && !argv[1].startsWith("-") ? argv[1] : undefined;
|
|
556
|
+
await runMcpServer(process.cwd(), CLI_VERSION, dirArg);
|
|
475
557
|
return;
|
|
476
558
|
}
|
|
477
559
|
// `docs-mcp` runs the PUBLIC docs MCP over HTTP - project-independent (serves the bundled corpus), so
|
|
@@ -501,11 +583,36 @@ async function main() {
|
|
|
501
583
|
const { runCheck } = await import("./check.js");
|
|
502
584
|
if (!(await runCheck(process.cwd(), {
|
|
503
585
|
json: argv.includes("--json"),
|
|
586
|
+
structured: argv.includes("--json"),
|
|
504
587
|
lintsOnly: argv.includes("--lints-only"),
|
|
505
588
|
})))
|
|
506
589
|
process.exitCode = 1;
|
|
507
590
|
return;
|
|
508
591
|
}
|
|
592
|
+
if (command === "fix") {
|
|
593
|
+
const codeIndex = argv.indexOf("--code");
|
|
594
|
+
const code = codeIndex === -1 ? undefined : argv[codeIndex + 1];
|
|
595
|
+
try {
|
|
596
|
+
const { collectCheckResult } = await import("./check.js");
|
|
597
|
+
const { applyDiagnosticRecipe } = await import("./fix-recipes.js");
|
|
598
|
+
const result = await collectCheckResult(process.cwd(), { lintsOnly: true });
|
|
599
|
+
const changed = [];
|
|
600
|
+
for (const diagnostic of result.structuredDiagnostics ?? []) {
|
|
601
|
+
if (code !== undefined && diagnostic.code !== code)
|
|
602
|
+
continue;
|
|
603
|
+
changed.push(...(await applyDiagnosticRecipe(process.cwd(), diagnostic)));
|
|
604
|
+
}
|
|
605
|
+
const final = await collectCheckResult(process.cwd(), { lintsOnly: true });
|
|
606
|
+
console.log(JSON.stringify({ changed, diagnostics: final.structuredDiagnostics ?? [] }, null, 2));
|
|
607
|
+
if (final.ok === false)
|
|
608
|
+
process.exitCode = 1;
|
|
609
|
+
}
|
|
610
|
+
catch (err) {
|
|
611
|
+
console.error(formatCliError(err));
|
|
612
|
+
process.exitCode = 1;
|
|
613
|
+
}
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
509
616
|
// `init-agents` retrofits the agent-discovery files (.mcp.json, CLAUDE.md, …) into the cwd. It's a
|
|
510
617
|
// pure file-writing command independent of the app loading, so dispatch it before the eager `loadApp`
|
|
511
618
|
// (an existing app might be API-only or not yet built). It always succeeds unless a write throws.
|
|
@@ -538,9 +645,18 @@ async function main() {
|
|
|
538
645
|
// so it runs on an API-only / not-yet-built project.
|
|
539
646
|
if (command === "doctor") {
|
|
540
647
|
const { runDoctor } = await import("./doctor.js");
|
|
648
|
+
const targetIdx = argv.indexOf("--target");
|
|
649
|
+
const target = targetIdx !== -1 ? argv[targetIdx + 1] : undefined;
|
|
650
|
+
if (targetIdx !== -1 && (target === undefined || target.startsWith("-"))) {
|
|
651
|
+
console.error("[nifra] --target needs a value: bun | node | deno | cf-pages | vercel");
|
|
652
|
+
process.exitCode = 1;
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
541
655
|
if (!(await runDoctor(process.cwd(), {
|
|
542
656
|
json: argv.includes("--json"),
|
|
543
657
|
autoFix: argv.includes("--auto-fix") || argv.includes("--fix"),
|
|
658
|
+
strict: argv.includes("--strict"),
|
|
659
|
+
...(target === undefined ? {} : { target }),
|
|
544
660
|
})))
|
|
545
661
|
process.exitCode = 1;
|
|
546
662
|
return;
|
|
@@ -618,6 +734,15 @@ async function main() {
|
|
|
618
734
|
try {
|
|
619
735
|
if (!(await runAssurance(process.cwd(), {
|
|
620
736
|
json: argv.includes("--json"),
|
|
737
|
+
bundle: argv.includes("--json") ||
|
|
738
|
+
argv.includes("--strict") ||
|
|
739
|
+
argv.includes("--hydration") ||
|
|
740
|
+
argv.includes("--interact") ||
|
|
741
|
+
argv.includes("--out"),
|
|
742
|
+
strict: argv.includes("--strict"),
|
|
743
|
+
hydration: argv.includes("--hydration"),
|
|
744
|
+
interact: argv.includes("--interact"),
|
|
745
|
+
...(argv.includes("--out") ? { out: argv[argv.indexOf("--out") + 1] } : {}),
|
|
621
746
|
...(config !== undefined ? { config } : {}),
|
|
622
747
|
})))
|
|
623
748
|
process.exitCode = 1;
|
|
@@ -628,6 +753,57 @@ async function main() {
|
|
|
628
753
|
}
|
|
629
754
|
return;
|
|
630
755
|
}
|
|
756
|
+
if (command === "contracts") {
|
|
757
|
+
const action = argv[1];
|
|
758
|
+
const outIndex = argv.indexOf("--out");
|
|
759
|
+
const out = outIndex === -1 ? undefined : argv[outIndex + 1];
|
|
760
|
+
try {
|
|
761
|
+
const { checkContractsLock, snapshotContracts } = await import("./contracts.js");
|
|
762
|
+
if (action === "snapshot") {
|
|
763
|
+
const lock = await snapshotContracts(process.cwd(), out);
|
|
764
|
+
if (argv.includes("--json"))
|
|
765
|
+
console.log(JSON.stringify(lock, null, 2));
|
|
766
|
+
else
|
|
767
|
+
console.log(`[nifra] wrote ${Object.keys(lock.routes).length} contract digests`);
|
|
768
|
+
}
|
|
769
|
+
else if (action === "check") {
|
|
770
|
+
const result = await checkContractsLock(process.cwd());
|
|
771
|
+
const output = { ok: result.present && result.diagnostics.length === 0, ...result };
|
|
772
|
+
console.log(JSON.stringify(output, null, 2));
|
|
773
|
+
if (!output.ok)
|
|
774
|
+
process.exitCode = 1;
|
|
775
|
+
}
|
|
776
|
+
else {
|
|
777
|
+
console.error("[nifra] contracts needs snapshot or check");
|
|
778
|
+
process.exitCode = 1;
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
catch (err) {
|
|
782
|
+
console.error(formatCliError(err));
|
|
783
|
+
process.exitCode = 1;
|
|
784
|
+
}
|
|
785
|
+
return;
|
|
786
|
+
}
|
|
787
|
+
if (command === "replay") {
|
|
788
|
+
const file = argv[1];
|
|
789
|
+
if (file === undefined || file.startsWith("-")) {
|
|
790
|
+
console.error("[nifra] replay needs a file path");
|
|
791
|
+
process.exitCode = 1;
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
try {
|
|
795
|
+
const { runReplay } = await import("./replay.js");
|
|
796
|
+
const result = await runReplay(process.cwd(), file);
|
|
797
|
+
console.log(JSON.stringify(result, null, 2));
|
|
798
|
+
if (!result.ok)
|
|
799
|
+
process.exitCode = 1;
|
|
800
|
+
}
|
|
801
|
+
catch (err) {
|
|
802
|
+
console.error(formatCliError(err));
|
|
803
|
+
process.exitCode = 1;
|
|
804
|
+
}
|
|
805
|
+
return;
|
|
806
|
+
}
|
|
631
807
|
if (command === "capabilities") {
|
|
632
808
|
const action = argv[1];
|
|
633
809
|
const valueAfter = (flag) => {
|
|
@@ -740,6 +916,45 @@ async function main() {
|
|
|
740
916
|
}
|
|
741
917
|
return;
|
|
742
918
|
}
|
|
919
|
+
if (command === "prove") {
|
|
920
|
+
const valueAfter = (flag) => {
|
|
921
|
+
const index = argv.indexOf(flag);
|
|
922
|
+
const value = index === -1 ? undefined : argv[index + 1];
|
|
923
|
+
if (index !== -1 && (value === undefined || value.startsWith("-"))) {
|
|
924
|
+
throw new Error(`[nifra] ${flag} needs a value`);
|
|
925
|
+
}
|
|
926
|
+
return value;
|
|
927
|
+
};
|
|
928
|
+
try {
|
|
929
|
+
const minRaw = valueAfter("--min");
|
|
930
|
+
const min = minRaw === undefined ? undefined : Number(minRaw);
|
|
931
|
+
if (min !== undefined && (!Number.isInteger(min) || min < 0 || min > 4)) {
|
|
932
|
+
throw new Error("[nifra] --min must be an integer level between 0 and 4");
|
|
933
|
+
}
|
|
934
|
+
const changedFiles = [];
|
|
935
|
+
for (let index = 0; index < argv.length; index++) {
|
|
936
|
+
if (argv[index] !== "--file")
|
|
937
|
+
continue;
|
|
938
|
+
const file = argv[index + 1];
|
|
939
|
+
if (file === undefined || file.startsWith("-"))
|
|
940
|
+
throw new Error("[nifra] --file needs a path");
|
|
941
|
+
changedFiles.push(file);
|
|
942
|
+
index++;
|
|
943
|
+
}
|
|
944
|
+
const { runWorkGraph } = await import("./work-graph.js");
|
|
945
|
+
if (!(await runWorkGraph(process.cwd(), {
|
|
946
|
+
json: argv.includes("--json"),
|
|
947
|
+
...(changedFiles.length === 0 ? {} : { changedFiles }),
|
|
948
|
+
...(min === undefined ? {} : { minLevel: min }),
|
|
949
|
+
})))
|
|
950
|
+
process.exitCode = 1;
|
|
951
|
+
}
|
|
952
|
+
catch (err) {
|
|
953
|
+
console.error(formatCliError(err));
|
|
954
|
+
process.exitCode = 1;
|
|
955
|
+
}
|
|
956
|
+
return;
|
|
957
|
+
}
|
|
743
958
|
// `upgrade` is a pure cwd file-transformer (package.json pins + import moves) driven by a per-release
|
|
744
959
|
// recipe, then verified with `nifra check`. Dispatch before the eager `loadApp` - it must run on any
|
|
745
960
|
// repo (API-only, not built, or mid-upgrade with edits that don't yet typecheck under dry-run).
|