@effected/workspaces 0.24.1 → 0.26.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/testing.d.ts ADDED
@@ -0,0 +1,1382 @@
1
+ import { DependencyField, WorkspaceResolver } from "@effected/npm";
2
+ import { Context, Duration, Effect, FileSystem, Layer, Option, Path, Schema } from "effect";
3
+ import { GlobPattern } from "@effected/glob";
4
+ import { WorkspaceManifest } from "@effected/lockfiles";
5
+ import { Package } from "@effected/package-json";
6
+ //#region src/LayerPolicy.d.ts
7
+ declare const LayerPolicyError_base: Schema.Class<LayerPolicyError, Schema.TaggedStruct<"LayerPolicyError", {
8
+ /** `read`: the file could not be read. `json`: it is not JSON. `decode`: it is not a `LayerPolicy`. */
9
+ readonly reason: Schema.Literals<readonly ["read", "json", "decode"]>;
10
+ /** The file, when the policy came from one. */
11
+ readonly path: Schema.optionalKey<Schema.String>;
12
+ /** The originating failure. */
13
+ readonly cause: Schema.Defect;
14
+ }>, import("effect/Cause").YieldableError>;
15
+ /**
16
+ * Raised when a layer policy cannot be read, parsed or decoded.
17
+ *
18
+ * @public
19
+ */
20
+ export declare class LayerPolicyError extends LayerPolicyError_base {
21
+ /** Renders the failure kind and the file into one line. */
22
+ get message(): string;
23
+ }
24
+ declare const LayerPolicy_base: Schema.Class<LayerPolicy, Schema.Struct<{
25
+ /** Exact package names (never relative paths) per layer, top layer first. */
26
+ readonly layers: Schema.$Array<Schema.$Array<Schema.String>>;
27
+ /** Exact package names (never relative paths) any layer may depend on that never depend on a layer. */
28
+ readonly tooling: Schema.$Array<Schema.String>;
29
+ /** Globs matched against package names (never relative paths) for packages whose own edges are not checked. */
30
+ readonly unconstrained: Schema.$Array<Schema.String>;
31
+ /** The dependency maps to check. Absent means all four. */
32
+ readonly fields: Schema.optionalKey<Schema.$Array<Schema.Literals<readonly ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]>>>;
33
+ /** Edges that must exist, written `"a -> b"`. */
34
+ readonly requiredEdges: Schema.optionalKey<Schema.$Array<Schema.String>>;
35
+ }>, {}>;
36
+ /**
37
+ * A committed dependency-layering policy (`layers.json`).
38
+ *
39
+ * @remarks
40
+ * `layers` is top-down: an edge may only point from a layer to one BELOW it
41
+ * (a higher index), or into `tooling`. `tooling` packages may depend on each
42
+ * other but never reach a layer. `unconstrained` holds globs (through
43
+ * `@effected/glob`) for packages whose own edges are not checked (a test
44
+ * harness, the private root). `fields` narrows which dependency maps count,
45
+ * defaulting to all four. `requiredEdges` (`"a -> b"`) are edges that must
46
+ * exist in a checked field: the non-vacuity guard against a discovery that
47
+ * quietly drops real edges.
48
+ *
49
+ * Every entry matches a package's `name`, never its `relativePath`: `layers`
50
+ * and `tooling` list exact names, and `unconstrained` globs match names. A
51
+ * private root named `my-monorepo` at `relativePath` `"."` is classified as
52
+ * `"my-monorepo"`; an entry of `"."` or `"packages/*"` classifies nothing.
53
+ *
54
+ * Decoding is strict: a key the policy does not model fails `decode`, and the
55
+ * message names every such key. A typo on an optional key would otherwise be
56
+ * silently dropped: `requiredEdge` would remove the non-vacuity guard and
57
+ * leave the report green. `$schema` is always accepted. A file that carries
58
+ * keys of its own, such as systems' `harness`, passes them in `allowKeys`.
59
+ *
60
+ * @example
61
+ * ```ts
62
+ * import { NodeServices } from "@effect/platform-node";
63
+ * import { LayerPolicy } from "@effected/workspaces/testing";
64
+ * import { Effect } from "effect";
65
+ *
66
+ * const policy = LayerPolicy.load("/repo/layers.json").pipe(Effect.provide(NodeServices.layer));
67
+ * ```
68
+ *
69
+ * @public
70
+ */
71
+ export declare class LayerPolicy extends LayerPolicy_base {
72
+ /** The dependency maps a check reads: `fields`, or all four. */
73
+ get effectiveFields(): ReadonlyArray<DependencyField>;
74
+ /**
75
+ * Decode a policy from a parsed JSON value.
76
+ *
77
+ * @remarks
78
+ * Every key the policy does not model fails, except `$schema` and the
79
+ * keys named in `options.allowKeys`, which are dropped before decoding.
80
+ * `options.path` names the file in the error.
81
+ */
82
+ static readonly decode: (input: unknown, options?: {
83
+ readonly path?: string | undefined;
84
+ readonly allowKeys?: ReadonlyArray<string> | undefined;
85
+ } | undefined) => Effect.Effect<LayerPolicy, LayerPolicyError, never>;
86
+ /** Read, parse and decode the policy file at `path`; `options.allowKeys` is passed to `decode`. */
87
+ static readonly load: (path: string, options?: {
88
+ readonly allowKeys?: ReadonlyArray<string> | undefined;
89
+ } | undefined) => Effect.Effect<LayerPolicy, LayerPolicyError, FileSystem.FileSystem>;
90
+ }
91
+ //#endregion
92
+ //#region src/WorkspacePackage.d.ts
93
+ declare const PublishConfig_base: Schema.Class<PublishConfig, Schema.Struct<{
94
+ /** Scoped-package visibility. Its presence overrides `private`. */
95
+ readonly access: Schema.optionalKey<Schema.Literals<readonly ["public", "restricted"]>>;
96
+ /** The registry to publish to. */
97
+ readonly registry: Schema.optionalKey<Schema.String>;
98
+ /** A subdirectory to publish instead of the package root. */
99
+ readonly directory: Schema.optionalKey<Schema.String>;
100
+ /**
101
+ * Whether workspace links point into `directory` during local development —
102
+ * pnpm symlinks the publish directory instead of the package root, so
103
+ * siblings resolve the built artifact they would install from the registry.
104
+ * Meaningful only alongside `directory`.
105
+ */
106
+ readonly linkDirectory: Schema.optionalKey<Schema.Boolean>;
107
+ /** The dist-tag to publish under. */
108
+ readonly tag: Schema.optionalKey<Schema.String>;
109
+ }>, {}>;
110
+ /**
111
+ * The `publishConfig` fields workspace tooling reads.
112
+ *
113
+ * @remarks
114
+ * Deliberately narrow. `@effected/package-json` models `publishConfig` as an
115
+ * open `Record<string, unknown>` for round-trip fidelity, which preserves every
116
+ * key but types none of them; this is the typed projection of the handful that
117
+ * decide *where, whether and as what* a package publishes. Unknown keys are
118
+ * ignored, not rejected.
119
+ *
120
+ * @public
121
+ */
122
+ export declare class PublishConfig extends PublishConfig_base {}
123
+ /**
124
+ * The result of comparing two {@link WorkspacePackage} dependency snapshots.
125
+ *
126
+ * @remarks
127
+ * Comparison runs across all four dependency kinds combined, so a dependency
128
+ * that moves between kinds at the same version does not appear in the diff.
129
+ *
130
+ * @public
131
+ */
132
+ interface DependencyDiff {
133
+ /** Present in the receiver, absent from the other. */
134
+ readonly added: Record<string, string>;
135
+ /** Present in the other, absent from the receiver. */
136
+ readonly removed: Record<string, string>;
137
+ /** Present in both at different specifiers. */
138
+ readonly changed: Record<string, {
139
+ readonly from: string;
140
+ readonly to: string;
141
+ }>;
142
+ }
143
+ declare const WorkspaceManifestError_base: Schema.Class<WorkspaceManifestError, Schema.TaggedStruct<"WorkspaceManifestError", {
144
+ /** Absolute path to the `package.json` that failed. */
145
+ readonly packageJsonPath: Schema.String;
146
+ /** Whether the file could not be read, or read but not decoded. */
147
+ readonly kind: Schema.Literals<readonly ["read", "decode"]>;
148
+ /** The originating failure, preserved rather than flattened to a string. */
149
+ readonly cause: Schema.Defect;
150
+ }>, import("effect/Cause").YieldableError>;
151
+ /**
152
+ * Raised when a workspace member's `package.json` cannot be read or decoded
153
+ * into the strict `@effected/package-json` `Package` model.
154
+ *
155
+ * @remarks
156
+ * Discovery itself never raises this — it uses the tolerant projection. Only
157
+ * `WorkspacePackage.manifest` does, so opting into the strict model is an
158
+ * explicit, individually recoverable step.
159
+ *
160
+ * @public
161
+ */
162
+ export declare class WorkspaceManifestError extends WorkspaceManifestError_base {
163
+ /** Renders the path and failure kind into a one-line message. */
164
+ get message(): string;
165
+ }
166
+ declare const WorkspacePackage_base: Schema.Class<WorkspacePackage, Schema.Struct<{
167
+ /** The package name. */
168
+ readonly name: Schema.NonEmptyString;
169
+ /**
170
+ * The raw `version` string — deliberately not semver-validated — or absent
171
+ * when the manifest declares none.
172
+ *
173
+ * @remarks
174
+ * pnpm accepts a version-less private package, and a private monorepo root
175
+ * without a `version` is the ordinary shape, so discovery carries the field
176
+ * exactly as the manifest has it: a non-empty string when present, verbatim
177
+ * and un-validated, absent when the key is absent — never a `"0.0.0"`
178
+ * placeholder and never a present `undefined` key. **Only ABSENCE is
179
+ * tolerated**: a `version` that is present but not a string, or present and
180
+ * `""`, fails discovery as `invalidShape` on both surfaces. `""` in
181
+ * particular is not a pnpm shape and would resolve `workspace:^` to a bare
182
+ * `"^"`.
183
+ * Anything that needs a concrete version (`WorkspaceResolver.versionOf`,
184
+ * a release tag) answers the absence itself rather than inventing one.
185
+ */
186
+ readonly version: Schema.optionalKey<Schema.String>;
187
+ /** Absolute path to the package directory. */
188
+ readonly path: Schema.NonEmptyString;
189
+ /** Absolute path to the package's `package.json`. */
190
+ readonly packageJsonPath: Schema.NonEmptyString;
191
+ /** POSIX path relative to the workspace root; `"."` for the root package. */
192
+ readonly relativePath: Schema.String;
193
+ /**
194
+ * Absolute path to the workspace root this package was discovered under.
195
+ *
196
+ * @remarks
197
+ * Carried, not derived. Whoever built this value already knew the root —
198
+ * `WorkspaceDiscovery` resolved it before enumerating, and the sync entry
199
+ * point is handed it — so dropping it forced every consumer into per-package
200
+ * root arithmetic (counting `relativePath` segments and re-ascending that
201
+ * many `..`). That reconstruction is exact only while `path` and
202
+ * `relativePath` agree, and it re-derives something the kit never had to
203
+ * lose.
204
+ *
205
+ * For the root package this equals `path`, and `relativePath` is `"."`.
206
+ */
207
+ readonly workspaceRoot: Schema.NonEmptyString;
208
+ /** Whether the package is marked private. */
209
+ readonly private: Schema.withConstructorDefault<Schema.withDecodingDefaultKey<Schema.Boolean, never>>;
210
+ /** Production dependencies. */
211
+ readonly dependencies: Schema.withConstructorDefault<Schema.withDecodingDefaultKey<Schema.$Record<Schema.String, Schema.String>, never>>;
212
+ /** Development dependencies. */
213
+ readonly devDependencies: Schema.withConstructorDefault<Schema.withDecodingDefaultKey<Schema.$Record<Schema.String, Schema.String>, never>>;
214
+ /** Peer dependencies. */
215
+ readonly peerDependencies: Schema.withConstructorDefault<Schema.withDecodingDefaultKey<Schema.$Record<Schema.String, Schema.String>, never>>;
216
+ /** Optional dependencies. */
217
+ readonly optionalDependencies: Schema.withConstructorDefault<Schema.withDecodingDefaultKey<Schema.$Record<Schema.String, Schema.String>, never>>;
218
+ /** The `publishConfig` block, when present. */
219
+ readonly publishConfig: Schema.optionalKey<typeof PublishConfig>;
220
+ /**
221
+ * The package's `package.json` as read — tolerant access to every field
222
+ * outside the typed discovery slice (`scripts`, `exports`, …) without a
223
+ * second file read.
224
+ *
225
+ * @remarks
226
+ * Values are `unknown` and exactly what discovery parsed; nothing here is
227
+ * validated beyond being a record. For the strict typed model use
228
+ * `manifest()`, which deliberately **re-reads** the file — a point-in-time
229
+ * refresh this captured record cannot provide. Defaults to `{}` for
230
+ * construction sites and previously-serialized values that predate the
231
+ * field.
232
+ */
233
+ readonly manifestRecord: Schema.withConstructorDefault<Schema.withDecodingDefaultKey<Schema.$Record<Schema.String, Schema.Unknown>, never>>;
234
+ }>, {}>;
235
+ /**
236
+ * A single package inside a workspace: the discovery-relevant slice of its
237
+ * `package.json` plus its filesystem location.
238
+ *
239
+ * @remarks
240
+ * Produced by `WorkspaceDiscovery` for every directory the `packages:` patterns
241
+ * enumerate. The root package is always present with `relativePath` `"."`.
242
+ *
243
+ * @example
244
+ * ```ts
245
+ * import { WorkspacePackage } from "@effected/workspaces";
246
+ *
247
+ * const pkg = WorkspacePackage.make({
248
+ * name: "@my-org/utils",
249
+ * version: "1.0.0",
250
+ * path: "/repo/packages/utils",
251
+ * packageJsonPath: "/repo/packages/utils/package.json",
252
+ * relativePath: "packages/utils",
253
+ * workspaceRoot: "/repo",
254
+ * });
255
+ *
256
+ * pkg.isRootWorkspace; // false
257
+ * pkg.unscopedName; // "utils"
258
+ * pkg.workspaceRoot; // "/repo"
259
+ * ```
260
+ *
261
+ * @public
262
+ */
263
+ export declare class WorkspacePackage extends WorkspacePackage_base {
264
+ /** Whether this is the workspace root package. */
265
+ get isRootWorkspace(): boolean;
266
+ /** Whether the package is publishable in principle (not marked private). */
267
+ get isPublic(): boolean;
268
+ /** The npm scope (`@org`), or `Option.none()` for an unscoped name. */
269
+ get scope(): Option.Option<string>;
270
+ /** The name with any scope stripped. */
271
+ get unscopedName(): string;
272
+ /**
273
+ * Every dependency, merged across the four kinds.
274
+ *
275
+ * @remarks
276
+ * Precedence on a name declared in several kinds runs
277
+ * `dependencies` \> `devDependencies` \> `peerDependencies` \>
278
+ * `optionalDependencies`.
279
+ */
280
+ get allDependencies(): Record<string, string>;
281
+ /** Whether `name` is a production dependency. */
282
+ hasDependency(name: string): boolean;
283
+ /** Whether `name` is a development dependency. */
284
+ hasDevDependency(name: string): boolean;
285
+ /** Whether `name` is a peer dependency. */
286
+ hasPeerDependency(name: string): boolean;
287
+ /** Whether `name` is an optional dependency. */
288
+ hasOptionalDependency(name: string): boolean;
289
+ /** Whether `name` appears in any of the four dependency kinds. */
290
+ hasAnyDependencyOn(name: string): boolean;
291
+ /** The declared specifier for `name`, searched across all four kinds. */
292
+ dependencyVersion(name: string): Option.Option<string>;
293
+ /**
294
+ * Whether any dependency name matches `pattern` — the `minimatch` runtime
295
+ * dependency's one call site, now over `@effected/glob`'s vendored engine.
296
+ *
297
+ * @remarks
298
+ * A `GlobPattern` is total and free to test. A `string` is compiled on every
299
+ * call and an **uncompilable** literal throws: a glob written into a call
300
+ * site is developer wiring, not untrusted input, so it belongs in the defect
301
+ * channel rather than widening the typed channel every caller must branch
302
+ * on. Compile once with `GlobPattern.compile` and pass the result when
303
+ * testing many packages.
304
+ *
305
+ * @param pattern - A compiled pattern, or a source string to compile.
306
+ */
307
+ matchesDependency(pattern: GlobPattern | string): boolean;
308
+ /** Compare this package's dependencies against `other`'s. */
309
+ dependencyDiff(other: WorkspacePackage): DependencyDiff;
310
+ /**
311
+ * Project to `@effected/lockfiles`' `WorkspaceManifest` — the input shape of
312
+ * `LockfileIntegrity.compare`. Total.
313
+ */
314
+ toWorkspaceManifest(): WorkspaceManifest;
315
+ /**
316
+ * Read and decode this package's `package.json` into the strict
317
+ * `@effected/package-json` `Package` model — the bridge from the
318
+ * tolerant discovery projection to the fully typed manifest.
319
+ */
320
+ static readonly manifest: (self: WorkspacePackage) => Effect.Effect<Package, WorkspaceManifestError, FileSystem.FileSystem>;
321
+ /** Instance form of `WorkspacePackage.manifest`. */
322
+ manifest(): Effect.Effect<Package, WorkspaceManifestError, FileSystem.FileSystem>;
323
+ }
324
+ //#endregion
325
+ //#region src/PackageManagerName.d.ts
326
+ /**
327
+ * The four package managers this package understands.
328
+ *
329
+ * @public
330
+ */
331
+ export declare const PackageManagerName: Schema.Literals<readonly ["npm", "pnpm", "yarn", "bun"]>;
332
+ /**
333
+ * The decoded type of {@link (PackageManagerName:variable)}: `"npm" | "pnpm" | "yarn" | "bun"`.
334
+ *
335
+ * @public
336
+ */
337
+ export type PackageManagerName = typeof PackageManagerName.Type;
338
+ //#endregion
339
+ //#region src/WorkspaceRoot.d.ts
340
+ /**
341
+ * Options for {@link WorkspaceRoot}'s `find`.
342
+ *
343
+ * @remarks
344
+ * Both bounds are passed straight through to `@effected/walker`'s
345
+ * `Walker.ascend`; this package does not re-decide either.
346
+ *
347
+ * @public
348
+ */
349
+ interface FindWorkspaceRootOptions {
350
+ /**
351
+ * A ceiling directory. The ascent stops after probing it, so an unmarked
352
+ * `stopAt` fails typed as {@link WorkspaceRootNotFoundError} rather than
353
+ * silently escaping into an enclosing repository.
354
+ *
355
+ * @remarks
356
+ * Resolved to an absolute path before comparison, exactly as `cwd` is — a
357
+ * relative or non-normalized ceiling that never string-matched an ancestor
358
+ * would reintroduce the unbounded ascent it was passed to prevent.
359
+ */
360
+ readonly stopAt?: string;
361
+ /**
362
+ * Hard cap on the number of directories probed.
363
+ *
364
+ * @remarks
365
+ * A non-integer or non-positive value is a **defect**, not a typed failure —
366
+ * it is developer wiring, and walker's guard raises it.
367
+ *
368
+ * @defaultValue 256
369
+ */
370
+ readonly maxDepth?: number;
371
+ }
372
+ declare const WorkspaceRootNotFoundError_base: Schema.Class<WorkspaceRootNotFoundError, Schema.TaggedStruct<"WorkspaceRootNotFoundError", {
373
+ /** The directory the ascent started from. */
374
+ readonly searchPath: Schema.String;
375
+ /** The marker filenames probed at each ancestor. */
376
+ readonly markers: Schema.$Array<Schema.String>;
377
+ /**
378
+ * The resolved ceiling the ascent was bounded by, when one was supplied.
379
+ *
380
+ * @remarks
381
+ * Absent means the ascent ran to the filesystem root. Its presence is what
382
+ * lets a caller tell "there is no workspace root anywhere above me" from
383
+ * "there is none below the ceiling I set" — two failures that otherwise
384
+ * render identically.
385
+ */
386
+ readonly stopAt: Schema.optionalKey<Schema.String>;
387
+ }>, import("effect/Cause").YieldableError>;
388
+ /**
389
+ * Raised when no workspace root can be found by ascending from a directory.
390
+ *
391
+ * @remarks
392
+ * `markers` records what was probed, so the failure names the contract rather
393
+ * than paraphrasing it in prose.
394
+ *
395
+ * @public
396
+ */
397
+ export declare class WorkspaceRootNotFoundError extends WorkspaceRootNotFoundError_base {
398
+ /** Renders the search path, probed markers and any ceiling into a one-line message. */
399
+ get message(): string;
400
+ }
401
+ /**
402
+ * The {@link WorkspaceRoot} service contract.
403
+ *
404
+ * @remarks
405
+ * Named so a consumer can type its own double — or a `Layer.succeed` — against
406
+ * the contract rather than re-deriving it, exactly as `WorkspaceDiscoveryShape`
407
+ * does. Prefer {@link WorkspaceRoot.layerTest} to hand-rolling one.
408
+ *
409
+ * @public
410
+ */
411
+ interface WorkspaceRootShape {
412
+ /**
413
+ * The nearest workspace root at or above `cwd`.
414
+ *
415
+ * @param cwd - The directory to start the ascent from; resolved to an
416
+ * absolute path first.
417
+ * @param options - Optional ascent bounds. Unbounded by default, which
418
+ * walks to the filesystem root and can therefore resolve to an enclosing
419
+ * repository's root; pass `stopAt` when the caller knows the ceiling.
420
+ */
421
+ readonly find: (cwd: string, options?: FindWorkspaceRootOptions) => Effect.Effect<string, WorkspaceRootNotFoundError>;
422
+ }
423
+ declare const WorkspaceRoot_base: Context.ServiceClass<WorkspaceRoot, "@effected/workspaces/WorkspaceRoot", WorkspaceRootShape>;
424
+ /**
425
+ * Locates the workspace root by ascending from a starting directory.
426
+ *
427
+ * @example
428
+ * ```ts
429
+ * import { WorkspaceRoot } from "@effected/workspaces";
430
+ * import { Effect } from "effect";
431
+ *
432
+ * const program = Effect.gen(function* () {
433
+ * const root = yield* WorkspaceRoot;
434
+ * return yield* root.find("/repo/packages/utils/src");
435
+ * });
436
+ * ```
437
+ *
438
+ * @example
439
+ * Bounded: an unmarked fixture directory fails typed instead of escaping into
440
+ * the enclosing repository.
441
+ *
442
+ * ```ts
443
+ * import { WorkspaceRoot } from "@effected/workspaces";
444
+ * import { Effect } from "effect";
445
+ *
446
+ * const program = Effect.gen(function* () {
447
+ * const roots = yield* WorkspaceRoot;
448
+ * return yield* roots.find("/tmp/fixture/packages/a", { stopAt: "/tmp/fixture" });
449
+ * });
450
+ * ```
451
+ *
452
+ * @public
453
+ */
454
+ export declare class WorkspaceRoot extends WorkspaceRoot_base {
455
+ /** Builds the service over core `FileSystem` and `Path`. */
456
+ static readonly make: Effect.Effect<WorkspaceRootShape, never, FileSystem.FileSystem | Path.Path>;
457
+ /** The live layer. */
458
+ static readonly layer: Layer.Layer<WorkspaceRoot, never, FileSystem.FileSystem | Path.Path>;
459
+ /**
460
+ * A test double resolving every `find` to `root`, with no filesystem.
461
+ *
462
+ * @remarks
463
+ * The nine-copies-of-a-four-line-mock case — a `Layer.succeed` over a `find`
464
+ * that ignores its arguments and succeeds with a fixed root is what consumers
465
+ * were writing by hand. The difference is that this double **honours
466
+ * `stopAt`**: a hand-rolled `find` that ignores the ceiling makes a bounded
467
+ * call pass under test and fail against the live service, which is the
468
+ * failure mode the option exists to catch. A `root` above the ceiling fails
469
+ * here exactly as it would live, with the same
470
+ * {@link WorkspaceRootNotFoundError}.
471
+ *
472
+ * The ceiling is `path.resolve`d through the injected `Path` service before
473
+ * the comparison, exactly as the live `make` path does — so a `stopAt`
474
+ * carrying `..` segments bounds the double identically to the live service,
475
+ * not by raw string. This is why `makeTest` yields an `Effect` requiring
476
+ * `Path`: it captures the service once at construction, the same shape as
477
+ * `make`. Consumers reach for {@link WorkspaceRoot.layerTest}, which provides
478
+ * `Path.layer` internally, so the requirement never surfaces at their call
479
+ * site.
480
+ *
481
+ * `maxDepth` is deliberately NOT modelled: the double does not walk, so it has
482
+ * no depth to cap, and pretending otherwise would encode a fiction. A suite
483
+ * exercising the depth guard wants the live service over a fixture tree.
484
+ *
485
+ * @param root - The root every unbounded `find` resolves to.
486
+ */
487
+ static readonly makeTest: (root: string) => Effect.Effect<WorkspaceRootShape, never, Path.Path>;
488
+ /**
489
+ * The test layer: {@link WorkspaceRoot.makeTest} with `Path.layer` provided.
490
+ *
491
+ * @remarks
492
+ * `makeTest` requires `Path` to normalize the `stopAt` ceiling; this layer
493
+ * supplies core's `Path.layer` internally, so the requirement never reaches a
494
+ * consumer — the published type stays `Layer.Layer<WorkspaceRoot>`.
495
+ *
496
+ * A parameterized layer factory mints a **fresh reference per call**, and
497
+ * layers memoize by reference — bind the result to a `const` and reuse it
498
+ * rather than calling `layerTest(...)` at each composition site.
499
+ *
500
+ * Pair it with `WorkspaceDiscovery.layerTest` to stand up the whole discovery
501
+ * path without a filesystem; between them there is nothing left for a
502
+ * module-level mock of `@effected/workspaces` to do, and a provided layer
503
+ * keeps the service graph — and its typed errors — intact.
504
+ *
505
+ * @example
506
+ * ```ts
507
+ * import { WorkspaceDiscovery, WorkspaceRoot } from "@effected/workspaces";
508
+ * import { Effect } from "effect";
509
+ *
510
+ * const TestRoot = WorkspaceRoot.layerTest("/repo");
511
+ * const TestDiscovery = WorkspaceDiscovery.layerTest({
512
+ * listPackages: () => Effect.succeed([]),
513
+ * });
514
+ * // program.pipe(Effect.provide(TestRoot), Effect.provide(TestDiscovery))
515
+ * ```
516
+ */
517
+ static readonly layerTest: (root: string) => Layer.Layer<WorkspaceRoot>;
518
+ }
519
+ //#endregion
520
+ //#region src/WorkspaceDiscovery.d.ts
521
+ declare const WorkspaceDiscoveryError_base: Schema.Class<WorkspaceDiscoveryError, Schema.TaggedStruct<"WorkspaceDiscoveryError", {
522
+ /** The workspace root discovery was running against. */
523
+ readonly root: Schema.String;
524
+ /** The file that failed. */
525
+ readonly path: Schema.String;
526
+ /** What went wrong with it. */
527
+ readonly kind: Schema.Literals<readonly ["read", "invalidJson", "invalidShape", "invalidYaml", "missingName"]>;
528
+ /** The originating failure, if there was one. */
529
+ readonly cause: Schema.Defect;
530
+ }>, import("effect/Cause").YieldableError>;
531
+ /**
532
+ * Raised when a workspace member's `package.json` cannot be read, parsed, or
533
+ * used — it is missing, malformed, or lacks a `name`.
534
+ *
535
+ * @remarks
536
+ * `kind` is the discriminant a caller branches on; `cause` preserves the
537
+ * originating failure rather than flattening it into a sentence.
538
+ *
539
+ * A manifest with no `version` is NOT a failure: pnpm accepts a version-less
540
+ * private package and a private monorepo root without one is the ordinary
541
+ * shape, so the member is discovered with `WorkspacePackage.version` absent.
542
+ * The former `missingVersion` kind is retired. Only ABSENCE is tolerated: a
543
+ * `version` that is present but not a string — or present and `""`, which pnpm
544
+ * never wrote and which would resolve `workspace:^` to a bare `"^"` — is the
545
+ * manifest's shape being wrong and reports `invalidShape`.
546
+ *
547
+ * @public
548
+ */
549
+ export declare class WorkspaceDiscoveryError extends WorkspaceDiscoveryError_base {
550
+ /** Renders the failing file and kind into a one-line message. */
551
+ get message(): string;
552
+ }
553
+ declare const WorkspacePatternError_base: Schema.Class<WorkspacePatternError, Schema.TaggedStruct<"WorkspacePatternError", {
554
+ /** The workspace root the patterns were expanded against. */
555
+ readonly root: Schema.String;
556
+ /** The offending pattern, verbatim. */
557
+ readonly pattern: Schema.String;
558
+ /** Why it could not be enumerated. */
559
+ readonly kind: Schema.Literals<readonly ["missingBaseDir", "uncompilable", "depthExceeded", "budgetExceeded", "unreadableDirectory"]>;
560
+ /** A short, structured detail — the missing directory, or the bound exceeded. */
561
+ readonly detail: Schema.String;
562
+ }>, import("effect/Cause").YieldableError>;
563
+ /**
564
+ * Raised when a `packages:` pattern cannot be enumerated: its base directory is
565
+ * absent (usually a typo), the descent exceeded its depth cap, or the visit
566
+ * budget was exhausted.
567
+ *
568
+ * @public
569
+ */
570
+ export declare class WorkspacePatternError extends WorkspacePatternError_base {
571
+ /** Renders the pattern and failure kind into a one-line message. */
572
+ get message(): string;
573
+ }
574
+ declare const PackageNotFoundError_base: Schema.Class<PackageNotFoundError, Schema.TaggedStruct<"PackageNotFoundError", {
575
+ /** The name that was requested. */
576
+ readonly name: Schema.String;
577
+ /** Every workspace package name that does exist. */
578
+ readonly available: Schema.$Array<Schema.String>;
579
+ }>, import("effect/Cause").YieldableError>;
580
+ /**
581
+ * Raised when a workspace package is requested by a name no member carries.
582
+ *
583
+ * @remarks
584
+ * `available` lists every known member, which is what makes the error
585
+ * actionable — a typo is obvious next to the list it missed.
586
+ *
587
+ * @public
588
+ */
589
+ export declare class PackageNotFoundError extends PackageNotFoundError_base {
590
+ /** Renders the requested name into a one-line message. */
591
+ get message(): string;
592
+ }
593
+ declare const WorkspaceInfo_base: Schema.Class<WorkspaceInfo, Schema.Struct<{
594
+ /** Absolute path to the workspace root. */
595
+ readonly root: Schema.String;
596
+ /** The `packages:` patterns, verbatim. */
597
+ readonly patterns: Schema.$Array<Schema.String>;
598
+ }>, {}>;
599
+ /**
600
+ * Top-level facts about a workspace: where it is, what manages it, and the
601
+ * patterns that define its membership.
602
+ *
603
+ * @public
604
+ */
605
+ export declare class WorkspaceInfo extends WorkspaceInfo_base {}
606
+ /**
607
+ * Every failure `WorkspaceDiscovery.getPackage` can surface: the discovery
608
+ * failures plus a name that matches no member.
609
+ *
610
+ * @public
611
+ */
612
+ type WorkspaceLookupFailure = WorkspaceRootNotFoundError | WorkspaceDiscoveryError | WorkspacePatternError | PackageNotFoundError;
613
+ /**
614
+ * The error channel of the discovery methods that do not look a package up by
615
+ * name — everything except `getPackage`.
616
+ *
617
+ * @public
618
+ */
619
+ type WorkspaceDiscoveryFailure = Exclude<WorkspaceLookupFailure, PackageNotFoundError>;
620
+ /**
621
+ * Options for the {@link WorkspaceDiscovery} layer.
622
+ *
623
+ * @public
624
+ */
625
+ interface WorkspaceDiscoveryOptions {
626
+ /**
627
+ * The directory the workspace root is resolved from.
628
+ *
629
+ * @defaultValue `process.cwd()`, read lazily on first use — so a
630
+ * `process.chdir` between providing the layer and the first call is
631
+ * honoured.
632
+ */
633
+ readonly cwd?: string;
634
+ /** Descent cap for segment-crossing patterns. Defaults to 32. */
635
+ readonly maxDepth?: number;
636
+ }
637
+ /**
638
+ * The {@link WorkspaceDiscovery} service shape.
639
+ *
640
+ * @public
641
+ */
642
+ interface WorkspaceDiscoveryShape {
643
+ /** Facts about the resolved workspace. */
644
+ readonly info: () => Effect.Effect<WorkspaceInfo, WorkspaceDiscoveryFailure>;
645
+ /** Every workspace package, root first, then the rest sorted by relative path. */
646
+ readonly listPackages: () => Effect.Effect<ReadonlyArray<WorkspacePackage>, WorkspaceDiscoveryFailure>;
647
+ /** The discovered packages keyed by their root-relative importer path. */
648
+ readonly importerMap: () => Effect.Effect<ReadonlyMap<string, WorkspacePackage>, WorkspaceDiscoveryFailure>;
649
+ /** A single package by name. */
650
+ readonly getPackage: (name: string) => Effect.Effect<WorkspacePackage, WorkspaceLookupFailure>;
651
+ /** The package owning an absolute file path, by longest-prefix match. */
652
+ readonly resolveFile: (filePath: string) => Effect.Effect<Option.Option<WorkspacePackage>, WorkspaceDiscoveryFailure>;
653
+ /** The distinct packages owning any of `filePaths`. */
654
+ readonly resolveFiles: (filePaths: ReadonlyArray<string>) => Effect.Effect<ReadonlyArray<WorkspacePackage>, WorkspaceDiscoveryFailure>;
655
+ /**
656
+ * Facts about the workspace containing `directory`, discovered against THAT
657
+ * root rather than the layer-bound one.
658
+ */
659
+ readonly infoIn: (directory: string) => Effect.Effect<WorkspaceInfo, WorkspaceDiscoveryFailure>;
660
+ /**
661
+ * Every package of the workspace containing `directory`, discovered against
662
+ * THAT root rather than the layer-bound one.
663
+ *
664
+ * @remarks
665
+ * For a **long-lived host serving many roots** — an MCP server or a language
666
+ * server that resolves one workspace at startup and then answers calls
667
+ * scoped to a git worktree, a nested repository, or another project
668
+ * entirely. The layer-bound {@link WorkspaceDiscoveryShape.listPackages}
669
+ * answers about the root discovered from `options.cwd`, which such a host
670
+ * has no way to vary per call without building a fresh layer.
671
+ *
672
+ * **This re-reads; it does not re-root.** The tempting cheap fix — take the
673
+ * layer's package list and rewrite each `path` onto the caller's directory —
674
+ * produces correct-looking paths over the ORIGINAL root's manifests, so a
675
+ * worktree whose branch adds, removes or renames a package reports the other
676
+ * branch's membership with no error. Patterns, member manifests, names and
677
+ * versions all come from beneath `directory`'s own root here.
678
+ *
679
+ * `directory` may be the workspace root or anything inside it: the root is
680
+ * resolved by the same upward walk the layer-bound path uses, and results are
681
+ * memoized per RESOLVED root, so many directories in one workspace share one
682
+ * discovery. The memo holds one entry per distinct root for the layer's
683
+ * lifetime; {@link WorkspaceDiscoveryShape.refresh} drops all of them.
684
+ *
685
+ * @param directory - Absolute path to the workspace root, or to any
686
+ * directory inside it.
687
+ */
688
+ readonly listPackagesIn: (directory: string) => Effect.Effect<ReadonlyArray<WorkspacePackage>, WorkspaceDiscoveryFailure>;
689
+ /**
690
+ * Drop every memoized discovery — the layer-bound one and each per-root memo
691
+ * — so the next call re-reads the filesystem.
692
+ */
693
+ readonly refresh: () => Effect.Effect<void>;
694
+ /**
695
+ * Drop only the memo for the workspace containing `directory`, leaving the
696
+ * layer-bound memo and every other root's untouched.
697
+ *
698
+ * @remarks
699
+ * The precise counterpart to {@link WorkspaceDiscoveryShape.refresh} for a
700
+ * host serving several roots: refreshing one worktree because it changed
701
+ * should not discard sibling worktrees that did not, which is all `refresh`
702
+ * can do.
703
+ *
704
+ * **Fails typed on a directory in no workspace**, exactly as
705
+ * {@link WorkspaceDiscoveryShape.listPackagesIn} does for the same input —
706
+ * the three per-root methods answer a bad path the same way, and a caller
707
+ * that would rather treat it as a no-op writes `Effect.ignore`. Refreshing a
708
+ * root that HAS no memo is an ordinary no-op and not an error.
709
+ *
710
+ * @param directory - Absolute path to the workspace root, or to any
711
+ * directory inside it.
712
+ */
713
+ readonly refreshIn: (directory: string) => Effect.Effect<void, WorkspaceRootNotFoundError>;
714
+ }
715
+ declare const WorkspaceDiscovery_base: Context.ServiceClass<WorkspaceDiscovery, "@effected/workspaces/WorkspaceDiscovery", WorkspaceDiscoveryShape>;
716
+ /**
717
+ * Discovers the packages of a workspace.
718
+ *
719
+ * @remarks
720
+ * Layer construction is O(1): the root walk, pattern read, enumeration and
721
+ * per-package decode all happen on the first method call and are memoized for
722
+ * the lifetime of the layer. A Vitest reporter that builds the layer per call
723
+ * site and never queries it pays nothing.
724
+ *
725
+ * The memo is **success-only**. `Effect.cached` memoizes the first `Exit`,
726
+ * *including an interrupt* — an init interrupted by an unrelated timeout would
727
+ * otherwise brick the layer permanently with a cause outside its declared error
728
+ * channel. A failure or interrupt is therefore retried on the next call, which
729
+ * is a deliberate behaviour change from the v3 library.
730
+ *
731
+ * @example
732
+ * ```ts
733
+ * import { WorkspaceDiscovery } from "@effected/workspaces";
734
+ * import { Effect } from "effect";
735
+ *
736
+ * const program = Effect.gen(function* () {
737
+ * const discovery = yield* WorkspaceDiscovery;
738
+ * const packages = yield* discovery.listPackages();
739
+ * return packages.map((p) => p.name);
740
+ * });
741
+ * ```
742
+ *
743
+ * @public
744
+ */
745
+ export declare class WorkspaceDiscovery extends WorkspaceDiscovery_base {
746
+ /**
747
+ * Builds the service. Root resolution is one explicit concern: `cwd` is an
748
+ * option here, never an ambient `process.cwd()` read inside a method.
749
+ */
750
+ static readonly make: (options?: WorkspaceDiscoveryOptions) => Effect.Effect<WorkspaceDiscoveryShape, never, WorkspaceRoot | FileSystem.FileSystem | Path.Path>;
751
+ /**
752
+ * The live layer.
753
+ *
754
+ * @remarks
755
+ * A parameterized layer factory mints a **fresh reference per call**, and
756
+ * layers memoize by reference — bind the result to a `const` and reuse it
757
+ * rather than calling `layer(...)` at each composition site.
758
+ */
759
+ static readonly layer: (options?: WorkspaceDiscoveryOptions) => Layer.Layer<WorkspaceDiscovery, never, WorkspaceRoot | FileSystem.FileSystem | Path.Path>;
760
+ /**
761
+ * An in-memory test double of the service shape, with every method
762
+ * defaulted so a test stubs only what it exercises.
763
+ *
764
+ * @remarks
765
+ * The defaults model an **empty workspace**, and the derived methods run
766
+ * over the *effective* `listPackages` — the override when one is supplied —
767
+ * so stubbing only `listPackages` yields a consistent double:
768
+ *
769
+ * - `listPackages` — succeeds with `[]`.
770
+ * - `importerMap` — derived: the packages keyed by `relativePath`.
771
+ * - `getPackage` — derived: a name lookup that fails with the service's own
772
+ * typed {@link PackageNotFoundError} on a miss, exactly as the live
773
+ * implementation does.
774
+ * - `resolveFile` / `resolveFiles` — derived: longest-prefix ownership over
775
+ * `pkg.path`, POSIX-terminated (`"/"`); supply a win32 double explicitly
776
+ * if your fixture paths are win32.
777
+ * - `refresh` — a no-op (`Effect.void`); there is nothing memoized to drop.
778
+ * - `info` — **dies** with an explanatory defect. No honest default exists
779
+ * (a fabricated root path would leak into consumer path logic), so an
780
+ * unstubbed `info()` call is a test-wiring mistake and fails loudly as a
781
+ * defect rather than succeeding with a lie or failing with a dishonest
782
+ * typed error. A defect is not absorbed by `Effect.catch` or any
783
+ * typed-error handler — deliberately, so code under test with a
784
+ * best-effort `catch` cannot make the mandatory stub look optional; the
785
+ * unstubbed call still fails the test.
786
+ *
787
+ * @example
788
+ * ```ts
789
+ * import { WorkspaceDiscovery, WorkspacePackage } from "@effected/workspaces";
790
+ * import { Effect } from "effect";
791
+ *
792
+ * const double = WorkspaceDiscovery.makeTest({
793
+ * listPackages: () =>
794
+ * Effect.succeed([
795
+ * WorkspacePackage.make({
796
+ * name: "@my-org/utils",
797
+ * version: "1.0.0",
798
+ * path: "/repo/packages/utils",
799
+ * packageJsonPath: "/repo/packages/utils/package.json",
800
+ * relativePath: "packages/utils",
801
+ * workspaceRoot: "/repo",
802
+ * }),
803
+ * ]),
804
+ * });
805
+ * // `getPackage`, `importerMap`, `resolveFile(s)` now answer consistently.
806
+ * ```
807
+ */
808
+ static readonly makeTest: (overrides?: Partial<WorkspaceDiscoveryShape>) => WorkspaceDiscoveryShape;
809
+ /**
810
+ * The test layer: {@link WorkspaceDiscovery.makeTest} behind
811
+ * `Layer.succeed`, so a suite provides only the methods it exercises.
812
+ *
813
+ * @remarks
814
+ * A parameterized layer factory mints a **fresh reference per call**, and
815
+ * layers memoize by reference — bind the result to a `const` and reuse it
816
+ * rather than calling `layerTest(...)` at each composition site.
817
+ *
818
+ * @example
819
+ * ```ts
820
+ * import { WorkspaceDiscovery } from "@effected/workspaces";
821
+ * import { Effect } from "effect";
822
+ *
823
+ * const TestDiscovery = WorkspaceDiscovery.layerTest({
824
+ * listPackages: () => Effect.succeed([]),
825
+ * });
826
+ * // program.pipe(Effect.provide(TestDiscovery))
827
+ * ```
828
+ */
829
+ static readonly layerTest: (overrides?: Partial<WorkspaceDiscoveryShape>) => Layer.Layer<WorkspaceDiscovery>;
830
+ /**
831
+ * The real implementation of `@effected/npm`'s `WorkspaceResolver` contract
832
+ * — the one `@effected/package-json` declares but cannot fill.
833
+ *
834
+ * @remarks
835
+ * `versionOf` returns `Option.none()` for a name that is not a workspace
836
+ * member, per the contract's convention; the `DependencyResolutionError`
837
+ * channel is reserved for a failure of the resolution *mechanism* (an
838
+ * unfindable root, an unreadable manifest), never an ordinary miss.
839
+ *
840
+ * A member that declares **no `version`** is neither: it is a known member
841
+ * with nothing for `workspace:` to resolve to. Answering `none` would read
842
+ * as "not a member" downstream, so it fails typed instead, naming the
843
+ * specifier — the same channel a consumer already handles for an
844
+ * unresolvable workspace.
845
+ *
846
+ * @example
847
+ * ```ts
848
+ * import { Package } from "@effected/package-json";
849
+ * import { WorkspaceDiscovery } from "@effected/workspaces";
850
+ * import { Layer } from "effect";
851
+ *
852
+ * const resolvers = WorkspaceDiscovery.workspaceResolver.pipe(
853
+ * Layer.provide(WorkspaceDiscovery.layer()),
854
+ * );
855
+ * // `Package.resolve` now resolves `workspace:*` for real.
856
+ * ```
857
+ */
858
+ static readonly workspaceResolver: Layer.Layer<WorkspaceResolver, never, WorkspaceDiscovery>;
859
+ }
860
+ //#endregion
861
+ //#region src/PackedInstall.d.ts
862
+ /**
863
+ * Where each closure package is packed from.
864
+ *
865
+ * @remarks
866
+ * `{ directory }` runs `npm pack` in that subdirectory of each package, whose
867
+ * manifest must already be publish-ready. The default,
868
+ * `{ directory: "dist/prod/npm/pkg" }`, is the effected bundler's prod npm
869
+ * output: the same file list a release publishes.
870
+ *
871
+ * `"source"` runs `pnpm pack` in the package directory: pnpm honours
872
+ * `publishConfig.directory` and rewrites `workspace:` and `catalog:`
873
+ * specifiers. Two costs. Under the effected bundler `publishConfig.directory`
874
+ * names the DEV build, so source mode proves the dev artifact, not the
875
+ * published one. And pnpm can rewrite a `workspace:` specifier only in a
876
+ * workspace that has been `pnpm install`ed: in a never-installed one the pack
877
+ * fails with `ERR_PNPM_CANNOT_RESOLVE_WORKSPACE_PROTOCOL`, which
878
+ * `PackedInstall.run` reports as a `PackFailed` error naming the missing
879
+ * install.
880
+ *
881
+ * @public
882
+ */
883
+ type PackSource = "source" | {
884
+ readonly directory: string;
885
+ };
886
+ /**
887
+ * Options for {@link PackedInstall.run}.
888
+ *
889
+ * @public
890
+ */
891
+ interface PackedInstallOptions {
892
+ /** The carrier: a direct dependency of each scratch consumer, beside only `consumerDependencies`. */
893
+ readonly carrier: string;
894
+ /** The other workspace packages to pack and override; `"auto"` is the carrier's transitive runtime workspace dependencies. */
895
+ readonly closure: ReadonlyArray<string> | "auto";
896
+ /**
897
+ * The package managers to install with; each is used only if it answers
898
+ * `--version`. A manager listed twice is installed once; an empty list
899
+ * fails `NoManagerAvailable`.
900
+ */
901
+ readonly managers: ReadonlyArray<PackageManagerName>;
902
+ /** `"all"` fails if any listed manager is unavailable; `"any"` (the default) needs one. */
903
+ readonly require?: "all" | "any" | undefined;
904
+ /**
905
+ * Where to pack from. Defaults to `{ directory: "dist/prod/npm/pkg" }`, the
906
+ * published artifact; see {@link PackSource} for what `"source"` costs.
907
+ */
908
+ readonly packFrom?: PackSource | undefined;
909
+ /** Bin names every consumer must expose, executable, in `node_modules/.bin`. */
910
+ readonly bins: ReadonlyArray<string>;
911
+ /** The environment for every spawn: pass `process.env` from the test file. The parent manager's context is stripped. */
912
+ readonly env: Readonly<Record<string, string | undefined>>;
913
+ /**
914
+ * Extra consumer dependencies, name to range or `file:` tarball spec.
915
+ *
916
+ * @remarks
917
+ * Declare here every package the consumer's own code imports directly
918
+ * other than the carrier. pnpm's isolated layout links only the consumer's
919
+ * declared dependencies at its top level, so a closure package reached only
920
+ * through the carrier's peers or the overrides resolves inside the carrier
921
+ * but fails `ERR_MODULE_NOT_FOUND` when imported from the consumer root,
922
+ * while npm and bun hoist it and pass. That is a property of the consumer,
923
+ * not of the pack.
924
+ *
925
+ * An entry naming a packed package (the carrier or a closure member) is
926
+ * written as that package's `file:` tarball, whatever spec you pass, so
927
+ * any range will do: the packed tarball always wins. npm fails an install
928
+ * whose direct spec differs from its override (`EOVERRIDE`), so a range
929
+ * written through unchanged would break every npm consumer.
930
+ */
931
+ readonly consumerDependencies?: Readonly<Record<string, string>> | undefined;
932
+ /** Ceiling on each install. Defaults to four minutes. Expiry fails `InstallFailed` with a message naming the manager and this duration. */
933
+ readonly installTimeout?: Duration.Input | undefined;
934
+ }
935
+ declare const PackedInstallError_base: Schema.Class<PackedInstallError, Schema.TaggedStruct<"PackedInstallError", {
936
+ /** What failed. */
937
+ readonly reason: Schema.Literals<readonly ["UnsupportedPlatform", "NoManagerAvailable", "ManagerUnavailable", "UnknownPackage", "PackSourceMissing", "PackFailed", "UnresolvedProtocol", "InstallFailed", "MissingBin", "Discovery", "Io"]>;
938
+ /** One line, naming the package or manager involved. */
939
+ readonly message: Schema.String;
940
+ /** The package manager involved. */
941
+ readonly manager: Schema.optionalKey<Schema.Literals<readonly ["npm", "pnpm", "yarn", "bun"]>>;
942
+ /** The workspace package involved. */
943
+ readonly package: Schema.optionalKey<Schema.String>;
944
+ /** The tail of the failing command's output. */
945
+ readonly output: Schema.optionalKey<Schema.String>;
946
+ /** The originating failure. */
947
+ readonly cause: Schema.optionalKey<Schema.Defect>;
948
+ }>, import("effect/Cause").YieldableError>;
949
+ /**
950
+ * Why a packed install could not be proven.
951
+ *
952
+ * @public
953
+ */
954
+ export declare class PackedInstallError extends PackedInstallError_base {}
955
+ declare const InstalledConsumer_base: Schema.Class<InstalledConsumer, Schema.Struct<{
956
+ /** The package manager that installed it. */
957
+ readonly manager: Schema.Literals<readonly ["npm", "pnpm", "yarn", "bun"]>;
958
+ /** The version it reported and was pinned to. */
959
+ readonly managerVersion: Schema.String;
960
+ /** The consumer project directory (realpath'd). */
961
+ readonly directory: Schema.String;
962
+ }>, {}>;
963
+ /**
964
+ * One scratch project, outside the workspace, with the carrier installed.
965
+ *
966
+ * @public
967
+ */
968
+ export declare class InstalledConsumer extends InstalledConsumer_base {
969
+ /** The installed bin `name`, in `node_modules/.bin`. POSIX: `PackedInstall` runs only there. */
970
+ binPath(name: string): string;
971
+ }
972
+ declare const PackedInstallResult_base: Schema.Class<PackedInstallResult, Schema.Struct<{
973
+ /** One consumer per available manager, in the order requested. */
974
+ readonly consumers: Schema.$Array<typeof InstalledConsumer>;
975
+ /** The requested managers that did not answer `--version`. */
976
+ readonly unavailable: Schema.$Array<Schema.Literals<readonly ["npm", "pnpm", "yarn", "bun"]>>;
977
+ /** Every packed package: name to absolute tarball path. */
978
+ readonly tarballs: Schema.$Record<Schema.String, Schema.String>;
979
+ }>, {}>;
980
+ /**
981
+ * What a packed install produced.
982
+ *
983
+ * @public
984
+ */
985
+ export declare class PackedInstallResult extends PackedInstallResult_base {}
986
+ /**
987
+ * Prove a carrier's bins reach a consumer that is not part of the workspace,
988
+ * once per available package manager.
989
+ *
990
+ * @remarks
991
+ * Packs the carrier and its closure into a scoped, realpath'd scratch
992
+ * directory (so `file:` specs and the install cwd agree on macOS). Then, for
993
+ * each manager that answers `--version` from that directory (so a corepack
994
+ * pin in the repo cannot refuse it), it writes a consumer whose only direct
995
+ * dependency is the carrier tarball, steers the closure to its tarballs
996
+ * through the manager's own override field, pins the probed version (as
997
+ * `packageManager`, or for pnpm as `devEngines.packageManager` with `onFail:
998
+ * "ignore"`, since pnpm resolves a `packageManager` pin from the registry even
999
+ * when it names the running version), installs with lifecycle scripts skipped,
1000
+ * and checks every
1001
+ * expected bin is present and executable. A packed manifest that still
1002
+ * carries `workspace:`, `catalog:`, `link:` or a relative `file:` specifier
1003
+ * fails `UnresolvedProtocol` before any install.
1004
+ *
1005
+ * It asserts nothing about what the bins DO: run them from the test through
1006
+ * `InstalledConsumer.binPath`, for an MCP bin with `McpProbe` from
1007
+ * `@effected/mcp/testing`, inside the same scope, because the scratch
1008
+ * directory is removed when the scope closes. POSIX only.
1009
+ *
1010
+ * @example
1011
+ * ```ts
1012
+ * import { NodeServices } from "@effect/platform-node";
1013
+ * import { Run } from "@effected/commands";
1014
+ * import { Workspaces } from "@effected/workspaces";
1015
+ * import { PackedInstall } from "@effected/workspaces/testing";
1016
+ * import { Effect, Layer } from "effect";
1017
+ * import { ChildProcess } from "effect/unstable/process";
1018
+ *
1019
+ * const Live = Workspaces.layer({ cwd: "/repo" }).pipe(Layer.provideMerge(NodeServices.layer));
1020
+ *
1021
+ * // Use the consumers INSIDE the scope: closing it removes the scratch
1022
+ * // directory, so a binPath returned out of Effect.scoped points at nothing.
1023
+ * const program = Effect.gen(function* () {
1024
+ * const result = yield* PackedInstall.run({
1025
+ * carrier: "my-tool",
1026
+ * closure: "auto",
1027
+ * managers: ["npm", "pnpm"],
1028
+ * bins: ["my-tool"],
1029
+ * env: process.env,
1030
+ * });
1031
+ * const env = PackedInstall.scrubEnv(process.env);
1032
+ * for (const consumer of result.consumers) {
1033
+ * const version = yield* Run.text(
1034
+ * ChildProcess.make(consumer.binPath("my-tool"), ["--version"], { env, extendEnv: false }),
1035
+ * );
1036
+ * console.log(consumer.manager, version.trim());
1037
+ * }
1038
+ * }).pipe(Effect.scoped, Effect.provide(Live));
1039
+ * ```
1040
+ *
1041
+ * @public
1042
+ */
1043
+ export declare class PackedInstall {
1044
+ private constructor();
1045
+ /** `env` without undefined values or the parent manager's context: the environment the installs run under. Reuse it to run the installed bins. */
1046
+ static readonly scrubEnv: (env: Readonly<Record<string, string | undefined>>) => Record<string, string>;
1047
+ /** Pack, then install under every available manager. */
1048
+ static readonly run: (options: PackedInstallOptions) => Effect.Effect<PackedInstallResult, PackedInstallError, import("effect/unstable/process/ChildProcessSpawner").ChildProcessSpawner | FileSystem.FileSystem | Path.Path | import("effect/Scope").Scope | WorkspaceDiscovery>;
1049
+ }
1050
+ //#endregion
1051
+ //#region src/SourceBoundary.d.ts
1052
+ /**
1053
+ * One rule a source file must keep.
1054
+ *
1055
+ * @remarks
1056
+ * `"process"` forbids a read of the global `process`. `"node:process"` forbids
1057
+ * importing `node:process` or `process`. `"stdout-write"` forbids a
1058
+ * `stdout.write` on anything, and a `stdout.end(chunk)`, which writes its
1059
+ * chunk before closing; an `end()` with no chunk is spared. It matches the
1060
+ * name `stdout`, so a renamed receiver (`const { stdout: o } = process`) is
1061
+ * not seen: the `"process"` rule is what backstops it, so waiving `"process"`
1062
+ * for a file waives that backstop too. `"console"` forbids any reference to the
1063
+ * global `console`. `"console-stdout"` forbids a reference to the global
1064
+ * `console` except a member access to one of the methods Node's console
1065
+ * writes to stderr: `error`, `warn`, `trace` and `assert`. A bare or aliased
1066
+ * reference (`const c = console`, `f(console)`, `console[m]`) is still
1067
+ * flagged, since it can reach the stdout methods. Neither console rule flags
1068
+ * core's `Console` service. `{ forbidImports }` forbids an import whose specifier
1069
+ * equals an entry, is a subpath of one, or starts with an entry's text before a
1070
+ * trailing `*` (so `"node:*"` and `"@effect/platform*"` are prefixes).
1071
+ *
1072
+ * `"node:*"` matches only the `node:` spelling: a bare built-in such as `"fs"`
1073
+ * is not caught. To forbid both, spread Node's own list from the test file,
1074
+ * `{ forbidImports: ["node:*", ...builtinModules] }` with `builtinModules`
1075
+ * from `node:module`. That also forbids npm packages that share a built-in's
1076
+ * name (`events`, `buffer`, `punycode`). No built-in list ships here: it
1077
+ * would drift with Node releases.
1078
+ *
1079
+ * @public
1080
+ */
1081
+ type BoundaryRule = "process" | "node:process" | "stdout-write" | "console" | "console-stdout" | {
1082
+ readonly forbidImports: ReadonlyArray<string>;
1083
+ };
1084
+ /**
1085
+ * Options for the `process` rule.
1086
+ *
1087
+ * @public
1088
+ */
1089
+ interface ReferenceOptions {
1090
+ /**
1091
+ * Exact tokens exempt from the `process` rule, each starting at the
1092
+ * reference (`"process.env.MY_CONSTANT"`). `process.env.__PACKAGE_VERSION__`
1093
+ * is always exempt: the bundler substitutes it at build time.
1094
+ */
1095
+ readonly ignoreTokens?: ReadonlyArray<string> | undefined;
1096
+ }
1097
+ /**
1098
+ * A snippet with the verdict one rule must reach on it.
1099
+ *
1100
+ * @public
1101
+ */
1102
+ interface BoundaryFixture {
1103
+ /** What the snippet exercises. */
1104
+ readonly name: string;
1105
+ /** The source text. */
1106
+ readonly source: string;
1107
+ /** The rule it is checked against. */
1108
+ readonly rule: BoundaryRule;
1109
+ /** Whether that rule must flag it. */
1110
+ readonly flagged: boolean;
1111
+ }
1112
+ declare const Offence_base: Schema.Class<Offence, Schema.Struct<{
1113
+ /** The file, relative to the scanned root, with `/` separators. */
1114
+ readonly file: Schema.String;
1115
+ /** The 1-based line. */
1116
+ readonly line: Schema.Number;
1117
+ /** The 1-based column, in UTF-16 code units. */
1118
+ readonly column: Schema.Number;
1119
+ /** The rule broken. */
1120
+ readonly rule: Schema.Literals<readonly ["process", "node:process", "stdout-write", "console", "console-stdout", "forbidImports"]>;
1121
+ /** What matched: the identifier, the call, or the import specifier. */
1122
+ readonly detail: Schema.String;
1123
+ }>, {}>;
1124
+ /**
1125
+ * One place a source file breaks a {@link BoundaryRule}.
1126
+ *
1127
+ * @public
1128
+ */
1129
+ export declare class Offence extends Offence_base {
1130
+ /** `file:line:column rule detail`, the form an assertion message reads best in. */
1131
+ get label(): string;
1132
+ }
1133
+ /**
1134
+ * The rule an {@link Offence} names: every string {@link BoundaryRule}, plus
1135
+ * `"forbidImports"` for any `{ forbidImports }` rule. These are the keys of
1136
+ * {@link ScanOptions.allowRules}.
1137
+ *
1138
+ * @public
1139
+ */
1140
+ type OffenceRule = Offence["rule"];
1141
+ declare const SourceScan_base: Schema.Class<SourceScan, Schema.Struct<{
1142
+ /** Every source file visited, relative to the root with `/` separators, sorted. Assert it is non-empty. */
1143
+ readonly files: Schema.$Array<Schema.String>;
1144
+ /** The visited files an `allow` glob exempted from every rule. */
1145
+ readonly allowed: Schema.$Array<Schema.String>;
1146
+ /** Every offence, sorted by file, then line, then column. */
1147
+ readonly offences: Schema.$Array<typeof Offence>;
1148
+ /**
1149
+ * Every offence an `allowRules` glob waived, sorted like `offences`. Assert
1150
+ * it is exactly what you meant to waive: a waiver that no longer waives
1151
+ * anything, or waives more than intended, shows up here.
1152
+ */
1153
+ readonly waived: Schema.$Array<typeof Offence>;
1154
+ }>, {}>;
1155
+ /**
1156
+ * What a scan read and found.
1157
+ *
1158
+ * @public
1159
+ */
1160
+ export declare class SourceScan extends SourceScan_base {
1161
+ /** One `file:line:column rule detail` label per offence: `[]` means clean. */
1162
+ get violations(): ReadonlyArray<string>;
1163
+ }
1164
+ /**
1165
+ * Options for {@link SourceBoundary.scan}.
1166
+ *
1167
+ * @public
1168
+ */
1169
+ interface ScanOptions extends ReferenceOptions {
1170
+ /** The directory to scan. */
1171
+ readonly root: string;
1172
+ /** The rules every non-allowed file must keep. */
1173
+ readonly rules: ReadonlyArray<BoundaryRule>;
1174
+ /** Globs, relative to `root` with `/` separators, naming files exempt from every rule. */
1175
+ readonly allow?: ReadonlyArray<string> | undefined;
1176
+ /**
1177
+ * Globs per rule, matched like `allow`, naming files exempt from that one
1178
+ * rule. A matching file is still checked against every other rule, and
1179
+ * each offence a glob waives is reported in `SourceScan.waived`
1180
+ * rather than dropped. The `"forbidImports"` key covers every
1181
+ * `{ forbidImports }` rule. A file `allow` matches is exempt from every
1182
+ * rule, so nothing in it is waived.
1183
+ */
1184
+ readonly allowRules?: { readonly [R in OffenceRule]?: ReadonlyArray<string> | undefined; } | undefined;
1185
+ /**
1186
+ * File extensions to scan. Defaults to `.ts`, `.mts`, `.cts`, `.js`,
1187
+ * `.mjs` and `.cjs`; declaration files are always skipped.
1188
+ */
1189
+ readonly extensions?: ReadonlyArray<string> | undefined;
1190
+ }
1191
+ /**
1192
+ * Source-text boundary checks: which files read `process`, import a forbidden
1193
+ * module, or write to stdout or the console.
1194
+ *
1195
+ * @remarks
1196
+ * Every check runs over one lexer pass that separates code from comments,
1197
+ * strings, template text and regex bodies, so `process` in prose, in a string
1198
+ * or in an embedded script's template text never counts, and a `/*` inside a
1199
+ * string never hides the code behind it. A `/` after an operand divides (an
1200
+ * identifier, `)`, `]`, a postfix `++`/`--` or a non-null `!`), while one after
1201
+ * an operator, a keyword or the `)` of an `if`/`while`/`for`/`with` condition
1202
+ * opens a regex. An object-literal key or type member named `process`
1203
+ * (`{ process: 1 }`) is not a read.
1204
+ *
1205
+ * It is a lexer, not a type checker, and these misses are known:
1206
+ *
1207
+ * - there is no scope analysis, so ANY local binding named `process` or
1208
+ * `console` is flagged like the global: a parameter
1209
+ * (`(process: Handle) => process.kill()`), a variable, a label, or an
1210
+ * unannotated class field (`process = 1`). An annotated class field
1211
+ * (`process: T`) reads as a type member and is spared. Prefer renaming the
1212
+ * binding; otherwise waive that one rule for the file with an
1213
+ * `allowRules` glob, which leaves every other rule in force and reports
1214
+ * what it waived. An `allow` glob exempts the file from every rule;
1215
+ *
1216
+ * - a computed access through a string key (`globalThis["process"]`) and a
1217
+ * destructuring of a global (`const { process: p } = globalThis`) are not seen;
1218
+ *
1219
+ * - a regex literal directly after a block-closing `}` reads as a division, so
1220
+ * a quote or `/*` inside it can hide the code after it;
1221
+ *
1222
+ * - JSX text reads as code;
1223
+ *
1224
+ * - `forbidImports: ["node:*"]` does not catch a bare built-in such as
1225
+ * `"fs"` (see {@link BoundaryRule}).
1226
+ *
1227
+ * Assert on {@link SourceBoundary.verifyFixtures} beside your own scan: it
1228
+ * proves the scanner you are trusting still flags what it must and spares
1229
+ * what it must.
1230
+ *
1231
+ * @example
1232
+ * ```ts
1233
+ * import { SourceBoundary } from "@effected/workspaces/testing";
1234
+ *
1235
+ * const offences = SourceBoundary.check("src/a.ts", "const { env } = process;", ["process"]);
1236
+ * const clean = SourceBoundary.verifyFixtures().length === 0;
1237
+ * console.log(offences.map((offence) => offence.label), clean);
1238
+ * // => [ 'src/a.ts:1:17 process process' ] true
1239
+ * ```
1240
+ *
1241
+ * @public
1242
+ */
1243
+ export declare class SourceBoundary {
1244
+ private constructor();
1245
+ /** `text` with every comment blanked to spaces; strings, templates and regexes untouched, length and line breaks kept. */
1246
+ static readonly stripComments: (text: string) => string;
1247
+ /** Whether `text` reads the global `process` in code (see {@link BoundaryRule}). */
1248
+ static readonly referencesProcess: (text: string, options?: ReferenceOptions) => boolean;
1249
+ /** Every module specifier `text` imports: static, side-effect, re-export, type-only, `import(...)` with a literal, and `require(...)`. */
1250
+ static readonly importSpecifiers: (text: string) => ReadonlyArray<string>;
1251
+ /** Whether `text` imports the Node built-in `module`, in either its `node:` or bare form. */
1252
+ static readonly importsNode: (text: string, module: string) => boolean;
1253
+ /** Every place `text` breaks one of `rules`, in source order, attributed to `file`. */
1254
+ static readonly check: (file: string, text: string, rules: ReadonlyArray<BoundaryRule>, options?: ReferenceOptions) => ReadonlyArray<Offence>;
1255
+ /**
1256
+ * Check every source file under `root` against `rules`.
1257
+ *
1258
+ * @remarks
1259
+ * Walks with an explicit stack, visiting each real directory once (via
1260
+ * `realPath`), so a symlink loop terminates and a linked directory is not
1261
+ * scanned twice. `node_modules` is never entered. Paths come back relative
1262
+ * and `/`-separated whatever the platform's separator, and that is also
1263
+ * what `allow` globs match against. A missing root fails; it never scans
1264
+ * nothing. A dangling symlink under the root is skipped, having nothing to
1265
+ * scan; any other entry that cannot be read fails the scan.
1266
+ *
1267
+ * @example
1268
+ * ```ts
1269
+ * import { NodeServices } from "@effect/platform-node";
1270
+ * import { SourceBoundary } from "@effected/workspaces/testing";
1271
+ * import { Effect } from "effect";
1272
+ *
1273
+ * const scan = SourceBoundary.scan({
1274
+ * root: "/repo/packages/engine/src",
1275
+ * rules: ["process", "node:process", { forbidImports: ["node:*", "@effect/platform*"] }],
1276
+ * }).pipe(Effect.provide(NodeServices.layer));
1277
+ * ```
1278
+ */
1279
+ static readonly scan: (options: ScanOptions) => Effect.Effect<SourceScan, import("@effected/glob").GlobPatternError | import("effect/PlatformError").PlatformError, FileSystem.FileSystem | Path.Path>;
1280
+ /** The shipped positive- and negative-control snippets, at least one of each per rule kind. */
1281
+ static readonly fixtures: ReadonlyArray<BoundaryFixture>;
1282
+ /** The names of shipped fixtures the scanner now gets wrong; `[]` means it still flags and spares what it must. */
1283
+ static readonly verifyFixtures: () => ReadonlyArray<string>;
1284
+ }
1285
+ //#endregion
1286
+ //#region src/WorkspaceLayering.d.ts
1287
+ declare const LayerEdge_base: Schema.Class<LayerEdge, Schema.Struct<{
1288
+ /** The dependent package. */
1289
+ readonly from: Schema.String;
1290
+ /** The package depended on. */
1291
+ readonly to: Schema.String;
1292
+ /** The manifest map that declares it. */
1293
+ readonly field: Schema.Literals<readonly ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]>;
1294
+ }>, {}>;
1295
+ /**
1296
+ * One dependency edge between two workspace packages, in one field.
1297
+ *
1298
+ * @public
1299
+ */
1300
+ export declare class LayerEdge extends LayerEdge_base {
1301
+ /** `from -> to (field)`. */
1302
+ get label(): string;
1303
+ }
1304
+ /**
1305
+ * The input to {@link WorkspaceLayering.check}: package names, and per-field edges between them.
1306
+ *
1307
+ * @public
1308
+ */
1309
+ interface LayeringGraph {
1310
+ /** Every workspace package name, the root included. */
1311
+ readonly names: ReadonlyArray<string>;
1312
+ /** One edge per declaring field. */
1313
+ readonly edges: ReadonlyArray<LayerEdge>;
1314
+ }
1315
+ declare const LayeringReport_base: Schema.Class<LayeringReport, Schema.Struct<{
1316
+ /** Packages the policy declares more than once, or declares and also matches with an unconstrained glob. */
1317
+ readonly duplicates: Schema.$Array<Schema.String>;
1318
+ /** Workspace packages the policy does not classify. */
1319
+ readonly unclassified: Schema.$Array<Schema.String>;
1320
+ /** Edges that break the policy, each with why. */
1321
+ readonly offenders: Schema.$Array<Schema.Struct<{
1322
+ readonly edge: typeof LayerEdge;
1323
+ readonly reason: Schema.Literals<readonly ["upward", "sameLayer", "toolingReachesLayer", "intoUnconstrained", "intoUnclassified"]>;
1324
+ }>>;
1325
+ /** The members of every dependency cycle in the checked fields, or none. */
1326
+ readonly cycle: Schema.Option<Schema.$Array<Schema.String>>;
1327
+ /** Packages the policy names that the workspace does not contain. */
1328
+ readonly missingDeclared: Schema.$Array<Schema.String>;
1329
+ /** Required edges absent from the checked fields. */
1330
+ readonly missingRequiredEdges: Schema.$Array<Schema.String>;
1331
+ /** Edges in the checked fields; `0` is itself a violation. */
1332
+ readonly edgeCount: Schema.Number;
1333
+ }>, {}>;
1334
+ /**
1335
+ * What a layering check found.
1336
+ *
1337
+ * @public
1338
+ */
1339
+ export declare class LayeringReport extends LayeringReport_base {
1340
+ /** One line per violation: `[]` means the graph honours the policy and the check was not vacuous. */
1341
+ get violations(): ReadonlyArray<string>;
1342
+ }
1343
+ /**
1344
+ * Holds a workspace's package graph to a committed {@link LayerPolicy}.
1345
+ *
1346
+ * @remarks
1347
+ * `check` is pure, so positive-control fixture graphs need no filesystem.
1348
+ * `edgesOf` recomputes one edge per declaring field, because
1349
+ * `DependencyGraph` merges the four fields into one adjacency and a policy
1350
+ * may check only some of them. An edge exists wherever a dependency NAME is
1351
+ * a workspace package, whatever its specifier protocol.
1352
+ *
1353
+ * @example
1354
+ * ```ts
1355
+ * import { NodeServices } from "@effect/platform-node";
1356
+ * import { Workspaces } from "@effected/workspaces";
1357
+ * import { LayerPolicy, WorkspaceLayering } from "@effected/workspaces/testing";
1358
+ * import { Effect, Layer } from "effect";
1359
+ *
1360
+ * const Live = Workspaces.layer({ cwd: "/repo" }).pipe(Layer.provideMerge(NodeServices.layer));
1361
+ *
1362
+ * const violations = Effect.gen(function* () {
1363
+ * const policy = yield* LayerPolicy.load("/repo/layers.json");
1364
+ * const report = yield* WorkspaceLayering.checkWorkspace(policy);
1365
+ * return report.violations;
1366
+ * }).pipe(Effect.provide(Live));
1367
+ * ```
1368
+ *
1369
+ * @public
1370
+ */
1371
+ export declare class WorkspaceLayering {
1372
+ private constructor();
1373
+ /** Check `graph` against `policy`, reading only the policy's fields. Pure. */
1374
+ static readonly check: (graph: LayeringGraph, policy: LayerPolicy) => LayeringReport;
1375
+ /** One edge per declaring field between workspace packages; self-edges dropped. */
1376
+ static readonly edgesOf: (packages: ReadonlyArray<WorkspacePackage>) => ReadonlyArray<LayerEdge>;
1377
+ /** Discover the workspace and check it against `policy`. */
1378
+ static readonly checkWorkspace: (policy: LayerPolicy) => Effect.Effect<LayeringReport, WorkspaceDiscoveryFailure, WorkspaceDiscovery>;
1379
+ }
1380
+ //#endregion
1381
+ export type { BoundaryFixture, BoundaryRule, DependencyDiff, FindWorkspaceRootOptions, LayeringGraph, OffenceRule, PackSource, PackedInstallOptions, ReferenceOptions, ScanOptions, WorkspaceDiscoveryFailure, WorkspaceDiscoveryOptions, WorkspaceDiscoveryShape, WorkspaceLookupFailure, WorkspaceRootShape };
1382
+ //# sourceMappingURL=testing.d.ts.map