@effected/workspaces 0.18.3 → 0.20.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/README.md CHANGED
@@ -193,7 +193,7 @@ const packages = root === null ? [] : getWorkspacePackagesSync(root, nodeSyncOps
193
193
  // packages: the discovered workspace packages, empty when there is no root
194
194
  ```
195
195
 
196
- Both entry points take their path positionally, so the bag usually passes through verbatim; spread it to add `getWorkspacePackagesSync`'s traversal extras — `{ ...nodeSyncOps, maxDepth }`. The bindings are a separate subpath deliberately: the main entry imports nothing platform-shaped, and re-exporting them from it would drag `node:*` into every consumer, including the ones supplying their own operations. `nodePath` is the running platform's `node:path`, so on Windows the paths handed back are win32 paths.
196
+ Both entry points take their path positionally, so the bag usually passes through verbatim; spread it to add `getWorkspacePackagesSync`'s traversal extras — `{ ...nodeSyncOps, maxDepth, onSkip }`. The function is total, so it cannot fail on a manifest it cannot use — but it never drops one silently either: `onSkip` receives a `WorkspaceDiscoverySkip` (`root`, `path`, `kind`, `cause`) for every manifest left out, with the same `kind` the Effect surface would have failed with, so "no packages" and "packages rejected" stay distinguishable. A manifest with no `version` is not skipped on either surface; it is a member with `version` absent, as pnpm treats it. The bindings are a separate subpath deliberately: the main entry imports nothing platform-shaped, and re-exporting them from it would drag `node:*` into every consumer, including the ones supplying their own operations. `nodePath` is the running platform's `node:path`, so on Windows the paths handed back are win32 paths.
197
197
 
198
198
  Write the operations yourself when Node's built-ins are not the platform you mean — a Bun or Deno binding, a test fake, or `node:path/win32` to pin a dialect rather than follow the running platform. Each one is a one-liner:
199
199
 
@@ -293,7 +293,7 @@ A name miss in the derived `getPackage` fails with the service's own typed `Pack
293
293
  - `Workspaces.resolverLayer` / `Workspaces.resolveManifest` — the one-call manifest-resolution path: a fresh, unmemoized layer per call so root discovery follows your cwd, and one-shot resolution of a whole `Manifest` against the real workspace.
294
294
  - `WorkspaceRoot` — root discovery from a `cwd`, over `WORKSPACE_MARKERS`.
295
295
  - `WorkspaceDiscovery` — package enumeration with a bounded descent for segment-crossing `packages/**` patterns, per-package lookup and the `makeTest` / `layerTest` in-memory test doubles.
296
- - `WorkspacePackage` — a deliberately tolerant manifest model, so one member with an odd version cannot fail discovery for the whole repo. `manifestRecord` keeps the as-read `package.json` for tolerant access to fields outside the typed slice without a second read; `WorkspacePackage.manifest(pkg)` re-reads and is the opt-in bridge to `@effected/package-json`'s strict `Package`.
296
+ - `WorkspacePackage` — a deliberately tolerant manifest model, so one member with an odd version cannot fail discovery for the whole repo. `version` is optional and carried exactly as the manifest has it — absent for the ordinary version-less private root or member, never a `"0.0.0"` placeholder; a `version` that is present but not a string is `invalidShape`. `manifestRecord` keeps the as-read `package.json` for tolerant access to fields outside the typed slice without a second read; `WorkspacePackage.manifest(pkg)` re-reads and is the opt-in bridge to `@effected/package-json`'s strict `Package`.
297
297
  - `DependencyGraph` — a value class over discovered packages: `levels()` for parallel build tiers, the flattened topological order, `toMermaid()` for a deterministic Mermaid `flowchart TD` of the whole graph, and `CyclicDependencyError` — naming the cycle's actual members — when there is no order.
298
298
  - `PackageManagerDetector` — npm, pnpm, yarn or bun from lockfiles and the `packageManager` field.
299
299
  - `WorkspaceCatalogs` — pnpm catalog assembly and `catalog:` resolution, on pnpm's own catalog packages; `releaseAgeGate()` assembles the effective `@effected/npm` `ReleaseAgeGate` from inline `pnpm-workspace.yaml` release-age keys and replayed hook contributions, strictest-wins, in the same pass as the catalogs.
@@ -9,12 +9,20 @@ import { DependencyResolutionError, WorkspaceResolver } from "@effected/npm";
9
9
  //#region src/WorkspaceDiscovery.ts
10
10
  /**
11
11
  * Raised when a workspace member's `package.json` cannot be read, parsed, or
12
- * used — it is missing, malformed, or lacks a `name` or `version`.
12
+ * used — it is missing, malformed, or lacks a `name`.
13
13
  *
14
14
  * @remarks
15
15
  * `kind` is the discriminant a caller branches on; `cause` preserves the
16
16
  * originating failure rather than flattening it into a sentence.
17
17
  *
18
+ * A manifest with no `version` is NOT a failure: pnpm accepts a version-less
19
+ * private package and a private monorepo root without one is the ordinary
20
+ * shape, so the member is discovered with `WorkspacePackage.version` absent.
21
+ * The former `missingVersion` kind is retired. Only ABSENCE is tolerated: a
22
+ * `version` that is present but not a string — or present and `""`, which pnpm
23
+ * never wrote and which would resolve `workspace:^` to a bare `"^"` — is the
24
+ * manifest's shape being wrong and reports `invalidShape`.
25
+ *
18
26
  * @public
19
27
  */
20
28
  var WorkspaceDiscoveryError = class extends Schema.TaggedError()("WorkspaceDiscoveryError", {
@@ -28,8 +36,7 @@ var WorkspaceDiscoveryError = class extends Schema.TaggedError()("WorkspaceDisco
28
36
  "invalidJson",
29
37
  "invalidShape",
30
38
  "invalidYaml",
31
- "missingName",
32
- "missingVersion"
39
+ "missingName"
33
40
  ]),
34
41
  /** The originating failure, if there was one. */
35
42
  cause: Schema.Defect()
@@ -172,15 +179,21 @@ var WorkspaceDiscovery = class WorkspaceDiscovery extends Context.Service()("@ef
172
179
  cause: void 0
173
180
  }));
174
181
  const version = raw.version;
175
- if (typeof version !== "string" || version.length === 0) return yield* Effect.fail(new WorkspaceDiscoveryError({
182
+ if (version !== void 0 && typeof version !== "string") return yield* Effect.fail(new WorkspaceDiscoveryError({
176
183
  root,
177
184
  path: packageJsonPath,
178
- kind: "missingVersion",
179
- cause: void 0
185
+ kind: "invalidShape",
186
+ cause: /* @__PURE__ */ new Error(`version must be a string, got ${typeof version}`)
187
+ }));
188
+ if (version === "") return yield* Effect.fail(new WorkspaceDiscoveryError({
189
+ root,
190
+ path: packageJsonPath,
191
+ kind: "invalidShape",
192
+ cause: /* @__PURE__ */ new Error("version must be a non-empty string")
180
193
  }));
181
194
  return yield* Schema.decodeUnknownEffect(WorkspacePackage)({
182
195
  name,
183
- version,
196
+ ...version !== void 0 ? { version } : {},
184
197
  path: directory,
185
198
  packageJsonPath,
186
199
  relativePath,
@@ -494,6 +507,12 @@ var WorkspaceDiscovery = class WorkspaceDiscovery extends Context.Service()("@ef
494
507
  * channel is reserved for a failure of the resolution *mechanism* (an
495
508
  * unfindable root, an unreadable manifest), never an ordinary miss.
496
509
  *
510
+ * A member that declares **no `version`** is neither: it is a known member
511
+ * with nothing for `workspace:` to resolve to. Answering `none` would read
512
+ * as "not a member" downstream, so it fails typed instead, naming the
513
+ * specifier — the same channel a consumer already handles for an
514
+ * unresolvable workspace.
515
+ *
497
516
  * @example
498
517
  * ```ts
499
518
  * import { Package } from "@effected/package-json";
@@ -517,10 +536,21 @@ var WorkspaceDiscovery = class WorkspaceDiscovery extends Context.Service()("@ef
517
536
  versionIndexes.set(all, index);
518
537
  return index;
519
538
  };
520
- return { versionOf: (packageName) => discovery.listPackages().pipe(Effect.map((all) => Option.fromUndefinedOr(versionsByName(all).get(packageName))), Effect.mapError((cause) => new DependencyResolutionError({
521
- specifier: `workspace:${packageName}`,
522
- cause
523
- }))) };
539
+ return { versionOf: (packageName) => {
540
+ const specifier = `workspace:${packageName}`;
541
+ return discovery.listPackages().pipe(Effect.mapError((cause) => new DependencyResolutionError({
542
+ specifier,
543
+ cause
544
+ })), Effect.flatMap((all) => {
545
+ const index = versionsByName(all);
546
+ if (!index.has(packageName)) return Effect.succeed(Option.none());
547
+ const version = index.get(packageName);
548
+ return version === void 0 ? Effect.fail(new DependencyResolutionError({
549
+ specifier,
550
+ cause: /* @__PURE__ */ new Error(`Workspace member "${packageName}" declares no version`)
551
+ })) : Effect.succeed(Option.some(version));
552
+ }));
553
+ } };
524
554
  }));
525
555
  };
526
556
  const isStringRecord = (value) => value !== null && typeof value === "object" && !Array.isArray(value) && Object.values(value).every((entry) => typeof entry === "string");
@@ -91,8 +91,24 @@ var WorkspaceManifestError = class extends Schema.TaggedError()("WorkspaceManife
91
91
  var WorkspacePackage = class WorkspacePackage extends Schema.Class("WorkspacePackage")({
92
92
  /** The package name. */
93
93
  name: Schema.NonEmptyString,
94
- /** The raw `version` string — deliberately not semver-validated. */
95
- version: Schema.String,
94
+ /**
95
+ * The raw `version` string — deliberately not semver-validated — or absent
96
+ * when the manifest declares none.
97
+ *
98
+ * @remarks
99
+ * pnpm accepts a version-less private package, and a private monorepo root
100
+ * without a `version` is the ordinary shape, so discovery carries the field
101
+ * exactly as the manifest has it: a non-empty string when present, verbatim
102
+ * and un-validated, absent when the key is absent — never a `"0.0.0"`
103
+ * placeholder and never a present `undefined` key. **Only ABSENCE is
104
+ * tolerated**: a `version` that is present but not a string, or present and
105
+ * `""`, fails discovery as `invalidShape` on both surfaces. `""` in
106
+ * particular is not a pnpm shape and would resolve `workspace:^` to a bare
107
+ * `"^"`.
108
+ * Anything that needs a concrete version (`WorkspaceResolver.versionOf`,
109
+ * a release tag) answers the absence itself rather than inventing one.
110
+ */
111
+ version: Schema.optionalKey(Schema.String),
96
112
  /** Absolute path to the package directory. */
97
113
  path: Schema.NonEmptyString,
98
114
  /** Absolute path to the package's `package.json`. */
@@ -197,7 +197,7 @@ var WorkspaceSnapshots = class WorkspaceSnapshots extends Context.Service()("@ef
197
197
  const importerVersions = yield* catalogsService.importerVersions();
198
198
  const snapshotPackages = packages.map((pkg) => PackageStateSnapshot.make({
199
199
  name: pkg.name,
200
- version: pkg.version,
200
+ version: pkg.version ?? "",
201
201
  relativePath: pkg.relativePath,
202
202
  dependencies: pkg.dependencies,
203
203
  devDependencies: pkg.devDependencies,
@@ -1,7 +1,7 @@
1
1
  import { unanimousVersionOf } from "./internal/importerVersions.js";
2
2
  import { CatalogSet } from "./WorkspaceCatalogs.js";
3
3
  import { Effect, Exit, Layer, Option, Schema } from "effect";
4
- import { CatalogResolver, DependencySpecifier, WorkspaceResolver } from "@effected/npm";
4
+ import { CatalogResolver, DependencyResolutionError, DependencySpecifier, WorkspaceResolver } from "@effected/npm";
5
5
 
6
6
  //#region src/WorkspaceStateSnapshot.ts
7
7
  const EMPTY = Object.freeze(Object.create(null));
@@ -23,7 +23,23 @@ const DependencyMap = Schema.Record(Schema.String, Schema.String).pipe(Schema.wi
23
23
  var PackageStateSnapshot = class extends Schema.Class("PackageStateSnapshot")({
24
24
  /** The package name. */
25
25
  name: Schema.NonEmptyString,
26
- /** The raw `version` string, as recorded at the captured moment. */
26
+ /**
27
+ * The raw `version` string, as recorded at the captured moment — or `""` for
28
+ * a manifest that declared none.
29
+ *
30
+ * @remarks
31
+ * `WorkspacePackage.version` is optional and a version-less member is an
32
+ * ordinary pnpm shape, but this field stays a plain required string because a
33
+ * snapshot is a serialized value someone stores and diffs: an absent key and
34
+ * a present one would read as a change the moment one side of a diff was
35
+ * captured by a different code path. **`""` is therefore the sentinel for
36
+ * "the manifest declared no version", not a version anyone can use** — both
37
+ * `snapshotOf` at a ref and the worktree snapshot write it, so the two sides
38
+ * agree. Every resolution surface treats it as absence:
39
+ * {@link WorkspaceStateSnapshot.resolve} answers `Option.none()` for a
40
+ * `workspace:` specifier against it, because `some("")` would rewrite
41
+ * `workspace:^` as a bare `"^"`.
42
+ */
27
43
  version: Schema.String,
28
44
  /** POSIX path relative to the workspace root; `"."` for the root package. */
29
45
  relativePath: Schema.String,
@@ -137,7 +153,17 @@ var WorkspaceStateSnapshot = class WorkspaceStateSnapshot extends Schema.Class("
137
153
  if (this.#packageIndex === void 0) this.#packageIndex = new Map(this.packages.map((pkg) => [pkg.name, pkg]));
138
154
  return this.#packageIndex;
139
155
  }
140
- /** Every captured package's name → version. Total; O(1) after the first call. */
156
+ /**
157
+ * Every captured package's name → version. Total; O(1) after the first call.
158
+ *
159
+ * @remarks
160
+ * Values are `PackageStateSnapshot.version` verbatim, so a member whose
161
+ * manifest declared no version maps to the **`""` sentinel** rather than
162
+ * being absent from the map — presence here answers membership, not
163
+ * "has a usable version". A caller reading a version out of this map owes
164
+ * the `""` check itself; {@link WorkspaceStateSnapshot.resolve} already
165
+ * makes it.
166
+ */
141
167
  get versions() {
142
168
  return this.#versions();
143
169
  }
@@ -222,7 +248,10 @@ var WorkspaceStateSnapshot = class WorkspaceStateSnapshot extends Schema.Class("
222
248
  const fromCatalogs = this.#catalogRange(dependency, classified.name);
223
249
  return Option.isSome(fromCatalogs) ? fromCatalogs : onUnresolvedCatalog();
224
250
  }
225
- case "workspace": return Option.fromUndefinedOr(this.#versions().get(dependency));
251
+ case "workspace": {
252
+ const version = this.#versions().get(dependency);
253
+ return version === void 0 || version === "" ? Option.none() : Option.some(version);
254
+ }
226
255
  default: return Option.none();
227
256
  }
228
257
  }
@@ -368,7 +397,15 @@ var WorkspaceStateSnapshot = class WorkspaceStateSnapshot extends Schema.Class("
368
397
  * `workspace:` specifiers as of this ref. Built once per instance and cached.
369
398
  */
370
399
  get workspaceResolver() {
371
- if (this.#workspaceResolver === void 0) this.#workspaceResolver = Layer.succeed(WorkspaceResolver, { versionOf: (packageName) => Effect.succeed(Option.fromUndefinedOr(this.#versions().get(packageName))) });
400
+ if (this.#workspaceResolver === void 0) this.#workspaceResolver = Layer.succeed(WorkspaceResolver, { versionOf: (packageName) => {
401
+ const version = this.#versions().get(packageName);
402
+ if (version === void 0) return Effect.succeed(Option.none());
403
+ if (version === "") return Effect.fail(new DependencyResolutionError({
404
+ specifier: `workspace:${packageName}`,
405
+ cause: /* @__PURE__ */ new Error(`Workspace member "${packageName}" declares no version`)
406
+ }));
407
+ return Effect.succeed(Option.some(version));
408
+ } });
372
409
  return this.#workspaceResolver;
373
410
  }
374
411
  /** Both snapshot-scoped resolver layers merged. Built once per instance and cached. */
package/WorkspacesSync.js CHANGED
@@ -7,6 +7,38 @@ import { Yaml } from "@effected/yaml";
7
7
 
8
8
  //#region src/WorkspacesSync.ts
9
9
  /**
10
+ * Read one `package.json` into a plain record, or say why not. Never throws.
11
+ *
12
+ * The three failures are kept apart — a throwing consumer `readFile`, a JSON
13
+ * syntax error, and valid JSON that is not an object — because the skip report
14
+ * carries the distinction; the two other callers of {@link readJson} do not
15
+ * need it and keep the collapsed form.
16
+ */
17
+ const readManifest = (fileSystem, file) => {
18
+ let text;
19
+ try {
20
+ text = fileSystem.readFile(file);
21
+ } catch (cause) {
22
+ return {
23
+ kind: "read",
24
+ cause
25
+ };
26
+ }
27
+ let parsed;
28
+ try {
29
+ parsed = JSON.parse(text);
30
+ } catch (cause) {
31
+ return {
32
+ kind: "invalidJson",
33
+ cause
34
+ };
35
+ }
36
+ return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? { raw: parsed } : {
37
+ kind: "invalidShape",
38
+ cause: /* @__PURE__ */ new Error("package.json is not a JSON object")
39
+ };
40
+ };
41
+ /**
10
42
  * Read and JSON-parse a file into a plain object, or `undefined`. Never throws.
11
43
  *
12
44
  * The non-object check is load-bearing, not defensive noise. `JSON.parse`
@@ -143,21 +175,47 @@ const readPatternsSync = (options, root) => {
143
175
  }
144
176
  return manifestPatternsOf(readJson(fileSystem, path.join(root, "package.json")) ?? {});
145
177
  };
146
- /** Build a `WorkspacePackage` from a directory, or `null` if its manifest is unusable. */
178
+ /**
179
+ * Build a `WorkspacePackage` from a directory, or a {@link WorkspaceDiscoverySkip}
180
+ * saying why its manifest is unusable — never `null`, so a skip cannot go
181
+ * unreported.
182
+ */
147
183
  const readPackageSync = (options, root, directory, relativePath) => {
148
184
  const packageJsonPath = options.path.join(directory, "package.json");
149
- const raw = readJson(options.fileSystem, packageJsonPath);
150
- if (raw === void 0) return null;
185
+ const read = readManifest(options.fileSystem, packageJsonPath);
186
+ if (!("raw" in read)) return {
187
+ root,
188
+ path: packageJsonPath,
189
+ kind: read.kind,
190
+ cause: read.cause
191
+ };
192
+ const raw = read.raw;
151
193
  const name = raw.name;
194
+ if (typeof name !== "string" || name.length === 0) return {
195
+ root,
196
+ path: packageJsonPath,
197
+ kind: "missingName",
198
+ cause: void 0
199
+ };
152
200
  const version = raw.version;
153
- if (typeof name !== "string" || name.length === 0) return null;
154
- if (typeof version !== "string" || version.length === 0) return null;
201
+ if (version !== void 0 && typeof version !== "string") return {
202
+ root,
203
+ path: packageJsonPath,
204
+ kind: "invalidShape",
205
+ cause: /* @__PURE__ */ new Error(`version must be a string, got ${typeof version}`)
206
+ };
207
+ if (version === "") return {
208
+ root,
209
+ path: packageJsonPath,
210
+ kind: "invalidShape",
211
+ cause: /* @__PURE__ */ new Error("version must be a non-empty string")
212
+ };
155
213
  const stringRecord = (value) => value !== null && typeof value === "object" && !Array.isArray(value) && Object.values(value).every((entry) => typeof entry === "string") ? value : void 0;
156
214
  const publishConfig = raw.publishConfig;
157
215
  const config = publishConfig !== null && typeof publishConfig === "object" ? Effect.runSyncExit(Schema.decodeUnknownEffect(PublishConfig)(publishConfig)) : void 0;
158
216
  return WorkspacePackage.make({
159
217
  name,
160
- version,
218
+ ...version !== void 0 ? { version } : {},
161
219
  path: directory,
162
220
  packageJsonPath,
163
221
  relativePath,
@@ -194,6 +252,13 @@ const readPackageSync = (options, root, directory, relativePath) => {
194
252
  * caller mistakes: a `maxDepth` that is not a positive integer throws, matching
195
253
  * the enumerator's defect.
196
254
  *
255
+ * **A skip is never silent.** Every manifest left out is reported through
256
+ * `onSkip` with its path and the same `kind` the Effect surface would have
257
+ * failed with (a `WorkspaceDiscoverySkip`), so "no packages" and "packages
258
+ * rejected" stay distinguishable. A manifest with no `version` is NOT skipped:
259
+ * pnpm accepts a version-less private package, so it is a member with
260
+ * `version` absent — on both surfaces.
261
+ *
197
262
  * @param root - The workspace root, from {@link findWorkspaceRootSync}.
198
263
  * @param options - The consumer-supplied operations and traversal bounds; see
199
264
  * {@link GetWorkspacePackagesSyncOptions}.
@@ -261,13 +326,18 @@ const getWorkspacePackagesSync = (root, options) => {
261
326
  }
262
327
  }
263
328
  const members = [];
329
+ const admit = (read) => {
330
+ if (read instanceof WorkspacePackage) members.push(read);
331
+ else options.onSkip?.(read);
332
+ };
264
333
  for (const [relativePath, absolute] of [...included.entries()].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)) {
265
334
  if (relativePath === "." || absolute === root) continue;
266
- const pkg = readPackageSync(options, root, absolute, relativePath);
267
- if (pkg !== null) members.push(pkg);
335
+ admit(readPackageSync(options, root, absolute, relativePath));
268
336
  }
269
337
  const rootPackage = readPackageSync(options, root, root, ".");
270
- return rootPackage === null ? members : [rootPackage, ...members];
338
+ if (rootPackage instanceof WorkspacePackage) return [rootPackage, ...members];
339
+ options.onSkip?.(rootPackage);
340
+ return members;
271
341
  };
272
342
 
273
343
  //#endregion
package/index.d.ts CHANGED
@@ -36,7 +36,7 @@ declare const PublishConfig_base: Schema.Class<PublishConfig, Schema.Struct<{
36
36
  *
37
37
  * @public
38
38
  */
39
- declare class PublishConfig extends PublishConfig_base {}
39
+ export declare class PublishConfig extends PublishConfig_base {}
40
40
  /**
41
41
  * The result of comparing two {@link WorkspacePackage} dependency snapshots.
42
42
  *
@@ -76,15 +76,31 @@ declare const WorkspaceManifestError_base: Schema.Class<WorkspaceManifestError,
76
76
  *
77
77
  * @public
78
78
  */
79
- declare class WorkspaceManifestError extends WorkspaceManifestError_base {
79
+ export declare class WorkspaceManifestError extends WorkspaceManifestError_base {
80
80
  /** Renders the path and failure kind into a one-line message. */
81
81
  get message(): string;
82
82
  }
83
83
  declare const WorkspacePackage_base: Schema.Class<WorkspacePackage, Schema.Struct<{
84
84
  /** The package name. */
85
85
  readonly name: Schema.NonEmptyString;
86
- /** The raw `version` string — deliberately not semver-validated. */
87
- readonly version: Schema.String;
86
+ /**
87
+ * The raw `version` string — deliberately not semver-validated — or absent
88
+ * when the manifest declares none.
89
+ *
90
+ * @remarks
91
+ * pnpm accepts a version-less private package, and a private monorepo root
92
+ * without a `version` is the ordinary shape, so discovery carries the field
93
+ * exactly as the manifest has it: a non-empty string when present, verbatim
94
+ * and un-validated, absent when the key is absent — never a `"0.0.0"`
95
+ * placeholder and never a present `undefined` key. **Only ABSENCE is
96
+ * tolerated**: a `version` that is present but not a string, or present and
97
+ * `""`, fails discovery as `invalidShape` on both surfaces. `""` in
98
+ * particular is not a pnpm shape and would resolve `workspace:^` to a bare
99
+ * `"^"`.
100
+ * Anything that needs a concrete version (`WorkspaceResolver.versionOf`,
101
+ * a release tag) answers the absence itself rather than inventing one.
102
+ */
103
+ readonly version: Schema.optionalKey<Schema.String>;
88
104
  /** Absolute path to the package directory. */
89
105
  readonly path: Schema.NonEmptyString;
90
106
  /** Absolute path to the package's `package.json`. */
@@ -161,7 +177,7 @@ declare const WorkspacePackage_base: Schema.Class<WorkspacePackage, Schema.Struc
161
177
  *
162
178
  * @public
163
179
  */
164
- declare class WorkspacePackage extends WorkspacePackage_base {
180
+ export declare class WorkspacePackage extends WorkspacePackage_base {
165
181
  /** Whether this is the workspace root package. */
166
182
  get isRootWorkspace(): boolean;
167
183
  /** Whether the package is publishable in principle (not marked private). */
@@ -229,7 +245,7 @@ declare class WorkspacePackage extends WorkspacePackage_base {
229
245
  *
230
246
  * @public
231
247
  */
232
- declare const WORKSPACE_MARKERS: ReadonlyArray<string>;
248
+ export declare const WORKSPACE_MARKERS: ReadonlyArray<string>;
233
249
  /**
234
250
  * Options for {@link WorkspaceRoot}'s `find`.
235
251
  *
@@ -287,7 +303,7 @@ declare const WorkspaceRootNotFoundError_base: Schema.Class<WorkspaceRootNotFoun
287
303
  *
288
304
  * @public
289
305
  */
290
- declare class WorkspaceRootNotFoundError extends WorkspaceRootNotFoundError_base {
306
+ export declare class WorkspaceRootNotFoundError extends WorkspaceRootNotFoundError_base {
291
307
  /** Renders the search path, probed markers and any ceiling into a one-line message. */
292
308
  get message(): string;
293
309
  }
@@ -344,7 +360,7 @@ declare const WorkspaceRoot_base: Context.ServiceClass<WorkspaceRoot, "@effected
344
360
  *
345
361
  * @public
346
362
  */
347
- declare class WorkspaceRoot extends WorkspaceRoot_base {
363
+ export declare class WorkspaceRoot extends WorkspaceRoot_base {
348
364
  /** Builds the service over core `FileSystem` and `Path`. */
349
365
  static readonly make: Effect.Effect<WorkspaceRootShape, never, FileSystem.FileSystem | Path.Path>;
350
366
  /** The live layer. */
@@ -417,21 +433,29 @@ declare const WorkspaceDiscoveryError_base: Schema.Class<WorkspaceDiscoveryError
417
433
  /** The file that failed. */
418
434
  readonly path: Schema.String;
419
435
  /** What went wrong with it. */
420
- readonly kind: Schema.Literals<readonly ["read", "invalidJson", "invalidShape", "invalidYaml", "missingName", "missingVersion"]>;
436
+ readonly kind: Schema.Literals<readonly ["read", "invalidJson", "invalidShape", "invalidYaml", "missingName"]>;
421
437
  /** The originating failure, if there was one. */
422
438
  readonly cause: Schema.Defect;
423
439
  }>, import("effect/Cause").YieldableError>;
424
440
  /**
425
441
  * Raised when a workspace member's `package.json` cannot be read, parsed, or
426
- * used — it is missing, malformed, or lacks a `name` or `version`.
442
+ * used — it is missing, malformed, or lacks a `name`.
427
443
  *
428
444
  * @remarks
429
445
  * `kind` is the discriminant a caller branches on; `cause` preserves the
430
446
  * originating failure rather than flattening it into a sentence.
431
447
  *
448
+ * A manifest with no `version` is NOT a failure: pnpm accepts a version-less
449
+ * private package and a private monorepo root without one is the ordinary
450
+ * shape, so the member is discovered with `WorkspacePackage.version` absent.
451
+ * The former `missingVersion` kind is retired. Only ABSENCE is tolerated: a
452
+ * `version` that is present but not a string — or present and `""`, which pnpm
453
+ * never wrote and which would resolve `workspace:^` to a bare `"^"` — is the
454
+ * manifest's shape being wrong and reports `invalidShape`.
455
+ *
432
456
  * @public
433
457
  */
434
- declare class WorkspaceDiscoveryError extends WorkspaceDiscoveryError_base {
458
+ export declare class WorkspaceDiscoveryError extends WorkspaceDiscoveryError_base {
435
459
  /** Renders the failing file and kind into a one-line message. */
436
460
  get message(): string;
437
461
  }
@@ -452,7 +476,7 @@ declare const WorkspacePatternError_base: Schema.Class<WorkspacePatternError, Sc
452
476
  *
453
477
  * @public
454
478
  */
455
- declare class WorkspacePatternError extends WorkspacePatternError_base {
479
+ export declare class WorkspacePatternError extends WorkspacePatternError_base {
456
480
  /** Renders the pattern and failure kind into a one-line message. */
457
481
  get message(): string;
458
482
  }
@@ -471,7 +495,7 @@ declare const PackageNotFoundError_base: Schema.Class<PackageNotFoundError, Sche
471
495
  *
472
496
  * @public
473
497
  */
474
- declare class PackageNotFoundError extends PackageNotFoundError_base {
498
+ export declare class PackageNotFoundError extends PackageNotFoundError_base {
475
499
  /** Renders the requested name into a one-line message. */
476
500
  get message(): string;
477
501
  }
@@ -487,7 +511,7 @@ declare const WorkspaceInfo_base: Schema.Class<WorkspaceInfo, Schema.Struct<{
487
511
  *
488
512
  * @public
489
513
  */
490
- declare class WorkspaceInfo extends WorkspaceInfo_base {}
514
+ export declare class WorkspaceInfo extends WorkspaceInfo_base {}
491
515
  /**
492
516
  * Every failure `WorkspaceDiscovery.getPackage` can surface: the discovery
493
517
  * failures plus a name that matches no member.
@@ -627,7 +651,7 @@ declare const WorkspaceDiscovery_base: Context.ServiceClass<WorkspaceDiscovery,
627
651
  *
628
652
  * @public
629
653
  */
630
- declare class WorkspaceDiscovery extends WorkspaceDiscovery_base {
654
+ export declare class WorkspaceDiscovery extends WorkspaceDiscovery_base {
631
655
  /**
632
656
  * Builds the service. Root resolution is one explicit concern: `cwd` is an
633
657
  * option here, never an ambient `process.cwd()` read inside a method.
@@ -722,6 +746,12 @@ declare class WorkspaceDiscovery extends WorkspaceDiscovery_base {
722
746
  * channel is reserved for a failure of the resolution *mechanism* (an
723
747
  * unfindable root, an unreadable manifest), never an ordinary miss.
724
748
  *
749
+ * A member that declares **no `version`** is neither: it is a known member
750
+ * with nothing for `workspace:` to resolve to. Answering `none` would read
751
+ * as "not a member" downstream, so it fails typed instead, naming the
752
+ * specifier — the same channel a consumer already handles for an
753
+ * unresolvable workspace.
754
+ *
725
755
  * @example
726
756
  * ```ts
727
757
  * import { Package } from "@effected/package-json";
@@ -772,7 +802,7 @@ declare const ChangeDetectionOptions_base: Schema.Class<ChangeDetectionOptions,
772
802
  *
773
803
  * @public
774
804
  */
775
- declare class ChangeDetectionOptions extends ChangeDetectionOptions_base {}
805
+ export declare class ChangeDetectionOptions extends ChangeDetectionOptions_base {}
776
806
  declare const ChangeDetectionError_base: Schema.Class<ChangeDetectionError, Schema.TaggedStruct<"ChangeDetectionError", {
777
807
  /** The operation that could not run. */
778
808
  readonly operation: Schema.String;
@@ -792,7 +822,7 @@ declare const ChangeDetectionError_base: Schema.Class<ChangeDetectionError, Sche
792
822
  *
793
823
  * @public
794
824
  */
795
- declare class ChangeDetectionError extends ChangeDetectionError_base {
825
+ export declare class ChangeDetectionError extends ChangeDetectionError_base {
796
826
  /** Renders the failed operation into a one-line message. */
797
827
  get message(): string;
798
828
  }
@@ -846,7 +876,7 @@ declare const ChangeDetector_base: Context.ServiceClass<ChangeDetector, "@effect
846
876
  *
847
877
  * @public
848
878
  */
849
- declare class ChangeDetector extends ChangeDetector_base {
879
+ export declare class ChangeDetector extends ChangeDetector_base {
850
880
  /** Builds the service over `Git` and {@link WorkspaceDiscovery}. */
851
881
  static readonly make: Effect.Effect<ChangeDetectorShape, never, Git | WorkspaceDiscovery>;
852
882
  /** The live layer. */
@@ -899,7 +929,7 @@ interface PeerDependencyRules {
899
929
  *
900
930
  * @public
901
931
  */
902
- declare const NoPeerDependencyRules: PeerDependencyRules;
932
+ export declare const NoPeerDependencyRules: PeerDependencyRules;
903
933
  /**
904
934
  * The result of replaying a workspace's `configDependencies` hooks: the catalogs
905
935
  * the hooks yield, and the release-age gate contribution they leave on the
@@ -997,7 +1027,7 @@ declare const ConfigDependencyHooks_base: Context.ServiceClass<ConfigDependencyH
997
1027
  *
998
1028
  * @public
999
1029
  */
1000
- declare class ConfigDependencyHooks extends ConfigDependencyHooks_base {
1030
+ export declare class ConfigDependencyHooks extends ConfigDependencyHooks_base {
1001
1031
  /**
1002
1032
  * The no-op layer: `inject` returns the seed unchanged and never touches a
1003
1033
  * config dependency. The default {@link WorkspaceCatalogs} layer wires this, so
@@ -1092,7 +1122,7 @@ declare const CyclicDependencyError_base: Schema.Class<CyclicDependencyError, Sc
1092
1122
  *
1093
1123
  * @public
1094
1124
  */
1095
- declare class CyclicDependencyError extends CyclicDependencyError_base {
1125
+ export declare class CyclicDependencyError extends CyclicDependencyError_base {
1096
1126
  /** Renders the cycle members into a one-line message. */
1097
1127
  get message(): string;
1098
1128
  }
@@ -1127,7 +1157,7 @@ declare const DependencyGraph_base: Schema.Class<DependencyGraph, Schema.Struct<
1127
1157
  *
1128
1158
  * @public
1129
1159
  */
1130
- declare class DependencyGraph extends DependencyGraph_base {
1160
+ export declare class DependencyGraph extends DependencyGraph_base {
1131
1161
  #private;
1132
1162
  /** Every workspace package name, sorted. Total. */
1133
1163
  get names(): ReadonlyArray<string>;
@@ -1187,13 +1217,13 @@ declare class DependencyGraph extends DependencyGraph_base {
1187
1217
  *
1188
1218
  * @public
1189
1219
  */
1190
- declare const PackageManagerName: Schema.Literals<readonly ["npm", "pnpm", "yarn", "bun"]>;
1220
+ export declare const PackageManagerName: Schema.Literals<readonly ["npm", "pnpm", "yarn", "bun"]>;
1191
1221
  /**
1192
1222
  * The decoded type of {@link (PackageManagerName:variable)}: `"npm" | "pnpm" | "yarn" | "bun"`.
1193
1223
  *
1194
1224
  * @public
1195
1225
  */
1196
- type PackageManagerName = typeof PackageManagerName.Type;
1226
+ export type PackageManagerName = typeof PackageManagerName.Type;
1197
1227
  /**
1198
1228
  * The markers {@link PackageManagerDetector} probes, in the priority order it
1199
1229
  * probes them.
@@ -1212,14 +1242,14 @@ type PackageManagerName = typeof PackageManagerName.Type;
1212
1242
  *
1213
1243
  * @public
1214
1244
  */
1215
- declare const PackageManagerEvidence: Schema.Literals<readonly ["pnpm-workspace.yaml", "bun.lock", "bun.lockb", "yarn.lock", "package.json#workspaces", "pnpm-lock.yaml", "package-lock.json", "package.json#devEngines.packageManager", "package.json#packageManager"]>;
1245
+ export declare const PackageManagerEvidence: Schema.Literals<readonly ["pnpm-workspace.yaml", "bun.lock", "bun.lockb", "yarn.lock", "package.json#workspaces", "pnpm-lock.yaml", "package-lock.json", "package.json#devEngines.packageManager", "package.json#packageManager"]>;
1216
1246
  /**
1217
1247
  * The decoded type of {@link (PackageManagerEvidence:variable)}: the marker
1218
1248
  * that decided a detection.
1219
1249
  *
1220
1250
  * @public
1221
1251
  */
1222
- type PackageManagerEvidence = typeof PackageManagerEvidence.Type;
1252
+ export type PackageManagerEvidence = typeof PackageManagerEvidence.Type;
1223
1253
  declare const DetectedPackageManager_base: Schema.Class<DetectedPackageManager, Schema.Struct<{
1224
1254
  /** The detected manager. */
1225
1255
  readonly name: Schema.Literals<readonly ["npm", "pnpm", "yarn", "bun"]>;
@@ -1252,7 +1282,7 @@ declare const DetectedPackageManager_base: Schema.Class<DetectedPackageManager,
1252
1282
  *
1253
1283
  * @public
1254
1284
  */
1255
- declare class DetectedPackageManager extends DetectedPackageManager_base {}
1285
+ export declare class DetectedPackageManager extends DetectedPackageManager_base {}
1256
1286
  declare const PackageManagerDetectionError_base: Schema.Class<PackageManagerDetectionError, Schema.TaggedStruct<"PackageManagerDetectionError", {
1257
1287
  /** The workspace root that was probed. */
1258
1288
  readonly root: Schema.String;
@@ -1265,7 +1295,7 @@ declare const PackageManagerDetectionError_base: Schema.Class<PackageManagerDete
1265
1295
  *
1266
1296
  * @public
1267
1297
  */
1268
- declare class PackageManagerDetectionError extends PackageManagerDetectionError_base {
1298
+ export declare class PackageManagerDetectionError extends PackageManagerDetectionError_base {
1269
1299
  /** Renders the root and probed markers into a one-line message. */
1270
1300
  get message(): string;
1271
1301
  }
@@ -1333,7 +1363,7 @@ declare const PackageManagerDetector_base: Context.ServiceClass<PackageManagerDe
1333
1363
  *
1334
1364
  * @public
1335
1365
  */
1336
- declare class PackageManagerDetector extends PackageManagerDetector_base {
1366
+ export declare class PackageManagerDetector extends PackageManagerDetector_base {
1337
1367
  /** Builds the service over core `FileSystem` and `Path`. */
1338
1368
  static readonly make: Effect.Effect<{
1339
1369
  readonly detect: (root: string) => Effect.Effect<DetectedPackageManager, PackageManagerDetectionFailure>;
@@ -1414,7 +1444,7 @@ declare const LockfileReadError_base: Schema.Class<LockfileReadError, Schema.Tag
1414
1444
  *
1415
1445
  * @public
1416
1446
  */
1417
- declare class LockfileReadError extends LockfileReadError_base {
1447
+ export declare class LockfileReadError extends LockfileReadError_base {
1418
1448
  /** Renders the unreadable path into a one-line message. */
1419
1449
  get message(): string;
1420
1450
  }
@@ -1497,7 +1527,7 @@ declare const LockfileReader_base: Context.ServiceClass<LockfileReader, "@effect
1497
1527
  *
1498
1528
  * @public
1499
1529
  */
1500
- declare class LockfileReader extends LockfileReader_base {
1530
+ export declare class LockfileReader extends LockfileReader_base {
1501
1531
  /** Builds the service. */
1502
1532
  static readonly make: (options?: LockfileReaderOptions) => Effect.Effect<LockfileReaderShape, never, WorkspaceRoot | PackageManagerDetector | WorkspaceDiscovery | FileSystem.FileSystem | Path.Path>;
1503
1533
  /**
@@ -1631,7 +1661,7 @@ declare const PeerParent_base: Schema.Class<PeerParent, Schema.Struct<{
1631
1661
  *
1632
1662
  * @public
1633
1663
  */
1634
- declare class PeerParent extends PeerParent_base {}
1664
+ export declare class PeerParent extends PeerParent_base {}
1635
1665
  declare const UnsatisfiedPeer_base: Schema.Class<UnsatisfiedPeer, Schema.Struct<{
1636
1666
  /** The importer path the problem belongs to (`"."` for the root). */
1637
1667
  readonly importer: Schema.NonEmptyString;
@@ -1672,7 +1702,7 @@ declare const UnsatisfiedPeer_base: Schema.Class<UnsatisfiedPeer, Schema.Struct<
1672
1702
  *
1673
1703
  * @public
1674
1704
  */
1675
- declare class UnsatisfiedPeer extends UnsatisfiedPeer_base {}
1705
+ export declare class UnsatisfiedPeer extends UnsatisfiedPeer_base {}
1676
1706
  declare const PeerCheck_base: Schema.Class<PeerCheck, Schema.Struct<{
1677
1707
  /**
1678
1708
  * Whether the lockfile's format records peer resolution at all.
@@ -1738,7 +1768,7 @@ declare const PeerCheck_base: Schema.Class<PeerCheck, Schema.Struct<{
1738
1768
  *
1739
1769
  * @public
1740
1770
  */
1741
- declare class PeerCheck extends PeerCheck_base {
1771
+ export declare class PeerCheck extends PeerCheck_base {
1742
1772
  /** The unsatisfied peers a gate should act on — the non-optional ones. */
1743
1773
  get required(): ReadonlyArray<UnsatisfiedPeer>;
1744
1774
  /**
@@ -1800,7 +1830,7 @@ declare const PublishTarget_base: Schema.Class<PublishTarget, Schema.Struct<{
1800
1830
  *
1801
1831
  * @public
1802
1832
  */
1803
- declare class PublishTarget extends PublishTarget_base {}
1833
+ export declare class PublishTarget extends PublishTarget_base {}
1804
1834
  /**
1805
1835
  * The {@link PublishabilityDetector} service shape.
1806
1836
  *
@@ -1894,7 +1924,7 @@ declare const PublishabilityDetector_base: Context.ServiceClass<PublishabilityDe
1894
1924
  *
1895
1925
  * @public
1896
1926
  */
1897
- declare class PublishabilityDetector extends PublishabilityDetector_base {
1927
+ export declare class PublishabilityDetector extends PublishabilityDetector_base {
1898
1928
  /**
1899
1929
  * Standard npm publishing semantics, **as a value**. Pure — no filesystem,
1900
1930
  * no platform services.
@@ -1969,13 +1999,13 @@ declare class PublishabilityDetector extends PublishabilityDetector_base {
1969
1999
  *
1970
2000
  * @public
1971
2001
  */
1972
- declare const TagStyle: Schema.Literals<readonly ["single", "scoped"]>;
2002
+ export declare const TagStyle: Schema.Literals<readonly ["single", "scoped"]>;
1973
2003
  /**
1974
2004
  * The decoded type of {@link (TagStyle:variable)}: `"single" | "scoped"`.
1975
2005
  *
1976
2006
  * @public
1977
2007
  */
1978
- type TagStyle = typeof TagStyle.Type;
2008
+ export type TagStyle = typeof TagStyle.Type;
1979
2009
  /**
1980
2010
  * Formatting knobs for {@link ReleaseTag.single} and {@link ReleaseTag.scoped}.
1981
2011
  *
@@ -2056,7 +2086,7 @@ declare const TrackingTag_base: Schema.Class<TrackingTag, Schema.Struct<{
2056
2086
  *
2057
2087
  * @public
2058
2088
  */
2059
- declare class TrackingTag extends TrackingTag_base {
2089
+ export declare class TrackingTag extends TrackingTag_base {
2060
2090
  /** Whether this alias tracks a whole major line, or one minor line inside it. */
2061
2091
  get precision(): "major" | "minor";
2062
2092
  /**
@@ -2125,7 +2155,7 @@ type TagClassification = {
2125
2155
  *
2126
2156
  * @public
2127
2157
  */
2128
- declare const classifyTag: (tag: string) => TagClassification;
2158
+ export declare const classifyTag: (tag: string) => TagClassification;
2129
2159
  declare const ReleaseTag_base: Schema.Class<ReleaseTag, Schema.Struct<{
2130
2160
  /** The tag string exactly as it appears in git. */
2131
2161
  readonly value: Schema.NonEmptyString;
@@ -2162,7 +2192,7 @@ declare const ReleaseTag_base: Schema.Class<ReleaseTag, Schema.Struct<{
2162
2192
  *
2163
2193
  * @public
2164
2194
  */
2165
- declare class ReleaseTag extends ReleaseTag_base {
2195
+ export declare class ReleaseTag extends ReleaseTag_base {
2166
2196
  /**
2167
2197
  * One shared tag for a whole release: `1.2.3`.
2168
2198
  *
@@ -2195,13 +2225,13 @@ declare class ReleaseTag extends ReleaseTag_base {
2195
2225
  *
2196
2226
  * @public
2197
2227
  */
2198
- declare const VersioningStrategyType: Schema.Literals<readonly ["single", "fixed-group", "independent"]>;
2228
+ export declare const VersioningStrategyType: Schema.Literals<readonly ["single", "fixed-group", "independent"]>;
2199
2229
  /**
2200
2230
  * The decoded type of {@link (VersioningStrategyType:variable)}.
2201
2231
  *
2202
2232
  * @public
2203
2233
  */
2204
- type VersioningStrategyType = typeof VersioningStrategyType.Type;
2234
+ export type VersioningStrategyType = typeof VersioningStrategyType.Type;
2205
2235
  /**
2206
2236
  * Arguments to {@link VersioningStrategy.classify}.
2207
2237
  *
@@ -2277,7 +2307,7 @@ declare const VersioningStrategy_base: Schema.Class<VersioningStrategy, Schema.S
2277
2307
  *
2278
2308
  * @public
2279
2309
  */
2280
- declare class VersioningStrategy extends VersioningStrategy_base {
2310
+ export declare class VersioningStrategy extends VersioningStrategy_base {
2281
2311
  /**
2282
2312
  * Whether a release needs one tag per package rather than one shared tag.
2283
2313
  */
@@ -2358,7 +2388,7 @@ declare const CatalogSet_base: Schema.Class<CatalogSet, Schema.Struct<{
2358
2388
  *
2359
2389
  * @public
2360
2390
  */
2361
- declare class CatalogSet extends CatalogSet_base {
2391
+ export declare class CatalogSet extends CatalogSet_base {
2362
2392
  /** The empty set — a workspace with no catalogs. */
2363
2393
  static empty(): CatalogSet;
2364
2394
  /** Wrap a pnpm `Catalogs` map, dropping unusable entries. */
@@ -2566,7 +2596,7 @@ declare const WorkspaceCatalogs_base: Context.ServiceClass<WorkspaceCatalogs, "@
2566
2596
  *
2567
2597
  * @public
2568
2598
  */
2569
- declare class WorkspaceCatalogs extends WorkspaceCatalogs_base {
2599
+ export declare class WorkspaceCatalogs extends WorkspaceCatalogs_base {
2570
2600
  /** Builds the service. */
2571
2601
  static readonly make: (options?: WorkspaceCatalogsOptions) => Effect.Effect<WorkspaceCatalogsShape, never, WorkspaceRoot | LockfileReader | ConfigDependencyHooks | FileSystem.FileSystem | Path.Path>;
2572
2602
  /**
@@ -2689,7 +2719,23 @@ declare class WorkspaceCatalogs extends WorkspaceCatalogs_base {
2689
2719
  declare const PackageStateSnapshot_base: Schema.Class<PackageStateSnapshot, Schema.Struct<{
2690
2720
  /** The package name. */
2691
2721
  readonly name: Schema.NonEmptyString;
2692
- /** The raw `version` string, as recorded at the captured moment. */
2722
+ /**
2723
+ * The raw `version` string, as recorded at the captured moment — or `""` for
2724
+ * a manifest that declared none.
2725
+ *
2726
+ * @remarks
2727
+ * `WorkspacePackage.version` is optional and a version-less member is an
2728
+ * ordinary pnpm shape, but this field stays a plain required string because a
2729
+ * snapshot is a serialized value someone stores and diffs: an absent key and
2730
+ * a present one would read as a change the moment one side of a diff was
2731
+ * captured by a different code path. **`""` is therefore the sentinel for
2732
+ * "the manifest declared no version", not a version anyone can use** — both
2733
+ * `snapshotOf` at a ref and the worktree snapshot write it, so the two sides
2734
+ * agree. Every resolution surface treats it as absence:
2735
+ * {@link WorkspaceStateSnapshot.resolve} answers `Option.none()` for a
2736
+ * `workspace:` specifier against it, because `some("")` would rewrite
2737
+ * `workspace:^` as a bare `"^"`.
2738
+ */
2693
2739
  readonly version: Schema.String;
2694
2740
  /** POSIX path relative to the workspace root; `"."` for the root package. */
2695
2741
  readonly relativePath: Schema.String;
@@ -2716,7 +2762,7 @@ declare const PackageStateSnapshot_base: Schema.Class<PackageStateSnapshot, Sche
2716
2762
  *
2717
2763
  * @public
2718
2764
  */
2719
- declare class PackageStateSnapshot extends PackageStateSnapshot_base {
2765
+ export declare class PackageStateSnapshot extends PackageStateSnapshot_base {
2720
2766
  /**
2721
2767
  * Every dependency, merged across the four kinds.
2722
2768
  *
@@ -2798,9 +2844,19 @@ declare const WorkspaceStateSnapshot_base: Schema.Class<WorkspaceStateSnapshot,
2798
2844
  *
2799
2845
  * @public
2800
2846
  */
2801
- declare class WorkspaceStateSnapshot extends WorkspaceStateSnapshot_base {
2847
+ export declare class WorkspaceStateSnapshot extends WorkspaceStateSnapshot_base {
2802
2848
  #private;
2803
- /** Every captured package's name → version. Total; O(1) after the first call. */
2849
+ /**
2850
+ * Every captured package's name → version. Total; O(1) after the first call.
2851
+ *
2852
+ * @remarks
2853
+ * Values are `PackageStateSnapshot.version` verbatim, so a member whose
2854
+ * manifest declared no version maps to the **`""` sentinel** rather than
2855
+ * being absent from the map — presence here answers membership, not
2856
+ * "has a usable version". A caller reading a version out of this map owes
2857
+ * the `""` check itself; {@link WorkspaceStateSnapshot.resolve} already
2858
+ * makes it.
2859
+ */
2804
2860
  get versions(): ReadonlyMap<string, string>;
2805
2861
  /** A single captured package by name, or `Option.none()`. Total. */
2806
2862
  package(name: string): Option.Option<PackageStateSnapshot>;
@@ -3086,7 +3142,7 @@ declare const WorkspaceSnapshots_base: Context.ServiceClass<WorkspaceSnapshots,
3086
3142
  *
3087
3143
  * @public
3088
3144
  */
3089
- declare class WorkspaceSnapshots extends WorkspaceSnapshots_base {
3145
+ export declare class WorkspaceSnapshots extends WorkspaceSnapshots_base {
3090
3146
  /** Builds the service over `Git`, {@link WorkspaceRoot}, {@link WorkspaceDiscovery} and {@link WorkspaceCatalogs}. */
3091
3147
  static readonly make: (options?: WorkspaceSnapshotsOptions) => Effect.Effect<WorkspaceSnapshotsShape, never, Git | WorkspaceRoot | WorkspaceDiscovery | WorkspaceCatalogs>;
3092
3148
  /**
@@ -3204,7 +3260,7 @@ type WorkspacesServices = WorkspaceRoot | PackageManagerDetector | WorkspaceDisc
3204
3260
  *
3205
3261
  * @public
3206
3262
  */
3207
- declare class Workspaces {
3263
+ export declare class Workspaces {
3208
3264
  private constructor();
3209
3265
  /**
3210
3266
  * Every service that needs only a filesystem: root, package-manager
@@ -3631,6 +3687,61 @@ interface WorkspacesSyncOptions {
3631
3687
  /** The synchronous path implementation (Node: the `node:path` module itself). */
3632
3688
  readonly path: SyncPath;
3633
3689
  }
3690
+ /**
3691
+ * Why {@link getWorkspacePackagesSync} left a manifest out of its result — the
3692
+ * `WorkspaceDiscoveryError.kind` values a single manifest read can produce.
3693
+ *
3694
+ * @remarks
3695
+ * The same vocabulary the Effect surface fails with, deliberately: a member the
3696
+ * async `listPackages()` rejects as `missingName` is the member the sync facade
3697
+ * reports as `missingName`. `invalidYaml` is excluded because it describes the
3698
+ * `pnpm-workspace.yaml` read, not a manifest. There is no `missingVersion` —
3699
+ * a version-less manifest is a member on both surfaces.
3700
+ *
3701
+ * The vocabulary is shared; the CHECKS are shared only as far as this list.
3702
+ * Both surfaces perform the same five: the file read (`read`), `JSON.parse`
3703
+ * (`invalidJson`), the parsed value being a non-null non-array object
3704
+ * (`invalidShape`), a non-empty string `name` (`missingName`), and a `version`
3705
+ * that is either absent or a non-empty string (`invalidShape`). Beyond them
3706
+ * they diverge, and one divergence is deliberate: a malformed `publishConfig`
3707
+ * FAILS the async surface, because it reaches the decode, while the sync facade
3708
+ * drops the field and keeps the member. A consumer that must reject such a
3709
+ * manifest has to use the Effect surface.
3710
+ *
3711
+ * @public
3712
+ */
3713
+ type WorkspaceDiscoverySkipKind = Exclude<WorkspaceDiscoveryError["kind"], "invalidYaml">;
3714
+ /**
3715
+ * One manifest {@link getWorkspacePackagesSync} skipped, reported through
3716
+ * {@link GetWorkspacePackagesSyncOptions.onSkip}.
3717
+ *
3718
+ * @remarks
3719
+ * The sync facade is total, so it cannot fail the way `WorkspaceDiscovery`
3720
+ * does — but a skipped member must still be observable. Before this record
3721
+ * existed the skip was silent, and a hand-written fixture with one unusable
3722
+ * manifest enumerated as a plausible empty array indistinguishable from "no
3723
+ * workspaces configured" (issue #605). The fields mirror
3724
+ * `WorkspaceDiscoveryError`: `root`, `path`, `kind`, and on `cause` either the
3725
+ * caught throwable (`read` / `invalidJson`) or an `Error` carrying the same
3726
+ * sentence the Effect surface fails with (`invalidShape`). Only `missingName`
3727
+ * has `cause: undefined` — the field is simply absent, and nothing threw.
3728
+ *
3729
+ * @public
3730
+ */
3731
+ interface WorkspaceDiscoverySkip {
3732
+ /** The workspace root the enumeration ran against. */
3733
+ readonly root: string;
3734
+ /** Absolute path to the `package.json` that was skipped. */
3735
+ readonly path: string;
3736
+ /** Why it was skipped. */
3737
+ readonly kind: WorkspaceDiscoverySkipKind;
3738
+ /**
3739
+ * The thrown value for `read` and `invalidJson`, an `Error` whose message
3740
+ * matches the Effect surface's for `invalidShape`, and `undefined` for
3741
+ * `missingName` alone.
3742
+ */
3743
+ readonly cause: unknown;
3744
+ }
3634
3745
  /**
3635
3746
  * The nearest workspace root at or above `cwd`, or `null`.
3636
3747
  *
@@ -3674,7 +3785,7 @@ interface WorkspacesSyncOptions {
3674
3785
  *
3675
3786
  * @public
3676
3787
  */
3677
- declare const findWorkspaceRootSync: (cwd: string, options: WorkspacesSyncOptions) => string | null;
3788
+ export declare const findWorkspaceRootSync: (cwd: string, options: WorkspacesSyncOptions) => string | null;
3678
3789
  /**
3679
3790
  * Options for {@link getWorkspacePackagesSync}: the required consumer-supplied
3680
3791
  * operations plus the traversal bound.
@@ -3689,6 +3800,21 @@ interface GetWorkspacePackagesSyncOptions extends WorkspacesSyncOptions {
3689
3800
  * @defaultValue 32
3690
3801
  */
3691
3802
  readonly maxDepth?: number;
3803
+ /**
3804
+ * Called once for every manifest the enumeration found and could not use,
3805
+ * with the file and the reason — so a skipped member is observable even
3806
+ * though this function is total and has no error channel to raise it on.
3807
+ *
3808
+ * @remarks
3809
+ * Omit it and skips are simply not reported, which is the pre-existing
3810
+ * behaviour; nothing is logged in its place. The callback is invoked
3811
+ * synchronously, before the result is returned, in enumeration order —
3812
+ * members first, then the root, which is read last even though it is
3813
+ * returned first. Its
3814
+ * result is discarded, and a throw from it propagates: the facade is total
3815
+ * over *data*, not over caller mistakes, exactly as a bad `maxDepth` is.
3816
+ */
3817
+ readonly onSkip?: ((skip: WorkspaceDiscoverySkip) => void) | undefined;
3692
3818
  }
3693
3819
  /**
3694
3820
  * Every workspace package under `root`, root package first.
@@ -3713,6 +3839,13 @@ interface GetWorkspacePackagesSyncOptions extends WorkspacesSyncOptions {
3713
3839
  * caller mistakes: a `maxDepth` that is not a positive integer throws, matching
3714
3840
  * the enumerator's defect.
3715
3841
  *
3842
+ * **A skip is never silent.** Every manifest left out is reported through
3843
+ * `onSkip` with its path and the same `kind` the Effect surface would have
3844
+ * failed with (a `WorkspaceDiscoverySkip`), so "no packages" and "packages
3845
+ * rejected" stay distinguishable. A manifest with no `version` is NOT skipped:
3846
+ * pnpm accepts a version-less private package, so it is a member with
3847
+ * `version` absent — on both surfaces.
3848
+ *
3716
3849
  * @param root - The workspace root, from {@link findWorkspaceRootSync}.
3717
3850
  * @param options - The consumer-supplied operations and traversal bounds; see
3718
3851
  * {@link GetWorkspacePackagesSyncOptions}.
@@ -3738,7 +3871,7 @@ interface GetWorkspacePackagesSyncOptions extends WorkspacesSyncOptions {
3738
3871
  *
3739
3872
  * @public
3740
3873
  */
3741
- declare const getWorkspacePackagesSync: (root: string, options: GetWorkspacePackagesSyncOptions) => ReadonlyArray<WorkspacePackage>;
3874
+ export declare const getWorkspacePackagesSync: (root: string, options: GetWorkspacePackagesSyncOptions) => ReadonlyArray<WorkspacePackage>;
3742
3875
  //#endregion
3743
- export { type CatalogAssemblyFailure, CatalogSet, ChangeDetectionError, type ChangeDetectionFailure, ChangeDetectionOptions, ChangeDetector, type ChangeDetectorShape, type ClassifyOptions, ConfigDependencyHooks, type ConfigDependencyHooksShape, CyclicDependencyError, type DependencyDiff, DependencyGraph, DetectedPackageManager, type FindWorkspaceRootOptions, type GetWorkspacePackagesSyncOptions, type HookInjection, type ImporterVersions, LockfileReadError, type LockfileReadFailure, LockfileReader, type LockfileReaderOptions, type LockfileReaderShape, NoPeerDependencyRules, PackageManagerDetectionError, type PackageManagerDetectionFailure, PackageManagerDetector, type PackageManagerDetectorShape, PackageManagerEvidence, PackageManagerName, PackageNotFoundError, type PackageRelease, PackageStateSnapshot, PeerCheck, type PeerCheckOptions, type PeerDependencyRules, PeerParent, PublishConfig, PublishTarget, PublishabilityDetector, type PublishabilityDetectorShape, ReleaseTag, type SyncDirectoryEntry, type SyncFileSystem, type SyncPath, type TagClassification, type TagFormatOptions, TagStyle, TrackingTag, type TrackingTagOptions, UnsatisfiedPeer, type UnverifiedReason, type VersioningDetectOptions, VersioningStrategy, VersioningStrategyType, WORKSPACE_MARKERS, WorkspaceCatalogs, type WorkspaceCatalogsOptions, type WorkspaceCatalogsShape, WorkspaceDiscovery, WorkspaceDiscoveryError, type WorkspaceDiscoveryFailure, type WorkspaceDiscoveryOptions, type WorkspaceDiscoveryShape, WorkspaceInfo, type WorkspaceLookupFailure, WorkspaceManifestError, WorkspacePackage, WorkspacePatternError, WorkspaceRoot, WorkspaceRootNotFoundError, type WorkspaceRootShape, type WorkspaceSnapshotAtFailure, type WorkspaceSnapshotWorktreeFailure, WorkspaceSnapshots, type WorkspaceSnapshotsOptions, type WorkspaceSnapshotsShape, WorkspaceStateSnapshot, Workspaces, type WorkspacesGitOptions, type WorkspacesOptions, type WorkspacesServices, type WorkspacesSyncOptions, classifyTag, findWorkspaceRootSync, getWorkspacePackagesSync };
3876
+ export type { CatalogAssemblyFailure, ChangeDetectionFailure, ChangeDetectorShape, ClassifyOptions, ConfigDependencyHooksShape, DependencyDiff, FindWorkspaceRootOptions, GetWorkspacePackagesSyncOptions, HookInjection, ImporterVersions, LockfileReadFailure, LockfileReaderOptions, LockfileReaderShape, PackageManagerDetectionFailure, PackageManagerDetectorShape, PackageRelease, PeerCheckOptions, PeerDependencyRules, PublishabilityDetectorShape, SyncDirectoryEntry, SyncFileSystem, SyncPath, TagClassification, TagFormatOptions, TrackingTagOptions, UnverifiedReason, VersioningDetectOptions, WorkspaceCatalogsOptions, WorkspaceCatalogsShape, WorkspaceDiscoveryFailure, WorkspaceDiscoveryOptions, WorkspaceDiscoveryShape, WorkspaceDiscoverySkip, WorkspaceDiscoverySkipKind, WorkspaceLookupFailure, WorkspaceRootShape, WorkspaceSnapshotAtFailure, WorkspaceSnapshotWorktreeFailure, WorkspaceSnapshotsOptions, WorkspaceSnapshotsShape, WorkspacesGitOptions, WorkspacesOptions, WorkspacesServices, WorkspacesSyncOptions };
3744
3877
  //# sourceMappingURL=index.d.ts.map
package/node-sync.d.ts CHANGED
@@ -1,7 +1,8 @@
1
+ import "@effected/npm";
2
+ import "effect";
1
3
  import "@effected/glob";
2
4
  import "@effected/lockfiles";
3
5
  import "@effected/package-json";
4
- import "effect";
5
6
  //#region src/WorkspacesSync.d.ts
6
7
  /**
7
8
  * The synchronous file operations the sync entry points need, supplied by the
@@ -141,7 +142,7 @@ interface WorkspacesSyncOptions {
141
142
  *
142
143
  * @public
143
144
  */
144
- declare const nodeFileSystem: SyncFileSystem;
145
+ export declare const nodeFileSystem: SyncFileSystem;
145
146
  /**
146
147
  * `SyncPath` as the running platform's `node:path` — win32 semantics on
147
148
  * Windows, posix elsewhere. Pass `node:path/win32` or `node:path/posix`
@@ -149,7 +150,7 @@ declare const nodeFileSystem: SyncFileSystem;
149
150
  *
150
151
  * @public
151
152
  */
152
- declare const nodePath: SyncPath;
153
+ export declare const nodePath: SyncPath;
153
154
  /**
154
155
  * The complete Node-bound options bag for `findWorkspaceRootSync` and
155
156
  * `getWorkspacePackagesSync` — {@link nodeFileSystem} plus {@link nodePath}.
@@ -159,7 +160,7 @@ declare const nodePath: SyncPath;
159
160
  *
160
161
  * @public
161
162
  */
162
- declare const nodeSyncOps: WorkspacesSyncOptions;
163
+ export declare const nodeSyncOps: WorkspacesSyncOptions;
163
164
  //#endregion
164
- export { type SyncDirectoryEntry, type SyncFileSystem, type SyncPath, type WorkspacesSyncOptions, nodeFileSystem, nodePath, nodeSyncOps };
165
+ export type { SyncDirectoryEntry, SyncFileSystem, SyncPath, WorkspacesSyncOptions };
165
166
  //# sourceMappingURL=node-sync.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effected/workspaces",
3
- "version": "0.18.3",
3
+ "version": "0.20.0",
4
4
  "private": false,
5
5
  "description": "Monorepo workspace tooling as Effect services — root discovery, package enumeration, the dependency graph, package-manager detection, pnpm catalog resolution, lockfile IO and git-based change detection.",
6
6
  "keywords": [
@@ -46,22 +46,22 @@
46
46
  "./package.json": "./package.json"
47
47
  },
48
48
  "dependencies": {
49
- "@effected/commands": "^0.5.0",
50
- "@effected/git": "^0.10.0",
51
- "@effected/glob": "^0.4.0",
52
- "@effected/lockfiles": "^0.7.1",
53
- "@effected/npm": "^0.12.1",
54
- "@effected/package-json": "^0.13.0",
55
- "@effected/semver": "^0.5.0",
56
- "@effected/walker": "^0.5.0",
57
- "@effected/yaml": "^0.12.0",
49
+ "@effected/commands": "^0.6.0",
50
+ "@effected/git": "^0.11.0",
51
+ "@effected/glob": "^0.5.0",
52
+ "@effected/lockfiles": "^0.8.0",
53
+ "@effected/npm": "^0.13.0",
54
+ "@effected/package-json": "^0.14.0",
55
+ "@effected/semver": "^0.6.0",
56
+ "@effected/walker": "^0.6.0",
57
+ "@effected/yaml": "^0.13.0",
58
58
  "@pnpm/catalogs.config": "^1100.0.5",
59
59
  "@pnpm/catalogs.protocol-parser": "^1100.0.0",
60
60
  "@pnpm/catalogs.resolver": "^1100.0.0",
61
61
  "@pnpm/catalogs.types": "^1100.0.0"
62
62
  },
63
63
  "peerDependencies": {
64
- "effect": "4.0.0-rc.109"
64
+ "effect": "4.0.0-rc.112"
65
65
  },
66
66
  "engines": {
67
67
  "node": ">=24.11.0"