@effected/workspaces 0.30.4 → 0.32.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.
@@ -3,7 +3,7 @@
3
3
  * Strip pnpm's peer-disambiguation suffix from a recorded importer version.
4
4
  *
5
5
  * pnpm records an importer version as the resolved version followed by the peer
6
- * context it was resolved under — `4.0.0-beta.101(effect@4.0.0-beta.101)(ioredis@5.11.1(supports-color@8.1.1))`.
6
+ * context it was resolved under — `4.0.0(effect@4.0.0)(ioredis@5.11.1(supports-color@8.1.1))`.
7
7
  * `@effected/lockfiles` stores that string verbatim (its `packages:` key parser
8
8
  * strips the suffix; the importer entries deliberately keep it), so the raw
9
9
  * value must never reach a consumer's dependency table — it would render the
package/node-sync.d.ts CHANGED
@@ -92,7 +92,9 @@ interface SyncDirectoryEntry {
92
92
  * satisfies it verbatim:
93
93
  *
94
94
  * ```ts
95
+ * import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
95
96
  * import * as path from "node:path";
97
+ * import type { WorkspacesSyncOptions } from "@effected/workspaces";
96
98
  *
97
99
  * const options: WorkspacesSyncOptions = {
98
100
  * fileSystem: { exists: existsSync, readFile: (p) => readFileSync(p, "utf8"), readDirectory: (p) => readdirSync(p), isDirectory: (p) => statSync(p).isDirectory() },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effected/workspaces",
3
- "version": "0.30.4",
3
+ "version": "0.32.0",
4
4
  "private": false,
5
5
  "description": "Monorepo workspace tooling as Effect services — root discovery, package enumeration, the dependency graph, package-manager detection, pnpm catalog resolution, lockfile IO and git-based change detection.",
6
6
  "keywords": [
@@ -51,23 +51,23 @@
51
51
  "./package.json": "./package.json"
52
52
  },
53
53
  "dependencies": {
54
- "@effected/commands": "^0.10.0",
55
- "@effected/git": "^0.19.0",
56
- "@effected/glob": "^0.9.0",
57
- "@effected/jsonc": "^0.14.0",
58
- "@effected/lockfiles": "^0.14.0",
59
- "@effected/npm": "^0.19.1",
60
- "@effected/package-json": "^0.19.1",
61
- "@effected/semver": "^0.10.1",
62
- "@effected/walker": "^0.14.1",
63
- "@effected/yaml": "^0.18.0",
54
+ "@effected/commands": "^0.11.0",
55
+ "@effected/git": "^0.20.0",
56
+ "@effected/glob": "^0.10.0",
57
+ "@effected/jsonc": "^0.15.1",
58
+ "@effected/lockfiles": "^0.15.0",
59
+ "@effected/npm": "^0.20.0",
60
+ "@effected/package-json": "^0.20.0",
61
+ "@effected/semver": "^0.11.0",
62
+ "@effected/walker": "^0.15.0",
63
+ "@effected/yaml": "^0.19.1",
64
64
  "@pnpm/catalogs.config": "^1100.0.5",
65
65
  "@pnpm/catalogs.protocol-parser": "^1100.0.0",
66
66
  "@pnpm/catalogs.resolver": "^1100.0.0",
67
67
  "@pnpm/catalogs.types": "^1100.0.0"
68
68
  },
69
69
  "peerDependencies": {
70
- "effect": "4.0.0-rc.118"
70
+ "effect": "^4.0.0"
71
71
  },
72
72
  "engines": {
73
73
  "node": ">=24.11.0"
package/testing.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { DependencyField, WorkspaceResolver } from "@effected/npm";
2
- import { Context, Duration, Effect, FileSystem, Layer, Option, Path, Schema } from "effect";
2
+ import { Context, Duration, Effect, FileSystem, Layer, Option, Path, PlatformError, Schema, Stream } from "effect";
3
3
  import { GlobPattern } from "@effected/glob";
4
4
  import { WorkspaceManifest } from "@effected/lockfiles";
5
5
  import { Package } from "@effected/package-json";
@@ -57,7 +57,7 @@ declare const LayerPolicy_base: Schema.Class<LayerPolicy, Schema.Struct<{
57
57
  * message names every such key. A typo on an optional key would otherwise be
58
58
  * silently dropped: `requiredEdge` would remove the non-vacuity guard and
59
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`.
60
+ * keys of its own, such as a `harness` block, passes them in `allowKeys`.
61
61
  *
62
62
  * @example
63
63
  * ```ts
@@ -136,7 +136,7 @@ interface DependencyDiff {
136
136
  readonly added: Record<string, string>;
137
137
  /** Present in the other, absent from the receiver. */
138
138
  readonly removed: Record<string, string>;
139
- /** Present in both at different specifiers. */
139
+ /** Present in both at different specifiers: `from` is the other package's, `to` the receiver's. */
140
140
  readonly changed: Record<string, {
141
141
  readonly from: string;
142
142
  readonly to: string;
@@ -196,13 +196,9 @@ declare const WorkspacePackage_base: Schema.Class<WorkspacePackage, Schema.Struc
196
196
  * Absolute path to the workspace root this package was discovered under.
197
197
  *
198
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.
199
+ * Carried, not derived: `WorkspaceDiscovery` resolves the root before
200
+ * enumerating, so every package arrives with it and consumers need no
201
+ * per-package root arithmetic over `relativePath`.
206
202
  *
207
203
  * For the root package this equals `path`, and `relativePath` is `"."`.
208
204
  */
@@ -229,8 +225,7 @@ declare const WorkspacePackage_base: Schema.Class<WorkspacePackage, Schema.Struc
229
225
  * validated beyond being a record. For the strict typed model use
230
226
  * `manifest()`, which deliberately **re-reads** the file — a point-in-time
231
227
  * refresh this captured record cannot provide. Defaults to `{}` for
232
- * construction sites and previously-serialized values that predate the
233
- * field.
228
+ * values constructed or decoded without the field.
234
229
  */
235
230
  readonly manifestRecord: Schema.withConstructorDefault<Schema.withDecodingDefaultKey<Schema.$Record<Schema.String, Schema.Unknown>, never>>;
236
231
  }>, {}>;
@@ -293,8 +288,8 @@ export declare class WorkspacePackage extends WorkspacePackage_base {
293
288
  /** The declared specifier for `name`, searched across all four kinds. */
294
289
  dependencyVersion(name: string): Option.Option<string>;
295
290
  /**
296
- * Whether any dependency name matches `pattern` — the `minimatch` runtime
297
- * dependency's one call site, now over `@effected/glob`'s vendored engine.
291
+ * Whether any dependency name (across all four kinds) matches the glob
292
+ * `pattern`, using `@effected/glob`.
298
293
  *
299
294
  * @remarks
300
295
  * A `GlobPattern` is total and free to test. A `string` is compiled on every
@@ -307,7 +302,12 @@ export declare class WorkspacePackage extends WorkspacePackage_base {
307
302
  * @param pattern - A compiled pattern, or a source string to compile.
308
303
  */
309
304
  matchesDependency(pattern: GlobPattern | string): boolean;
310
- /** Compare this package's dependencies against `other`'s. */
305
+ /**
306
+ * Compare this package's dependencies against `other`'s, treating `other` as
307
+ * the baseline: what this package added, removed or re-specified.
308
+ *
309
+ * @param other - The package to compare against.
310
+ */
311
311
  dependencyDiff(other: WorkspacePackage): DependencyDiff;
312
312
  /**
313
313
  * Project to `@effected/lockfiles`' `WorkspaceManifest` — the input shape of
@@ -318,6 +318,10 @@ export declare class WorkspacePackage extends WorkspacePackage_base {
318
318
  * Read and decode this package's `package.json` into the strict
319
319
  * `@effected/package-json` `Package` model — the bridge from the
320
320
  * tolerant discovery projection to the fully typed manifest.
321
+ *
322
+ * @remarks
323
+ * Fails with {@link WorkspaceManifestError} (`kind: "read"` or `"decode"`) and
324
+ * requires core `FileSystem`.
321
325
  */
322
326
  static readonly manifest: (self: WorkspacePackage) => Effect.Effect<Package, WorkspaceManifestError, FileSystem.FileSystem>;
323
327
  /** Instance form of `WorkspacePackage.manifest`. */
@@ -462,12 +466,10 @@ export declare class WorkspaceRoot extends WorkspaceRoot_base {
462
466
  * A test double resolving every `find` to `root`, with no filesystem.
463
467
  *
464
468
  * @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
469
+ * Stands in for a hand-written `Layer.succeed` over a `find` that ignores its
470
+ * arguments. Unlike that, this double **honours `stopAt`**: a `find` that
471
+ * ignores the ceiling makes a bounded call pass under test and fail against
472
+ * the live service, which is the failure mode the option exists to catch. A `root` above the ceiling fails
471
473
  * here exactly as it would live, with the same
472
474
  * {@link WorkspaceRootNotFoundError}.
473
475
  *
@@ -541,7 +543,7 @@ declare const WorkspaceDiscoveryError_base: Schema.Class<WorkspaceDiscoveryError
541
543
  * A manifest with no `version` is NOT a failure: pnpm accepts a version-less
542
544
  * private package and a private monorepo root without one is the ordinary
543
545
  * shape, so the member is discovered with `WorkspacePackage.version` absent.
544
- * The former `missingVersion` kind is retired. Only ABSENCE is tolerated: a
546
+ * Only ABSENCE is tolerated: a
545
547
  * `version` that is present but not a string — or present and `""`, which pnpm
546
548
  * never wrote and which would resolve `workspace:^` to a bare `"^"` — is the
547
549
  * manifest's shape being wrong and reports `invalidShape`.
@@ -650,11 +652,10 @@ interface WorkspaceDiscoveryOptions {
650
652
  * `importerMap`, `getPackage`, `resolveFile`, `resolveFiles`). The per-call
651
653
  * `infoIn`, `listPackagesIn` and `refreshIn` resolve from an arbitrary
652
654
  * directory the caller names, which a single layer-level ceiling cannot
653
- * sensibly bound, so they ascend unbounded as before.
655
+ * sensibly bound, so they ascend unbounded.
654
656
  *
655
657
  * `LockfileReader`, `WorkspaceCatalogs` and `WorkspaceSnapshots` each
656
- * take the same option; give them the same
657
- * value when wiring by hand, or let a `Workspaces.*` composite forward one
658
+ * take the same option; give them the same value when wiring by hand, or let a `Workspaces.*` composite forward one
658
659
  * `stopAt` to all of them, so no service adopts a root another refused.
659
660
  *
660
661
  * @defaultValue no ceiling — the ascent runs to the filesystem root.
@@ -751,11 +752,9 @@ declare const WorkspaceDiscovery_base: Context.ServiceClass<WorkspaceDiscovery,
751
752
  * the lifetime of the layer. A Vitest reporter that builds the layer per call
752
753
  * site and never queries it pays nothing.
753
754
  *
754
- * The memo is **success-only**. `Effect.cached` memoizes the first `Exit`,
755
- * *including an interrupt* — an init interrupted by an unrelated timeout would
756
- * otherwise brick the layer permanently with a cause outside its declared error
757
- * channel. A failure or interrupt is therefore retried on the next call, which
758
- * is a deliberate behaviour change from the v3 library.
755
+ * The memo is **success-only**: a failure or interrupt is not cached, so an
756
+ * init interrupted by an unrelated timeout cannot brick the layer, and the next
757
+ * call retries.
759
758
  *
760
759
  * @example
761
760
  * ```ts
@@ -778,12 +777,14 @@ export declare class WorkspaceDiscovery extends WorkspaceDiscovery_base {
778
777
  */
779
778
  static readonly make: (options?: WorkspaceDiscoveryOptions) => Effect.Effect<WorkspaceDiscoveryShape, never, WorkspaceRoot | FileSystem.FileSystem | Path.Path>;
780
779
  /**
781
- * The live layer.
780
+ * The live layer, discovering the workspace from `options.cwd`.
782
781
  *
783
782
  * @remarks
784
783
  * A parameterized layer factory mints a **fresh reference per call**, and
785
784
  * layers memoize by reference — bind the result to a `const` and reuse it
786
785
  * rather than calling `layer(...)` at each composition site.
786
+ *
787
+ * @param options - Root resolution and enumeration bounds.
787
788
  */
788
789
  static readonly layer: (options?: WorkspaceDiscoveryOptions) => Layer.Layer<WorkspaceDiscovery, never, WorkspaceRoot | FileSystem.FileSystem | Path.Path>;
789
790
  /**
@@ -894,12 +895,12 @@ export declare class WorkspaceDiscovery extends WorkspaceDiscovery_base {
894
895
  * @remarks
895
896
  * `{ directory }` runs `npm pack` in that subdirectory of each package, whose
896
897
  * manifest must already be publish-ready. The default,
897
- * `{ directory: "dist/prod/npm/pkg" }`, is the effected bundler's prod npm
898
+ * `{ directory: "dist/prod/npm/pkg" }`, is `@savvy-web/bundler`'s prod npm
898
899
  * output: the same file list a release publishes.
899
900
  *
900
901
  * `"source"` runs `pnpm pack` in the package directory: pnpm honours
901
902
  * `publishConfig.directory` and rewrites `workspace:` and `catalog:`
902
- * specifiers. Two costs. Under the effected bundler `publishConfig.directory`
903
+ * specifiers. Two costs. Under `@savvy-web/bundler`, `publishConfig.directory`
903
904
  * names the DEV build, so source mode proves the dev artifact, not the
904
905
  * published one. And pnpm can rewrite a `workspace:` specifier only in a
905
906
  * workspace that has been `pnpm install`ed: in a never-installed one the pack
@@ -937,10 +938,10 @@ interface PackedInstallClosureOptions {
937
938
  * `.tgz`, with or without a `file:` prefix.
938
939
  *
939
940
  * @remarks
940
- * The dogfood case: a closure member imports a surface of a dependency
941
- * that exists only in a sibling checkout's unreleased build, and the
942
- * registry's copy lacks it. A directory is `npm pack`ed, so it must be
943
- * publish-ready (for an effected-bundled package, its
941
+ * For a closure member that imports a surface of a dependency existing
942
+ * only in a sibling checkout's unreleased build, which the registry's copy
943
+ * lacks. A directory is `npm pack`ed, so it must be
944
+ * publish-ready (for a package built by `@savvy-web/bundler`, its
944
945
  * `dist/prod/npm/pkg`); a `.tgz` is used as it is. Either way the packed
945
946
  * manifest must be named as its key, carry no specifier only a workspace
946
947
  * could resolve, and it joins `PackedInstallResult.tarballs`. Each
@@ -957,9 +958,9 @@ interface PackedInstallClosureOptions {
957
958
  readonly overrides?: Readonly<Record<string, string>> | undefined;
958
959
  /**
959
960
  * Also read the workspace root's `pnpm-workspace.yaml` and take every
960
- * `overrides:` entry of the dogfood link shape,
961
- * `"@scope/name": "file:<dir>"`, as if passed through `overrides`: the
962
- * linked sibling builds the workspace itself installs. Entries that are not `file:`, or
961
+ * `overrides:` entry of the link shape `"@scope/name": "file:<dir>"` as if
962
+ * passed through `overrides`: the linked sibling builds the workspace itself
963
+ * installs. Entries that are not `file:`, or
963
964
  * whose key carries a selector (`a>b`, `a@1`), are left out. A root with
964
965
  * no readable `pnpm-workspace.yaml`, or one that is not YAML, fails
965
966
  * `InvalidOverride`.
@@ -1029,7 +1030,12 @@ interface PackedInstallOptions extends PackedInstallClosureOptions {
1029
1030
  * `false`, so sharing is an explicit choice.
1030
1031
  */
1031
1032
  readonly allowSharedBins?: boolean | undefined;
1032
- /** The environment for every spawn: pass `process.env` from the test file. The parent manager's context is stripped. */
1033
+ /**
1034
+ * The environment for every spawn: pass `process.env` from the test file, the edge that supplies the host
1035
+ * environment. It is taken whole because the parent manager's context is stripped by prefix (`npm_*`,
1036
+ * `pnpm_config_*`, `yarn_*`), which `Config`, reading keys by name, cannot enumerate; read single keys such as
1037
+ * `CI` through `Config`.
1038
+ */
1033
1039
  readonly env: Readonly<Record<string, string | undefined>>;
1034
1040
  /**
1035
1041
  * Extra consumer dependencies, name to range or `file:` tarball spec.
@@ -1106,6 +1112,22 @@ interface BinCommandOptions {
1106
1112
  interface RunBinOptions extends BinCommandOptions {
1107
1113
  /** Ceiling on the run. Defaults to one minute. Expiry fails `BinFailed` with a message naming the bin and this duration. */
1108
1114
  readonly timeout?: Duration.Input | undefined;
1115
+ /**
1116
+ * What to write to the bin's stdin, which is then closed: a string (UTF-8
1117
+ * encoded), bytes, or a byte stream for input that must arrive in pieces,
1118
+ * such as a framed LSP session.
1119
+ *
1120
+ * @remarks
1121
+ * Omitted or `""`, the bin's stdin is the null device: it reads end of
1122
+ * input immediately, never an open pipe, so a bin that reads stdin exits
1123
+ * instead of hanging until the ceiling. Empty bytes and an empty stream
1124
+ * are sent as given: the bin sees an ended, empty input. A bin that answers
1125
+ * only after the input is complete is driven by this option; one that must
1126
+ * be interleaved with its output is driven through
1127
+ * {@link InstalledConsumer.command} or {@link InstalledConsumer.carrierCommand},
1128
+ * which leave stdin open.
1129
+ */
1130
+ readonly stdin?: string | Uint8Array | Stream.Stream<Uint8Array, PlatformError.PlatformError> | undefined;
1109
1131
  }
1110
1132
  /**
1111
1133
  * Which installed package a `node_modules/.bin` symlink resolves into.
@@ -1208,7 +1230,9 @@ export declare class InstalledConsumer extends InstalledConsumer_base {
1208
1230
  * Run the installed bin `name` to completion and collect what it wrote.
1209
1231
  *
1210
1232
  * @remarks
1211
- * Spawns {@link InstalledConsumer.command} with stdin ignored: the bin
1233
+ * Spawns {@link InstalledConsumer.command} with `options.stdin` as its
1234
+ * input (the null device when omitted, so a bin never waits on an open
1235
+ * pipe): the bin
1212
1236
  * from the consumer's directory, under the install's scrubbed environment
1213
1237
  * with `options.env` layered over it after the scrub, and nothing
1214
1238
  * inherited beyond that. A
@@ -1220,7 +1244,7 @@ export declare class InstalledConsumer extends InstalledConsumer_base {
1220
1244
  *
1221
1245
  * @param name - The bin, as named in `node_modules/.bin`.
1222
1246
  * @param args - Its arguments.
1223
- * @param options - Extra environment, working directory and ceiling.
1247
+ * @param options - Extra environment, working directory, ceiling and stdin.
1224
1248
  */
1225
1249
  runBin(name: string, args?: ReadonlyArray<string>, options?: RunBinOptions): Effect.Effect<CommandOutput, PackedInstallError, ChildProcessSpawner.ChildProcessSpawner>;
1226
1250
  /**
@@ -1260,7 +1284,8 @@ export declare class InstalledConsumer extends InstalledConsumer_base {
1260
1284
  * the `node_modules/.bin` slot.
1261
1285
  *
1262
1286
  * @remarks
1263
- * Spawns {@link InstalledConsumer.carrierCommand} with stdin ignored, and
1287
+ * Spawns {@link InstalledConsumer.carrierCommand} with `options.stdin` as
1288
+ * its input (the null device when omitted), and
1264
1289
  * reports as {@link InstalledConsumer.runBin} does: a non-zero exit is a
1265
1290
  * result, and a bin that cannot spawn or outlives `options.timeout` (one
1266
1291
  * minute by default) fails `BinFailed`. Use it beside `runBin` under
@@ -1271,7 +1296,7 @@ export declare class InstalledConsumer extends InstalledConsumer_base {
1271
1296
  *
1272
1297
  * @param name - The bin, as the carrier's `bin` map names it.
1273
1298
  * @param args - Its arguments.
1274
- * @param options - Extra environment, working directory and ceiling.
1299
+ * @param options - Extra environment, working directory, ceiling and stdin.
1275
1300
  */
1276
1301
  runCarrierBin(name: string, args?: ReadonlyArray<string>, options?: RunBinOptions): Effect.Effect<CommandOutput, PackedInstallError, FileSystem.FileSystem | Path.Path | ChildProcessSpawner.ChildProcessSpawner>;
1277
1302
  }
@@ -1322,6 +1347,43 @@ interface PackedInstallBudget {
1322
1347
  */
1323
1348
  readonly perConsumer?: Duration.Input | undefined;
1324
1349
  }
1350
+ /**
1351
+ * Options for {@link PackedInstall.preflight}: the run's closure and pack
1352
+ * source, so the check answers for exactly the run those options describe.
1353
+ *
1354
+ * @public
1355
+ */
1356
+ interface PackedInstallPreflightOptions extends PackedInstallClosureOptions {
1357
+ /** The carrier, as the run's `carrier`. */
1358
+ readonly carrier: string;
1359
+ /** Where the run packs from; defaults as the run's does, to `{ directory: "dist/prod/npm/pkg" }`. */
1360
+ readonly packFrom?: PackSource | undefined;
1361
+ }
1362
+ /**
1363
+ * Whether the pack source a run needs is on disk.
1364
+ *
1365
+ * @public
1366
+ */
1367
+ interface PackedInstallPreflight {
1368
+ /** `true` when every package the run packs has its pack source in place. */
1369
+ readonly ready: boolean;
1370
+ /** The absolute `package.json` paths that do not exist, in pack order; empty exactly when `ready`. */
1371
+ readonly missing: ReadonlyArray<string>;
1372
+ }
1373
+ /**
1374
+ * What a test suite should do about a {@link PackedInstallPreflight}.
1375
+ *
1376
+ * @public
1377
+ */
1378
+ interface PackedInstallGate {
1379
+ /**
1380
+ * `"run"` when the pack source is there; `"skip"` when it is missing and
1381
+ * the run is not under CI; `"fail"` when it is missing under CI.
1382
+ */
1383
+ readonly action: "run" | "skip" | "fail";
1384
+ /** Why, naming the missing paths and the build to run. Empty when `action` is `"run"`. */
1385
+ readonly message: string;
1386
+ }
1325
1387
  /**
1326
1388
  * Prove a carrier's bins reach a consumer that is not part of the workspace,
1327
1389
  * once per available package manager.
@@ -1489,6 +1551,76 @@ export declare class PackedInstall {
1489
1551
  static readonly timeoutBudgetFor: (options: PackedInstallOptions, extra?: {
1490
1552
  readonly perConsumer?: Duration.Input | undefined;
1491
1553
  }) => Effect.Effect<Duration.Duration, PackedInstallError, WorkspaceDiscovery | FileSystem.FileSystem | Path.Path>;
1554
+ /**
1555
+ * Whether the pack source {@link PackedInstall.run} would read is on disk,
1556
+ * before any package manager is spawned.
1557
+ *
1558
+ * @remarks
1559
+ * Plans the closure as the run does (`PackedInstall.closure`'s planner) and
1560
+ * checks the `package.json` of each closure package under `packFrom`
1561
+ * (default `{ directory: "dist/prod/npm/pkg" }`), the same file whose
1562
+ * absence makes the run fail `PackSourceMissing`. Under `packFrom: "source"`
1563
+ * the packer builds nothing of its own, so nothing is checked and the
1564
+ * result is ready. A package a bundler's dev build produced is no
1565
+ * substitute: the prod output exists only after the prod build, which a
1566
+ * test job's usual `build:dev` pre-step does not run. Pair it with
1567
+ * {@link PackedInstall.gate} to skip locally and fail under CI.
1568
+ *
1569
+ * Fails as `closure` does, plus `Io` when a path cannot be inspected.
1570
+ *
1571
+ * @example
1572
+ * ```ts
1573
+ * import { NodeServices } from "@effect/platform-node";
1574
+ * import { Workspaces } from "@effected/workspaces";
1575
+ * import { PackedInstall } from "@effected/workspaces/testing";
1576
+ * import { Effect, Layer } from "effect";
1577
+ *
1578
+ * const Live = Workspaces.layer({ cwd: "/repo" }).pipe(Layer.provideMerge(NodeServices.layer));
1579
+ * const preflight = await Effect.runPromise(
1580
+ * PackedInstall.preflight({ carrier: "my-tool", closure: "auto" }).pipe(Effect.provide(Live)),
1581
+ * );
1582
+ * console.log(preflight.ready, preflight.missing);
1583
+ * ```
1584
+ *
1585
+ * @param options - The run's carrier, closure, overrides and pack source.
1586
+ */
1587
+ static readonly preflight: (options: PackedInstallPreflightOptions) => Effect.Effect<PackedInstallPreflight, PackedInstallError, WorkspaceDiscovery | FileSystem.FileSystem | Path.Path>;
1588
+ /**
1589
+ * Turns a {@link PackedInstallPreflight} into the suite's decision: run,
1590
+ * skip locally, or fail under CI.
1591
+ *
1592
+ * @remarks
1593
+ * `CI` is read through `Config`, so the ambient `ConfigProvider` decides —
1594
+ * the process environment at a test file's top level, and whatever a test
1595
+ * provides in a test; nothing here reads `process`. `CI` counts as set when
1596
+ * it is present and not `""`, `"0"` or `"false"` (case-insensitive), and an
1597
+ * unreadable `CI` counts as unset. Ready is always `"run"`;
1598
+ * missing is `"skip"` off CI and `"fail"` on it, so a CI job that never ran
1599
+ * the prod build cannot pass by silently skipping the proof. The message
1600
+ * names every missing path. The test job must run the prod build
1601
+ * (`build:prod` for a `@savvy-web/bundler` package) before the packed-install
1602
+ * tests: a dev build leaves `dist/prod` absent.
1603
+ *
1604
+ * @example
1605
+ * ```ts
1606
+ * import { assert, describe, it } from "@effect/vitest";
1607
+ * import { PackedInstall } from "@effected/workspaces/testing";
1608
+ * import { Effect } from "effect";
1609
+ *
1610
+ * declare const preflight: { readonly ready: boolean; readonly missing: ReadonlyArray<string> };
1611
+ * const gate = Effect.runSync(PackedInstall.gate(preflight));
1612
+ *
1613
+ * describe.runIf(gate.action === "run")("packed install", () => {
1614
+ * it("installs", () => {});
1615
+ * });
1616
+ * describe.runIf(gate.action === "fail")("packed install prod build", () => {
1617
+ * it("exists", () => assert.fail(gate.message));
1618
+ * });
1619
+ * ```
1620
+ *
1621
+ * @param preflight - What {@link PackedInstall.preflight} answered.
1622
+ */
1623
+ static readonly gate: (preflight: PackedInstallPreflight) => Effect.Effect<PackedInstallGate>;
1492
1624
  /** Pack, then install under every available manager. */
1493
1625
  static readonly run: (options: PackedInstallOptions) => Effect.Effect<PackedInstallResult, PackedInstallError, ChildProcessSpawner.ChildProcessSpawner | FileSystem.FileSystem | Path.Path | import("effect/Scope").Scope | WorkspaceDiscovery>;
1494
1626
  }
@@ -1761,7 +1893,7 @@ export declare class SourceBoundary {
1761
1893
  static readonly scan: (options: ScanOptions) => Effect.Effect<SourceScan, import("@effected/glob").GlobPatternError | import("effect/PlatformError").PlatformError, FileSystem.FileSystem | Path.Path>;
1762
1894
  /** The shipped positive- and negative-control snippets, at least one of each per rule kind. */
1763
1895
  static readonly fixtures: ReadonlyArray<BoundaryFixture>;
1764
- /** The names of shipped fixtures the scanner now gets wrong; `[]` means it still flags and spares what it must. */
1896
+ /** The names of shipped fixtures the scanner gets wrong; `[]` means it still flags and spares what it must. */
1765
1897
  static readonly verifyFixtures: () => ReadonlyArray<string>;
1766
1898
  }
1767
1899
  //#endregion
@@ -1860,5 +1992,5 @@ export declare class WorkspaceLayering {
1860
1992
  static readonly checkWorkspace: (policy: LayerPolicy) => Effect.Effect<LayeringReport, WorkspaceDiscoveryFailure, WorkspaceDiscovery>;
1861
1993
  }
1862
1994
  //#endregion
1863
- export type { BinCommandOptions, BinProvenance, BoundaryFixture, BoundaryRule, DependencyDiff, FindWorkspaceRootOptions, LayeringGraph, OffenceRule, PackSource, PackedInstallBudget, PackedInstallClosureOptions, PackedInstallOptions, ReferenceOptions, RunBinOptions, ScanOptions, WorkspaceDiscoveryFailure, WorkspaceDiscoveryOptions, WorkspaceDiscoveryShape, WorkspaceLookupFailure, WorkspaceRootShape };
1995
+ export type { BinCommandOptions, BinProvenance, BoundaryFixture, BoundaryRule, DependencyDiff, FindWorkspaceRootOptions, LayeringGraph, OffenceRule, PackSource, PackedInstallBudget, PackedInstallClosureOptions, PackedInstallGate, PackedInstallOptions, PackedInstallPreflight, PackedInstallPreflightOptions, ReferenceOptions, RunBinOptions, ScanOptions, WorkspaceDiscoveryFailure, WorkspaceDiscoveryOptions, WorkspaceDiscoveryShape, WorkspaceLookupFailure, WorkspaceRootShape };
1864
1996
  //# sourceMappingURL=testing.d.ts.map