rolldown-pnpm-config 2.2.0 → 2.3.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.
@@ -1,10 +1,9 @@
1
- import { discoverOwnedPatches, withResolvedBuildPatches } from "../../patches/build.js";
2
1
  import { DESCRIPTORS } from "../../descriptors/index.js";
2
+ import { discoverOwnedPatches, withResolvedBuildPatches } from "../../patches/build.js";
3
+ import { reconcilePatches } from "../../patches/reconcile.js";
3
4
  import { freeze } from "../../plugin/freeze.js";
4
5
  import { resolveRootName } from "../../runtime/ctx.js";
5
- import { canonicalize, renderWorkspace } from "../workspace-file.js";
6
6
  import { WorkingDirectory, resolveWorkspacePath } from "../cwd.js";
7
- import { reconcilePatches } from "../../patches/reconcile.js";
8
7
  import { buildDiff } from "../diff/build.js";
9
8
  import { renderExportDiff } from "../diff/render.js";
10
9
  import { effectiveManaged } from "../effective.js";
@@ -13,11 +12,12 @@ import { legend } from "../render/legend.js";
13
12
  import { printView } from "../render/print.js";
14
13
  import { failureDoc } from "../render/report.js";
15
14
  import { findConfigFiles, pickConfigCandidate } from "../select-file.js";
15
+ import { canonicalize, renderWorkspace } from "../workspace-file.js";
16
16
  import { overlayWorkspace } from "../workspace-overlay.js";
17
- import { Data, Effect, Option, Predicate } from "effect";
17
+ import { Data, Effect, FileSystem, Option, Predicate } from "effect";
18
18
  import { dirname, relative } from "node:path";
19
19
  import { CliDoc, CliMessage } from "@effected/cli";
20
- import { existsSync, writeFileSync } from "node:fs";
20
+ import { existsSync } from "node:fs";
21
21
  import { Argument, Command, Flag } from "effect/cli";
22
22
 
23
23
  // src/cli/commands/export.ts
@@ -90,10 +90,7 @@ function runExport(opts) {
90
90
  diff,
91
91
  report
92
92
  };
93
- yield* Effect.try({
94
- try: () => writeFileSync(path, rendered, "utf8"),
95
- catch: () => new ExportError({ message: `Cannot write ${path}` })
96
- });
93
+ yield* (yield* FileSystem.FileSystem).writeFileString(path, rendered).pipe(Effect.mapError(() => new ExportError({ message: `Cannot write ${path}` })));
97
94
  return {
98
95
  path,
99
96
  rendered,
@@ -2,13 +2,13 @@ import { freeze } from "../../plugin/freeze.js";
2
2
  import { resolveRootName } from "../../runtime/ctx.js";
3
3
  import { WorkingDirectory, resolveWorkspacePath } from "../cwd.js";
4
4
  import { loadConfigAndWorkspace } from "../load-config.js";
5
+ import { buildPreviewViews } from "../preview-views.js";
5
6
  import { legend } from "../render/legend.js";
6
7
  import { printView } from "../render/print.js";
7
8
  import { failureDoc } from "../render/report.js";
8
9
  import { findConfigFiles, pickConfigCandidate } from "../select-file.js";
9
- import { WORKSPACE_FIELDS } from "./export.js";
10
- import { buildPreviewViews } from "../preview-views.js";
11
10
  import { previewScreen } from "../ui/screens.js";
11
+ import { WORKSPACE_FIELDS } from "./export.js";
12
12
  import { Data, Effect, Option } from "effect";
13
13
  import { dirname } from "node:path";
14
14
  import { CliDoc, CliInteractive } from "@effected/cli";
@@ -1,27 +1,26 @@
1
1
  import { bareVersion } from "../../semver-util.js";
2
2
  import { WorkingDirectory } from "../cwd.js";
3
- import { RegistryResolver } from "../resolve.js";
4
- import { evaluatePluginConfig } from "../evaluate.js";
5
- import { failureDoc, warningDoc } from "../render/report.js";
6
3
  import { discoverCatalogEntries } from "../discover.js";
7
- import { filterEntriesByCatalog, findConfigFiles, pickConfigCandidate } from "../select-file.js";
8
- import { walkScreen, withProgress } from "../ui/screens.js";
9
- import { versionKeyOf } from "../version-key.js";
10
4
  import { buildEdits, entryEdits } from "../edits.js";
5
+ import { evaluatePluginConfig } from "../evaluate.js";
11
6
  import { buildInteropEdits, interopEntryChanged, makePeerFetcher, runInterop } from "../interop.js";
12
7
  import { buildGroupModel, computeGroupPeers } from "../interop-live.js";
13
8
  import { derivePeerRange } from "../peer-range.js";
14
9
  import { defaultPick, planEntry } from "../plan.js";
15
10
  import { parsePnpmGate, readConfigReleaseAge } from "../release-age.js";
11
+ import { failureDoc, warningDoc } from "../render/report.js";
12
+ import { RegistryResolver } from "../resolve.js";
16
13
  import { applyEdits } from "../rewrite.js";
14
+ import { filterEntriesByCatalog, findConfigFiles, pickConfigCandidate } from "../select-file.js";
17
15
  import { summaryDoc } from "../summary.js";
16
+ import { walkScreen, withProgress } from "../ui/screens.js";
18
17
  import { validateEdits } from "../validate.js";
18
+ import { versionKeyOf } from "../version-key.js";
19
19
  import { buildWalkItems } from "../walk-plan.js";
20
20
  import { findWorkspaceRoot, makeWorkspaceResolver } from "../workspace-resolve.js";
21
- import { Clock, Data, Effect, Option, Result } from "effect";
21
+ import { Clock, Data, Effect, FileSystem, Option, Result } from "effect";
22
22
  import { dirname } from "node:path";
23
23
  import { CliDoc, CliExit, CliFormat, CliInteractive, CliMessage, Doc, Fmt } from "@effected/cli";
24
- import { readFileSync, writeFileSync } from "node:fs";
25
24
  import { Argument, Command, Flag } from "effect/cli";
26
25
  import { CliUi } from "@effected/cli/ui";
27
26
  import { ReleaseAgeGate } from "@effected/npm";
@@ -71,13 +70,14 @@ var CheckFailedError = class extends Data.TaggedError("CheckFailedError") {
71
70
  return failureDoc(`${CHECK_FAILED_LABEL}\n${indent(this.message)}`);
72
71
  }
73
72
  };
73
+ /** Write the rewritten config source back to `file`. */
74
+ function writeSource(file, next) {
75
+ return Effect.flatMap(FileSystem.FileSystem, (fs) => fs.writeFileString(file, next).pipe(Effect.mapError(() => new UpgradeError({ message: `Cannot write ${file}` }))));
76
+ }
74
77
  /** Read a config file and statically discover its catalog entries. @internal */
75
78
  function readCatalogSource(file) {
76
79
  return Effect.gen(function* () {
77
- const source = yield* Effect.try({
78
- try: () => readFileSync(file, "utf8"),
79
- catch: () => new UpgradeError({ message: `Cannot read ${file}` })
80
- });
80
+ const source = yield* (yield* FileSystem.FileSystem).readFileString(file).pipe(Effect.mapError(() => new UpgradeError({ message: `Cannot read ${file}` })));
81
81
  const { entries, skipped } = yield* Effect.try({
82
82
  try: () => discoverCatalogEntries(source, file),
83
83
  catch: (e) => new UpgradeError({ message: Fmt.describe(e) })
@@ -165,16 +165,15 @@ function resolveGatedVersions(entries, resolver, gate, now, onProgress, workspac
165
165
  * @internal
166
166
  */
167
167
  function resolveProgress(report) {
168
- const noun = (n) => `${n} package${n === 1 ? "" : "s"}`;
169
168
  const finished = (total) => report({
170
169
  _tag: "Finished",
171
- label: `Resolved ${noun(total)}`
170
+ label: `Resolved ${Fmt.plural(total, "package")}`
172
171
  });
173
172
  return (resolved, total) => {
174
173
  if (resolved === 0) {
175
174
  const begin = report({
176
175
  _tag: "Phase",
177
- label: `Resolving ${noun(total)}`,
176
+ label: `Resolving ${Fmt.plural(total, "package")}`,
178
177
  total
179
178
  });
180
179
  return total === 0 ? Effect.andThen(begin, finished(total)) : begin;
@@ -283,10 +282,7 @@ function runUpgrade(opts) {
283
282
  const allEdits = [...accepted, ...interopEdits];
284
283
  if (allEdits.length > 0 && !opts.dryRun) {
285
284
  const next = applyEdits(source, allEdits);
286
- yield* Effect.try({
287
- try: () => writeFileSync(opts.file, next, "utf8"),
288
- catch: () => new UpgradeError({ message: `Cannot write ${opts.file}` })
289
- });
285
+ yield* writeSource(opts.file, next);
290
286
  }
291
287
  return {
292
288
  updated: changedSpans.size,
@@ -316,11 +312,7 @@ function applyInteropAndDecisions(file, source, nonInteropEdits, interopEdits) {
316
312
  return Effect.gen(function* () {
317
313
  const edits = [...nonInteropEdits, ...interopEdits];
318
314
  if (edits.length === 0) return;
319
- const next = applyEdits(source, edits);
320
- yield* Effect.try({
321
- try: () => writeFileSync(file, next, "utf8"),
322
- catch: () => new UpgradeError({ message: `Cannot write ${file}` })
323
- });
315
+ yield* writeSource(file, applyEdits(source, edits));
324
316
  });
325
317
  }
326
318
  /**
package/cli/diff/build.js CHANGED
@@ -1,4 +1,4 @@
1
- import { Predicate } from "effect";
1
+ import { Order, Predicate } from "effect";
2
2
 
3
3
  // src/cli/diff/build.ts
4
4
  const isObject = Predicate.isObject;
@@ -49,7 +49,7 @@ function diffArray(key, path, before, after, tag) {
49
49
  const here = [...path, key];
50
50
  const b = new Set(before.map(String));
51
51
  const a = new Set(after.map(String));
52
- const children = [.../* @__PURE__ */ new Set([...b, ...a])].sort((x, y) => x.localeCompare(y)).map((el) => {
52
+ const children = [.../* @__PURE__ */ new Set([...b, ...a])].sort(Order.String).map((el) => {
53
53
  const inB = b.has(el);
54
54
  const inA = a.has(el);
55
55
  const kind = inB && inA ? "unchanged" : inA ? "added" : "removed";
@@ -73,7 +73,7 @@ function diffArray(key, path, before, after, tag) {
73
73
  function diffValue(key, path, before, after, tag) {
74
74
  const here = [...path, key];
75
75
  if (isObject(before) && isObject(after)) {
76
- const children = [.../* @__PURE__ */ new Set([...Object.keys(before), ...Object.keys(after)])].sort((x, y) => x.localeCompare(y)).map((k) => {
76
+ const children = [.../* @__PURE__ */ new Set([...Object.keys(before), ...Object.keys(after)])].sort(Order.String).map((k) => {
77
77
  if (!(k in before)) return uniform(k, here, after[k], "added");
78
78
  if (!(k in after)) return uniform(k, here, before[k], "removed");
79
79
  return diffValue(k, here, before[k], after[k]);
@@ -104,7 +104,7 @@ function diffValue(key, path, before, after, tag) {
104
104
  * @internal
105
105
  */
106
106
  function buildDiff(before, after, meta) {
107
- const children = [.../* @__PURE__ */ new Set([...Object.keys(before), ...Object.keys(after)])].sort((x, y) => x.localeCompare(y)).map((k) => {
107
+ const children = [.../* @__PURE__ */ new Set([...Object.keys(before), ...Object.keys(after)])].sort(Order.String).map((k) => {
108
108
  const tag = meta.localKeys.has(k) ? "local" : meta.managedKeys.has(k) ? void 0 : "unmanaged";
109
109
  if (!(k in before)) return uniform(k, [], after[k], "added", tag);
110
110
  if (!(k in after)) return uniform(k, [], before[k], "removed", tag);
@@ -1,5 +1,5 @@
1
1
  import { bareVersion, parseRange, parseVersion } from "../semver-util.js";
2
- import "./interop.js";
2
+ import { lowestVersion } from "./interop.js";
3
3
  import { Effect } from "effect";
4
4
 
5
5
  // src/cli/interop-live.ts
@@ -28,19 +28,7 @@ function computeGroupPeers(model, selected) {
28
28
  for (const m of model.members) {
29
29
  const v = selected.get(m);
30
30
  if (v === void 0) continue;
31
- const declared = declaredFloors.get(m);
32
- if (declared?.length) {
33
- let minStr = declared[0];
34
- let minSv = model.ver.get(minStr) ?? null;
35
- for (const f of declared.slice(1)) {
36
- const sv = model.ver.get(f) ?? null;
37
- if (sv && (minSv === null || sv.compare(minSv) < 0)) {
38
- minSv = sv;
39
- minStr = f;
40
- }
41
- }
42
- peer.set(m, `^${minStr}`);
43
- } else peer.set(m, `^${v}`);
31
+ peer.set(m, `^${lowestVersion(declaredFloors.get(m) ?? []) ?? v}`);
44
32
  }
45
33
  const conflict = /* @__PURE__ */ new Map();
46
34
  for (const m of model.members) {
package/cli/interop.js CHANGED
@@ -50,7 +50,13 @@ function deriveFloors(resolved, fetchPeer) {
50
50
  return out;
51
51
  });
52
52
  }
53
- /** The lowest parseable version string in `list`, or null when none parses. */
53
+ /**
54
+ * The lowest parseable version string in `list`, or null when none parses.
55
+ * Shared by the `--yes` resolver and the interactive table so both write the
56
+ * same peer floor.
57
+ *
58
+ * @internal
59
+ */
54
60
  function lowestVersion(list) {
55
61
  let best = null;
56
62
  for (const f of list) {
@@ -223,4 +229,4 @@ function buildInteropEdits(entries, result) {
223
229
  return edits;
224
230
  }
225
231
 
226
- export { buildInteropEdits, deriveFloors, interopEntryChanged, makePeerFetcher, resolveGroup, runInterop };
232
+ export { buildInteropEdits, deriveFloors, interopEntryChanged, lowestVersion, makePeerFetcher, resolveGroup, runInterop };
@@ -1,8 +1,7 @@
1
- import { parseWorkspace } from "./workspace-file.js";
2
1
  import { evaluatePluginConfig } from "./evaluate.js";
3
- import { Effect, Predicate } from "effect";
2
+ import { parseWorkspace } from "./workspace-file.js";
3
+ import { Effect, FileSystem, Predicate } from "effect";
4
4
  import { Fmt } from "@effected/cli";
5
- import { existsSync, readFileSync } from "node:fs";
6
5
 
7
6
  // src/cli/load-config.ts
8
7
  /**
@@ -11,22 +10,26 @@ import { existsSync, readFileSync } from "node:fs";
11
10
  * parse the workspace file when present. `mkError` wraps each failure
12
11
  * message in the command's own error type.
13
12
  *
13
+ * IO goes through the platform `FileSystem`. Only a workspace file that is
14
+ * NOT FOUND reads as absent (`fs.exists` maps `NotFound` to `false`); an
15
+ * existence check that fails for any other reason (EACCES, ENOTDIR, EIO) is a
16
+ * `Cannot read or parse` failure, never a silent "create it fresh".
17
+ *
14
18
  * @internal
15
19
  */
16
20
  function loadConfigAndWorkspace(opts, mkError) {
17
21
  return Effect.gen(function* () {
18
- const configSource = yield* Effect.try({
19
- try: () => readFileSync(opts.configFile, "utf8"),
20
- catch: () => mkError(`Cannot read ${opts.configFile}`)
21
- });
22
+ const fs = yield* FileSystem.FileSystem;
23
+ const configSource = yield* fs.readFileString(opts.configFile).pipe(Effect.mapError(() => mkError(`Cannot read ${opts.configFile}`)));
22
24
  const { config, errors } = evaluatePluginConfig(configSource, opts.configFile);
23
25
  if (config === null) return yield* Effect.fail(mkError(`No PnpmConfigPlugin call found in ${opts.configFile}`));
24
26
  if (errors.length > 0) return yield* Effect.fail(mkError(`Non-literal config values: ${errors.join("; ")}`));
25
27
  const path = opts.workspacePath;
26
- const parsed = existsSync(path) ? yield* Effect.try({
27
- try: () => parseWorkspace(readFileSync(path, "utf8")),
28
- catch: (e) => mkError(`Cannot read or parse ${path}: ${Fmt.causeMessage(e) ?? Fmt.describe(e)}`)
29
- }) : {};
28
+ const cannotRead = (e) => mkError(`Cannot read or parse ${path}: ${Fmt.errorText(e)}`);
29
+ const parsed = (yield* fs.exists(path).pipe(Effect.mapError(cannotRead))) ? yield* fs.readFileString(path).pipe(Effect.mapError(cannotRead), Effect.flatMap((text) => Effect.try({
30
+ try: () => parseWorkspace(text),
31
+ catch: cannotRead
32
+ }))) : {};
30
33
  return {
31
34
  config,
32
35
  localCfg: Predicate.isObject(config.local) ? config.local : void 0,
@@ -1,4 +1,4 @@
1
- import { Predicate } from "effect";
1
+ import { Array as Array$1, Predicate } from "effect";
2
2
 
3
3
  // src/cli/local-merge.ts
4
4
  /** Default protocols whose existing-file override entries are preserved. @internal */
@@ -30,9 +30,7 @@ function combine(managed, value, strategy) {
30
30
  if (Array.isArray(managed) || Array.isArray(value)) {
31
31
  const m = Array.isArray(managed) ? managed : [];
32
32
  const v = Array.isArray(value) ? value : [];
33
- if (strategy === "union") return [.../* @__PURE__ */ new Set([...m, ...v])];
34
- const drop = new Set(v.map((x) => JSON.stringify(x)));
35
- return m.filter((x) => !drop.has(JSON.stringify(x)));
33
+ return strategy === "union" ? Array$1.dedupe(Array$1.appendAll(m, v)) : Array$1.difference(m, v);
36
34
  }
37
35
  const m = Predicate.isObject(managed) ? managed : {};
38
36
  const v = Predicate.isObject(value) ? value : {};
@@ -1,10 +1,10 @@
1
- import { canonicalize } from "./workspace-file.js";
1
+ import { WORKSPACE_FIELDS } from "./commands/export.js";
2
2
  import { buildDiff } from "./diff/build.js";
3
3
  import { renderExportDiff } from "./diff/render.js";
4
4
  import { effectiveManaged, vanillaManaged } from "./effective.js";
5
- import { overlayWorkspace } from "./workspace-overlay.js";
6
- import { WORKSPACE_FIELDS } from "./commands/export.js";
7
5
  import { renderSimulated } from "./simulated-view.js";
6
+ import { canonicalize } from "./workspace-file.js";
7
+ import { overlayWorkspace } from "./workspace-overlay.js";
8
8
 
9
9
  // src/cli/preview-views.ts
10
10
  /**
package/cli/resolve.js CHANGED
@@ -86,7 +86,7 @@ const RegistryResolverLive = Layer.effect(RegistryResolver, Effect.gen(function*
86
86
  "--json"
87
87
  ])).pipe(Effect.map(stripPnpmNotices), Effect.mapError((e) => new ResolveError({
88
88
  pkg,
89
- message: Fmt.causeMessage(e) ?? Fmt.describe(e)
89
+ message: Fmt.errorText(e)
90
90
  })));
91
91
  return {
92
92
  versions: (pkg) => pnpmView(pkg, pkg, "versions").pipe(Effect.flatMap((out) => parseVersions(pkg, out))),
@@ -1,10 +1,9 @@
1
1
  import { discoverCatalogEntries } from "./discover.js";
2
- import { Effect, Result } from "effect";
2
+ import { Effect, FileSystem, Result } from "effect";
3
3
  import { join } from "node:path";
4
4
  import { GlobPattern, GlobPatternOptions } from "@effected/glob";
5
5
  import { descendSync } from "@effected/walker";
6
6
  import { nodeWalkerSyncOps } from "@effected/walker/node-sync";
7
- import { readFileSync } from "node:fs";
8
7
 
9
8
  // src/cli/select-file.ts
10
9
  /** Top-level `.ts` FILES of a directory, no dotfiles, sorted. */
@@ -20,7 +19,8 @@ const TS_FILES = Result.getOrThrow(GlobPattern.compileResult("*.ts", GlobPattern
20
19
  * @internal
21
20
  */
22
21
  function findConfigFiles(dir) {
23
- return Effect.sync(() => {
22
+ return Effect.gen(function* () {
23
+ const fs = yield* FileSystem.FileSystem;
24
24
  const names = Result.getOrElse(descendSync(TS_FILES, {
25
25
  cwd: dir,
26
26
  ...nodeWalkerSyncOps
@@ -29,11 +29,7 @@ function findConfigFiles(dir) {
29
29
  for (const name of names) {
30
30
  if (name.endsWith(".d.ts")) continue;
31
31
  const path = join(dir, name);
32
- try {
33
- const source = readFileSync(path, "utf8");
34
- const { entries } = discoverCatalogEntries(source, path);
35
- if (entries.length > 0) matches.push(path);
36
- } catch {}
32
+ if (yield* fs.readFileString(path).pipe(Effect.flatMap((source) => Effect.try(() => discoverCatalogEntries(source, path).entries.length > 0)), Effect.orElseSucceed(() => false))) matches.push(path);
37
33
  }
38
34
  return matches;
39
35
  });
@@ -1,6 +1,7 @@
1
- import { canonicalize } from "./workspace-file.js";
2
- import { row, tone } from "./render/tone.js";
3
1
  import { scalarText } from "./diff/render.js";
2
+ import { row, tone } from "./render/tone.js";
3
+ import { canonicalize } from "./workspace-file.js";
4
+ import { Predicate } from "effect";
4
5
  import { Doc } from "@effected/cli";
5
6
 
6
7
  // src/cli/simulated-view.ts
@@ -42,7 +43,7 @@ function annotation(entry) {
42
43
  /** Flatten one key/value into YAML-shaped plain lines; `ann` annotates the head. */
43
44
  function flatten(key, value, depth, ann) {
44
45
  if (Array.isArray(value)) return [row(" ", depth, [tone(`${key}:`, "plain"), ...ann]), ...value.map((el) => row(" ", depth + 1, [tone(`- ${scalarText(el)}`, "plain")]))];
45
- if (value !== null && typeof value === "object") return [row(" ", depth, [tone(`${key}:`, "plain"), ...ann]), ...Object.entries(value).flatMap(([k, v]) => flatten(k, v, depth + 1, []))];
46
+ if (Predicate.isObject(value)) return [row(" ", depth, [tone(`${key}:`, "plain"), ...ann]), ...Object.entries(value).flatMap(([k, v]) => flatten(k, v, depth + 1, []))];
46
47
  return [row(" ", depth, [tone(`${key}: ${scalarText(value)}`, "plain"), ...ann])];
47
48
  }
48
49
  /**
@@ -1,5 +1,5 @@
1
- import { TONES } from "../render/tone.js";
2
1
  import { computeGroupPeers } from "../interop-live.js";
2
+ import { TONES } from "../render/tone.js";
3
3
  import { cellTone, displayCandidates, initTable, peerFor, tableDecisions, tableLayout, tableStep } from "../walk-reducer.js";
4
4
  import { Fmt } from "@effected/cli";
5
5
  import { KeyHelp, KeyTable, Styled, useKeys, useTerminalSize } from "@effected/cli/ui";
package/cli/version.js CHANGED
@@ -6,6 +6,6 @@
6
6
  *
7
7
  * @internal
8
8
  */
9
- const CLI_VERSION = "2.2.0";
9
+ const CLI_VERSION = "2.3.0";
10
10
 
11
11
  export { CLI_VERSION };
package/cli/walk-plan.js CHANGED
@@ -1,7 +1,7 @@
1
- import { versionKeyOf } from "./version-key.js";
1
+ import { detectPeerDrift } from "./drift.js";
2
2
  import { derivePeerRange } from "./peer-range.js";
3
3
  import { planEntry } from "./plan.js";
4
- import { detectPeerDrift } from "./drift.js";
4
+ import { versionKeyOf } from "./version-key.js";
5
5
  import { Effect } from "effect";
6
6
 
7
7
  // src/cli/walk-plan.ts
@@ -1,6 +1,6 @@
1
- import { Result } from "effect";
2
- import { dirname, join } from "node:path";
3
- import { existsSync } from "node:fs";
1
+ import { Order, Predicate, Result } from "effect";
2
+ import { locateWorkspaceRootSync } from "@effected/workspaces";
3
+ import { nodeSyncOps } from "@effected/workspaces/node-sync";
4
4
  import { Yaml, YamlStringifyOptions } from "@effected/yaml";
5
5
 
6
6
  // src/cli/workspace-file.ts
@@ -23,31 +23,28 @@ function allPrimitive(arr) {
23
23
  function canonicalize(value) {
24
24
  if (Array.isArray(value)) {
25
25
  const mapped = value.map(canonicalize);
26
- return allPrimitive(mapped) ? [...mapped].sort((a, b) => String(a).localeCompare(String(b))) : mapped;
26
+ return allPrimitive(mapped) ? [...mapped].sort((a, b) => Order.String(String(a), String(b))) : mapped;
27
27
  }
28
- if (value !== null && typeof value === "object") return Object.fromEntries(Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([k, v]) => [k, canonicalize(v)]));
28
+ if (Predicate.isObject(value)) return Object.fromEntries(Object.entries(value).sort(([a], [b]) => Order.String(a, b)).map(([k, v]) => [k, canonicalize(v)]));
29
29
  return value;
30
30
  }
31
31
  /**
32
32
  * Walk up from `startDir` to the nearest directory containing a
33
- * pnpm-workspace.yaml; returns the file path or null.
33
+ * pnpm-workspace.yaml; returns the file path or null. A `package.json`
34
+ * `workspaces` field is not a marker here: this tool targets pnpm only.
34
35
  *
35
36
  * @internal
36
37
  */
37
38
  function findWorkspaceFile(startDir) {
38
- let dir = startDir;
39
- for (;;) {
40
- const candidate = join(dir, FILENAME);
41
- if (existsSync(candidate)) return candidate;
42
- const parent = dirname(dir);
43
- if (parent === dir) return null;
44
- dir = parent;
45
- }
39
+ return locateWorkspaceRootSync(startDir, {
40
+ ...nodeSyncOps,
41
+ markers: [FILENAME]
42
+ })?.path ?? null;
46
43
  }
47
44
  /** Parse pnpm-workspace.yaml source; empty/whitespace yields an empty object. @internal */
48
45
  function parseWorkspace(source) {
49
46
  const parsed = Result.getOrThrow(Yaml.parseResult(source));
50
- return parsed && typeof parsed === "object" ? parsed : {};
47
+ return Predicate.isObject(parsed) ? parsed : {};
51
48
  }
52
49
  /** Render a workspace object: deterministic key sort + yaml.stringify. @internal */
53
50
  function renderWorkspace(obj) {
@@ -1,13 +1,12 @@
1
1
  import { parseVersion } from "../semver-util.js";
2
- import { findWorkspaceFile } from "./workspace-file.js";
3
2
  import { ResolveError } from "./resolve.js";
4
3
  import { Effect, Layer, Result } from "effect";
5
- import { dirname, join, resolve } from "node:path";
4
+ import { join, resolve } from "node:path";
6
5
  import { GlobPattern, GlobPatternOptions } from "@effected/glob";
7
6
  import { descendSync } from "@effected/walker";
8
7
  import { nodeWalkerSyncOps } from "@effected/walker/node-sync";
9
8
  import { readFileSync } from "node:fs";
10
- import { getWorkspacePackagesSync } from "@effected/workspaces";
9
+ import { findWorkspaceRootSync, getWorkspacePackagesSync } from "@effected/workspaces";
11
10
  import { nodeSyncOps } from "@effected/workspaces/node-sync";
12
11
 
13
12
  // src/cli/workspace-resolve.ts
@@ -159,8 +158,10 @@ function makeWorkspaceResolver(rootDir) {
159
158
  * @internal
160
159
  */
161
160
  function findWorkspaceRoot(startDir) {
162
- const file = findWorkspaceFile(resolve(startDir));
163
- return file === null ? resolve(startDir) : dirname(file);
161
+ return findWorkspaceRootSync(startDir, {
162
+ ...nodeSyncOps,
163
+ markers: ["pnpm-workspace.yaml"]
164
+ }) ?? resolve(startDir);
164
165
  }
165
166
 
166
167
  export { findWorkspaceRoot, makeWorkspaceResolver, readManifests, readWorkspaceVersions };
package/index.d.ts CHANGED
@@ -83,12 +83,28 @@ interface AllowedVersionsFromCatalogs {
83
83
  * authored by hand wins over a derived one on the same selector, and the
84
84
  * consumer's own `overrides` still win over the plugin's at install time.
85
85
  *
86
+ * `include` and `exclude` select which members are pinned, so one catalog can
87
+ * hold both a release family that is pinned graph-wide and packages that are
88
+ * not. Each entry is an exact package name or a glob pattern (`@effect/*`),
89
+ * matched with `effect/Glob` against the whole name; `*` stays within the
90
+ * scope or the bare name. A member matched by both lists is excluded. Every
91
+ * entry must match at least one member, and an empty `include` is rejected,
92
+ * so a typo throws instead of silently widening or narrowing the pin set.
93
+ *
86
94
  * @public
87
95
  */
88
96
  interface OverridesFromCatalogs {
89
97
  /** The catalog whose members are pinned. */
90
98
  readonly catalog: string;
91
- /** Members left out, e.g. a tool that rides the catalog but is not part of the release family. */
99
+ /**
100
+ * Members to pin, as exact names or glob patterns (`effect`, `@effect/*`);
101
+ * the rest are skipped. Omitted, every member is a candidate.
102
+ */
103
+ readonly include?: readonly string[];
104
+ /**
105
+ * Members left out, as exact names or glob patterns, e.g. a tool that rides
106
+ * the catalog but is not part of the release family. Wins over `include`.
107
+ */
92
108
  readonly exclude?: readonly string[];
93
109
  /**
94
110
  * How each selector is scoped. `"line"` (the default) qualifies the package
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rolldown-pnpm-config",
3
- "version": "2.2.0",
3
+ "version": "2.3.0",
4
4
  "private": false,
5
5
  "description": "A dogfooding example of our plugin",
6
6
  "repository": {
@@ -35,16 +35,16 @@
35
35
  "rolldown-pnpm-config": "bin/rolldown-pnpm-config.js"
36
36
  },
37
37
  "dependencies": {
38
- "@effect/platform-node": "^4.0.6",
39
- "@effected/cli": "^0.23.0",
40
- "@effected/env": "^0.6.0",
41
- "@effected/glob": "^0.15.0",
42
- "@effected/npm": "^0.27.1",
43
- "@effected/semver": "^0.18.0",
44
- "@effected/walker": "^0.21.0",
45
- "@effected/workspaces": "^0.39.1",
46
- "@effected/yaml": "^0.25.1",
47
- "effect": "^4.0.6",
38
+ "@effect/platform-node": "~4.1.1",
39
+ "@effected/cli": "^0.25.0",
40
+ "@effected/env": "^0.9.0",
41
+ "@effected/glob": "^0.17.0",
42
+ "@effected/npm": "^0.29.0",
43
+ "@effected/semver": "^0.20.0",
44
+ "@effected/walker": "^0.24.0",
45
+ "@effected/workspaces": "^0.41.0",
46
+ "@effected/yaml": "^0.27.0",
47
+ "effect": "~4.1.1",
48
48
  "ink": "^8.0.0",
49
49
  "oxc-parser": "^0.156.0",
50
50
  "react": "^19.3.1"
@@ -1,5 +1,6 @@
1
1
  import { rangeOf } from "../catalogs.js";
2
2
  import { bareVersion } from "../semver-util.js";
3
+ import { Predicate } from "effect";
3
4
 
4
5
  // src/plugin/allowed-versions.ts
5
6
  /** An exact version: no range operator, no wildcard — `X.Y.Z` with an optional prerelease. */
@@ -43,7 +44,7 @@ function deriveAllowedVersions(catalogs, directives) {
43
44
  * @internal
44
45
  */
45
46
  function resolvePeerDependencyRules(value, catalogs) {
46
- if (value === null || typeof value !== "object" || !("allowedVersionsFromCatalogs" in value)) return value;
47
+ if (!Predicate.isObject(value) || !("allowedVersionsFromCatalogs" in value)) return value;
47
48
  const { allowedVersionsFromCatalogs, ...rest } = value;
48
49
  const merged = {
49
50
  ...deriveAllowedVersions(catalogs, allowedVersionsFromCatalogs),
package/plugin/freeze.js CHANGED
@@ -3,7 +3,7 @@ import { DESCRIPTORS, deriveSchemas } from "../descriptors/index.js";
3
3
  import { FIELD_REGISTRY } from "../registry.js";
4
4
  import { resolvePeerDependencyRules } from "./allowed-versions.js";
5
5
  import { resolveOverrides } from "./overrides-from-catalogs.js";
6
- import { Data, Effect, Schema } from "effect";
6
+ import { Data, Effect, Predicate, Schema } from "effect";
7
7
  import { Fmt } from "@effected/cli";
8
8
 
9
9
  // src/plugin/freeze.ts
@@ -33,7 +33,7 @@ const WRAPPED_KEYS = /* @__PURE__ */ new Set([
33
33
  * @internal
34
34
  */
35
35
  function isWrappedField(input) {
36
- if (input === null || typeof input !== "object") return false;
36
+ if (!Predicate.isObject(input)) return false;
37
37
  const keys = Object.keys(input);
38
38
  return keys.includes("value") && keys.every((k) => WRAPPED_KEYS.has(k));
39
39
  }
@@ -1,5 +1,6 @@
1
1
  import { rangeOf } from "../catalogs.js";
2
2
  import { bareVersion } from "../semver-util.js";
3
+ import { Glob, Predicate } from "effect";
3
4
 
4
5
  // src/plugin/overrides-from-catalogs.ts
5
6
  /** A plain release version: `X.Y.Z`, no prerelease, no build metadata. */
@@ -16,15 +17,29 @@ function releaseLine(major, minor) {
16
17
  if (major !== "0") return `^${major}.0.0`;
17
18
  return minor === "0" ? "~0.0.0" : `^0.${minor}.0`;
18
19
  }
19
- /** Derive the overrides for one directive. Pure; throws when the catalog or an excluded member is absent. */
20
- function deriveOne(catalogs, { catalog, exclude = [], scope = "line" }) {
20
+ /**
21
+ * Compile a filter list into a member predicate, throwing when an entry
22
+ * matches no member (an exact name or a pattern alike).
23
+ */
24
+ function compileFilter(entries, members, label, catalog) {
25
+ const globs = entries.map((entry) => {
26
+ const glob = Glob.make(entry);
27
+ if (!members.some((name) => Glob.match(glob, name))) throw new Error(`overridesFromCatalogs: ${label} member "${entry}" is not in catalog "${catalog}"`);
28
+ return glob;
29
+ });
30
+ return (name) => globs.some((glob) => Glob.match(glob, name));
31
+ }
32
+ /** Derive the overrides for one directive. Pure; throws when the catalog is absent or a filter entry matches nothing. */
33
+ function deriveOne(catalogs, { catalog, include, exclude = [], scope = "line" }) {
21
34
  const decl = catalogs[catalog];
22
35
  if (decl === void 0) throw new Error(`overridesFromCatalogs: catalog "${catalog}" is not declared`);
23
- for (const name of exclude) if (!(name in decl.packages)) throw new Error(`overridesFromCatalogs: excluded member "${name}" is not in catalog "${catalog}"`);
24
- const excluded = new Set(exclude);
36
+ if (include?.length === 0) throw new Error(`overridesFromCatalogs: include for catalog "${catalog}" is empty, which would pin nothing`);
37
+ const members = Object.keys(decl.packages);
38
+ const isIncluded = include === void 0 ? () => true : compileFilter(include, members, "included", catalog);
39
+ const isExcluded = compileFilter(exclude, members, "excluded", catalog);
25
40
  const table = {};
26
41
  for (const [name, spec] of Object.entries(decl.packages)) {
27
- if (excluded.has(name)) continue;
42
+ if (!isIncluded(name) || isExcluded(name)) continue;
28
43
  const range = rangeOf(spec);
29
44
  if (!SIMPLE_RANGE.test(range) || /\s/.test(range.trim())) continue;
30
45
  const version = bareVersion(range);
@@ -58,7 +73,7 @@ function deriveOverrides(catalogs, directives) {
58
73
  function resolveOverrides(authored, directives, catalogs) {
59
74
  if (directives === void 0) return authored;
60
75
  const derived = deriveOverrides(catalogs, directives);
61
- const manual = authored !== null && typeof authored === "object" ? authored : {};
76
+ const manual = Predicate.isObject(authored) ? authored : {};
62
77
  return {
63
78
  ...derived,
64
79
  ...manual
@@ -1,8 +1,10 @@
1
+ import { Order, Predicate } from "effect";
2
+
1
3
  // src/plugin/serialize.ts
2
4
  /** Recursively sort object keys for deterministic output; arrays keep order. */
3
5
  function sortKeys(value) {
4
6
  if (Array.isArray(value)) return value.map(sortKeys);
5
- if (value !== null && typeof value === "object") return Object.fromEntries(Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([k, v]) => [k, sortKeys(v)]));
7
+ if (Predicate.isObject(value)) return Object.fromEntries(Object.entries(value).sort(([a], [b]) => Order.String(a, b)).map(([k, v]) => [k, sortKeys(v)]));
6
8
  return value;
7
9
  }
8
10
  /** Source for the `catalogs` virtual module: a sorted Map literal (plain-JS branch). */
@@ -1,9 +1,11 @@
1
1
  // src/runtime/strategies/arrays.ts
2
+ /** Code-unit order: locale-independent, so the render is the same on every machine. */
3
+ const byCodeUnit = (a, b) => a < b ? -1 : a > b ? 1 : 0;
2
4
  /** Union `managed` with `local` (when present), deduped and sorted for a stable render. @internal */
3
5
  function unionSort(managed, local) {
4
6
  const set = new Set(managed);
5
7
  for (const item of local ?? []) set.add(item);
6
- return [...set].sort((a, b) => a.localeCompare(b));
8
+ return [...set].sort(byCodeUnit);
7
9
  }
8
10
  /**
9
11
  * Union + sort string arrays; child entries are added to the managed set. Quiet.
@@ -24,7 +26,7 @@ const arrayRecordUnion = (base, local) => {
24
26
  const child = local ?? {};
25
27
  const keys = /* @__PURE__ */ new Set([...Object.keys(managed), ...Object.keys(child)]);
26
28
  const result = {};
27
- for (const key of [...keys].sort((a, b) => a.localeCompare(b))) {
29
+ for (const key of [...keys].sort(byCodeUnit)) {
28
30
  const merged = unionSort(managed[key] ?? [], child[key]);
29
31
  if (merged.length > 0) result[key] = merged;
30
32
  }
@@ -1,7 +1,7 @@
1
1
  import { arrayRecordUnion, arrayUnion } from "./arrays.js";
2
- import { overrides, peerDependencyRules } from "./overrides.js";
3
2
  import { catalogs } from "./catalogs.js";
4
3
  import { allowBuilds, mapChildWins } from "./maps.js";
4
+ import { overrides, peerDependencyRules } from "./overrides.js";
5
5
  import { scalar, securityFlag, securityMin } from "./scalar.js";
6
6
 
7
7
  // src/runtime/strategies/table.ts
@@ -5,7 +5,7 @@
5
5
  "toolPackages": [
6
6
  {
7
7
  "packageName": "@microsoft/api-extractor",
8
- "packageVersion": "7.59.4"
8
+ "packageVersion": "7.60.0"
9
9
  }
10
10
  ]
11
11
  }