rolldown-pnpm-config 0.8.1 → 1.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/bin/rolldown-pnpm-config.js +1 -1
- package/cli/ast.js +54 -0
- package/cli/commands/export.js +14 -33
- package/cli/commands/preview.js +7 -20
- package/cli/commands/upgrade.js +72 -142
- package/cli/diff/build.js +3 -3
- package/cli/diff/render.js +2 -1
- package/cli/discover.js +5 -38
- package/cli/edits.js +40 -28
- package/cli/evaluate.js +2 -24
- package/cli/interop-live.js +5 -13
- package/cli/interop.js +55 -65
- package/cli/load-config.js +40 -0
- package/cli/local-merge.js +7 -8
- package/cli/plan.js +37 -51
- package/cli/release-age.js +3 -4
- package/cli/resolve.js +40 -63
- package/cli/simulated-view.js +1 -3
- package/cli/summary.js +13 -45
- package/cli/ui/Walk.js +3 -12
- package/cli/ui/env.js +3 -5
- package/cli/ui/run-preview.js +6 -7
- package/cli/ui/run-walk.js +7 -4
- package/cli/validate.js +12 -16
- package/cli/walk-plan.js +2 -2
- package/cli/walk-reducer.js +29 -1
- package/cli/workspace-file.js +3 -3
- package/cli/workspace-resolve.js +9 -17
- package/package.json +2 -3
- package/patches/build.js +24 -10
- package/plugin/allowed-versions.js +2 -2
- package/plugin/freeze.js +13 -6
- package/runtime/strategies/arrays.js +2 -1
- package/runtime/strategies/catalogs.js +5 -14
- package/runtime/strategies/overrides.js +11 -4
- package/runtime/strategies/scalar.js +4 -4
- package/runtime/strategies/table.js +1 -1
- package/semver-util.js +29 -0
|
@@ -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: "1.0.0" }).pipe(Effect.provide(NodeServices.layer), NodeRuntime.runMain);
|
|
16
16
|
|
|
17
17
|
//#endregion
|
|
18
18
|
export { };
|
package/cli/ast.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
//#region src/cli/ast.ts
|
|
2
|
+
/**
|
|
3
|
+
* The static name of an ObjectExpression property key: an Identifier's name
|
|
4
|
+
* or a Literal's stringified value; undefined for a computed key.
|
|
5
|
+
*
|
|
6
|
+
* @internal
|
|
7
|
+
*/
|
|
8
|
+
function keyName(key) {
|
|
9
|
+
return key.type === "Identifier" ? key.name : key.type === "Literal" ? String(key.value) : void 0;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Find a property value by key name in an ObjectExpression node. Handles both
|
|
13
|
+
* Identifier keys (unquoted) and Literal keys (quoted).
|
|
14
|
+
*
|
|
15
|
+
* @internal
|
|
16
|
+
*/
|
|
17
|
+
function prop(obj, key) {
|
|
18
|
+
for (const p of obj.properties ?? []) {
|
|
19
|
+
if (p.type !== "Property") continue;
|
|
20
|
+
if (keyName(p.key) === key) return p.value;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Find the first `PnpmConfigPlugin(...)` call's first argument (an object
|
|
25
|
+
* literal). The single AST walker behind both static discovery (`upgrade`)
|
|
26
|
+
* and static evaluation (`export` / `preview`), so the two always agree on
|
|
27
|
+
* which call they found.
|
|
28
|
+
*
|
|
29
|
+
* @internal
|
|
30
|
+
*/
|
|
31
|
+
function findPluginArg(program) {
|
|
32
|
+
let found;
|
|
33
|
+
const visit = (node) => {
|
|
34
|
+
if (found || node === null || typeof node !== "object") return;
|
|
35
|
+
const n = node;
|
|
36
|
+
if (n.type === "CallExpression") {
|
|
37
|
+
const callee = n.callee;
|
|
38
|
+
if (callee?.type === "Identifier" && callee.name === "PnpmConfigPlugin") {
|
|
39
|
+
const args = n.arguments;
|
|
40
|
+
if (args?.[0]?.type === "ObjectExpression") {
|
|
41
|
+
found = args[0];
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
for (const value of Object.values(n)) if (Array.isArray(value)) value.forEach(visit);
|
|
47
|
+
else if (value && typeof value === "object") visit(value);
|
|
48
|
+
};
|
|
49
|
+
visit(program);
|
|
50
|
+
return found;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
//#endregion
|
|
54
|
+
export { findPluginArg, keyName, prop };
|
package/cli/commands/export.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { isRewriteDirective, readLocalPatchesDir, withResolvedBuildPatches } from "../../patches/build.js";
|
|
1
|
+
import { discoverOwnedPatches, withResolvedBuildPatches } from "../../patches/build.js";
|
|
3
2
|
import { DESCRIPTORS } from "../../descriptors/index.js";
|
|
4
3
|
import { freeze } from "../../plugin/freeze.js";
|
|
5
4
|
import { resolveRootName } from "../../runtime/ctx.js";
|
|
@@ -7,16 +6,16 @@ import { reconcilePatches } from "../../patches/reconcile.js";
|
|
|
7
6
|
import { buildDiff } from "../diff/build.js";
|
|
8
7
|
import { renderExportDiff } from "../diff/render.js";
|
|
9
8
|
import { effectiveManaged } from "../effective.js";
|
|
10
|
-
import {
|
|
9
|
+
import { canonicalize, renderWorkspace } from "../workspace-file.js";
|
|
10
|
+
import { loadConfigAndWorkspace } from "../load-config.js";
|
|
11
11
|
import { findConfigFiles, pickConfigCandidate } from "../select-file.js";
|
|
12
12
|
import { toAnsi } from "../ui/ansi.js";
|
|
13
13
|
import { detectCapabilities } from "../ui/env.js";
|
|
14
14
|
import { legendLines } from "../ui/legend.js";
|
|
15
|
-
import { canonicalize, findWorkspaceFile, parseWorkspace, renderWorkspace } from "../workspace-file.js";
|
|
16
15
|
import { overlayWorkspace } from "../workspace-overlay.js";
|
|
17
|
-
import { Data, Effect, Option } from "effect";
|
|
18
|
-
import { existsSync,
|
|
19
|
-
import { dirname,
|
|
16
|
+
import { Data, Effect, Option, Predicate } from "effect";
|
|
17
|
+
import { existsSync, writeFileSync } from "node:fs";
|
|
18
|
+
import { dirname, relative } from "node:path";
|
|
20
19
|
import { Argument, Command, Flag } from "effect/unstable/cli";
|
|
21
20
|
|
|
22
21
|
//#region src/cli/commands/export.ts
|
|
@@ -43,43 +42,25 @@ const WORKSPACE_FIELDS = new Set(Object.entries(DESCRIPTORS).filter(([, d]) => d
|
|
|
43
42
|
*/
|
|
44
43
|
function runExport(opts) {
|
|
45
44
|
return Effect.gen(function* () {
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const { config, errors } = evaluatePluginConfig(configSource, opts.configFile);
|
|
51
|
-
if (config === null) return yield* Effect.fail(new ExportError({ message: `No PnpmConfigPlugin call found in ${opts.configFile}` }));
|
|
52
|
-
if (errors.length > 0) return yield* Effect.fail(new ExportError({ message: `Non-literal config values: ${errors.join("; ")}` }));
|
|
53
|
-
const resolvedConfig = withResolvedBuildPatches(config, dirname(opts.configFile));
|
|
45
|
+
const { config, localCfg, path, parsed } = yield* loadConfigAndWorkspace(opts, (message) => new ExportError({ message }));
|
|
46
|
+
const pluginConfig = config;
|
|
47
|
+
const owned = discoverOwnedPatches(pluginConfig, dirname(opts.configFile));
|
|
48
|
+
const resolvedConfig = withResolvedBuildPatches(pluginConfig, dirname(opts.configFile), owned);
|
|
54
49
|
const { base, manifest } = yield* freeze(resolvedConfig).pipe(Effect.mapError((e) => new ExportError({ message: e.message })));
|
|
55
50
|
const managed = {};
|
|
56
51
|
for (const [k, v] of Object.entries(base)) if (WORKSPACE_FIELDS.has(k)) managed[k] = v;
|
|
57
|
-
const path = opts.workspacePath ?? findWorkspaceFile(process.cwd()) ?? join(process.cwd(), "pnpm-workspace.yaml");
|
|
58
|
-
const parsed = existsSync(path) ? yield* Effect.try({
|
|
59
|
-
try: () => parseWorkspace(readFileSync(path, "utf8")),
|
|
60
|
-
catch: (e) => new ExportError({ message: `Cannot read or parse ${path}: ${String(e)}` })
|
|
61
|
-
}) : {};
|
|
62
52
|
const rootName = resolveRootName({ dir: dirname(path) });
|
|
63
|
-
const localCfg = config.local && typeof config.local === "object" ? config.local : void 0;
|
|
64
53
|
const effective = effectiveManaged(managed, localCfg, parsed, manifest, rootName);
|
|
65
|
-
const
|
|
66
|
-
const explicitPatchMap = rawPatched !== void 0 && !isRewriteDirective(rawPatched);
|
|
54
|
+
const explicitPatchMap = owned === void 0;
|
|
67
55
|
const contributed = {};
|
|
68
56
|
if (explicitPatchMap) {
|
|
69
|
-
|
|
70
|
-
if (explicit !== null && typeof explicit === "object" && !Array.isArray(explicit)) Object.assign(contributed, explicit);
|
|
57
|
+
if (Predicate.isObject(effective.patchedDependencies)) Object.assign(contributed, effective.patchedDependencies);
|
|
71
58
|
} else {
|
|
72
|
-
const localPatchesDir = readLocalPatchesDir(config);
|
|
73
|
-
const owned = discoverPatches({
|
|
74
|
-
baseDir: dirname(opts.configFile),
|
|
75
|
-
name: typeof config.name === "string" ? config.name : "",
|
|
76
|
-
...localPatchesDir !== void 0 ? { localPatchesDir } : {}
|
|
77
|
-
});
|
|
78
59
|
const workspaceRoot = dirname(path);
|
|
79
60
|
for (const p of owned) contributed[p.key] = relative(workspaceRoot, p.absPath).split(/[\\/]/).join("/");
|
|
80
61
|
}
|
|
81
62
|
if (Object.keys(contributed).length > 0) effective.patchedDependencies = {
|
|
82
|
-
...
|
|
63
|
+
...parsed.patchedDependencies ?? {},
|
|
83
64
|
...contributed
|
|
84
65
|
};
|
|
85
66
|
const report = reconcilePatches({
|
|
@@ -89,7 +70,7 @@ function runExport(opts) {
|
|
|
89
70
|
});
|
|
90
71
|
const merged = overlayWorkspace(effective, parsed);
|
|
91
72
|
const rendered = renderWorkspace(merged);
|
|
92
|
-
const localKeys = new Set(
|
|
73
|
+
const localKeys = new Set(Object.keys(localCfg ?? {}));
|
|
93
74
|
const tree = buildDiff(canonicalize(parsed), canonicalize(merged), {
|
|
94
75
|
localKeys,
|
|
95
76
|
managedKeys: WORKSPACE_FIELDS
|
package/cli/commands/preview.js
CHANGED
|
@@ -1,17 +1,14 @@
|
|
|
1
1
|
import { freeze } from "../../plugin/freeze.js";
|
|
2
2
|
import { resolveRootName } from "../../runtime/ctx.js";
|
|
3
|
-
import {
|
|
3
|
+
import { loadConfigAndWorkspace } from "../load-config.js";
|
|
4
4
|
import { findConfigFiles, pickConfigCandidate } from "../select-file.js";
|
|
5
5
|
import { toAnsi } from "../ui/ansi.js";
|
|
6
6
|
import { detectCapabilities } from "../ui/env.js";
|
|
7
7
|
import { legendLines } from "../ui/legend.js";
|
|
8
|
-
import { findWorkspaceFile, parseWorkspace } from "../workspace-file.js";
|
|
9
8
|
import { WORKSPACE_FIELDS } from "./export.js";
|
|
10
9
|
import { buildPreviewViews } from "../preview-views.js";
|
|
11
|
-
import { runPreview } from "../ui/run-preview.js";
|
|
12
10
|
import { Data, Effect, Option } from "effect";
|
|
13
|
-
import {
|
|
14
|
-
import { dirname, join } from "node:path";
|
|
11
|
+
import { dirname } from "node:path";
|
|
15
12
|
import { Argument, Command } from "effect/unstable/cli";
|
|
16
13
|
|
|
17
14
|
//#region src/cli/commands/preview.ts
|
|
@@ -25,22 +22,10 @@ var PreviewError = class extends Data.TaggedError("PreviewError") {};
|
|
|
25
22
|
*/
|
|
26
23
|
function runPreviewViews(opts) {
|
|
27
24
|
return Effect.gen(function* () {
|
|
28
|
-
const
|
|
29
|
-
try: () => readFileSync(opts.configFile, "utf8"),
|
|
30
|
-
catch: () => new PreviewError({ message: `Cannot read ${opts.configFile}` })
|
|
31
|
-
});
|
|
32
|
-
const { config, errors } = evaluatePluginConfig(configSource, opts.configFile);
|
|
33
|
-
if (config === null) return yield* Effect.fail(new PreviewError({ message: `No PnpmConfigPlugin call found in ${opts.configFile}` }));
|
|
34
|
-
if (errors.length > 0) return yield* Effect.fail(new PreviewError({ message: `Non-literal config values: ${errors.join("; ")}` }));
|
|
25
|
+
const { config, localCfg, path, parsed } = yield* loadConfigAndWorkspace(opts, (message) => new PreviewError({ message }));
|
|
35
26
|
const { base, manifest } = yield* freeze(config).pipe(Effect.mapError((e) => new PreviewError({ message: e.message })));
|
|
36
27
|
const managed = {};
|
|
37
28
|
for (const [k, v] of Object.entries(base)) if (WORKSPACE_FIELDS.has(k)) managed[k] = v;
|
|
38
|
-
const path = opts.workspacePath ?? findWorkspaceFile(process.cwd()) ?? join(process.cwd(), "pnpm-workspace.yaml");
|
|
39
|
-
const parsed = existsSync(path) ? yield* Effect.try({
|
|
40
|
-
try: () => parseWorkspace(readFileSync(path, "utf8")),
|
|
41
|
-
catch: (e) => new PreviewError({ message: `Cannot read or parse ${path}: ${String(e)}` })
|
|
42
|
-
}) : {};
|
|
43
|
-
const localCfg = config.local && typeof config.local === "object" ? config.local : void 0;
|
|
44
29
|
return buildPreviewViews({
|
|
45
30
|
managed,
|
|
46
31
|
...localCfg ? { local: localCfg } : {},
|
|
@@ -68,8 +53,10 @@ const previewCommand = Command.make("preview", { path: pathArg }, ({ path }) =>
|
|
|
68
53
|
...workspacePath !== void 0 ? { workspacePath } : {}
|
|
69
54
|
});
|
|
70
55
|
const caps = detectCapabilities();
|
|
71
|
-
if (caps.interactive)
|
|
72
|
-
|
|
56
|
+
if (caps.interactive) {
|
|
57
|
+
const { runPreview } = yield* Effect.promise(() => import("../ui/run-preview.js"));
|
|
58
|
+
yield* runPreview(views);
|
|
59
|
+
} else yield* Effect.sync(() => {
|
|
73
60
|
const legend = caps.color ? `${toAnsi(legendLines(), { color: caps.color })}\n\n` : "";
|
|
74
61
|
process.stdout.write(`${legend}${toAnsi(views.changes, { color: caps.color })}\n`);
|
|
75
62
|
});
|
package/cli/commands/upgrade.js
CHANGED
|
@@ -1,19 +1,18 @@
|
|
|
1
|
+
import { bareVersion } from "../../semver-util.js";
|
|
1
2
|
import { evaluatePluginConfig } from "../evaluate.js";
|
|
2
3
|
import { discoverCatalogEntries } from "../discover.js";
|
|
3
4
|
import { filterEntriesByCatalog, findConfigFiles, pickConfigCandidate } from "../select-file.js";
|
|
4
5
|
import { detectCapabilities } from "../ui/env.js";
|
|
5
|
-
import { derivePeerRange } from "../peer-range.js";
|
|
6
|
-
import { detectPeerDrift } from "../drift.js";
|
|
7
6
|
import { versionKeyOf } from "../version-key.js";
|
|
8
|
-
import { buildEdits } from "../edits.js";
|
|
9
|
-
import { buildInteropEdits, interopEntryChanged, runInterop } from "../interop.js";
|
|
7
|
+
import { buildEdits, entryEdits } from "../edits.js";
|
|
8
|
+
import { buildInteropEdits, interopEntryChanged, makePeerFetcher, runInterop } from "../interop.js";
|
|
10
9
|
import { buildGroupModel, computeGroupPeers } from "../interop-live.js";
|
|
11
|
-
import {
|
|
10
|
+
import { derivePeerRange } from "../peer-range.js";
|
|
11
|
+
import { defaultPick, planEntry } from "../plan.js";
|
|
12
12
|
import { parsePnpmGate, readConfigReleaseAge } from "../release-age.js";
|
|
13
13
|
import { RegistryResolver, RegistryResolverLive } from "../resolve.js";
|
|
14
14
|
import { applyEdits } from "../rewrite.js";
|
|
15
15
|
import { renderSummary } from "../summary.js";
|
|
16
|
-
import { runWalk } from "../ui/run-walk.js";
|
|
17
16
|
import { validateEdits } from "../validate.js";
|
|
18
17
|
import { buildWalkItems } from "../walk-plan.js";
|
|
19
18
|
import { findWorkspaceRoot, makeWorkspaceResolver } from "../workspace-resolve.js";
|
|
@@ -31,12 +30,30 @@ import { ReleaseAgeGate } from "@effected/npm";
|
|
|
31
30
|
* @internal
|
|
32
31
|
*/
|
|
33
32
|
var UpgradeError = class extends Data.TaggedError("UpgradeError") {};
|
|
33
|
+
/** Read a config file and statically discover its catalog entries. @internal */
|
|
34
|
+
function readCatalogSource(file) {
|
|
35
|
+
return Effect.gen(function* () {
|
|
36
|
+
const source = yield* Effect.try({
|
|
37
|
+
try: () => readFileSync(file, "utf8"),
|
|
38
|
+
catch: () => new UpgradeError({ message: `Cannot read ${file}` })
|
|
39
|
+
});
|
|
40
|
+
const { entries, skipped } = yield* Effect.try({
|
|
41
|
+
try: () => discoverCatalogEntries(source, file),
|
|
42
|
+
catch: (e) => new UpgradeError({ message: String(e) })
|
|
43
|
+
});
|
|
44
|
+
return {
|
|
45
|
+
source,
|
|
46
|
+
entries,
|
|
47
|
+
skipped
|
|
48
|
+
};
|
|
49
|
+
});
|
|
50
|
+
}
|
|
34
51
|
/** Combine the config-declared and pnpm-resolved release-age gates (strictest of both). @internal */
|
|
35
52
|
function computeGate(source, file, resolver) {
|
|
36
53
|
return Effect.gen(function* () {
|
|
37
|
-
const { config } = yield* Effect.try(() => evaluatePluginConfig(source, file)).pipe(Effect.
|
|
54
|
+
const { config } = yield* Effect.try(() => evaluatePluginConfig(source, file)).pipe(Effect.orElseSucceed(() => ({ config: null })));
|
|
38
55
|
const cfg = readConfigReleaseAge(config);
|
|
39
|
-
const [age, exc] = yield* Effect.all([resolver.pnpmConfig("minimumReleaseAge").pipe(Effect.
|
|
56
|
+
const [age, exc] = yield* Effect.all([resolver.pnpmConfig("minimumReleaseAge").pipe(Effect.orElseSucceed(() => null)), resolver.pnpmConfig("minimumReleaseAgeExclude").pipe(Effect.orElseSucceed(() => null))], { concurrency: "unbounded" });
|
|
40
57
|
const contributions = [cfg, parsePnpmGate(age, exc)].filter((g) => g !== null);
|
|
41
58
|
return ReleaseAgeGate.combine(...contributions);
|
|
42
59
|
});
|
|
@@ -70,27 +87,22 @@ function resolveGatedVersions(entries, resolver, gate, now, onProgress, workspac
|
|
|
70
87
|
let resolved = 0;
|
|
71
88
|
onProgress?.(0, total);
|
|
72
89
|
return Effect.forEach([...pairs], ([key, { pkg, fromWorkspace }]) => Effect.gen(function* () {
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
return [
|
|
77
|
-
key,
|
|
78
|
-
pkg,
|
|
79
|
-
[],
|
|
80
|
-
[]
|
|
81
|
-
];
|
|
82
|
-
}
|
|
83
|
-
if (fromWorkspace) {
|
|
84
|
-
onProgress?.(++resolved, total);
|
|
85
|
-
return [
|
|
86
|
-
key,
|
|
87
|
-
pkg,
|
|
88
|
-
vr.success,
|
|
89
|
-
vr.success
|
|
90
|
-
];
|
|
91
|
-
}
|
|
92
|
-
const times = gate.ageMinutes > 0 ? yield* resolver.times(pkg).pipe(Effect.catch(() => Effect.succeed({}))) : {};
|
|
90
|
+
const routed = fromWorkspace && workspace !== void 0 ? workspace : resolver;
|
|
91
|
+
const needTimes = !fromWorkspace && gate.ageMinutes > 0;
|
|
92
|
+
const [vr, times] = yield* Effect.all([routed.versions(pkg).pipe(Effect.result), needTimes ? resolver.times(pkg).pipe(Effect.orElseSucceed(() => ({}))) : Effect.succeed({})], { concurrency: "unbounded" });
|
|
93
93
|
onProgress?.(++resolved, total);
|
|
94
|
+
if (Result.isFailure(vr)) return [
|
|
95
|
+
key,
|
|
96
|
+
pkg,
|
|
97
|
+
[],
|
|
98
|
+
[]
|
|
99
|
+
];
|
|
100
|
+
if (fromWorkspace) return [
|
|
101
|
+
key,
|
|
102
|
+
pkg,
|
|
103
|
+
vr.success,
|
|
104
|
+
vr.success
|
|
105
|
+
];
|
|
94
106
|
return [
|
|
95
107
|
key,
|
|
96
108
|
pkg,
|
|
@@ -137,14 +149,7 @@ function writeResolveProgress(resolved, total) {
|
|
|
137
149
|
*/
|
|
138
150
|
function runUpgrade(opts) {
|
|
139
151
|
return Effect.gen(function* () {
|
|
140
|
-
const source = yield*
|
|
141
|
-
try: () => readFileSync(opts.file, "utf8"),
|
|
142
|
-
catch: () => new UpgradeError({ message: `Cannot read ${opts.file}` })
|
|
143
|
-
});
|
|
144
|
-
const { entries, skipped } = yield* Effect.try({
|
|
145
|
-
try: () => discoverCatalogEntries(source, opts.file),
|
|
146
|
-
catch: (e) => new UpgradeError({ message: String(e) })
|
|
147
|
-
});
|
|
152
|
+
const { source, entries, skipped } = yield* readCatalogSource(opts.file);
|
|
148
153
|
const gate = yield* computeGate(source, opts.file, opts.resolver);
|
|
149
154
|
const versionsByPkg = yield* resolveGatedVersions(entries, opts.resolver, gate, Date.now(), opts.onProgress, opts.workspaceResolver);
|
|
150
155
|
if (versionsByPkg.unresolved.length > 0) return yield* Effect.fail(new UpgradeError({ message: unresolvedMessage(versionsByPkg.unresolved) }));
|
|
@@ -167,70 +172,21 @@ function runUpgrade(opts) {
|
|
|
167
172
|
};
|
|
168
173
|
for (const entry of entries) {
|
|
169
174
|
if (entry.strategy === "interop") continue;
|
|
170
|
-
const
|
|
171
|
-
const
|
|
172
|
-
const
|
|
173
|
-
const rangeEdit = (span, value) => ({
|
|
174
|
-
span,
|
|
175
|
-
text: JSON.stringify(value),
|
|
176
|
-
pkg,
|
|
177
|
-
versionKey,
|
|
178
|
-
kind: "range",
|
|
179
|
-
value
|
|
180
|
-
});
|
|
181
|
-
const peerEdit = (span, value) => ({
|
|
182
|
-
span,
|
|
183
|
-
text: JSON.stringify(value),
|
|
184
|
-
pkg,
|
|
185
|
-
versionKey,
|
|
186
|
-
kind: "peer",
|
|
187
|
-
value
|
|
188
|
-
});
|
|
189
|
-
const peerInsert = (at, value) => ({
|
|
190
|
-
span: [at, at],
|
|
191
|
-
text: `, peer: ${JSON.stringify(value)}`,
|
|
192
|
-
pkg,
|
|
193
|
-
versionKey,
|
|
194
|
-
kind: "peer",
|
|
195
|
-
value
|
|
196
|
-
});
|
|
197
|
-
const derived = entry.strategy ? yield* derivePeerRange(entry.currentRange, entry.strategy).pipe(Effect.catch(() => Effect.succeed(null))) : null;
|
|
175
|
+
const versions = versionsByPkg.gated.get(versionKeyOf(entry)) ?? [];
|
|
176
|
+
const { range, setPeer } = entryEdits(entry);
|
|
177
|
+
const derived = entry.strategy ? yield* derivePeerRange(entry.currentRange, entry.strategy).pipe(Effect.orElseSucceed(() => null)) : null;
|
|
198
178
|
if (derived?.warning) warnings.push(`${entry.pkg}: ${derived.warning.message}`);
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
} else if (!entry.peer && entry.strategy && derived !== null) {
|
|
209
|
-
edits.push(peerInsert(at, derived.range));
|
|
210
|
-
markChanged(entry);
|
|
211
|
-
continue;
|
|
212
|
-
}
|
|
213
|
-
skipped.push(`${entry.catalog}.${entry.pkg}`);
|
|
214
|
-
continue;
|
|
215
|
-
}
|
|
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");
|
|
218
|
-
const at = entry.rangeSpan[1];
|
|
219
|
-
if (inRange) {
|
|
220
|
-
edits.push(rangeEdit(entry.rangeSpan, inRange.range));
|
|
221
|
-
markChanged(entry, inRange.range);
|
|
222
|
-
if (entry.peer && inRange.peerRange) edits.push(peerEdit(entry.peer.span, inRange.peerRange));
|
|
223
|
-
else if (!entry.peer && entry.strategy && inRange.peerRange) edits.push(peerInsert(at, inRange.peerRange));
|
|
224
|
-
} else if (!entry.peer && entry.strategy && derived !== null) {
|
|
225
|
-
edits.push(peerInsert(at, derived.range));
|
|
179
|
+
const peerOnly = derived === null ? null : entry.peer ? derived.range === entry.peer.value ? null : derived.range : derived.range;
|
|
180
|
+
const candidates = versions.length === 0 ? [] : yield* planEntry(entry, versions).pipe(Effect.orElseSucceed(() => []));
|
|
181
|
+
const pick = defaultPick(entry, candidates);
|
|
182
|
+
if (pick) {
|
|
183
|
+
edits.push(range(pick.range));
|
|
184
|
+
markChanged(entry, pick.range);
|
|
185
|
+
if (pick.peerRange) edits.push(setPeer(pick.peerRange));
|
|
186
|
+
} else if (peerOnly !== null) {
|
|
187
|
+
edits.push(setPeer(peerOnly));
|
|
226
188
|
markChanged(entry);
|
|
227
|
-
} else if (
|
|
228
|
-
const expected = yield* detectPeerDrift(entry).pipe(Effect.catch(() => Effect.succeed(null)));
|
|
229
|
-
if (expected !== null) {
|
|
230
|
-
edits.push(peerEdit(entry.peer.span, expected));
|
|
231
|
-
markChanged(entry);
|
|
232
|
-
}
|
|
233
|
-
}
|
|
189
|
+
} else if (versions.length === 0) skipped.push(`${entry.catalog}.${entry.pkg}`);
|
|
234
190
|
}
|
|
235
191
|
const interopEntries = entries.filter((e) => e.strategy === "interop");
|
|
236
192
|
const conflicts = [];
|
|
@@ -244,8 +200,8 @@ function runUpgrade(opts) {
|
|
|
244
200
|
const members = [];
|
|
245
201
|
for (const e of group) {
|
|
246
202
|
const versions = versionsByPkg.gated.get(versionKeyOf(e)) ?? [];
|
|
247
|
-
const inRange = (yield* planEntry(e, versions).pipe(Effect.
|
|
248
|
-
const ceiling = inRange ? inRange.version : e.currentRange
|
|
203
|
+
const inRange = (yield* planEntry(e, versions).pipe(Effect.orElseSucceed(() => []))).find((c) => c.kind === "in-range");
|
|
204
|
+
const ceiling = inRange ? inRange.version : bareVersion(e.currentRange);
|
|
249
205
|
members.push({
|
|
250
206
|
pkg: e.pkg,
|
|
251
207
|
ceiling,
|
|
@@ -336,11 +292,11 @@ function unresolvedMessage(unresolved) {
|
|
|
336
292
|
function projectDecisions(items, full) {
|
|
337
293
|
const out = [];
|
|
338
294
|
for (const i of items) {
|
|
339
|
-
const
|
|
340
|
-
if (
|
|
295
|
+
const pick = defaultPick(i.entry, i.candidates);
|
|
296
|
+
if (pick) {
|
|
341
297
|
out.push({
|
|
342
298
|
item: i,
|
|
343
|
-
chosen:
|
|
299
|
+
chosen: pick
|
|
344
300
|
});
|
|
345
301
|
continue;
|
|
346
302
|
}
|
|
@@ -367,17 +323,10 @@ function projectDecisions(items, full) {
|
|
|
367
323
|
/** Build the colored preview summary string without writing. @internal */
|
|
368
324
|
function runUpgradePreview(opts) {
|
|
369
325
|
return Effect.gen(function* () {
|
|
370
|
-
const source = yield*
|
|
371
|
-
try: () => readFileSync(opts.file, "utf8"),
|
|
372
|
-
catch: () => new UpgradeError({ message: `Cannot read ${opts.file}` })
|
|
373
|
-
});
|
|
374
|
-
const discovered = yield* Effect.try({
|
|
375
|
-
try: () => discoverCatalogEntries(source, opts.file),
|
|
376
|
-
catch: (e) => new UpgradeError({ message: String(e) })
|
|
377
|
-
});
|
|
326
|
+
const { source, entries } = yield* readCatalogSource(opts.file);
|
|
378
327
|
const gate = yield* computeGate(source, opts.file, opts.resolver);
|
|
379
|
-
const versions = yield* resolveGatedVersions(
|
|
380
|
-
const items = yield* buildWalkItems(
|
|
328
|
+
const versions = yield* resolveGatedVersions(entries, opts.resolver, gate, Date.now(), void 0, opts.workspaceResolver);
|
|
329
|
+
const items = yield* buildWalkItems(entries, versions.gated).pipe(Effect.mapError((e) => new UpgradeError({ message: e.message })));
|
|
381
330
|
const text = renderSummary(projectDecisions(items, opts.full), void 0, { color: opts.color ?? false });
|
|
382
331
|
return versions.unresolved.length > 0 ? `${text}\n⚠ ${unresolvedMessage(versions.unresolved)}` : text;
|
|
383
332
|
});
|
|
@@ -642,18 +591,11 @@ const upgradeCommand = Command.make("upgrade", {
|
|
|
642
591
|
}
|
|
643
592
|
return;
|
|
644
593
|
}
|
|
645
|
-
const source = yield*
|
|
646
|
-
try: () => readFileSync(file, "utf8"),
|
|
647
|
-
catch: () => new UpgradeError({ message: `Cannot read ${file}` })
|
|
648
|
-
});
|
|
649
|
-
const discovered = yield* Effect.try({
|
|
650
|
-
try: () => discoverCatalogEntries(source, file),
|
|
651
|
-
catch: (e) => new UpgradeError({ message: String(e) })
|
|
652
|
-
});
|
|
594
|
+
const { source, entries: discovered } = yield* readCatalogSource(file);
|
|
653
595
|
const catalogName = Option.getOrUndefined(catalog);
|
|
654
|
-
const entries = filterEntriesByCatalog(discovered
|
|
596
|
+
const entries = filterEntriesByCatalog(discovered, catalogName);
|
|
655
597
|
const versions = yield* resolveGatedVersions(entries, resolver, yield* computeGate(source, file, resolver), Date.now(), caps.interactive ? writeResolveProgress : void 0, workspaceResolver);
|
|
656
|
-
const items = yield* buildWalkItems(entries, versions.gated).pipe(Effect.
|
|
598
|
+
const items = yield* buildWalkItems(entries, versions.gated).pipe(Effect.mapError((e) => new UpgradeError({ message: e.message })));
|
|
657
599
|
if (!caps.interactive) {
|
|
658
600
|
const text = renderSummary(projectDecisions(items, full), void 0, { color: caps.color });
|
|
659
601
|
const note = dryRun ? "(dry run — nothing written)" : "(non-interactive terminal — run with --yes to apply, or in a TTY to choose)";
|
|
@@ -673,26 +615,18 @@ const upgradeCommand = Command.make("upgrade", {
|
|
|
673
615
|
list.push(e);
|
|
674
616
|
interopByCatalog.set(e.catalog, list);
|
|
675
617
|
}
|
|
676
|
-
const
|
|
677
|
-
const fetchPeer = (pkg, v) => {
|
|
678
|
-
const k = `${pkg}@${v}`;
|
|
679
|
-
const cached = peerCache.get(k);
|
|
680
|
-
if (cached !== void 0) return Effect.succeed(cached);
|
|
681
|
-
return resolver.peerDependencies(pkg, v).pipe(Effect.catch(() => Effect.succeed({})), Effect.map((deps) => {
|
|
682
|
-
peerCache.set(k, deps);
|
|
683
|
-
return deps;
|
|
684
|
-
}));
|
|
685
|
-
};
|
|
618
|
+
const fetchPeer = makePeerFetcher(resolver);
|
|
686
619
|
if (interopByCatalog.size > 0) yield* Effect.sync(() => process.stderr.write("Resolving peer dependencies…\n"));
|
|
687
620
|
const interopModels = /* @__PURE__ */ new Map();
|
|
688
621
|
for (const [catalog, group] of interopByCatalog) {
|
|
689
622
|
const candByPkg = /* @__PURE__ */ new Map();
|
|
690
623
|
for (const e of group) {
|
|
691
624
|
const it = items.find((i) => i.entry.catalog === catalog && i.entry.pkg === e.pkg);
|
|
692
|
-
candByPkg.set(e.pkg, it ? it.candidates.map((c) => c.version) : [e.currentRange
|
|
625
|
+
candByPkg.set(e.pkg, it ? it.candidates.map((c) => c.version) : [bareVersion(e.currentRange)]);
|
|
693
626
|
}
|
|
694
627
|
interopModels.set(catalog, yield* buildGroupModel(candByPkg, fetchPeer));
|
|
695
628
|
}
|
|
629
|
+
const { runWalk } = yield* Effect.promise(() => import("../ui/run-walk.js"));
|
|
696
630
|
const decisions = yield* runWalk(items, dryRun, versions.unresolved, interopModels);
|
|
697
631
|
const nonInteropDecisions = decisions.filter((d) => d.item.entry.strategy !== "interop");
|
|
698
632
|
const interopEdits = [];
|
|
@@ -704,14 +638,13 @@ const upgradeCommand = Command.make("upgrade", {
|
|
|
704
638
|
const selected = /* @__PURE__ */ new Map();
|
|
705
639
|
for (const e of group) {
|
|
706
640
|
const d = decisions.find((dd) => dd.item.entry.catalog === catalog && dd.item.entry.pkg === e.pkg);
|
|
707
|
-
selected.set(e.pkg, d ? d.chosen.version : e.currentRange
|
|
641
|
+
selected.set(e.pkg, d ? d.chosen.version : bareVersion(e.currentRange));
|
|
708
642
|
}
|
|
709
643
|
const { peer, conflict } = computeGroupPeers(model, selected);
|
|
710
644
|
interopEdits.push(...buildInteropEdits(group, {
|
|
711
645
|
resolved: selected,
|
|
712
646
|
peers: peer,
|
|
713
|
-
conflicts: []
|
|
714
|
-
peerDepsOf: () => ({})
|
|
647
|
+
conflicts: []
|
|
715
648
|
}));
|
|
716
649
|
for (const [pkg, blockedBy] of conflict) allConflicts.push({
|
|
717
650
|
pkg,
|
|
@@ -730,10 +663,7 @@ const upgradeCommand = Command.make("upgrade", {
|
|
|
730
663
|
const planned = buildEdits(nonInteropDecisions);
|
|
731
664
|
const { accepted, rejected } = yield* validateEdits(planned, versions.raw);
|
|
732
665
|
const acceptedPkgs = new Set(accepted.map((e) => e.pkg));
|
|
733
|
-
yield* Effect.sync(() => process.stdout.write(`${renderSummary(decisions, {
|
|
734
|
-
adjustments: [],
|
|
735
|
-
conflicts: allConflicts
|
|
736
|
-
}, { color: caps.color }, rejected)}\n`));
|
|
666
|
+
yield* Effect.sync(() => process.stdout.write(`${renderSummary(decisions, { conflicts: allConflicts }, { color: caps.color }, rejected)}\n`));
|
|
737
667
|
if (!dryRun) yield* applyInteropAndDecisions(file, source, accepted, interopEdits);
|
|
738
668
|
const changed = countChangedDecisions(nonInteropDecisions.filter((d) => acceptedPkgs.has(d.item.entry.pkg))) + interopChanged;
|
|
739
669
|
yield* Effect.sync(() => process.stdout.write(dryRun ? `Dry run — no changes written. ${changed} change(s) would be applied.\n` : `Applied ${changed} change(s).\n`));
|
|
@@ -741,4 +671,4 @@ const upgradeCommand = Command.make("upgrade", {
|
|
|
741
671
|
}).pipe(Effect.provide(RegistryResolverLive), Effect.provide(NodeServices.layer))).pipe(Command.withDescription("Upgrade catalog versions in a config file"));
|
|
742
672
|
|
|
743
673
|
//#endregion
|
|
744
|
-
export { UpgradeError, applyInteropAndDecisions, checkFailureOutcome, checkJsonOutcome, checkOutcome, computeGate, countChangedDecisions, nothingToUpgradeMessage, projectDecisions, resolveGatedVersions, resolveTargetFile, runUpgrade, runUpgradePreview, unresolvedMessage, upgradeCommand, upgradeJsonOutcome, validateJsonMode, writeResolveProgress };
|
|
674
|
+
export { UpgradeError, applyInteropAndDecisions, checkFailureOutcome, checkJsonOutcome, checkOutcome, computeGate, countChangedDecisions, nothingToUpgradeMessage, projectDecisions, readCatalogSource, resolveGatedVersions, resolveTargetFile, runUpgrade, runUpgradePreview, unresolvedMessage, upgradeCommand, upgradeJsonOutcome, validateJsonMode, writeResolveProgress };
|
package/cli/diff/build.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import { Predicate } from "effect";
|
|
2
|
+
|
|
1
3
|
//#region src/cli/diff/build.ts
|
|
2
|
-
|
|
3
|
-
return v !== null && typeof v === "object" && !Array.isArray(v);
|
|
4
|
-
}
|
|
4
|
+
const isObject = Predicate.isObject;
|
|
5
5
|
/** Worst kind among children: changed if any differs, added/removed if uniform, else unchanged. */
|
|
6
6
|
function rollup(children) {
|
|
7
7
|
if (children.length === 0) return "unchanged";
|
package/cli/diff/render.js
CHANGED
|
@@ -12,6 +12,7 @@ const STYLE = {
|
|
|
12
12
|
changed: "changed",
|
|
13
13
|
unchanged: "unchanged"
|
|
14
14
|
};
|
|
15
|
+
/** A scalar as YAML-shaped text: strings verbatim, everything else JSON. @internal */
|
|
15
16
|
function scalarText(v) {
|
|
16
17
|
return typeof v === "string" ? v : JSON.stringify(v);
|
|
17
18
|
}
|
|
@@ -101,4 +102,4 @@ function renderExportDiff(root, opts) {
|
|
|
101
102
|
}
|
|
102
103
|
|
|
103
104
|
//#endregion
|
|
104
|
-
export { renderExportDiff };
|
|
105
|
+
export { renderExportDiff, scalarText };
|
package/cli/discover.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { findPluginArg, keyName, prop } from "./ast.js";
|
|
1
2
|
import { Data } from "effect";
|
|
2
3
|
import { parseSync } from "oxc-parser";
|
|
3
4
|
|
|
@@ -16,40 +17,6 @@ function operatorOf(range) {
|
|
|
16
17
|
return "";
|
|
17
18
|
}
|
|
18
19
|
/**
|
|
19
|
-
* Find a property value by key name in an ObjectExpression node.
|
|
20
|
-
* Handles both Identifier keys (unquoted) and Literal keys (quoted).
|
|
21
|
-
*/
|
|
22
|
-
function prop(obj, key) {
|
|
23
|
-
const properties = obj.properties ?? [];
|
|
24
|
-
for (const p of properties) {
|
|
25
|
-
if (p.type !== "Property") continue;
|
|
26
|
-
const k = p.key;
|
|
27
|
-
if ((k.type === "Identifier" ? k.name : k.type === "Literal" ? String(k.value) : void 0) === key) return p.value;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
/** Find the first `PnpmConfigPlugin(...)` CallExpression's first argument object. */
|
|
31
|
-
function findPluginArg(program) {
|
|
32
|
-
let found;
|
|
33
|
-
const visit = (node) => {
|
|
34
|
-
if (found || node === null || typeof node !== "object") return;
|
|
35
|
-
const n = node;
|
|
36
|
-
if (n.type === "CallExpression") {
|
|
37
|
-
const callee = n.callee;
|
|
38
|
-
if (callee?.type === "Identifier" && callee.name === "PnpmConfigPlugin") {
|
|
39
|
-
const args = n.arguments;
|
|
40
|
-
if (args?.[0]?.type === "ObjectExpression") {
|
|
41
|
-
found = args[0];
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
for (const value of Object.values(n)) if (Array.isArray(value)) value.forEach(visit);
|
|
47
|
-
else if (value && typeof value === "object") visit(value);
|
|
48
|
-
};
|
|
49
|
-
visit(program);
|
|
50
|
-
return found;
|
|
51
|
-
}
|
|
52
|
-
/**
|
|
53
20
|
* Statically discover the catalog version literals in a config source. Locates
|
|
54
21
|
* the single `PnpmConfigPlugin(...)` call and walks `.catalogs.<name>.packages`.
|
|
55
22
|
* Each package whose range is a simple-operator string literal yields a
|
|
@@ -76,16 +43,16 @@ function discoverCatalogEntries(source, filename) {
|
|
|
76
43
|
};
|
|
77
44
|
for (const catProp of catalogs.properties ?? []) {
|
|
78
45
|
if (catProp.type !== "Property") continue;
|
|
79
|
-
const
|
|
80
|
-
|
|
46
|
+
const catalog = keyName(catProp.key);
|
|
47
|
+
if (catalog === void 0) continue;
|
|
81
48
|
const decl = catProp.value;
|
|
82
49
|
if (decl.type !== "ObjectExpression") continue;
|
|
83
50
|
const packages = prop(decl, "packages");
|
|
84
51
|
if (packages?.type !== "ObjectExpression") continue;
|
|
85
52
|
for (const pkgProp of packages.properties ?? []) {
|
|
86
53
|
if (pkgProp.type !== "Property") continue;
|
|
87
|
-
const
|
|
88
|
-
|
|
54
|
+
const pkg = keyName(pkgProp.key);
|
|
55
|
+
if (pkg === void 0) continue;
|
|
89
56
|
const value = pkgProp.value;
|
|
90
57
|
let rangeNode;
|
|
91
58
|
let peerNode;
|