@effected/workspaces 0.18.3 → 0.19.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 +2 -2
- package/WorkspaceDiscovery.js +41 -11
- package/WorkspacePackage.js +18 -2
- package/WorkspaceSnapshots.js +1 -1
- package/WorkspaceStateSnapshot.js +42 -5
- package/WorkspacesSync.js +79 -9
- package/index.d.ts +140 -7
- package/node-sync.d.ts +2 -1
- package/package.json +2 -2
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.
|
package/WorkspaceDiscovery.js
CHANGED
|
@@ -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
|
|
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"
|
|
182
|
+
if (version !== void 0 && typeof version !== "string") return yield* Effect.fail(new WorkspaceDiscoveryError({
|
|
176
183
|
root,
|
|
177
184
|
path: packageJsonPath,
|
|
178
|
-
kind: "
|
|
179
|
-
cause:
|
|
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) =>
|
|
521
|
-
specifier
|
|
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");
|
package/WorkspacePackage.js
CHANGED
|
@@ -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
|
-
/**
|
|
95
|
-
version
|
|
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`. */
|
package/WorkspaceSnapshots.js
CHANGED
|
@@ -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
|
-
/**
|
|
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
|
-
/**
|
|
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":
|
|
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) =>
|
|
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
|
-
/**
|
|
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
|
|
150
|
-
if (raw
|
|
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
|
|
154
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
@@ -83,8 +83,24 @@ declare class WorkspaceManifestError extends WorkspaceManifestError_base {
|
|
|
83
83
|
declare const WorkspacePackage_base: Schema.Class<WorkspacePackage, Schema.Struct<{
|
|
84
84
|
/** The package name. */
|
|
85
85
|
readonly name: Schema.NonEmptyString;
|
|
86
|
-
/**
|
|
87
|
-
|
|
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`. */
|
|
@@ -417,18 +433,26 @@ 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"
|
|
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
|
|
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
458
|
declare class WorkspaceDiscoveryError extends WorkspaceDiscoveryError_base {
|
|
@@ -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";
|
|
@@ -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
|
-
/**
|
|
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;
|
|
@@ -2800,7 +2846,17 @@ declare const WorkspaceStateSnapshot_base: Schema.Class<WorkspaceStateSnapshot,
|
|
|
2800
2846
|
*/
|
|
2801
2847
|
declare class WorkspaceStateSnapshot extends WorkspaceStateSnapshot_base {
|
|
2802
2848
|
#private;
|
|
2803
|
-
/**
|
|
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>;
|
|
@@ -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
|
*
|
|
@@ -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}.
|
|
@@ -3740,5 +3873,5 @@ interface GetWorkspacePackagesSyncOptions extends WorkspacesSyncOptions {
|
|
|
3740
3873
|
*/
|
|
3741
3874
|
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, 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, type WorkspaceDiscoverySkip, type WorkspaceDiscoverySkipKind, 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 };
|
|
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
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@effected/workspaces",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.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": [
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"@effected/lockfiles": "^0.7.1",
|
|
53
53
|
"@effected/npm": "^0.12.1",
|
|
54
54
|
"@effected/package-json": "^0.13.0",
|
|
55
|
-
"@effected/semver": "^0.5.
|
|
55
|
+
"@effected/semver": "^0.5.1",
|
|
56
56
|
"@effected/walker": "^0.5.0",
|
|
57
57
|
"@effected/yaml": "^0.12.0",
|
|
58
58
|
"@pnpm/catalogs.config": "^1100.0.5",
|