@nifrajs/cli 4.0.0 → 5.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 +3 -0
- package/dist/agent-files.d.ts +107 -0
- package/dist/agent-files.d.ts.map +1 -0
- package/dist/agent-files.js +162 -0
- package/dist/agent-files.js.map +1 -0
- package/dist/app-files.d.ts +6 -0
- package/dist/app-files.d.ts.map +1 -1
- package/dist/app-files.js +27 -2
- package/dist/app-files.js.map +1 -1
- package/dist/assure-hydration.d.ts.map +1 -1
- package/dist/assure-hydration.js +7 -2
- package/dist/assure-hydration.js.map +1 -1
- package/dist/capabilities-tool.d.ts.map +1 -1
- package/dist/capabilities-tool.js +13 -3
- package/dist/capabilities-tool.js.map +1 -1
- package/dist/check-diagnostics.d.ts +20 -2
- package/dist/check-diagnostics.d.ts.map +1 -1
- package/dist/check-diagnostics.js +54 -7
- package/dist/check-diagnostics.js.map +1 -1
- package/dist/check-scan.d.ts +75 -22
- package/dist/check-scan.d.ts.map +1 -1
- package/dist/check-scan.js +1475 -310
- package/dist/check-scan.js.map +1 -1
- package/dist/check.d.ts +2 -2
- package/dist/check.d.ts.map +1 -1
- package/dist/check.js +192 -65
- package/dist/check.js.map +1 -1
- package/dist/cli.d.ts +7 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +112 -5
- package/dist/cli.js.map +1 -1
- package/dist/client-module.d.ts +18 -0
- package/dist/client-module.d.ts.map +1 -0
- package/dist/client-module.js +49 -0
- package/dist/client-module.js.map +1 -0
- package/dist/command-catalog.d.ts +21 -5
- package/dist/command-catalog.d.ts.map +1 -1
- package/dist/command-catalog.js +138 -38
- package/dist/command-catalog.js.map +1 -1
- package/dist/component-check.d.ts +13 -0
- package/dist/component-check.d.ts.map +1 -0
- package/dist/component-check.js +59 -0
- package/dist/component-check.js.map +1 -0
- package/dist/contract-proof.d.ts +1 -1
- package/dist/contract-proof.d.ts.map +1 -1
- package/dist/contract-proof.js +2 -24
- package/dist/contract-proof.js.map +1 -1
- package/dist/contracts.d.ts +19 -0
- package/dist/contracts.d.ts.map +1 -1
- package/dist/contracts.js +50 -3
- package/dist/contracts.js.map +1 -1
- package/dist/db-tool.d.ts.map +1 -1
- package/dist/db-tool.js +4 -3
- package/dist/db-tool.js.map +1 -1
- package/dist/dev-bun-config.d.ts +5 -1
- package/dist/dev-bun-config.d.ts.map +1 -1
- package/dist/dev-bun-config.js +44 -11
- package/dist/dev-bun-config.js.map +1 -1
- package/dist/diagnostics.d.ts +3 -0
- package/dist/diagnostics.d.ts.map +1 -1
- package/dist/diagnostics.js.map +1 -1
- package/dist/diff-tool.d.ts +9 -8
- package/dist/diff-tool.d.ts.map +1 -1
- package/dist/diff-tool.js +28 -24
- package/dist/diff-tool.js.map +1 -1
- package/dist/doctor.d.ts +13 -0
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +23 -15
- package/dist/doctor.js.map +1 -1
- package/dist/env-file.d.ts +6 -0
- package/dist/env-file.d.ts.map +1 -1
- package/dist/env-file.js +6 -0
- package/dist/env-file.js.map +1 -1
- package/dist/fix-recipes.d.ts.map +1 -1
- package/dist/fix-recipes.js +28 -3
- package/dist/fix-recipes.js.map +1 -1
- package/dist/hand-built-manifest.d.ts +27 -0
- package/dist/hand-built-manifest.d.ts.map +1 -0
- package/dist/hand-built-manifest.js +124 -0
- package/dist/hand-built-manifest.js.map +1 -0
- package/dist/hooks-report.d.ts +23 -0
- package/dist/hooks-report.d.ts.map +1 -0
- package/dist/hooks-report.js +104 -0
- package/dist/hooks-report.js.map +1 -0
- package/dist/init-agents.d.ts +2 -2
- package/dist/init-agents.d.ts.map +1 -1
- package/dist/init-agents.js +17 -11
- package/dist/init-agents.js.map +1 -1
- package/dist/internal/nifra-project.d.ts +41 -0
- package/dist/internal/nifra-project.d.ts.map +1 -0
- package/dist/internal/nifra-project.js +183 -0
- package/dist/internal/nifra-project.js.map +1 -0
- package/dist/internal/project-specifiers.d.ts +16 -0
- package/dist/internal/project-specifiers.d.ts.map +1 -0
- package/dist/internal/project-specifiers.js +396 -0
- package/dist/internal/project-specifiers.js.map +1 -0
- package/dist/internal/script-kind.d.ts.map +1 -1
- package/dist/internal/script-kind.js +2 -1
- package/dist/internal/script-kind.js.map +1 -1
- package/dist/internal/source-facts.d.ts +2 -0
- package/dist/internal/source-facts.d.ts.map +1 -1
- package/dist/internal/source-facts.js +11 -0
- package/dist/internal/source-facts.js.map +1 -1
- package/dist/internal/typescript-import.d.ts +1 -1
- package/dist/internal/typescript-import.d.ts.map +1 -1
- package/dist/internal/typescript-import.js +5 -3
- package/dist/internal/typescript-import.js.map +1 -1
- package/dist/introspect.d.ts +6 -6
- package/dist/introspect.d.ts.map +1 -1
- package/dist/introspect.js +55 -24
- package/dist/introspect.js.map +1 -1
- package/dist/levels-tool.d.ts.map +1 -1
- package/dist/levels-tool.js +93 -15
- package/dist/levels-tool.js.map +1 -1
- package/dist/load.d.ts +31 -8
- package/dist/load.d.ts.map +1 -1
- package/dist/load.js +37 -7
- package/dist/load.js.map +1 -1
- package/dist/mcp-context.d.ts +5 -5
- package/dist/mcp-context.d.ts.map +1 -1
- package/dist/mcp-context.js +2 -2
- package/dist/mcp-context.js.map +1 -1
- package/dist/mcp-exec.d.ts +12 -4
- package/dist/mcp-exec.d.ts.map +1 -1
- package/dist/mcp-exec.js +50 -13
- package/dist/mcp-exec.js.map +1 -1
- package/dist/mcp-http.js +1 -1
- package/dist/mcp-io.d.ts +17 -0
- package/dist/mcp-io.d.ts.map +1 -1
- package/dist/mcp-io.js +40 -0
- package/dist/mcp-io.js.map +1 -1
- package/dist/mcp-isolate.d.ts.map +1 -1
- package/dist/mcp-isolate.js +3 -2
- package/dist/mcp-isolate.js.map +1 -1
- package/dist/mcp-project-child.js +1 -1
- package/dist/mcp-project-child.js.map +1 -1
- package/dist/mcp-root.d.ts.map +1 -1
- package/dist/mcp-root.js +50 -10
- package/dist/mcp-root.js.map +1 -1
- package/dist/mcp-ws.d.ts.map +1 -1
- package/dist/mcp-ws.js +3 -1
- package/dist/mcp-ws.js.map +1 -1
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +8 -2
- package/dist/mcp.js.map +1 -1
- package/dist/migrate-layout.d.ts +7 -0
- package/dist/migrate-layout.d.ts.map +1 -1
- package/dist/migrate-layout.js +958 -71
- package/dist/migrate-layout.js.map +1 -1
- package/dist/output-schemas.d.ts +94 -0
- package/dist/output-schemas.d.ts.map +1 -0
- package/dist/output-schemas.js +864 -0
- package/dist/output-schemas.js.map +1 -0
- package/dist/port.d.ts.map +1 -1
- package/dist/port.js +3 -2
- package/dist/port.js.map +1 -1
- package/dist/project-facts.d.ts +15 -4
- package/dist/project-facts.d.ts.map +1 -1
- package/dist/project-facts.js +1 -0
- package/dist/project-facts.js.map +1 -1
- package/dist/recipes/4.0.0.d.ts.map +1 -1
- package/dist/recipes/4.0.0.js +2 -0
- package/dist/recipes/4.0.0.js.map +1 -1
- package/dist/recipes/5.0.0.d.ts +11 -0
- package/dist/recipes/5.0.0.d.ts.map +1 -0
- package/dist/recipes/5.0.0.js +34 -0
- package/dist/recipes/5.0.0.js.map +1 -0
- package/dist/recipes/index.d.ts +7 -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/replay.d.ts.map +1 -1
- package/dist/replay.js +12 -3
- package/dist/replay.js.map +1 -1
- package/dist/review.d.ts +3 -1
- package/dist/review.d.ts.map +1 -1
- package/dist/review.js +38 -7
- package/dist/review.js.map +1 -1
- package/dist/rules/codes.d.ts +10 -0
- package/dist/rules/codes.d.ts.map +1 -1
- package/dist/rules/codes.js +10 -0
- package/dist/rules/codes.js.map +1 -1
- package/dist/rules/comment-markers.d.ts +16 -7
- package/dist/rules/comment-markers.d.ts.map +1 -1
- package/dist/rules/comment-markers.js +58 -19
- package/dist/rules/comment-markers.js.map +1 -1
- package/dist/rules/contract-visibility.d.ts +3 -0
- package/dist/rules/contract-visibility.d.ts.map +1 -0
- package/dist/rules/contract-visibility.js +337 -0
- package/dist/rules/contract-visibility.js.map +1 -0
- package/dist/rules/data-guard.d.ts +7 -0
- package/dist/rules/data-guard.d.ts.map +1 -1
- package/dist/rules/data-guard.js +319 -37
- package/dist/rules/data-guard.js.map +1 -1
- package/dist/rules/layout.d.ts +4 -0
- package/dist/rules/layout.d.ts.map +1 -0
- package/dist/rules/layout.js +19 -0
- package/dist/rules/layout.js.map +1 -0
- package/dist/rules/legacy.d.ts.map +1 -1
- package/dist/rules/legacy.js +158 -43
- package/dist/rules/legacy.js.map +1 -1
- package/dist/rules/pages.d.ts +24 -0
- package/dist/rules/pages.d.ts.map +1 -1
- package/dist/rules/pages.js +221 -3
- package/dist/rules/pages.js.map +1 -1
- package/dist/rules/routes.d.ts +6 -0
- package/dist/rules/routes.d.ts.map +1 -1
- package/dist/rules/routes.js +111 -16
- package/dist/rules/routes.js.map +1 -1
- package/dist/rules/secrets.js +2 -2
- package/dist/rules/secrets.js.map +1 -1
- package/dist/rules/security.d.ts +12 -1
- package/dist/rules/security.d.ts.map +1 -1
- package/dist/rules/security.js +281 -68
- package/dist/rules/security.js.map +1 -1
- package/dist/rules/zones.d.ts +13 -0
- package/dist/rules/zones.d.ts.map +1 -1
- package/dist/rules/zones.js +192 -40
- package/dist/rules/zones.js.map +1 -1
- package/dist/scaffold.d.ts.map +1 -1
- package/dist/scaffold.js +6 -4
- package/dist/scaffold.js.map +1 -1
- package/dist/sdk.d.ts.map +1 -1
- package/dist/sdk.js +14 -5
- package/dist/sdk.js.map +1 -1
- package/dist/stylex-migrate.d.ts.map +1 -1
- package/dist/stylex-migrate.js +163 -7
- package/dist/stylex-migrate.js.map +1 -1
- package/dist/sync-manifest.d.ts +10 -2
- package/dist/sync-manifest.d.ts.map +1 -1
- package/dist/sync-manifest.js +42 -18
- package/dist/sync-manifest.js.map +1 -1
- package/dist/test-tool.d.ts.map +1 -1
- package/dist/test-tool.js +12 -3
- package/dist/test-tool.js.map +1 -1
- package/dist/typecheck-coverage.d.ts +20 -0
- package/dist/typecheck-coverage.d.ts.map +1 -0
- package/dist/typecheck-coverage.js +129 -0
- package/dist/typecheck-coverage.js.map +1 -0
- package/dist/upgrade.d.ts +69 -0
- package/dist/upgrade.d.ts.map +1 -1
- package/dist/upgrade.js +420 -77
- package/dist/upgrade.js.map +1 -1
- package/dist/verification-plan.d.ts.map +1 -1
- package/dist/verification-plan.js +11 -0
- package/dist/verification-plan.js.map +1 -1
- package/dist/version-drift.d.ts +22 -0
- package/dist/version-drift.d.ts.map +1 -0
- package/dist/version-drift.js +42 -0
- package/dist/version-drift.js.map +1 -0
- package/dist/web-app-options.d.ts +1 -1
- package/dist/web-app-options.d.ts.map +1 -1
- package/dist/web-app-options.js +1 -1
- package/dist/web-app-options.js.map +1 -1
- package/dist/work-graph.d.ts.map +1 -1
- package/dist/work-graph.js +20 -8
- package/dist/work-graph.js.map +1 -1
- package/docs/examples.json +54 -6
- package/docs/llms-full.txt +1539 -138
- package/docs/types.json +1456 -206
- package/package.json +11 -12
- package/src/agent-files.ts +203 -0
- package/src/app-files.ts +34 -4
- package/src/assure-hydration.ts +15 -2
- package/src/capabilities-tool.ts +15 -3
- package/src/check-diagnostics.ts +73 -9
- package/src/check-scan.ts +1773 -381
- package/src/check.ts +237 -76
- package/src/cli.ts +141 -5
- package/src/client-module.ts +61 -0
- package/src/command-catalog.ts +181 -47
- package/src/component-check.ts +79 -0
- package/src/contract-proof.ts +3 -25
- package/src/contracts.ts +60 -3
- package/src/db-tool.ts +5 -2
- package/src/dev-bun-config.ts +51 -10
- package/src/diagnostics.ts +3 -0
- package/src/diff-tool.ts +33 -26
- package/src/doctor.ts +43 -21
- package/src/env-file.ts +7 -0
- package/src/fix-recipes.ts +34 -3
- package/src/hand-built-manifest.ts +144 -0
- package/src/hooks-report.ts +135 -0
- package/src/init-agents.ts +17 -11
- package/src/internal/nifra-project.ts +191 -0
- package/src/internal/project-specifiers.ts +424 -0
- package/src/internal/script-kind.ts +2 -1
- package/src/internal/source-facts.ts +25 -0
- package/src/internal/typescript-import.ts +10 -3
- package/src/introspect.ts +65 -27
- package/src/levels-tool.ts +117 -20
- package/src/load.ts +78 -14
- package/src/mcp-context.ts +7 -7
- package/src/mcp-exec.ts +60 -17
- package/src/mcp-http.ts +1 -1
- package/src/mcp-io.ts +41 -0
- package/src/mcp-isolate.ts +9 -2
- package/src/mcp-project-child.ts +1 -1
- package/src/mcp-root.ts +58 -8
- package/src/mcp-ws.ts +4 -2
- package/src/mcp.ts +8 -3
- package/src/migrate-layout.ts +1109 -70
- package/src/output-schemas.ts +1084 -0
- package/src/port.ts +3 -2
- package/src/project-facts.ts +17 -3
- package/src/recipes/4.0.0.ts +2 -0
- package/src/recipes/5.0.0.ts +35 -0
- package/src/recipes/index.ts +10 -0
- package/src/replay.ts +14 -3
- package/src/review.ts +39 -10
- package/src/rules/codes.ts +10 -0
- package/src/rules/comment-markers.ts +57 -21
- package/src/rules/contract-visibility.ts +383 -0
- package/src/rules/data-guard.ts +351 -42
- package/src/rules/layout.ts +22 -0
- package/src/rules/legacy.ts +184 -51
- package/src/rules/pages.ts +238 -3
- package/src/rules/routes.ts +124 -16
- package/src/rules/secrets.ts +2 -2
- package/src/rules/security.ts +352 -71
- package/src/rules/zones.ts +205 -38
- package/src/scaffold.ts +6 -4
- package/src/sdk.ts +14 -5
- package/src/stylex-migrate.ts +134 -7
- package/src/sync-manifest.ts +48 -18
- package/src/test-tool.ts +18 -3
- package/src/typecheck-coverage.ts +134 -0
- package/src/upgrade.ts +487 -71
- package/src/verification-plan.ts +24 -0
- package/src/version-drift.ts +67 -0
- package/src/web-app-options.ts +2 -2
- package/src/work-graph.ts +17 -12
package/dist/upgrade.js
CHANGED
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
*
|
|
6
6
|
* 1. **pin sweep** - set every matching dependency to the target version across the workspace's
|
|
7
7
|
* package.json files, preserving the caret/tilde/exact style and skipping `workspace:`/`link:` specs.
|
|
8
|
+
* `overrides`, `resolutions` and `pnpm.overrides` are swept too, since they decide what installs;
|
|
9
|
+
* a version pinned anywhere else (an MCP config, a Dockerfile, another lockfile) is listed only.
|
|
8
10
|
* 2. **dependency moves** - replace removed packages with their supported successor without
|
|
9
11
|
* reserializing package.json or leaving duplicate dependency keys.
|
|
10
12
|
* 3. **import moves** - rewrite exact import specifiers to their updated module paths.
|
|
@@ -21,10 +23,13 @@
|
|
|
21
23
|
* Deliberately NOT a codemod engine: transforms are string/specifier-level only. Structural (AST)
|
|
22
24
|
* transforms are a future addition - a recipe that needs one is the signal to add the engine, not before.
|
|
23
25
|
*/
|
|
24
|
-
import { readFileSync, writeFileSync } from "node:fs";
|
|
25
|
-
import { join, relative } from "node:path";
|
|
26
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, realpathSync, statSync, writeFileSync, } from "node:fs";
|
|
27
|
+
import { dirname, join, relative, sep } from "node:path";
|
|
28
|
+
import { fileURLToPath } from "node:url";
|
|
26
29
|
import { Glob } from "bun";
|
|
30
|
+
import { collectHookReport, diffHookReports, HOOKS_BEFORE_UPGRADE_FILE, parseHookReport, renderHookDiff, } from "./hooks-report.js";
|
|
27
31
|
import { getRecipe, listRecipeVersions } from "./recipes/index.js";
|
|
32
|
+
import { markerTarget } from "./rules/comment-markers.js";
|
|
28
33
|
const DEP_FIELDS = [
|
|
29
34
|
"dependencies",
|
|
30
35
|
"devDependencies",
|
|
@@ -131,71 +136,155 @@ export function pinSweepText(text, rules, allowDowngrade = false, exact = false)
|
|
|
131
136
|
let out = text;
|
|
132
137
|
const changes = [];
|
|
133
138
|
const downgrades = [];
|
|
139
|
+
for (const { path, key, target: name, spec: rawSpec } of pinEntries(parsed)) {
|
|
140
|
+
const field = path.join(".");
|
|
141
|
+
const rule = rules.find((r) => name === r.match || (r.match.endsWith("/") && name.startsWith(r.match)));
|
|
142
|
+
if (!rule)
|
|
143
|
+
continue;
|
|
144
|
+
const next = rewriteVersionSpec(rawSpec, rule.to, exact);
|
|
145
|
+
if (next === null)
|
|
146
|
+
continue;
|
|
147
|
+
// Refuse a rollback: `upgrade <version>` pins an exact target, so an OLD target on a newer install
|
|
148
|
+
// walks dependencies backward (e.g. ^2.3.0 → ^2.0.0) and can break a shared-package peer range.
|
|
149
|
+
const current = specVersionTuple(rawSpec);
|
|
150
|
+
const target = specVersionTuple(rule.to);
|
|
151
|
+
if (!allowDowngrade &&
|
|
152
|
+
current !== null &&
|
|
153
|
+
target !== null &&
|
|
154
|
+
compareSemverSpec(rawSpec, rule.to) > 0) {
|
|
155
|
+
downgrades.push({ field, name: key, from: rawSpec, to: next });
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
// Replace exactly `"key"<ws>:<ws>"spec"` inside this field's object, preserving the whitespace. A
|
|
159
|
+
// nested `overrides` entry repeated with the same spec maps the same way, so every one is replaced.
|
|
160
|
+
const range = objectRangeAt(out, path);
|
|
161
|
+
if (range === undefined)
|
|
162
|
+
continue;
|
|
163
|
+
const pattern = new RegExp(`("${escapeRegExp(key)}"\\s*:\\s*")${escapeRegExp(rawSpec)}(")`, "g");
|
|
164
|
+
const body = out.slice(range.start, range.end);
|
|
165
|
+
const replaced = body.replace(pattern, `$1${next}$2`);
|
|
166
|
+
if (replaced !== body) {
|
|
167
|
+
out = out.slice(0, range.start) + replaced + out.slice(range.end);
|
|
168
|
+
changes.push({ field, name: key, from: rawSpec, to: next });
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return { text: out, changes, downgrades };
|
|
172
|
+
}
|
|
173
|
+
/** Where a package manager forces one version over every range: npm and Bun `overrides` (nested by
|
|
174
|
+
* parent), Yarn and Bun `resolutions`, pnpm `pnpm.overrides`. */
|
|
175
|
+
const OVERRIDE_FIELDS = [
|
|
176
|
+
["overrides"],
|
|
177
|
+
["resolutions"],
|
|
178
|
+
["pnpm", "overrides"],
|
|
179
|
+
];
|
|
180
|
+
/**
|
|
181
|
+
* The package an override key pins: `@nifrajs/core`, `parent/@nifrajs/core` and the same behind a `**`
|
|
182
|
+
* glob (resolutions), `parent>@nifrajs/core` and `@nifrajs/core@<4` (pnpm).
|
|
183
|
+
*/
|
|
184
|
+
function overrideTarget(key) {
|
|
185
|
+
const last = key.slice(key.lastIndexOf(">") + 1);
|
|
186
|
+
return /(?:^|\/)((?:@[^/@]+\/)?[^/@]+)(?:@[^/]*)?$/.exec(last)?.[1] ?? last;
|
|
187
|
+
}
|
|
188
|
+
/** Every string version spec a package.json declares for a package, with the field path it sits in. */
|
|
189
|
+
function pinEntries(parsed) {
|
|
190
|
+
const entries = [];
|
|
134
191
|
for (const field of DEP_FIELDS) {
|
|
135
192
|
const deps = parsed[field];
|
|
136
193
|
if (typeof deps !== "object" || deps === null)
|
|
137
194
|
continue;
|
|
138
|
-
for (const [name,
|
|
139
|
-
if (typeof
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
compareSemverSpec(rawSpec, rule.to) > 0) {
|
|
155
|
-
downgrades.push({ field, name, from: rawSpec, to: next });
|
|
156
|
-
continue;
|
|
195
|
+
for (const [name, spec] of Object.entries(deps))
|
|
196
|
+
if (typeof spec === "string")
|
|
197
|
+
entries.push({ path: [field], key: name, target: name, spec });
|
|
198
|
+
}
|
|
199
|
+
for (const path of OVERRIDE_FIELDS) {
|
|
200
|
+
let value = parsed;
|
|
201
|
+
for (const key of path)
|
|
202
|
+
value = typeof value === "object" && value !== null ? Reflect.get(value, key) : undefined;
|
|
203
|
+
// npm nests an override under the package whose dependency it pins; `.` pins that package itself.
|
|
204
|
+
const walk = (object, parent) => {
|
|
205
|
+
for (const [key, spec] of Object.entries(object)) {
|
|
206
|
+
const target = key === "." ? parent : overrideTarget(key);
|
|
207
|
+
if (typeof spec === "string" && target !== undefined)
|
|
208
|
+
entries.push({ path, key, target, spec });
|
|
209
|
+
else if (typeof spec === "object" && spec !== null && !Array.isArray(spec))
|
|
210
|
+
walk(spec, target);
|
|
157
211
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
if (replaced !== out) {
|
|
162
|
-
out = replaced;
|
|
163
|
-
changes.push({ field, name, from: rawSpec, to: next });
|
|
164
|
-
}
|
|
165
|
-
}
|
|
212
|
+
};
|
|
213
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value))
|
|
214
|
+
walk(value, undefined);
|
|
166
215
|
}
|
|
167
|
-
return
|
|
216
|
+
return entries;
|
|
168
217
|
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
218
|
+
/** The body (between the braces) of the object at `path` in a JSON text: top-level key first, then
|
|
219
|
+
* each nested one, never a same-named key deeper in the document. */
|
|
220
|
+
function objectRangeAt(text, path) {
|
|
221
|
+
const rootOpen = text.indexOf("{");
|
|
222
|
+
if (rootOpen === -1)
|
|
172
223
|
return undefined;
|
|
173
|
-
|
|
224
|
+
let range = {
|
|
225
|
+
start: rootOpen + 1,
|
|
226
|
+
end: closingBrace(text, rootOpen),
|
|
227
|
+
};
|
|
228
|
+
for (const key of path) {
|
|
229
|
+
if (range === undefined)
|
|
230
|
+
return undefined;
|
|
231
|
+
range = memberObjectRange(text, range, key);
|
|
232
|
+
}
|
|
233
|
+
return range;
|
|
234
|
+
}
|
|
235
|
+
/** The index of the `"` that closes the string opening at `open`. */
|
|
236
|
+
function stringEnd(text, open) {
|
|
237
|
+
for (let index = open + 1; index < text.length; index++) {
|
|
238
|
+
if (text[index] === "\\")
|
|
239
|
+
index++;
|
|
240
|
+
else if (text[index] === '"')
|
|
241
|
+
return index;
|
|
242
|
+
}
|
|
243
|
+
return text.length;
|
|
244
|
+
}
|
|
245
|
+
/** The index of the brace that closes the one at `open`. */
|
|
246
|
+
function closingBrace(text, open) {
|
|
174
247
|
let depth = 0;
|
|
175
|
-
let quoted = false;
|
|
176
|
-
let escaped = false;
|
|
177
248
|
for (let index = open; index < text.length; index++) {
|
|
178
249
|
const char = text[index];
|
|
179
|
-
if (quoted) {
|
|
180
|
-
if (escaped)
|
|
181
|
-
escaped = false;
|
|
182
|
-
else if (char === "\\")
|
|
183
|
-
escaped = true;
|
|
184
|
-
else if (char === '"')
|
|
185
|
-
quoted = false;
|
|
186
|
-
continue;
|
|
187
|
-
}
|
|
188
250
|
if (char === '"')
|
|
189
|
-
|
|
190
|
-
else if (char === "{")
|
|
251
|
+
index = stringEnd(text, index);
|
|
252
|
+
else if (char === "{" || char === "[")
|
|
253
|
+
depth++;
|
|
254
|
+
else if ((char === "}" || char === "]") && --depth === 0)
|
|
255
|
+
return index;
|
|
256
|
+
}
|
|
257
|
+
return text.length;
|
|
258
|
+
}
|
|
259
|
+
/** The body of `key`'s object value among the direct members of the object body `within`. */
|
|
260
|
+
function memberObjectRange(text, within, key) {
|
|
261
|
+
let depth = 0;
|
|
262
|
+
for (let index = within.start; index < within.end; index++) {
|
|
263
|
+
const char = text[index];
|
|
264
|
+
if (char === "{" || char === "[")
|
|
191
265
|
depth++;
|
|
192
|
-
else if (char === "}"
|
|
193
|
-
|
|
266
|
+
else if (char === "}" || char === "]")
|
|
267
|
+
depth--;
|
|
268
|
+
else if (char === '"') {
|
|
269
|
+
const close = stringEnd(text, index);
|
|
270
|
+
let after = close + 1;
|
|
271
|
+
while (/\s/.test(text[after] ?? ""))
|
|
272
|
+
after++;
|
|
273
|
+
if (depth === 0 && text[after] === ":" && text.slice(index + 1, close) === key) {
|
|
274
|
+
after++;
|
|
275
|
+
while (/\s/.test(text[after] ?? ""))
|
|
276
|
+
after++;
|
|
277
|
+
return text[after] === "{"
|
|
278
|
+
? { start: after + 1, end: closingBrace(text, after) }
|
|
279
|
+
: undefined;
|
|
280
|
+
}
|
|
281
|
+
index = close;
|
|
282
|
+
}
|
|
194
283
|
}
|
|
195
284
|
return undefined;
|
|
196
285
|
}
|
|
197
286
|
function rewriteDependencyEntry(text, field, from, fromVersion, to, toVersion) {
|
|
198
|
-
const range =
|
|
287
|
+
const range = objectRangeAt(text, [field]);
|
|
199
288
|
if (range === undefined)
|
|
200
289
|
return text;
|
|
201
290
|
const body = text.slice(range.start, range.end);
|
|
@@ -308,12 +397,14 @@ const isIgnored = (path) => path
|
|
|
308
397
|
.split(/[\\/]/)
|
|
309
398
|
.slice(0, -1)
|
|
310
399
|
.some((segment) => IGNORED_DIRECTORIES.has(segment));
|
|
311
|
-
function scan(cwd, pattern) {
|
|
400
|
+
function scan(cwd, pattern, dot = false) {
|
|
312
401
|
const glob = new Glob(pattern);
|
|
313
402
|
const out = [];
|
|
314
|
-
|
|
403
|
+
// The write boundary: a symlinked file or directory (a linked repository) is never yielded, so the
|
|
404
|
+
// upgrade can only edit files that live under `cwd`.
|
|
405
|
+
for (const path of glob.scanSync({ cwd, dot, followSymlinks: false })) {
|
|
315
406
|
if (!isIgnored(path))
|
|
316
|
-
out.push(path);
|
|
407
|
+
out.push(path.replaceAll("\\", "/"));
|
|
317
408
|
}
|
|
318
409
|
return out.sort(); // deterministic order
|
|
319
410
|
}
|
|
@@ -325,11 +416,12 @@ const groupPins = (version) => [
|
|
|
325
416
|
{ match: "nifra", to: version },
|
|
326
417
|
];
|
|
327
418
|
/**
|
|
328
|
-
*
|
|
329
|
-
*
|
|
419
|
+
* Every fixed-group version the package.json files under `cwd` declare with a plain semver spec, each
|
|
420
|
+
* with the files that declare it. An override counts: it decides what installs. `workspace:`, `link:`
|
|
421
|
+
* and other non-semver specs are not versions.
|
|
330
422
|
*/
|
|
331
|
-
export function
|
|
332
|
-
|
|
423
|
+
export function groupPinVersions(cwd) {
|
|
424
|
+
const versions = new Map();
|
|
333
425
|
for (const rel of scan(cwd, "**/package.json")) {
|
|
334
426
|
let parsed;
|
|
335
427
|
try {
|
|
@@ -338,19 +430,131 @@ export function installedGroupVersion(cwd) {
|
|
|
338
430
|
catch {
|
|
339
431
|
continue;
|
|
340
432
|
}
|
|
341
|
-
for (const
|
|
342
|
-
|
|
343
|
-
if (typeof deps !== "object" || deps === null)
|
|
433
|
+
for (const { target, spec } of pinEntries(parsed)) {
|
|
434
|
+
if (!isFixedGroup(target))
|
|
344
435
|
continue;
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
436
|
+
const version = specVersion(spec);
|
|
437
|
+
if (version === null)
|
|
438
|
+
continue;
|
|
439
|
+
const files = versions.get(version) ?? [];
|
|
440
|
+
if (!files.includes(rel))
|
|
441
|
+
files.push(rel);
|
|
442
|
+
versions.set(version, files);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
return versions;
|
|
446
|
+
}
|
|
447
|
+
const STRAY_PIN_FILES = [
|
|
448
|
+
"**/{.mcp.json,mcp.json}",
|
|
449
|
+
"**/{Dockerfile,Dockerfile.*,*.Dockerfile,*.dockerfile,Containerfile}",
|
|
450
|
+
"**/{docker-compose,compose}*.{yml,yaml}",
|
|
451
|
+
".github/workflows/*.{yml,yaml}",
|
|
452
|
+
".gitlab-ci.yml",
|
|
453
|
+
"**/*.lock",
|
|
454
|
+
"**/{package-lock.json,pnpm-lock.yaml}",
|
|
455
|
+
];
|
|
456
|
+
const LOCKFILE = /(?:\.lock|package-lock\.json|pnpm-lock\.yaml)$/;
|
|
457
|
+
const GROUP_PIN = /(?<![\w.@/-])(@nifrajs\/[a-z0-9._-]+|create-nifra|nifra)@(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)/g;
|
|
458
|
+
/**
|
|
459
|
+
* Fixed-group versions other than `version` pinned where `nifra upgrade` does not edit: MCP configs,
|
|
460
|
+
* container files, CI workflows, and lockfiles besides the workspace's own (which install rewrites).
|
|
461
|
+
*/
|
|
462
|
+
export function strayPins(cwd, version) {
|
|
463
|
+
const found = [];
|
|
464
|
+
const files = new Set(STRAY_PIN_FILES.flatMap((pattern) => scan(cwd, pattern, true)));
|
|
465
|
+
for (const rel of [...files].sort()) {
|
|
466
|
+
const lockfile = LOCKFILE.test(rel);
|
|
467
|
+
if (lockfile && !/[\\/]/.test(rel))
|
|
468
|
+
continue;
|
|
469
|
+
let text;
|
|
470
|
+
try {
|
|
471
|
+
if (statSync(join(cwd, rel)).size > 8_000_000)
|
|
472
|
+
continue;
|
|
473
|
+
text = readFileSync(join(cwd, rel), "utf8");
|
|
474
|
+
}
|
|
475
|
+
catch {
|
|
476
|
+
continue;
|
|
477
|
+
}
|
|
478
|
+
const stale = [...text.matchAll(GROUP_PIN)].filter((match) => compareSemverSpec(match[2] ?? "", version) !== 0);
|
|
479
|
+
const site = (match, more) => ({
|
|
480
|
+
file: rel,
|
|
481
|
+
line: text.slice(0, match.index ?? 0).split("\n").length,
|
|
482
|
+
pin: match[0],
|
|
483
|
+
lockfile,
|
|
484
|
+
more,
|
|
485
|
+
});
|
|
486
|
+
const first = stale[0];
|
|
487
|
+
if (first === undefined)
|
|
488
|
+
continue;
|
|
489
|
+
if (lockfile)
|
|
490
|
+
found.push(site(first, stale.length - 1));
|
|
491
|
+
else
|
|
492
|
+
for (const match of stale)
|
|
493
|
+
found.push(site(match, 0));
|
|
494
|
+
}
|
|
495
|
+
return found;
|
|
496
|
+
}
|
|
497
|
+
const packageVersionAt = (dir) => {
|
|
498
|
+
try {
|
|
499
|
+
const meta = JSON.parse(readFileSync(join(dir, "package.json"), "utf8"));
|
|
500
|
+
const version = Reflect.get(Object(meta), "version");
|
|
501
|
+
return typeof version === "string" && version.length > 0 ? version : undefined;
|
|
502
|
+
}
|
|
503
|
+
catch {
|
|
504
|
+
return undefined;
|
|
505
|
+
}
|
|
506
|
+
};
|
|
507
|
+
/**
|
|
508
|
+
* Every fixed-group package installed in `cwd/node_modules`, realpath-resolved so a linked repository
|
|
509
|
+
* reports its own version. `inside` is false for one that resolves outside the workspace root.
|
|
510
|
+
*/
|
|
511
|
+
export function installedGroupPackages(cwd) {
|
|
512
|
+
const modules = join(cwd, "node_modules");
|
|
513
|
+
const names = ["nifra", "create-nifra"].filter((name) => existsSync(join(modules, name)));
|
|
514
|
+
try {
|
|
515
|
+
for (const name of readdirSync(join(modules, "@nifrajs")))
|
|
516
|
+
names.push(`@nifrajs/${name}`);
|
|
517
|
+
}
|
|
518
|
+
catch {
|
|
519
|
+
// No scoped installs.
|
|
520
|
+
}
|
|
521
|
+
let root;
|
|
522
|
+
try {
|
|
523
|
+
root = realpathSync(cwd);
|
|
524
|
+
}
|
|
525
|
+
catch {
|
|
526
|
+
root = cwd;
|
|
527
|
+
}
|
|
528
|
+
const found = [];
|
|
529
|
+
for (const name of names) {
|
|
530
|
+
let path;
|
|
531
|
+
try {
|
|
532
|
+
path = realpathSync(join(modules, name));
|
|
533
|
+
}
|
|
534
|
+
catch {
|
|
535
|
+
continue;
|
|
353
536
|
}
|
|
537
|
+
const version = packageVersionAt(path);
|
|
538
|
+
if (version === undefined)
|
|
539
|
+
continue;
|
|
540
|
+
found.push({
|
|
541
|
+
package: name,
|
|
542
|
+
path,
|
|
543
|
+
version,
|
|
544
|
+
inside: path === root || path.startsWith(root + sep),
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
return found;
|
|
548
|
+
}
|
|
549
|
+
/**
|
|
550
|
+
* The lowest fixed-group version any package.json under `cwd` declares with a plain semver spec, or
|
|
551
|
+
* null when none does. Recipes newer than it still have edits to make somewhere in the workspace.
|
|
552
|
+
*/
|
|
553
|
+
export function installedGroupVersion(cwd) {
|
|
554
|
+
let lowest = null;
|
|
555
|
+
for (const version of groupPinVersions(cwd).keys()) {
|
|
556
|
+
if (lowest === null || compareSemverSpec(version, lowest) < 0)
|
|
557
|
+
lowest = version;
|
|
354
558
|
}
|
|
355
559
|
return lowest;
|
|
356
560
|
}
|
|
@@ -379,6 +583,7 @@ export function chainRecipes(target, installed) {
|
|
|
379
583
|
pins: [...pins].map(([match, to]) => ({ match, to })),
|
|
380
584
|
dependencyMoves: recipes.flatMap((recipe) => recipe.dependencyMoves ?? []),
|
|
381
585
|
importMoves: recipes.flatMap((recipe) => recipe.importMoves),
|
|
586
|
+
retiredMarkers: recipes.flatMap((recipe) => recipe.retiredMarkers ?? []),
|
|
382
587
|
notes: recipes.flatMap((recipe) => (recipe.notes ?? []).map((note) => `${recipe.version}: ${note}`)),
|
|
383
588
|
};
|
|
384
589
|
}
|
|
@@ -388,6 +593,10 @@ export function computeUpgrade(cwd, recipe, write, allowDowngrade = false, exact
|
|
|
388
593
|
const dependencyMoves = [];
|
|
389
594
|
const importMoves = [];
|
|
390
595
|
const downgrades = [];
|
|
596
|
+
const retiredMarkers = [];
|
|
597
|
+
const linkedOutside = installedGroupPackages(cwd)
|
|
598
|
+
.filter((entry) => !entry.inside && entry.version !== recipe.version)
|
|
599
|
+
.map(({ package: name, path, version }) => ({ package: name, path, version }));
|
|
391
600
|
if ((recipe.dependencyMoves?.length ?? 0) > 0 || recipe.pins.length > 0) {
|
|
392
601
|
for (const rel of scan(cwd, "**/package.json")) {
|
|
393
602
|
const abs = join(cwd, rel);
|
|
@@ -417,12 +626,36 @@ export function computeUpgrade(cwd, recipe, write, allowDowngrade = false, exact
|
|
|
417
626
|
writeFileSync(abs, result.text);
|
|
418
627
|
}
|
|
419
628
|
}
|
|
629
|
+
// Read after the import moves, so a listed line number is the one the file has after `--write`.
|
|
630
|
+
const retired = recipe.retiredMarkers ?? [];
|
|
631
|
+
if (retired.length > 0) {
|
|
632
|
+
for (const rel of scan(cwd, SOURCE_GLOB)) {
|
|
633
|
+
const text = readFileSync(join(cwd, rel), "utf8");
|
|
634
|
+
if (!retired.some(({ marker }) => text.includes(marker)))
|
|
635
|
+
continue;
|
|
636
|
+
const lines = text.split("\n");
|
|
637
|
+
for (let line = 1; line <= lines.length; line++) {
|
|
638
|
+
for (const { marker, instead } of retired) {
|
|
639
|
+
if (!(lines[line - 1] ?? "").includes(marker))
|
|
640
|
+
continue;
|
|
641
|
+
const target = markerTarget(lines, line);
|
|
642
|
+
const covers = target === undefined
|
|
643
|
+
? undefined
|
|
644
|
+
: { line: target, text: (lines[target - 1] ?? "").trim() };
|
|
645
|
+
retiredMarkers.push({ file: rel, line, marker, covers, instead });
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
}
|
|
420
650
|
return {
|
|
421
651
|
version: recipe.version,
|
|
422
652
|
pins,
|
|
423
653
|
dependencyMoves,
|
|
424
654
|
importMoves,
|
|
425
655
|
downgrades,
|
|
656
|
+
retiredMarkers,
|
|
657
|
+
linkedOutside,
|
|
658
|
+
strayPins: strayPins(cwd, recipe.version),
|
|
426
659
|
notes: recipe.notes ?? [],
|
|
427
660
|
};
|
|
428
661
|
}
|
|
@@ -438,7 +671,10 @@ function renderPlan(plan, write, from, steps) {
|
|
|
438
671
|
lines.push("");
|
|
439
672
|
if (plan.pins.length === 0 &&
|
|
440
673
|
plan.dependencyMoves.length === 0 &&
|
|
441
|
-
plan.importMoves.length === 0
|
|
674
|
+
plan.importMoves.length === 0 &&
|
|
675
|
+
plan.retiredMarkers.length === 0 &&
|
|
676
|
+
plan.linkedOutside.length === 0 &&
|
|
677
|
+
plan.strayPins.length === 0) {
|
|
442
678
|
lines.push("Already up to date - no changes.");
|
|
443
679
|
return lines.join("\n");
|
|
444
680
|
}
|
|
@@ -465,6 +701,39 @@ function renderPlan(plan, write, from, steps) {
|
|
|
465
701
|
lines.push(` ${m.file} ${m.from} → ${m.to} (${m.count})`);
|
|
466
702
|
lines.push("");
|
|
467
703
|
}
|
|
704
|
+
if (plan.retiredMarkers.length > 0) {
|
|
705
|
+
lines.push(`${plan.retiredMarkers.length} retired comment(s) that silence nothing now; nifra check reports what each covered:`);
|
|
706
|
+
for (const site of plan.retiredMarkers) {
|
|
707
|
+
const covers = site.covers === undefined
|
|
708
|
+
? "covers no line"
|
|
709
|
+
: `covers line ${site.covers.line}: ${site.covers.text}`;
|
|
710
|
+
lines.push(` ${site.file}:${site.line} ${site.marker} ${covers}`);
|
|
711
|
+
lines.push(` write instead: ${site.instead}`);
|
|
712
|
+
}
|
|
713
|
+
lines.push("");
|
|
714
|
+
}
|
|
715
|
+
if (plan.linkedOutside.length > 0) {
|
|
716
|
+
lines.push("Left alone, linked from outside the workspace root (upgrade that repository on its own):");
|
|
717
|
+
for (const entry of plan.linkedOutside)
|
|
718
|
+
lines.push(` ${entry.package} ${entry.version} at ${entry.path}`);
|
|
719
|
+
lines.push("");
|
|
720
|
+
}
|
|
721
|
+
const pinned = plan.strayPins.filter((site) => !site.lockfile);
|
|
722
|
+
if (pinned.length > 0) {
|
|
723
|
+
lines.push(`Left alone, another version pinned outside package.json (update by hand):`);
|
|
724
|
+
for (const site of pinned)
|
|
725
|
+
lines.push(` ${site.file}:${site.line} ${site.pin}`);
|
|
726
|
+
lines.push("");
|
|
727
|
+
}
|
|
728
|
+
const locks = plan.strayPins.filter((site) => site.lockfile);
|
|
729
|
+
if (locks.length > 0) {
|
|
730
|
+
lines.push(`Left alone, a lockfile besides the workspace's own (regenerate it after installing):`);
|
|
731
|
+
for (const site of locks) {
|
|
732
|
+
const more = site.more === 0 ? "" : ` and ${site.more} more`;
|
|
733
|
+
lines.push(` ${site.file}:${site.line} ${site.pin}${more}`);
|
|
734
|
+
}
|
|
735
|
+
lines.push("");
|
|
736
|
+
}
|
|
468
737
|
for (const note of plan.notes)
|
|
469
738
|
lines.push(`note: ${note}`);
|
|
470
739
|
if (!write)
|
|
@@ -483,6 +752,63 @@ function renderDowngradeRefusal(version, downgrades) {
|
|
|
483
752
|
"installed one, or pass --allow-downgrade if the rollback is intended.",
|
|
484
753
|
].join("\n");
|
|
485
754
|
}
|
|
755
|
+
async function hookReportBefore(cwd) {
|
|
756
|
+
try {
|
|
757
|
+
const { loadBackendApp } = await import("./load.js");
|
|
758
|
+
const { backend } = await loadBackendApp(cwd);
|
|
759
|
+
return await collectHookReport(backend);
|
|
760
|
+
}
|
|
761
|
+
catch (error) {
|
|
762
|
+
return { error: error instanceof Error ? error.message : String(error) };
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
/** The report after the edits, from a fresh process: the edited sources must not come from this one's
|
|
766
|
+
* module cache. Saves the pre-upgrade report for `nifra routes --hooks --since` after an install. */
|
|
767
|
+
async function hookScopeAcross(cwd, before) {
|
|
768
|
+
if ("error" in before)
|
|
769
|
+
return { error: before.error };
|
|
770
|
+
const saved = join(cwd, HOOKS_BEFORE_UPGRADE_FILE);
|
|
771
|
+
mkdirSync(dirname(saved), { recursive: true });
|
|
772
|
+
writeFileSync(saved, `${JSON.stringify(before, null, 2)}\n`);
|
|
773
|
+
const entry = fileURLToPath(new URL(import.meta.url.endsWith(".ts") ? "./cli.ts" : "./cli.js", import.meta.url));
|
|
774
|
+
const child = Bun.spawn([process.execPath, entry, "routes", "--hooks", "--json"], {
|
|
775
|
+
cwd,
|
|
776
|
+
env: process.env,
|
|
777
|
+
stdout: "pipe",
|
|
778
|
+
stderr: "pipe",
|
|
779
|
+
});
|
|
780
|
+
const [text, , code] = await Promise.all([
|
|
781
|
+
new Response(child.stdout).text(),
|
|
782
|
+
new Response(child.stderr).text(),
|
|
783
|
+
child.exited,
|
|
784
|
+
]);
|
|
785
|
+
const after = parseHookReport(text);
|
|
786
|
+
if (after === undefined) {
|
|
787
|
+
return {
|
|
788
|
+
error: `\`nifra routes --hooks\` exited ${code} after the edits`,
|
|
789
|
+
saved: HOOKS_BEFORE_UPGRADE_FILE,
|
|
790
|
+
};
|
|
791
|
+
}
|
|
792
|
+
return { saved: HOOKS_BEFORE_UPGRADE_FILE, changes: diffHookReports(before, after) };
|
|
793
|
+
}
|
|
794
|
+
function renderHookScope(scope) {
|
|
795
|
+
const since = (saved) => [
|
|
796
|
+
"The new release's hooks run once its packages are installed. After installing, compare against the report saved before the edits:",
|
|
797
|
+
` nifra routes --hooks --since ${saved}`,
|
|
798
|
+
];
|
|
799
|
+
if ("error" in scope) {
|
|
800
|
+
const head = `Hook scope: not captured (${scope.error}).`;
|
|
801
|
+
return scope.saved === undefined ? head : [head, ...since(scope.saved)].join("\n");
|
|
802
|
+
}
|
|
803
|
+
return [
|
|
804
|
+
"Hook scope across the edits (nifra routes --hooks):",
|
|
805
|
+
renderHookDiff(scope.changes)
|
|
806
|
+
.split("\n")
|
|
807
|
+
.map((line) => ` ${line}`)
|
|
808
|
+
.join("\n"),
|
|
809
|
+
...since(scope.saved),
|
|
810
|
+
].join("\n");
|
|
811
|
+
}
|
|
486
812
|
/** The targets this CLI applies: each release recipe up to its own version, and that version. */
|
|
487
813
|
export function upgradeTargets(cliVersion) {
|
|
488
814
|
const versions = listRecipeVersions().filter((version) => compareSemverSpec(version, cliVersion) <= 0);
|
|
@@ -497,6 +823,7 @@ function newerCliCommand(version, options) {
|
|
|
497
823
|
options.exact === true ? "--exact" : "",
|
|
498
824
|
options.allowDowngrade === true ? "--allow-downgrade" : "",
|
|
499
825
|
options.verify === false ? "--no-verify" : "",
|
|
826
|
+
options.hooks === false ? "--no-hooks" : "",
|
|
500
827
|
options.json === true ? "--json" : "",
|
|
501
828
|
].filter((flag) => flag !== "");
|
|
502
829
|
return ["bunx", `@nifrajs/cli@${version}`, "upgrade", version, ...flags].join(" ");
|
|
@@ -565,20 +892,36 @@ export async function runUpgrade(cwd, options) {
|
|
|
565
892
|
}
|
|
566
893
|
return false;
|
|
567
894
|
}
|
|
895
|
+
const captureHooks = write && options.hooks !== false;
|
|
896
|
+
const before = captureHooks ? await hookReportBefore(cwd) : undefined;
|
|
568
897
|
const plan = write ? computeUpgrade(cwd, recipe, true, allowDowngrade, exact) : preview;
|
|
898
|
+
const hooks = before === undefined ? undefined : await hookScopeAcross(cwd, before);
|
|
899
|
+
// Verify only makes sense once edits are on disk. Default on after --write; opt out with --no-verify.
|
|
900
|
+
const verify = write && options.verify !== false;
|
|
569
901
|
if (options.json) {
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
902
|
+
// The check result rides inside the one JSON document; its human report would break the stream.
|
|
903
|
+
const report = { ...plan, from, steps: recipe.steps, write };
|
|
904
|
+
if (hooks !== undefined)
|
|
905
|
+
report.hooks = hooks;
|
|
906
|
+
let ok = true;
|
|
907
|
+
if (verify) {
|
|
908
|
+
const { collectProjectVerification } = await import("./verification.js");
|
|
909
|
+
const verification = await collectProjectVerification(cwd, { lintsOnly: false });
|
|
910
|
+
const check = await verification.check();
|
|
911
|
+
report.check = check;
|
|
912
|
+
ok = check.ok;
|
|
913
|
+
}
|
|
914
|
+
console.log(JSON.stringify(report, null, 2));
|
|
915
|
+
return ok;
|
|
574
916
|
}
|
|
575
|
-
|
|
576
|
-
if (
|
|
917
|
+
console.log(renderPlan(plan, write, from, recipe.steps));
|
|
918
|
+
if (hooks !== undefined)
|
|
919
|
+
console.log(`\n${renderHookScope(hooks)}`);
|
|
920
|
+
if (verify) {
|
|
577
921
|
const { runCheck } = await import("./check.js");
|
|
578
922
|
const ok = await runCheck(cwd, { json: false });
|
|
579
923
|
if (!ok) {
|
|
580
|
-
|
|
581
|
-
console.error("\n[nifra] upgrade applied but `nifra check` failed - review above.");
|
|
924
|
+
console.error("\n[nifra] upgrade applied but `nifra check` failed - review above.");
|
|
582
925
|
return false;
|
|
583
926
|
}
|
|
584
927
|
}
|