@effected/workspaces 0.26.0 → 0.28.0-next-20260927062352
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 +31 -10
- package/ConfigDependencySpec.js +170 -0
- package/PackedInstall.js +530 -51
- package/README.md +67 -18
- package/SourceBoundary.js +87 -3
- package/WorkspaceCatalogs.js +9 -4
- package/WorkspaceSnapshots.js +11 -5
- package/index.d.ts +217 -12
- package/index.js +2 -1
- package/internal/configDependencyFetch.js +146 -0
- package/internal/configDependencyResolution.js +83 -57
- package/internal/configDependencyShared.js +51 -0
- package/internal/configDependencySpecGrammar.js +15 -0
- package/internal/packedInstallPlan.js +73 -8
- package/internal/sourceText.js +32 -6
- package/package.json +4 -4
- package/testing.d.ts +484 -29
package/index.d.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { Git, GitCommandError, NotARepositoryError, UnknownRefError } from "@effected/git";
|
|
2
|
-
import { Context, Effect, FileSystem, Layer, Option, Path, Schema } from "effect";
|
|
2
|
+
import { Context, Effect, FileSystem, Layer, Option, Path, Result, Schema } from "effect";
|
|
3
3
|
import { CatalogAssemblyError, CatalogResolver, DependencyResolutionError, Manifest, PartialReleaseAgeGate, ReleaseAgeGate, UnresolvedDependencyError, WorkspaceResolver } from "@effected/npm";
|
|
4
4
|
import { GlobPattern } from "@effected/glob";
|
|
5
5
|
import { Lockfile, LockfileFramingError, LockfileIntegrity, LockfileParseError, ResolvedPackage, WorkspaceManifest } from "@effected/lockfiles";
|
|
6
6
|
import { Package } from "@effected/package-json";
|
|
7
7
|
import { ChildProcessSpawner } from "effect/unstable/process";
|
|
8
|
+
import { SemVer } from "@effected/semver";
|
|
8
9
|
import { LocalExec } from "@effected/commands";
|
|
9
10
|
//#region src/WorkspacePackage.d.ts
|
|
10
11
|
declare const PublishConfig_base: Schema.Class<PublishConfig, Schema.Struct<{
|
|
@@ -934,12 +935,43 @@ interface PeerDependencyRules {
|
|
|
934
935
|
export declare const NoPeerDependencyRules: PeerDependencyRules;
|
|
935
936
|
/**
|
|
936
937
|
* Where a replayed config dependency's declared version was found: the
|
|
937
|
-
* `node_modules/.pnpm-config` copy, the pnpm store's `links/` tree,
|
|
938
|
+
* `node_modules/.pnpm-config` copy, the pnpm store's `links/` tree, the store
|
|
939
|
+
* after {@link ConfigDependencyHooks.layerSubprocess} fetched it, or a
|
|
938
940
|
* {@link ConfigDependencyHooks.layerFrom} entry.
|
|
939
941
|
*
|
|
940
942
|
* @public
|
|
941
943
|
*/
|
|
942
|
-
type HookReplaySource = "installed" | "store" | "supplied";
|
|
944
|
+
type HookReplaySource = "installed" | "store" | "fetched" | "supplied";
|
|
945
|
+
/**
|
|
946
|
+
* What the side whose `configDependencies` are being replayed recorded beyond
|
|
947
|
+
* that map — the input the fetch rung needs to verify what it fetches.
|
|
948
|
+
*
|
|
949
|
+
* @remarks
|
|
950
|
+
* Both fields are optional, and omitting the whole context is always valid:
|
|
951
|
+
* nothing here changes which version is replayed. `lockfile` matters only when
|
|
952
|
+
* a replaying layer must fetch a version installed nowhere, and only
|
|
953
|
+
* {@link ConfigDependencyHooks.layerSubprocess} fetches. Without it, a bare
|
|
954
|
+
* `configDependencies` spec has no integrity to verify a fetch against, so the
|
|
955
|
+
* fetch fails closed with `reason: "integrityUnavailable"`.
|
|
956
|
+
*
|
|
957
|
+
* `WorkspaceCatalogs` passes the working tree's lockfile, and
|
|
958
|
+
* `WorkspaceSnapshots.at(ref)` passes the lockfile and the ref it read at, so
|
|
959
|
+
* each side of a diff is verified against its own record.
|
|
960
|
+
*
|
|
961
|
+
* @public
|
|
962
|
+
*/
|
|
963
|
+
interface HookReplayContext {
|
|
964
|
+
/**
|
|
965
|
+
* The declaring side's `pnpm-lock.yaml` text, whose env preamble records each
|
|
966
|
+
* config dependency's integrity. `undefined` when that side has no lockfile.
|
|
967
|
+
*/
|
|
968
|
+
readonly lockfile?: string | undefined;
|
|
969
|
+
/**
|
|
970
|
+
* The git ref whose `pnpm-workspace.yaml` declared the `configDependencies`,
|
|
971
|
+
* used to name the side in error messages. `undefined` for the working tree.
|
|
972
|
+
*/
|
|
973
|
+
readonly ref?: string | undefined;
|
|
974
|
+
}
|
|
943
975
|
/**
|
|
944
976
|
* Which version of a config dependency a replay actually loaded, and where
|
|
945
977
|
* that version came from.
|
|
@@ -949,7 +981,9 @@ type HookReplaySource = "installed" | "store" | "supplied";
|
|
|
949
981
|
* WHAT the hooks injected but which pinned version injected it — the
|
|
950
982
|
* evidence that a "range moved" row came from a config-dependency bump and
|
|
951
983
|
* not from a hook edit. `installed` is the `node_modules/.pnpm-config` copy,
|
|
952
|
-
* `store` the pnpm store's `links/` tree, `
|
|
984
|
+
* `store` the pnpm store's `links/` tree, `fetched` the store copy
|
|
985
|
+
* {@link ConfigDependencyHooks.layerSubprocess} fetched, verified, because
|
|
986
|
+
* neither held the declared version, and `supplied` a
|
|
953
987
|
* {@link ConfigDependencyHooks.layerFrom} entry.
|
|
954
988
|
*
|
|
955
989
|
* @public
|
|
@@ -1048,8 +1082,12 @@ interface ConfigDependencyHooksShape {
|
|
|
1048
1082
|
* threaded config so hooks merge onto them rather than replacing them.
|
|
1049
1083
|
* Omitted means "the workspace file declares none", which is different
|
|
1050
1084
|
* from "nobody looked" — the caller owns that distinction.
|
|
1085
|
+
* @param context - What the declaring side recorded beyond the map: its
|
|
1086
|
+
* lockfile, which verifies a fetched version, and its ref, which names it
|
|
1087
|
+
* in errors. Only {@link ConfigDependencyHooks.layerSubprocess} reads the
|
|
1088
|
+
* lockfile; omitting the context never changes which version is replayed.
|
|
1051
1089
|
*/
|
|
1052
|
-
readonly inject: (root: string, configDependencies: Readonly<Record<string, string>>, seed: Readonly<Record<string, Readonly<Record<string, string>>>>, rules?: PeerDependencyRules) => Effect.Effect<HookInjection, CatalogAssemblyError>;
|
|
1090
|
+
readonly inject: (root: string, configDependencies: Readonly<Record<string, string>>, seed: Readonly<Record<string, Readonly<Record<string, string>>>>, rules?: PeerDependencyRules, context?: HookReplayContext) => Effect.Effect<HookInjection, CatalogAssemblyError>;
|
|
1053
1091
|
}
|
|
1054
1092
|
declare const ConfigDependencyHooks_base: Context.ServiceClass<ConfigDependencyHooks, "@effected/workspaces/ConfigDependencyHooks", ConfigDependencyHooksShape>;
|
|
1055
1093
|
/**
|
|
@@ -1100,9 +1138,10 @@ export declare class ConfigDependencyHooks extends ConfigDependencyHooks_base {
|
|
|
1100
1138
|
* `links/<name>/<declared>/*\/node_modules/<name>` (the store is located
|
|
1101
1139
|
* from `node_modules/.modules.yaml`, the realpath of any `.pnpm-config`
|
|
1102
1140
|
* entry, then `$PNPM_HOME` / `$XDG_DATA_HOME` / the platform default), else
|
|
1103
|
-
* a typed error naming the package, the
|
|
1104
|
-
* installed, the stores searched, and the
|
|
1105
|
-
* (`pnpm add --config <name>@<version>` in a throwaway
|
|
1141
|
+
* a typed error with `reason: "notInstalled"` naming the package, the
|
|
1142
|
+
* declared version, what is installed, the stores searched, and the
|
|
1143
|
+
* remediation (`pnpm add --config <name>@<version>` in a throwaway
|
|
1144
|
+
* workspace, or the subprocess layer, which fetches). In the
|
|
1106
1145
|
* chosen directory the first of `pnpmfile.mjs`, `pnpmfile.cjs`,
|
|
1107
1146
|
* `pnpmfile.js` present in one directory listing is loaded; a listed but
|
|
1108
1147
|
* unreadable file fails typed at import time, never as "ships no hook".
|
|
@@ -1174,9 +1213,24 @@ export declare class ConfigDependencyHooks extends ConfigDependencyHooks_base {
|
|
|
1174
1213
|
*
|
|
1175
1214
|
* The declared-version resolution is the PARENT's: the same ladder as
|
|
1176
1215
|
* `layerLive` (`.pnpm-config` when it holds the declared version, else the
|
|
1177
|
-
* pnpm store
|
|
1178
|
-
*
|
|
1179
|
-
*
|
|
1216
|
+
* pnpm store) runs here, and the child receives the resolved
|
|
1217
|
+
* `[name, fileUrl]` pairs as one JSON argv argument.
|
|
1218
|
+
*
|
|
1219
|
+
* One rung more than `layerLive`: a version held nowhere is FETCHED into
|
|
1220
|
+
* the store rather than failing, so a diff whose base side declares a
|
|
1221
|
+
* version this checkout never installed still replays that version. pnpm
|
|
1222
|
+
* runs `install --frozen-lockfile` in a scratch workspace (removed
|
|
1223
|
+
* afterwards) whose lockfile pins the integrity the declaring side recorded
|
|
1224
|
+
* — the inline `<version>+<integrity>` spec, else that side's
|
|
1225
|
+
* `pnpm-lock.yaml` env preamble ({@link HookReplayContext.lockfile}) — so
|
|
1226
|
+
* pnpm itself refuses a download that does not match. It writes to the
|
|
1227
|
+
* first store the ladder searched, so the next replay finds the version
|
|
1228
|
+
* there. The replay records `source: "fetched"`. Fail-closed: two recorded
|
|
1229
|
+
* integrities that disagree fail with `reason: "integrityMismatch"`, none
|
|
1230
|
+
* with `reason: "integrityUnavailable"` — nothing is fetched in either case
|
|
1231
|
+
* — and a fetch that fails with `reason: "fetchFailed"`, keeping the
|
|
1232
|
+
* not-installed remediation. The fetch needs `pnpm` 11 or 12 on `PATH` and
|
|
1233
|
+
* registry access, and is bounded at two minutes. An empty `configDependencies`, or one whose dependencies all
|
|
1180
1234
|
* ship no pnpmfile, returns the seed without spawning anything; a `..` path
|
|
1181
1235
|
* segment in a dependency name fails typed **before** any spawn; a
|
|
1182
1236
|
* load failure in the child — a syntax error, a throwing top level, an
|
|
@@ -1210,6 +1264,157 @@ export declare class ConfigDependencyHooks extends ConfigDependencyHooks_base {
|
|
|
1210
1264
|
static readonly layerSubprocess: Layer.Layer<ConfigDependencyHooks, never, ChildProcessSpawner.ChildProcessSpawner>;
|
|
1211
1265
|
}
|
|
1212
1266
|
//#endregion
|
|
1267
|
+
//#region src/ConfigDependencySpec.d.ts
|
|
1268
|
+
declare const InvalidConfigDependencySpecError_base: Schema.Class<InvalidConfigDependencySpecError, Schema.TaggedStruct<"InvalidConfigDependencySpecError", {
|
|
1269
|
+
/** The raw spec string that failed validation. */
|
|
1270
|
+
readonly input: Schema.String;
|
|
1271
|
+
/**
|
|
1272
|
+
* Which half of the spec failed: `version` (the text before the first
|
|
1273
|
+
* `+` is not an exact SemVer version — ranges, partial versions,
|
|
1274
|
+
* dist-tags and padded values all land here) or `integrity` (the text
|
|
1275
|
+
* after the first `+` is not an SRI `<algo>-<base64>` hash).
|
|
1276
|
+
*/
|
|
1277
|
+
readonly reason: Schema.Literals<readonly ["version", "integrity"]>;
|
|
1278
|
+
}>, import("effect/Cause").YieldableError>;
|
|
1279
|
+
/**
|
|
1280
|
+
* Indicates that a string could not be parsed as a pnpm `configDependencies`
|
|
1281
|
+
* spec (`<version>[+<integrity>]`).
|
|
1282
|
+
*
|
|
1283
|
+
* Raised by {@link ConfigDependencySpec.parse} and
|
|
1284
|
+
* {@link ConfigDependencySpec.parseResult}; the decode direction of
|
|
1285
|
+
* {@link ConfigDependencySpec.FromString} reports the same failure through a
|
|
1286
|
+
* generic `Schema` parse error carrying the same message. The offending string
|
|
1287
|
+
* is preserved on `input`; `reason` says which half failed.
|
|
1288
|
+
*
|
|
1289
|
+
* @public
|
|
1290
|
+
*/
|
|
1291
|
+
export declare class InvalidConfigDependencySpecError extends InvalidConfigDependencySpecError_base {
|
|
1292
|
+
get message(): string;
|
|
1293
|
+
}
|
|
1294
|
+
declare const ConfigDependencySpec_base: Schema.Class<ConfigDependencySpec, Schema.Struct<{
|
|
1295
|
+
/**
|
|
1296
|
+
* The exact declared version. Never carries build metadata — the spec
|
|
1297
|
+
* grammar cannot express it (see the class remarks), and a version
|
|
1298
|
+
* constructed with build identifiers is rejected at construction.
|
|
1299
|
+
*/
|
|
1300
|
+
readonly version: Schema.decodeTo<Schema.declareConstructor<SemVer, {
|
|
1301
|
+
readonly major: number;
|
|
1302
|
+
readonly minor: number;
|
|
1303
|
+
readonly patch: number;
|
|
1304
|
+
readonly prerelease: readonly (string | number)[];
|
|
1305
|
+
readonly build: readonly string[];
|
|
1306
|
+
}, readonly [Schema.Struct<{
|
|
1307
|
+
readonly major: Schema.Number;
|
|
1308
|
+
readonly minor: Schema.Number;
|
|
1309
|
+
readonly patch: Schema.Number;
|
|
1310
|
+
readonly prerelease: Schema.$Array<Schema.Union<readonly [Schema.String, Schema.Number]>>;
|
|
1311
|
+
readonly build: Schema.$Array<Schema.String>;
|
|
1312
|
+
}>], {
|
|
1313
|
+
readonly major: number;
|
|
1314
|
+
readonly minor: number;
|
|
1315
|
+
readonly patch: number;
|
|
1316
|
+
readonly prerelease: readonly (string | number)[];
|
|
1317
|
+
readonly build: readonly string[];
|
|
1318
|
+
}>, Schema.Struct<{
|
|
1319
|
+
readonly major: Schema.Number;
|
|
1320
|
+
readonly minor: Schema.Number;
|
|
1321
|
+
readonly patch: Schema.Number;
|
|
1322
|
+
readonly prerelease: Schema.$Array<Schema.Union<readonly [Schema.String, Schema.Number]>>;
|
|
1323
|
+
readonly build: Schema.$Array<Schema.String>;
|
|
1324
|
+
}>, never, never>;
|
|
1325
|
+
/**
|
|
1326
|
+
* The inline integrity (e.g. `sha512-m35m…==`): `@effected/npm`'s
|
|
1327
|
+
* `SriIntegrityHash`, the shared restriction of the `IntegrityHash` brand
|
|
1328
|
+
* to the SRI `<algo>-<base64>` form. `None` on the bare form pnpm 11+
|
|
1329
|
+
* writes.
|
|
1330
|
+
*/
|
|
1331
|
+
readonly integrity: Schema.Option<Schema.brand<Schema.String, "IntegrityHash">>;
|
|
1332
|
+
}>, {}>;
|
|
1333
|
+
/**
|
|
1334
|
+
* A pnpm `configDependencies` spec from `pnpm-workspace.yaml`: an exact
|
|
1335
|
+
* `version` and, on the legacy inline form only, an SRI `integrity`.
|
|
1336
|
+
*
|
|
1337
|
+
* @remarks
|
|
1338
|
+
* pnpm 11 and later write the **bare** form — `pnpm add --config` records
|
|
1339
|
+
* `0.11.1`, and the integrity lives in the lockfile. The inline form
|
|
1340
|
+
* `0.11.1+sha512-<base64>` is deprecated but still read, and workspaces in the
|
|
1341
|
+
* wild still carry it. {@link ConfigDependencySpec.bare} renders the bare form
|
|
1342
|
+
* (a tool normalizing the field writes this); `toString()` re-renders the form
|
|
1343
|
+
* that was parsed, so a tool that must not rewrite the field round-trips it
|
|
1344
|
+
* byte-identically.
|
|
1345
|
+
*
|
|
1346
|
+
* The first `+` always begins the integrity, never semver build metadata, so
|
|
1347
|
+
* `0.11.1+sha512-…` is version `0.11.1` plus integrity `sha512-…`; a tail that
|
|
1348
|
+
* is not an SRI hash fails with `reason: "integrity"` rather than falling back
|
|
1349
|
+
* to build-metadata parsing.
|
|
1350
|
+
*
|
|
1351
|
+
* `version` is `@effected/semver`'s `SemVer`: prerelease versions are
|
|
1352
|
+
* accepted, ranges (`^0.11.1`), partial versions, dist-tags, a `v` prefix and
|
|
1353
|
+
* padded values are not.
|
|
1354
|
+
*
|
|
1355
|
+
* @example
|
|
1356
|
+
* ```ts
|
|
1357
|
+
* import { ConfigDependencySpec } from "@effected/workspaces";
|
|
1358
|
+
* import { Effect } from "effect";
|
|
1359
|
+
*
|
|
1360
|
+
* const program = Effect.gen(function* () {
|
|
1361
|
+
* const spec = yield* ConfigDependencySpec.parse("0.11.1+sha512-m35m…==");
|
|
1362
|
+
* return [spec.bare, spec.hasIntegrity] as const; // ["0.11.1", true]
|
|
1363
|
+
* });
|
|
1364
|
+
* ```
|
|
1365
|
+
*
|
|
1366
|
+
* @public
|
|
1367
|
+
*/
|
|
1368
|
+
export declare class ConfigDependencySpec extends ConfigDependencySpec_base {
|
|
1369
|
+
/**
|
|
1370
|
+
* Schema transformation between the `<version>[+<integrity>]` string and a
|
|
1371
|
+
* {@link ConfigDependencySpec}. Decoding parses via
|
|
1372
|
+
* {@link ConfigDependencySpec.parseResult} (the surfaces share one grammar);
|
|
1373
|
+
* encoding prints `toString()`, the form that was parsed.
|
|
1374
|
+
*/
|
|
1375
|
+
static readonly FromString: Schema.Codec<ConfigDependencySpec, string>;
|
|
1376
|
+
/**
|
|
1377
|
+
* Parse a `configDependencies` spec, synchronously, returning a `Result`
|
|
1378
|
+
* instead of an `Effect`.
|
|
1379
|
+
*
|
|
1380
|
+
* @remarks
|
|
1381
|
+
* The text before the first `+` must be an exact SemVer version that
|
|
1382
|
+
* renders back to itself byte for byte (`SemVer.isPinnable`, plus a
|
|
1383
|
+
* canonical-spelling check so `toString()` round-trips); the text after it,
|
|
1384
|
+
* when present, must be an SRI `<algo>-<base64>` hash. An empty tail
|
|
1385
|
+
* (`0.11.1+`) is an integrity failure, not a bare spec.
|
|
1386
|
+
* {@link ConfigDependencySpec.parse} is defined in terms of this function.
|
|
1387
|
+
*
|
|
1388
|
+
* @param input - the spec string to parse
|
|
1389
|
+
* @returns a `Result` succeeding with the parsed {@link ConfigDependencySpec},
|
|
1390
|
+
* or failing with {@link InvalidConfigDependencySpecError} naming the half
|
|
1391
|
+
* that failed.
|
|
1392
|
+
*/
|
|
1393
|
+
static parseResult(input: string): Result.Result<ConfigDependencySpec, InvalidConfigDependencySpecError>;
|
|
1394
|
+
/**
|
|
1395
|
+
* Parse a `configDependencies` spec. Defined in terms of
|
|
1396
|
+
* {@link ConfigDependencySpec.parseResult} — synchronous callers can use
|
|
1397
|
+
* that variant directly.
|
|
1398
|
+
*
|
|
1399
|
+
* @param input - the spec string to parse
|
|
1400
|
+
* @returns the parsed {@link ConfigDependencySpec}. Fails with
|
|
1401
|
+
* {@link InvalidConfigDependencySpecError} when `input` is not a valid spec.
|
|
1402
|
+
*/
|
|
1403
|
+
static readonly parse: (input: string) => Effect.Effect<ConfigDependencySpec, InvalidConfigDependencySpecError, never>;
|
|
1404
|
+
/** Whether the spec carries an inline integrity (the legacy form). */
|
|
1405
|
+
get hasIntegrity(): boolean;
|
|
1406
|
+
/**
|
|
1407
|
+
* The bare `<version>` form — what pnpm 11+ writes to `configDependencies`
|
|
1408
|
+
* — with any inline integrity dropped.
|
|
1409
|
+
*/
|
|
1410
|
+
get bare(): string;
|
|
1411
|
+
/**
|
|
1412
|
+
* The spec as parsed: `<version>` or `<version>+<integrity>`. The encode
|
|
1413
|
+
* direction of {@link ConfigDependencySpec.FromString} prints exactly this.
|
|
1414
|
+
*/
|
|
1415
|
+
toString(): string;
|
|
1416
|
+
}
|
|
1417
|
+
//#endregion
|
|
1213
1418
|
//#region src/DependencyGraph.d.ts
|
|
1214
1419
|
declare const CyclicDependencyError_base: Schema.Class<CyclicDependencyError, Schema.TaggedStruct<"CyclicDependencyError", {
|
|
1215
1420
|
/** The packages participating in the cycle. */
|
|
@@ -4312,5 +4517,5 @@ interface GetWorkspacePackagesSyncOptions extends WorkspacesSyncOptions {
|
|
|
4312
4517
|
*/
|
|
4313
4518
|
export declare const getWorkspacePackagesSync: (root: string, options: GetWorkspacePackagesSyncOptions) => ReadonlyArray<WorkspacePackage>;
|
|
4314
4519
|
//#endregion
|
|
4315
|
-
export type { CatalogAssemblyFailure, ChangeDetectionFailure, ChangeDetectorShape, ClassifyOptions, ConfigDependencyHooksShape, DependencyDiff, DuplicateCheckOptions, FindWorkspaceRootOptions, GetWorkspacePackagesSyncOptions, HookInjection, HookReplay, 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 };
|
|
4520
|
+
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 };
|
|
4316
4521
|
//# sourceMappingURL=index.d.ts.map
|
package/index.js
CHANGED
|
@@ -4,6 +4,7 @@ import { PackageNotFoundError, WorkspaceDiscovery, WorkspaceDiscoveryError, Work
|
|
|
4
4
|
import { CyclicDependencyError, DependencyGraph } from "./DependencyGraph.js";
|
|
5
5
|
import { ChangeDetectionError, ChangeDetectionOptions, ChangeDetector } from "./ChangeDetector.js";
|
|
6
6
|
import { ConfigDependencyHooks, NoPeerDependencyRules } from "./ConfigDependencyHooks.js";
|
|
7
|
+
import { ConfigDependencySpec, InvalidConfigDependencySpecError } from "./ConfigDependencySpec.js";
|
|
7
8
|
import { Dependent, DuplicateCheck, DuplicateInstance, DuplicatedPackage, DuplicatedVersion } from "./DuplicateCheck.js";
|
|
8
9
|
import { DetectedPackageManager, PackageManagerDetectionError, PackageManagerDetector, PackageManagerEvidence, PackageManagerName } from "./PackageManagerName.js";
|
|
9
10
|
import { LockfileReadError, LockfileReader } from "./LockfileReader.js";
|
|
@@ -17,4 +18,4 @@ import { WorkspaceSnapshots } from "./WorkspaceSnapshots.js";
|
|
|
17
18
|
import { Workspaces } from "./Workspaces.js";
|
|
18
19
|
import { findWorkspaceRootSync, getWorkspacePackagesSync } from "./WorkspacesSync.js";
|
|
19
20
|
|
|
20
|
-
export { CatalogSet, ChangeDetectionError, ChangeDetectionOptions, ChangeDetector, ConfigDependencyHooks, CyclicDependencyError, DependencyGraph, Dependent, DetectedPackageManager, DuplicateCheck, DuplicateInstance, DuplicatedPackage, DuplicatedVersion, LockfileReadError, LockfileReader, NoPeerDependencyRules, PackageManagerDetectionError, PackageManagerDetector, PackageManagerEvidence, PackageManagerName, PackageNotFoundError, PackageStateSnapshot, PeerCheck, PeerParent, PublishConfig, PublishTarget, PublishabilityDetector, ReleaseTag, TagStyle, TrackingTag, UnsatisfiedPeer, VersioningStrategy, VersioningStrategyType, WORKSPACE_MARKERS, WorkspaceCatalogs, WorkspaceDiscovery, WorkspaceDiscoveryError, WorkspaceInfo, WorkspaceManifestError, WorkspacePackage, WorkspacePatternError, WorkspaceRoot, WorkspaceRootNotFoundError, WorkspaceSnapshots, WorkspaceStateSnapshot, Workspaces, classifyTag, findWorkspaceRootSync, getWorkspacePackagesSync };
|
|
21
|
+
export { CatalogSet, ChangeDetectionError, ChangeDetectionOptions, ChangeDetector, ConfigDependencyHooks, ConfigDependencySpec, CyclicDependencyError, DependencyGraph, Dependent, DetectedPackageManager, DuplicateCheck, DuplicateInstance, DuplicatedPackage, DuplicatedVersion, InvalidConfigDependencySpecError, LockfileReadError, LockfileReader, NoPeerDependencyRules, PackageManagerDetectionError, PackageManagerDetector, PackageManagerEvidence, PackageManagerName, PackageNotFoundError, PackageStateSnapshot, PeerCheck, PeerParent, PublishConfig, PublishTarget, PublishabilityDetector, ReleaseTag, TagStyle, TrackingTag, UnsatisfiedPeer, VersioningStrategy, VersioningStrategyType, WORKSPACE_MARKERS, WorkspaceCatalogs, WorkspaceDiscovery, WorkspaceDiscoveryError, WorkspaceInfo, WorkspaceManifestError, WorkspacePackage, WorkspacePatternError, WorkspaceRoot, WorkspaceRootNotFoundError, WorkspaceSnapshots, WorkspaceStateSnapshot, Workspaces, classifyTag, findWorkspaceRootSync, getWorkspacePackagesSync };
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { carries, manifestVersion, messageOf, sideLabel } from "./configDependencyShared.js";
|
|
2
|
+
import { splitConfigDependencySpec } from "./configDependencySpecGrammar.js";
|
|
3
|
+
import { Duration, Effect } from "effect";
|
|
4
|
+
import { IntegrityHash } from "@effected/npm";
|
|
5
|
+
import { Run } from "@effected/commands";
|
|
6
|
+
import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";
|
|
7
|
+
import { mkdtemp, readFile, realpath, rm, writeFile } from "node:fs/promises";
|
|
8
|
+
import { tmpdir } from "node:os";
|
|
9
|
+
import { basename, dirname, join } from "node:path";
|
|
10
|
+
|
|
11
|
+
//#region src/internal/configDependencyFetch.ts
|
|
12
|
+
const fetchFailure = (reason, message, cause) => cause === void 0 ? {
|
|
13
|
+
reason,
|
|
14
|
+
message
|
|
15
|
+
} : {
|
|
16
|
+
reason,
|
|
17
|
+
message,
|
|
18
|
+
cause
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* The ceiling on one fetch. It downloads one small tarball; two minutes covers
|
|
22
|
+
* a slow registry without letting a hung pnpm stall catalog assembly forever.
|
|
23
|
+
*/
|
|
24
|
+
const FETCH_TIMEOUT = Duration.minutes(2);
|
|
25
|
+
/**
|
|
26
|
+
* The integrity the fetched copy must match, decided before anything is
|
|
27
|
+
* spawned: the inline spec integrity (legacy) and the declaring side's
|
|
28
|
+
* lockfile preamble entry for exactly `<name>@<version>`. Both present and
|
|
29
|
+
* different, neither present, an inline integrity that is not SRI, or a
|
|
30
|
+
* lockfile that cannot be read all fail typed. A lockfile that records the
|
|
31
|
+
* name at a different version is not a source for this version.
|
|
32
|
+
*
|
|
33
|
+
* @remarks
|
|
34
|
+
* An unreadable lockfile fails closed even when the spec carries an inline
|
|
35
|
+
* integrity: the lockfile is the declaring side's own checksum store, and a
|
|
36
|
+
* store that cannot be read cannot be confirmed to agree with the inline pin.
|
|
37
|
+
*/
|
|
38
|
+
const expectedIntegrity = (request) => Effect.gen(function* () {
|
|
39
|
+
const { name, version, side } = request;
|
|
40
|
+
const key = `${name}@${version}`;
|
|
41
|
+
const inline = splitConfigDependencySpec(request.spec).integrity;
|
|
42
|
+
if (inline !== void 0 && !IntegrityHash.isSri(inline)) return yield* Effect.fail(fetchFailure("integrityUnavailable", `config dependency ${key} declared by ${sideLabel(side)} carries an inline integrity that is not an SRI hash, so a fetched copy cannot be verified and nothing was fetched.`));
|
|
43
|
+
const lock = (yield* request.locks.pipe(Effect.mapError((cause) => fetchFailure("integrityUnavailable", `config dependency ${key} must be fetched, but the pnpm-lock.yaml of ${sideLabel(side)} cannot be read (${messageOf(cause)}), so its recorded integrity cannot be checked and nothing was fetched.`, cause))))?.get(name);
|
|
44
|
+
const locked = lock !== void 0 && lock.version === version ? lock.integrity : void 0;
|
|
45
|
+
if (inline !== void 0 && locked !== void 0 && inline !== locked) return yield* Effect.fail(fetchFailure("integrityMismatch", `config dependency ${key} declared by ${sideLabel(side)} has two different recorded integrities: pnpm-workspace.yaml pins ${inline} and pnpm-lock.yaml records ${locked}. Nothing was fetched; reconcile the two (a fresh \`pnpm install\` rewrites the lockfile) before retrying.`));
|
|
46
|
+
const integrity = inline ?? locked;
|
|
47
|
+
if (integrity === void 0) return yield* Effect.fail(fetchFailure("integrityUnavailable", `config dependency ${key} must be fetched, but ${sideLabel(side)} records no integrity for it (no inline integrity in pnpm-workspace.yaml and no pnpm-lock.yaml entry for that version), so a fetched copy could not be verified and nothing was fetched.`));
|
|
48
|
+
return integrity;
|
|
49
|
+
});
|
|
50
|
+
/**
|
|
51
|
+
* The scratch workspace's `pnpm-workspace.yaml`. Every scalar is a
|
|
52
|
+
* JSON-quoted string, which YAML reads verbatim, so no declared text can
|
|
53
|
+
* change the document's structure.
|
|
54
|
+
*/
|
|
55
|
+
const scratchWorkspaceYaml = (name, version) => `configDependencies:\n ${JSON.stringify(name)}: ${JSON.stringify(version)}\n`;
|
|
56
|
+
/**
|
|
57
|
+
* The scratch workspace's `pnpm-lock.yaml`: the env preamble pnpm 11 and 12
|
|
58
|
+
* write for a lone config dependency, pinned to `integrity`, followed by the
|
|
59
|
+
* empty lockfile document they write when there are no projects.
|
|
60
|
+
*/
|
|
61
|
+
const scratchLockfile = (name, version, integrity) => {
|
|
62
|
+
const key = JSON.stringify(`${name}@${version}`);
|
|
63
|
+
return [
|
|
64
|
+
"---",
|
|
65
|
+
"lockfileVersion: '9.0'",
|
|
66
|
+
"",
|
|
67
|
+
"importers:",
|
|
68
|
+
"",
|
|
69
|
+
" .:",
|
|
70
|
+
" configDependencies:",
|
|
71
|
+
` ${JSON.stringify(name)}:`,
|
|
72
|
+
` specifier: ${JSON.stringify(version)}`,
|
|
73
|
+
` version: ${JSON.stringify(version)}`,
|
|
74
|
+
"",
|
|
75
|
+
"packages:",
|
|
76
|
+
"",
|
|
77
|
+
` ${key}:`,
|
|
78
|
+
` resolution: {integrity: ${JSON.stringify(integrity)}}`,
|
|
79
|
+
"",
|
|
80
|
+
"snapshots:",
|
|
81
|
+
"",
|
|
82
|
+
` ${key}: {}`,
|
|
83
|
+
"",
|
|
84
|
+
"---",
|
|
85
|
+
""
|
|
86
|
+
].join("\n");
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* The `--store-dir` to hand pnpm so it writes into the store the ladder
|
|
90
|
+
* searched first. Discovered stores are the versioned directories
|
|
91
|
+
* (`<root>/v11`), and pnpm appends that segment itself, so a versioned
|
|
92
|
+
* directory is passed as its parent. `undefined` (pnpm's own default) when no
|
|
93
|
+
* store was discovered.
|
|
94
|
+
*/
|
|
95
|
+
const storeDirArgument = (stores) => {
|
|
96
|
+
const first = stores[0];
|
|
97
|
+
if (first === void 0) return void 0;
|
|
98
|
+
return /^v\d+$/.test(basename(first)) ? dirname(first) : first;
|
|
99
|
+
};
|
|
100
|
+
/** The pnpm argv for the verified fetch. */
|
|
101
|
+
const fetchArgs = (scratch, stores) => {
|
|
102
|
+
const storeDir = storeDirArgument(stores);
|
|
103
|
+
return [
|
|
104
|
+
"install",
|
|
105
|
+
"--frozen-lockfile",
|
|
106
|
+
"--dir",
|
|
107
|
+
scratch,
|
|
108
|
+
...storeDir === void 0 ? [] : ["--store-dir", storeDir]
|
|
109
|
+
];
|
|
110
|
+
};
|
|
111
|
+
/** Whether `resolved` sits under a store's `links/<name>/<version>/` tree. */
|
|
112
|
+
const isStoreEntry = (resolved, name, version) => resolved.split(/[/\\]/).join("/").includes(`/links/${name}/${version}/`);
|
|
113
|
+
/**
|
|
114
|
+
* Build the fetch rung over a spawner. Wired only by
|
|
115
|
+
* `ConfigDependencyHooks.layerSubprocess`.
|
|
116
|
+
*/
|
|
117
|
+
const makeFetchConfigDependency = (spawner) => (request) => Effect.scoped(Effect.gen(function* () {
|
|
118
|
+
const { name, version } = request;
|
|
119
|
+
const key = `${name}@${version}`;
|
|
120
|
+
const failed = (message, cause) => fetchFailure("fetchFailed", message, cause);
|
|
121
|
+
const io = (what, run) => Effect.tryPromise({
|
|
122
|
+
try: run,
|
|
123
|
+
catch: (cause) => failed(`${what} failed: ${messageOf(cause)}`, cause)
|
|
124
|
+
});
|
|
125
|
+
const integrity = yield* expectedIntegrity(request);
|
|
126
|
+
const scratch = yield* Effect.acquireRelease(io("creating a scratch workspace", () => mkdtemp(join(tmpdir(), "effected-config-dependency-"))), (dir) => Effect.promise(() => rm(dir, {
|
|
127
|
+
recursive: true,
|
|
128
|
+
force: true
|
|
129
|
+
}).catch(() => void 0)));
|
|
130
|
+
const lockfile = scratchLockfile(name, version, integrity);
|
|
131
|
+
yield* io("writing the scratch workspace", async () => {
|
|
132
|
+
await writeFile(join(scratch, "pnpm-workspace.yaml"), scratchWorkspaceYaml(name, version));
|
|
133
|
+
await writeFile(join(scratch, "pnpm-lock.yaml"), lockfile);
|
|
134
|
+
});
|
|
135
|
+
const command = ChildProcess.make("pnpm", fetchArgs(scratch, request.stores), { cwd: scratch });
|
|
136
|
+
yield* Run.text(command, { timeout: FETCH_TIMEOUT }).pipe(Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner), Effect.mapError((cause) => failed(`fetching ${key} with \`pnpm ${fetchArgs("<scratch>", request.stores).join(" ")}\` failed: ${messageOf(cause)}`, cause)));
|
|
137
|
+
if ((yield* io("reading back the scratch lockfile", () => readFile(join(scratch, "pnpm-lock.yaml"), "utf8"))) !== lockfile) return yield* Effect.fail(failed(`fetching ${key}: pnpm rewrote the integrity-pinned scratch lockfile instead of installing from it, so the installed copy was not verified against the recorded integrity and is not used.`));
|
|
138
|
+
const linked = join(scratch, "node_modules", ".pnpm-config", name);
|
|
139
|
+
const resolved = yield* io(`resolving ${linked}`, () => realpath(linked));
|
|
140
|
+
const manifest = yield* manifestVersion(name, resolved).pipe(Effect.mapError((error) => failed(`the fetched ${key} has an unreadable package.json: ${messageOf(error.cause)}`, error.cause)));
|
|
141
|
+
if (!carries(manifest, version) || !isStoreEntry(resolved, name, version)) return yield* Effect.fail(failed(`fetching ${key}: pnpm linked ${resolved}, which is not a store copy of that version (its manifest reads ${manifest._tag === "version" ? manifest.version : "no version"}).`));
|
|
142
|
+
return resolved;
|
|
143
|
+
}));
|
|
144
|
+
|
|
145
|
+
//#endregion
|
|
146
|
+
export { expectedIntegrity, fetchArgs, makeFetchConfigDependency, storeDirArgument };
|