@effected/workspaces 0.28.0-next-20260927071821 → 0.29.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/ConfigDependencyHooks.js +13 -1
- package/LockfileReader.js +7 -2
- package/PeerCheck.js +449 -36
- package/README.md +20 -5
- package/WorkspaceCatalogs.js +2 -1
- package/WorkspaceDiscovery.js +4 -3
- package/WorkspaceSnapshots.js +5 -4
- package/WorkspaceStateSnapshot.js +47 -30
- package/Workspaces.js +6 -2
- package/WorkspacesSync.js +11 -1
- package/index.d.ts +972 -721
- package/internal/configDependencyFetch.js +39 -6
- package/internal/configDependencyResolution.js +1 -0
- package/internal/layerRoot.js +14 -0
- package/package.json +4 -4
- package/testing.d.ts +5 -0
package/index.d.ts
CHANGED
|
@@ -560,6 +560,11 @@ interface WorkspaceDiscoveryOptions {
|
|
|
560
560
|
* directory the caller names, which a single layer-level ceiling cannot
|
|
561
561
|
* sensibly bound, so they ascend unbounded as before.
|
|
562
562
|
*
|
|
563
|
+
* `LockfileReader`, `WorkspaceCatalogs` and `WorkspaceSnapshots` each
|
|
564
|
+
* take the same option; give them the same
|
|
565
|
+
* value when wiring by hand, or let a `Workspaces.*` composite forward one
|
|
566
|
+
* `stopAt` to all of them, so no service adopts a root another refused.
|
|
567
|
+
*
|
|
563
568
|
* @defaultValue no ceiling — the ascent runs to the filesystem root.
|
|
564
569
|
*/
|
|
565
570
|
readonly stopAt?: string | undefined;
|
|
@@ -1252,7 +1257,19 @@ export declare class ConfigDependencyHooks extends ConfigDependencyHooks_base {
|
|
|
1252
1257
|
* with `reason: "integrityUnavailable"` — nothing is fetched in either case
|
|
1253
1258
|
* — and a fetch that fails with `reason: "fetchFailed"`, keeping the
|
|
1254
1259
|
* not-installed remediation. The fetch needs `pnpm` 11 or 12 on `PATH` and
|
|
1255
|
-
* registry access, and is bounded at two minutes.
|
|
1260
|
+
* registry access, and is bounded at two minutes.
|
|
1261
|
+
*
|
|
1262
|
+
* The scratch workspace fetches through the same registries a
|
|
1263
|
+
* `pnpm install` in the workspace would: the workspace root's `.npmrc` (scoped
|
|
1264
|
+
* registries, mirrors, auth) is copied into it as-is, with `${VAR}`
|
|
1265
|
+
* references left for pnpm to expand, never read or logged, and removed
|
|
1266
|
+
* with the scratch; and the root `pnpm-workspace.yaml`'s `registry` and
|
|
1267
|
+
* `registries` keys are carried into the scratch's. That is the CURRENT
|
|
1268
|
+
* checkout's registry config for both sides of a diff — a base ref's
|
|
1269
|
+
* `.npmrc` is not read through git — and relative paths inside the `.npmrc`
|
|
1270
|
+
* (a `cafile=./ca.pem`) resolve against the scratch, not the root.
|
|
1271
|
+
*
|
|
1272
|
+
* An empty `configDependencies`, or one whose dependencies all
|
|
1256
1273
|
* ship no pnpmfile, returns the seed without spawning anything; a `..` path
|
|
1257
1274
|
* segment in a dependency name fails typed **before** any spawn; a
|
|
1258
1275
|
* load failure in the child — a syntax error, a throwing top level, an
|
|
@@ -2054,6 +2071,21 @@ interface LockfileReaderOptions {
|
|
|
2054
2071
|
* @defaultValue `process.cwd()`, read lazily on first use.
|
|
2055
2072
|
*/
|
|
2056
2073
|
readonly cwd?: string;
|
|
2074
|
+
/**
|
|
2075
|
+
* A ceiling for the root ascent from `cwd`, passed straight through to the
|
|
2076
|
+
* `stopAt` of {@link WorkspaceRoot}'s `find`.
|
|
2077
|
+
*
|
|
2078
|
+
* @remarks
|
|
2079
|
+
* Inclusive, and resolved to an absolute path exactly as `cwd` is. When no
|
|
2080
|
+
* root is found at or below the ceiling, `read` fails with
|
|
2081
|
+
* {@link WorkspaceRootNotFoundError} carrying the resolved `stopAt` rather
|
|
2082
|
+
* than adopting an enclosing directory's workspace. Pass the same value as
|
|
2083
|
+
* {@link WorkspaceDiscoveryOptions.stopAt} so every service agrees on the
|
|
2084
|
+
* root; the `Workspaces.*` composites forward one `stopAt` to all of them.
|
|
2085
|
+
*
|
|
2086
|
+
* @defaultValue no ceiling — the ascent runs to the filesystem root.
|
|
2087
|
+
*/
|
|
2088
|
+
readonly stopAt?: string | undefined;
|
|
2057
2089
|
}
|
|
2058
2090
|
declare const LockfileReader_base: Context.ServiceClass<LockfileReader, "@effected/workspaces/LockfileReader", LockfileReaderShape>;
|
|
2059
2091
|
/**
|
|
@@ -2150,181 +2182,675 @@ export declare class LockfileReader extends LockfileReader_base {
|
|
|
2150
2182
|
static readonly layerTest: (overrides?: Partial<LockfileReaderShape>) => Layer.Layer<LockfileReader>;
|
|
2151
2183
|
}
|
|
2152
2184
|
//#endregion
|
|
2153
|
-
//#region src/
|
|
2154
|
-
/**
|
|
2155
|
-
* Why a report is not a complete answer.
|
|
2156
|
-
*
|
|
2157
|
-
* @remarks
|
|
2158
|
-
* Closed at exactly two, by measurement rather than by guess:
|
|
2159
|
-
*
|
|
2160
|
-
* - `"peerRulesNotApplied"` — the effective suppression policy was not applied,
|
|
2161
|
-
* so pnpm's post-hoc suppression could not be replicated and some reported
|
|
2162
|
-
* rows may be ones pnpm hides. **Omitting the option always produces this**,
|
|
2163
|
-
* because "nobody looked" and "I looked and there are none" are different
|
|
2164
|
-
* facts. Supplied rules never produce it: all three axes —
|
|
2165
|
-
* `allowedVersions`, `ignoreMissing` and `allowAny` — are applied with
|
|
2166
|
-
* pnpm's measured semantics.
|
|
2167
|
-
* - `"unresolvedEdge"` — some instance records an edge this model could not
|
|
2168
|
-
* name (`ResolvedPackage.unresolvedEdges`), so a peer that edge satisfies
|
|
2169
|
-
* cannot be verified either way.
|
|
2170
|
-
*
|
|
2171
|
-
* Both mean **fail closed**: a gate should treat an unverified report as "not
|
|
2172
|
-
* proven clean" rather than as a pass.
|
|
2173
|
-
*
|
|
2174
|
-
* @public
|
|
2175
|
-
*/
|
|
2176
|
-
type UnverifiedReason = "peerRulesNotApplied" | "unresolvedEdge";
|
|
2185
|
+
//#region src/WorkspaceCatalogs.d.ts
|
|
2177
2186
|
/**
|
|
2178
|
-
*
|
|
2187
|
+
* Each importer's dependency-name → resolved-version map, keyed by importer path
|
|
2188
|
+
* (`"."` for the root package — the same keys `WorkspaceDiscovery.importerMap()`
|
|
2189
|
+
* uses, and the same value `PackageStateSnapshot.relativePath` carries).
|
|
2179
2190
|
*
|
|
2180
2191
|
* @remarks
|
|
2181
|
-
*
|
|
2182
|
-
*
|
|
2183
|
-
*
|
|
2184
|
-
*
|
|
2185
|
-
*
|
|
2186
|
-
* "unchecked".
|
|
2187
|
-
*
|
|
2188
|
-
* **All three axes of the supplied rules are applied**, each with the
|
|
2189
|
-
* semantics measured against pnpm (see {@link PeerCheck.run}): `allowedVersions`
|
|
2190
|
-
* permits a resolved version by range, `ignoreMissing` hides a required peer
|
|
2191
|
-
* nothing resolved for, and `allowAny` hides a peer that resolved outside its
|
|
2192
|
-
* range. The two list axes are name patterns, never `parent>peer` keys.
|
|
2193
|
-
*
|
|
2194
|
-
* @public
|
|
2195
|
-
*/
|
|
2196
|
-
interface PeerCheckOptions {
|
|
2197
|
-
/** The workspace's effective rules, from `WorkspaceCatalogs.peerDependencyRules()`. */
|
|
2198
|
-
readonly peerDependencyRules?: PeerDependencyRules;
|
|
2199
|
-
}
|
|
2200
|
-
declare const PeerParent_base: Schema.Class<PeerParent, Schema.Struct<{
|
|
2201
|
-
/** The package name. */
|
|
2202
|
-
readonly name: Schema.NonEmptyString;
|
|
2203
|
-
/** The resolved version of that package. */
|
|
2204
|
-
readonly version: Schema.String;
|
|
2205
|
-
}>, {}>;
|
|
2206
|
-
/**
|
|
2207
|
-
* One link in the chain from an importer to the package that declared an
|
|
2208
|
-
* unsatisfied peer.
|
|
2192
|
+
* What a manager's lockfile records as actually installed, as opposed to what a
|
|
2193
|
+
* manifest declares. It answers a `catalog:` specifier no committed catalog
|
|
2194
|
+
* source declares — the shape a pnpm config-dependency pnpmfile hook produces —
|
|
2195
|
+
* which would otherwise resolve to nothing on both sides of a diff and hide a
|
|
2196
|
+
* real version movement.
|
|
2209
2197
|
*
|
|
2210
|
-
*
|
|
2211
|
-
*
|
|
2212
|
-
*
|
|
2198
|
+
* Versions are normalized: pnpm's peer-disambiguation suffix
|
|
2199
|
+
* (`1.2.3(effect@4.0.0)`) is stripped, and `link:` / `file:` entries are omitted
|
|
2200
|
+
* because a filesystem edge is not a version. Only pnpm records importer
|
|
2201
|
+
* versions; bun and npm yield an empty index.
|
|
2213
2202
|
*
|
|
2214
2203
|
* @public
|
|
2215
2204
|
*/
|
|
2216
|
-
|
|
2217
|
-
declare const
|
|
2218
|
-
/**
|
|
2219
|
-
readonly
|
|
2220
|
-
/** The peer dependency's name. */
|
|
2221
|
-
readonly dependency: Schema.NonEmptyString;
|
|
2222
|
-
/** The range the declaring package asked for. */
|
|
2223
|
-
readonly wanted: Schema.String;
|
|
2224
|
-
/** The version that resolved, or `null` when nothing resolved. */
|
|
2225
|
-
readonly found: Schema.NullOr<Schema.String>;
|
|
2226
|
-
/** Whether the declaring package marked the peer optional. */
|
|
2227
|
-
readonly optional: Schema.Boolean;
|
|
2228
|
-
/** The path from the importer to the declaring package. */
|
|
2229
|
-
readonly parents: Schema.$Array<typeof PeerParent>;
|
|
2205
|
+
type ImporterVersions = Readonly<Record<string, Readonly<Record<string, string>>>>;
|
|
2206
|
+
declare const CatalogSet_base: Schema.Class<CatalogSet, Schema.Struct<{
|
|
2207
|
+
/** Catalog name → dependency name → version range. */
|
|
2208
|
+
readonly entries: Schema.$Record<Schema.String, Schema.$Record<Schema.String, Schema.String>>;
|
|
2230
2209
|
}>, {}>;
|
|
2231
2210
|
/**
|
|
2232
|
-
*
|
|
2211
|
+
* An immutable, fully-normalized catalog collection — the one catalog
|
|
2212
|
+
* resolution semantic in the package.
|
|
2233
2213
|
*
|
|
2234
2214
|
* @remarks
|
|
2235
|
-
* `
|
|
2236
|
-
*
|
|
2237
|
-
*
|
|
2238
|
-
*
|
|
2239
|
-
*
|
|
2240
|
-
* `optional` must be respected by any gate — an unsatisfied *optional* peer is
|
|
2241
|
-
* normal and should not fail a build, which is why the flag travels with the
|
|
2242
|
-
* row rather than being filtered out here. {@link PeerCheck.required} is the
|
|
2243
|
-
* convenience for the common case.
|
|
2244
|
-
*
|
|
2245
|
-
* `parents` is the path from the importer down to the package that declared
|
|
2246
|
-
* the peer, nearest-to-the-importer first. It is a path rather than a single
|
|
2247
|
-
* package because a peer declared by a transitive dependency is still that
|
|
2248
|
-
* importer's problem, and the chain is what makes the report actionable.
|
|
2249
|
-
*
|
|
2250
|
-
* A package an importer reaches by more than one path yields **one** row, not
|
|
2251
|
-
* one per path, carrying the path the walk reached first — which is what
|
|
2252
|
-
* `pnpm peers check` reports for the same graph. Read `parents` as *a* route to
|
|
2253
|
-
* the declaring package, never as the complete set of them.
|
|
2215
|
+
* `entries` is catalog name → dependency → range, with pnpm's unnamed
|
|
2216
|
+
* top-level `catalog:` block normalized under the key `"default"`. Lockfile
|
|
2217
|
+
* catalog entries, which pnpm records as `{ specifier, version }`, are
|
|
2218
|
+
* normalized to their `specifier` — the declared range, which is what a catalog
|
|
2219
|
+
* resolves to.
|
|
2254
2220
|
*
|
|
2255
2221
|
* @public
|
|
2256
2222
|
*/
|
|
2257
|
-
export declare class
|
|
2258
|
-
|
|
2223
|
+
export declare class CatalogSet extends CatalogSet_base {
|
|
2224
|
+
/** The empty set — a workspace with no catalogs. */
|
|
2225
|
+
static empty(): CatalogSet;
|
|
2226
|
+
/** Wrap a pnpm `Catalogs` map, dropping unusable entries. */
|
|
2227
|
+
static fromCatalogs(catalogs: unknown): CatalogSet;
|
|
2259
2228
|
/**
|
|
2260
|
-
*
|
|
2229
|
+
* The `catalog:` and `catalogs:` blocks of a `pnpm-workspace.yaml` document.
|
|
2261
2230
|
*
|
|
2262
|
-
*
|
|
2263
|
-
* peer-bearing package one `@virtual:` locator per consumer, and the
|
|
2264
|
-
* lockfile does not record which virtual instance satisfied which peer.
|
|
2265
|
-
* The answer is not recoverable, so it is not fabricated — `unsatisfied`
|
|
2266
|
-
* is empty and this flag says why.
|
|
2231
|
+
* @param text - The raw YAML text.
|
|
2267
2232
|
*/
|
|
2268
|
-
readonly
|
|
2269
|
-
/** Every unsatisfied peer found, optional ones included. */
|
|
2270
|
-
readonly unsatisfied: Schema.$Array<typeof UnsatisfiedPeer>;
|
|
2233
|
+
static readonly fromWorkspaceYaml: (text: string) => Effect.Effect<CatalogSet, CatalogAssemblyError, never>;
|
|
2271
2234
|
/**
|
|
2272
|
-
*
|
|
2273
|
-
*
|
|
2235
|
+
* The `catalogs:` section of a pnpm lockfile, whose entries are either a bare
|
|
2236
|
+
* range or a `{ specifier, version }` pair.
|
|
2237
|
+
*/
|
|
2238
|
+
static fromLockfileCatalogs(raw: unknown): CatalogSet;
|
|
2239
|
+
/**
|
|
2240
|
+
* The catalog set a parsed lockfile records, PM-aware.
|
|
2274
2241
|
*
|
|
2275
2242
|
* @remarks
|
|
2276
|
-
*
|
|
2277
|
-
*
|
|
2278
|
-
*
|
|
2279
|
-
*
|
|
2243
|
+
* Both pnpm and bun record catalogs in the lockfile, in different shapes:
|
|
2244
|
+
* pnpm under `extension.catalogs`, bun under `extension.catalog` /
|
|
2245
|
+
* `extension.catalogs`. Assembly reads whichever the parsed lockfile carries
|
|
2246
|
+
* rather than assuming the pnpm extension. A lockfile with no extension, or a
|
|
2247
|
+
* pnpm one with no catalogs, yields the empty set.
|
|
2280
2248
|
*
|
|
2281
|
-
*
|
|
2282
|
-
* exists: a gate that sees no rows is entitled to know whether that means
|
|
2283
|
-
* "clean" or "not looked at".
|
|
2249
|
+
* @param lockfile - A parsed lockfile from `@effected/lockfiles`.
|
|
2284
2250
|
*/
|
|
2285
|
-
|
|
2251
|
+
static fromLockfile(lockfile: Lockfile): CatalogSet;
|
|
2286
2252
|
/**
|
|
2287
|
-
*
|
|
2253
|
+
* A catalog set from bun's `{ catalog, catalogs }` blocks — used for the
|
|
2254
|
+
* `bun.lock` extension and for the root `package.json` `workspaces` block. The
|
|
2255
|
+
* unnamed default catalog normalizes under `"default"`. Tolerant: unusable
|
|
2256
|
+
* values are dropped, never fatal.
|
|
2257
|
+
*
|
|
2258
|
+
* @param blocks - The `catalog` (default) and `catalogs` (named) blocks.
|
|
2259
|
+
*/
|
|
2260
|
+
static fromBunBlocks(blocks: {
|
|
2261
|
+
readonly catalog?: unknown;
|
|
2262
|
+
readonly catalogs?: unknown;
|
|
2263
|
+
}): CatalogSet;
|
|
2264
|
+
/**
|
|
2265
|
+
* The `catalog` / `catalogs` blocks of a root `package.json` `workspaces` field
|
|
2266
|
+
* — bun's package.json analogue of pnpm's `pnpm-workspace.yaml` blocks.
|
|
2288
2267
|
*
|
|
2289
2268
|
* @remarks
|
|
2290
|
-
*
|
|
2291
|
-
*
|
|
2292
|
-
*
|
|
2269
|
+
* **Hard-fail by design**, preserving the semantics catalog output is
|
|
2270
|
+
* load-bearing for: a present-but-malformed `workspaces` shape (a number, a
|
|
2271
|
+
* string, an object with a malformed `packages` / `catalog` / `catalogs`), or
|
|
2272
|
+
* the default catalog declared twice — once as `workspaces.catalog` and again
|
|
2273
|
+
* as `workspaces.catalogs.default` — fails with `CatalogAssemblyError`
|
|
2274
|
+
* naming what was wrong. An absent `workspaces` field, one explicitly `null`,
|
|
2275
|
+
* or the plain array form (npm/yarn patterns, which carry no catalogs) yields
|
|
2276
|
+
* the empty set. Presence is checked structurally, so an explicitly-declared
|
|
2277
|
+
* empty `catalog: {}` still counts as a declaration.
|
|
2278
|
+
*
|
|
2279
|
+
* @param text - The raw root `package.json` text.
|
|
2293
2280
|
*/
|
|
2294
|
-
readonly
|
|
2295
|
-
|
|
2281
|
+
static readonly fromManifestWorkspaces: (text: string) => Effect.Effect<CatalogSet, CatalogAssemblyError, never>;
|
|
2282
|
+
/** Merge sets. Later sets win per dependency within a catalog. */
|
|
2283
|
+
static merge(...sets: ReadonlyArray<CatalogSet>): CatalogSet;
|
|
2284
|
+
/** Whether any catalog declares anything. */
|
|
2285
|
+
get isEmpty(): boolean;
|
|
2286
|
+
/**
|
|
2287
|
+
* The range a `catalog:` specifier resolves to.
|
|
2288
|
+
*
|
|
2289
|
+
* @remarks
|
|
2290
|
+
* Total: an unmatched dependency, an unknown catalog name, or a non-catalog
|
|
2291
|
+
* specifier all yield `Option.none()`. A *malformed* catalog — one pnpm
|
|
2292
|
+
* itself rejects — also yields `Option.none()` here; the fallible surface is
|
|
2293
|
+
* {@link WorkspaceCatalogs}, which fails typed on assembly instead.
|
|
2294
|
+
*
|
|
2295
|
+
* @param dependency - The package name being resolved.
|
|
2296
|
+
* @param specifier - The declared specifier, e.g. `catalog:` or `catalog:build`.
|
|
2297
|
+
*/
|
|
2298
|
+
resolveSpecifier(dependency: string, specifier: string): Option.Option<string>;
|
|
2299
|
+
/**
|
|
2300
|
+
* The range `dependency` carries in a named catalog, or in the default
|
|
2301
|
+
* catalog when `catalog` is `Option.none()`.
|
|
2302
|
+
*
|
|
2303
|
+
* @remarks
|
|
2304
|
+
* The shape `@effected/npm`'s `CatalogResolver` contract asks for.
|
|
2305
|
+
*/
|
|
2306
|
+
rangeOf(dependency: string, catalog: Option.Option<string>): Option.Option<string>;
|
|
2307
|
+
}
|
|
2296
2308
|
/**
|
|
2297
|
-
*
|
|
2298
|
-
*
|
|
2299
|
-
* @remarks
|
|
2300
|
-
* A report rather than a bare array, because an empty array is the most
|
|
2301
|
-
* dangerous success shape this domain has: it is indistinguishable from "this
|
|
2302
|
-
* format cannot be checked". `supported` and `unresolvedImporters` make the
|
|
2303
|
-
* difference legible, so a consumer cannot read a limitation as a clean bill
|
|
2304
|
-
* of health.
|
|
2305
|
-
*
|
|
2306
|
-
* Pure and total — construct it with {@link PeerCheck.run}, which never fails.
|
|
2307
|
-
*
|
|
2308
|
-
* @example
|
|
2309
|
-
* ```ts
|
|
2310
|
-
* import { PeerCheck } from "@effected/workspaces";
|
|
2311
|
-
* import { Lockfile } from "@effected/lockfiles";
|
|
2312
|
-
* import { Effect } from "effect";
|
|
2309
|
+
* Every failure catalog assembly can surface.
|
|
2313
2310
|
*
|
|
2314
|
-
*
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
* }
|
|
2319
|
-
* ```
|
|
2311
|
+
* @public
|
|
2312
|
+
*/
|
|
2313
|
+
type CatalogAssemblyFailure = CatalogAssemblyError | WorkspaceRootNotFoundError;
|
|
2314
|
+
/**
|
|
2315
|
+
* The {@link WorkspaceCatalogs} service shape.
|
|
2320
2316
|
*
|
|
2321
2317
|
* @public
|
|
2322
2318
|
*/
|
|
2323
|
-
|
|
2324
|
-
/** The
|
|
2325
|
-
|
|
2319
|
+
interface WorkspaceCatalogsShape {
|
|
2320
|
+
/** The assembled catalog set for the workspace. Memoized after the first call. */
|
|
2321
|
+
readonly set: () => Effect.Effect<CatalogSet, CatalogAssemblyFailure>;
|
|
2322
|
+
/** Resolve one `catalog:` specifier; `Option.none()` when it names nothing. */
|
|
2323
|
+
readonly resolveSpecifier: (dependency: string, specifier: string) => Effect.Effect<Option.Option<string>, CatalogAssemblyFailure>;
|
|
2326
2324
|
/**
|
|
2327
|
-
*
|
|
2325
|
+
* The effective pnpm release-age gate for the workspace, combined
|
|
2326
|
+
* strictest-wins from the inline `pnpm-workspace.yaml` keys
|
|
2327
|
+
* (`minimumReleaseAge` / `minimumReleaseAgeExclude`) and the replayed
|
|
2328
|
+
* config-dependency hooks. Assembled from the same single read and hook
|
|
2329
|
+
* replay as `set`, and memoized with it.
|
|
2330
|
+
*
|
|
2331
|
+
* @remarks
|
|
2332
|
+
* Under the default layer (no-op hooks) only inline values contribute; under
|
|
2333
|
+
* {@link WorkspaceCatalogs.layerWithConfigDependencies} the replayed hooks
|
|
2334
|
+
* contribute too. A workspace with no pnpm-workspace.yaml (a bun/npm
|
|
2335
|
+
* workspace) has no release-age keys, so the gate is the inert zero gate.
|
|
2336
|
+
*/
|
|
2337
|
+
readonly releaseAgeGate: () => Effect.Effect<ReleaseAgeGate, CatalogAssemblyFailure>;
|
|
2338
|
+
/**
|
|
2339
|
+
* The workspace's **effective** `peerDependencyRules` — pnpm's post-hoc
|
|
2340
|
+
* suppression policy, which the lockfile does not record at all.
|
|
2341
|
+
*
|
|
2342
|
+
* @remarks
|
|
2343
|
+
* Assembled from the same single read and hook replay as `set`, and memoized
|
|
2344
|
+
* with it. The `pnpm-workspace.yaml` block (what `pnpm:export` materializes)
|
|
2345
|
+
* is **seeded** into the replayed config and the config-dependency hooks
|
|
2346
|
+
* merge onto it, so one object carries both sources — the kit never merges
|
|
2347
|
+
* them itself, because that would be a second implementation of a rule the
|
|
2348
|
+
* hook seam already enforces.
|
|
2349
|
+
*
|
|
2350
|
+
* A consumer needs these to avoid **reporting** what pnpm suppresses: pnpm
|
|
2351
|
+
* computes the same peer violations and then hides the ones a rule allows,
|
|
2352
|
+
* so a checker without them reports findings pnpm calls clean. `PeerCheck`
|
|
2353
|
+
* applies all three axes.
|
|
2354
|
+
*/
|
|
2355
|
+
readonly peerDependencyRules: () => Effect.Effect<PeerDependencyRules, CatalogAssemblyFailure>;
|
|
2356
|
+
/**
|
|
2357
|
+
* Each importer's dependency-name → resolved-version map, as the manager's
|
|
2358
|
+
* lockfile records it. Read from the same single lockfile read as `set`, and
|
|
2359
|
+
* memoized with it.
|
|
2360
|
+
*
|
|
2361
|
+
* @remarks
|
|
2362
|
+
* Feeds `WorkspaceStateSnapshot`'s fallback for a `catalog:` specifier no
|
|
2363
|
+
* committed catalog source declares — the shape a config-dependency pnpmfile
|
|
2364
|
+
* hook produces. Only pnpm records importer versions; a bun or npm workspace
|
|
2365
|
+
* yields an empty index, and an absent or unreadable lockfile contributes
|
|
2366
|
+
* nothing rather than failing.
|
|
2367
|
+
*/
|
|
2368
|
+
readonly importerVersions: () => Effect.Effect<ImporterVersions, CatalogAssemblyFailure>;
|
|
2369
|
+
/**
|
|
2370
|
+
* Which version each declared config dependency was replayed from, keyed
|
|
2371
|
+
* by name — off the same single memoized assemble pass as `set()`.
|
|
2372
|
+
*
|
|
2373
|
+
* @remarks
|
|
2374
|
+
* Every dependency the hooks layer resolved when a `pnpm-workspace.yaml`
|
|
2375
|
+
* was read — `{}` when it declares no config dependencies or under the
|
|
2376
|
+
* no-op layer, which resolves nothing. Config dependencies are a pnpm
|
|
2377
|
+
* feature, so the bun / `package.json` path yields `{}` too, exactly as
|
|
2378
|
+
* `importerVersions` does.
|
|
2379
|
+
*/
|
|
2380
|
+
readonly hookReplays: () => Effect.Effect<Readonly<Record<string, HookReplay>>, CatalogAssemblyFailure>;
|
|
2381
|
+
/**
|
|
2382
|
+
* Discard the memoized assembly so the **next** read re-assembles — the same
|
|
2383
|
+
* single read and hook replay as the first, over the workspace as it stands
|
|
2384
|
+
* then.
|
|
2385
|
+
*
|
|
2386
|
+
* @remarks
|
|
2387
|
+
* The explicit memoization boundary for a tool that **mutates the workspace
|
|
2388
|
+
* mid-run** — installs, bumps a config dependency, regenerates the lockfile.
|
|
2389
|
+
* Release-age gating wants the before-state, a post-install peer check the
|
|
2390
|
+
* after-state, and one infinite memo cannot serve both without this call in
|
|
2391
|
+
* between; without it, `peerDependencyRules` keeps answering from the
|
|
2392
|
+
* pre-mutation hook replay and the checker reports findings the new rules
|
|
2393
|
+
* suppress.
|
|
2394
|
+
*
|
|
2395
|
+
* Unconditional and infallible: the memo is already success-only (a failed
|
|
2396
|
+
* or interrupted assembly retries by itself), so `refresh` exists solely to
|
|
2397
|
+
* discard a *successful* assembly that mutation has made stale. Calling it
|
|
2398
|
+
* before any read is harmless.
|
|
2399
|
+
*/
|
|
2400
|
+
readonly refresh: () => Effect.Effect<void>;
|
|
2401
|
+
}
|
|
2402
|
+
/**
|
|
2403
|
+
* Options for the {@link WorkspaceCatalogs} layer.
|
|
2404
|
+
*
|
|
2405
|
+
* @public
|
|
2406
|
+
*/
|
|
2407
|
+
interface WorkspaceCatalogsOptions {
|
|
2408
|
+
/**
|
|
2409
|
+
* The directory the workspace root is resolved from.
|
|
2410
|
+
*
|
|
2411
|
+
* @defaultValue `process.cwd()`, read lazily on first use.
|
|
2412
|
+
*/
|
|
2413
|
+
readonly cwd?: string;
|
|
2414
|
+
/**
|
|
2415
|
+
* A ceiling for the root ascent from `cwd`, passed straight through to the
|
|
2416
|
+
* `stopAt` of {@link WorkspaceRoot}'s `find`.
|
|
2417
|
+
*
|
|
2418
|
+
* @remarks
|
|
2419
|
+
* Inclusive, and resolved to an absolute path exactly as `cwd` is. When no
|
|
2420
|
+
* root is found at or below the ceiling, catalog assembly fails with
|
|
2421
|
+
* {@link WorkspaceRootNotFoundError} carrying the resolved `stopAt` rather
|
|
2422
|
+
* than adopting an enclosing directory's workspace. Pass the same value as
|
|
2423
|
+
* {@link WorkspaceDiscoveryOptions.stopAt} so every service agrees on the
|
|
2424
|
+
* root; the `Workspaces.*` composites forward one `stopAt` to all of them.
|
|
2425
|
+
*
|
|
2426
|
+
* @defaultValue no ceiling — the ascent runs to the filesystem root.
|
|
2427
|
+
*/
|
|
2428
|
+
readonly stopAt?: string | undefined;
|
|
2429
|
+
}
|
|
2430
|
+
declare const WorkspaceCatalogs_base: Context.ServiceClass<WorkspaceCatalogs, "@effected/workspaces/WorkspaceCatalogs", WorkspaceCatalogsShape>;
|
|
2431
|
+
/**
|
|
2432
|
+
* Assembles a workspace's catalogs, package-manager-aware.
|
|
2433
|
+
*
|
|
2434
|
+
* @remarks
|
|
2435
|
+
* The inline source is picked by **file presence**, the same rule
|
|
2436
|
+
* `internal/patterns.ts` uses for globs: a `pnpm-workspace.yaml` selects the pnpm
|
|
2437
|
+
* path (its `catalog:` / `catalogs:` blocks); its absence selects the root
|
|
2438
|
+
* `package.json` `workspaces.catalog` / `workspaces.catalogs` (bun's analogue).
|
|
2439
|
+
* The lockfile source is PM-aware too — whichever extension the parsed lockfile
|
|
2440
|
+
* carries (pnpm or bun) contributes its recorded catalogs.
|
|
2441
|
+
*
|
|
2442
|
+
* Precedence, lowest first: catalogs recorded in the lockfile, then the inline
|
|
2443
|
+
* declaration, then — only under {@link WorkspaceCatalogs.layerWithConfigDependencies}
|
|
2444
|
+
* — the config-dependency `pnpmfile.cjs` hooks, each layer winning per dependency
|
|
2445
|
+
* within a catalog. An unreadable or absent lockfile degrades to no lockfile
|
|
2446
|
+
* catalogs rather than failing (the inline declaration is the source of truth, the
|
|
2447
|
+
* lockfile a record of what was installed); a malformed inline source **fails
|
|
2448
|
+
* typed** with `CatalogAssemblyError`, because a silently-empty catalog read
|
|
2449
|
+
* is the "every dependency looks newly added" bug.
|
|
2450
|
+
*
|
|
2451
|
+
* Assembly is deferred to the first call and memoized success-only, matching
|
|
2452
|
+
* {@link WorkspaceDiscovery}.
|
|
2453
|
+
*
|
|
2454
|
+
* @public
|
|
2455
|
+
*/
|
|
2456
|
+
export declare class WorkspaceCatalogs extends WorkspaceCatalogs_base {
|
|
2457
|
+
/** Builds the service. */
|
|
2458
|
+
static readonly make: (options?: WorkspaceCatalogsOptions) => Effect.Effect<WorkspaceCatalogsShape, never, WorkspaceRoot | LockfileReader | ConfigDependencyHooks | FileSystem.FileSystem | Path.Path>;
|
|
2459
|
+
/**
|
|
2460
|
+
* The service over an explicit {@link ConfigDependencyHooks} layer — the
|
|
2461
|
+
* one builder every `layer*` static and the `Workspaces` composites
|
|
2462
|
+
* delegate to, so the hooks layer is provided to {@link WorkspaceCatalogs.make}
|
|
2463
|
+
* in exactly one place.
|
|
2464
|
+
*
|
|
2465
|
+
* @remarks
|
|
2466
|
+
* Reach for it when the hooks layer is chosen elsewhere — a composite that
|
|
2467
|
+
* must hand ONE hooks reference to both this service and
|
|
2468
|
+
* `WorkspaceSnapshots` (layers memoize by reference, so two references
|
|
2469
|
+
* would replay twice and could run two policies), or a test wiring
|
|
2470
|
+
* {@link ConfigDependencyHooks.layerFrom}. The three `layer*` statics are
|
|
2471
|
+
* this with the hooks layer fixed. `R` is the hooks layer's own
|
|
2472
|
+
* requirement (the subprocess variant's `ChildProcessSpawner`), threaded
|
|
2473
|
+
* through. Parameterized, so bind it to a `const` and reuse it.
|
|
2474
|
+
*
|
|
2475
|
+
* @param hooks - The {@link ConfigDependencyHooks} layer to replay through.
|
|
2476
|
+
* @param options - The same options the other layer statics take.
|
|
2477
|
+
*/
|
|
2478
|
+
static readonly layerWithHooks: <R = never>(hooks: Layer.Layer<ConfigDependencyHooks, never, R>, options?: WorkspaceCatalogsOptions) => Layer.Layer<WorkspaceCatalogs, never, WorkspaceRoot | LockfileReader | FileSystem.FileSystem | Path.Path | R>;
|
|
2479
|
+
/**
|
|
2480
|
+
* The live layer — the default, which **never executes config-dependency
|
|
2481
|
+
* code**: it wires {@link ConfigDependencyHooks.layerNoop}, whose hooks return
|
|
2482
|
+
* the inline-catalog seed untouched.
|
|
2483
|
+
*
|
|
2484
|
+
* @remarks
|
|
2485
|
+
* Parameterized, so it mints a fresh reference per call — bind it to a
|
|
2486
|
+
* `const` and reuse it, or layer memoization does not apply.
|
|
2487
|
+
*/
|
|
2488
|
+
static readonly layer: (options?: WorkspaceCatalogsOptions) => Layer.Layer<WorkspaceCatalogs, never, WorkspaceRoot | LockfileReader | FileSystem.FileSystem | Path.Path>;
|
|
2489
|
+
/**
|
|
2490
|
+
* The opt-in live layer that **does** replay config-dependency `pnpmfile.cjs`
|
|
2491
|
+
* hooks: it wires {@link ConfigDependencyHooks.layerLive}, which dynamically
|
|
2492
|
+
* imports and runs each config dependency's `updateConfig` in process.
|
|
2493
|
+
*
|
|
2494
|
+
* @remarks
|
|
2495
|
+
* Same output and requirement set as {@link WorkspaceCatalogs.layer} — the only
|
|
2496
|
+
* difference is that config-dependency code is executed. Use it deliberately;
|
|
2497
|
+
* the default catalog path stays free of any config-dependency execution.
|
|
2498
|
+
* Parameterized, so bind it to a `const` and reuse it.
|
|
2499
|
+
*/
|
|
2500
|
+
static readonly layerWithConfigDependencies: (options?: WorkspaceCatalogsOptions) => Layer.Layer<WorkspaceCatalogs, never, WorkspaceRoot | LockfileReader | FileSystem.FileSystem | Path.Path>;
|
|
2501
|
+
/**
|
|
2502
|
+
* The opt-in layer that replays config-dependency `pnpmfile` hooks in a
|
|
2503
|
+
* `node` **child process**: it wires
|
|
2504
|
+
* {@link ConfigDependencyHooks.layerSubprocess} in place of the in-process
|
|
2505
|
+
* `layerLive`.
|
|
2506
|
+
*
|
|
2507
|
+
* @remarks
|
|
2508
|
+
* Same typed semantics as {@link WorkspaceCatalogs.layerWithConfigDependencies}
|
|
2509
|
+
* — the two hook layers are drop-in interchangeable — but the replay's
|
|
2510
|
+
* computed dynamic `import()` runs in the subprocess, so it survives bundling
|
|
2511
|
+
* (a bundler compiles a computed in-process `import()` into a context module
|
|
2512
|
+
* that cannot resolve at runtime — every bundled GitHub Action hits this).
|
|
2513
|
+
* The cost is one extra requirement: core's `ChildProcessSpawner`, provided
|
|
2514
|
+
* once at the edge (`@effect/platform-node`'s `NodeServices.layer`) — the
|
|
2515
|
+
* same sanctioned R-widening as `Workspaces.layerWithGit`. Parameterized, so
|
|
2516
|
+
* bind it to a `const` and reuse it.
|
|
2517
|
+
*/
|
|
2518
|
+
static readonly layerWithConfigDependenciesSubprocess: (options?: WorkspaceCatalogsOptions) => Layer.Layer<WorkspaceCatalogs, never, WorkspaceRoot | LockfileReader | FileSystem.FileSystem | Path.Path | ChildProcessSpawner.ChildProcessSpawner>;
|
|
2519
|
+
/**
|
|
2520
|
+
* A test double satisfying the full {@link WorkspaceCatalogsShape} with no
|
|
2521
|
+
* filesystem, lockfile read, or hook replay.
|
|
2522
|
+
*
|
|
2523
|
+
* @remarks
|
|
2524
|
+
* There is **no honest default catalog set**: an empty `CatalogSet` that
|
|
2525
|
+
* looks like a legitimate answer is the "every dependency looks newly added"
|
|
2526
|
+
* failure class the live assembler hard-fails to prevent, so every method
|
|
2527
|
+
* **dies** with an instructive defect until stubbed — a test-wiring mistake
|
|
2528
|
+
* fails loudly as a defect rather than succeeding with a lie or failing with
|
|
2529
|
+
* a dishonest typed error.
|
|
2530
|
+
*
|
|
2531
|
+
* The one derivation mirrors `WorkspaceDiscovery.makeTest`'s
|
|
2532
|
+
* derived-from-the-primary rule: when a `set` override is supplied,
|
|
2533
|
+
* `resolveSpecifier` answers from that `CatalogSet`'s own
|
|
2534
|
+
* {@link CatalogSet.resolveSpecifier} — exactly what the live service runs
|
|
2535
|
+
* over its assembled set — so the two stay consistent by construction.
|
|
2536
|
+
* `releaseAgeGate`, `importerVersions` and `hookReplays` are **not**
|
|
2537
|
+
* derivable from a catalog set (the gate comes from release-age keys and
|
|
2538
|
+
* hook contributions, the importer index from the lockfile's importer
|
|
2539
|
+
* blocks, the replay record from the hooks layer — none is in a
|
|
2540
|
+
* `CatalogSet`) and always die unless stubbed.
|
|
2541
|
+
*
|
|
2542
|
+
* `refresh` defaults to `Effect.void` honestly: the double holds no memo,
|
|
2543
|
+
* so "drop the memoized assembly" is genuinely a no-op — the stubs answer
|
|
2544
|
+
* fresh on every call already. This mirrors `WorkspaceDiscovery.makeTest`'s
|
|
2545
|
+
* `refresh`.
|
|
2546
|
+
*
|
|
2547
|
+
* @example
|
|
2548
|
+
* ```ts
|
|
2549
|
+
* import { CatalogSet, WorkspaceCatalogs } from "@effected/workspaces";
|
|
2550
|
+
* import { Effect } from "effect";
|
|
2551
|
+
*
|
|
2552
|
+
* const double = WorkspaceCatalogs.makeTest({
|
|
2553
|
+
* set: () => Effect.succeed(CatalogSet.fromCatalogs({ default: { effect: "4.0.0" } })),
|
|
2554
|
+
* });
|
|
2555
|
+
* // `resolveSpecifier` now answers consistently from that set.
|
|
2556
|
+
* ```
|
|
2557
|
+
*/
|
|
2558
|
+
static readonly makeTest: (overrides?: Partial<WorkspaceCatalogsShape>) => WorkspaceCatalogsShape;
|
|
2559
|
+
/**
|
|
2560
|
+
* The test layer: {@link WorkspaceCatalogs.makeTest} behind `Layer.succeed`,
|
|
2561
|
+
* so a suite provides only the methods it exercises.
|
|
2562
|
+
*
|
|
2563
|
+
* @remarks
|
|
2564
|
+
* A parameterized layer factory mints a **fresh reference per call**, and
|
|
2565
|
+
* layers memoize by reference — bind the result to a `const` and reuse it
|
|
2566
|
+
* rather than calling `layerTest(...)` at each composition site.
|
|
2567
|
+
*
|
|
2568
|
+
* @example
|
|
2569
|
+
* ```ts
|
|
2570
|
+
* import { CatalogSet, WorkspaceCatalogs } from "@effected/workspaces";
|
|
2571
|
+
* import { Effect } from "effect";
|
|
2572
|
+
*
|
|
2573
|
+
* const TestCatalogs = WorkspaceCatalogs.layerTest({
|
|
2574
|
+
* set: () => Effect.succeed(CatalogSet.empty()),
|
|
2575
|
+
* });
|
|
2576
|
+
* // program.pipe(Effect.provide(TestCatalogs))
|
|
2577
|
+
* ```
|
|
2578
|
+
*/
|
|
2579
|
+
static readonly layerTest: (overrides?: Partial<WorkspaceCatalogsShape>) => Layer.Layer<WorkspaceCatalogs>;
|
|
2580
|
+
/**
|
|
2581
|
+
* The real implementation of `@effected/npm`'s `CatalogResolver` contract —
|
|
2582
|
+
* the one `@effected/package-json` declares but cannot fill.
|
|
2583
|
+
*
|
|
2584
|
+
* @remarks
|
|
2585
|
+
* `rangeOf` returns `Option.none()` for a dependency no catalog declares, per
|
|
2586
|
+
* the contract's convention. A failed catalog *assembly* — an unreadable or
|
|
2587
|
+
* malformed `pnpm-workspace.yaml`, a broken config-dependency hook — passes
|
|
2588
|
+
* through **typed** as the contract's `CatalogAssemblyError` (it used to be
|
|
2589
|
+
* folded into `DependencyResolutionError`'s defect `cause`, which forced
|
|
2590
|
+
* consumers to `_tag`-sniff `unknown`); only the remaining mechanism failure,
|
|
2591
|
+
* an unfindable workspace root, is wrapped as `DependencyResolutionError`.
|
|
2592
|
+
*/
|
|
2593
|
+
static readonly catalogResolver: Layer.Layer<CatalogResolver, never, WorkspaceCatalogs>;
|
|
2594
|
+
}
|
|
2595
|
+
//#endregion
|
|
2596
|
+
//#region src/PeerCheck.d.ts
|
|
2597
|
+
/**
|
|
2598
|
+
* Why a report is not a complete answer.
|
|
2599
|
+
*
|
|
2600
|
+
* @remarks
|
|
2601
|
+
* Closed at exactly four, by measurement rather than by guess:
|
|
2602
|
+
*
|
|
2603
|
+
* - `"peerRulesNotApplied"` — the effective suppression policy was not applied,
|
|
2604
|
+
* so pnpm's post-hoc suppression could not be replicated and some reported
|
|
2605
|
+
* rows may be ones pnpm hides. **Omitting the option always produces this**,
|
|
2606
|
+
* because "nobody looked" and "I looked and there are none" are different
|
|
2607
|
+
* facts. Supplied rules never produce it: all three axes —
|
|
2608
|
+
* `allowedVersions`, `ignoreMissing` and `allowAny` — are applied with
|
|
2609
|
+
* pnpm's measured semantics.
|
|
2610
|
+
* - `"unresolvedEdge"` — some instance records an edge this model could not
|
|
2611
|
+
* name (`ResolvedPackage.unresolvedEdges`), or an importer dependency
|
|
2612
|
+
* resolved through `link:` whose target the caller did not supply, so its
|
|
2613
|
+
* manifest peers are still invisible. Either way the peers that edge
|
|
2614
|
+
* involves are neither confirmed satisfied nor confirmed unmet.
|
|
2615
|
+
*
|
|
2616
|
+
* Supplying {@link PeerCheckOptions.workspacePackages} clears the second
|
|
2617
|
+
* trigger for every `link:` target the supplied set covers AND the walk
|
|
2618
|
+
* reaches: the linked manifest's peers are joined and judged for real, which
|
|
2619
|
+
* is what makes the report an answer rather than a refusal. Omitting the
|
|
2620
|
+
* option — or omitting a linked target from it — leaves the edge unverified,
|
|
2621
|
+
* because an unjoined parent's peers are recorded nowhere in the lockfile; so
|
|
2622
|
+
* does a covered target the lockfile records no workspace row for, since
|
|
2623
|
+
* there is nothing to walk and its peers were never judged.
|
|
2624
|
+
*
|
|
2625
|
+
* - `"peerRangeUnresolved"` — a peer declared by a joined `link:` manifest has
|
|
2626
|
+
* a range that is a protocol specifier this check could not turn into a
|
|
2627
|
+
* range, while something resolved for that peer, so the comparison was
|
|
2628
|
+
* never performed. The trigger is a `catalog:` specifier with
|
|
2629
|
+
* {@link PeerCheckOptions.catalogs} omitted, a `catalog:` specifier the
|
|
2630
|
+
* supplied set names nothing for, or any other protocol (`workspace:*` and
|
|
2631
|
+
* friends). A peer with NOTHING resolved never produces it: "no provider"
|
|
2632
|
+
* is reportable without the range, so that row is emitted as usual.
|
|
2633
|
+
* - `"peerVersionUnresolved"` — a peer resolved to a non-workspace provider
|
|
2634
|
+
* whose version is a protocol specifier rather than a version, so the
|
|
2635
|
+
* comparison was never performed. That is any `file:` provider — a
|
|
2636
|
+
* directory, a tarball, or either through a `file:` override
|
|
2637
|
+
* (`file:../x`) — and equally a git or remote-tarball provider, which
|
|
2638
|
+
* pnpm keys by its URL (`https://codeload.github.com/…`,
|
|
2639
|
+
* `git+https://…`), so the URL stands where a version belongs. The
|
|
2640
|
+
* lockfile records no version for a `file:` directory at all, and
|
|
2641
|
+
* `pnpm peers check` reports such a peer as a `bad` row carrying the
|
|
2642
|
+
* specifier as its found version even when the directory's own manifest
|
|
2643
|
+
* satisfies the range, so calling it satisfied would pass a workspace
|
|
2644
|
+
* pnpm rejects. Applies to a peer declared by a lockfile row and
|
|
2645
|
+
* by a joined `link:` manifest alike. A workspace-row provider never
|
|
2646
|
+
* produces it (it is accepted as always), and neither does a plain
|
|
2647
|
+
* unparseable version, which is skipped.
|
|
2648
|
+
*
|
|
2649
|
+
* All four mean **fail closed**: a gate should treat an unverified report as
|
|
2650
|
+
* "not proven clean" rather than as a pass.
|
|
2651
|
+
*
|
|
2652
|
+
* @public
|
|
2653
|
+
*/
|
|
2654
|
+
type UnverifiedReason = "peerRulesNotApplied" | "unresolvedEdge" | "peerRangeUnresolved" | "peerVersionUnresolved";
|
|
2655
|
+
/**
|
|
2656
|
+
* Options for {@link PeerCheck.run}.
|
|
2657
|
+
*
|
|
2658
|
+
* @remarks
|
|
2659
|
+
* **Presence of `peerDependencyRules` is the assertion; its contents are the
|
|
2660
|
+
* value.** Supplying `NoPeerDependencyRules` asserts the workspace has none and
|
|
2661
|
+
* yields a verified report; omitting the key entirely says nothing was looked
|
|
2662
|
+
* up and always yields `"peerRulesNotApplied"`. The two are deliberately
|
|
2663
|
+
* different results, because a gate must be able to tell "clean" from
|
|
2664
|
+
* "unchecked".
|
|
2665
|
+
*
|
|
2666
|
+
* **All three axes of the supplied rules are applied**, each with the
|
|
2667
|
+
* semantics measured against pnpm (see {@link PeerCheck.run}): `allowedVersions`
|
|
2668
|
+
* permits a resolved version by range, `ignoreMissing` hides a required peer
|
|
2669
|
+
* nothing resolved for, and `allowAny` hides a peer that resolved outside its
|
|
2670
|
+
* range. The two list axes are name patterns, never `parent>peer` keys.
|
|
2671
|
+
*
|
|
2672
|
+
* @public
|
|
2673
|
+
*/
|
|
2674
|
+
interface PeerCheckOptions {
|
|
2675
|
+
/** The workspace's effective rules, from `WorkspaceCatalogs.peerDependencyRules()`. */
|
|
2676
|
+
readonly peerDependencyRules?: PeerDependencyRules;
|
|
2677
|
+
/**
|
|
2678
|
+
* The workspace's discovered packages, from `WorkspaceDiscovery` — the
|
|
2679
|
+
* manifests a `link:`-resolved parent's peers have to be read from,
|
|
2680
|
+
* because the lockfile does not record them.
|
|
2681
|
+
*
|
|
2682
|
+
* @remarks
|
|
2683
|
+
* **Supplying this key is what turns a `link:` edge from a refusal into an
|
|
2684
|
+
* answer.** pnpm records no peer declarations for workspace projects, so a
|
|
2685
|
+
* parent reached through `link:` joins at best to a row whose peers are
|
|
2686
|
+
* empty *by design*; with the manifests supplied, that parent's declared
|
|
2687
|
+
* peers are joined and judged instead. A `link:` target absent from the
|
|
2688
|
+
* supplied set — and every target, when the key is omitted — keeps the
|
|
2689
|
+
* report's `"unresolvedEdge"` marker, on the same presence-is-the-
|
|
2690
|
+
* assertion rule as `peerDependencyRules`.
|
|
2691
|
+
*
|
|
2692
|
+
* A `link:` target matches a package when it names the package's
|
|
2693
|
+
* `relativePath` — the POSIX workspace-relative directory the lockfile's
|
|
2694
|
+
* importer paths are spelled in — or its publish directory: pnpm links a
|
|
2695
|
+
* workspace dependency INTO `publishConfig.directory` unless
|
|
2696
|
+
* `publishConfig.linkDirectory` is `false` (it defaults to true), so the
|
|
2697
|
+
* lockfile then records `link:../a/dist`. Both spellings are normalized,
|
|
2698
|
+
* and the caller passes what discovery produced without re-spelling
|
|
2699
|
+
* anything.
|
|
2700
|
+
*
|
|
2701
|
+
* For a publish-directory link, pnpm reads the peers from the manifest AT
|
|
2702
|
+
* the link target — the built one — while this check reads the supplied
|
|
2703
|
+
* source manifest and resolves its `catalog:` ranges through
|
|
2704
|
+
* {@link PeerCheckOptions.catalogs}. The two agree when the build emits the
|
|
2705
|
+
* peer ranges the source's specifiers resolve to, which is the contract a
|
|
2706
|
+
* publish-directory build keeps.
|
|
2707
|
+
*/
|
|
2708
|
+
readonly workspacePackages?: ReadonlyArray<WorkspacePackage>;
|
|
2709
|
+
/**
|
|
2710
|
+
* The workspace's catalogs, from `WorkspaceCatalogs.set()` — which already
|
|
2711
|
+
* includes any catalogs a config-dependency hook injects.
|
|
2712
|
+
*
|
|
2713
|
+
* @remarks
|
|
2714
|
+
* A joined manifest may declare a peer as `catalog:` or `catalog:<name>`
|
|
2715
|
+
* rather than as a range. With this key supplied, such a specifier is
|
|
2716
|
+
* resolved through the set and the RESOLVED range is judged and reported
|
|
2717
|
+
* as `wanted`, which is what `pnpm peers check` reports as `wantedRange`.
|
|
2718
|
+
* A specifier the set names nothing for — and every `catalog:` specifier,
|
|
2719
|
+
* when the key is omitted — cannot be judged against a provider, and the
|
|
2720
|
+
* report carries `"peerRangeUnresolved"` rather than calling the peer
|
|
2721
|
+
* satisfied: the same presence-is-the-assertion rule as the other two
|
|
2722
|
+
* keys.
|
|
2723
|
+
*/
|
|
2724
|
+
readonly catalogs?: CatalogSet;
|
|
2725
|
+
}
|
|
2726
|
+
declare const PeerParent_base: Schema.Class<PeerParent, Schema.Struct<{
|
|
2727
|
+
/** The package name. */
|
|
2728
|
+
readonly name: Schema.NonEmptyString;
|
|
2729
|
+
/** The resolved version of that package. */
|
|
2730
|
+
readonly version: Schema.String;
|
|
2731
|
+
}>, {}>;
|
|
2732
|
+
/**
|
|
2733
|
+
* One link in the chain from an importer to the package that declared an
|
|
2734
|
+
* unsatisfied peer.
|
|
2735
|
+
*
|
|
2736
|
+
* @remarks
|
|
2737
|
+
* Mirrors the shape `pnpm peers check --json` reports, so the two can be
|
|
2738
|
+
* compared directly.
|
|
2739
|
+
*
|
|
2740
|
+
* @public
|
|
2741
|
+
*/
|
|
2742
|
+
export declare class PeerParent extends PeerParent_base {}
|
|
2743
|
+
declare const UnsatisfiedPeer_base: Schema.Class<UnsatisfiedPeer, Schema.Struct<{
|
|
2744
|
+
/** The importer path the problem belongs to (`"."` for the root). */
|
|
2745
|
+
readonly importer: Schema.NonEmptyString;
|
|
2746
|
+
/** The peer dependency's name. */
|
|
2747
|
+
readonly dependency: Schema.NonEmptyString;
|
|
2748
|
+
/** The range the declaring package asked for. */
|
|
2749
|
+
readonly wanted: Schema.String;
|
|
2750
|
+
/** The version that resolved, or `null` when nothing resolved. */
|
|
2751
|
+
readonly found: Schema.NullOr<Schema.String>;
|
|
2752
|
+
/** Whether the declaring package marked the peer optional. */
|
|
2753
|
+
readonly optional: Schema.Boolean;
|
|
2754
|
+
/** The path from the importer to the declaring package. */
|
|
2755
|
+
readonly parents: Schema.$Array<typeof PeerParent>;
|
|
2756
|
+
}>, {}>;
|
|
2757
|
+
/**
|
|
2758
|
+
* One peer dependency that is declared but not satisfied.
|
|
2759
|
+
*
|
|
2760
|
+
* @remarks
|
|
2761
|
+
* `found` carries the version that actually resolved, or `null` when nothing
|
|
2762
|
+
* resolved at all. There is deliberately **no separate discriminant** between
|
|
2763
|
+
* "missing" and "unmet": `found === null` is the whole distinction, and a
|
|
2764
|
+
* renderer reads it directly.
|
|
2765
|
+
*
|
|
2766
|
+
* `optional` must be respected by any gate — an unsatisfied *optional* peer is
|
|
2767
|
+
* normal and should not fail a build, which is why the flag travels with the
|
|
2768
|
+
* row rather than being filtered out here. {@link PeerCheck.required} is the
|
|
2769
|
+
* convenience for the common case.
|
|
2770
|
+
*
|
|
2771
|
+
* `parents` is the path from the importer down to the package that declared
|
|
2772
|
+
* the peer, nearest-to-the-importer first. It is a path rather than a single
|
|
2773
|
+
* package because a peer declared by a transitive dependency is still that
|
|
2774
|
+
* importer's problem, and the chain is what makes the report actionable.
|
|
2775
|
+
*
|
|
2776
|
+
* A package an importer reaches by more than one path yields **one** row, not
|
|
2777
|
+
* one per path, carrying the path the walk reached first — which is what
|
|
2778
|
+
* `pnpm peers check` reports for the same graph. Read `parents` as *a* route to
|
|
2779
|
+
* the declaring package, never as the complete set of them.
|
|
2780
|
+
*
|
|
2781
|
+
* @public
|
|
2782
|
+
*/
|
|
2783
|
+
export declare class UnsatisfiedPeer extends UnsatisfiedPeer_base {}
|
|
2784
|
+
declare const PeerCheck_base: Schema.Class<PeerCheck, Schema.Struct<{
|
|
2785
|
+
/**
|
|
2786
|
+
* Whether the lockfile's format records peer resolution at all.
|
|
2787
|
+
*
|
|
2788
|
+
* `false` for yarn: yarn resolves peers **virtually**, giving a
|
|
2789
|
+
* peer-bearing package one `@virtual:` locator per consumer, and the
|
|
2790
|
+
* lockfile does not record which virtual instance satisfied which peer.
|
|
2791
|
+
* The answer is not recoverable, so it is not fabricated — `unsatisfied`
|
|
2792
|
+
* is empty and this flag says why.
|
|
2793
|
+
*/
|
|
2794
|
+
readonly supported: Schema.Boolean;
|
|
2795
|
+
/** Every unsatisfied peer found, optional ones included. */
|
|
2796
|
+
readonly unsatisfied: Schema.$Array<typeof UnsatisfiedPeer>;
|
|
2797
|
+
/**
|
|
2798
|
+
* Importers whose dependencies could not be resolved to instances, so no
|
|
2799
|
+
* verdict was reached for them.
|
|
2800
|
+
*
|
|
2801
|
+
* @remarks
|
|
2802
|
+
* In practice this is the **root importer under npm and bun**: neither
|
|
2803
|
+
* records a resolved version per importer dependency, and neither emits a
|
|
2804
|
+
* package row for the root, so there is nothing to join on. pnpm records a
|
|
2805
|
+
* version per importer dependency and is unaffected.
|
|
2806
|
+
*
|
|
2807
|
+
* Reported rather than silently skipped, for the same reason `supported`
|
|
2808
|
+
* exists: a gate that sees no rows is entitled to know whether that means
|
|
2809
|
+
* "clean" or "not looked at".
|
|
2810
|
+
*/
|
|
2811
|
+
readonly unresolvedImporters: Schema.$Array<Schema.String>;
|
|
2812
|
+
/**
|
|
2813
|
+
* Why this report is not a complete answer, or empty when it is.
|
|
2814
|
+
*
|
|
2815
|
+
* @remarks
|
|
2816
|
+
* See {@link UnverifiedReason}. A gate must treat a non-empty `unverified`
|
|
2817
|
+
* as **not proven clean** rather than as a pass: every reason means some
|
|
2818
|
+
* finding may be missing or spurious, and failing closed is the requirement.
|
|
2819
|
+
*/
|
|
2820
|
+
readonly unverified: Schema.$Array<Schema.Literals<readonly ["peerRulesNotApplied", "unresolvedEdge", "peerRangeUnresolved", "peerVersionUnresolved"]>>;
|
|
2821
|
+
}>, {}>;
|
|
2822
|
+
/**
|
|
2823
|
+
* The result of checking a lockfile for unsatisfied peer dependencies.
|
|
2824
|
+
*
|
|
2825
|
+
* @remarks
|
|
2826
|
+
* A report rather than a bare array, because an empty array is the most
|
|
2827
|
+
* dangerous success shape this domain has: it is indistinguishable from "this
|
|
2828
|
+
* format cannot be checked". `supported` and `unresolvedImporters` make the
|
|
2829
|
+
* difference legible, so a consumer cannot read a limitation as a clean bill
|
|
2830
|
+
* of health.
|
|
2831
|
+
*
|
|
2832
|
+
* Pure and total — construct it with {@link PeerCheck.run}, which never fails.
|
|
2833
|
+
*
|
|
2834
|
+
* @example
|
|
2835
|
+
* ```ts
|
|
2836
|
+
* import { PeerCheck } from "@effected/workspaces";
|
|
2837
|
+
* import { Lockfile } from "@effected/lockfiles";
|
|
2838
|
+
* import { Effect } from "effect";
|
|
2839
|
+
*
|
|
2840
|
+
* const program = Effect.gen(function* () {
|
|
2841
|
+
* const lockfile = yield* Lockfile.parse(text, { format: "pnpm" });
|
|
2842
|
+
* const report = PeerCheck.run(lockfile);
|
|
2843
|
+
* return report.supported ? report.required : [];
|
|
2844
|
+
* });
|
|
2845
|
+
* ```
|
|
2846
|
+
*
|
|
2847
|
+
* @public
|
|
2848
|
+
*/
|
|
2849
|
+
export declare class PeerCheck extends PeerCheck_base {
|
|
2850
|
+
/** The unsatisfied peers a gate should act on — the non-optional ones. */
|
|
2851
|
+
get required(): ReadonlyArray<UnsatisfiedPeer>;
|
|
2852
|
+
/**
|
|
2853
|
+
* Compute the unsatisfied peer dependencies of a parsed lockfile.
|
|
2328
2854
|
*
|
|
2329
2855
|
* @remarks
|
|
2330
2856
|
* Pure, total and format-free: no IO, no error channel, and no knowledge of
|
|
@@ -2335,8 +2861,13 @@ export declare class PeerCheck extends PeerCheck_base {
|
|
|
2335
2861
|
* `resolved` edges, so a peer declared by a *transitive* dependency is
|
|
2336
2862
|
* attributed to the importer that pulls it in, with the chain in
|
|
2337
2863
|
* `parents` — matching how `pnpm peers check` attributes them.
|
|
2864
|
+
* Attribution stops at a workspace package: a linked package's own
|
|
2865
|
+
* dependencies — registry or linked — are judged by that package's own
|
|
2866
|
+
* importer, and a linked package's manifest peers are judged only for the
|
|
2867
|
+
* importer that links it DIRECTLY, against that importer's dependencies.
|
|
2868
|
+
* pnpm never surfaces them on a consumer one link further out.
|
|
2338
2869
|
*
|
|
2339
|
-
*
|
|
2870
|
+
* Four judgements are deliberate:
|
|
2340
2871
|
*
|
|
2341
2872
|
* - **A required peer with nothing resolved is reported** (`found: null`),
|
|
2342
2873
|
* whether or not the declared range is parseable — a peer with no
|
|
@@ -2349,6 +2880,9 @@ export declare class PeerCheck extends PeerCheck_base {
|
|
|
2349
2880
|
* - **An unparseable range or version with something resolved is skipped.**
|
|
2350
2881
|
* The check cannot judge it, and asserting "unsatisfied" on a comparison
|
|
2351
2882
|
* that was never performed would be a wrong answer rather than a gap.
|
|
2883
|
+
* Two protocol-specifier cases are the exception and fail the report
|
|
2884
|
+
* closed instead: a joined manifest's range, and a provider whose version
|
|
2885
|
+
* is a protocol specifier (both below).
|
|
2352
2886
|
* - **A workspace-linked provider counts.** Resolution is followed through
|
|
2353
2887
|
* whatever the edge names, including a workspace row.
|
|
2354
2888
|
*
|
|
@@ -2356,7 +2890,44 @@ export declare class PeerCheck extends PeerCheck_base {
|
|
|
2356
2890
|
* bun root importer (see `unresolvedImporters`), and pnpm recording no peer
|
|
2357
2891
|
* declarations for workspace projects themselves — under pnpm a workspace
|
|
2358
2892
|
* package's *own* unsatisfied peers are not in the lockfile at all, and
|
|
2359
|
-
* `pnpm peers check` does not report them either.
|
|
2893
|
+
* `pnpm peers check` does not report them either. A `link:`-resolved
|
|
2894
|
+
* importer dependency is the same blind spot from the other side, and there
|
|
2895
|
+
* `pnpm peers check` DOES report the linked parent's peers (it reads the
|
|
2896
|
+
* manifest on disk): every `link:` edge whose target the caller did not
|
|
2897
|
+
* supply therefore fails the report closed rather than letting the
|
|
2898
|
+
* invisible parent pass as checked (effected#800).
|
|
2899
|
+
*
|
|
2900
|
+
* Supplying {@link PeerCheckOptions.workspacePackages} closes that gap for
|
|
2901
|
+
* every `link:` target the set covers — the root importer's included,
|
|
2902
|
+
* whose linked targets the walk seeds itself because the root has no
|
|
2903
|
+
* workspace row to reach them through: the claimed manifest's declared
|
|
2904
|
+
* peers are walked, judged against the importer's own dependency set
|
|
2905
|
+
* (never a sibling importer's), and reported with the manifest's name and
|
|
2906
|
+
* version in `parents` — the two facts a workspace row cannot carry, since
|
|
2907
|
+
* the lockfile names an importer by directory at the placeholder version
|
|
2908
|
+
* `0.0.0`.
|
|
2909
|
+
*
|
|
2910
|
+
* A joined manifest's peer range may be a protocol specifier rather than a
|
|
2911
|
+
* range. A `catalog:` specifier is resolved through
|
|
2912
|
+
* {@link PeerCheckOptions.catalogs} and judged and reported by its
|
|
2913
|
+
* resolved range, as `pnpm peers check` reports `wantedRange`. One that
|
|
2914
|
+
* stays unresolvable — the catalogs key omitted, a catalog naming nothing
|
|
2915
|
+
* for the peer, or another protocol such as `workspace:*` — is not judged
|
|
2916
|
+
* against a provider that resolved for it, and the report carries
|
|
2917
|
+
* `"peerRangeUnresolved"` rather than passing the peer as satisfied. With
|
|
2918
|
+
* nothing resolved for it, the row is still reported, since "no provider"
|
|
2919
|
+
* needs no range.
|
|
2920
|
+
*
|
|
2921
|
+
* The provider side has the same hole: a peer that resolved to a `file:`
|
|
2922
|
+
* dependency, directly or through a `file:` override, carries the specifier
|
|
2923
|
+
* (`file:vendor/react`) where a version belongs, and for a `file:` directory
|
|
2924
|
+
* the lockfile records no version anywhere. `pnpm peers check` reports such a
|
|
2925
|
+
* peer as a `bad` row with the specifier as its found version, even when the
|
|
2926
|
+
* directory's manifest satisfies the range. Such a peer is neither reported
|
|
2927
|
+
* nor passed: no row is fabricated, and the report carries
|
|
2928
|
+
* `"peerVersionUnresolved"`, for a lockfile row's peer and a joined
|
|
2929
|
+
* manifest's peer alike. A workspace-row provider is still accepted without a
|
|
2930
|
+
* version check, and a plain unparseable version is still skipped.
|
|
2360
2931
|
*
|
|
2361
2932
|
* @param lockfile - a lockfile parsed by `@effected/lockfiles`
|
|
2362
2933
|
* @returns the report; never fails
|
|
@@ -2690,612 +3261,216 @@ type TagClassification = {
|
|
|
2690
3261
|
*
|
|
2691
3262
|
* @remarks
|
|
2692
3263
|
* The two families are told apart by **segment count**, not by the `v` prefix:
|
|
2693
|
-
* three numeric segments is a version (so `1.0.0` and `v1.0.0` are both release
|
|
2694
|
-
* tags), while one or two segments is a truncated alias. The `v` *is* required
|
|
2695
|
-
* on an alias — a bare `1` is neither valid SemVer nor the tracking convention,
|
|
2696
|
-
* and accepting it would make this function guess.
|
|
2697
|
-
*
|
|
2698
|
-
* The package prefix splits at the **last** `@`, so a leading npm scope
|
|
2699
|
-
* survives: `@scope/pkg@1.0.0` is package `@scope/pkg` at version `1.0.0`.
|
|
2700
|
-
*
|
|
2701
|
-
* Round-tripping is a tested property: every tag {@link ReleaseTag} and
|
|
2702
|
-
* {@link TrackingTag} format classifies back to the family that produced it,
|
|
2703
|
-
* with its fields intact.
|
|
2704
|
-
*
|
|
2705
|
-
* @param tag - Any tag string.
|
|
2706
|
-
* @returns The classification.
|
|
2707
|
-
*
|
|
2708
|
-
* @public
|
|
2709
|
-
*/
|
|
2710
|
-
export declare const classifyTag: (tag: string) => TagClassification;
|
|
2711
|
-
declare const ReleaseTag_base: Schema.Class<ReleaseTag, Schema.Struct<{
|
|
2712
|
-
/** The tag string exactly as it appears in git. */
|
|
2713
|
-
readonly value: Schema.NonEmptyString;
|
|
2714
|
-
/** The package the tag names; absent on a workspace-wide single tag. */
|
|
2715
|
-
readonly packageName: Schema.optionalKey<Schema.NonEmptyString>;
|
|
2716
|
-
/** The version the tag names, without any prefix. */
|
|
2717
|
-
readonly version: Schema.NonEmptyString;
|
|
2718
|
-
/** Which style produced it. */
|
|
2719
|
-
readonly style: Schema.Literals<readonly ["single", "scoped"]>;
|
|
2720
|
-
}>, {}>;
|
|
2721
|
-
/**
|
|
2722
|
-
* A git tag naming a release, and the parts it was built from.
|
|
2723
|
-
*
|
|
2724
|
-
* @remarks
|
|
2725
|
-
* `value` is the tag exactly as it appears in git; `version` stays **bare**
|
|
2726
|
-
* even when `value` carries a prefix, so a consumer comparing versions never
|
|
2727
|
-
* has to strip one back off.
|
|
2728
|
-
*
|
|
2729
|
-
* Formatting is **total**: there is no error channel, because the only failure
|
|
2730
|
-
* v3 modelled — an empty version — is caught by `Schema.NonEmptyString` when
|
|
2731
|
-
* the value is constructed. A bad version reaching these statics is developer
|
|
2732
|
-
* wiring rather than untrusted input, so it dies as a defect, the same posture
|
|
2733
|
-
* as an uncompilable glob literal in `WorkspacePackage.matchesDependency`.
|
|
2734
|
-
*
|
|
2735
|
-
* @example
|
|
2736
|
-
* ```ts
|
|
2737
|
-
* import { ReleaseTag } from "@effected/workspaces";
|
|
2738
|
-
*
|
|
2739
|
-
* ReleaseTag.single("1.2.3").value; // "1.2.3"
|
|
2740
|
-
* ReleaseTag.scoped("@acme/cli", "1.2.3").value; // "@acme/cli@1.2.3"
|
|
2741
|
-
* ReleaseTag.scoped("cli", "1.2.3").value; // "cli@1.2.3"
|
|
2742
|
-
* ReleaseTag.scoped("cli", "1.2.3", { versionPrefix: "v" }).value; // "cli@v1.2.3"
|
|
2743
|
-
* ```
|
|
2744
|
-
*
|
|
2745
|
-
* @public
|
|
2746
|
-
*/
|
|
2747
|
-
export declare class ReleaseTag extends ReleaseTag_base {
|
|
2748
|
-
/**
|
|
2749
|
-
* One shared tag for a whole release: `1.2.3`.
|
|
2750
|
-
*
|
|
2751
|
-
* @param version - The version being released. Must not be empty.
|
|
2752
|
-
* @param options - Formatting overrides.
|
|
2753
|
-
*/
|
|
2754
|
-
static single(version: string, options?: TagFormatOptions): ReleaseTag;
|
|
2755
|
-
/**
|
|
2756
|
-
* A per-package tag: `<packageName>@<version>` — `@scope/pkg@1.2.3` for a
|
|
2757
|
-
* scoped name, `pkg@1.2.3` for an unscoped one, uniformly, unless
|
|
2758
|
-
* `options.versionPrefix` says otherwise.
|
|
2759
|
-
*
|
|
2760
|
-
* @param packageName - The package being released. Must not be empty.
|
|
2761
|
-
* @param version - The version being released. Must not be empty.
|
|
2762
|
-
* @param options - Formatting overrides.
|
|
2763
|
-
*/
|
|
2764
|
-
static scoped(packageName: string, version: string, options?: TagFormatOptions): ReleaseTag;
|
|
2765
|
-
}
|
|
2766
|
-
//#endregion
|
|
2767
|
-
//#region src/VersioningStrategy.d.ts
|
|
2768
|
-
/**
|
|
2769
|
-
* How a workspace assigns versions across its publishable packages.
|
|
2770
|
-
*
|
|
2771
|
-
* @remarks
|
|
2772
|
-
* - `single` — zero or one publishable package, so one tag names the release.
|
|
2773
|
-
* - `fixed-group` — every publishable package sits inside one group that
|
|
2774
|
-
* versions in lockstep, so one tag still names the release.
|
|
2775
|
-
* - `independent` — publishable packages version separately, so a shared tag
|
|
2776
|
-
* would be ambiguous and each package needs its own.
|
|
2777
|
-
*
|
|
2778
|
-
* @public
|
|
2779
|
-
*/
|
|
2780
|
-
export declare const VersioningStrategyType: Schema.Literals<readonly ["single", "fixed-group", "independent"]>;
|
|
2781
|
-
/**
|
|
2782
|
-
* The decoded type of {@link (VersioningStrategyType:variable)}.
|
|
2783
|
-
*
|
|
2784
|
-
* @public
|
|
2785
|
-
*/
|
|
2786
|
-
export type VersioningStrategyType = typeof VersioningStrategyType.Type;
|
|
2787
|
-
/**
|
|
2788
|
-
* Arguments to {@link VersioningStrategy.classify}.
|
|
2789
|
-
*
|
|
2790
|
-
* @public
|
|
2791
|
-
*/
|
|
2792
|
-
interface ClassifyOptions {
|
|
2793
|
-
/** Publishable package names, in any order. Duplicates are collapsed. */
|
|
2794
|
-
readonly packages: ReadonlyArray<string>;
|
|
2795
|
-
/**
|
|
2796
|
-
* Groups of packages that version in lockstep.
|
|
2797
|
-
*
|
|
2798
|
-
* @remarks
|
|
2799
|
-
* A **plain argument, deliberately.** Fixed groups are a release tool's
|
|
2800
|
-
* concept — changesets writes them to `.changeset/config.json` — and a
|
|
2801
|
-
* workspace-model package that read that file would be taking on one tool's
|
|
2802
|
-
* schema and one tool's release policy. The caller reads its own tool's
|
|
2803
|
-
* config and hands the groups in. Groups may name packages that are not
|
|
2804
|
-
* publishable, or do not exist; only whether some single group covers the
|
|
2805
|
-
* whole publishable set matters.
|
|
2806
|
-
*/
|
|
2807
|
-
readonly fixedGroups?: ReadonlyArray<ReadonlyArray<string>>;
|
|
2808
|
-
}
|
|
2809
|
-
/**
|
|
2810
|
-
* Arguments to {@link VersioningStrategy.detect}.
|
|
2811
|
-
*
|
|
2812
|
-
* @public
|
|
2813
|
-
*/
|
|
2814
|
-
interface VersioningDetectOptions {
|
|
2815
|
-
/** See {@link ClassifyOptions.fixedGroups}. Defaults to none. */
|
|
2816
|
-
readonly fixedGroups?: ReadonlyArray<ReadonlyArray<string>>;
|
|
2817
|
-
}
|
|
2818
|
-
/**
|
|
2819
|
-
* One entry in a release batch: which package went out, at which version.
|
|
2820
|
-
*
|
|
2821
|
-
* @remarks
|
|
2822
|
-
* Deliberately structural and minimal — a caller passes whatever it already
|
|
2823
|
-
* has (a publish result, a changeset plan row) without projecting it into a
|
|
2824
|
-
* package-specific type first.
|
|
2825
|
-
*
|
|
2826
|
-
* @public
|
|
2827
|
-
*/
|
|
2828
|
-
interface PackageRelease {
|
|
2829
|
-
/** The package that was released. */
|
|
2830
|
-
readonly name: string;
|
|
2831
|
-
/** The version it was released at. */
|
|
2832
|
-
readonly version: string;
|
|
2833
|
-
}
|
|
2834
|
-
declare const VersioningStrategy_base: Schema.Class<VersioningStrategy, Schema.Struct<{
|
|
2835
|
-
/** The classification. */
|
|
2836
|
-
readonly type: Schema.Literals<readonly ["single", "fixed-group", "independent"]>;
|
|
2837
|
-
/** The groups classification was performed against, as supplied. */
|
|
2838
|
-
readonly fixedGroups: Schema.$Array<Schema.$Array<Schema.String>>;
|
|
2839
|
-
/** The publishable package names, sorted and de-duplicated. */
|
|
2840
|
-
readonly publishablePackages: Schema.$Array<Schema.String>;
|
|
2841
|
-
}>, {}>;
|
|
2842
|
-
/**
|
|
2843
|
-
* How a workspace versions, and the tagging that follows from it.
|
|
2844
|
-
*
|
|
2845
|
-
* @remarks
|
|
2846
|
-
* Built either purely with {@link VersioningStrategy.classify}, or from a live
|
|
2847
|
-
* workspace with {@link VersioningStrategy.detect}.
|
|
2848
|
-
*
|
|
2849
|
-
* @example
|
|
2850
|
-
* ```ts
|
|
2851
|
-
* import { VersioningStrategy } from "@effected/workspaces";
|
|
2852
|
-
* import { Effect } from "effect";
|
|
2853
|
-
*
|
|
2854
|
-
* const program = Effect.gen(function* () {
|
|
2855
|
-
* const strategy = yield* VersioningStrategy.detect({ fixedGroups });
|
|
2856
|
-
* return strategy.tagsFor(released).map((tag) => tag.value);
|
|
2857
|
-
* });
|
|
2858
|
-
* ```
|
|
2859
|
-
*
|
|
2860
|
-
* @public
|
|
2861
|
-
*/
|
|
2862
|
-
export declare class VersioningStrategy extends VersioningStrategy_base {
|
|
2863
|
-
/**
|
|
2864
|
-
* Whether a release needs one tag per package rather than one shared tag.
|
|
2865
|
-
*/
|
|
2866
|
-
get perPackageTags(): boolean;
|
|
2867
|
-
/** The tag style this strategy implies. */
|
|
2868
|
-
get tagStyle(): TagStyle;
|
|
2869
|
-
/**
|
|
2870
|
-
* Classify a workspace from its publishable package names and fixed groups.
|
|
2871
|
-
*
|
|
2872
|
-
* @remarks
|
|
2873
|
-
* Pure and total — no IO, no error channel. `packages` is sorted and
|
|
2874
|
-
* de-duplicated first, so a name listed twice cannot inflate a one-package
|
|
2875
|
-
* repo into an independent one.
|
|
2876
|
-
*/
|
|
2877
|
-
static classify(options: ClassifyOptions): VersioningStrategy;
|
|
2878
|
-
/**
|
|
2879
|
-
* Classify the ambient workspace: enumerate its packages, keep the ones the
|
|
2880
|
-
* {@link PublishabilityDetector} says publish somewhere, and classify those.
|
|
2881
|
-
*
|
|
2882
|
-
* @remarks
|
|
2883
|
-
* The publishability question is asked through the service precisely so a
|
|
2884
|
-
* consumer with its own rules — honouring a release tool's ignore list, say —
|
|
2885
|
-
* swaps the layer instead of filtering afterwards.
|
|
2886
|
-
*/
|
|
2887
|
-
static readonly detect: (options?: VersioningDetectOptions | undefined) => Effect.Effect<VersioningStrategy, WorkspaceDiscoveryFailure, PublishabilityDetector | WorkspaceDiscovery>;
|
|
2888
|
-
/**
|
|
2889
|
-
* The tags a release of `releases` produces under this strategy.
|
|
2890
|
-
*
|
|
2891
|
-
* @remarks
|
|
2892
|
-
* Under `independent` this is one {@link ReleaseTag} per release, in the
|
|
2893
|
-
* order given. Under `single` and `fixed-group` it is exactly one shared tag
|
|
2894
|
-
* carrying the **first** release's version — every release in a lockstep
|
|
2895
|
-
* batch shares a version by construction, so the choice is only visible on a
|
|
2896
|
-
* batch that should not exist. Whether a batch actually agreed is a property
|
|
2897
|
-
* of that batch rather than of the workspace, so it stays the caller's
|
|
2898
|
-
* one-line check rather than a field here.
|
|
2899
|
-
*
|
|
2900
|
-
* An empty batch produces no tags under either style.
|
|
2901
|
-
*/
|
|
2902
|
-
tagsFor(releases: ReadonlyArray<PackageRelease>, options?: TagFormatOptions): ReadonlyArray<ReleaseTag>;
|
|
2903
|
-
}
|
|
2904
|
-
//#endregion
|
|
2905
|
-
//#region src/WorkspaceCatalogs.d.ts
|
|
2906
|
-
/**
|
|
2907
|
-
* Each importer's dependency-name → resolved-version map, keyed by importer path
|
|
2908
|
-
* (`"."` for the root package — the same keys `WorkspaceDiscovery.importerMap()`
|
|
2909
|
-
* uses, and the same value `PackageStateSnapshot.relativePath` carries).
|
|
3264
|
+
* three numeric segments is a version (so `1.0.0` and `v1.0.0` are both release
|
|
3265
|
+
* tags), while one or two segments is a truncated alias. The `v` *is* required
|
|
3266
|
+
* on an alias — a bare `1` is neither valid SemVer nor the tracking convention,
|
|
3267
|
+
* and accepting it would make this function guess.
|
|
2910
3268
|
*
|
|
2911
|
-
*
|
|
2912
|
-
*
|
|
2913
|
-
* manifest declares. It answers a `catalog:` specifier no committed catalog
|
|
2914
|
-
* source declares — the shape a pnpm config-dependency pnpmfile hook produces —
|
|
2915
|
-
* which would otherwise resolve to nothing on both sides of a diff and hide a
|
|
2916
|
-
* real version movement.
|
|
3269
|
+
* The package prefix splits at the **last** `@`, so a leading npm scope
|
|
3270
|
+
* survives: `@scope/pkg@1.0.0` is package `@scope/pkg` at version `1.0.0`.
|
|
2917
3271
|
*
|
|
2918
|
-
*
|
|
2919
|
-
*
|
|
2920
|
-
*
|
|
2921
|
-
*
|
|
3272
|
+
* Round-tripping is a tested property: every tag {@link ReleaseTag} and
|
|
3273
|
+
* {@link TrackingTag} format classifies back to the family that produced it,
|
|
3274
|
+
* with its fields intact.
|
|
3275
|
+
*
|
|
3276
|
+
* @param tag - Any tag string.
|
|
3277
|
+
* @returns The classification.
|
|
2922
3278
|
*
|
|
2923
3279
|
* @public
|
|
2924
3280
|
*/
|
|
2925
|
-
|
|
2926
|
-
declare const
|
|
2927
|
-
/**
|
|
2928
|
-
readonly
|
|
3281
|
+
export declare const classifyTag: (tag: string) => TagClassification;
|
|
3282
|
+
declare const ReleaseTag_base: Schema.Class<ReleaseTag, Schema.Struct<{
|
|
3283
|
+
/** The tag string exactly as it appears in git. */
|
|
3284
|
+
readonly value: Schema.NonEmptyString;
|
|
3285
|
+
/** The package the tag names; absent on a workspace-wide single tag. */
|
|
3286
|
+
readonly packageName: Schema.optionalKey<Schema.NonEmptyString>;
|
|
3287
|
+
/** The version the tag names, without any prefix. */
|
|
3288
|
+
readonly version: Schema.NonEmptyString;
|
|
3289
|
+
/** Which style produced it. */
|
|
3290
|
+
readonly style: Schema.Literals<readonly ["single", "scoped"]>;
|
|
2929
3291
|
}>, {}>;
|
|
2930
3292
|
/**
|
|
2931
|
-
*
|
|
2932
|
-
* resolution semantic in the package.
|
|
3293
|
+
* A git tag naming a release, and the parts it was built from.
|
|
2933
3294
|
*
|
|
2934
3295
|
* @remarks
|
|
2935
|
-
* `
|
|
2936
|
-
*
|
|
2937
|
-
*
|
|
2938
|
-
*
|
|
2939
|
-
*
|
|
3296
|
+
* `value` is the tag exactly as it appears in git; `version` stays **bare**
|
|
3297
|
+
* even when `value` carries a prefix, so a consumer comparing versions never
|
|
3298
|
+
* has to strip one back off.
|
|
3299
|
+
*
|
|
3300
|
+
* Formatting is **total**: there is no error channel, because the only failure
|
|
3301
|
+
* v3 modelled — an empty version — is caught by `Schema.NonEmptyString` when
|
|
3302
|
+
* the value is constructed. A bad version reaching these statics is developer
|
|
3303
|
+
* wiring rather than untrusted input, so it dies as a defect, the same posture
|
|
3304
|
+
* as an uncompilable glob literal in `WorkspacePackage.matchesDependency`.
|
|
3305
|
+
*
|
|
3306
|
+
* @example
|
|
3307
|
+
* ```ts
|
|
3308
|
+
* import { ReleaseTag } from "@effected/workspaces";
|
|
3309
|
+
*
|
|
3310
|
+
* ReleaseTag.single("1.2.3").value; // "1.2.3"
|
|
3311
|
+
* ReleaseTag.scoped("@acme/cli", "1.2.3").value; // "@acme/cli@1.2.3"
|
|
3312
|
+
* ReleaseTag.scoped("cli", "1.2.3").value; // "cli@1.2.3"
|
|
3313
|
+
* ReleaseTag.scoped("cli", "1.2.3", { versionPrefix: "v" }).value; // "cli@v1.2.3"
|
|
3314
|
+
* ```
|
|
2940
3315
|
*
|
|
2941
3316
|
* @public
|
|
2942
3317
|
*/
|
|
2943
|
-
export declare class
|
|
2944
|
-
/** The empty set — a workspace with no catalogs. */
|
|
2945
|
-
static empty(): CatalogSet;
|
|
2946
|
-
/** Wrap a pnpm `Catalogs` map, dropping unusable entries. */
|
|
2947
|
-
static fromCatalogs(catalogs: unknown): CatalogSet;
|
|
2948
|
-
/**
|
|
2949
|
-
* The `catalog:` and `catalogs:` blocks of a `pnpm-workspace.yaml` document.
|
|
2950
|
-
*
|
|
2951
|
-
* @param text - The raw YAML text.
|
|
2952
|
-
*/
|
|
2953
|
-
static readonly fromWorkspaceYaml: (text: string) => Effect.Effect<CatalogSet, CatalogAssemblyError, never>;
|
|
2954
|
-
/**
|
|
2955
|
-
* The `catalogs:` section of a pnpm lockfile, whose entries are either a bare
|
|
2956
|
-
* range or a `{ specifier, version }` pair.
|
|
2957
|
-
*/
|
|
2958
|
-
static fromLockfileCatalogs(raw: unknown): CatalogSet;
|
|
2959
|
-
/**
|
|
2960
|
-
* The catalog set a parsed lockfile records, PM-aware.
|
|
2961
|
-
*
|
|
2962
|
-
* @remarks
|
|
2963
|
-
* Both pnpm and bun record catalogs in the lockfile, in different shapes:
|
|
2964
|
-
* pnpm under `extension.catalogs`, bun under `extension.catalog` /
|
|
2965
|
-
* `extension.catalogs`. Assembly reads whichever the parsed lockfile carries
|
|
2966
|
-
* rather than assuming the pnpm extension. A lockfile with no extension, or a
|
|
2967
|
-
* pnpm one with no catalogs, yields the empty set.
|
|
2968
|
-
*
|
|
2969
|
-
* @param lockfile - A parsed lockfile from `@effected/lockfiles`.
|
|
2970
|
-
*/
|
|
2971
|
-
static fromLockfile(lockfile: Lockfile): CatalogSet;
|
|
2972
|
-
/**
|
|
2973
|
-
* A catalog set from bun's `{ catalog, catalogs }` blocks — used for the
|
|
2974
|
-
* `bun.lock` extension and for the root `package.json` `workspaces` block. The
|
|
2975
|
-
* unnamed default catalog normalizes under `"default"`. Tolerant: unusable
|
|
2976
|
-
* values are dropped, never fatal.
|
|
2977
|
-
*
|
|
2978
|
-
* @param blocks - The `catalog` (default) and `catalogs` (named) blocks.
|
|
2979
|
-
*/
|
|
2980
|
-
static fromBunBlocks(blocks: {
|
|
2981
|
-
readonly catalog?: unknown;
|
|
2982
|
-
readonly catalogs?: unknown;
|
|
2983
|
-
}): CatalogSet;
|
|
2984
|
-
/**
|
|
2985
|
-
* The `catalog` / `catalogs` blocks of a root `package.json` `workspaces` field
|
|
2986
|
-
* — bun's package.json analogue of pnpm's `pnpm-workspace.yaml` blocks.
|
|
2987
|
-
*
|
|
2988
|
-
* @remarks
|
|
2989
|
-
* **Hard-fail by design**, preserving the semantics catalog output is
|
|
2990
|
-
* load-bearing for: a present-but-malformed `workspaces` shape (a number, a
|
|
2991
|
-
* string, an object with a malformed `packages` / `catalog` / `catalogs`), or
|
|
2992
|
-
* the default catalog declared twice — once as `workspaces.catalog` and again
|
|
2993
|
-
* as `workspaces.catalogs.default` — fails with `CatalogAssemblyError`
|
|
2994
|
-
* naming what was wrong. An absent `workspaces` field, one explicitly `null`,
|
|
2995
|
-
* or the plain array form (npm/yarn patterns, which carry no catalogs) yields
|
|
2996
|
-
* the empty set. Presence is checked structurally, so an explicitly-declared
|
|
2997
|
-
* empty `catalog: {}` still counts as a declaration.
|
|
2998
|
-
*
|
|
2999
|
-
* @param text - The raw root `package.json` text.
|
|
3000
|
-
*/
|
|
3001
|
-
static readonly fromManifestWorkspaces: (text: string) => Effect.Effect<CatalogSet, CatalogAssemblyError, never>;
|
|
3002
|
-
/** Merge sets. Later sets win per dependency within a catalog. */
|
|
3003
|
-
static merge(...sets: ReadonlyArray<CatalogSet>): CatalogSet;
|
|
3004
|
-
/** Whether any catalog declares anything. */
|
|
3005
|
-
get isEmpty(): boolean;
|
|
3318
|
+
export declare class ReleaseTag extends ReleaseTag_base {
|
|
3006
3319
|
/**
|
|
3007
|
-
*
|
|
3008
|
-
*
|
|
3009
|
-
* @remarks
|
|
3010
|
-
* Total: an unmatched dependency, an unknown catalog name, or a non-catalog
|
|
3011
|
-
* specifier all yield `Option.none()`. A *malformed* catalog — one pnpm
|
|
3012
|
-
* itself rejects — also yields `Option.none()` here; the fallible surface is
|
|
3013
|
-
* {@link WorkspaceCatalogs}, which fails typed on assembly instead.
|
|
3320
|
+
* One shared tag for a whole release: `1.2.3`.
|
|
3014
3321
|
*
|
|
3015
|
-
* @param
|
|
3016
|
-
* @param
|
|
3322
|
+
* @param version - The version being released. Must not be empty.
|
|
3323
|
+
* @param options - Formatting overrides.
|
|
3017
3324
|
*/
|
|
3018
|
-
|
|
3325
|
+
static single(version: string, options?: TagFormatOptions): ReleaseTag;
|
|
3019
3326
|
/**
|
|
3020
|
-
*
|
|
3021
|
-
*
|
|
3327
|
+
* A per-package tag: `<packageName>@<version>` — `@scope/pkg@1.2.3` for a
|
|
3328
|
+
* scoped name, `pkg@1.2.3` for an unscoped one, uniformly, unless
|
|
3329
|
+
* `options.versionPrefix` says otherwise.
|
|
3022
3330
|
*
|
|
3023
|
-
* @
|
|
3024
|
-
* The
|
|
3331
|
+
* @param packageName - The package being released. Must not be empty.
|
|
3332
|
+
* @param version - The version being released. Must not be empty.
|
|
3333
|
+
* @param options - Formatting overrides.
|
|
3025
3334
|
*/
|
|
3026
|
-
|
|
3335
|
+
static scoped(packageName: string, version: string, options?: TagFormatOptions): ReleaseTag;
|
|
3027
3336
|
}
|
|
3337
|
+
//#endregion
|
|
3338
|
+
//#region src/VersioningStrategy.d.ts
|
|
3028
3339
|
/**
|
|
3029
|
-
*
|
|
3340
|
+
* How a workspace assigns versions across its publishable packages.
|
|
3341
|
+
*
|
|
3342
|
+
* @remarks
|
|
3343
|
+
* - `single` — zero or one publishable package, so one tag names the release.
|
|
3344
|
+
* - `fixed-group` — every publishable package sits inside one group that
|
|
3345
|
+
* versions in lockstep, so one tag still names the release.
|
|
3346
|
+
* - `independent` — publishable packages version separately, so a shared tag
|
|
3347
|
+
* would be ambiguous and each package needs its own.
|
|
3030
3348
|
*
|
|
3031
3349
|
* @public
|
|
3032
3350
|
*/
|
|
3033
|
-
|
|
3351
|
+
export declare const VersioningStrategyType: Schema.Literals<readonly ["single", "fixed-group", "independent"]>;
|
|
3034
3352
|
/**
|
|
3035
|
-
* The {@link
|
|
3353
|
+
* The decoded type of {@link (VersioningStrategyType:variable)}.
|
|
3036
3354
|
*
|
|
3037
3355
|
* @public
|
|
3038
3356
|
*/
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
* config-dependency hooks. Assembled from the same single read and hook
|
|
3049
|
-
* replay as `set`, and memoized with it.
|
|
3050
|
-
*
|
|
3051
|
-
* @remarks
|
|
3052
|
-
* Under the default layer (no-op hooks) only inline values contribute; under
|
|
3053
|
-
* {@link WorkspaceCatalogs.layerWithConfigDependencies} the replayed hooks
|
|
3054
|
-
* contribute too. A workspace with no pnpm-workspace.yaml (a bun/npm
|
|
3055
|
-
* workspace) has no release-age keys, so the gate is the inert zero gate.
|
|
3056
|
-
*/
|
|
3057
|
-
readonly releaseAgeGate: () => Effect.Effect<ReleaseAgeGate, CatalogAssemblyFailure>;
|
|
3058
|
-
/**
|
|
3059
|
-
* The workspace's **effective** `peerDependencyRules` — pnpm's post-hoc
|
|
3060
|
-
* suppression policy, which the lockfile does not record at all.
|
|
3061
|
-
*
|
|
3062
|
-
* @remarks
|
|
3063
|
-
* Assembled from the same single read and hook replay as `set`, and memoized
|
|
3064
|
-
* with it. The `pnpm-workspace.yaml` block (what `pnpm:export` materializes)
|
|
3065
|
-
* is **seeded** into the replayed config and the config-dependency hooks
|
|
3066
|
-
* merge onto it, so one object carries both sources — the kit never merges
|
|
3067
|
-
* them itself, because that would be a second implementation of a rule the
|
|
3068
|
-
* hook seam already enforces.
|
|
3069
|
-
*
|
|
3070
|
-
* A consumer needs these to avoid **reporting** what pnpm suppresses: pnpm
|
|
3071
|
-
* computes the same peer violations and then hides the ones a rule allows,
|
|
3072
|
-
* so a checker without them reports findings pnpm calls clean. `PeerCheck`
|
|
3073
|
-
* applies all three axes.
|
|
3074
|
-
*/
|
|
3075
|
-
readonly peerDependencyRules: () => Effect.Effect<PeerDependencyRules, CatalogAssemblyFailure>;
|
|
3076
|
-
/**
|
|
3077
|
-
* Each importer's dependency-name → resolved-version map, as the manager's
|
|
3078
|
-
* lockfile records it. Read from the same single lockfile read as `set`, and
|
|
3079
|
-
* memoized with it.
|
|
3080
|
-
*
|
|
3081
|
-
* @remarks
|
|
3082
|
-
* Feeds `WorkspaceStateSnapshot`'s fallback for a `catalog:` specifier no
|
|
3083
|
-
* committed catalog source declares — the shape a config-dependency pnpmfile
|
|
3084
|
-
* hook produces. Only pnpm records importer versions; a bun or npm workspace
|
|
3085
|
-
* yields an empty index, and an absent or unreadable lockfile contributes
|
|
3086
|
-
* nothing rather than failing.
|
|
3087
|
-
*/
|
|
3088
|
-
readonly importerVersions: () => Effect.Effect<ImporterVersions, CatalogAssemblyFailure>;
|
|
3089
|
-
/**
|
|
3090
|
-
* Which version each declared config dependency was replayed from, keyed
|
|
3091
|
-
* by name — off the same single memoized assemble pass as `set()`.
|
|
3092
|
-
*
|
|
3093
|
-
* @remarks
|
|
3094
|
-
* Every dependency the hooks layer resolved when a `pnpm-workspace.yaml`
|
|
3095
|
-
* was read — `{}` when it declares no config dependencies or under the
|
|
3096
|
-
* no-op layer, which resolves nothing. Config dependencies are a pnpm
|
|
3097
|
-
* feature, so the bun / `package.json` path yields `{}` too, exactly as
|
|
3098
|
-
* `importerVersions` does.
|
|
3099
|
-
*/
|
|
3100
|
-
readonly hookReplays: () => Effect.Effect<Readonly<Record<string, HookReplay>>, CatalogAssemblyFailure>;
|
|
3357
|
+
export type VersioningStrategyType = typeof VersioningStrategyType.Type;
|
|
3358
|
+
/**
|
|
3359
|
+
* Arguments to {@link VersioningStrategy.classify}.
|
|
3360
|
+
*
|
|
3361
|
+
* @public
|
|
3362
|
+
*/
|
|
3363
|
+
interface ClassifyOptions {
|
|
3364
|
+
/** Publishable package names, in any order. Duplicates are collapsed. */
|
|
3365
|
+
readonly packages: ReadonlyArray<string>;
|
|
3101
3366
|
/**
|
|
3102
|
-
*
|
|
3103
|
-
* single read and hook replay as the first, over the workspace as it stands
|
|
3104
|
-
* then.
|
|
3105
|
-
*
|
|
3106
|
-
* @remarks
|
|
3107
|
-
* The explicit memoization boundary for a tool that **mutates the workspace
|
|
3108
|
-
* mid-run** — installs, bumps a config dependency, regenerates the lockfile.
|
|
3109
|
-
* Release-age gating wants the before-state, a post-install peer check the
|
|
3110
|
-
* after-state, and one infinite memo cannot serve both without this call in
|
|
3111
|
-
* between; without it, `peerDependencyRules` keeps answering from the
|
|
3112
|
-
* pre-mutation hook replay and the checker reports findings the new rules
|
|
3113
|
-
* suppress.
|
|
3367
|
+
* Groups of packages that version in lockstep.
|
|
3114
3368
|
*
|
|
3115
|
-
*
|
|
3116
|
-
*
|
|
3117
|
-
*
|
|
3118
|
-
*
|
|
3369
|
+
* @remarks
|
|
3370
|
+
* A **plain argument, deliberately.** Fixed groups are a release tool's
|
|
3371
|
+
* concept — changesets writes them to `.changeset/config.json` — and a
|
|
3372
|
+
* workspace-model package that read that file would be taking on one tool's
|
|
3373
|
+
* schema and one tool's release policy. The caller reads its own tool's
|
|
3374
|
+
* config and hands the groups in. Groups may name packages that are not
|
|
3375
|
+
* publishable, or do not exist; only whether some single group covers the
|
|
3376
|
+
* whole publishable set matters.
|
|
3119
3377
|
*/
|
|
3120
|
-
readonly
|
|
3378
|
+
readonly fixedGroups?: ReadonlyArray<ReadonlyArray<string>>;
|
|
3121
3379
|
}
|
|
3122
3380
|
/**
|
|
3123
|
-
*
|
|
3381
|
+
* Arguments to {@link VersioningStrategy.detect}.
|
|
3124
3382
|
*
|
|
3125
3383
|
* @public
|
|
3126
3384
|
*/
|
|
3127
|
-
interface
|
|
3128
|
-
/**
|
|
3129
|
-
|
|
3130
|
-
*
|
|
3131
|
-
* @defaultValue `process.cwd()`, read lazily on first use.
|
|
3132
|
-
*/
|
|
3133
|
-
readonly cwd?: string;
|
|
3385
|
+
interface VersioningDetectOptions {
|
|
3386
|
+
/** See {@link ClassifyOptions.fixedGroups}. Defaults to none. */
|
|
3387
|
+
readonly fixedGroups?: ReadonlyArray<ReadonlyArray<string>>;
|
|
3134
3388
|
}
|
|
3135
|
-
declare const WorkspaceCatalogs_base: Context.ServiceClass<WorkspaceCatalogs, "@effected/workspaces/WorkspaceCatalogs", WorkspaceCatalogsShape>;
|
|
3136
3389
|
/**
|
|
3137
|
-
*
|
|
3390
|
+
* One entry in a release batch: which package went out, at which version.
|
|
3138
3391
|
*
|
|
3139
3392
|
* @remarks
|
|
3140
|
-
*
|
|
3141
|
-
*
|
|
3142
|
-
*
|
|
3143
|
-
* `package.json` `workspaces.catalog` / `workspaces.catalogs` (bun's analogue).
|
|
3144
|
-
* The lockfile source is PM-aware too — whichever extension the parsed lockfile
|
|
3145
|
-
* carries (pnpm or bun) contributes its recorded catalogs.
|
|
3393
|
+
* Deliberately structural and minimal — a caller passes whatever it already
|
|
3394
|
+
* has (a publish result, a changeset plan row) without projecting it into a
|
|
3395
|
+
* package-specific type first.
|
|
3146
3396
|
*
|
|
3147
|
-
*
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3397
|
+
* @public
|
|
3398
|
+
*/
|
|
3399
|
+
interface PackageRelease {
|
|
3400
|
+
/** The package that was released. */
|
|
3401
|
+
readonly name: string;
|
|
3402
|
+
/** The version it was released at. */
|
|
3403
|
+
readonly version: string;
|
|
3404
|
+
}
|
|
3405
|
+
declare const VersioningStrategy_base: Schema.Class<VersioningStrategy, Schema.Struct<{
|
|
3406
|
+
/** The classification. */
|
|
3407
|
+
readonly type: Schema.Literals<readonly ["single", "fixed-group", "independent"]>;
|
|
3408
|
+
/** The groups classification was performed against, as supplied. */
|
|
3409
|
+
readonly fixedGroups: Schema.$Array<Schema.$Array<Schema.String>>;
|
|
3410
|
+
/** The publishable package names, sorted and de-duplicated. */
|
|
3411
|
+
readonly publishablePackages: Schema.$Array<Schema.String>;
|
|
3412
|
+
}>, {}>;
|
|
3413
|
+
/**
|
|
3414
|
+
* How a workspace versions, and the tagging that follows from it.
|
|
3155
3415
|
*
|
|
3156
|
-
*
|
|
3157
|
-
* {@link
|
|
3416
|
+
* @remarks
|
|
3417
|
+
* Built either purely with {@link VersioningStrategy.classify}, or from a live
|
|
3418
|
+
* workspace with {@link VersioningStrategy.detect}.
|
|
3419
|
+
*
|
|
3420
|
+
* @example
|
|
3421
|
+
* ```ts
|
|
3422
|
+
* import { VersioningStrategy } from "@effected/workspaces";
|
|
3423
|
+
* import { Effect } from "effect";
|
|
3424
|
+
*
|
|
3425
|
+
* const program = Effect.gen(function* () {
|
|
3426
|
+
* const strategy = yield* VersioningStrategy.detect({ fixedGroups });
|
|
3427
|
+
* return strategy.tagsFor(released).map((tag) => tag.value);
|
|
3428
|
+
* });
|
|
3429
|
+
* ```
|
|
3158
3430
|
*
|
|
3159
3431
|
* @public
|
|
3160
3432
|
*/
|
|
3161
|
-
export declare class
|
|
3162
|
-
/** Builds the service. */
|
|
3163
|
-
static readonly make: (options?: WorkspaceCatalogsOptions) => Effect.Effect<WorkspaceCatalogsShape, never, WorkspaceRoot | LockfileReader | ConfigDependencyHooks | FileSystem.FileSystem | Path.Path>;
|
|
3164
|
-
/**
|
|
3165
|
-
* The service over an explicit {@link ConfigDependencyHooks} layer — the
|
|
3166
|
-
* one builder every `layer*` static and the `Workspaces` composites
|
|
3167
|
-
* delegate to, so the hooks layer is provided to {@link WorkspaceCatalogs.make}
|
|
3168
|
-
* in exactly one place.
|
|
3169
|
-
*
|
|
3170
|
-
* @remarks
|
|
3171
|
-
* Reach for it when the hooks layer is chosen elsewhere — a composite that
|
|
3172
|
-
* must hand ONE hooks reference to both this service and
|
|
3173
|
-
* `WorkspaceSnapshots` (layers memoize by reference, so two references
|
|
3174
|
-
* would replay twice and could run two policies), or a test wiring
|
|
3175
|
-
* {@link ConfigDependencyHooks.layerFrom}. The three `layer*` statics are
|
|
3176
|
-
* this with the hooks layer fixed. `R` is the hooks layer's own
|
|
3177
|
-
* requirement (the subprocess variant's `ChildProcessSpawner`), threaded
|
|
3178
|
-
* through. Parameterized, so bind it to a `const` and reuse it.
|
|
3179
|
-
*
|
|
3180
|
-
* @param hooks - The {@link ConfigDependencyHooks} layer to replay through.
|
|
3181
|
-
* @param options - The same options the other layer statics take.
|
|
3182
|
-
*/
|
|
3183
|
-
static readonly layerWithHooks: <R = never>(hooks: Layer.Layer<ConfigDependencyHooks, never, R>, options?: WorkspaceCatalogsOptions) => Layer.Layer<WorkspaceCatalogs, never, WorkspaceRoot | LockfileReader | FileSystem.FileSystem | Path.Path | R>;
|
|
3184
|
-
/**
|
|
3185
|
-
* The live layer — the default, which **never executes config-dependency
|
|
3186
|
-
* code**: it wires {@link ConfigDependencyHooks.layerNoop}, whose hooks return
|
|
3187
|
-
* the inline-catalog seed untouched.
|
|
3188
|
-
*
|
|
3189
|
-
* @remarks
|
|
3190
|
-
* Parameterized, so it mints a fresh reference per call — bind it to a
|
|
3191
|
-
* `const` and reuse it, or layer memoization does not apply.
|
|
3192
|
-
*/
|
|
3193
|
-
static readonly layer: (options?: WorkspaceCatalogsOptions) => Layer.Layer<WorkspaceCatalogs, never, WorkspaceRoot | LockfileReader | FileSystem.FileSystem | Path.Path>;
|
|
3433
|
+
export declare class VersioningStrategy extends VersioningStrategy_base {
|
|
3194
3434
|
/**
|
|
3195
|
-
*
|
|
3196
|
-
* hooks: it wires {@link ConfigDependencyHooks.layerLive}, which dynamically
|
|
3197
|
-
* imports and runs each config dependency's `updateConfig` in process.
|
|
3198
|
-
*
|
|
3199
|
-
* @remarks
|
|
3200
|
-
* Same output and requirement set as {@link WorkspaceCatalogs.layer} — the only
|
|
3201
|
-
* difference is that config-dependency code is executed. Use it deliberately;
|
|
3202
|
-
* the default catalog path stays free of any config-dependency execution.
|
|
3203
|
-
* Parameterized, so bind it to a `const` and reuse it.
|
|
3435
|
+
* Whether a release needs one tag per package rather than one shared tag.
|
|
3204
3436
|
*/
|
|
3205
|
-
|
|
3437
|
+
get perPackageTags(): boolean;
|
|
3438
|
+
/** The tag style this strategy implies. */
|
|
3439
|
+
get tagStyle(): TagStyle;
|
|
3206
3440
|
/**
|
|
3207
|
-
*
|
|
3208
|
-
* `node` **child process**: it wires
|
|
3209
|
-
* {@link ConfigDependencyHooks.layerSubprocess} in place of the in-process
|
|
3210
|
-
* `layerLive`.
|
|
3441
|
+
* Classify a workspace from its publishable package names and fixed groups.
|
|
3211
3442
|
*
|
|
3212
3443
|
* @remarks
|
|
3213
|
-
*
|
|
3214
|
-
*
|
|
3215
|
-
*
|
|
3216
|
-
* (a bundler compiles a computed in-process `import()` into a context module
|
|
3217
|
-
* that cannot resolve at runtime — every bundled GitHub Action hits this).
|
|
3218
|
-
* The cost is one extra requirement: core's `ChildProcessSpawner`, provided
|
|
3219
|
-
* once at the edge (`@effect/platform-node`'s `NodeServices.layer`) — the
|
|
3220
|
-
* same sanctioned R-widening as `Workspaces.layerWithGit`. Parameterized, so
|
|
3221
|
-
* bind it to a `const` and reuse it.
|
|
3444
|
+
* Pure and total — no IO, no error channel. `packages` is sorted and
|
|
3445
|
+
* de-duplicated first, so a name listed twice cannot inflate a one-package
|
|
3446
|
+
* repo into an independent one.
|
|
3222
3447
|
*/
|
|
3223
|
-
static
|
|
3448
|
+
static classify(options: ClassifyOptions): VersioningStrategy;
|
|
3224
3449
|
/**
|
|
3225
|
-
*
|
|
3226
|
-
*
|
|
3450
|
+
* Classify the ambient workspace: enumerate its packages, keep the ones the
|
|
3451
|
+
* {@link PublishabilityDetector} says publish somewhere, and classify those.
|
|
3227
3452
|
*
|
|
3228
3453
|
* @remarks
|
|
3229
|
-
*
|
|
3230
|
-
*
|
|
3231
|
-
*
|
|
3232
|
-
* **dies** with an instructive defect until stubbed — a test-wiring mistake
|
|
3233
|
-
* fails loudly as a defect rather than succeeding with a lie or failing with
|
|
3234
|
-
* a dishonest typed error.
|
|
3235
|
-
*
|
|
3236
|
-
* The one derivation mirrors `WorkspaceDiscovery.makeTest`'s
|
|
3237
|
-
* derived-from-the-primary rule: when a `set` override is supplied,
|
|
3238
|
-
* `resolveSpecifier` answers from that `CatalogSet`'s own
|
|
3239
|
-
* {@link CatalogSet.resolveSpecifier} — exactly what the live service runs
|
|
3240
|
-
* over its assembled set — so the two stay consistent by construction.
|
|
3241
|
-
* `releaseAgeGate`, `importerVersions` and `hookReplays` are **not**
|
|
3242
|
-
* derivable from a catalog set (the gate comes from release-age keys and
|
|
3243
|
-
* hook contributions, the importer index from the lockfile's importer
|
|
3244
|
-
* blocks, the replay record from the hooks layer — none is in a
|
|
3245
|
-
* `CatalogSet`) and always die unless stubbed.
|
|
3246
|
-
*
|
|
3247
|
-
* `refresh` defaults to `Effect.void` honestly: the double holds no memo,
|
|
3248
|
-
* so "drop the memoized assembly" is genuinely a no-op — the stubs answer
|
|
3249
|
-
* fresh on every call already. This mirrors `WorkspaceDiscovery.makeTest`'s
|
|
3250
|
-
* `refresh`.
|
|
3251
|
-
*
|
|
3252
|
-
* @example
|
|
3253
|
-
* ```ts
|
|
3254
|
-
* import { CatalogSet, WorkspaceCatalogs } from "@effected/workspaces";
|
|
3255
|
-
* import { Effect } from "effect";
|
|
3256
|
-
*
|
|
3257
|
-
* const double = WorkspaceCatalogs.makeTest({
|
|
3258
|
-
* set: () => Effect.succeed(CatalogSet.fromCatalogs({ default: { effect: "4.0.0" } })),
|
|
3259
|
-
* });
|
|
3260
|
-
* // `resolveSpecifier` now answers consistently from that set.
|
|
3261
|
-
* ```
|
|
3454
|
+
* The publishability question is asked through the service precisely so a
|
|
3455
|
+
* consumer with its own rules — honouring a release tool's ignore list, say —
|
|
3456
|
+
* swaps the layer instead of filtering afterwards.
|
|
3262
3457
|
*/
|
|
3263
|
-
static readonly
|
|
3458
|
+
static readonly detect: (options?: VersioningDetectOptions | undefined) => Effect.Effect<VersioningStrategy, WorkspaceDiscoveryFailure, PublishabilityDetector | WorkspaceDiscovery>;
|
|
3264
3459
|
/**
|
|
3265
|
-
* The
|
|
3266
|
-
* so a suite provides only the methods it exercises.
|
|
3460
|
+
* The tags a release of `releases` produces under this strategy.
|
|
3267
3461
|
*
|
|
3268
3462
|
* @remarks
|
|
3269
|
-
*
|
|
3270
|
-
*
|
|
3271
|
-
*
|
|
3272
|
-
*
|
|
3273
|
-
*
|
|
3274
|
-
*
|
|
3275
|
-
*
|
|
3276
|
-
* import { Effect } from "effect";
|
|
3277
|
-
*
|
|
3278
|
-
* const TestCatalogs = WorkspaceCatalogs.layerTest({
|
|
3279
|
-
* set: () => Effect.succeed(CatalogSet.empty()),
|
|
3280
|
-
* });
|
|
3281
|
-
* // program.pipe(Effect.provide(TestCatalogs))
|
|
3282
|
-
* ```
|
|
3283
|
-
*/
|
|
3284
|
-
static readonly layerTest: (overrides?: Partial<WorkspaceCatalogsShape>) => Layer.Layer<WorkspaceCatalogs>;
|
|
3285
|
-
/**
|
|
3286
|
-
* The real implementation of `@effected/npm`'s `CatalogResolver` contract —
|
|
3287
|
-
* the one `@effected/package-json` declares but cannot fill.
|
|
3463
|
+
* Under `independent` this is one {@link ReleaseTag} per release, in the
|
|
3464
|
+
* order given. Under `single` and `fixed-group` it is exactly one shared tag
|
|
3465
|
+
* carrying the **first** release's version — every release in a lockstep
|
|
3466
|
+
* batch shares a version by construction, so the choice is only visible on a
|
|
3467
|
+
* batch that should not exist. Whether a batch actually agreed is a property
|
|
3468
|
+
* of that batch rather than of the workspace, so it stays the caller's
|
|
3469
|
+
* one-line check rather than a field here.
|
|
3288
3470
|
*
|
|
3289
|
-
*
|
|
3290
|
-
* `rangeOf` returns `Option.none()` for a dependency no catalog declares, per
|
|
3291
|
-
* the contract's convention. A failed catalog *assembly* — an unreadable or
|
|
3292
|
-
* malformed `pnpm-workspace.yaml`, a broken config-dependency hook — passes
|
|
3293
|
-
* through **typed** as the contract's `CatalogAssemblyError` (it used to be
|
|
3294
|
-
* folded into `DependencyResolutionError`'s defect `cause`, which forced
|
|
3295
|
-
* consumers to `_tag`-sniff `unknown`); only the remaining mechanism failure,
|
|
3296
|
-
* an unfindable workspace root, is wrapped as `DependencyResolutionError`.
|
|
3471
|
+
* An empty batch produces no tags under either style.
|
|
3297
3472
|
*/
|
|
3298
|
-
|
|
3473
|
+
tagsFor(releases: ReadonlyArray<PackageRelease>, options?: TagFormatOptions): ReadonlyArray<ReleaseTag>;
|
|
3299
3474
|
}
|
|
3300
3475
|
//#endregion
|
|
3301
3476
|
//#region src/WorkspaceStateSnapshot.d.ts
|
|
@@ -3303,23 +3478,24 @@ declare const PackageStateSnapshot_base: Schema.Class<PackageStateSnapshot, Sche
|
|
|
3303
3478
|
/** The package name. */
|
|
3304
3479
|
readonly name: Schema.NonEmptyString;
|
|
3305
3480
|
/**
|
|
3306
|
-
* The raw `version` string, as recorded at the captured moment —
|
|
3481
|
+
* The raw `version` string, as recorded at the captured moment — absent for
|
|
3307
3482
|
* a manifest that declared none.
|
|
3308
3483
|
*
|
|
3309
3484
|
* @remarks
|
|
3310
|
-
* `WorkspacePackage.version` is
|
|
3311
|
-
* ordinary pnpm shape
|
|
3312
|
-
*
|
|
3313
|
-
*
|
|
3314
|
-
*
|
|
3315
|
-
*
|
|
3316
|
-
*
|
|
3317
|
-
*
|
|
3318
|
-
* {@link WorkspaceStateSnapshot.
|
|
3319
|
-
*
|
|
3320
|
-
* `
|
|
3321
|
-
|
|
3322
|
-
|
|
3485
|
+
* Optional exactly as `WorkspacePackage.version` is: a version-less member is
|
|
3486
|
+
* an ordinary pnpm shape. Both capture paths — `WorkspaceSnapshots.at(ref)`
|
|
3487
|
+
* and `WorkspaceSnapshots.worktree()` — omit the key for such a member, so
|
|
3488
|
+
* the two sides of a diff agree without a placeholder. Never `""`: `make`
|
|
3489
|
+
* rejects it, and a snapshot serialized when `""` was the "no version"
|
|
3490
|
+
* sentinel decodes to the absent key.
|
|
3491
|
+
*
|
|
3492
|
+
* A version-less member is still a member: it appears in `packages` and
|
|
3493
|
+
* answers {@link WorkspaceStateSnapshot.package}, but is absent from
|
|
3494
|
+
* {@link WorkspaceStateSnapshot.versions}, resolves a `workspace:` specifier
|
|
3495
|
+
* to `Option.none()`, and fails its snapshot-bound `WorkspaceResolver`'s
|
|
3496
|
+
* `versionOf` typed.
|
|
3497
|
+
*/
|
|
3498
|
+
readonly version: Schema.decodeTo<Schema.optionalKey<Schema.NonEmptyString>, Schema.optionalKey<Schema.String>, never, never>;
|
|
3323
3499
|
/** POSIX path relative to the workspace root; `"."` for the root package. */
|
|
3324
3500
|
readonly relativePath: Schema.String;
|
|
3325
3501
|
/** Production dependencies. */
|
|
@@ -3450,15 +3626,13 @@ declare const WorkspaceStateSnapshot_base: Schema.Class<WorkspaceStateSnapshot,
|
|
|
3450
3626
|
export declare class WorkspaceStateSnapshot extends WorkspaceStateSnapshot_base {
|
|
3451
3627
|
#private;
|
|
3452
3628
|
/**
|
|
3453
|
-
* Every captured package's name → version
|
|
3629
|
+
* Every captured package's name → version, for the packages that declared
|
|
3630
|
+
* one. Total; O(1) after the first call.
|
|
3454
3631
|
*
|
|
3455
3632
|
* @remarks
|
|
3456
|
-
*
|
|
3457
|
-
*
|
|
3458
|
-
*
|
|
3459
|
-
* "has a usable version". A caller reading a version out of this map owes
|
|
3460
|
-
* the `""` check itself; {@link WorkspaceStateSnapshot.resolve} already
|
|
3461
|
-
* makes it.
|
|
3633
|
+
* A member whose manifest declared no version is **absent** from this map,
|
|
3634
|
+
* so every value is a real version and presence answers "has a version",
|
|
3635
|
+
* not membership — ask {@link WorkspaceStateSnapshot.package} for that.
|
|
3462
3636
|
*/
|
|
3463
3637
|
get versions(): ReadonlyMap<string, string>;
|
|
3464
3638
|
/** A single captured package by name, or `Option.none()`. Total. */
|
|
@@ -3684,6 +3858,21 @@ interface WorkspaceSnapshotsOptions {
|
|
|
3684
3858
|
* first call is honoured.
|
|
3685
3859
|
*/
|
|
3686
3860
|
readonly cwd?: string;
|
|
3861
|
+
/**
|
|
3862
|
+
* A ceiling for the root ascent from `cwd`, passed straight through to the
|
|
3863
|
+
* `stopAt` of {@link WorkspaceRoot}'s `find`.
|
|
3864
|
+
*
|
|
3865
|
+
* @remarks
|
|
3866
|
+
* Inclusive, and resolved to an absolute path exactly as `cwd` is. When no
|
|
3867
|
+
* root is found at or below the ceiling, `at(ref)` fails with
|
|
3868
|
+
* {@link WorkspaceRootNotFoundError} carrying the resolved `stopAt` rather
|
|
3869
|
+
* than adopting an enclosing directory's workspace. Pass the same value as
|
|
3870
|
+
* {@link WorkspaceDiscoveryOptions.stopAt} so every service agrees on the
|
|
3871
|
+
* root; the `Workspaces.*` composites forward one `stopAt` to all of them.
|
|
3872
|
+
*
|
|
3873
|
+
* @defaultValue no ceiling — the ascent runs to the filesystem root.
|
|
3874
|
+
*/
|
|
3875
|
+
readonly stopAt?: string | undefined;
|
|
3687
3876
|
/**
|
|
3688
3877
|
* Catalogs every snapshot this service produces carries as its
|
|
3689
3878
|
* `seededCatalogs` — consulted only where the snapshot's own catalogs cannot
|
|
@@ -3851,9 +4040,17 @@ export declare class WorkspaceSnapshots extends WorkspaceSnapshots_base {
|
|
|
3851
4040
|
/**
|
|
3852
4041
|
* Options shared by the composite layers.
|
|
3853
4042
|
*
|
|
4043
|
+
* @remarks
|
|
4044
|
+
* Derived from the option shapes of the services a composite builds —
|
|
4045
|
+
* {@link WorkspaceDiscoveryOptions}, {@link LockfileReaderOptions} and
|
|
4046
|
+
* {@link WorkspaceCatalogsOptions} — and handed to each of them unchanged, so a
|
|
4047
|
+
* root-resolving option one service grows cannot silently stop at the
|
|
4048
|
+
* composite. `cwd` and `stopAt` are re-declared here only to document them as
|
|
4049
|
+
* the one concern they are across the composite.
|
|
4050
|
+
*
|
|
3854
4051
|
* @public
|
|
3855
4052
|
*/
|
|
3856
|
-
interface WorkspacesOptions {
|
|
4053
|
+
interface WorkspacesOptions extends WorkspaceDiscoveryOptions, LockfileReaderOptions, WorkspaceCatalogsOptions {
|
|
3857
4054
|
/**
|
|
3858
4055
|
* The directory every root-consuming service resolves the workspace root
|
|
3859
4056
|
* from — one explicit concern, applied uniformly.
|
|
@@ -3861,8 +4058,23 @@ interface WorkspacesOptions {
|
|
|
3861
4058
|
* @defaultValue `process.cwd()`, read lazily on first use.
|
|
3862
4059
|
*/
|
|
3863
4060
|
readonly cwd?: string;
|
|
3864
|
-
/**
|
|
3865
|
-
|
|
4061
|
+
/**
|
|
4062
|
+
* A ceiling for the root ascent from `cwd`, forwarded to EVERY
|
|
4063
|
+
* root-resolving service the composite builds — {@link WorkspaceDiscovery},
|
|
4064
|
+
* {@link LockfileReader}, {@link WorkspaceCatalogs} and, on the git
|
|
4065
|
+
* composites, {@link WorkspaceSnapshots}.
|
|
4066
|
+
*
|
|
4067
|
+
* @remarks
|
|
4068
|
+
* Inclusive and resolved to an absolute path, as {@link WorkspaceRoot}'s
|
|
4069
|
+
* `find` does. Pass `stopAt: cwd` for a checkout nested inside someone
|
|
4070
|
+
* else's workspace: every service then fails with
|
|
4071
|
+
* {@link WorkspaceRootNotFoundError} consistently, instead of discovery
|
|
4072
|
+
* refusing the enclosing workspace while the lockfile and catalog reads
|
|
4073
|
+
* adopt it. A checkout that is itself a workspace root still resolves.
|
|
4074
|
+
*
|
|
4075
|
+
* @defaultValue no ceiling — the ascent runs to the filesystem root.
|
|
4076
|
+
*/
|
|
4077
|
+
readonly stopAt?: string | undefined;
|
|
3866
4078
|
}
|
|
3867
4079
|
/**
|
|
3868
4080
|
* Options for the composites that additionally provide {@link WorkspaceSnapshots}
|
|
@@ -4108,7 +4320,10 @@ export declare class Workspaces {
|
|
|
4108
4320
|
* verbatim.
|
|
4109
4321
|
*
|
|
4110
4322
|
* `directory` is the resolved **workspace root**, not the caller's cwd: a
|
|
4111
|
-
* project-local launcher has to run where the workspace is.
|
|
4323
|
+
* project-local launcher has to run where the workspace is. `stopAt` caps
|
|
4324
|
+
* that ascent exactly as {@link WorkspacesOptions.stopAt} does, and a
|
|
4325
|
+
* ceiling with no root at or below it is the same `Option.none()` as no
|
|
4326
|
+
* root at all.
|
|
4112
4327
|
*
|
|
4113
4328
|
* A consumer with no monorepo never needs this layer, and therefore never
|
|
4114
4329
|
* installs this package — `LocalExec.layerNone` and `LocalExec.layerFor`
|
|
@@ -4137,6 +4352,7 @@ export declare class Workspaces {
|
|
|
4137
4352
|
*/
|
|
4138
4353
|
static readonly localExecLayer: (options?: {
|
|
4139
4354
|
readonly cwd?: string;
|
|
4355
|
+
readonly stopAt?: string | undefined;
|
|
4140
4356
|
}) => Layer.Layer<LocalExec, never, PackageManagerDetector | WorkspaceRoot>;
|
|
4141
4357
|
/**
|
|
4142
4358
|
* Resolve every `catalog:` and `workspace:` specifier in one `Manifest`
|
|
@@ -4408,6 +4624,33 @@ interface WorkspaceDiscoverySkip {
|
|
|
4408
4624
|
*/
|
|
4409
4625
|
readonly cause: unknown;
|
|
4410
4626
|
}
|
|
4627
|
+
/**
|
|
4628
|
+
* Options for {@link findWorkspaceRootSync}: the consumer-supplied operations
|
|
4629
|
+
* plus the ascent's ceiling.
|
|
4630
|
+
*
|
|
4631
|
+
* @public
|
|
4632
|
+
*/
|
|
4633
|
+
interface FindWorkspaceRootSyncOptions extends WorkspacesSyncOptions {
|
|
4634
|
+
/**
|
|
4635
|
+
* A ceiling directory, with the semantics of `FindWorkspaceRootOptions.stopAt`
|
|
4636
|
+
* on the Effect surface. The ascent stops after probing it, so an unmarked
|
|
4637
|
+
* `stopAt` returns `null` rather than silently adopting an enclosing
|
|
4638
|
+
* repository's workspace.
|
|
4639
|
+
*
|
|
4640
|
+
* @remarks
|
|
4641
|
+
* Inclusive: a ceiling that is itself a workspace root is returned. The
|
|
4642
|
+
* ceiling is resolved through the supplied {@link SyncPath.resolve} at
|
|
4643
|
+
* lookup time, so a relative one is taken against the process working
|
|
4644
|
+
* directory when the call runs (with `node:path`). A ceiling that names no
|
|
4645
|
+
* ancestor of `cwd` never matches, and the ascent runs to the filesystem
|
|
4646
|
+
* root. Omit it for the unbounded ascent.
|
|
4647
|
+
*
|
|
4648
|
+
* Pass `stopAt: cwd` for a checkout nested inside someone else's workspace
|
|
4649
|
+
* (a self-hosted runner, `actions/checkout` with `path:`): a checkout that is
|
|
4650
|
+
* itself a root still resolves, and one that is not returns `null`.
|
|
4651
|
+
*/
|
|
4652
|
+
readonly stopAt?: string | undefined;
|
|
4653
|
+
}
|
|
4411
4654
|
/**
|
|
4412
4655
|
* The nearest workspace root at or above `cwd`, or `null`.
|
|
4413
4656
|
*
|
|
@@ -4428,9 +4671,17 @@ interface WorkspaceDiscoverySkip {
|
|
|
4428
4671
|
* Markers match the async service exactly: a `pnpm-workspace.yaml`, or a
|
|
4429
4672
|
* `package.json` carrying a `workspaces` field.
|
|
4430
4673
|
*
|
|
4674
|
+
* The ascent is unbounded unless `options.stopAt` names a ceiling
|
|
4675
|
+
* ({@link FindWorkspaceRootSyncOptions.stopAt}), with the Effect surface's
|
|
4676
|
+
* semantics: inclusive, resolved at lookup time. Where `WorkspaceRoot.find`
|
|
4677
|
+
* fails `WorkspaceRootNotFoundError` carrying the resolved ceiling, this
|
|
4678
|
+
* returns `null` — the facade is total and has no error channel, and the
|
|
4679
|
+
* caller already holds the ceiling it passed.
|
|
4680
|
+
*
|
|
4431
4681
|
* @param cwd - Where to start the ascent (typically `process.cwd()`),
|
|
4432
4682
|
* matching the Effect layers' `{ cwd }` option.
|
|
4433
|
-
* @param options - The consumer-supplied file and path operations
|
|
4683
|
+
* @param options - The consumer-supplied file and path operations, plus an
|
|
4684
|
+
* optional `stopAt` ceiling.
|
|
4434
4685
|
*
|
|
4435
4686
|
* @example
|
|
4436
4687
|
* ```ts
|
|
@@ -4451,7 +4702,7 @@ interface WorkspaceDiscoverySkip {
|
|
|
4451
4702
|
*
|
|
4452
4703
|
* @public
|
|
4453
4704
|
*/
|
|
4454
|
-
export declare const findWorkspaceRootSync: (cwd: string, options:
|
|
4705
|
+
export declare const findWorkspaceRootSync: (cwd: string, options: FindWorkspaceRootSyncOptions) => string | null;
|
|
4455
4706
|
/**
|
|
4456
4707
|
* Options for {@link getWorkspacePackagesSync}: the required consumer-supplied
|
|
4457
4708
|
* operations plus the traversal bound.
|
|
@@ -4539,5 +4790,5 @@ interface GetWorkspacePackagesSyncOptions extends WorkspacesSyncOptions {
|
|
|
4539
4790
|
*/
|
|
4540
4791
|
export declare const getWorkspacePackagesSync: (root: string, options: GetWorkspacePackagesSyncOptions) => ReadonlyArray<WorkspacePackage>;
|
|
4541
4792
|
//#endregion
|
|
4542
|
-
export type { CatalogAssemblyFailure, ChangeDetectionFailure, ChangeDetectorShape, ClassifyOptions, ConfigDependencyHooksShape, DependencyDiff, DuplicateCheckOptions, FindWorkspaceRootOptions, GetWorkspacePackagesSyncOptions, HookInjection, HookReplay, HookReplayContext, HookReplaySource, ImporterVersions, LockfileReadFailure, LockfileReaderOptions, LockfileReaderShape, PackageManagerDetectionFailure, PackageManagerDetectorShape, PackageRelease, PeerCheckOptions, PeerDependencyRules, PublishabilityDetectorShape, SyncDirectoryEntry, SyncFileSystem, SyncPath, TagClassification, TagFormatOptions, TrackingTagOptions, UnverifiedReason, VersioningDetectOptions, WorkspaceCatalogsOptions, WorkspaceCatalogsShape, WorkspaceDiscoveryFailure, WorkspaceDiscoveryOptions, WorkspaceDiscoveryShape, WorkspaceDiscoverySkip, WorkspaceDiscoverySkipKind, WorkspaceLookupFailure, WorkspaceRootShape, WorkspaceSnapshotAtFailure, WorkspaceSnapshotWorktreeFailure, WorkspaceSnapshotsOptions, WorkspaceSnapshotsShape, WorkspacesGitOptions, WorkspacesOptions, WorkspacesServices, WorkspacesSyncOptions };
|
|
4793
|
+
export type { CatalogAssemblyFailure, ChangeDetectionFailure, ChangeDetectorShape, ClassifyOptions, ConfigDependencyHooksShape, DependencyDiff, DuplicateCheckOptions, FindWorkspaceRootOptions, FindWorkspaceRootSyncOptions, GetWorkspacePackagesSyncOptions, HookInjection, HookReplay, HookReplayContext, HookReplaySource, ImporterVersions, LockfileReadFailure, LockfileReaderOptions, LockfileReaderShape, PackageManagerDetectionFailure, PackageManagerDetectorShape, PackageRelease, PeerCheckOptions, PeerDependencyRules, PublishabilityDetectorShape, SyncDirectoryEntry, SyncFileSystem, SyncPath, TagClassification, TagFormatOptions, TrackingTagOptions, UnverifiedReason, VersioningDetectOptions, WorkspaceCatalogsOptions, WorkspaceCatalogsShape, WorkspaceDiscoveryFailure, WorkspaceDiscoveryOptions, WorkspaceDiscoveryShape, WorkspaceDiscoverySkip, WorkspaceDiscoverySkipKind, WorkspaceLookupFailure, WorkspaceRootShape, WorkspaceSnapshotAtFailure, WorkspaceSnapshotWorktreeFailure, WorkspaceSnapshotsOptions, WorkspaceSnapshotsShape, WorkspacesGitOptions, WorkspacesOptions, WorkspacesServices, WorkspacesSyncOptions };
|
|
4543
4794
|
//# sourceMappingURL=index.d.ts.map
|