@effected/workspaces 0.28.0 → 0.29.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.
@@ -548,7 +548,8 @@ var WorkspaceDiscovery = class WorkspaceDiscovery extends Context.Service()("@ef
548
548
  const version = index.get(packageName);
549
549
  return version === void 0 ? Effect.fail(new DependencyResolutionError({
550
550
  specifier,
551
- cause: /* @__PURE__ */ new Error(`Workspace member "${packageName}" declares no version`)
551
+ reason: "no-version",
552
+ cause: void 0
552
553
  })) : Effect.succeed(Option.some(version));
553
554
  }));
554
555
  } };
@@ -52,10 +52,10 @@ const snapshotOf = (content, relativePath) => {
52
52
  const parsed = parseJsonObject(content.value);
53
53
  const name = parsed.name;
54
54
  if (typeof name !== "string" || name.length === 0) return Option.none();
55
- const version = typeof parsed.version === "string" ? parsed.version : "";
55
+ const version = parsed.version;
56
56
  return Option.some(PackageStateSnapshot.make({
57
57
  name,
58
- version,
58
+ ...typeof version === "string" && version !== "" ? { version } : {},
59
59
  relativePath,
60
60
  ...isStringRecord(parsed.dependencies) ? { dependencies: parsed.dependencies } : {},
61
61
  ...isStringRecord(parsed.devDependencies) ? { devDependencies: parsed.devDependencies } : {},
@@ -233,7 +233,7 @@ var WorkspaceSnapshots = class WorkspaceSnapshots extends Context.Service()("@ef
233
233
  const hookReplays = yield* catalogsService.hookReplays();
234
234
  const snapshotPackages = packages.map((pkg) => PackageStateSnapshot.make({
235
235
  name: pkg.name,
236
- version: pkg.version ?? "",
236
+ ...pkg.version === void 0 ? {} : { version: pkg.version },
237
237
  relativePath: pkg.relativePath,
238
238
  dependencies: pkg.dependencies,
239
239
  devDependencies: pkg.devDependencies,
@@ -1,12 +1,27 @@
1
1
  import { unanimousVersionOf } from "./internal/importerVersions.js";
2
2
  import { CatalogSet } from "./WorkspaceCatalogs.js";
3
- import { Effect, Exit, Layer, Option, Schema } from "effect";
3
+ import { Effect, Exit, Layer, Option, Schema, SchemaTransformation } from "effect";
4
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));
8
8
  const DependencyMap = Schema.Record(Schema.String, Schema.String).pipe(Schema.withDecodingDefaultKey(Effect.succeed(EMPTY)), Schema.withConstructorDefault(Effect.succeed(EMPTY)));
9
9
  /**
10
+ * A snapshot's `version` field: absent when the manifest declared none, and
11
+ * never `""`.
12
+ *
13
+ * @remarks
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.
19
+ */
20
+ const SnapshotVersion = Schema.optionalKey(Schema.String).pipe(Schema.decodeTo(Schema.optionalKey(Schema.NonEmptyString), SchemaTransformation.transformOptional({
21
+ decode: (encoded) => Option.filter(encoded, (version) => version !== ""),
22
+ encode: (version) => version
23
+ })));
24
+ /**
10
25
  * One workspace member as captured in a {@link WorkspaceStateSnapshot} — the
11
26
  * serializable slice a snapshot diff reads: identity, version, location, and the
12
27
  * four dependency records.
@@ -24,23 +39,24 @@ var PackageStateSnapshot = class extends Schema.Class("PackageStateSnapshot")({
24
39
  /** The package name. */
25
40
  name: Schema.NonEmptyString,
26
41
  /**
27
- * The raw `version` string, as recorded at the captured moment — or `""` for
42
+ * The raw `version` string, as recorded at the captured moment — absent for
28
43
  * a manifest that declared none.
29
44
  *
30
45
  * @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 `"^"`.
46
+ * Optional exactly as `WorkspacePackage.version` is: a version-less member is
47
+ * an ordinary pnpm shape. Both capture paths — `WorkspaceSnapshots.at(ref)`
48
+ * and `WorkspaceSnapshots.worktree()` — omit the key for such a member, so
49
+ * 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.
52
+ *
53
+ * A version-less member is still a member: it appears in `packages` and
54
+ * answers {@link WorkspaceStateSnapshot.package}, but is absent from
55
+ * {@link WorkspaceStateSnapshot.versions}, resolves a `workspace:` specifier
56
+ * to `Option.none()`, and fails its snapshot-bound `WorkspaceResolver`'s
57
+ * `versionOf` typed.
42
58
  */
43
- version: Schema.String,
59
+ version: SnapshotVersion,
44
60
  /** POSIX path relative to the workspace root; `"."` for the root package. */
45
61
  relativePath: Schema.String,
46
62
  /** Production dependencies. */
@@ -166,7 +182,11 @@ var WorkspaceStateSnapshot = class WorkspaceStateSnapshot extends Schema.Class("
166
182
  #workspaceResolver;
167
183
  #resolvers;
168
184
  #versions() {
169
- if (this.#versionIndex === void 0) this.#versionIndex = new Map(this.packages.map((pkg) => [pkg.name, pkg.version]));
185
+ if (this.#versionIndex === void 0) {
186
+ const index = /* @__PURE__ */ new Map();
187
+ for (const pkg of this.packages) if (pkg.version !== void 0) index.set(pkg.name, pkg.version);
188
+ this.#versionIndex = index;
189
+ }
170
190
  return this.#versionIndex;
171
191
  }
172
192
  #packages() {
@@ -174,15 +194,13 @@ var WorkspaceStateSnapshot = class WorkspaceStateSnapshot extends Schema.Class("
174
194
  return this.#packageIndex;
175
195
  }
176
196
  /**
177
- * Every captured package's name → version. Total; O(1) after the first call.
197
+ * Every captured package's name → version, for the packages that declared
198
+ * one. Total; O(1) after the first call.
178
199
  *
179
200
  * @remarks
180
- * Values are `PackageStateSnapshot.version` verbatim, so a member whose
181
- * manifest declared no version maps to the **`""` sentinel** rather than
182
- * being absent from the map — presence here answers membership, not
183
- * "has a usable version". A caller reading a version out of this map owes
184
- * the `""` check itself; {@link WorkspaceStateSnapshot.resolve} already
185
- * makes it.
201
+ * A member whose manifest declared no version is **absent** from this map,
202
+ * so every value is a real version and presence answers "has a version",
203
+ * not membership — ask {@link WorkspaceStateSnapshot.package} for that.
186
204
  */
187
205
  get versions() {
188
206
  return this.#versions();
@@ -268,10 +286,7 @@ var WorkspaceStateSnapshot = class WorkspaceStateSnapshot extends Schema.Class("
268
286
  const fromCatalogs = this.#catalogRange(dependency, classified.name);
269
287
  return Option.isSome(fromCatalogs) ? fromCatalogs : onUnresolvedCatalog();
270
288
  }
271
- case "workspace": {
272
- const version = this.#versions().get(dependency);
273
- return version === void 0 || version === "" ? Option.none() : Option.some(version);
274
- }
289
+ case "workspace": return Option.fromUndefinedOr(this.#versions().get(dependency));
275
290
  default: return Option.none();
276
291
  }
277
292
  }
@@ -422,11 +437,13 @@ var WorkspaceStateSnapshot = class WorkspaceStateSnapshot extends Schema.Class("
422
437
  */
423
438
  get workspaceResolver() {
424
439
  if (this.#workspaceResolver === void 0) this.#workspaceResolver = Layer.succeed(WorkspaceResolver, { versionOf: (packageName) => {
425
- const version = this.#versions().get(packageName);
426
- if (version === void 0) return Effect.succeed(Option.none());
427
- if (version === "") return Effect.fail(new DependencyResolutionError({
440
+ const member = this.#packages().get(packageName);
441
+ if (member === void 0) return Effect.succeed(Option.none());
442
+ const version = member.version;
443
+ if (version === void 0) return Effect.fail(new DependencyResolutionError({
428
444
  specifier: `workspace:${packageName}`,
429
- cause: /* @__PURE__ */ new Error(`Workspace member "${packageName}" declares no version`)
445
+ reason: "no-version",
446
+ cause: void 0
430
447
  }));
431
448
  return Effect.succeed(Option.some(version));
432
449
  } });
package/WorkspacesSync.js CHANGED
@@ -120,9 +120,17 @@ const isPackage = (options, dir) => options.fileSystem.exists(options.path.join(
120
120
  * Markers match the async service exactly: a `pnpm-workspace.yaml`, or a
121
121
  * `package.json` carrying a `workspaces` field.
122
122
  *
123
+ * The ascent is unbounded unless `options.stopAt` names a ceiling
124
+ * ({@link FindWorkspaceRootSyncOptions.stopAt}), with the Effect surface's
125
+ * semantics: inclusive, resolved at lookup time. Where `WorkspaceRoot.find`
126
+ * fails `WorkspaceRootNotFoundError` carrying the resolved ceiling, this
127
+ * returns `null` — the facade is total and has no error channel, and the
128
+ * caller already holds the ceiling it passed.
129
+ *
123
130
  * @param cwd - Where to start the ascent (typically `process.cwd()`),
124
131
  * matching the Effect layers' `{ cwd }` option.
125
- * @param options - The consumer-supplied file and path operations.
132
+ * @param options - The consumer-supplied file and path operations, plus an
133
+ * optional `stopAt` ceiling.
126
134
  *
127
135
  * @example
128
136
  * ```ts
@@ -146,10 +154,12 @@ const isPackage = (options, dir) => options.fileSystem.exists(options.path.join(
146
154
  const findWorkspaceRootSync = (cwd, options) => {
147
155
  const { fileSystem, path } = options;
148
156
  let current = path.resolve(cwd);
157
+ const ceiling = options.stopAt === void 0 ? void 0 : path.resolve(options.stopAt);
149
158
  for (let depth = 0; depth < 32 * 8; depth++) {
150
159
  if (fileSystem.exists(path.join(current, "pnpm-workspace.yaml"))) return current;
151
160
  const manifest = readJson(fileSystem, path.join(current, "package.json"));
152
161
  if (manifest?.workspaces !== void 0 && manifest.workspaces !== null) return current;
162
+ if (ceiling !== void 0 && current === ceiling) return null;
153
163
  const parent = path.dirname(current);
154
164
  if (parent === current) return null;
155
165
  current = parent;