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/cli/resolve.js CHANGED
@@ -1,4 +1,4 @@
1
- import { Context, Data, Effect, Layer } from "effect";
1
+ import { Context, Data, Effect, Layer, Predicate } from "effect";
2
2
  import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";
3
3
 
4
4
  //#region src/cli/resolve.ts
@@ -17,6 +17,18 @@ var ResolveError = class extends Data.TaggedError("ResolveError") {};
17
17
  * @internal
18
18
  */
19
19
  var RegistryResolver = class extends Context.Service()("RegistryResolver") {};
20
+ /**
21
+ * Drop pnpm's own notice lines from captured stdout. pnpm prints e.g.
22
+ * `[WARN] This project is configured to use 12.5.0… Your current pnpm is v12.4.2`
23
+ * (a `packageManager` mismatch) to STDOUT ahead of the `--json` payload, which
24
+ * would otherwise make every `pnpm view` unparseable and report each real
25
+ * package as an unresolvable typo.
26
+ *
27
+ * @internal
28
+ */
29
+ function stripPnpmNotices(stdout) {
30
+ return stdout.replace(/^[ \t]*(?:\[WARN\]|\[ERR\]|WARN\b|ERR_PNPM_\w+)[^\n]*\n?/gm, "");
31
+ }
20
32
  /** Parse `pnpm view ... versions --json` stdout: a JSON array, or a single JSON string. */
21
33
  function parseVersions(pkg, stdout) {
22
34
  return Effect.try({
@@ -32,43 +44,31 @@ function parseVersions(pkg, stdout) {
32
44
  })
33
45
  });
34
46
  }
35
- /** Parse `pnpm view <pkg> time --json` stdout: an object of version → ISO date. @internal */
36
- function parseTimes(pkg, stdout) {
47
+ /** Parse a `pnpm view ... --json` object of string values into a string record. */
48
+ function parseStringRecord(pkg, stdout, what) {
37
49
  return Effect.try({
38
50
  try: () => {
39
51
  const json = JSON.parse(stdout);
40
- if (json && typeof json === "object" && !Array.isArray(json)) {
41
- const out = {};
42
- for (const [k, v] of Object.entries(json)) out[k] = String(v);
43
- return out;
44
- }
45
- throw new Error("unexpected shape");
52
+ if (!Predicate.isObject(json)) throw new Error("unexpected shape");
53
+ const out = {};
54
+ for (const [k, v] of Object.entries(json)) out[k] = String(v);
55
+ return out;
46
56
  },
47
57
  catch: () => new ResolveError({
48
58
  pkg,
49
- message: `Could not parse times for ${pkg}`
59
+ message: `Could not parse ${what} for ${pkg}`
50
60
  })
51
61
  });
52
62
  }
63
+ /** Parse `pnpm view <pkg> time --json` stdout: an object of version → ISO date. @internal */
64
+ function parseTimes(pkg, stdout) {
65
+ return parseStringRecord(pkg, stdout, "times");
66
+ }
53
67
  /** Parse `pnpm view <pkg>@<version> peerDependencies --json`; empty stdout → {}. @internal */
54
68
  function parsePeerDeps(pkg, stdout) {
55
69
  const trimmed = stdout.trim();
56
70
  if (trimmed === "") return Effect.succeed({});
57
- return Effect.try({
58
- try: () => {
59
- const json = JSON.parse(trimmed);
60
- if (json && typeof json === "object" && !Array.isArray(json)) {
61
- const out = {};
62
- for (const [k, v] of Object.entries(json)) out[k] = String(v);
63
- return out;
64
- }
65
- throw new Error("unexpected shape");
66
- },
67
- catch: () => new ResolveError({
68
- pkg,
69
- message: `Could not parse peerDependencies for ${pkg}`
70
- })
71
- });
71
+ return parseStringRecord(pkg, trimmed, "peerDependencies");
72
72
  }
73
73
  /**
74
74
  * Live RegistryResolver backed by `pnpm view <pkg> versions --json`.
@@ -77,53 +77,30 @@ function parsePeerDeps(pkg, stdout) {
77
77
  */
78
78
  const RegistryResolverLive = Layer.effect(RegistryResolver, Effect.gen(function* () {
79
79
  const spawner = yield* Spawner;
80
+ /** `pnpm view <spec> <field> --json` stdout, with a spawn failure typed against `pkg`. */
81
+ const pnpmView = (pkg, spec, field) => spawner.string(ChildProcess.make("pnpm", [
82
+ "view",
83
+ spec,
84
+ field,
85
+ "--json"
86
+ ])).pipe(Effect.map(stripPnpmNotices), Effect.mapError((e) => new ResolveError({
87
+ pkg,
88
+ message: String(e)
89
+ })));
80
90
  return {
81
- versions: (pkg) => Effect.gen(function* () {
82
- const cmd = ChildProcess.make("pnpm", [
83
- "view",
84
- pkg,
85
- "versions",
86
- "--json"
87
- ]);
88
- return yield* parseVersions(pkg, yield* spawner.string(cmd).pipe(Effect.mapError((e) => new ResolveError({
89
- pkg,
90
- message: String(e)
91
- }))));
92
- }),
93
- times: (pkg) => Effect.gen(function* () {
94
- const cmd = ChildProcess.make("pnpm", [
95
- "view",
96
- pkg,
97
- "time",
98
- "--json"
99
- ]);
100
- return yield* parseTimes(pkg, yield* spawner.string(cmd).pipe(Effect.mapError((e) => new ResolveError({
101
- pkg,
102
- message: String(e)
103
- }))));
104
- }),
105
- peerDependencies: (pkg, version) => Effect.gen(function* () {
106
- const cmd = ChildProcess.make("pnpm", [
107
- "view",
108
- `${pkg}@${version}`,
109
- "peerDependencies",
110
- "--json"
111
- ]);
112
- return yield* parsePeerDeps(pkg, yield* spawner.string(cmd).pipe(Effect.mapError((e) => new ResolveError({
113
- pkg,
114
- message: String(e)
115
- }))));
116
- }),
91
+ versions: (pkg) => pnpmView(pkg, pkg, "versions").pipe(Effect.flatMap((out) => parseVersions(pkg, out))),
92
+ times: (pkg) => pnpmView(pkg, pkg, "time").pipe(Effect.flatMap((out) => parseTimes(pkg, out))),
93
+ peerDependencies: (pkg, version) => pnpmView(pkg, `${pkg}@${version}`, "peerDependencies").pipe(Effect.flatMap((out) => parsePeerDeps(pkg, out))),
117
94
  pnpmConfig: (key) => Effect.gen(function* () {
118
95
  const cmd = ChildProcess.make("pnpm", [
119
96
  "config",
120
97
  "get",
121
98
  key
122
99
  ]);
123
- return yield* spawner.string(cmd).pipe(Effect.map((s) => s.trim()), Effect.catch(() => Effect.succeed(null)));
100
+ return yield* spawner.string(cmd).pipe(Effect.map((s) => stripPnpmNotices(s).trim()), Effect.orElseSucceed(() => null));
124
101
  })
125
102
  };
126
103
  }));
127
104
 
128
105
  //#endregion
129
- export { RegistryResolver, RegistryResolverLive, ResolveError, parsePeerDeps, parseTimes, parseVersions };
106
+ export { RegistryResolver, RegistryResolverLive, ResolveError, parsePeerDeps, parseTimes, parseVersions, stripPnpmNotices };
@@ -1,3 +1,4 @@
1
+ import { scalarText } from "./diff/render.js";
1
2
  import { canonicalize } from "./workspace-file.js";
2
3
 
3
4
  //#region src/cli/simulated-view.ts
@@ -51,9 +52,6 @@ function annotation(entry) {
51
52
  }
52
53
  ];
53
54
  }
54
- function scalarText(v) {
55
- return typeof v === "string" ? v : JSON.stringify(v);
56
- }
57
55
  /** Flatten one key/value into YAML-shaped plain lines; `ann` annotates the head. */
58
56
  function flatten(key, value, depth, ann) {
59
57
  if (Array.isArray(value)) return [{
package/cli/summary.js CHANGED
@@ -1,19 +1,12 @@
1
1
  import { toAnsi } from "./ui/ansi.js";
2
- import { displayCandidates, peerFor } from "./walk-reducer.js";
2
+ import { displayCandidates, peerFor, tableLayout } from "./walk-reducer.js";
3
3
 
4
4
  //#region src/cli/summary.ts
5
- /** Trailing annotation appended to a major candidate's cell, e.g. " ⚠ major". */
6
- const MAJOR_SUFFIX = " ⚠ major";
7
- /** Filled / hollow radio glyphs. MUST match `ui/Walk.ts` — the summary mirrors the table. */
8
- const SELECTED = "●";
9
- const UNSELECTED = "○";
10
- /** "● " / "○ " glyph-plus-space prefix width, common to every cell. */
11
- const BUBBLE_WIDTH = 2;
12
5
  /**
13
6
  * Build the pending-decisions summary as styled lines: one table row per
14
7
  * decision — mirroring the interactive selection table, catalog headers,
15
- * chosen bubble filled — then a dim tally, interop adjustments, conflicts,
16
- * and any rejected edits. Pure; color is applied by `renderSummary`/`toAnsi`.
8
+ * chosen bubble filled — then a dim tally, interop conflicts, and any
9
+ * rejected edits. Pure; color is applied by `renderSummary`/`toAnsi`.
17
10
  *
18
11
  * @internal
19
12
  */
@@ -24,10 +17,7 @@ function summaryLines(decisions, interop, rejected) {
24
17
  let resync = 0;
25
18
  let materialize = 0;
26
19
  let upToDate = 0;
27
- const pkgWidth = decisions.length ? Math.max(...decisions.map((d) => d.item.entry.pkg.length)) : 0;
28
- const cellWidth = decisions.length ? Math.max(...decisions.flatMap((d) => displayCandidates(d.item).map((c) => c.range.length + (c.isMajor ? 8 : 0)))) : 0;
29
- const maxCells = decisions.length ? Math.max(...decisions.map((d) => displayCandidates(d.item).length)) : 0;
30
- const blankCell = `${" ".repeat(BUBBLE_WIDTH + cellWidth)} `;
20
+ const { pkgWidth, maxCells, blankCell, cellText } = tableLayout(decisions.map((d) => d.item));
31
21
  let lastCatalog = null;
32
22
  for (const { item, chosen } of decisions) {
33
23
  const { entry } = item;
@@ -49,11 +39,9 @@ function summaryLines(decisions, interop, rejected) {
49
39
  }];
50
40
  for (const c of cells) {
51
41
  const selected = c.kind === chosen.kind;
52
- const bubble = selected ? SELECTED : UNSELECTED;
53
42
  const style = !selected ? "unchanged" : c.kind === "keep" ? "unchanged" : c.isMajor ? "changed" : "added";
54
- const content = `${c.range}${c.isMajor ? MAJOR_SUFFIX : ""}`.padEnd(cellWidth);
55
43
  segments.push({
56
- text: `${bubble} ${content} `,
44
+ text: cellText(c, selected),
57
45
  style
58
46
  });
59
47
  }
@@ -94,34 +82,14 @@ function summaryLines(decisions, interop, rejected) {
94
82
  style: "unchanged"
95
83
  }]
96
84
  });
97
- if (interop) {
98
- for (const a of interop.adjustments) {
99
- lines.push({
100
- indent: 0,
101
- gutter: "~",
102
- segments: [{
103
- text: `↓ ${a.pkg} ${a.from} → ${a.to}`,
104
- style: "changed"
105
- }]
106
- });
107
- lines.push({
108
- indent: 1,
109
- gutter: "~",
110
- segments: [{
111
- text: `↳ peer → ${a.peer}`,
112
- style: "changed"
113
- }]
114
- });
115
- }
116
- for (const c of interop.conflicts) lines.push({
117
- indent: 0,
118
- gutter: "⚠",
119
- segments: [{
120
- text: `${c.pkg} (kept ${c.ceiling}) blocked by ${c.blockedBy}`,
121
- style: "warn"
122
- }]
123
- });
124
- }
85
+ if (interop) for (const c of interop.conflicts) lines.push({
86
+ indent: 0,
87
+ gutter: "⚠",
88
+ segments: [{
89
+ text: `${c.pkg} (kept ${c.ceiling}) blocked by ${c.blockedBy}`,
90
+ style: "warn"
91
+ }]
92
+ });
125
93
  if (rejected && rejected.length > 0) {
126
94
  lines.push({
127
95
  indent: 0,
package/cli/ui/Walk.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { computeGroupPeers } from "../interop-live.js";
2
- import { cellColor, displayCandidates, initTable, peerFor, tableDecisions, tableStep, truncateEnd } from "../walk-reducer.js";
2
+ import { cellColor, displayCandidates, initTable, peerFor, tableDecisions, tableLayout, tableStep, truncateEnd } from "../walk-reducer.js";
3
3
  import { Box, Text, useApp, useInput } from "ink";
4
4
  import { createElement, useEffect, useState } from "react";
5
5
 
@@ -37,13 +37,7 @@ function Walk({ items, onDone, dryRun = false, unresolved = [], interopModels =
37
37
  }
38
38
  });
39
39
  if (state.done || items.length === 0) return createElement(Text, null, "Done.");
40
- const pkgWidth = Math.max(...items.map((i) => i.entry.pkg.length));
41
- const MAJOR_SUFFIX = " ⚠ major";
42
- const SELECTED = "●";
43
- const UNSELECTED = "○";
44
- const cellWidth = Math.max(...items.flatMap((i) => displayCandidates(i).map((c) => c.range.length + (c.isMajor ? 8 : 0))));
45
- const maxCells = Math.max(...items.map((i) => displayCandidates(i).length));
46
- const blankCell = `${" ".repeat(2 + cellWidth)} `;
40
+ const { pkgWidth, cellWidth, maxCells, blankCell, cellText } = tableLayout(items);
47
41
  const termCols = process.stdout.columns ?? 100;
48
42
  const fixedPrefix = 2 + (pkgWidth + 2) + maxCells * (cellWidth + 4) + 2;
49
43
  const start = Math.max(0, Math.min(state.cursor - Math.floor(VIEWPORT / 2), items.length - VIEWPORT));
@@ -74,14 +68,11 @@ function Walk({ items, onDone, dryRun = false, unresolved = [], interopModels =
74
68
  const candidates = displayCandidates(item);
75
69
  const cells = candidates.map((c, ci) => {
76
70
  const selected = ci === pick;
77
- const bubble = selected ? SELECTED : UNSELECTED;
78
- const major = c.isMajor ? MAJOR_SUFFIX : "";
79
- const content = `${c.range}${major}`.padEnd(cellWidth);
80
71
  const color = cellColor(c, selected);
81
72
  return createElement(Text, {
82
73
  key: c.kind,
83
74
  ...color ? { color } : {}
84
- }, `${bubble} ${content} `);
75
+ }, cellText(c, selected));
85
76
  });
86
77
  for (let ci = candidates.length; ci < maxCells; ci++) cells.push(createElement(Text, { key: `blank-${ci}` }, blankCell));
87
78
  const chosen = candidates[pick];
package/cli/ui/env.js CHANGED
@@ -1,9 +1,8 @@
1
1
  import { hasTTY, isAgent, isCI, isColorSupported } from "std-env";
2
- import { supportsHyperlinks } from "std-osc8";
3
2
 
4
3
  //#region src/cli/ui/env.ts
5
4
  /**
6
- * Detect color / interactivity / hyperlink support once, at the command edge.
5
+ * Detect color / interactivity once, at the command edge.
7
6
  * The render layer consumes the returned flags and never reads the environment.
8
7
  *
9
8
  * @internal
@@ -11,10 +10,9 @@ import { supportsHyperlinks } from "std-osc8";
11
10
  function detectCapabilities() {
12
11
  return {
13
12
  color: isColorSupported,
14
- interactive: hasTTY && !isCI && !isAgent,
15
- hyperlinks: supportsHyperlinks
13
+ interactive: hasTTY && !isCI && !isAgent
16
14
  };
17
15
  }
18
16
 
19
17
  //#endregion
20
- export { detectCapabilities, supportsHyperlinks };
18
+ export { detectCapabilities };
@@ -6,17 +6,16 @@ import { createElement } from "react";
6
6
  //#region src/cli/ui/run-preview.ts
7
7
  /**
8
8
  * Render the interactive Preview inside an Effect, resolving once the user
9
- * exits and Ink has fully torn down.
9
+ * exits and Ink has fully torn down. An Ink crash rejects `waitUntilExit()`,
10
+ * which `Effect.promise` surfaces as a defect rather than a hung fiber.
10
11
  *
11
12
  * @internal
12
13
  */
13
14
  function runPreview(views) {
14
- return Effect.callback((resume) => {
15
- render(createElement(Preview, {
16
- views,
17
- onExit: () => {}
18
- })).waitUntilExit().then(() => resume(Effect.void)).catch((err) => resume(Effect.die(err)));
19
- });
15
+ return Effect.promise(() => render(createElement(Preview, {
16
+ views,
17
+ onExit: () => {}
18
+ })).waitUntilExit());
20
19
  }
21
20
 
22
21
  //#endregion
@@ -7,14 +7,16 @@ import { createElement } from "react";
7
7
  /**
8
8
  * Render the interactive table inside an Effect, resolving with the collected
9
9
  * decisions once the user submits (or with an empty list when they cancel with
10
- * Esc, or when no rows are actionable), after Ink has fully exited.
10
+ * Esc, or when no rows are actionable), after Ink has fully exited. An Ink
11
+ * crash rejects `waitUntilExit()`, which `Effect.promise` surfaces as a
12
+ * defect rather than a hung fiber.
11
13
  *
12
14
  * @internal
13
15
  */
14
16
  function runWalk(items, dryRun = false, unresolved = [], interopModels = /* @__PURE__ */ new Map()) {
15
- return Effect.callback((resume) => {
17
+ return Effect.promise(async () => {
16
18
  let collected = [];
17
- render(createElement(Walk, {
19
+ await render(createElement(Walk, {
18
20
  items,
19
21
  dryRun,
20
22
  unresolved,
@@ -22,7 +24,8 @@ function runWalk(items, dryRun = false, unresolved = [], interopModels = /* @__P
22
24
  onDone: (d) => {
23
25
  collected = d;
24
26
  }
25
- })).waitUntilExit().then(() => resume(Effect.succeed([...collected]))).catch((err) => resume(Effect.die(err)));
27
+ })).waitUntilExit();
28
+ return [...collected];
26
29
  });
27
30
  }
28
31
 
package/cli/validate.js CHANGED
@@ -1,5 +1,5 @@
1
+ import { parseRange, parseVersion } from "../semver-util.js";
1
2
  import { Effect } from "effect";
2
- import { Range, SemVer } from "@effected/semver";
3
3
 
4
4
  //#region src/cli/validate.ts
5
5
  /**
@@ -19,15 +19,12 @@ import { Range, SemVer } from "@effected/semver";
19
19
  * @internal
20
20
  */
21
21
  function rangeIsSatisfiable(range, versions) {
22
- return Effect.gen(function* () {
23
- if (versions.length === 0) return true;
24
- const parsedRange = yield* Range.parse(range).pipe(Effect.catch(() => Effect.succeed(null)));
25
- if (parsedRange === null) return true;
26
- for (const v of versions) {
27
- const sv = yield* SemVer.parse(v).pipe(Effect.catch(() => Effect.succeed(null)));
28
- if (sv && parsedRange.test(sv)) return true;
29
- }
30
- return false;
22
+ if (versions.length === 0) return true;
23
+ const parsedRange = parseRange(range);
24
+ if (parsedRange === null) return true;
25
+ return versions.some((v) => {
26
+ const sv = parseVersion(v);
27
+ return sv !== null && parsedRange.test(sv);
31
28
  });
32
29
  }
33
30
  /**
@@ -49,7 +46,7 @@ function rangeIsSatisfiable(range, versions) {
49
46
  * @internal
50
47
  */
51
48
  function validateEdits(edits, versionsByPkg) {
52
- return Effect.gen(function* () {
49
+ return Effect.sync(() => {
53
50
  const byPkg = /* @__PURE__ */ new Map();
54
51
  for (const e of edits) {
55
52
  const key = e.versionKey ?? e.pkg;
@@ -62,11 +59,10 @@ function validateEdits(edits, versionsByPkg) {
62
59
  for (const [key, group] of byPkg) {
63
60
  const pkg = group[0]?.pkg ?? key;
64
61
  const versions = versionsByPkg.get(key) ?? [];
65
- const checked = [];
66
- for (const e of group) checked.push({
67
- edit: e,
68
- ok: yield* rangeIsSatisfiable(e.value, versions)
69
- });
62
+ const checked = group.map((edit) => ({
63
+ edit,
64
+ ok: rangeIsSatisfiable(edit.value, versions)
65
+ }));
70
66
  const failing = checked.filter((c) => !c.ok);
71
67
  if (failing.length === 0) {
72
68
  accepted.push(...group);
package/cli/walk-plan.js CHANGED
@@ -1,7 +1,7 @@
1
- import { derivePeerRange } from "./peer-range.js";
2
- import { detectPeerDrift } from "./drift.js";
3
1
  import { versionKeyOf } from "./version-key.js";
2
+ import { derivePeerRange } from "./peer-range.js";
4
3
  import { planEntry } from "./plan.js";
4
+ import { detectPeerDrift } from "./drift.js";
5
5
  import { Effect } from "effect";
6
6
 
7
7
  //#region src/cli/walk-plan.ts
@@ -5,6 +5,34 @@ const ORDER = {
5
5
  minor: 2,
6
6
  latest: 3
7
7
  };
8
+ /** Trailing annotation appended to a major candidate's cell, e.g. " ⚠ major". @internal */
9
+ const MAJOR_SUFFIX = " ⚠ major";
10
+ /** Filled / hollow radio glyphs, matching `pnpm up -i`. @internal */
11
+ const SELECTED = "●";
12
+ /** @internal */
13
+ const UNSELECTED = "○";
14
+ /** "● " / "○ " glyph-plus-space prefix width, common to every cell. */
15
+ const BUBBLE_WIDTH = 2;
16
+ /**
17
+ * Compute the table geometry once for a set of rows. The range and the major
18
+ * suffix are padded TOGETHER so a major annotation never shifts a later
19
+ * column, and every row emits `maxCells` cells so the peer separator lands
20
+ * in the same column regardless of how many candidates a row has.
21
+ *
22
+ * @internal
23
+ */
24
+ function tableLayout(items) {
25
+ const cells = items.map(displayCandidates);
26
+ const pkgWidth = items.length ? Math.max(...items.map((i) => i.entry.pkg.length)) : 0;
27
+ const cellWidth = items.length ? Math.max(...cells.flatMap((cs) => cs.map((c) => c.range.length + (c.isMajor ? 8 : 0)))) : 0;
28
+ return {
29
+ pkgWidth,
30
+ cellWidth,
31
+ maxCells: items.length ? Math.max(...cells.map((cs) => cs.length)) : 0,
32
+ blankCell: `${" ".repeat(BUBBLE_WIDTH + cellWidth)} `,
33
+ cellText: (c, selected) => `${selected ? "●" : "○"} ${`${c.range}${c.isMajor ? MAJOR_SUFFIX : ""}`.padEnd(cellWidth)} `
34
+ };
35
+ }
8
36
  /**
9
37
  * Truncate `s` to at most `max` display columns, appending `…` when clipped.
10
38
  * Keeps a long peer-conflict annotation from wrapping and breaking the table's
@@ -130,4 +158,4 @@ function cellColor(candidate, selected) {
130
158
  }
131
159
 
132
160
  //#endregion
133
- export { cellColor, displayCandidates, initTable, peerFor, tableDecisions, tableStep, truncateEnd };
161
+ export { MAJOR_SUFFIX, cellColor, displayCandidates, initTable, peerFor, tableDecisions, tableLayout, tableStep, truncateEnd };
@@ -1,4 +1,4 @@
1
- import { Effect } from "effect";
1
+ import { Result } from "effect";
2
2
  import { existsSync } from "node:fs";
3
3
  import { dirname, join } from "node:path";
4
4
  import { Yaml, YamlStringifyOptions } from "@effected/yaml";
@@ -46,12 +46,12 @@ function findWorkspaceFile(startDir) {
46
46
  }
47
47
  /** Parse pnpm-workspace.yaml source; empty/whitespace yields an empty object. @internal */
48
48
  function parseWorkspace(source) {
49
- const parsed = Effect.runSync(Yaml.parse(source));
49
+ const parsed = Result.getOrThrow(Yaml.parseResult(source));
50
50
  return parsed && typeof parsed === "object" ? parsed : {};
51
51
  }
52
52
  /** Render a workspace object: deterministic key sort + yaml.stringify. @internal */
53
53
  function renderWorkspace(obj) {
54
- return Effect.runSync(Yaml.stringify(canonicalize(obj), STRINGIFY_OPTIONS));
54
+ return Result.getOrThrow(Yaml.stringifyResult(canonicalize(obj), STRINGIFY_OPTIONS));
55
55
  }
56
56
 
57
57
  //#endregion
@@ -1,8 +1,9 @@
1
+ import { parseVersion } from "../semver-util.js";
2
+ import { findWorkspaceFile } from "./workspace-file.js";
1
3
  import { ResolveError } from "./resolve.js";
2
4
  import { Effect, Layer } from "effect";
3
5
  import { existsSync, readFileSync, readdirSync } from "node:fs";
4
6
  import { dirname, join, resolve } from "node:path";
5
- import { SemVer } from "@effected/semver";
6
7
  import { getWorkspacePackagesSync } from "@effected/workspaces";
7
8
  import { nodeSyncOps } from "@effected/workspaces/node-sync";
8
9
 
@@ -96,12 +97,8 @@ function applyPendingBumps(rootDir, versions) {
96
97
  for (const [pkg, bump] of bumps) {
97
98
  const current = versions.get(pkg);
98
99
  if (current === void 0) continue;
99
- let parsed;
100
- try {
101
- parsed = Effect.runSync(SemVer.parse(current));
102
- } catch {
103
- continue;
104
- }
100
+ const parsed = parseVersion(current);
101
+ if (parsed === null) continue;
105
102
  versions.set(pkg, parsed.bump[bump]().toString());
106
103
  }
107
104
  }
@@ -112,9 +109,9 @@ function applyPendingBumps(rootDir, versions) {
112
109
  *
113
110
  * @internal
114
111
  */
115
- function readWorkspaceVersions(rootDir) {
112
+ function readWorkspaceVersions(rootDir, manifests = readManifests(rootDir)) {
116
113
  const out = /* @__PURE__ */ new Map();
117
- for (const [name, manifest] of readManifests(rootDir)) out.set(name, manifest.version);
114
+ for (const [name, manifest] of manifests) out.set(name, manifest.version);
118
115
  applyPendingBumps(rootDir, out);
119
116
  return out;
120
117
  }
@@ -131,7 +128,7 @@ function makeWorkspaceResolver(rootDir) {
131
128
  let versions;
132
129
  const load = () => {
133
130
  manifests ??= readManifests(rootDir);
134
- versions ??= readWorkspaceVersions(rootDir);
131
+ versions ??= readWorkspaceVersions(rootDir, manifests);
135
132
  return {
136
133
  manifests,
137
134
  versions
@@ -160,13 +157,8 @@ function makeWorkspaceResolver(rootDir) {
160
157
  * @internal
161
158
  */
162
159
  function findWorkspaceRoot(startDir) {
163
- let dir = resolve(startDir);
164
- for (;;) {
165
- if (existsSync(join(dir, "pnpm-workspace.yaml"))) return dir;
166
- const parent = dirname(dir);
167
- if (parent === dir) return resolve(startDir);
168
- dir = parent;
169
- }
160
+ const file = findWorkspaceFile(resolve(startDir));
161
+ return file === null ? resolve(startDir) : dirname(file);
170
162
  }
171
163
 
172
164
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rolldown-pnpm-config",
3
- "version": "0.8.1",
3
+ "version": "1.0.0",
4
4
  "private": false,
5
5
  "description": "A dogfooding example of our plugin",
6
6
  "repository": {
@@ -45,8 +45,7 @@
45
45
  "ink-tab": "^5.2.0",
46
46
  "oxc-parser": "^0.150.0",
47
47
  "react": "^19.3.0",
48
- "std-env": "^4.2.0",
49
- "std-osc8": "^0.2.0"
48
+ "std-env": "^4.2.0"
50
49
  },
51
50
  "peerDependencies": {
52
51
  "rolldown": "^1.2.0"
package/patches/build.js CHANGED
@@ -1,9 +1,10 @@
1
1
  import { discoverPatches } from "./discover.js";
2
+ import { Predicate } from "effect";
2
3
 
3
4
  //#region src/patches/build.ts
4
5
  /** True only for the `{ strategy: "rewrite" }` directive. @internal */
5
6
  function isRewriteDirective(v) {
6
- return v !== null && typeof v === "object" && !Array.isArray(v) && Object.keys(v).length === 1 && v.strategy === "rewrite";
7
+ return Predicate.isObject(v) && Object.keys(v).length === 1 && v.strategy === "rewrite";
7
8
  }
8
9
  /** Read `local.localPatchesDir` when it is a string. @internal */
9
10
  function readLocalPatchesDir(config) {
@@ -11,22 +12,35 @@ function readLocalPatchesDir(config) {
11
12
  return typeof local?.localPatchesDir === "string" ? local.localPatchesDir : void 0;
12
13
  }
13
14
  /**
14
- * Resolve build-time `patchedDependencies`. When the field is absent or the
15
- * `{ strategy: "rewrite" }` directive, run discovery and inject the distributed
16
- * map (`name`-scoped `.pnpm-config` paths) so `freeze` sees a plain map. An
17
- * explicit map / wrapped value passes through untouched.
15
+ * Discover this plugin's own patches when `patchedDependencies` is absent or
16
+ * the `{ strategy: "rewrite" }` directive; undefined when an explicit map /
17
+ * wrapped value is declared (the escape hatch skips discovery entirely).
18
18
  *
19
19
  * @internal
20
20
  */
21
- function withResolvedBuildPatches(config, baseDir) {
21
+ function discoverOwnedPatches(config, baseDir) {
22
22
  const raw = config.patchedDependencies;
23
- if (raw !== void 0 && !isRewriteDirective(raw)) return config;
23
+ if (raw !== void 0 && !isRewriteDirective(raw)) return void 0;
24
24
  const localPatchesDir = readLocalPatchesDir(config);
25
- const distributed = discoverPatches({
25
+ return discoverPatches({
26
26
  baseDir,
27
27
  name: config.name,
28
28
  ...localPatchesDir !== void 0 ? { localPatchesDir } : {}
29
- }).filter((p) => p.distributed);
29
+ });
30
+ }
31
+ /**
32
+ * Resolve build-time `patchedDependencies`. When the field is absent or the
33
+ * `{ strategy: "rewrite" }` directive, run discovery and inject the distributed
34
+ * map (`name`-scoped `.pnpm-config` paths) so `freeze` sees a plain map. An
35
+ * explicit map / wrapped value passes through untouched. A caller that has
36
+ * already run discovery passes its result as `owned`.
37
+ *
38
+ * @internal
39
+ */
40
+ function withResolvedBuildPatches(config, baseDir, owned = discoverOwnedPatches(config, baseDir)) {
41
+ const raw = config.patchedDependencies;
42
+ if (owned === void 0) return config;
43
+ const distributed = owned.filter((p) => p.distributed);
30
44
  if (distributed.length === 0) {
31
45
  if (raw === void 0) return config;
32
46
  const { patchedDependencies: _drop, ...rest } = config;
@@ -41,4 +55,4 @@ function withResolvedBuildPatches(config, baseDir) {
41
55
  }
42
56
 
43
57
  //#endregion
44
- export { isRewriteDirective, readLocalPatchesDir, withResolvedBuildPatches };
58
+ export { discoverOwnedPatches, isRewriteDirective, readLocalPatchesDir, withResolvedBuildPatches };
@@ -1,10 +1,10 @@
1
+ import { bareVersion } from "../semver-util.js";
2
+
1
3
  //#region src/plugin/allowed-versions.ts
2
4
  /** An exact version: no range operator, no wildcard — `X.Y.Z` with an optional prerelease. */
3
5
  const EXACT_VERSION = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
4
6
  /** The install range of a catalog spec (bare string or `{ range }` object). */
5
7
  const rangeOf = (spec) => typeof spec === "string" ? spec : spec.range;
6
- /** Strip a leading range operator to the bare version digits (e.g. `^3.17.0` → `3.17.0`). */
7
- const bareVersion = (range) => range.replace(/^[\^~>=<\s]+/, "").split(/\s/)[0] ?? range;
8
8
  /** Apply the `prefix` transform to a peer value: verbatim, re-prefixed, or stripped to exact. */
9
9
  function applyPrefix(peerValue, prefix) {
10
10
  if (prefix === void 0) return peerValue;