rolldown-pnpm-config 0.6.2 → 0.7.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/bin/rolldown-pnpm-config.js +1 -1
- package/catalogs.js +1 -0
- package/cli/commands/upgrade.js +278 -29
- package/cli/discover.js +5 -1
- package/cli/edits.js +6 -0
- package/cli/validate.js +10 -7
- package/cli/version-key.js +18 -0
- package/cli/walk-plan.js +2 -1
- package/cli/workspace-resolve.js +162 -0
- package/index.d.ts +16 -2
- package/package.json +3 -2
- package/tsdoc-metadata.json +1 -1
|
@@ -12,7 +12,7 @@ const root = Command.make("rolldown-pnpm-config").pipe(Command.withSubcommands([
|
|
|
12
12
|
exportCommand,
|
|
13
13
|
previewCommand
|
|
14
14
|
]));
|
|
15
|
-
Command.run(root, { version: "0.
|
|
15
|
+
Command.run(root, { version: "0.7.0" }).pipe(Effect.provide(NodeServices.layer), NodeRuntime.runMain);
|
|
16
16
|
|
|
17
17
|
//#endregion
|
|
18
18
|
export { };
|
package/catalogs.js
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
function normalizeCatalogs(input) {
|
|
12
12
|
const out = {};
|
|
13
13
|
for (const [name, decl] of Object.entries(input)) {
|
|
14
|
+
if (typeof decl !== "object" || decl === null || typeof decl.packages !== "object" || decl.packages === null) continue;
|
|
14
15
|
const base = {};
|
|
15
16
|
const peers = {};
|
|
16
17
|
for (const [pkg, spec] of Object.entries(decl.packages)) {
|
package/cli/commands/upgrade.js
CHANGED
|
@@ -4,6 +4,7 @@ import { filterEntriesByCatalog, findConfigFiles, pickConfigCandidate } from "..
|
|
|
4
4
|
import { detectCapabilities } from "../ui/env.js";
|
|
5
5
|
import { derivePeerRange } from "../peer-range.js";
|
|
6
6
|
import { detectPeerDrift } from "../drift.js";
|
|
7
|
+
import { versionKeyOf } from "../version-key.js";
|
|
7
8
|
import { buildEdits } from "../edits.js";
|
|
8
9
|
import { buildInteropEdits, interopEntryChanged, runInterop } from "../interop.js";
|
|
9
10
|
import { buildGroupModel, computeGroupPeers } from "../interop-live.js";
|
|
@@ -15,8 +16,10 @@ import { renderSummary } from "../summary.js";
|
|
|
15
16
|
import { runWalk } from "../ui/run-walk.js";
|
|
16
17
|
import { validateEdits } from "../validate.js";
|
|
17
18
|
import { buildWalkItems } from "../walk-plan.js";
|
|
19
|
+
import { findWorkspaceRoot, makeWorkspaceResolver } from "../workspace-resolve.js";
|
|
18
20
|
import { Data, Effect, Option, Result } from "effect";
|
|
19
21
|
import { readFileSync, writeFileSync } from "node:fs";
|
|
22
|
+
import { dirname } from "node:path";
|
|
20
23
|
import { NodeServices } from "@effect/platform-node";
|
|
21
24
|
import { Argument, Command, Flag } from "effect/unstable/cli";
|
|
22
25
|
import { ReleaseAgeGate } from "@effected/npm";
|
|
@@ -41,40 +44,63 @@ function computeGate(source, file, resolver) {
|
|
|
41
44
|
/** Maximum number of per-package version+times fetches to issue concurrently. @internal */
|
|
42
45
|
const RESOLVE_CONCURRENCY = 12;
|
|
43
46
|
/**
|
|
44
|
-
* Fetch and age-gate the version list for each unique
|
|
47
|
+
* Fetch and age-gate the version list for each unique (pkg × route) pair.
|
|
48
|
+
* The returned maps are keyed by `versionKeyOf(entry)`, NOT the bare package
|
|
49
|
+
* name: the same name can appear workspace-sourced in one catalog and
|
|
50
|
+
* registry-sourced in another, and the two routes must neither share a version
|
|
51
|
+
* list nor a gate exemption.
|
|
45
52
|
*
|
|
46
53
|
* @param onProgress - Optional callback invoked after each package resolves with
|
|
47
54
|
* `(resolved, total)`. Useful for emitting CLI progress feedback. Called with
|
|
48
55
|
* `(0, total)` before any work starts so callers can emit the initial banner.
|
|
56
|
+
* @param workspace - Optional workspace-backed resolver. An entry whose
|
|
57
|
+
* `source` is `"workspace"` resolves through it instead of the registry, and
|
|
58
|
+
* is EXEMPT from the release-age gate: its next version is unpublished, so
|
|
59
|
+
* `times` is empty and the gate would otherwise hold it forever.
|
|
49
60
|
*
|
|
50
61
|
* @internal
|
|
51
62
|
*/
|
|
52
|
-
function resolveGatedVersions(entries, resolver, gate, now, onProgress) {
|
|
53
|
-
const
|
|
54
|
-
const
|
|
63
|
+
function resolveGatedVersions(entries, resolver, gate, now, onProgress, workspace) {
|
|
64
|
+
const pairs = /* @__PURE__ */ new Map();
|
|
65
|
+
for (const e of entries) pairs.set(versionKeyOf(e), {
|
|
66
|
+
pkg: e.pkg,
|
|
67
|
+
fromWorkspace: workspace !== void 0 && e.source === "workspace"
|
|
68
|
+
});
|
|
69
|
+
const total = pairs.size;
|
|
55
70
|
let resolved = 0;
|
|
56
71
|
onProgress?.(0, total);
|
|
57
|
-
return Effect.forEach(
|
|
58
|
-
const vr = yield* resolver.versions(pkg).pipe(Effect.result);
|
|
72
|
+
return Effect.forEach([...pairs], ([key, { pkg, fromWorkspace }]) => Effect.gen(function* () {
|
|
73
|
+
const vr = yield* (fromWorkspace && workspace !== void 0 ? workspace : resolver).versions(pkg).pipe(Effect.result);
|
|
59
74
|
if (Result.isFailure(vr)) {
|
|
60
75
|
onProgress?.(++resolved, total);
|
|
61
76
|
return [
|
|
77
|
+
key,
|
|
62
78
|
pkg,
|
|
63
79
|
[],
|
|
64
80
|
[]
|
|
65
81
|
];
|
|
66
82
|
}
|
|
83
|
+
if (fromWorkspace) {
|
|
84
|
+
onProgress?.(++resolved, total);
|
|
85
|
+
return [
|
|
86
|
+
key,
|
|
87
|
+
pkg,
|
|
88
|
+
vr.success,
|
|
89
|
+
vr.success
|
|
90
|
+
];
|
|
91
|
+
}
|
|
67
92
|
const times = gate.ageMinutes > 0 ? yield* resolver.times(pkg).pipe(Effect.catch(() => Effect.succeed({}))) : {};
|
|
68
93
|
onProgress?.(++resolved, total);
|
|
69
94
|
return [
|
|
95
|
+
key,
|
|
70
96
|
pkg,
|
|
71
97
|
[...gate.filterVersions(vr.success, times, pkg, now)],
|
|
72
98
|
vr.success
|
|
73
99
|
];
|
|
74
|
-
}), { concurrency: 12 }).pipe(Effect.map((
|
|
75
|
-
gated: new Map(
|
|
76
|
-
raw: new Map(
|
|
77
|
-
unresolved:
|
|
100
|
+
}), { concurrency: 12 }).pipe(Effect.map((rows) => ({
|
|
101
|
+
gated: new Map(rows.map(([key, , gated]) => [key, gated])),
|
|
102
|
+
raw: new Map(rows.map(([key, , , raw]) => [key, raw])),
|
|
103
|
+
unresolved: [...new Set(rows.filter(([, , , raw]) => raw.length === 0).map(([, pkg]) => pkg))]
|
|
78
104
|
})));
|
|
79
105
|
}
|
|
80
106
|
/**
|
|
@@ -120,20 +146,35 @@ function runUpgrade(opts) {
|
|
|
120
146
|
catch: (e) => new UpgradeError({ message: String(e) })
|
|
121
147
|
});
|
|
122
148
|
const gate = yield* computeGate(source, opts.file, opts.resolver);
|
|
123
|
-
const versionsByPkg = yield* resolveGatedVersions(entries, opts.resolver, gate, Date.now(), opts.onProgress);
|
|
149
|
+
const versionsByPkg = yield* resolveGatedVersions(entries, opts.resolver, gate, Date.now(), opts.onProgress, opts.workspaceResolver);
|
|
124
150
|
if (versionsByPkg.unresolved.length > 0) return yield* Effect.fail(new UpgradeError({ message: unresolvedMessage(versionsByPkg.unresolved) }));
|
|
125
151
|
const edits = [];
|
|
126
152
|
const interopEdits = [];
|
|
127
153
|
const warnings = [];
|
|
128
154
|
const changedSpans = /* @__PURE__ */ new Set();
|
|
155
|
+
const changedPkgs = /* @__PURE__ */ new Map();
|
|
156
|
+
const markChanged = (entry, to) => {
|
|
157
|
+
changedSpans.add(entry.rangeSpan[0]);
|
|
158
|
+
const name = `${entry.catalog}.${entry.pkg}`;
|
|
159
|
+
changedPkgs.set(name, {
|
|
160
|
+
name,
|
|
161
|
+
catalog: entry.catalog,
|
|
162
|
+
pkg: entry.pkg,
|
|
163
|
+
from: entry.currentRange,
|
|
164
|
+
...to !== void 0 ? { to } : {},
|
|
165
|
+
source: entry.source ?? "registry"
|
|
166
|
+
});
|
|
167
|
+
};
|
|
129
168
|
for (const entry of entries) {
|
|
130
169
|
if (entry.strategy === "interop") continue;
|
|
131
|
-
const
|
|
170
|
+
const versionKey = versionKeyOf(entry);
|
|
171
|
+
const versions = versionsByPkg.gated.get(versionKey) ?? [];
|
|
132
172
|
const pkg = entry.pkg;
|
|
133
173
|
const rangeEdit = (span, value) => ({
|
|
134
174
|
span,
|
|
135
175
|
text: JSON.stringify(value),
|
|
136
176
|
pkg,
|
|
177
|
+
versionKey,
|
|
137
178
|
kind: "range",
|
|
138
179
|
value
|
|
139
180
|
});
|
|
@@ -141,6 +182,7 @@ function runUpgrade(opts) {
|
|
|
141
182
|
span,
|
|
142
183
|
text: JSON.stringify(value),
|
|
143
184
|
pkg,
|
|
185
|
+
versionKey,
|
|
144
186
|
kind: "peer",
|
|
145
187
|
value
|
|
146
188
|
});
|
|
@@ -148,6 +190,7 @@ function runUpgrade(opts) {
|
|
|
148
190
|
span: [at, at],
|
|
149
191
|
text: `, peer: ${JSON.stringify(value)}`,
|
|
150
192
|
pkg,
|
|
193
|
+
versionKey,
|
|
151
194
|
kind: "peer",
|
|
152
195
|
value
|
|
153
196
|
});
|
|
@@ -159,32 +202,33 @@ function runUpgrade(opts) {
|
|
|
159
202
|
const expected = yield* detectPeerDrift(entry).pipe(Effect.catch(() => Effect.succeed(null)));
|
|
160
203
|
if (expected !== null) {
|
|
161
204
|
edits.push(peerEdit(entry.peer.span, expected));
|
|
162
|
-
|
|
205
|
+
markChanged(entry);
|
|
163
206
|
continue;
|
|
164
207
|
}
|
|
165
208
|
} else if (!entry.peer && entry.strategy && derived !== null) {
|
|
166
209
|
edits.push(peerInsert(at, derived.range));
|
|
167
|
-
|
|
210
|
+
markChanged(entry);
|
|
168
211
|
continue;
|
|
169
212
|
}
|
|
170
213
|
skipped.push(`${entry.catalog}.${entry.pkg}`);
|
|
171
214
|
continue;
|
|
172
215
|
}
|
|
173
|
-
const
|
|
216
|
+
const candidates = yield* planEntry(entry, versions).pipe(Effect.catch(() => Effect.succeed([])));
|
|
217
|
+
const inRange = entry.source === "workspace" ? candidates.find((c) => c.kind !== "keep") : candidates.find((c) => c.kind === "in-range");
|
|
174
218
|
const at = entry.rangeSpan[1];
|
|
175
219
|
if (inRange) {
|
|
176
220
|
edits.push(rangeEdit(entry.rangeSpan, inRange.range));
|
|
177
|
-
|
|
221
|
+
markChanged(entry, inRange.range);
|
|
178
222
|
if (entry.peer && inRange.peerRange) edits.push(peerEdit(entry.peer.span, inRange.peerRange));
|
|
179
223
|
else if (!entry.peer && entry.strategy && inRange.peerRange) edits.push(peerInsert(at, inRange.peerRange));
|
|
180
224
|
} else if (!entry.peer && entry.strategy && derived !== null) {
|
|
181
225
|
edits.push(peerInsert(at, derived.range));
|
|
182
|
-
|
|
226
|
+
markChanged(entry);
|
|
183
227
|
} else if (entry.peer && entry.strategy) {
|
|
184
228
|
const expected = yield* detectPeerDrift(entry).pipe(Effect.catch(() => Effect.succeed(null)));
|
|
185
229
|
if (expected !== null) {
|
|
186
230
|
edits.push(peerEdit(entry.peer.span, expected));
|
|
187
|
-
|
|
231
|
+
markChanged(entry);
|
|
188
232
|
}
|
|
189
233
|
}
|
|
190
234
|
}
|
|
@@ -199,7 +243,7 @@ function runUpgrade(opts) {
|
|
|
199
243
|
for (const [, group] of byCatalog) {
|
|
200
244
|
const members = [];
|
|
201
245
|
for (const e of group) {
|
|
202
|
-
const versions = versionsByPkg.gated.get(e
|
|
246
|
+
const versions = versionsByPkg.gated.get(versionKeyOf(e)) ?? [];
|
|
203
247
|
const inRange = (yield* planEntry(e, versions).pipe(Effect.catch(() => Effect.succeed([])))).find((c) => c.kind === "in-range");
|
|
204
248
|
const ceiling = inRange ? inRange.version : e.currentRange.replace(/^[\^~]/, "");
|
|
205
249
|
members.push({
|
|
@@ -210,10 +254,18 @@ function runUpgrade(opts) {
|
|
|
210
254
|
}
|
|
211
255
|
const result = yield* runInterop(members, opts.resolver);
|
|
212
256
|
interopEdits.push(...buildInteropEdits(group, result));
|
|
213
|
-
for (const e of group)
|
|
257
|
+
for (const e of group) {
|
|
258
|
+
if (!interopEntryChanged(e, result)) continue;
|
|
259
|
+
const next = result.resolved.get(e.pkg);
|
|
260
|
+
const nextRange = next === void 0 ? void 0 : `${e.operator}${next}`;
|
|
261
|
+
markChanged(e, nextRange !== void 0 && nextRange !== e.currentRange ? nextRange : void 0);
|
|
262
|
+
}
|
|
214
263
|
conflicts.push(...result.conflicts);
|
|
215
264
|
}
|
|
216
|
-
if (warnings.length > 0) return yield* Effect.fail(new UpgradeError({
|
|
265
|
+
if (warnings.length > 0) return yield* Effect.fail(new UpgradeError({
|
|
266
|
+
message: `Refusing to apply with an incompatible peer strategy:\n${warnings.map((w) => ` ${w}`).join("\n")}`,
|
|
267
|
+
kind: "peer-strategy"
|
|
268
|
+
}));
|
|
217
269
|
const { accepted, rejected } = yield* validateEdits(edits, versionsByPkg.raw);
|
|
218
270
|
if (rejected.length > 0) return yield* Effect.fail(new UpgradeError({ message: `Refusing to write unsatisfiable range(s):\n${rejected.map((r) => ` ${r.reason}`).join("\n")}` }));
|
|
219
271
|
const allEdits = [...accepted, ...interopEdits];
|
|
@@ -228,7 +280,8 @@ function runUpgrade(opts) {
|
|
|
228
280
|
updated: changedSpans.size,
|
|
229
281
|
skipped,
|
|
230
282
|
conflicts,
|
|
231
|
-
rejected
|
|
283
|
+
rejected,
|
|
284
|
+
changed: [...changedPkgs.values()]
|
|
232
285
|
};
|
|
233
286
|
});
|
|
234
287
|
}
|
|
@@ -283,7 +336,7 @@ function unresolvedMessage(unresolved) {
|
|
|
283
336
|
function projectDecisions(items, full) {
|
|
284
337
|
const out = [];
|
|
285
338
|
for (const i of items) {
|
|
286
|
-
const inRange = i.candidates.find((c) => c.kind === "in-range");
|
|
339
|
+
const inRange = i.entry.source === "workspace" ? i.candidates.find((c) => c.kind !== "keep") : i.candidates.find((c) => c.kind === "in-range");
|
|
287
340
|
if (inRange) {
|
|
288
341
|
out.push({
|
|
289
342
|
item: i,
|
|
@@ -323,13 +376,156 @@ function runUpgradePreview(opts) {
|
|
|
323
376
|
catch: (e) => new UpgradeError({ message: String(e) })
|
|
324
377
|
});
|
|
325
378
|
const gate = yield* computeGate(source, opts.file, opts.resolver);
|
|
326
|
-
const versions = yield* resolveGatedVersions(discovered.entries, opts.resolver, gate, Date.now());
|
|
379
|
+
const versions = yield* resolveGatedVersions(discovered.entries, opts.resolver, gate, Date.now(), void 0, opts.workspaceResolver);
|
|
327
380
|
const items = yield* buildWalkItems(discovered.entries, versions.gated).pipe(Effect.catch((e) => Effect.fail(new UpgradeError({ message: e.message }))));
|
|
328
381
|
const text = renderSummary(projectDecisions(items, opts.full), void 0, { color: opts.color ?? false });
|
|
329
382
|
return versions.unresolved.length > 0 ? `${text}\n⚠ ${unresolvedMessage(versions.unresolved)}` : text;
|
|
330
383
|
});
|
|
331
384
|
}
|
|
332
385
|
/**
|
|
386
|
+
* Map a check run's drift list to the process outcome. `--check` is a pure
|
|
387
|
+
* gate: exit 0 when every entry is in sync, exit 1 when an `upgrade --yes`
|
|
388
|
+
* would rewrite anything — the exit code IS the contract (a release
|
|
389
|
+
* validation phase calls this), and it never writes.
|
|
390
|
+
*
|
|
391
|
+
* @internal
|
|
392
|
+
*/
|
|
393
|
+
function checkOutcome(changed) {
|
|
394
|
+
if (changed.length === 0) return {
|
|
395
|
+
exitCode: 0,
|
|
396
|
+
text: "Catalogs are in sync.\n"
|
|
397
|
+
};
|
|
398
|
+
const list = changed.map((c) => ` ${c.name} (${c.source})`).join("\n");
|
|
399
|
+
return {
|
|
400
|
+
exitCode: 1,
|
|
401
|
+
text: `Catalog drift detected in ${changed.length} package(s):\n${list}\nRun \`rolldown-pnpm-config upgrade --yes\` to apply.\n`
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* Map a check run's FAILURE (a resolution failure or peer warning — an
|
|
406
|
+
* UpgradeError, not drift) to the process outcome. Shares --check's single
|
|
407
|
+
* non-zero exit code with drift, so the OUTPUT must name the failure family:
|
|
408
|
+
* a gate consuming the exit code reports every non-zero as "drifted", and
|
|
409
|
+
* without this label the CI log lies about a typo'd package or auth failure.
|
|
410
|
+
*
|
|
411
|
+
* @internal
|
|
412
|
+
*/
|
|
413
|
+
function checkFailureOutcome(message) {
|
|
414
|
+
return {
|
|
415
|
+
exitCode: 1,
|
|
416
|
+
text: `Catalog check failed before drift could be evaluated (resolution error, not drift):\n${message.split("\n").map((l) => ` ${l}`).join("\n")}\n`
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
/** Project a drift row to its stable `--json` object: catalog/pkg/from/to?/source, camelCase, `to` omitted (never null) when unknown. */
|
|
420
|
+
function driftRowJson(row) {
|
|
421
|
+
return {
|
|
422
|
+
catalog: row.catalog,
|
|
423
|
+
pkg: row.pkg,
|
|
424
|
+
from: row.from,
|
|
425
|
+
...row.to !== void 0 ? { to: row.to } : {},
|
|
426
|
+
source: row.source
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
/** Serialize one single-line JSON document, newline-terminated — the ONLY bytes `--json` puts on stdout. */
|
|
430
|
+
function jsonLine(doc) {
|
|
431
|
+
return `${JSON.stringify(doc)}\n`;
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* The `--check --json` outcome. stdout carries exactly one single-line JSON
|
|
435
|
+
* document in EVERY case — including the resolution-failure family, which
|
|
436
|
+
* keeps its non-zero exit but must never leave a bash gate with exit 1 and an
|
|
437
|
+
* empty stdout. The human failure label stays on stderr; the in-sync/drift
|
|
438
|
+
* text is superseded by the document.
|
|
439
|
+
*
|
|
440
|
+
* @internal
|
|
441
|
+
*/
|
|
442
|
+
function checkJsonOutcome(result) {
|
|
443
|
+
if (Result.isFailure(result)) {
|
|
444
|
+
const message = result.failure.message;
|
|
445
|
+
return {
|
|
446
|
+
exitCode: 1,
|
|
447
|
+
stdout: jsonLine({
|
|
448
|
+
command: "check",
|
|
449
|
+
inSync: false,
|
|
450
|
+
error: {
|
|
451
|
+
kind: result.failure.kind ?? "resolution",
|
|
452
|
+
message
|
|
453
|
+
}
|
|
454
|
+
}),
|
|
455
|
+
stderr: checkFailureOutcome(message).text
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
const changed = result.success.changed;
|
|
459
|
+
return {
|
|
460
|
+
exitCode: changed.length === 0 ? 0 : 1,
|
|
461
|
+
stdout: jsonLine({
|
|
462
|
+
command: "check",
|
|
463
|
+
inSync: changed.length === 0,
|
|
464
|
+
drift: changed.map(driftRowJson)
|
|
465
|
+
}),
|
|
466
|
+
stderr: ""
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* The `--yes --json` / `--dry-run --json` outcome. `applied` reports whether
|
|
471
|
+
* the run wrote at least one change — always false under dry-run AND on an
|
|
472
|
+
* already-in-sync run, so a consumer keying on it never reads a no-op as a
|
|
473
|
+
* real apply; `changed` uses the same row object as check's `drift`. A failure
|
|
474
|
+
* emits an error document on stdout with a non-zero exit and the human message
|
|
475
|
+
* on stderr; `error.kind` is `"peer-strategy"` for a peer-strategy refusal and
|
|
476
|
+
* `"resolution"` otherwise.
|
|
477
|
+
*
|
|
478
|
+
* @internal
|
|
479
|
+
*/
|
|
480
|
+
function upgradeJsonOutcome(result, dryRun) {
|
|
481
|
+
if (Result.isFailure(result)) {
|
|
482
|
+
const message = result.failure.message;
|
|
483
|
+
return {
|
|
484
|
+
exitCode: 1,
|
|
485
|
+
stdout: jsonLine({
|
|
486
|
+
command: "upgrade",
|
|
487
|
+
applied: false,
|
|
488
|
+
error: {
|
|
489
|
+
kind: result.failure.kind ?? "resolution",
|
|
490
|
+
message
|
|
491
|
+
}
|
|
492
|
+
}),
|
|
493
|
+
stderr: `${message}\n`
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
const r = result.success;
|
|
497
|
+
return {
|
|
498
|
+
exitCode: 0,
|
|
499
|
+
stdout: jsonLine({
|
|
500
|
+
command: "upgrade",
|
|
501
|
+
applied: !dryRun && r.updated > 0,
|
|
502
|
+
updated: r.updated,
|
|
503
|
+
changed: r.changed.map(driftRowJson),
|
|
504
|
+
skipped: r.skipped,
|
|
505
|
+
conflicts: r.conflicts.map((c) => ({
|
|
506
|
+
pkg: c.pkg,
|
|
507
|
+
ceiling: c.ceiling,
|
|
508
|
+
blockedBy: c.blockedBy
|
|
509
|
+
}))
|
|
510
|
+
}),
|
|
511
|
+
stderr: ""
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
/**
|
|
515
|
+
* Reject `--json` outside the non-interactive modes it exists for. JSON mode
|
|
516
|
+
* is a machine contract (a GitHub Action parsing stdout from bash); the
|
|
517
|
+
* interactive table and the preview views have no meaningful document to emit.
|
|
518
|
+
* Returns the rejection to fail with, or null when the combination is valid.
|
|
519
|
+
*
|
|
520
|
+
* @internal
|
|
521
|
+
*/
|
|
522
|
+
function validateJsonMode(flags) {
|
|
523
|
+
if (!flags.json) return null;
|
|
524
|
+
if (flags.preview) return new UpgradeError({ message: "--json cannot be combined with --preview; use --check, --yes, or --dry-run" });
|
|
525
|
+
if (!flags.check && !flags.yes && !flags.dryRun) return new UpgradeError({ message: "--json requires a non-interactive mode: combine it with --check, --yes, or --dry-run" });
|
|
526
|
+
return null;
|
|
527
|
+
}
|
|
528
|
+
/**
|
|
333
529
|
* Resolve the target file: the passed path, or autodetect in cwd.
|
|
334
530
|
*
|
|
335
531
|
* @internal
|
|
@@ -350,6 +546,8 @@ const dryRunFlag = Flag.boolean("dry-run").pipe(Flag.withDefault(false));
|
|
|
350
546
|
const catalogOption = Flag.string("catalog").pipe(Flag.optional);
|
|
351
547
|
const previewFlag = Flag.boolean("preview").pipe(Flag.withDefault(false));
|
|
352
548
|
const fullFlag = Flag.boolean("full").pipe(Flag.withDefault(false));
|
|
549
|
+
const checkFlag = Flag.boolean("check").pipe(Flag.withDefault(false));
|
|
550
|
+
const jsonFlag = Flag.boolean("json").pipe(Flag.withDefault(false));
|
|
353
551
|
/**
|
|
354
552
|
* The "upgrade" command. The default path runs the interactive table;
|
|
355
553
|
* --yes applies latest-in-range non-interactively; --dry-run runs the identical
|
|
@@ -364,17 +562,67 @@ const upgradeCommand = Command.make("upgrade", {
|
|
|
364
562
|
dryRun: dryRunFlag,
|
|
365
563
|
catalog: catalogOption,
|
|
366
564
|
preview: previewFlag,
|
|
367
|
-
full: fullFlag
|
|
368
|
-
|
|
565
|
+
full: fullFlag,
|
|
566
|
+
check: checkFlag,
|
|
567
|
+
json: jsonFlag
|
|
568
|
+
}, ({ file: fileOpt, yes, dryRun, catalog, preview, full, check, json }) => Effect.gen(function* () {
|
|
569
|
+
const jsonRejection = validateJsonMode({
|
|
570
|
+
json,
|
|
571
|
+
check,
|
|
572
|
+
yes,
|
|
573
|
+
dryRun,
|
|
574
|
+
preview
|
|
575
|
+
});
|
|
576
|
+
if (jsonRejection !== null) return yield* Effect.fail(jsonRejection);
|
|
369
577
|
const file = yield* resolveTargetFile(fileOpt);
|
|
370
578
|
const resolver = yield* RegistryResolver;
|
|
371
579
|
const caps = detectCapabilities();
|
|
580
|
+
const workspaceResolver = makeWorkspaceResolver(findWorkspaceRoot(dirname(file)));
|
|
581
|
+
if (check) {
|
|
582
|
+
const result = yield* runUpgrade({
|
|
583
|
+
file,
|
|
584
|
+
resolver,
|
|
585
|
+
workspaceResolver,
|
|
586
|
+
dryRun: true
|
|
587
|
+
}).pipe(Effect.result);
|
|
588
|
+
if (json) {
|
|
589
|
+
const o = checkJsonOutcome(result);
|
|
590
|
+
yield* Effect.sync(() => {
|
|
591
|
+
if (o.stderr !== "") process.stderr.write(o.stderr);
|
|
592
|
+
process.stdout.write(o.stdout);
|
|
593
|
+
process.exitCode = o.exitCode;
|
|
594
|
+
});
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
const failed = Result.isFailure(result);
|
|
598
|
+
const outcome = failed ? checkFailureOutcome(result.failure.message) : checkOutcome(result.success.changed);
|
|
599
|
+
yield* Effect.sync(() => {
|
|
600
|
+
(failed ? process.stderr : process.stdout).write(outcome.text);
|
|
601
|
+
process.exitCode = outcome.exitCode;
|
|
602
|
+
});
|
|
603
|
+
return;
|
|
604
|
+
}
|
|
605
|
+
if (json) {
|
|
606
|
+
const o = upgradeJsonOutcome(yield* runUpgrade({
|
|
607
|
+
file,
|
|
608
|
+
resolver,
|
|
609
|
+
dryRun,
|
|
610
|
+
workspaceResolver
|
|
611
|
+
}).pipe(Effect.result), dryRun);
|
|
612
|
+
yield* Effect.sync(() => {
|
|
613
|
+
if (o.stderr !== "") process.stderr.write(o.stderr);
|
|
614
|
+
process.stdout.write(o.stdout);
|
|
615
|
+
process.exitCode = o.exitCode;
|
|
616
|
+
});
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
372
619
|
if (preview) {
|
|
373
620
|
const text = yield* runUpgradePreview({
|
|
374
621
|
file,
|
|
375
622
|
resolver,
|
|
376
623
|
full,
|
|
377
|
-
color: caps.color
|
|
624
|
+
color: caps.color,
|
|
625
|
+
workspaceResolver
|
|
378
626
|
});
|
|
379
627
|
yield* Effect.sync(() => process.stdout.write(`${text}\n`));
|
|
380
628
|
return;
|
|
@@ -384,6 +632,7 @@ const upgradeCommand = Command.make("upgrade", {
|
|
|
384
632
|
file,
|
|
385
633
|
resolver,
|
|
386
634
|
dryRun,
|
|
635
|
+
workspaceResolver,
|
|
387
636
|
...caps.interactive ? { onProgress: writeResolveProgress } : {}
|
|
388
637
|
});
|
|
389
638
|
yield* Effect.sync(() => process.stdout.write(dryRun ? `Dry run — no changes written. ${result.updated} package(s) would be updated; skipped ${result.skipped.length}.\n` : `Updated ${result.updated} package(s); skipped ${result.skipped.length}.\n`));
|
|
@@ -403,7 +652,7 @@ const upgradeCommand = Command.make("upgrade", {
|
|
|
403
652
|
});
|
|
404
653
|
const catalogName = Option.getOrUndefined(catalog);
|
|
405
654
|
const entries = filterEntriesByCatalog(discovered.entries, catalogName);
|
|
406
|
-
const versions = yield* resolveGatedVersions(entries, resolver, yield* computeGate(source, file, resolver), Date.now(), caps.interactive ? writeResolveProgress : void 0);
|
|
655
|
+
const versions = yield* resolveGatedVersions(entries, resolver, yield* computeGate(source, file, resolver), Date.now(), caps.interactive ? writeResolveProgress : void 0, workspaceResolver);
|
|
407
656
|
const items = yield* buildWalkItems(entries, versions.gated).pipe(Effect.catch((e) => Effect.fail(new UpgradeError({ message: e.message }))));
|
|
408
657
|
if (!caps.interactive) {
|
|
409
658
|
const text = renderSummary(projectDecisions(items, full), void 0, { color: caps.color });
|
|
@@ -492,4 +741,4 @@ const upgradeCommand = Command.make("upgrade", {
|
|
|
492
741
|
}).pipe(Effect.provide(RegistryResolverLive), Effect.provide(NodeServices.layer))).pipe(Command.withDescription("Upgrade catalog versions in a config file"));
|
|
493
742
|
|
|
494
743
|
//#endregion
|
|
495
|
-
export { UpgradeError, applyInteropAndDecisions, computeGate, countChangedDecisions, nothingToUpgradeMessage, projectDecisions, resolveGatedVersions, resolveTargetFile, runUpgrade, runUpgradePreview, unresolvedMessage, upgradeCommand, writeResolveProgress };
|
|
744
|
+
export { UpgradeError, applyInteropAndDecisions, checkFailureOutcome, checkJsonOutcome, checkOutcome, computeGate, countChangedDecisions, nothingToUpgradeMessage, projectDecisions, resolveGatedVersions, resolveTargetFile, runUpgrade, runUpgradePreview, unresolvedMessage, upgradeCommand, upgradeJsonOutcome, validateJsonMode, writeResolveProgress };
|
package/cli/discover.js
CHANGED
|
@@ -90,6 +90,7 @@ function discoverCatalogEntries(source, filename) {
|
|
|
90
90
|
let rangeNode;
|
|
91
91
|
let peerNode;
|
|
92
92
|
let strategy;
|
|
93
|
+
let source;
|
|
93
94
|
if (value.type === "Literal" && typeof value.value === "string") rangeNode = value;
|
|
94
95
|
else if (value.type === "ObjectExpression") {
|
|
95
96
|
const r = prop(value, "range");
|
|
@@ -98,6 +99,8 @@ function discoverCatalogEntries(source, filename) {
|
|
|
98
99
|
if (p?.type === "Literal" && typeof p.value === "string") peerNode = p;
|
|
99
100
|
const s = prop(value, "strategy");
|
|
100
101
|
if (s?.type === "Literal" && (s.value === "lock" || s.value === "lock-minor" || s.value === "interop")) strategy = s.value;
|
|
102
|
+
const src = prop(value, "source");
|
|
103
|
+
if (src?.type === "Literal" && (src.value === "registry" || src.value === "workspace")) source = src.value;
|
|
101
104
|
}
|
|
102
105
|
if (!rangeNode || !SIMPLE_RANGE_RE.test(rangeNode.value)) {
|
|
103
106
|
skipped.push(`${catalog}.${pkg}`);
|
|
@@ -114,7 +117,8 @@ function discoverCatalogEntries(source, filename) {
|
|
|
114
117
|
value: peerNode.value,
|
|
115
118
|
span: [peerNode.start, peerNode.end]
|
|
116
119
|
} } : {},
|
|
117
|
-
...strategy ? { strategy } : {}
|
|
120
|
+
...strategy ? { strategy } : {},
|
|
121
|
+
...source ? { source } : {}
|
|
118
122
|
});
|
|
119
123
|
}
|
|
120
124
|
}
|
package/cli/edits.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { versionKeyOf } from "./version-key.js";
|
|
2
|
+
|
|
1
3
|
//#region src/cli/edits.ts
|
|
2
4
|
/**
|
|
3
5
|
* Convert resolved decisions into span edits. A chosen upgrade rewrites the
|
|
@@ -15,11 +17,13 @@ function buildEdits(decisions) {
|
|
|
15
17
|
for (const { item, chosen } of decisions) {
|
|
16
18
|
const { entry } = item;
|
|
17
19
|
const pkg = entry.pkg;
|
|
20
|
+
const versionKey = versionKeyOf(entry);
|
|
18
21
|
const insertAt = entry.rangeSpan[1];
|
|
19
22
|
const range = (span, value) => ({
|
|
20
23
|
span,
|
|
21
24
|
text: JSON.stringify(value),
|
|
22
25
|
pkg,
|
|
26
|
+
versionKey,
|
|
23
27
|
kind: "range",
|
|
24
28
|
value
|
|
25
29
|
});
|
|
@@ -27,6 +31,7 @@ function buildEdits(decisions) {
|
|
|
27
31
|
span,
|
|
28
32
|
text: JSON.stringify(value),
|
|
29
33
|
pkg,
|
|
34
|
+
versionKey,
|
|
30
35
|
kind: "peer",
|
|
31
36
|
value
|
|
32
37
|
});
|
|
@@ -34,6 +39,7 @@ function buildEdits(decisions) {
|
|
|
34
39
|
span: [insertAt, insertAt],
|
|
35
40
|
text: `, peer: ${JSON.stringify(value)}`,
|
|
36
41
|
pkg,
|
|
42
|
+
versionKey,
|
|
37
43
|
kind: "peer",
|
|
38
44
|
value
|
|
39
45
|
});
|
package/cli/validate.js
CHANGED
|
@@ -41,9 +41,10 @@ function rangeIsSatisfiable(range, versions) {
|
|
|
41
41
|
* same drift and re-reject it forever. A package is either fully updated or
|
|
42
42
|
* not touched at all.
|
|
43
43
|
*
|
|
44
|
-
* `versionsByPkg` MUST be the UNGATED version list
|
|
45
|
-
*
|
|
46
|
-
*
|
|
44
|
+
* `versionsByPkg` MUST be the UNGATED version list, keyed by each edit's
|
|
45
|
+
* route-aware `versionKey` (bare package name for registry-routed edits).
|
|
46
|
+
* Validating against the release-age-gated list would spuriously reject a
|
|
47
|
+
* package whose only matching version was published inside the gate window.
|
|
47
48
|
*
|
|
48
49
|
* @internal
|
|
49
50
|
*/
|
|
@@ -51,14 +52,16 @@ function validateEdits(edits, versionsByPkg) {
|
|
|
51
52
|
return Effect.gen(function* () {
|
|
52
53
|
const byPkg = /* @__PURE__ */ new Map();
|
|
53
54
|
for (const e of edits) {
|
|
54
|
-
const
|
|
55
|
+
const key = e.versionKey ?? e.pkg;
|
|
56
|
+
const group = byPkg.get(key) ?? [];
|
|
55
57
|
group.push(e);
|
|
56
|
-
byPkg.set(
|
|
58
|
+
byPkg.set(key, group);
|
|
57
59
|
}
|
|
58
60
|
const accepted = [];
|
|
59
61
|
const rejected = [];
|
|
60
|
-
for (const [
|
|
61
|
-
const
|
|
62
|
+
for (const [key, group] of byPkg) {
|
|
63
|
+
const pkg = group[0]?.pkg ?? key;
|
|
64
|
+
const versions = versionsByPkg.get(key) ?? [];
|
|
62
65
|
const checked = [];
|
|
63
66
|
for (const e of group) checked.push({
|
|
64
67
|
edit: e,
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
//#region src/cli/version-key.ts
|
|
2
|
+
/**
|
|
3
|
+
* Route-aware key for the resolved-version maps returned by
|
|
4
|
+
* `resolveGatedVersions`. The same package name can appear workspace-sourced
|
|
5
|
+
* in one catalog and registry-sourced in another, and the two routes resolve
|
|
6
|
+
* through DIFFERENT resolvers to different version lists (with different
|
|
7
|
+
* release-age-gate treatment) — so the maps key each (pkg × route) pair
|
|
8
|
+
* separately. The registry route keys by the bare package name; `:` can never
|
|
9
|
+
* appear in an npm package name, so the prefixed workspace form cannot collide.
|
|
10
|
+
*
|
|
11
|
+
* @internal
|
|
12
|
+
*/
|
|
13
|
+
function versionKeyOf(entry) {
|
|
14
|
+
return entry.source === "workspace" ? `workspace:${entry.pkg}` : entry.pkg;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
//#endregion
|
|
18
|
+
export { versionKeyOf };
|
package/cli/walk-plan.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { derivePeerRange } from "./peer-range.js";
|
|
2
2
|
import { detectPeerDrift } from "./drift.js";
|
|
3
|
+
import { versionKeyOf } from "./version-key.js";
|
|
3
4
|
import { planEntry } from "./plan.js";
|
|
4
5
|
import { Effect } from "effect";
|
|
5
6
|
|
|
@@ -16,7 +17,7 @@ function buildWalkItems(entries, versionsByPkg) {
|
|
|
16
17
|
return Effect.gen(function* () {
|
|
17
18
|
const items = [];
|
|
18
19
|
for (const entry of entries) {
|
|
19
|
-
const versions = versionsByPkg.get(entry
|
|
20
|
+
const versions = versionsByPkg.get(versionKeyOf(entry)) ?? [];
|
|
20
21
|
const candidates = yield* planEntry(entry, [...versions]);
|
|
21
22
|
const driftPeer = yield* detectPeerDrift(entry);
|
|
22
23
|
let peerWarning = null;
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { ResolveError } from "./resolve.js";
|
|
2
|
+
import { Effect, Layer } from "effect";
|
|
3
|
+
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
4
|
+
import { dirname, join, resolve } from "node:path";
|
|
5
|
+
import { SemVer } from "@effected/semver";
|
|
6
|
+
import { getWorkspacePackagesSync } from "@effected/workspaces";
|
|
7
|
+
import { nodeSyncOps } from "@effected/workspaces/node-sync";
|
|
8
|
+
|
|
9
|
+
//#region src/cli/workspace-resolve.ts
|
|
10
|
+
const BUMP_ORDER = {
|
|
11
|
+
patch: 0,
|
|
12
|
+
minor: 1,
|
|
13
|
+
major: 2
|
|
14
|
+
};
|
|
15
|
+
/** Matches one changeset frontmatter line: `"@scope/name": minor` (quotes optional). */
|
|
16
|
+
const FRONTMATTER_LINE_RE = /^\s*["']?([^"':\s]+)["']?\s*:\s*(major|minor|patch)\s*$/;
|
|
17
|
+
/**
|
|
18
|
+
* Read every workspace package manifest under `rootDir` with a usable name and
|
|
19
|
+
* version, honoring the workspace's own `pnpm-workspace.yaml` `packages:`
|
|
20
|
+
* globs (including exclusion patterns) via `@effected/workspaces` — never a
|
|
21
|
+
* hardcoded `packages/` directory.
|
|
22
|
+
*
|
|
23
|
+
* There is deliberately NO publishability filter: the resolver's job is
|
|
24
|
+
* finding packages and their versions, full stop. Whether and how a package
|
|
25
|
+
* publishes is the author's decision, made elsewhere — and catalog membership
|
|
26
|
+
* is already an explicit choice in the consuming config.
|
|
27
|
+
*
|
|
28
|
+
* The sync discovery facade is total by contract — an unreadable or malformed
|
|
29
|
+
* manifest (including a version-less workspace root) is skipped, never raised —
|
|
30
|
+
* which is exactly this resolver's degrade-don't-fail contract; the Effect
|
|
31
|
+
* surface (`WorkspaceDiscovery`) instead fails typed on any unusable member.
|
|
32
|
+
*
|
|
33
|
+
* @internal
|
|
34
|
+
*/
|
|
35
|
+
function readManifests(rootDir) {
|
|
36
|
+
const out = /* @__PURE__ */ new Map();
|
|
37
|
+
for (const pkg of getWorkspacePackagesSync(rootDir, nodeSyncOps)) out.set(pkg.name, {
|
|
38
|
+
name: pkg.name,
|
|
39
|
+
version: pkg.version,
|
|
40
|
+
peerDependencies: pkg.peerDependencies
|
|
41
|
+
});
|
|
42
|
+
return out;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Read the pending changeset bumps from `<rootDir>/.changeset/*.md` (skipping
|
|
46
|
+
* README.md): each file's YAML frontmatter (between the first two `---` lines)
|
|
47
|
+
* maps package name → bump level; the strongest bump per package wins.
|
|
48
|
+
* An absent or unreadable `.changeset/` yields an empty map.
|
|
49
|
+
*/
|
|
50
|
+
function readPendingBumps(rootDir) {
|
|
51
|
+
const out = /* @__PURE__ */ new Map();
|
|
52
|
+
const dir = join(rootDir, ".changeset");
|
|
53
|
+
if (!existsSync(dir)) return out;
|
|
54
|
+
let names;
|
|
55
|
+
try {
|
|
56
|
+
names = readdirSync(dir).filter((n) => n.endsWith(".md") && n !== "README.md");
|
|
57
|
+
} catch {
|
|
58
|
+
return out;
|
|
59
|
+
}
|
|
60
|
+
for (const name of names) {
|
|
61
|
+
let text;
|
|
62
|
+
try {
|
|
63
|
+
text = readFileSync(join(dir, name), "utf8");
|
|
64
|
+
} catch {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
const lines = text.split("\n");
|
|
68
|
+
const fences = [];
|
|
69
|
+
for (let i = 0; i < lines.length && fences.length < 2; i++) if (lines[i]?.trim() === "---") fences.push(i);
|
|
70
|
+
const [open, close] = fences;
|
|
71
|
+
if (open === void 0 || close === void 0) continue;
|
|
72
|
+
for (const line of lines.slice(open + 1, close)) {
|
|
73
|
+
const m = FRONTMATTER_LINE_RE.exec(line);
|
|
74
|
+
if (!m) continue;
|
|
75
|
+
const [, pkg, bump] = m;
|
|
76
|
+
const prev = out.get(pkg);
|
|
77
|
+
if (prev === void 0 || BUMP_ORDER[bump] > BUMP_ORDER[prev]) out.set(pkg, bump);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return out;
|
|
81
|
+
}
|
|
82
|
+
/** Apply the strongest pending bump for each package already present in `versions`. */
|
|
83
|
+
function applyPendingBumps(rootDir, versions) {
|
|
84
|
+
const bumps = readPendingBumps(rootDir);
|
|
85
|
+
for (const [pkg, bump] of bumps) {
|
|
86
|
+
const current = versions.get(pkg);
|
|
87
|
+
if (current === void 0) continue;
|
|
88
|
+
let parsed;
|
|
89
|
+
try {
|
|
90
|
+
parsed = Effect.runSync(SemVer.parse(current));
|
|
91
|
+
} catch {
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
versions.set(pkg, parsed.bump[bump]().toString());
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Read every workspace package's NEXT release version: the current
|
|
99
|
+
* manifest version overlaid with the bump implied by any pending changeset.
|
|
100
|
+
* Absent or unreadable `.changeset/` degrades to current versions.
|
|
101
|
+
*
|
|
102
|
+
* @internal
|
|
103
|
+
*/
|
|
104
|
+
function readWorkspaceVersions(rootDir) {
|
|
105
|
+
const out = /* @__PURE__ */ new Map();
|
|
106
|
+
for (const [name, manifest] of readManifests(rootDir)) out.set(name, manifest.version);
|
|
107
|
+
applyPendingBumps(rootDir, out);
|
|
108
|
+
return out;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* The plain (layer-less) workspace resolver value, for callers that route
|
|
112
|
+
* per-entry between the registry service and the workspace instead of
|
|
113
|
+
* providing one layer for the whole run. Reads lazily on first use so
|
|
114
|
+
* constructing it for a config with no workspace-sourced entries costs nothing.
|
|
115
|
+
*
|
|
116
|
+
* @internal
|
|
117
|
+
*/
|
|
118
|
+
function makeWorkspaceResolver(rootDir) {
|
|
119
|
+
let manifests;
|
|
120
|
+
let versions;
|
|
121
|
+
const load = () => {
|
|
122
|
+
manifests ??= readManifests(rootDir);
|
|
123
|
+
versions ??= readWorkspaceVersions(rootDir);
|
|
124
|
+
return {
|
|
125
|
+
manifests,
|
|
126
|
+
versions
|
|
127
|
+
};
|
|
128
|
+
};
|
|
129
|
+
return {
|
|
130
|
+
versions: (pkg) => {
|
|
131
|
+
const v = load().versions.get(pkg);
|
|
132
|
+
return v === void 0 ? Effect.fail(new ResolveError({
|
|
133
|
+
pkg,
|
|
134
|
+
message: `${pkg} is not a workspace package`
|
|
135
|
+
})) : Effect.succeed([v]);
|
|
136
|
+
},
|
|
137
|
+
times: () => Effect.succeed({}),
|
|
138
|
+
peerDependencies: (pkg) => Effect.succeed(load().manifests.get(pkg)?.peerDependencies ?? {}),
|
|
139
|
+
pnpmConfig: () => Effect.succeed(null)
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Walk upward from `startDir` to the nearest directory containing
|
|
144
|
+
* `pnpm-workspace.yaml`, falling back to `startDir` itself. The config file an
|
|
145
|
+
* upgrade run rewrites may live in a nested package (e.g.
|
|
146
|
+
* `packages/<name>/savvy.build.ts`), while the workspace versions live at the
|
|
147
|
+
* repo root.
|
|
148
|
+
*
|
|
149
|
+
* @internal
|
|
150
|
+
*/
|
|
151
|
+
function findWorkspaceRoot(startDir) {
|
|
152
|
+
let dir = resolve(startDir);
|
|
153
|
+
for (;;) {
|
|
154
|
+
if (existsSync(join(dir, "pnpm-workspace.yaml"))) return dir;
|
|
155
|
+
const parent = dirname(dir);
|
|
156
|
+
if (parent === dir) return resolve(startDir);
|
|
157
|
+
dir = parent;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
//#endregion
|
|
162
|
+
export { findWorkspaceRoot, makeWorkspaceResolver, readManifests, readWorkspaceVersions };
|
package/index.d.ts
CHANGED
|
@@ -8,9 +8,22 @@ import { Plugin } from "rolldown";
|
|
|
8
8
|
* @public
|
|
9
9
|
*/
|
|
10
10
|
type PeerStrategy = "lock" | "lock-minor" | "interop";
|
|
11
|
+
/**
|
|
12
|
+
* Where a catalog entry's `range` is resolved from by the `upgrade` CLI.
|
|
13
|
+
* `"registry"` (the default when omitted) queries published versions;
|
|
14
|
+
* `"workspace"` reads the local workspace's NEXT release versions (current
|
|
15
|
+
* manifest versions overlaid with pending changeset bumps). Orthogonal to
|
|
16
|
+
* `strategy`, which governs how `peer` is derived from `range` — collapsing
|
|
17
|
+
* the two would drop peer materialization entirely. CLI-only metadata; the
|
|
18
|
+
* runtime ignores it.
|
|
19
|
+
*
|
|
20
|
+
* @public
|
|
21
|
+
*/
|
|
22
|
+
type VersionSource = "registry" | "workspace";
|
|
11
23
|
/**
|
|
12
24
|
* A package's version: a bare range, or an object carrying a materialized peer
|
|
13
|
-
* range (`peer`)
|
|
25
|
+
* range (`peer`), optional CLI recompute `strategy`, and optional CLI version
|
|
26
|
+
* `source`.
|
|
14
27
|
*
|
|
15
28
|
* @public
|
|
16
29
|
*/
|
|
@@ -18,6 +31,7 @@ type CatalogPackageSpec = string | {
|
|
|
18
31
|
readonly range: string;
|
|
19
32
|
readonly peer?: string;
|
|
20
33
|
readonly strategy?: PeerStrategy;
|
|
34
|
+
readonly source?: VersionSource;
|
|
21
35
|
};
|
|
22
36
|
/**
|
|
23
37
|
* One catalog's declaration: a map of package name to version spec.
|
|
@@ -406,5 +420,5 @@ interface PluginConfig {
|
|
|
406
420
|
*/
|
|
407
421
|
declare function PnpmConfigPlugin(config: PluginConfig): Plugin;
|
|
408
422
|
//#endregion
|
|
409
|
-
export { type AllowedVersionsFromCatalogs, type CatalogDeclaration, type CatalogPackageSpec, type Enforcement, type FieldInput, type LocalDirective, type PeerStrategy, type PluginConfig, PnpmConfigPlugin };
|
|
423
|
+
export { type AllowedVersionsFromCatalogs, type CatalogDeclaration, type CatalogPackageSpec, type Enforcement, type FieldInput, type LocalDirective, type PeerStrategy, type PluginConfig, PnpmConfigPlugin, type VersionSource };
|
|
410
424
|
//# sourceMappingURL=index.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rolldown-pnpm-config",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "A dogfooding example of our plugin",
|
|
6
6
|
"repository": {
|
|
@@ -36,8 +36,9 @@
|
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"@effect/platform-node": "4.0.0-rc.109",
|
|
39
|
-
"@effected/npm": "^0.11.
|
|
39
|
+
"@effected/npm": "^0.11.1",
|
|
40
40
|
"@effected/semver": "^0.5.0",
|
|
41
|
+
"@effected/workspaces": "^0.17.0",
|
|
41
42
|
"@effected/yaml": "^0.10.0",
|
|
42
43
|
"effect": "4.0.0-rc.109",
|
|
43
44
|
"ink": "^7.1.1",
|