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