rolldown-pnpm-config 1.0.10 → 2.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 +2 -0
- package/bin/rolldown-pnpm-config.js +2 -12
- package/cli/commands/export.js +23 -20
- package/cli/commands/preview.js +23 -20
- package/cli/commands/upgrade.js +273 -181
- package/cli/cwd.js +25 -0
- package/cli/diff/render.js +19 -37
- package/cli/load-config.js +4 -5
- package/cli/main.js +37 -0
- package/cli/preview-views.js +2 -2
- package/cli/render/legend.js +42 -0
- package/cli/render/print.js +50 -0
- package/cli/render/report.js +38 -0
- package/cli/render/tone.js +43 -0
- package/cli/root.js +22 -0
- package/cli/simulated-view.js +12 -53
- package/cli/summary.js +19 -96
- package/cli/ui/preview-screen.js +93 -0
- package/cli/ui/progress-view.js +47 -0
- package/cli/ui/progress.js +31 -0
- package/cli/ui/screens.js +40 -0
- package/cli/ui/walk-screen.js +176 -0
- package/cli/version.js +12 -0
- package/cli/walk-reducer.js +13 -24
- package/package.json +12 -10
- package/cli/ui/Preview.js +0 -65
- package/cli/ui/Walk.js +0 -89
- package/cli/ui/ansi.js +0 -20
- package/cli/ui/env.js +0 -18
- package/cli/ui/legend.js +0 -80
- package/cli/ui/run-preview.js +0 -22
- package/cli/ui/run-walk.js +0 -33
- package/cli/ui/styled.js +0 -36
package/cli/commands/upgrade.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { bareVersion } from "../../semver-util.js";
|
|
2
|
+
import { WorkingDirectory } from "../cwd.js";
|
|
2
3
|
import { evaluatePluginConfig } from "../evaluate.js";
|
|
4
|
+
import { printDoc } from "../render/print.js";
|
|
5
|
+
import { failureDoc, warningDoc } from "../render/report.js";
|
|
3
6
|
import { discoverCatalogEntries } from "../discover.js";
|
|
4
7
|
import { filterEntriesByCatalog, findConfigFiles, pickConfigCandidate } from "../select-file.js";
|
|
5
|
-
import {
|
|
8
|
+
import { walkScreen, withProgress } from "../ui/screens.js";
|
|
6
9
|
import { versionKeyOf } from "../version-key.js";
|
|
7
10
|
import { buildEdits, entryEdits } from "../edits.js";
|
|
8
11
|
import { buildInteropEdits, interopEntryChanged, makePeerFetcher, runInterop } from "../interop.js";
|
|
@@ -12,15 +15,16 @@ import { defaultPick, planEntry } from "../plan.js";
|
|
|
12
15
|
import { parsePnpmGate, readConfigReleaseAge } from "../release-age.js";
|
|
13
16
|
import { RegistryResolver, RegistryResolverLive } from "../resolve.js";
|
|
14
17
|
import { applyEdits } from "../rewrite.js";
|
|
15
|
-
import {
|
|
18
|
+
import { summaryDoc } from "../summary.js";
|
|
16
19
|
import { validateEdits } from "../validate.js";
|
|
17
20
|
import { buildWalkItems } from "../walk-plan.js";
|
|
18
21
|
import { findWorkspaceRoot, makeWorkspaceResolver } from "../workspace-resolve.js";
|
|
19
|
-
import { Data, Effect, Option, Result } from "effect";
|
|
22
|
+
import { Console, Data, Effect, Option, Result, Runtime } from "effect";
|
|
20
23
|
import { readFileSync, writeFileSync } from "node:fs";
|
|
21
24
|
import { dirname } from "node:path";
|
|
22
|
-
import {
|
|
25
|
+
import { CliDoc, CliExit, CliInteractive, CliMessage, Doc } from "@effected/cli";
|
|
23
26
|
import { Argument, Command, Flag } from "effect/cli";
|
|
27
|
+
import { CliUi } from "@effected/cli/ui";
|
|
24
28
|
import { ReleaseAgeGate } from "@effected/npm";
|
|
25
29
|
|
|
26
30
|
//#region src/cli/commands/upgrade.ts
|
|
@@ -29,7 +33,45 @@ import { ReleaseAgeGate } from "@effected/npm";
|
|
|
29
33
|
*
|
|
30
34
|
* @internal
|
|
31
35
|
*/
|
|
32
|
-
var UpgradeError = class extends Data.TaggedError("UpgradeError") {
|
|
36
|
+
var UpgradeError = class extends Data.TaggedError("UpgradeError") {
|
|
37
|
+
/** The failure report `CliRuntime.main` prints: the message, without the class name. */
|
|
38
|
+
[CliDoc]() {
|
|
39
|
+
return failureDoc(this.message);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* An invalid flag combination: a usage error, so it exits 64 (BSD
|
|
44
|
+
* `EX_USAGE`) rather than the 1 a resolution failure exits with.
|
|
45
|
+
*
|
|
46
|
+
* @internal
|
|
47
|
+
*/
|
|
48
|
+
var UpgradeUsageError = class extends Data.TaggedError("UpgradeUsageError") {
|
|
49
|
+
[Runtime.errorExitCode] = 64;
|
|
50
|
+
/** The failure report `CliRuntime.main` prints: the message, without the class name. */
|
|
51
|
+
[CliDoc]() {
|
|
52
|
+
return failureDoc(this.message);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
/** The heading of `--check`'s resolution-failure report. @internal */
|
|
56
|
+
const CHECK_FAILED_LABEL = "Catalog check failed before drift could be evaluated (resolution error, not drift):";
|
|
57
|
+
function indent(message) {
|
|
58
|
+
return message.split("\n").map((l) => ` ${l}`).join("\n");
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* `--check`'s resolution failure: the run could not get far enough to say
|
|
62
|
+
* whether the catalogs drifted. It shares `--check`'s non-zero exit with
|
|
63
|
+
* drift, so its report names the failure family — a gate consuming the exit
|
|
64
|
+
* code reports every non-zero as "drifted", and without the label the CI log
|
|
65
|
+
* would blame drift for a typo'd package or an auth failure.
|
|
66
|
+
*
|
|
67
|
+
* @internal
|
|
68
|
+
*/
|
|
69
|
+
var CheckFailedError = class extends Data.TaggedError("CheckFailedError") {
|
|
70
|
+
/** The failure report: the family label, then the underlying message indented. */
|
|
71
|
+
[CliDoc]() {
|
|
72
|
+
return failureDoc(`${CHECK_FAILED_LABEL}\n${indent(this.message)}`);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
33
75
|
/** Read a config file and statically discover its catalog entries. @internal */
|
|
34
76
|
function readCatalogSource(file) {
|
|
35
77
|
return Effect.gen(function* () {
|
|
@@ -67,9 +109,9 @@ const RESOLVE_CONCURRENCY = 12;
|
|
|
67
109
|
* registry-sourced in another, and the two routes must neither share a version
|
|
68
110
|
* list nor a gate exemption.
|
|
69
111
|
*
|
|
70
|
-
* @param onProgress - Optional
|
|
71
|
-
* `(resolved, total)
|
|
72
|
-
*
|
|
112
|
+
* @param onProgress - Optional reporter run after each package resolves with
|
|
113
|
+
* `(resolved, total)`, for CLI progress feedback. Run with `(0, total)`
|
|
114
|
+
* before any work starts so callers can begin the phase.
|
|
73
115
|
* @param workspace - Optional workspace-backed resolver. An entry whose
|
|
74
116
|
* `source` is `"workspace"` resolves through it instead of the registry, and
|
|
75
117
|
* is EXEMPT from the release-age gate: its next version is unpublished, so
|
|
@@ -85,12 +127,12 @@ function resolveGatedVersions(entries, resolver, gate, now, onProgress, workspac
|
|
|
85
127
|
});
|
|
86
128
|
const total = pairs.size;
|
|
87
129
|
let resolved = 0;
|
|
88
|
-
onProgress
|
|
89
|
-
return Effect.forEach([...pairs], ([key, { pkg, fromWorkspace }]) => Effect.gen(function* () {
|
|
130
|
+
const report = onProgress ?? (() => Effect.void);
|
|
131
|
+
return Effect.andThen(report(0, total), () => Effect.forEach([...pairs], ([key, { pkg, fromWorkspace }]) => Effect.gen(function* () {
|
|
90
132
|
const routed = fromWorkspace && workspace !== void 0 ? workspace : resolver;
|
|
91
133
|
const needTimes = !fromWorkspace && gate.ageMinutes > 0;
|
|
92
134
|
const [vr, times] = yield* Effect.all([routed.versions(pkg).pipe(Effect.result), needTimes ? resolver.times(pkg).pipe(Effect.orElseSucceed(() => ({}))) : Effect.succeed({})], { concurrency: "unbounded" });
|
|
93
|
-
|
|
135
|
+
yield* report(++resolved, total);
|
|
94
136
|
if (Result.isFailure(vr)) return [
|
|
95
137
|
key,
|
|
96
138
|
pkg,
|
|
@@ -113,22 +155,37 @@ function resolveGatedVersions(entries, resolver, gate, now, onProgress, workspac
|
|
|
113
155
|
gated: new Map(rows.map(([key, , gated]) => [key, gated])),
|
|
114
156
|
raw: new Map(rows.map(([key, , , raw]) => [key, raw])),
|
|
115
157
|
unresolved: [...new Set(rows.filter(([, , , raw]) => raw.length === 0).map(([, pkg]) => pkg))]
|
|
116
|
-
})));
|
|
158
|
+
}))));
|
|
117
159
|
}
|
|
118
160
|
/**
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
* Only call when caps.interactive is true; this function is not gated itself.
|
|
161
|
+
* Adapt a progress-event reporter to `resolveGatedVersions`' `(resolved,
|
|
162
|
+
* total)` callback: the first call begins the resolve phase, each later one
|
|
163
|
+
* steps it, and the last finishes it, so the live view commits a
|
|
164
|
+
* "Resolved N packages" line as soon as resolution is done.
|
|
125
165
|
*
|
|
126
166
|
* @internal
|
|
127
167
|
*/
|
|
128
|
-
function
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
168
|
+
function resolveProgress(report) {
|
|
169
|
+
const noun = (n) => `${n} package${n === 1 ? "" : "s"}`;
|
|
170
|
+
const finished = (total) => report({
|
|
171
|
+
_tag: "Finished",
|
|
172
|
+
label: `Resolved ${noun(total)}`
|
|
173
|
+
});
|
|
174
|
+
return (resolved, total) => {
|
|
175
|
+
if (resolved === 0) {
|
|
176
|
+
const begin = report({
|
|
177
|
+
_tag: "Phase",
|
|
178
|
+
label: `Resolving ${noun(total)}`,
|
|
179
|
+
total
|
|
180
|
+
});
|
|
181
|
+
return total === 0 ? Effect.andThen(begin, finished(total)) : begin;
|
|
182
|
+
}
|
|
183
|
+
const step = report({
|
|
184
|
+
_tag: "Step",
|
|
185
|
+
done: resolved
|
|
186
|
+
});
|
|
187
|
+
return resolved === total ? Effect.andThen(step, finished(total)) : step;
|
|
188
|
+
};
|
|
132
189
|
}
|
|
133
190
|
/**
|
|
134
191
|
* Non-interactive upgrade core: read the config, discover catalog entries,
|
|
@@ -275,7 +332,7 @@ function applyInteropAndDecisions(file, source, nonInteropEdits, interopEdits) {
|
|
|
275
332
|
* @internal
|
|
276
333
|
*/
|
|
277
334
|
function nothingToUpgradeMessage(totalItems) {
|
|
278
|
-
return totalItems === 0 ? "Nothing to upgrade — no catalog packages found
|
|
335
|
+
return totalItems === 0 ? "Nothing to upgrade — no catalog packages found." : `Nothing to upgrade — ${totalItems} package(s) already up to date.`;
|
|
279
336
|
}
|
|
280
337
|
/**
|
|
281
338
|
* The message for packages the registry could not resolve. Almost always a
|
|
@@ -320,49 +377,51 @@ function projectDecisions(items, full) {
|
|
|
320
377
|
}
|
|
321
378
|
return out;
|
|
322
379
|
}
|
|
323
|
-
/**
|
|
380
|
+
/**
|
|
381
|
+
* Build the `--preview` projection without writing: the summary document, and
|
|
382
|
+
* the packages the registry could not resolve (which render as up to date and
|
|
383
|
+
* would otherwise be invisible in the projection).
|
|
384
|
+
*
|
|
385
|
+
* @internal
|
|
386
|
+
*/
|
|
324
387
|
function runUpgradePreview(opts) {
|
|
325
388
|
return Effect.gen(function* () {
|
|
326
389
|
const { source, entries } = yield* readCatalogSource(opts.file);
|
|
327
390
|
const gate = yield* computeGate(source, opts.file, opts.resolver);
|
|
328
391
|
const versions = yield* resolveGatedVersions(entries, opts.resolver, gate, Date.now(), void 0, opts.workspaceResolver);
|
|
329
392
|
const items = yield* buildWalkItems(entries, versions.gated).pipe(Effect.mapError((e) => new UpgradeError({ message: e.message })));
|
|
330
|
-
|
|
331
|
-
|
|
393
|
+
return {
|
|
394
|
+
doc: summaryDoc(projectDecisions(items, opts.full)),
|
|
395
|
+
unresolved: versions.unresolved
|
|
396
|
+
};
|
|
332
397
|
});
|
|
333
398
|
}
|
|
334
399
|
/**
|
|
335
|
-
* Map a check run's drift list to
|
|
336
|
-
* gate: exit 0 when every entry is in sync, exit 1 when an
|
|
337
|
-
* would rewrite anything — the exit code IS the contract (a
|
|
338
|
-
* validation phase calls this), and it never writes.
|
|
400
|
+
* Map a check run's drift list to its report and exit code. `--check` is a
|
|
401
|
+
* pure gate: exit 0 when every entry is in sync, exit 1 when an
|
|
402
|
+
* `upgrade --yes` would rewrite anything — the exit code IS the contract (a
|
|
403
|
+
* release validation phase calls this), and it never writes. Drift is a
|
|
404
|
+
* finding, not a failure: the command succeeds and records the code with
|
|
405
|
+
* `CliExit.set`.
|
|
339
406
|
*
|
|
340
407
|
* @internal
|
|
341
408
|
*/
|
|
342
409
|
function checkOutcome(changed) {
|
|
343
410
|
if (changed.length === 0) return {
|
|
344
411
|
exitCode: 0,
|
|
345
|
-
|
|
346
|
-
};
|
|
347
|
-
const list = changed.map((c) => ` ${c.name} (${c.source})`).join("\n");
|
|
348
|
-
return {
|
|
349
|
-
exitCode: 1,
|
|
350
|
-
text: `Catalog drift detected in ${changed.length} package(s):\n${list}\nRun \`rolldown-pnpm-config upgrade --yes\` to apply.\n`
|
|
412
|
+
doc: [Doc.line("Catalogs are in sync.")]
|
|
351
413
|
};
|
|
352
|
-
}
|
|
353
|
-
/**
|
|
354
|
-
* Map a check run's FAILURE (a resolution failure or peer warning — an
|
|
355
|
-
* UpgradeError, not drift) to the process outcome. Shares --check's single
|
|
356
|
-
* non-zero exit code with drift, so the OUTPUT must name the failure family:
|
|
357
|
-
* a gate consuming the exit code reports every non-zero as "drifted", and
|
|
358
|
-
* without this label the CI log lies about a typo'd package or auth failure.
|
|
359
|
-
*
|
|
360
|
-
* @internal
|
|
361
|
-
*/
|
|
362
|
-
function checkFailureOutcome(message) {
|
|
363
414
|
return {
|
|
364
415
|
exitCode: 1,
|
|
365
|
-
|
|
416
|
+
doc: [
|
|
417
|
+
Doc.line(`Catalog drift detected in ${changed.length} package(s):`),
|
|
418
|
+
Doc.lines(changed.map((c) => ` ${c.name} (${c.source})`)),
|
|
419
|
+
Doc.line([
|
|
420
|
+
"Run ",
|
|
421
|
+
Doc.code("rolldown-pnpm-config upgrade --yes"),
|
|
422
|
+
" to apply."
|
|
423
|
+
])
|
|
424
|
+
]
|
|
366
425
|
};
|
|
367
426
|
}
|
|
368
427
|
/** Project a drift row to its stable `--json` object: catalog/pkg/from/to?/source, camelCase, `to` omitted (never null) when unknown. */
|
|
@@ -375,44 +434,43 @@ function driftRowJson(row) {
|
|
|
375
434
|
source: row.source
|
|
376
435
|
};
|
|
377
436
|
}
|
|
378
|
-
/** Serialize one single-line JSON document, newline-terminated — the ONLY bytes `--json` puts on stdout. */
|
|
379
|
-
function jsonLine(doc) {
|
|
380
|
-
return `${JSON.stringify(doc)}\n`;
|
|
381
|
-
}
|
|
382
437
|
/**
|
|
383
438
|
* The `--check --json` outcome. stdout carries exactly one single-line JSON
|
|
384
|
-
* document in EVERY case — including the
|
|
385
|
-
* keeps its non-zero exit but must never
|
|
386
|
-
*
|
|
387
|
-
*
|
|
439
|
+
* document (`json`, written with `Console.log`) in EVERY case — including the
|
|
440
|
+
* resolution-failure family, which keeps its non-zero exit but must never
|
|
441
|
+
* leave a bash gate with exit 1 and an empty stdout. A resolution failure is
|
|
442
|
+
* also returned as `failure`, which the command fails with so the labelled
|
|
443
|
+
* human report lands on stderr.
|
|
388
444
|
*
|
|
389
445
|
* @internal
|
|
390
446
|
*/
|
|
391
447
|
function checkJsonOutcome(result) {
|
|
392
448
|
if (Result.isFailure(result)) {
|
|
393
|
-
const message = result.failure
|
|
449
|
+
const { message, kind } = result.failure;
|
|
394
450
|
return {
|
|
395
451
|
exitCode: 1,
|
|
396
|
-
|
|
452
|
+
json: JSON.stringify({
|
|
397
453
|
command: "check",
|
|
398
454
|
inSync: false,
|
|
399
455
|
error: {
|
|
400
|
-
kind:
|
|
456
|
+
kind: kind ?? "resolution",
|
|
401
457
|
message
|
|
402
458
|
}
|
|
403
459
|
}),
|
|
404
|
-
|
|
460
|
+
failure: new CheckFailedError({
|
|
461
|
+
message,
|
|
462
|
+
...kind !== void 0 ? { kind } : {}
|
|
463
|
+
})
|
|
405
464
|
};
|
|
406
465
|
}
|
|
407
466
|
const changed = result.success.changed;
|
|
408
467
|
return {
|
|
409
468
|
exitCode: changed.length === 0 ? 0 : 1,
|
|
410
|
-
|
|
469
|
+
json: JSON.stringify({
|
|
411
470
|
command: "check",
|
|
412
471
|
inSync: changed.length === 0,
|
|
413
472
|
drift: changed.map(driftRowJson)
|
|
414
|
-
})
|
|
415
|
-
stderr: ""
|
|
473
|
+
})
|
|
416
474
|
};
|
|
417
475
|
}
|
|
418
476
|
/**
|
|
@@ -420,62 +478,59 @@ function checkJsonOutcome(result) {
|
|
|
420
478
|
* the run wrote at least one change — always false under dry-run AND on an
|
|
421
479
|
* already-in-sync run, so a consumer keying on it never reads a no-op as a
|
|
422
480
|
* real apply; `changed` uses the same row object as check's `drift`. A failure
|
|
423
|
-
* emits an error document on stdout
|
|
424
|
-
* on stderr; `error.kind` is
|
|
425
|
-
* `"resolution"` otherwise.
|
|
481
|
+
* emits an error document on stdout and is returned as `failure`, which the
|
|
482
|
+
* command fails with (non-zero exit, human message on stderr); `error.kind` is
|
|
483
|
+
* `"peer-strategy"` for a peer-strategy refusal and `"resolution"` otherwise.
|
|
426
484
|
*
|
|
427
485
|
* @internal
|
|
428
486
|
*/
|
|
429
487
|
function upgradeJsonOutcome(result, dryRun) {
|
|
430
488
|
if (Result.isFailure(result)) {
|
|
431
|
-
const message = result.failure
|
|
489
|
+
const { message, kind } = result.failure;
|
|
432
490
|
return {
|
|
433
|
-
|
|
434
|
-
stdout: jsonLine({
|
|
491
|
+
json: JSON.stringify({
|
|
435
492
|
command: "upgrade",
|
|
436
493
|
applied: false,
|
|
437
494
|
error: {
|
|
438
|
-
kind:
|
|
495
|
+
kind: kind ?? "resolution",
|
|
439
496
|
message
|
|
440
497
|
}
|
|
441
498
|
}),
|
|
442
|
-
|
|
499
|
+
failure: result.failure
|
|
443
500
|
};
|
|
444
501
|
}
|
|
445
502
|
const r = result.success;
|
|
446
|
-
return {
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
}))
|
|
459
|
-
}),
|
|
460
|
-
stderr: ""
|
|
461
|
-
};
|
|
503
|
+
return { json: JSON.stringify({
|
|
504
|
+
command: "upgrade",
|
|
505
|
+
applied: !dryRun && r.updated > 0,
|
|
506
|
+
updated: r.updated,
|
|
507
|
+
changed: r.changed.map(driftRowJson),
|
|
508
|
+
skipped: r.skipped,
|
|
509
|
+
conflicts: r.conflicts.map((c) => ({
|
|
510
|
+
pkg: c.pkg,
|
|
511
|
+
ceiling: c.ceiling,
|
|
512
|
+
blockedBy: c.blockedBy
|
|
513
|
+
}))
|
|
514
|
+
}) };
|
|
462
515
|
}
|
|
463
516
|
/**
|
|
464
517
|
* Reject `--json` outside the non-interactive modes it exists for. JSON mode
|
|
465
518
|
* is a machine contract (a GitHub Action parsing stdout from bash); the
|
|
466
519
|
* interactive table and the preview views have no meaningful document to emit.
|
|
467
|
-
* Returns the
|
|
520
|
+
* Returns the usage error to fail with (exit 64), or null when the combination
|
|
521
|
+
* is valid.
|
|
468
522
|
*
|
|
469
523
|
* @internal
|
|
470
524
|
*/
|
|
471
525
|
function validateJsonMode(flags) {
|
|
472
526
|
if (!flags.json) return null;
|
|
473
|
-
if (flags.preview) return new
|
|
474
|
-
if (!flags.check && !flags.yes && !flags.dryRun) return new
|
|
527
|
+
if (flags.preview) return new UpgradeUsageError({ message: "--json cannot be combined with --preview; use --check, --yes, or --dry-run" });
|
|
528
|
+
if (!flags.check && !flags.yes && !flags.dryRun) return new UpgradeUsageError({ message: "--json requires a non-interactive mode: combine it with --check, --yes, or --dry-run" });
|
|
475
529
|
return null;
|
|
476
530
|
}
|
|
477
531
|
/**
|
|
478
|
-
* Resolve the target file: the passed path, or autodetect in
|
|
532
|
+
* Resolve the target file: the passed path, or autodetect in the working
|
|
533
|
+
* directory.
|
|
479
534
|
*
|
|
480
535
|
* @internal
|
|
481
536
|
*/
|
|
@@ -483,20 +538,77 @@ function resolveTargetFile(fileOpt) {
|
|
|
483
538
|
return Effect.gen(function* () {
|
|
484
539
|
const explicit = Option.getOrUndefined(fileOpt);
|
|
485
540
|
if (explicit !== void 0) return explicit;
|
|
486
|
-
const matches = yield* findConfigFiles(
|
|
541
|
+
const matches = yield* findConfigFiles(yield* WorkingDirectory);
|
|
487
542
|
const picked = pickConfigCandidate(matches);
|
|
488
543
|
if (!picked.ok) return yield* Effect.fail(new UpgradeError({ message: picked.message }));
|
|
489
544
|
return picked.file;
|
|
490
545
|
});
|
|
491
546
|
}
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
547
|
+
/** Print the unresolved-packages warning to stderr, when there is one. */
|
|
548
|
+
function warnUnresolved(unresolved) {
|
|
549
|
+
return unresolved.length > 0 ? printDoc(warningDoc(unresolvedMessage(unresolved)), { stream: "stderr" }) : Effect.void;
|
|
550
|
+
}
|
|
551
|
+
/**
|
|
552
|
+
* Resolve every entry's versions, then — for interop catalogs — pre-fetch the
|
|
553
|
+
* peerDependencies of each candidate version so the live table can recompute
|
|
554
|
+
* peer floors + conflicts as picks change (the same data `runInterop` fetches,
|
|
555
|
+
* moved ahead of the walk). Interactively the live table IS the reconcile;
|
|
556
|
+
* `--yes`/CI keeps the auto-reconcile (`runUpgrade`). Each phase reports
|
|
557
|
+
* progress, which an interactive run draws as a live view.
|
|
558
|
+
*/
|
|
559
|
+
function resolveForWalk(entries, resolver, gate, workspaceResolver, report) {
|
|
560
|
+
return Effect.gen(function* () {
|
|
561
|
+
const versions = yield* resolveGatedVersions(entries, resolver, gate, Date.now(), resolveProgress(report), workspaceResolver);
|
|
562
|
+
const items = yield* buildWalkItems(entries, versions.gated).pipe(Effect.mapError((e) => new UpgradeError({ message: e.message })));
|
|
563
|
+
const interopByCatalog = /* @__PURE__ */ new Map();
|
|
564
|
+
for (const e of entries) {
|
|
565
|
+
if (e.strategy !== "interop") continue;
|
|
566
|
+
const list = interopByCatalog.get(e.catalog) ?? [];
|
|
567
|
+
list.push(e);
|
|
568
|
+
interopByCatalog.set(e.catalog, list);
|
|
569
|
+
}
|
|
570
|
+
const interopModels = /* @__PURE__ */ new Map();
|
|
571
|
+
if (interopByCatalog.size > 0) {
|
|
572
|
+
const fetchPeer = makePeerFetcher(resolver);
|
|
573
|
+
const total = interopByCatalog.size;
|
|
574
|
+
yield* report({
|
|
575
|
+
_tag: "Phase",
|
|
576
|
+
label: "Resolving peer dependencies",
|
|
577
|
+
total
|
|
578
|
+
});
|
|
579
|
+
for (const [catalog, group] of interopByCatalog) {
|
|
580
|
+
const candByPkg = /* @__PURE__ */ new Map();
|
|
581
|
+
for (const e of group) {
|
|
582
|
+
const it = items.find((i) => i.entry.catalog === catalog && i.entry.pkg === e.pkg);
|
|
583
|
+
candByPkg.set(e.pkg, it ? it.candidates.map((c) => c.version) : [bareVersion(e.currentRange)]);
|
|
584
|
+
}
|
|
585
|
+
interopModels.set(catalog, yield* buildGroupModel(candByPkg, fetchPeer));
|
|
586
|
+
yield* report({
|
|
587
|
+
_tag: "Step",
|
|
588
|
+
done: interopModels.size
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
yield* report({
|
|
592
|
+
_tag: "Finished",
|
|
593
|
+
label: "Resolved peer dependencies"
|
|
594
|
+
});
|
|
595
|
+
}
|
|
596
|
+
return {
|
|
597
|
+
versions,
|
|
598
|
+
items,
|
|
599
|
+
interopByCatalog,
|
|
600
|
+
interopModels
|
|
601
|
+
};
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
const fileArg = Argument.File("file").pipe(Argument.withDescription("The config file to upgrade (autodetected in the current directory when omitted)"), Argument.optional);
|
|
605
|
+
const yesFlag = Flag.Boolean("yes").pipe(Flag.withAlias("y"), Flag.withDescription("Apply the latest in-range version of every entry without asking (never a major)"), Flag.withDefault(false));
|
|
606
|
+
const dryRunFlag = Flag.Boolean("dry-run").pipe(Flag.withDescription("Run the whole flow and report what would change, but write nothing"), Flag.withDefault(false));
|
|
607
|
+
const catalogOption = Flag.String("catalog").pipe(Flag.withDescription("Only upgrade entries in the named catalog"), Flag.optional);
|
|
608
|
+
const previewFlag = Flag.Boolean("preview").pipe(Flag.withDescription("Print the default picks as a summary and exit"), Flag.withDefault(false));
|
|
609
|
+
const fullFlag = Flag.Boolean("full").pipe(Flag.withDescription("Include up-to-date packages in a printed summary"), Flag.withDefault(false));
|
|
610
|
+
const checkFlag = Flag.Boolean("check").pipe(Flag.withDescription("Exit 1 when --yes would change anything (a drift gate; never writes)"), Flag.withDefault(false));
|
|
611
|
+
const jsonFlag = Flag.Boolean("json").pipe(Flag.withDescription("Print one JSON document on stdout (with --check, --yes or --dry-run)"), Flag.withDefault(false));
|
|
500
612
|
/**
|
|
501
613
|
* The "upgrade" command. The default path runs the interactive table;
|
|
502
614
|
* --yes applies latest-in-range non-interactively; --dry-run runs the identical
|
|
@@ -523,111 +635,91 @@ const upgradeCommand = Command.make("upgrade", {
|
|
|
523
635
|
preview
|
|
524
636
|
});
|
|
525
637
|
if (jsonRejection !== null) return yield* Effect.fail(jsonRejection);
|
|
526
|
-
const file = yield* resolveTargetFile(fileOpt);
|
|
527
638
|
const resolver = yield* RegistryResolver;
|
|
528
|
-
const
|
|
529
|
-
const
|
|
639
|
+
const workspaceOf = (file) => makeWorkspaceResolver(findWorkspaceRoot(dirname(file)));
|
|
640
|
+
const nonInteractive = (forceDryRun) => resolveTargetFile(fileOpt).pipe(Effect.flatMap((file) => runUpgrade({
|
|
641
|
+
file,
|
|
642
|
+
resolver,
|
|
643
|
+
workspaceResolver: workspaceOf(file),
|
|
644
|
+
dryRun: forceDryRun || dryRun
|
|
645
|
+
})), Effect.result);
|
|
530
646
|
if (check) {
|
|
531
|
-
const result = yield*
|
|
532
|
-
file,
|
|
533
|
-
resolver,
|
|
534
|
-
workspaceResolver,
|
|
535
|
-
dryRun: true
|
|
536
|
-
}).pipe(Effect.result);
|
|
647
|
+
const result = yield* nonInteractive(true);
|
|
537
648
|
if (json) {
|
|
538
649
|
const o = checkJsonOutcome(result);
|
|
539
|
-
yield*
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
process.exitCode = o.exitCode;
|
|
543
|
-
});
|
|
544
|
-
return;
|
|
650
|
+
yield* Console.log(o.json);
|
|
651
|
+
if (o.failure !== void 0) return yield* Effect.fail(o.failure);
|
|
652
|
+
return yield* CliExit.set(o.exitCode);
|
|
545
653
|
}
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
654
|
+
if (Result.isFailure(result)) {
|
|
655
|
+
const { message, kind } = result.failure;
|
|
656
|
+
return yield* Effect.fail(new CheckFailedError({
|
|
657
|
+
message,
|
|
658
|
+
...kind !== void 0 ? { kind } : {}
|
|
659
|
+
}));
|
|
660
|
+
}
|
|
661
|
+
const outcome = checkOutcome(result.success.changed);
|
|
662
|
+
yield* printDoc(outcome.doc);
|
|
663
|
+
return yield* CliExit.set(outcome.exitCode);
|
|
553
664
|
}
|
|
554
665
|
if (json) {
|
|
555
|
-
const o = upgradeJsonOutcome(yield*
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
dryRun,
|
|
559
|
-
workspaceResolver
|
|
560
|
-
}).pipe(Effect.result), dryRun);
|
|
561
|
-
yield* Effect.sync(() => {
|
|
562
|
-
if (o.stderr !== "") process.stderr.write(o.stderr);
|
|
563
|
-
process.stdout.write(o.stdout);
|
|
564
|
-
process.exitCode = o.exitCode;
|
|
565
|
-
});
|
|
666
|
+
const o = upgradeJsonOutcome(yield* nonInteractive(false), dryRun);
|
|
667
|
+
yield* Console.log(o.json);
|
|
668
|
+
if (o.failure !== void 0) return yield* Effect.fail(o.failure);
|
|
566
669
|
return;
|
|
567
670
|
}
|
|
671
|
+
const file = yield* resolveTargetFile(fileOpt);
|
|
672
|
+
const workspaceResolver = workspaceOf(file);
|
|
568
673
|
if (preview) {
|
|
569
|
-
const
|
|
674
|
+
const { doc, unresolved } = yield* runUpgradePreview({
|
|
570
675
|
file,
|
|
571
676
|
resolver,
|
|
572
677
|
full,
|
|
573
|
-
color: caps.color,
|
|
574
678
|
workspaceResolver
|
|
575
679
|
});
|
|
576
|
-
yield*
|
|
577
|
-
return;
|
|
680
|
+
yield* printDoc(doc);
|
|
681
|
+
return yield* warnUnresolved(unresolved);
|
|
578
682
|
}
|
|
579
683
|
if (yes) {
|
|
580
|
-
const result = yield* runUpgrade({
|
|
684
|
+
const result = yield* withProgress((report) => runUpgrade({
|
|
581
685
|
file,
|
|
582
686
|
resolver,
|
|
583
687
|
dryRun,
|
|
584
688
|
workspaceResolver,
|
|
585
|
-
|
|
586
|
-
});
|
|
587
|
-
yield*
|
|
689
|
+
onProgress: resolveProgress(report)
|
|
690
|
+
}));
|
|
691
|
+
yield* dryRun ? CliMessage.info(`Dry run — no changes written. ${result.updated} package(s) would be updated; skipped ${result.skipped.length}.`) : CliMessage.success(`Updated ${result.updated} package(s); skipped ${result.skipped.length}.`);
|
|
588
692
|
if (result.conflicts.length > 0) {
|
|
589
|
-
const lines = result.conflicts.map((c) => ` ${c.pkg} (kept ${c.ceiling}) blocked by ${c.blockedBy}`)
|
|
590
|
-
yield*
|
|
693
|
+
const lines = result.conflicts.map((c) => ` ${c.pkg} (kept ${c.ceiling}) blocked by ${c.blockedBy}`);
|
|
694
|
+
yield* printDoc(warningDoc(["Interop conflicts (left at your pick):", ...lines].join("\n")), { stream: "stderr" });
|
|
591
695
|
}
|
|
592
696
|
return;
|
|
593
697
|
}
|
|
594
698
|
const { source, entries: discovered } = yield* readCatalogSource(file);
|
|
595
|
-
const
|
|
596
|
-
const
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
const
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
699
|
+
const entries = filterEntriesByCatalog(discovered, Option.getOrUndefined(catalog));
|
|
700
|
+
const gate = yield* computeGate(source, file, resolver);
|
|
701
|
+
if (!(yield* CliInteractive)) {
|
|
702
|
+
const versions = yield* resolveGatedVersions(entries, resolver, gate, Date.now(), void 0, workspaceResolver);
|
|
703
|
+
const items = yield* buildWalkItems(entries, versions.gated).pipe(Effect.mapError((e) => new UpgradeError({ message: e.message })));
|
|
704
|
+
const note = dryRun ? "(dry run — nothing written)" : "(not interactive — run with --yes to apply, or in a terminal to choose)";
|
|
705
|
+
yield* printDoc([
|
|
706
|
+
...summaryDoc(projectDecisions(items, full)),
|
|
707
|
+
Doc.line(""),
|
|
708
|
+
Doc.line(Doc.text(note, "muted"))
|
|
709
|
+
]);
|
|
710
|
+
return yield* warnUnresolved(versions.unresolved);
|
|
605
711
|
}
|
|
712
|
+
const { versions, items, interopByCatalog, interopModels } = yield* withProgress((report) => resolveForWalk(entries, resolver, gate, workspaceResolver, report));
|
|
606
713
|
if (items.length === 0) {
|
|
607
|
-
|
|
608
|
-
yield*
|
|
609
|
-
return;
|
|
610
|
-
}
|
|
611
|
-
const interopByCatalog = /* @__PURE__ */ new Map();
|
|
612
|
-
for (const e of entries) {
|
|
613
|
-
if (e.strategy !== "interop") continue;
|
|
614
|
-
const list = interopByCatalog.get(e.catalog) ?? [];
|
|
615
|
-
list.push(e);
|
|
616
|
-
interopByCatalog.set(e.catalog, list);
|
|
714
|
+
yield* warnUnresolved(versions.unresolved);
|
|
715
|
+
return yield* CliMessage.info(nothingToUpgradeMessage(0));
|
|
617
716
|
}
|
|
618
|
-
const
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
const it = items.find((i) => i.entry.catalog === catalog && i.entry.pkg === e.pkg);
|
|
625
|
-
candByPkg.set(e.pkg, it ? it.candidates.map((c) => c.version) : [bareVersion(e.currentRange)]);
|
|
626
|
-
}
|
|
627
|
-
interopModels.set(catalog, yield* buildGroupModel(candByPkg, fetchPeer));
|
|
628
|
-
}
|
|
629
|
-
const { runWalk } = yield* Effect.promise(() => import("../ui/run-walk.js"));
|
|
630
|
-
const decisions = yield* runWalk(items, dryRun, versions.unresolved, interopModels);
|
|
717
|
+
const decisions = yield* CliUi.run(walkScreen({
|
|
718
|
+
items,
|
|
719
|
+
dryRun,
|
|
720
|
+
unresolved: versions.unresolved,
|
|
721
|
+
interopModels
|
|
722
|
+
}));
|
|
631
723
|
const nonInteropDecisions = decisions.filter((d) => d.item.entry.strategy !== "interop");
|
|
632
724
|
const interopEdits = [];
|
|
633
725
|
const allConflicts = [];
|
|
@@ -663,12 +755,12 @@ const upgradeCommand = Command.make("upgrade", {
|
|
|
663
755
|
const planned = buildEdits(nonInteropDecisions);
|
|
664
756
|
const { accepted, rejected } = yield* validateEdits(planned, versions.raw);
|
|
665
757
|
const acceptedPkgs = new Set(accepted.map((e) => e.pkg));
|
|
666
|
-
yield*
|
|
758
|
+
yield* printDoc(summaryDoc(decisions, { conflicts: allConflicts }, rejected));
|
|
667
759
|
if (!dryRun) yield* applyInteropAndDecisions(file, source, accepted, interopEdits);
|
|
668
760
|
const changed = countChangedDecisions(nonInteropDecisions.filter((d) => acceptedPkgs.has(d.item.entry.pkg))) + interopChanged;
|
|
669
|
-
yield*
|
|
670
|
-
|
|
671
|
-
}).pipe(Effect.provide(RegistryResolverLive)
|
|
761
|
+
yield* dryRun ? CliMessage.info(`Dry run — no changes written. ${changed} change(s) would be applied.`) : CliMessage.success(`Applied ${changed} change(s).`);
|
|
762
|
+
yield* warnUnresolved(versions.unresolved);
|
|
763
|
+
}).pipe(Effect.provide(RegistryResolverLive))).pipe(Command.withDescription("Upgrade catalog versions in a config file"));
|
|
672
764
|
|
|
673
765
|
//#endregion
|
|
674
|
-
export { UpgradeError,
|
|
766
|
+
export { CHECK_FAILED_LABEL, CheckFailedError, UpgradeError, UpgradeUsageError, applyInteropAndDecisions, checkJsonOutcome, checkOutcome, computeGate, countChangedDecisions, nothingToUpgradeMessage, projectDecisions, readCatalogSource, resolveGatedVersions, resolveProgress, resolveTargetFile, runUpgrade, runUpgradePreview, unresolvedMessage, upgradeCommand, upgradeJsonOutcome, validateJsonMode };
|