import { Git, GitCommandError, NotARepositoryError, UnknownRefError } from "@effected/git"; import { Context, Effect, FileSystem, Layer, Option, Path, Schema } from "effect"; import { CatalogAssemblyError, CatalogResolver, DependencyResolutionError, Manifest, PartialReleaseAgeGate, ReleaseAgeGate, UnresolvedDependencyError, WorkspaceResolver } from "@effected/npm"; import { GlobPattern } from "@effected/glob"; import { Lockfile, LockfileFramingError, LockfileIntegrity, LockfileParseError, ResolvedPackage, WorkspaceManifest } from "@effected/lockfiles"; import { Package } from "@effected/package-json"; import { ChildProcessSpawner } from "effect/unstable/process"; import { LocalExec } from "@effected/commands"; //#region src/WorkspacePackage.d.ts declare const PublishConfig_base: Schema.Class>; /** The registry to publish to. */ readonly registry: Schema.optionalKey; /** A subdirectory to publish instead of the package root. */ readonly directory: Schema.optionalKey; /** * Whether workspace links point into `directory` during local development — * pnpm symlinks the publish directory instead of the package root, so * siblings resolve the built artifact they would install from the registry. * Meaningful only alongside `directory`. */ readonly linkDirectory: Schema.optionalKey; /** The dist-tag to publish under. */ readonly tag: Schema.optionalKey; }>, {}>; /** * The `publishConfig` fields workspace tooling reads. * * @remarks * Deliberately narrow. `@effected/package-json` models `publishConfig` as an * open `Record` for round-trip fidelity, which preserves every * key but types none of them; this is the typed projection of the handful that * decide *where, whether and as what* a package publishes. Unknown keys are * ignored, not rejected. * * @public */ declare class PublishConfig extends PublishConfig_base {} /** * The result of comparing two {@link WorkspacePackage} dependency snapshots. * * @remarks * Comparison runs across all four dependency kinds combined, so a dependency * that moves between kinds at the same version does not appear in the diff. * * @public */ interface DependencyDiff { /** Present in the receiver, absent from the other. */ readonly added: Record; /** Present in the other, absent from the receiver. */ readonly removed: Record; /** Present in both at different specifiers. */ readonly changed: Record; } declare const WorkspaceManifestError_base: Schema.Class; /** The originating failure, preserved rather than flattened to a string. */ readonly cause: Schema.Defect; }>, import("effect/Cause").YieldableError>; /** * Raised when a workspace member's `package.json` cannot be read or decoded * into the strict `@effected/package-json` `Package` model. * * @remarks * Discovery itself never raises this — it uses the tolerant projection. Only * `WorkspacePackage.manifest` does, so opting into the strict model is an * explicit, individually recoverable step. * * @public */ declare class WorkspaceManifestError extends WorkspaceManifestError_base { /** Renders the path and failure kind into a one-line message. */ get message(): string; } declare const WorkspacePackage_base: Schema.Class>; /** Production dependencies. */ readonly dependencies: Schema.withConstructorDefault, never>>; /** Development dependencies. */ readonly devDependencies: Schema.withConstructorDefault, never>>; /** Peer dependencies. */ readonly peerDependencies: Schema.withConstructorDefault, never>>; /** Optional dependencies. */ readonly optionalDependencies: Schema.withConstructorDefault, never>>; /** The `publishConfig` block, when present. */ readonly publishConfig: Schema.optionalKey; /** * The package's `package.json` as read — tolerant access to every field * outside the typed discovery slice (`scripts`, `exports`, …) without a * second file read. * * @remarks * Values are `unknown` and exactly what discovery parsed; nothing here is * validated beyond being a record. For the strict typed model use * `manifest()`, which deliberately **re-reads** the file — a point-in-time * refresh this captured record cannot provide. Defaults to `{}` for * construction sites and previously-serialized values that predate the * field. */ readonly manifestRecord: Schema.withConstructorDefault, never>>; }>, {}>; /** * A single package inside a workspace: the discovery-relevant slice of its * `package.json` plus its filesystem location. * * @remarks * Produced by `WorkspaceDiscovery` for every directory the `packages:` patterns * enumerate. The root package is always present with `relativePath` `"."`. * * @example * ```ts * import { WorkspacePackage } from "@effected/workspaces"; * * const pkg = WorkspacePackage.make({ * name: "@my-org/utils", * version: "1.0.0", * path: "/repo/packages/utils", * packageJsonPath: "/repo/packages/utils/package.json", * relativePath: "packages/utils", * workspaceRoot: "/repo", * }); * * pkg.isRootWorkspace; // false * pkg.unscopedName; // "utils" * pkg.workspaceRoot; // "/repo" * ``` * * @public */ declare class WorkspacePackage extends WorkspacePackage_base { /** Whether this is the workspace root package. */ get isRootWorkspace(): boolean; /** Whether the package is publishable in principle (not marked private). */ get isPublic(): boolean; /** The npm scope (`@org`), or `Option.none()` for an unscoped name. */ get scope(): Option.Option; /** The name with any scope stripped. */ get unscopedName(): string; /** * Every dependency, merged across the four kinds. * * @remarks * Precedence on a name declared in several kinds runs * `dependencies` \> `devDependencies` \> `peerDependencies` \> * `optionalDependencies`. */ get allDependencies(): Record; /** Whether `name` is a production dependency. */ hasDependency(name: string): boolean; /** Whether `name` is a development dependency. */ hasDevDependency(name: string): boolean; /** Whether `name` is a peer dependency. */ hasPeerDependency(name: string): boolean; /** Whether `name` is an optional dependency. */ hasOptionalDependency(name: string): boolean; /** Whether `name` appears in any of the four dependency kinds. */ hasAnyDependencyOn(name: string): boolean; /** The declared specifier for `name`, searched across all four kinds. */ dependencyVersion(name: string): Option.Option; /** * Whether any dependency name matches `pattern` — the `minimatch` runtime * dependency's one call site, now over `@effected/glob`'s vendored engine. * * @remarks * A `GlobPattern` is total and free to test. A `string` is compiled on every * call and an **uncompilable** literal throws: a glob written into a call * site is developer wiring, not untrusted input, so it belongs in the defect * channel rather than widening the typed channel every caller must branch * on. Compile once with `GlobPattern.compile` and pass the result when * testing many packages. * * @param pattern - A compiled pattern, or a source string to compile. */ matchesDependency(pattern: GlobPattern | string): boolean; /** Compare this package's dependencies against `other`'s. */ dependencyDiff(other: WorkspacePackage): DependencyDiff; /** * Project to `@effected/lockfiles`' `WorkspaceManifest` — the input shape of * `LockfileIntegrity.compare`. Total. */ toWorkspaceManifest(): WorkspaceManifest; /** * Read and decode this package's `package.json` into the strict * `@effected/package-json` `Package` model — the bridge from the * tolerant discovery projection to the fully typed manifest. */ static readonly manifest: (self: WorkspacePackage) => Effect.Effect; /** Instance form of `WorkspacePackage.manifest`. */ manifest(): Effect.Effect; } //#endregion //#region src/WorkspaceRoot.d.ts /** * The marker filenames {@link WorkspaceRoot} probes for, in priority order. * * @public */ declare const WORKSPACE_MARKERS: ReadonlyArray; /** * Options for {@link WorkspaceRoot}'s `find`. * * @remarks * Both bounds are passed straight through to `@effected/walker`'s * `Walker.ascend`; this package does not re-decide either. * * @public */ interface FindWorkspaceRootOptions { /** * A ceiling directory. The ascent stops after probing it, so an unmarked * `stopAt` fails typed as {@link WorkspaceRootNotFoundError} rather than * silently escaping into an enclosing repository. * * @remarks * Resolved to an absolute path before comparison, exactly as `cwd` is — a * relative or non-normalized ceiling that never string-matched an ancestor * would reintroduce the unbounded ascent it was passed to prevent. */ readonly stopAt?: string; /** * Hard cap on the number of directories probed. * * @remarks * A non-integer or non-positive value is a **defect**, not a typed failure — * it is developer wiring, and walker's guard raises it. * * @defaultValue 256 */ readonly maxDepth?: number; } declare const WorkspaceRootNotFoundError_base: Schema.Class; /** * The resolved ceiling the ascent was bounded by, when one was supplied. * * @remarks * Absent means the ascent ran to the filesystem root. Its presence is what * lets a caller tell "there is no workspace root anywhere above me" from * "there is none below the ceiling I set" — two failures that otherwise * render identically. */ readonly stopAt: Schema.optionalKey; }>, import("effect/Cause").YieldableError>; /** * Raised when no workspace root can be found by ascending from a directory. * * @remarks * `markers` records what was probed, so the failure names the contract rather * than paraphrasing it in prose. * * @public */ declare class WorkspaceRootNotFoundError extends WorkspaceRootNotFoundError_base { /** Renders the search path, probed markers and any ceiling into a one-line message. */ get message(): string; } /** * The {@link WorkspaceRoot} service contract. * * @remarks * Named so a consumer can type its own double — or a `Layer.succeed` — against * the contract rather than re-deriving it, exactly as `WorkspaceDiscoveryShape` * does. Prefer {@link WorkspaceRoot.layerTest} to hand-rolling one. * * @public */ interface WorkspaceRootShape { /** * The nearest workspace root at or above `cwd`. * * @param cwd - The directory to start the ascent from; resolved to an * absolute path first. * @param options - Optional ascent bounds. Unbounded by default, which * walks to the filesystem root and can therefore resolve to an enclosing * repository's root; pass `stopAt` when the caller knows the ceiling. */ readonly find: (cwd: string, options?: FindWorkspaceRootOptions) => Effect.Effect; } declare const WorkspaceRoot_base: Context.ServiceClass; /** * Locates the workspace root by ascending from a starting directory. * * @example * ```ts * import { WorkspaceRoot } from "@effected/workspaces"; * import { Effect } from "effect"; * * const program = Effect.gen(function* () { * const root = yield* WorkspaceRoot; * return yield* root.find("/repo/packages/utils/src"); * }); * ``` * * @example * Bounded: an unmarked fixture directory fails typed instead of escaping into * the enclosing repository. * * ```ts * import { WorkspaceRoot } from "@effected/workspaces"; * import { Effect } from "effect"; * * const program = Effect.gen(function* () { * const roots = yield* WorkspaceRoot; * return yield* roots.find("/tmp/fixture/packages/a", { stopAt: "/tmp/fixture" }); * }); * ``` * * @public */ declare class WorkspaceRoot extends WorkspaceRoot_base { /** Builds the service over core `FileSystem` and `Path`. */ static readonly make: Effect.Effect; /** The live layer. */ static readonly layer: Layer.Layer; /** * A test double resolving every `find` to `root`, with no filesystem. * * @remarks * The nine-copies-of-a-four-line-mock case — a `Layer.succeed` over a `find` * that ignores its arguments and succeeds with a fixed root is what consumers * were writing by hand. The difference is that this double **honours * `stopAt`**: a hand-rolled `find` that ignores the ceiling makes a bounded * call pass under test and fail against the live service, which is the * failure mode the option exists to catch. A `root` above the ceiling fails * here exactly as it would live, with the same * {@link WorkspaceRootNotFoundError}. * * The ceiling is `path.resolve`d through the injected `Path` service before * the comparison, exactly as the live `make` path does — so a `stopAt` * carrying `..` segments bounds the double identically to the live service, * not by raw string. This is why `makeTest` yields an `Effect` requiring * `Path`: it captures the service once at construction, the same shape as * `make`. Consumers reach for {@link WorkspaceRoot.layerTest}, which provides * `Path.layer` internally, so the requirement never surfaces at their call * site. * * `maxDepth` is deliberately NOT modelled: the double does not walk, so it has * no depth to cap, and pretending otherwise would encode a fiction. A suite * exercising the depth guard wants the live service over a fixture tree. * * @param root - The root every unbounded `find` resolves to. */ static readonly makeTest: (root: string) => Effect.Effect; /** * The test layer: {@link WorkspaceRoot.makeTest} with `Path.layer` provided. * * @remarks * `makeTest` requires `Path` to normalize the `stopAt` ceiling; this layer * supplies core's `Path.layer` internally, so the requirement never reaches a * consumer — the published type stays `Layer.Layer`. * * A parameterized layer factory mints a **fresh reference per call**, and * layers memoize by reference — bind the result to a `const` and reuse it * rather than calling `layerTest(...)` at each composition site. * * Pair it with `WorkspaceDiscovery.layerTest` to stand up the whole discovery * path without a filesystem; between them there is nothing left for a * module-level mock of `@effected/workspaces` to do, and a provided layer * keeps the service graph — and its typed errors — intact. * * @example * ```ts * import { WorkspaceDiscovery, WorkspaceRoot } from "@effected/workspaces"; * import { Effect } from "effect"; * * const TestRoot = WorkspaceRoot.layerTest("/repo"); * const TestDiscovery = WorkspaceDiscovery.layerTest({ * listPackages: () => Effect.succeed([]), * }); * // program.pipe(Effect.provide(TestRoot), Effect.provide(TestDiscovery)) * ``` */ static readonly layerTest: (root: string) => Layer.Layer; } //#endregion //#region src/WorkspaceDiscovery.d.ts declare const WorkspaceDiscoveryError_base: Schema.Class; /** The originating failure, if there was one. */ readonly cause: Schema.Defect; }>, import("effect/Cause").YieldableError>; /** * Raised when a workspace member's `package.json` cannot be read, parsed, or * used — it is missing, malformed, or lacks a `name` or `version`. * * @remarks * `kind` is the discriminant a caller branches on; `cause` preserves the * originating failure rather than flattening it into a sentence. * * @public */ declare class WorkspaceDiscoveryError extends WorkspaceDiscoveryError_base { /** Renders the failing file and kind into a one-line message. */ get message(): string; } declare const WorkspacePatternError_base: Schema.Class; /** A short, structured detail — the missing directory, or the bound exceeded. */ readonly detail: Schema.String; }>, import("effect/Cause").YieldableError>; /** * Raised when a `packages:` pattern cannot be enumerated: its base directory is * absent (usually a typo), the descent exceeded its depth cap, or the visit * budget was exhausted. * * @public */ declare class WorkspacePatternError extends WorkspacePatternError_base { /** Renders the pattern and failure kind into a one-line message. */ get message(): string; } declare const PackageNotFoundError_base: Schema.Class; }>, import("effect/Cause").YieldableError>; /** * Raised when a workspace package is requested by a name no member carries. * * @remarks * `available` lists every known member, which is what makes the error * actionable — a typo is obvious next to the list it missed. * * @public */ declare class PackageNotFoundError extends PackageNotFoundError_base { /** Renders the requested name into a one-line message. */ get message(): string; } declare const WorkspaceInfo_base: Schema.Class; }>, {}>; /** * Top-level facts about a workspace: where it is, what manages it, and the * patterns that define its membership. * * @public */ declare class WorkspaceInfo extends WorkspaceInfo_base {} /** * Every failure `WorkspaceDiscovery.getPackage` can surface: the discovery * failures plus a name that matches no member. * * @public */ type WorkspaceLookupFailure = WorkspaceRootNotFoundError | WorkspaceDiscoveryError | WorkspacePatternError | PackageNotFoundError; /** * The error channel of the discovery methods that do not look a package up by * name — everything except `getPackage`. * * @public */ type WorkspaceDiscoveryFailure = Exclude; /** * Options for the {@link WorkspaceDiscovery} layer. * * @public */ interface WorkspaceDiscoveryOptions { /** * The directory the workspace root is resolved from. * * @defaultValue `process.cwd()`, read lazily on first use — so a * `process.chdir` between providing the layer and the first call is * honoured. */ readonly cwd?: string; /** Descent cap for segment-crossing patterns. Defaults to 32. */ readonly maxDepth?: number; } /** * The {@link WorkspaceDiscovery} service shape. * * @public */ interface WorkspaceDiscoveryShape { /** Facts about the resolved workspace. */ readonly info: () => Effect.Effect; /** Every workspace package, root first, then the rest sorted by relative path. */ readonly listPackages: () => Effect.Effect, WorkspaceDiscoveryFailure>; /** The discovered packages keyed by their root-relative importer path. */ readonly importerMap: () => Effect.Effect, WorkspaceDiscoveryFailure>; /** A single package by name. */ readonly getPackage: (name: string) => Effect.Effect; /** The package owning an absolute file path, by longest-prefix match. */ readonly resolveFile: (filePath: string) => Effect.Effect, WorkspaceDiscoveryFailure>; /** The distinct packages owning any of `filePaths`. */ readonly resolveFiles: (filePaths: ReadonlyArray) => Effect.Effect, WorkspaceDiscoveryFailure>; /** * Facts about the workspace containing `directory`, discovered against THAT * root rather than the layer-bound one. */ readonly infoIn: (directory: string) => Effect.Effect; /** * Every package of the workspace containing `directory`, discovered against * THAT root rather than the layer-bound one. * * @remarks * For a **long-lived host serving many roots** — an MCP server or a language * server that resolves one workspace at startup and then answers calls * scoped to a git worktree, a nested repository, or another project * entirely. The layer-bound {@link WorkspaceDiscoveryShape.listPackages} * answers about the root discovered from `options.cwd`, which such a host * has no way to vary per call without building a fresh layer. * * **This re-reads; it does not re-root.** The tempting cheap fix — take the * layer's package list and rewrite each `path` onto the caller's directory — * produces correct-looking paths over the ORIGINAL root's manifests, so a * worktree whose branch adds, removes or renames a package reports the other * branch's membership with no error. Patterns, member manifests, names and * versions all come from beneath `directory`'s own root here. * * `directory` may be the workspace root or anything inside it: the root is * resolved by the same upward walk the layer-bound path uses, and results are * memoized per RESOLVED root, so many directories in one workspace share one * discovery. The memo holds one entry per distinct root for the layer's * lifetime; {@link WorkspaceDiscoveryShape.refresh} drops all of them. * * @param directory - Absolute path to the workspace root, or to any * directory inside it. */ readonly listPackagesIn: (directory: string) => Effect.Effect, WorkspaceDiscoveryFailure>; /** * Drop every memoized discovery — the layer-bound one and each per-root memo * — so the next call re-reads the filesystem. */ readonly refresh: () => Effect.Effect; /** * Drop only the memo for the workspace containing `directory`, leaving the * layer-bound memo and every other root's untouched. * * @remarks * The precise counterpart to {@link WorkspaceDiscoveryShape.refresh} for a * host serving several roots: refreshing one worktree because it changed * should not discard sibling worktrees that did not, which is all `refresh` * can do. * * **Fails typed on a directory in no workspace**, exactly as * {@link WorkspaceDiscoveryShape.listPackagesIn} does for the same input — * the three per-root methods answer a bad path the same way, and a caller * that would rather treat it as a no-op writes `Effect.ignore`. Refreshing a * root that HAS no memo is an ordinary no-op and not an error. * * @param directory - Absolute path to the workspace root, or to any * directory inside it. */ readonly refreshIn: (directory: string) => Effect.Effect; } declare const WorkspaceDiscovery_base: Context.ServiceClass; /** * Discovers the packages of a workspace. * * @remarks * Layer construction is O(1): the root walk, pattern read, enumeration and * per-package decode all happen on the first method call and are memoized for * the lifetime of the layer. A Vitest reporter that builds the layer per call * site and never queries it pays nothing. * * The memo is **success-only**. `Effect.cached` memoizes the first `Exit`, * *including an interrupt* — an init interrupted by an unrelated timeout would * otherwise brick the layer permanently with a cause outside its declared error * channel. A failure or interrupt is therefore retried on the next call, which * is a deliberate behaviour change from the v3 library. * * @example * ```ts * import { WorkspaceDiscovery } from "@effected/workspaces"; * import { Effect } from "effect"; * * const program = Effect.gen(function* () { * const discovery = yield* WorkspaceDiscovery; * const packages = yield* discovery.listPackages(); * return packages.map((p) => p.name); * }); * ``` * * @public */ declare class WorkspaceDiscovery extends WorkspaceDiscovery_base { /** * Builds the service. Root resolution is one explicit concern: `cwd` is an * option here, never an ambient `process.cwd()` read inside a method. */ static readonly make: (options?: WorkspaceDiscoveryOptions) => Effect.Effect; /** * The live layer. * * @remarks * A parameterized layer factory mints a **fresh reference per call**, and * layers memoize by reference — bind the result to a `const` and reuse it * rather than calling `layer(...)` at each composition site. */ static readonly layer: (options?: WorkspaceDiscoveryOptions) => Layer.Layer; /** * An in-memory test double of the service shape, with every method * defaulted so a test stubs only what it exercises. * * @remarks * The defaults model an **empty workspace**, and the derived methods run * over the *effective* `listPackages` — the override when one is supplied — * so stubbing only `listPackages` yields a consistent double: * * - `listPackages` — succeeds with `[]`. * - `importerMap` — derived: the packages keyed by `relativePath`. * - `getPackage` — derived: a name lookup that fails with the service's own * typed {@link PackageNotFoundError} on a miss, exactly as the live * implementation does. * - `resolveFile` / `resolveFiles` — derived: longest-prefix ownership over * `pkg.path`, POSIX-terminated (`"/"`); supply a win32 double explicitly * if your fixture paths are win32. * - `refresh` — a no-op (`Effect.void`); there is nothing memoized to drop. * - `info` — **dies** with an explanatory defect. No honest default exists * (a fabricated root path would leak into consumer path logic), so an * unstubbed `info()` call is a test-wiring mistake and fails loudly as a * defect rather than succeeding with a lie or failing with a dishonest * typed error. A defect is not absorbed by `Effect.catch` or any * typed-error handler — deliberately, so code under test with a * best-effort `catch` cannot make the mandatory stub look optional; the * unstubbed call still fails the test. * * @example * ```ts * import { WorkspaceDiscovery, WorkspacePackage } from "@effected/workspaces"; * import { Effect } from "effect"; * * const double = WorkspaceDiscovery.makeTest({ * listPackages: () => * Effect.succeed([ * WorkspacePackage.make({ * name: "@my-org/utils", * version: "1.0.0", * path: "/repo/packages/utils", * packageJsonPath: "/repo/packages/utils/package.json", * relativePath: "packages/utils", * workspaceRoot: "/repo", * }), * ]), * }); * // `getPackage`, `importerMap`, `resolveFile(s)` now answer consistently. * ``` */ static readonly makeTest: (overrides?: Partial) => WorkspaceDiscoveryShape; /** * The test layer: {@link WorkspaceDiscovery.makeTest} behind * `Layer.succeed`, so a suite provides only the methods it exercises. * * @remarks * A parameterized layer factory mints a **fresh reference per call**, and * layers memoize by reference — bind the result to a `const` and reuse it * rather than calling `layerTest(...)` at each composition site. * * @example * ```ts * import { WorkspaceDiscovery } from "@effected/workspaces"; * import { Effect } from "effect"; * * const TestDiscovery = WorkspaceDiscovery.layerTest({ * listPackages: () => Effect.succeed([]), * }); * // program.pipe(Effect.provide(TestDiscovery)) * ``` */ static readonly layerTest: (overrides?: Partial) => Layer.Layer; /** * The real implementation of `@effected/npm`'s `WorkspaceResolver` contract * — the one `@effected/package-json` declares but cannot fill. * * @remarks * `versionOf` returns `Option.none()` for a name that is not a workspace * member, per the contract's convention; the `DependencyResolutionError` * channel is reserved for a failure of the resolution *mechanism* (an * unfindable root, an unreadable manifest), never an ordinary miss. * * @example * ```ts * import { Package } from "@effected/package-json"; * import { WorkspaceDiscovery } from "@effected/workspaces"; * import { Layer } from "effect"; * * const resolvers = WorkspaceDiscovery.workspaceResolver.pipe( * Layer.provide(WorkspaceDiscovery.layer()), * ); * // `Package.resolve` now resolves `workspace:*` for real. * ``` */ static readonly workspaceResolver: Layer.Layer; } //#endregion //#region src/ChangeDetector.d.ts declare const ChangeDetectionOptions_base: Schema.Class>; /** * The ref to compare to. * * @defaultValue `"HEAD"` */ readonly head: Schema.withConstructorDefault>; /** * Whether to include staged, unstaged and untracked working-tree changes on * top of the committed range. * * @defaultValue `false` */ readonly includeUncommitted: Schema.withConstructorDefault>; }>, {}>; /** * Which git refs to compare, and whether to fold in the working tree. * * @example * ```ts * import { ChangeDetectionOptions } from "@effected/workspaces"; * * ChangeDetectionOptions.make({}); // HEAD~1...HEAD * ChangeDetectionOptions.make({ base: "origin/main" }); // against a branch * ``` * * @public */ declare class ChangeDetectionOptions extends ChangeDetectionOptions_base {} declare const ChangeDetectionError_base: Schema.Class, import("effect/Cause").YieldableError>; /** * Raised when change detection cannot proceed for a reason that is not one of * git's own typed failures — the wrapper for "detection has no ground to stand * on". * * @remarks * A git command that merely *fails* surfaces as one of `@effected/git`'s typed * errors ({@link ChangeDetectionFailure} carries `GitCommandError`, * `NotARepositoryError` and `UnknownRefError`) rather than being flattened into * this wrapper — a caller branches on git's taxonomy directly. * * @public */ declare class ChangeDetectionError extends ChangeDetectionError_base { /** Renders the failed operation into a one-line message. */ get message(): string; } /** * Every failure the change-detection methods can surface. * * @remarks * `@effected/git`'s typed errors surface directly alongside * {@link ChangeDetectionError} and the discovery failures — a git command that * fails is reported as git classified it (`NotARepositoryError` for a * non-repository, `UnknownRefError` for a bad ref, `GitCommandError` * otherwise), not re-wrapped. * * @public */ type ChangeDetectionFailure = ChangeDetectionError | GitCommandError | NotARepositoryError | UnknownRefError | WorkspaceDiscoveryFailure; /** * The {@link ChangeDetector} service shape. * * @public */ interface ChangeDetectorShape { /** The file paths (workspace-root-relative, as git reports them) changed in the range. */ readonly changedFiles: (options?: ChangeDetectionOptions) => Effect.Effect, ChangeDetectionFailure>; /** The workspace packages owning those files. */ readonly changedPackages: (options?: ChangeDetectionOptions) => Effect.Effect, ChangeDetectionFailure>; /** Those packages plus every workspace package that transitively depends on one. */ readonly affectedPackages: (options?: ChangeDetectionOptions) => Effect.Effect, ChangeDetectionFailure>; } declare const ChangeDetector_base: Context.ServiceClass; /** * Detects which workspace packages a git range touches. * * @remarks * Three depths on one service, cheapest first — raw file paths, the packages * owning them, and the transitive blast radius through the dependency graph. * * @example * ```ts * import { ChangeDetectionOptions, ChangeDetector } from "@effected/workspaces"; * import { Effect } from "effect"; * * const program = Effect.gen(function* () { * const detector = yield* ChangeDetector; * const affected = yield* detector.affectedPackages( * ChangeDetectionOptions.make({ base: "origin/main" }), * ); * return affected.map((pkg) => pkg.name); * }); * ``` * * @public */ declare class ChangeDetector extends ChangeDetector_base { /** Builds the service over `Git` and {@link WorkspaceDiscovery}. */ static readonly make: Effect.Effect; /** The live layer. */ static readonly layer: Layer.Layer; } //#endregion //#region src/ConfigDependencyHooks.d.ts /** * pnpm's `peerDependencyRules` block — the suppression policy pnpm applies * **after** computing peer violations, in pnpm's own shape. * * @remarks * The shape is pnpm's rather than ours because that is what comes back off the * threaded config: measured against `@savvy-web/pnpm-plugin-silk@0.27.0`, a * replayed hook returns `{ allowedVersions, ignoreMissing, allowAny }` intact, * needing no reshaping. * * Two keys are **carried but not yet consumed**. `ignoreMissing` and * `allowAny` are separate suppression axes that have not been measured, and an * unmeasured suppression is precisely what produced the false positives this * seam exists to remove — so they travel through the seam and no kit code acts * on them. Only `allowedVersions` is consumed today. * * `allowedVersions` keys come in two spellings in the wild, and both must be * handled: parent-versioned (`"@effect/ai-anthropic@4.0.0-rc.109>effect"`, as * `pnpm:export` materializes them into `pnpm-workspace.yaml`) and unversioned * (`"@effect/vitest>vitest"`, as a config-dependency plugin injects them). * * @public */ interface PeerDependencyRules { /** `parent>peer` → the peer version or range the rule permits. */ readonly allowedVersions: Readonly>; /** Peer names whose absence pnpm does not report. Carried, not consumed. */ readonly ignoreMissing: ReadonlyArray; /** Peer names for which any version is accepted. Carried, not consumed. */ readonly allowAny: ReadonlyArray; } /** * The empty {@link PeerDependencyRules}: every axis present and empty. * * @remarks * Exported so that "I assert this workspace's rules are empty" is **one token** * rather than three hand-written empty axes. That matters where the distinction * is load-bearing — supplying rules asserts they were looked up, while omitting * them asserts nothing — and a caller spelling the object out by hand will * eventually fill two of the three axes and mean the third. * * Frozen, since it is shared. * * @public */ declare const NoPeerDependencyRules: PeerDependencyRules; /** * The result of replaying a workspace's `configDependencies` hooks: the catalogs * the hooks yield, and the release-age gate contribution they leave on the * config (pnpm's `minimumReleaseAge` / `minimumReleaseAgeExclude`). * * @remarks * `releaseAge` is a `PartialReleaseAgeGate` — the age, the exclude list, * both, or neither, depending on what the replayed hooks set. It is deliberately * a *partial* contribution: a consumer folds it into an effective gate with * `ReleaseAgeGate.combine` alongside inline `pnpm-workspace.yaml` values. Hooks * that set no release-age keys contribute an empty gate (`{}`). * * @public */ interface HookInjection { /** The catalogs the replayed hooks yield, as `catalog name → dependency → range`. */ readonly catalogs: Readonly>>>; /** The release-age gate contribution the replayed hooks leave on the config. */ readonly releaseAge: PartialReleaseAgeGate; /** * The **effective** peer-dependency rules: the seeded workspace-file rules * with every replayed hook's contribution threaded over them. * * @remarks * Effective rather than hook-only because the rules are **seeded** into the * threaded config and the hooks merge onto them, exactly as pnpm seeds its * own config and takes back what the hooks return. That is deliberate: a * kit-owned merge function would be a second implementation of a rule this * seam already enforces, and the two would drift the first time pnpm changed * its threading. * * Measured caveat, and **not a bug to fix**: this repo's plugin *merges* * onto the seeded rules; another plugin could overwrite them. Under seeding * that is pnpm's own behaviour reproduced — a hook that overwrites * overwrites for pnpm too — so do not "repair" it into a merger. */ readonly peerDependencyRules: PeerDependencyRules; } /** * The {@link ConfigDependencyHooks} service shape. * * @remarks * `inject` is given the workspace root, the manifest's `configDependencies` * (name → version+integrity), and the inline-catalog seed as a plain * `catalog name → dependency name → range` record, and produces a * {@link HookInjection}: the catalogs the replayed hooks yield **and** the * release-age gate contribution they leave on the config. The default (no-op) * implementation returns the seed catalogs unchanged, contributes an empty * release-age gate, and loads nothing. * * @public */ interface ConfigDependencyHooksShape { /** * Replay each config dependency's `updateConfig` hook over `seed`, in * declaration order, and return both the resulting catalogs and the * release-age gate contribution the hooks leave behind. * * @remarks * The hooks are replayed once over a single threaded config object, exactly * as pnpm does — so catalogs and the release-age keys * (`minimumReleaseAge` / `minimumReleaseAgeExclude`) are both read off that * one final object, and the config-dependency code executes only once. When * two hooks both set a release-age key the **later hook wins** (it rewrites * the threaded value); a hook that returns a malformed value for a key leaves * the prior threaded value in place (tolerant threading, matching the catalog * slice). A hook failing to load or replay fails typed with a * `hooks`-source `CatalogAssemblyError`, never a silent skip. * * @param root - The workspace root; config dependencies resolve under * `/node_modules/.pnpm-config/`. * @param configDependencies - The `configDependencies` map (name → * version+integrity) declared in `pnpm-workspace.yaml`. * @param seed - The inline catalogs, as `catalog name → dependency → range`. * @param rules - The workspace file's `peerDependencyRules`, seeded into the * threaded config so hooks merge onto them rather than replacing them. * Omitted means "the workspace file declares none", which is different * from "nobody looked" — the caller owns that distinction. */ readonly inject: (root: string, configDependencies: Readonly>, seed: Readonly>>>, rules?: PeerDependencyRules) => Effect.Effect; } declare const ConfigDependencyHooks_base: Context.ServiceClass; /** * Replays a workspace's `configDependencies` `updateConfig` hooks over the inline * catalogs — the opt-in seam that lets hook-injected catalogs participate in * assembly. * * @remarks * A contract-only service: it declares the shape and ships two layers, never a * baked-in default. {@link ConfigDependencyHooks.layerNoop} executes no * config-dependency code (it returns the seed untouched) and is what the default * {@link WorkspaceCatalogs} layer wires; {@link ConfigDependencyHooks.layerLive} * dynamically imports each `pnpmfile.cjs` and replays it, and is wired only by the * explicit `WorkspaceCatalogs.layerWithConfigDependencies` opt-in. * * @public */ declare class ConfigDependencyHooks extends ConfigDependencyHooks_base { /** * The no-op layer: `inject` returns the seed unchanged and never touches a * config dependency. The default {@link WorkspaceCatalogs} layer wires this, so * the default catalog path provably executes no config-dependency code. */ static readonly layerNoop: Layer.Layer; /** * The live layer: dynamically imports each config dependency's `pnpmfile.cjs` * (in process, no subprocess) and replays its `updateConfig` hook over the * seed, in declaration order. A dependency without a `pnpmfile.cjs` contributes * nothing; a dependency whose file fails to load or replay fails typed with a * `hooks`-source `CatalogAssemblyError`, never a silent skip. * * @remarks * Runtime-coupled by design, not node-exclusive. The `import()` below loads * **and executes** a config dependency's pnpmfile in-process — code execution, * not IO, so no `FileSystem` / `Path` service abstracts it. The `node:path` and * `node:url` imports (`join`, `pathToFileURL`) exist only to build the URL that * `import()` consumes; node and bun both implement those builtins and dynamic * import, so this layer runs on either runtime. Only ever wired by * `WorkspaceCatalogs.layerWithConfigDependencies`. */ static readonly layerLive: Layer.Layer; /** * The subprocess layer: replays each config dependency's pnpmfile in a `node` * child process instead of an in-process dynamic `import()`, with identical * typed semantics to {@link ConfigDependencyHooks.layerLive} — the two are * drop-in interchangeable. * * @remarks * `layerLive` computes the `import()` path at runtime, and a bundler (rspack, * for one) compiles a *computed* dynamic import into a context module that * throws `Cannot find module 'file:///…'` at runtime — so in any bundled * consumer, a GitHub Action above all, the in-process replay is unreachable. * This layer keeps every computed load out of the bundle graph: the replay * program is a **static** string constant passed via argv * (`node --input-type=module -e