@effected/workspaces 0.30.3 → 0.31.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.
@@ -33,8 +33,9 @@ cycle: Schema.Array(Schema.String) }) {
33
33
  * `dependencies`, `devDependencies`, `peerDependencies` and
34
34
  * `optionalDependencies`; a self-edge is dropped.
35
35
  *
36
- * Total accessors never fail; the four fallible operations are `Effect.fn`
37
- * boundaries.
36
+ * Total accessors never fail. The lookups fail with `PackageNotFoundError` for
37
+ * an unknown name, and the ordering operations (`levels`, `sort`, `sortSubset`)
38
+ * fail with {@link CyclicDependencyError} when the graph has a cycle.
38
39
  *
39
40
  * @example
40
41
  * ```ts
@@ -141,8 +142,11 @@ packages: Schema.Array(WorkspacePackage) }) {
141
142
  })) : Effect.succeed([...dependents].sort());
142
143
  });
143
144
  /**
144
- * `name` plus every package that transitively depends on it — the blast
145
- * radius of a change. Sorted; includes `name` itself.
145
+ * Each of `names` plus every package that transitively depends on any of
146
+ * them — the blast radius of a change. Sorted and de-duplicated; the given
147
+ * names are included, whether or not the graph knows them.
148
+ *
149
+ * @param names - The changed package names.
146
150
  */
147
151
  affectedBy = Effect.fn("DependencyGraph.affectedBy")((names) => {
148
152
  const { reverse } = this.#index();
@@ -162,9 +166,8 @@ packages: Schema.Array(WorkspacePackage) }) {
162
166
  * the workspace, level *n* depends only on levels below it.
163
167
  *
164
168
  * @remarks
165
- * Kahn's algorithm over the reverse-edge index — linear, where v3's rescanned
166
- * the whole adjacency map per processed node. Each level is sorted
167
- * lexicographically, so the output is deterministic.
169
+ * Kahn's algorithm over the reverse-edge index, linear in the edge count.
170
+ * Each level is sorted lexicographically, so the output is deterministic.
168
171
  */
169
172
  levels = Effect.fn("DependencyGraph.levels")(() => Effect.suspend(() => {
170
173
  const edges = this.#index();
@@ -176,6 +179,13 @@ packages: Schema.Array(WorkspacePackage) }) {
176
179
  /**
177
180
  * A topological order over `names` plus their transitive workspace
178
181
  * dependencies — the build order for a subset.
182
+ *
183
+ * @remarks
184
+ * Fails with `PackageNotFoundError` for a name the graph does not contain,
185
+ * and with {@link CyclicDependencyError} when the subset's closure has a
186
+ * cycle.
187
+ *
188
+ * @param names - The packages to build; each must be a workspace package.
179
189
  */
180
190
  sortSubset = Effect.fn("DependencyGraph.sortSubset")((names) => Effect.suspend(() => {
181
191
  const { forward } = this.#index();
package/DuplicateCheck.js CHANGED
@@ -85,10 +85,12 @@ var DuplicatedPackage = class extends Schema.Class("DuplicatedPackage")({
85
85
  *
86
86
  * @example
87
87
  * ```ts
88
- * import { DuplicateCheck } from "@effected/workspaces";
89
88
  * import { Lockfile } from "@effected/lockfiles";
89
+ * import { DuplicateCheck } from "@effected/workspaces";
90
90
  * import { Effect } from "effect";
91
91
  *
92
+ * declare const text: string; // the text of a pnpm-lock.yaml
93
+ *
92
94
  * const program = Effect.gen(function* () {
93
95
  * const lockfile = yield* Lockfile.parse(text, { format: "pnpm" });
94
96
  * const report = DuplicateCheck.run(lockfile, { names: DuplicateCheck.kit });
@@ -119,8 +121,8 @@ var DuplicateCheck = class DuplicateCheck extends Schema.Class("DuplicateCheck")
119
121
  * In practice this is the **root importer under npm and bun**: neither
120
122
  * records a resolved version per importer dependency, and neither emits a
121
123
  * package row for the root, so there is nothing to join on. pnpm records a
122
- * version per importer dependency and is unaffected. The same limitation
123
- * `PeerCheck` reports, measured by the same code.
124
+ * version per importer dependency and is unaffected. `PeerCheck` reports the
125
+ * same limitation.
124
126
  *
125
127
  * Reported rather than silently skipped: a gate that sees no duplicates is
126
128
  * entitled to know whether that means "clean" or "not looked at".
@@ -147,8 +149,7 @@ var DuplicateCheck = class DuplicateCheck extends Schema.Class("DuplicateCheck")
147
149
  * Exactly those — not `@effect/*`, and not names merely starting with
148
150
  * `effect`. A duplicated kit package presents as a `Layer` requirement that
149
151
  * looks provided yet cannot be satisfied, at the entry point, naming neither
150
- * the package nor the skew (issue #298); this predicate is the check that
151
- * names both.
152
+ * the package nor the skew; this predicate is the check that names both.
152
153
  *
153
154
  * @param name - a package name
154
155
  * @returns whether the name belongs to the kit
@@ -222,7 +223,7 @@ var DuplicateCheck = class DuplicateCheck extends Schema.Class("DuplicateCheck")
222
223
  const dependents = /* @__PURE__ */ new Map();
223
224
  const seen = /* @__PURE__ */ new Set();
224
225
  const record = (targetId, dependent) => {
225
- const key = `${targetId}${renderKey(dependent)}`;
226
+ const key = `${targetId}\0${renderKey(dependent)}`;
226
227
  if (seen.has(key)) return;
227
228
  seen.add(key);
228
229
  const list = dependents.get(targetId) ?? [];
@@ -287,7 +288,7 @@ const dependentOf = (from) => from.isWorkspace && from.relativePath !== void 0 ?
287
288
  version: from.version
288
289
  };
289
290
  /** @internal */
290
- const renderKey = (dependent) => dependent._tag === "importer" ? `importer${dependent.path}` : `package${dependent.name}${dependent.version}`;
291
+ const renderKey = (dependent) => dependent._tag === "importer" ? `importer\0${dependent.path}` : `package\0${dependent.name}\0${dependent.version}`;
291
292
 
292
293
  //#endregion
293
294
  export { Dependent, DuplicateCheck, DuplicateInstance, DuplicatedPackage, DuplicatedVersion };
package/LayerPolicy.js CHANGED
@@ -59,7 +59,7 @@ var LayerPolicyError = class extends Schema.TaggedError()("LayerPolicyError", {
59
59
  * message names every such key. A typo on an optional key would otherwise be
60
60
  * silently dropped: `requiredEdge` would remove the non-vacuity guard and
61
61
  * leave the report green. `$schema` is always accepted. A file that carries
62
- * keys of its own, such as systems' `harness`, passes them in `allowKeys`.
62
+ * keys of its own, such as a `harness` block, passes them in `allowKeys`.
63
63
  *
64
64
  * @example
65
65
  * ```ts
package/LockfileReader.js CHANGED
@@ -112,11 +112,14 @@ var LockfileReader = class LockfileReader extends Context.Service()("@effected/w
112
112
  };
113
113
  });
114
114
  /**
115
- * The live layer.
115
+ * The live layer: reads the lockfile of the detected package manager at the
116
+ * workspace root.
116
117
  *
117
118
  * @remarks
118
119
  * Parameterized, so it mints a fresh reference per call — bind it to a
119
120
  * `const` and reuse it.
121
+ *
122
+ * @param options - Root resolution (`cwd`, `stopAt`).
120
123
  */
121
124
  static layer = (options) => Layer.effect(LockfileReader, LockfileReader.make(options));
122
125
  /**
@@ -82,8 +82,8 @@ const isPlainObject = (value) => typeof value === "object" && value !== null &&
82
82
  * is not an exact version.
83
83
  *
84
84
  * Reuses the corepack `name@version+integrity` grammar rather than a second
85
- * parser, so a `devEngines` version carrying a hash (`11.11.0+sha512.…`, which
86
- * this repo's own root manifest does) normalizes to the same `11.11.0` the
85
+ * parser, so a `devEngines` version carrying a hash (`11.11.0+sha512.…`)
86
+ * normalizes to the same `11.11.0` the
87
87
  * top-level field reports — and a *range* (`^11`, `11.x`) yields none, because a
88
88
  * range is not a version and corepack will not run one either.
89
89
  */
@@ -326,8 +326,7 @@ var PackageManagerDetector = class PackageManagerDetector extends Context.Servic
326
326
  * `WorkspaceDiscovery.info` posture, for the same reason. A stand-in that
327
327
  * answered `"pnpm"` would hand a consumer a fact nothing established, and it
328
328
  * would contradict the very service it stands in for: the live detector's
329
- * defining property is that it [refuses to
330
- * guess](https://github.com/spencerbeggs/effected) when no evidence matches.
329
+ * defining property is that it refuses to guess when no evidence matches.
331
330
  * A double that guesses is worse than no double.
332
331
  *
333
332
  * Failing typed would be the subtler mistake: `PackageManagerDetectionError`
package/PeerCheck.js CHANGED
@@ -402,10 +402,12 @@ const judgeJoined = (context, catalogs, peer, declared, optional) => {
402
402
  *
403
403
  * @example
404
404
  * ```ts
405
- * import { PeerCheck } from "@effected/workspaces";
406
405
  * import { Lockfile } from "@effected/lockfiles";
406
+ * import { PeerCheck } from "@effected/workspaces";
407
407
  * import { Effect } from "effect";
408
408
  *
409
+ * declare const text: string; // the text of a pnpm-lock.yaml
410
+ *
409
411
  * const program = Effect.gen(function* () {
410
412
  * const lockfile = yield* Lockfile.parse(text, { format: "pnpm" });
411
413
  * const report = PeerCheck.run(lockfile);
@@ -508,7 +510,7 @@ var PeerCheck = class PeerCheck extends Schema.Class("PeerCheck")({
508
510
  * `pnpm peers check` DOES report the linked parent's peers (it reads the
509
511
  * manifest on disk): every `link:` edge whose target the caller did not
510
512
  * supply therefore fails the report closed rather than letting the
511
- * invisible parent pass as checked (effected#800).
513
+ * invisible parent pass as checked.
512
514
  *
513
515
  * Supplying {@link PeerCheckOptions.workspacePackages} closes that gap for
514
516
  * every `link:` target the set covers — the root importer's included,
@@ -543,6 +545,8 @@ var PeerCheck = class PeerCheck extends Schema.Class("PeerCheck")({
543
545
  * version check, and a plain unparseable version is still skipped.
544
546
  *
545
547
  * @param lockfile - a lockfile parsed by `@effected/lockfiles`
548
+ * @param options - the workspace's effective rules, manifests and catalogs; see
549
+ * {@link PeerCheckOptions}
546
550
  * @returns the report; never fails
547
551
  */
548
552
  static run(lockfile, options) {
package/Publishability.js CHANGED
@@ -87,16 +87,13 @@ var PublishTarget = class extends Schema.Class("PublishTarget")({
87
87
  var PublishabilityDetector = class extends Context.Service()("@effected/workspaces/PublishabilityDetector") {
88
88
  /**
89
89
  * Standard npm publishing semantics, **as a value**. Pure — no filesystem,
90
- * no platform services.
90
+ * no platform services. Never publishes with provenance (`provenance: false`).
91
91
  *
92
92
  * @remarks
93
93
  * Exposed as a shape and not only as a layer, because a consumer composing
94
- * *around* these rules cannot reach them through a layer without re-entering
95
- * the very tag it is replacing. `@savvy-web/silk-effects` had to write
96
- * `Effect.provide(PublishabilityDetector, PublishabilityDetector.layer)`
97
- * **inside its own implementation of that tag** to get at this function for
98
- * its pass-through branch; with the value exposed that becomes
99
- * `PublishabilityDetector.npm.detect(pkg)`.
94
+ * *around* these rules cannot reach them through a layer without
95
+ * re-entering the very tag it is replacing. A custom detector's pass-through
96
+ * branch calls `PublishabilityDetector.npm.detect(pkg)` directly.
100
97
  *
101
98
  * @example
102
99
  * ```ts
@@ -131,13 +128,12 @@ var PublishabilityDetector = class extends Context.Service()("@effected/workspac
131
128
  *
132
129
  * @remarks
133
130
  * Named for its policy rather than called `layer`, deliberately. **No
134
- * composite in this package provides a publishability detector**: a
135
- * `Workspaces.layer()` that quietly supplied npm semantics made the choice
136
- * invisible, and worse, made a naively-ordered override lose to it in
137
- * silence — `Layer.mergeAll(myDetector, Workspaces.layer())` resolved to the
138
- * default, because `mergeAll` is last-wins. For a service that decides
139
- * whether a package publishes and to which registry, that silent revert was
140
- * the worst available failure.
131
+ * composite in this package provides a publishability detector**: one that
132
+ * quietly supplied npm semantics would hide the choice and, because
133
+ * `Layer.mergeAll` is last-wins, would let a naively-ordered override such
134
+ * as `Layer.mergeAll(myDetector, Workspaces.layer())` lose to the default in
135
+ * silence. For a service that decides whether a package publishes and to
136
+ * which registry, that silent revert is the worst available failure.
141
137
  *
142
138
  * The composites do not *require* a detector either — nothing inside them
143
139
  * asks a publishability question, so their `R` stays `FileSystem | Path`.
package/README.md CHANGED
@@ -7,10 +7,10 @@
7
7
 
8
8
  Monorepo workspace tooling for [Effect](https://effect.website) v4: find the workspace root, enumerate its packages, walk the dependency graph, detect the package manager, resolve pnpm catalogs, read the lockfile, check it for unsatisfied peer dependencies and work out which packages a git range touches. Every capability is a service you provide at the edge and swap in tests. Works with npm, pnpm, yarn Berry and bun.
9
9
 
10
- > **Pre-release.** This package is part of the `@effected/*` kit, in pre-`1.0.0`
11
- > development against a single pinned Effect v4 prerelease. Packages graduate to
12
- > `1.0.0` once Effect `4.0.0` ships. To hold your own `effect` versions at
13
- > exactly the ones the kit is built and tested against, install
10
+ > **Pre-`1.0.0`.** This package is part of the `@effected/*` kit, built on stable
11
+ > Effect v4 (`effect` `^4.0.0`) and still in `0.x` development. Stable Effect
12
+ > makes a kit `1.0.0` possible, not automatic. To keep your `effect` and
13
+ > `@effect/*` versions on the line the kit is built and tested against, install
14
14
  > [`@effected/pnpm-plugin-effect`](https://www.npmjs.com/package/@effected/pnpm-plugin-effect).
15
15
  >
16
16
  > **Stability: unstable.** This package's API surface is not yet considered
package/ReleaseTag.js CHANGED
@@ -194,11 +194,11 @@ const classifyTag = (tag) => {
194
194
  * even when `value` carries a prefix, so a consumer comparing versions never
195
195
  * has to strip one back off.
196
196
  *
197
- * Formatting is **total**: there is no error channel, because the only failure
198
- * v3 modelled — an empty version — is caught by `Schema.NonEmptyString` when
199
- * the value is constructed. A bad version reaching these statics is developer
200
- * wiring rather than untrusted input, so it dies as a defect, the same posture
201
- * as an uncompilable glob literal in `WorkspacePackage.matchesDependency`.
197
+ * Formatting has **no error channel**: an empty version is rejected by
198
+ * `Schema.NonEmptyString` when the value is constructed. A bad version
199
+ * reaching these statics is developer wiring rather than untrusted input, so it
200
+ * dies as a defect, the same posture as an uncompilable glob literal in
201
+ * `WorkspacePackage.matchesDependency`.
202
202
  *
203
203
  * @example
204
204
  * ```ts
@@ -227,6 +227,7 @@ var ReleaseTag = class ReleaseTag extends Schema.Class("ReleaseTag")({
227
227
  *
228
228
  * @param version - The version being released. Must not be empty.
229
229
  * @param options - Formatting overrides.
230
+ * @returns the single-style {@link ReleaseTag}.
230
231
  */
231
232
  static single(version, options) {
232
233
  const prefix = options?.versionPrefix ?? "";
package/SourceBoundary.js CHANGED
@@ -616,7 +616,7 @@ var SourceBoundary = class SourceBoundary {
616
616
  });
617
617
  /** The shipped positive- and negative-control snippets, at least one of each per rule kind. */
618
618
  static fixtures = FIXTURES;
619
- /** The names of shipped fixtures the scanner now gets wrong; `[]` means it still flags and spares what it must. */
619
+ /** The names of shipped fixtures the scanner gets wrong; `[]` means it still flags and spares what it must. */
620
620
  static verifyFixtures = () => FIXTURES.filter((fixture) => SourceBoundary.check("fixture.ts", fixture.source, [fixture.rule]).length > 0 !== fixture.flagged).map((fixture) => `${fixture.flagged ? "missed" : "false positive"}: ${fixture.name}`);
621
621
  };
622
622
 
@@ -30,13 +30,13 @@ const VersioningStrategyType = Schema.Literals([
30
30
  *
31
31
  * @example
32
32
  * ```ts
33
- * import { VersioningStrategy } from "@effected/workspaces";
33
+ * import { PublishabilityDetector, VersioningStrategy } from "@effected/workspaces";
34
34
  * import { Effect } from "effect";
35
35
  *
36
36
  * const program = Effect.gen(function* () {
37
- * const strategy = yield* VersioningStrategy.detect({ fixedGroups });
38
- * return strategy.tagsFor(released).map((tag) => tag.value);
39
- * });
37
+ * const strategy = yield* VersioningStrategy.detect({ fixedGroups: [["@acme/a", "@acme/b"]] });
38
+ * return strategy.tagsFor([{ name: "@acme/a", version: "1.2.3" }]).map((tag) => tag.value);
39
+ * }).pipe(Effect.provide(PublishabilityDetector.layerNpm));
40
40
  * ```
41
41
  *
42
42
  * @public
@@ -66,6 +66,9 @@ var VersioningStrategy = class VersioningStrategy extends Schema.Class("Versioni
66
66
  * Pure and total — no IO, no error channel. `packages` is sorted and
67
67
  * de-duplicated first, so a name listed twice cannot inflate a one-package
68
68
  * repo into an independent one.
69
+ *
70
+ * @param options - The publishable package names and any fixed groups.
71
+ * @returns the classified {@link VersioningStrategy}.
69
72
  */
70
73
  static classify(options) {
71
74
  const fixedGroups = options.fixedGroups ?? [];
@@ -85,6 +88,10 @@ var VersioningStrategy = class VersioningStrategy extends Schema.Class("Versioni
85
88
  * The publishability question is asked through the service precisely so a
86
89
  * consumer with its own rules — honouring a release tool's ignore list, say —
87
90
  * swaps the layer instead of filtering afterwards.
91
+ *
92
+ * Requires `WorkspaceDiscovery` and `PublishabilityDetector` (for example
93
+ * `PublishabilityDetector.layerNpm`) in `R`, and fails with
94
+ * `WorkspaceDiscoveryFailure` when discovery does.
88
95
  */
89
96
  static detect = Effect.fn("VersioningStrategy.detect")(function* (options) {
90
97
  const discovery = yield* WorkspaceDiscovery;
@@ -114,6 +121,9 @@ var VersioningStrategy = class VersioningStrategy extends Schema.Class("Versioni
114
121
  * one-line check rather than a field here.
115
122
  *
116
123
  * An empty batch produces no tags under either style.
124
+ *
125
+ * @param releases - The packages released and their versions.
126
+ * @param options - Tag formatting overrides, such as `versionPrefix`.
117
127
  */
118
128
  tagsFor(releases, options) {
119
129
  if (releases.length === 0) return [];
@@ -578,10 +578,9 @@ var WorkspaceCatalogs = class WorkspaceCatalogs extends Context.Service()("@effe
578
578
  * `rangeOf` returns `Option.none()` for a dependency no catalog declares, per
579
579
  * the contract's convention. A failed catalog *assembly* — an unreadable or
580
580
  * malformed `pnpm-workspace.yaml`, a broken config-dependency hook — passes
581
- * through **typed** as the contract's `CatalogAssemblyError` (it used to be
582
- * folded into `DependencyResolutionError`'s defect `cause`, which forced
583
- * consumers to `_tag`-sniff `unknown`); only the remaining mechanism failure,
584
- * an unfindable workspace root, is wrapped as `DependencyResolutionError`.
581
+ * through **typed** as the contract's `CatalogAssemblyError`; only the
582
+ * remaining mechanism failure, an unfindable workspace root, is wrapped as
583
+ * `DependencyResolutionError`.
585
584
  */
586
585
  static catalogResolver = Layer.effect(CatalogResolver, Effect.gen(function* () {
587
586
  const catalogs = yield* WorkspaceCatalogs;
@@ -19,7 +19,7 @@ import { DependencyResolutionError, WorkspaceResolver } from "@effected/npm";
19
19
  * A manifest with no `version` is NOT a failure: pnpm accepts a version-less
20
20
  * private package and a private monorepo root without one is the ordinary
21
21
  * shape, so the member is discovered with `WorkspacePackage.version` absent.
22
- * The former `missingVersion` kind is retired. Only ABSENCE is tolerated: a
22
+ * Only ABSENCE is tolerated: a
23
23
  * `version` that is present but not a string — or present and `""`, which pnpm
24
24
  * never wrote and which would resolve `workspace:^` to a bare `"^"` — is the
25
25
  * manifest's shape being wrong and reports `invalidShape`.
@@ -118,11 +118,9 @@ const patternKindOf = (kind) => kind;
118
118
  * the lifetime of the layer. A Vitest reporter that builds the layer per call
119
119
  * site and never queries it pays nothing.
120
120
  *
121
- * The memo is **success-only**. `Effect.cached` memoizes the first `Exit`,
122
- * *including an interrupt* — an init interrupted by an unrelated timeout would
123
- * otherwise brick the layer permanently with a cause outside its declared error
124
- * channel. A failure or interrupt is therefore retried on the next call, which
125
- * is a deliberate behaviour change from the v3 library.
121
+ * The memo is **success-only**: a failure or interrupt is not cached, so an
122
+ * init interrupted by an unrelated timeout cannot brick the layer, and the next
123
+ * call retries.
126
124
  *
127
125
  * @example
128
126
  * ```ts
@@ -380,12 +378,14 @@ var WorkspaceDiscovery = class WorkspaceDiscovery extends Context.Service()("@ef
380
378
  };
381
379
  });
382
380
  /**
383
- * The live layer.
381
+ * The live layer, discovering the workspace from `options.cwd`.
384
382
  *
385
383
  * @remarks
386
384
  * A parameterized layer factory mints a **fresh reference per call**, and
387
385
  * layers memoize by reference — bind the result to a `const` and reuse it
388
386
  * rather than calling `layer(...)` at each composition site.
387
+ *
388
+ * @param options - Root resolution and enumeration bounds.
389
389
  */
390
390
  static layer = (options) => Layer.effect(WorkspaceDiscovery, WorkspaceDiscovery.make(options));
391
391
  /**
@@ -119,13 +119,9 @@ var WorkspacePackage = class WorkspacePackage extends Schema.Class("WorkspacePac
119
119
  * Absolute path to the workspace root this package was discovered under.
120
120
  *
121
121
  * @remarks
122
- * Carried, not derived. Whoever built this value already knew the root —
123
- * `WorkspaceDiscovery` resolved it before enumerating, and the sync entry
124
- * point is handed it — so dropping it forced every consumer into per-package
125
- * root arithmetic (counting `relativePath` segments and re-ascending that
126
- * many `..`). That reconstruction is exact only while `path` and
127
- * `relativePath` agree, and it re-derives something the kit never had to
128
- * lose.
122
+ * Carried, not derived: `WorkspaceDiscovery` resolves the root before
123
+ * enumerating, so every package arrives with it and consumers need no
124
+ * per-package root arithmetic over `relativePath`.
129
125
  *
130
126
  * For the root package this equals `path`, and `relativePath` is `"."`.
131
127
  */
@@ -152,8 +148,7 @@ var WorkspacePackage = class WorkspacePackage extends Schema.Class("WorkspacePac
152
148
  * validated beyond being a record. For the strict typed model use
153
149
  * `manifest()`, which deliberately **re-reads** the file — a point-in-time
154
150
  * refresh this captured record cannot provide. Defaults to `{}` for
155
- * construction sites and previously-serialized values that predate the
156
- * field.
151
+ * values constructed or decoded without the field.
157
152
  */
158
153
  manifestRecord: Schema.Record(Schema.String, Schema.Unknown).pipe(Schema.withDecodingDefaultKey(Effect.succeed(EMPTY_MANIFEST)), Schema.withConstructorDefault(Effect.succeed(EMPTY_MANIFEST)))
159
154
  }) {
@@ -218,8 +213,8 @@ var WorkspacePackage = class WorkspacePackage extends Schema.Class("WorkspacePac
218
213
  return version === void 0 ? Option.none() : Option.some(version);
219
214
  }
220
215
  /**
221
- * Whether any dependency name matches `pattern` — the `minimatch` runtime
222
- * dependency's one call site, now over `@effected/glob`'s vendored engine.
216
+ * Whether any dependency name (across all four kinds) matches the glob
217
+ * `pattern`, using `@effected/glob`.
223
218
  *
224
219
  * @remarks
225
220
  * A `GlobPattern` is total and free to test. A `string` is compiled on every
@@ -235,7 +230,12 @@ var WorkspacePackage = class WorkspacePackage extends Schema.Class("WorkspacePac
235
230
  const compiled = typeof pattern === "string" ? GlobPattern.make({ source: pattern }) : pattern;
236
231
  return Object.keys(this.allDependencies).some((dependency) => compiled.matches(dependency));
237
232
  }
238
- /** Compare this package's dependencies against `other`'s. */
233
+ /**
234
+ * Compare this package's dependencies against `other`'s, treating `other` as
235
+ * the baseline: what this package added, removed or re-specified.
236
+ *
237
+ * @param other - The package to compare against.
238
+ */
239
239
  dependencyDiff(other) {
240
240
  const mine = this.allDependencies;
241
241
  const theirs = other.allDependencies;
@@ -271,6 +271,10 @@ var WorkspacePackage = class WorkspacePackage extends Schema.Class("WorkspacePac
271
271
  * Read and decode this package's `package.json` into the strict
272
272
  * `@effected/package-json` `Package` model — the bridge from the
273
273
  * tolerant discovery projection to the fully typed manifest.
274
+ *
275
+ * @remarks
276
+ * Fails with {@link WorkspaceManifestError} (`kind: "read"` or `"decode"`) and
277
+ * requires core `FileSystem`.
274
278
  */
275
279
  static manifest = Effect.fn("WorkspacePackage.manifest")(function* (self) {
276
280
  const content = yield* (yield* FileSystem.FileSystem).readFileString(self.packageJsonPath).pipe(Effect.mapError((cause) => new WorkspaceManifestError({
package/WorkspaceRoot.js CHANGED
@@ -127,12 +127,10 @@ var WorkspaceRoot = class WorkspaceRoot extends Context.Service()("@effected/wor
127
127
  * A test double resolving every `find` to `root`, with no filesystem.
128
128
  *
129
129
  * @remarks
130
- * The nine-copies-of-a-four-line-mock case — a `Layer.succeed` over a `find`
131
- * that ignores its arguments and succeeds with a fixed root is what consumers
132
- * were writing by hand. The difference is that this double **honours
133
- * `stopAt`**: a hand-rolled `find` that ignores the ceiling makes a bounded
134
- * call pass under test and fail against the live service, which is the
135
- * failure mode the option exists to catch. A `root` above the ceiling fails
130
+ * Stands in for a hand-written `Layer.succeed` over a `find` that ignores its
131
+ * arguments. Unlike that, this double **honours `stopAt`**: a `find` that
132
+ * ignores the ceiling makes a bounded call pass under test and fail against
133
+ * the live service, which is the failure mode the option exists to catch. A `root` above the ceiling fails
136
134
  * here exactly as it would live, with the same
137
135
  * {@link WorkspaceRootNotFoundError}.
138
136
  *
@@ -250,11 +250,14 @@ var WorkspaceSnapshots = class WorkspaceSnapshots extends Context.Service()("@ef
250
250
  };
251
251
  });
252
252
  /**
253
- * The live layer.
253
+ * The live layer, reading workspace state at a git ref or from the live
254
+ * worktree.
254
255
  *
255
256
  * @remarks
256
257
  * Parameterized, so it mints a fresh reference per call — bind it to a
257
258
  * `const` and reuse it, or layer memoization does not apply.
259
+ *
260
+ * @param options - Root resolution and the optional `seedCatalogs`.
258
261
  */
259
262
  static layer = (options) => Layer.effect(WorkspaceSnapshots, WorkspaceSnapshots.make(options));
260
263
  /**
@@ -12,10 +12,9 @@ const DependencyMap = Schema.Record(Schema.String, Schema.String).pipe(Schema.wi
12
12
  *
13
13
  * @remarks
14
14
  * The Type side is a `NonEmptyString`, so `make` rejects `""` outright. The
15
- * encoded side stays a plain optional string for one reason only: snapshots
16
- * serialized while `""` was the "declared no version" sentinel must still
17
- * decode, and they decode to the absent key they always meant — so an old
18
- * stored value and a fresh capture of the same manifest compare equal.
15
+ * encoded side stays a plain optional string so that a stored value carrying
16
+ * `""` still decodes, to the absent key it meant — a stored value and a fresh
17
+ * capture of the same manifest compare equal.
19
18
  */
20
19
  const SnapshotVersion = Schema.optionalKey(Schema.String).pipe(Schema.decodeTo(Schema.optionalKey(Schema.NonEmptyString), SchemaTransformation.transformOptional({
21
20
  decode: (encoded) => Option.filter(encoded, (version) => version !== ""),
@@ -47,8 +46,7 @@ var PackageStateSnapshot = class extends Schema.Class("PackageStateSnapshot")({
47
46
  * an ordinary pnpm shape. Both capture paths — `WorkspaceSnapshots.at(ref)`
48
47
  * and `WorkspaceSnapshots.worktree()` — omit the key for such a member, so
49
48
  * the two sides of a diff agree without a placeholder. Never `""`: `make`
50
- * rejects it, and a snapshot serialized when `""` was the "no version"
51
- * sentinel decodes to the absent key.
49
+ * rejects it, and a stored `""` decodes to the absent key.
52
50
  *
53
51
  * A version-less member is still a member: it appears in `packages` and
54
52
  * answers {@link WorkspaceStateSnapshot.package}, but is absent from
@@ -125,11 +123,9 @@ var WorkspaceStateSnapshot = class WorkspaceStateSnapshot extends Schema.Class("
125
123
  * lockfile recorded it at this moment.
126
124
  *
127
125
  * @remarks
128
- * Defaults to `{}`, so a `WorkspaceStateSnapshot` serialized before this field
129
- * existed still decodes — and an empty index simply makes the `catalog:`
130
- * fallback in {@link WorkspaceStateSnapshot.resolve} inert, which is exactly
131
- * the behavior those older values were captured under. Only pnpm records
132
- * importer versions; bun and npm yield an empty index.
126
+ * Defaults to `{}` when absent from a stored value; an empty index makes the
127
+ * `catalog:` fallback in {@link WorkspaceStateSnapshot.resolve} inert. Only
128
+ * pnpm records importer versions; bun and npm yield an empty index.
133
129
  */
134
130
  importerVersions: Schema.optionalKey(Schema.Record(Schema.String, Schema.Record(Schema.String, Schema.String))),
135
131
  /**
@@ -143,9 +139,8 @@ var WorkspaceStateSnapshot = class WorkspaceStateSnapshot extends Schema.Class("
143
139
  * diagnostic and off this serializable value. Every fresh read sets the
144
140
  * field: `{}` under the no-op layer, which resolves nothing, when the file
145
141
  * declares no config dependencies, or on the bun / `package.json` path,
146
- * where config dependencies do not exist. Absent only when decoding a
147
- * snapshot serialized before the field existed, so stored values stay
148
- * readable. Carried through `withSeededCatalogs` and `crossSeed`
142
+ * where config dependencies do not exist. Absent only when decoding a stored
143
+ * value that omits it. Carried through `withSeededCatalogs` and `crossSeed`
149
144
  * unchanged, like `importerVersions`.
150
145
  */
151
146
  hookReplays: Schema.optionalKey(Schema.Record(Schema.String, Schema.String)),
@@ -171,8 +166,7 @@ var WorkspaceStateSnapshot = class WorkspaceStateSnapshot extends Schema.Class("
171
166
  * replays the ref's own pinned hook and the seed answers only what neither
172
167
  * source declared.
173
168
  *
174
- * Defaults to absent, which makes the seed inert — exactly the behavior of
175
- * every snapshot captured before this field existed.
169
+ * Defaults to absent, which makes the seed inert.
176
170
  */
177
171
  seededCatalogs: Schema.optionalKey(CatalogSet)
178
172
  }) {
package/Workspaces.js CHANGED
@@ -80,13 +80,12 @@ var Workspaces = class {
80
80
  * @remarks
81
81
  * Requires core `FileSystem` and `Path`, which the consumer provides at the
82
82
  * edge (`@effect/platform-node`, `@effect/platform-bun`, or a test's
83
- * `FileSystem.layerNoop`).
83
+ * `@effected/memfs` volume).
84
84
  *
85
85
  * **`PublishabilityDetector` is neither provided nor required here.** The
86
- * composite used to bake in npm semantics, which a naively-ordered override
87
- * silently lost to; now it supplies no default, and — because nothing inside
88
- * the composite asks a publishability question — it does not require one in
89
- * `R` either. The requirement surfaces in the `R` of each operation that
86
+ * composite supplies no default policy, and — because nothing inside it asks
87
+ * a publishability question — it does not require one in `R` either. The
88
+ * requirement surfaces in the `R` of each operation that
90
89
  * asks (`VersioningStrategy.detect`, e.g.), so a program that asks and never
91
90
  * wires a detector fails to compile at that operation, and a program that
92
91
  * never asks never supplies a publish policy. Wire one explicitly where
@@ -167,10 +166,9 @@ var Workspaces = class {
167
166
  * `pnpmfile.cjs` hooks**.
168
167
  *
169
168
  * @remarks
170
- * The composite that did not exist. `layerWithGit` hard-wires the no-op
171
- * catalogs layer, so a consumer wanting snapshots + git + hook replay had to
172
- * rebuild the entire service graph by hand — which is exactly what
173
- * `@savvy-web/silk-effects` did, and the copy this static deletes.
169
+ * `layerWithGit` wires the no-op catalogs layer, so this is the composite to
170
+ * reach for when you want snapshots, git and hook replay together without
171
+ * assembling the service graph by hand.
174
172
  *
175
173
  * Requirements are unchanged from {@link Workspaces.layerWithGit}: a
176
174
  * filesystem, a path service, and core's `ChildProcessSpawner` (behind
@@ -233,8 +231,7 @@ var Workspaces = class {
233
231
  * and {@link ConfigDependencyHooks.layerSubprocess}. It exists for the fourth
234
232
  * policy: {@link ConfigDependencyHooks.layerFrom}, the hermetic seam, which a
235
233
  * snapshot-backed test otherwise cannot reach without rebuilding the whole
236
- * git graph by hand — the copy `layerWithGitAndConfigDependencies` was added
237
- * to delete. The ONE hooks reference is handed to both `WorkspaceCatalogs`
234
+ * git graph by hand. The ONE hooks reference is handed to both `WorkspaceCatalogs`
238
235
  * and `WorkspaceSnapshots`, so `at(ref)` and `worktree()` cannot drift on
239
236
  * which policy they replay. A hooks layer carrying its own requirement (the
240
237
  * subprocess variant's `ChildProcessSpawner`) threads it through to the
@@ -264,13 +261,11 @@ var Workspaces = class {
264
261
  * contract: how to run a project-local binary here.
265
262
  *
266
263
  * @remarks
267
- * **An inverted contract, the `@effected/npm` `CatalogResolver`
268
- * precedent.** Tool discovery needs package-manager detection and
269
- * workspace-root resolution, both of which live here — but a direct edge
270
- * from `@effected/commands` to this package would make that boundary-tier
271
- * package integrated, and through the planned `npm` → `commands` edge
272
- * would drag `npm`, `lockfiles` (pure!) and `package-json` up a tier with
273
- * it. So `commands` declares the narrow contract and we ship the layer.
264
+ * **An inverted contract, like `@effected/npm`'s `CatalogResolver`.** Tool
265
+ * discovery needs package-manager detection and workspace-root resolution,
266
+ * both of which live here, but `@effected/commands` must not depend on this
267
+ * package. So `commands` declares the narrow `LocalExec` contract and this
268
+ * package ships the layer.
274
269
  *
275
270
  * **The argv knowledge is not duplicated.** `LocalExec.prefixes(name)` is
276
271
  * the one home of the four managers' `exec`/`dlx`/script-runner prefixes;
@@ -304,6 +299,7 @@ var Workspaces = class {
304
299
  *
305
300
  * @example
306
301
  * ```ts
302
+ * import { NodeServices } from "@effect/platform-node";
307
303
  * import { ToolDiscovery } from "@effected/commands";
308
304
  * import { Workspaces } from "@effected/workspaces";
309
305
  * import { Layer } from "effect";
package/WorkspacesSync.js CHANGED
@@ -113,9 +113,7 @@ const isPackage = (options, dir) => options.fileSystem.exists(options.path.join(
113
113
  * The signature is path-first, options second — the same shape as
114
114
  * {@link getWorkspacePackagesSync} and the rest of the kit's sync facades
115
115
  * (`TsconfigLoaderSync.load(configPath, options)`). `cwd` is required and
116
- * positional: the earlier options-bag form defaulted it to an ambient
117
- * `process.cwd()` read, which both broke the symmetry with its sibling and
118
- * was the module's one platform assumption.
116
+ * positional, so the function reads no ambient `process.cwd()`.
119
117
  *
120
118
  * Markers match the async service exactly: a `pnpm-workspace.yaml`, or a
121
119
  * `package.json` carrying a `workspaces` field.