@effected/lockfiles 0.10.0 → 0.12.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/ConfigDependencyLock.js +36 -0
- package/Lockfile.js +28 -14
- package/PackageManagerLock.js +42 -0
- package/PnpmEnvLockfile.js +37 -0
- package/index.d.ts +239 -75
- package/index.js +4 -1
- package/internal/documents.js +45 -1
- package/internal/pnpm.js +2 -15
- package/internal/pnpmEnv.js +135 -0
- package/internal/shared.js +21 -1
- package/package.json +6 -6
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
|
|
3
|
+
//#region src/ConfigDependencyLock.ts
|
|
4
|
+
/**
|
|
5
|
+
* One config dependency a `pnpm-lock.yaml` records, with the integrity pnpm
|
|
6
|
+
* recorded for it — read from the lockfile's env preamble by
|
|
7
|
+
* `PnpmEnvLockfile.configDependencies`.
|
|
8
|
+
*
|
|
9
|
+
* @remarks
|
|
10
|
+
* pnpm 11 and 12 record each `configDependencies` entry of
|
|
11
|
+
* `pnpm-workspace.yaml` as the root importer's `configDependencies` in the env
|
|
12
|
+
* preamble document, and its integrity in the preamble's `packages:` section.
|
|
13
|
+
* A workspace that writes its `configDependencies` as bare versions keeps the
|
|
14
|
+
* integrity only here, which makes the lockfile the checksum store for its
|
|
15
|
+
* config dependencies, as it is for the package manager
|
|
16
|
+
* (`PackageManagerLock`).
|
|
17
|
+
*
|
|
18
|
+
* - `name` — the config dependency's package name.
|
|
19
|
+
* - `specifier` — the specifier pnpm recorded, verbatim. pnpm records the bare
|
|
20
|
+
* version even when `pnpm-workspace.yaml` declares the legacy inline
|
|
21
|
+
* `<version>+<integrity>` form.
|
|
22
|
+
* - `version` — the exact version pnpm resolved the specifier to.
|
|
23
|
+
* - `integrity` — the SRI integrity (`sha512-<base64>`) of
|
|
24
|
+
* `<name>@<version>`.
|
|
25
|
+
*
|
|
26
|
+
* @public
|
|
27
|
+
*/
|
|
28
|
+
var ConfigDependencyLock = class extends Schema.Class("ConfigDependencyLock")({
|
|
29
|
+
name: Schema.String,
|
|
30
|
+
specifier: Schema.String,
|
|
31
|
+
version: Schema.String,
|
|
32
|
+
integrity: Schema.String
|
|
33
|
+
}) {};
|
|
34
|
+
|
|
35
|
+
//#endregion
|
|
36
|
+
export { ConfigDependencyLock };
|
package/Lockfile.js
CHANGED
|
@@ -61,9 +61,14 @@ var LockfileParseError = class extends Schema.TaggedError()("LockfileParseError"
|
|
|
61
61
|
* such a file as having no lockfile.
|
|
62
62
|
* - `"noImporters"` — the located document declares no importers, so it
|
|
63
63
|
* describes no workspace. pnpm always records at least the root importer.
|
|
64
|
-
* - `"unexpectedDocuments"` — the stream carries
|
|
65
|
-
* format
|
|
66
|
-
*
|
|
64
|
+
* - `"unexpectedDocuments"` — the stream carries more documents than the
|
|
65
|
+
* format's framing defines: several in a format that defines none
|
|
66
|
+
* (yarn), or more than the env preamble and the lockfile when reading a
|
|
67
|
+
* pnpm env preamble. Rather than silently picking one, parsing refuses to
|
|
68
|
+
* guess. For pnpm the limit is the env reader's alone:
|
|
69
|
+
* `PnpmEnvLockfile` enforces at most two documents, while
|
|
70
|
+
* `Lockfile.parse` reads the last document of a stream of any count and
|
|
71
|
+
* never fails with this reason.
|
|
67
72
|
*
|
|
68
73
|
* It carries typed fields rather than a `cause`: unlike
|
|
69
74
|
* {@link LockfileParseError}, there is no underlying engine failure to wrap —
|
|
@@ -81,10 +86,27 @@ var LockfileFramingError = class extends Schema.TaggedError()("LockfileFramingEr
|
|
|
81
86
|
documents: Schema.Int
|
|
82
87
|
}) {
|
|
83
88
|
get message() {
|
|
84
|
-
const detail = this.reason === "noImporters" ? "the lockfile document declares no importers, so it describes no workspace" : this.reason === "unexpectedDocuments" ? `expected a single YAML document but the content carries ${this.documents}` : `the content carries no lockfile document (${this.documents} YAML document(s) found)`;
|
|
89
|
+
const detail = this.reason === "noImporters" ? "the lockfile document declares no importers, so it describes no workspace" : this.reason === "unexpectedDocuments" ? this.format === "pnpm" ? `expected at most two YAML documents (an env preamble and the lockfile) but the content carries ${this.documents}` : `expected a single YAML document but the content carries ${this.documents}` : `the content carries no lockfile document (${this.documents} YAML document(s) found)`;
|
|
85
90
|
return `Failed to parse ${this.format} lockfile: ${detail}`;
|
|
86
91
|
}
|
|
87
92
|
};
|
|
93
|
+
/**
|
|
94
|
+
* Materialize an internal {@link ParseFailure} record into the public error
|
|
95
|
+
* union — the one mapping every public fallible boundary shares, so
|
|
96
|
+
* `Lockfile.parse` and `PnpmEnvLockfile.packageManager` cannot disagree about
|
|
97
|
+
* which failure is which.
|
|
98
|
+
*
|
|
99
|
+
* @internal
|
|
100
|
+
*/
|
|
101
|
+
const materializeFailure = (format, failure) => failure.stage === "framing" ? new LockfileFramingError({
|
|
102
|
+
format,
|
|
103
|
+
reason: failure.reason,
|
|
104
|
+
documents: failure.documents
|
|
105
|
+
}) : new LockfileParseError({
|
|
106
|
+
format,
|
|
107
|
+
stage: failure.stage,
|
|
108
|
+
cause: failure.cause
|
|
109
|
+
});
|
|
88
110
|
const dispatch = (format, content) => {
|
|
89
111
|
switch (format) {
|
|
90
112
|
case "bun": return parseBun(content);
|
|
@@ -144,15 +166,7 @@ var Lockfile = class Lockfile extends Schema.Class("Lockfile")({
|
|
|
144
166
|
* multi-document `pnpm-lock.yaml` needs it).
|
|
145
167
|
*/
|
|
146
168
|
static parse = Effect.fn("Lockfile.parse")(function* (content, options) {
|
|
147
|
-
const fields = yield* dispatch(options.format, content).pipe(Effect.mapError((failure) =>
|
|
148
|
-
format: options.format,
|
|
149
|
-
reason: failure.reason,
|
|
150
|
-
documents: failure.documents
|
|
151
|
-
}) : new LockfileParseError({
|
|
152
|
-
format: options.format,
|
|
153
|
-
stage: failure.stage,
|
|
154
|
-
cause: failure.cause
|
|
155
|
-
})));
|
|
169
|
+
const fields = yield* dispatch(options.format, content).pipe(Effect.mapError((failure) => materializeFailure(options.format, failure)));
|
|
156
170
|
return Lockfile.make({
|
|
157
171
|
format: options.format,
|
|
158
172
|
lockfileVersion: fields.lockfileVersion,
|
|
@@ -288,4 +302,4 @@ var Lockfile = class Lockfile extends Schema.Class("Lockfile")({
|
|
|
288
302
|
};
|
|
289
303
|
|
|
290
304
|
//#endregion
|
|
291
|
-
export { Lockfile, LockfileFramingError, LockfileParseError };
|
|
305
|
+
export { Lockfile, LockfileFramingError, LockfileParseError, materializeFailure };
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
|
|
3
|
+
//#region src/PackageManagerLock.ts
|
|
4
|
+
/**
|
|
5
|
+
* The package manager a `pnpm-lock.yaml` pins, with the integrity pnpm
|
|
6
|
+
* recorded for it — read from the lockfile's env preamble by
|
|
7
|
+
* `PnpmEnvLockfile.packageManager`.
|
|
8
|
+
*
|
|
9
|
+
* @remarks
|
|
10
|
+
* pnpm records the package manager a workspace declares in
|
|
11
|
+
* `devEngines.packageManager` as the root importer's
|
|
12
|
+
* `packageManagerDependencies` in the env preamble document, and resolves it
|
|
13
|
+
* like any other dependency: the preamble's `packages:` section carries its
|
|
14
|
+
* integrity, and pnpm refuses to run a manager whose identity does not match.
|
|
15
|
+
* That makes the lockfile, not the `+sha512.<hex>` suffix on the declared
|
|
16
|
+
* version, the checksum store for the manager itself.
|
|
17
|
+
*
|
|
18
|
+
* - `name` — the package-manager package, always `"pnpm"`.
|
|
19
|
+
* - `specifier` — the declared specifier, verbatim. It may still carry the
|
|
20
|
+
* corepack-style `+sha512.<hex>` suffix the manifest declared it with.
|
|
21
|
+
* - `version` — the exact version pnpm resolved the specifier to.
|
|
22
|
+
* - `integrity` — the SRI integrity (`sha512-<base64>`) of the
|
|
23
|
+
* `pnpm@<version>` package.
|
|
24
|
+
* - `nativeIntegrity` — SRI integrity per native package, keyed by package
|
|
25
|
+
* name with no version (`"@pnpm/exe.linux-x64"`): every package the lockfile
|
|
26
|
+
* records as an optional dependency of `pnpm@<version>`. pnpm 12 records one
|
|
27
|
+
* `@pnpm/exe.<target>` entry per platform there; pnpm 11 records none, so
|
|
28
|
+
* the record is empty — pnpm 11 hangs its platform binaries off a separate
|
|
29
|
+
* `@pnpm/exe` wrapper entry instead, which this model does not carry.
|
|
30
|
+
*
|
|
31
|
+
* @public
|
|
32
|
+
*/
|
|
33
|
+
var PackageManagerLock = class extends Schema.Class("PackageManagerLock")({
|
|
34
|
+
name: Schema.Literal("pnpm"),
|
|
35
|
+
specifier: Schema.String,
|
|
36
|
+
version: Schema.String,
|
|
37
|
+
integrity: Schema.String,
|
|
38
|
+
nativeIntegrity: Schema.Record(Schema.String, Schema.String)
|
|
39
|
+
}) {};
|
|
40
|
+
|
|
41
|
+
//#endregion
|
|
42
|
+
export { PackageManagerLock };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { materializeFailure } from "./Lockfile.js";
|
|
2
|
+
import { readPnpmConfigDependencies, readPnpmPackageManager } from "./internal/pnpmEnv.js";
|
|
3
|
+
import { Effect, Option } from "effect";
|
|
4
|
+
|
|
5
|
+
//#region src/PnpmEnvLockfile.ts
|
|
6
|
+
/** Both readers surface an internal `ParseFailure` the one way `Lockfile.parse` does. */
|
|
7
|
+
const materialize = Effect.mapError((failure) => materializeFailure("pnpm", failure));
|
|
8
|
+
const packageManager = Effect.fn("PnpmEnvLockfile.packageManager")((content) => readPnpmPackageManager(content).pipe(materialize, Effect.map(Option.fromUndefinedOr)));
|
|
9
|
+
const configDependencies = Effect.fn("PnpmEnvLockfile.configDependencies")((content) => readPnpmConfigDependencies(content).pipe(materialize));
|
|
10
|
+
/**
|
|
11
|
+
* Readers over the env ("preamble") document of a `pnpm-lock.yaml` — see
|
|
12
|
+
* {@link PnpmEnvLockfileReaders}.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```typescript
|
|
16
|
+
* import { PnpmEnvLockfile } from "@effected/lockfiles";
|
|
17
|
+
* import { Effect, Option } from "effect";
|
|
18
|
+
*
|
|
19
|
+
* const program = Effect.gen(function* () {
|
|
20
|
+
* const lock = yield* PnpmEnvLockfile.packageManager(content);
|
|
21
|
+
* const configDependencies = yield* PnpmEnvLockfile.configDependencies(content);
|
|
22
|
+
* return {
|
|
23
|
+
* pnpm: Option.map(lock, (pm) => pm.integrity),
|
|
24
|
+
* plugin: configDependencies.get("@effected/pnpm-plugin-effect")?.integrity,
|
|
25
|
+
* };
|
|
26
|
+
* });
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* @public
|
|
30
|
+
*/
|
|
31
|
+
const PnpmEnvLockfile = {
|
|
32
|
+
packageManager,
|
|
33
|
+
configDependencies
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
//#endregion
|
|
37
|
+
export { PnpmEnvLockfile };
|
package/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Effect, Option, Schema } from "effect";
|
|
2
|
+
import "@effected/npm";
|
|
2
3
|
//#region src/BunExtension.d.ts
|
|
3
4
|
declare const BunExtension_base: Schema.Class<BunExtension, Schema.Struct<{
|
|
4
5
|
readonly _tag: Schema.tag<"bun">;
|
|
@@ -21,6 +22,39 @@ declare const BunExtension_base: Schema.Class<BunExtension, Schema.Struct<{
|
|
|
21
22
|
*/
|
|
22
23
|
export declare class BunExtension extends BunExtension_base {}
|
|
23
24
|
//#endregion
|
|
25
|
+
//#region src/ConfigDependencyLock.d.ts
|
|
26
|
+
declare const ConfigDependencyLock_base: Schema.Class<ConfigDependencyLock, Schema.Struct<{
|
|
27
|
+
readonly name: Schema.String;
|
|
28
|
+
readonly specifier: Schema.String;
|
|
29
|
+
readonly version: Schema.String;
|
|
30
|
+
readonly integrity: Schema.String;
|
|
31
|
+
}>, {}>;
|
|
32
|
+
/**
|
|
33
|
+
* One config dependency a `pnpm-lock.yaml` records, with the integrity pnpm
|
|
34
|
+
* recorded for it — read from the lockfile's env preamble by
|
|
35
|
+
* `PnpmEnvLockfile.configDependencies`.
|
|
36
|
+
*
|
|
37
|
+
* @remarks
|
|
38
|
+
* pnpm 11 and 12 record each `configDependencies` entry of
|
|
39
|
+
* `pnpm-workspace.yaml` as the root importer's `configDependencies` in the env
|
|
40
|
+
* preamble document, and its integrity in the preamble's `packages:` section.
|
|
41
|
+
* A workspace that writes its `configDependencies` as bare versions keeps the
|
|
42
|
+
* integrity only here, which makes the lockfile the checksum store for its
|
|
43
|
+
* config dependencies, as it is for the package manager
|
|
44
|
+
* (`PackageManagerLock`).
|
|
45
|
+
*
|
|
46
|
+
* - `name` — the config dependency's package name.
|
|
47
|
+
* - `specifier` — the specifier pnpm recorded, verbatim. pnpm records the bare
|
|
48
|
+
* version even when `pnpm-workspace.yaml` declares the legacy inline
|
|
49
|
+
* `<version>+<integrity>` form.
|
|
50
|
+
* - `version` — the exact version pnpm resolved the specifier to.
|
|
51
|
+
* - `integrity` — the SRI integrity (`sha512-<base64>`) of
|
|
52
|
+
* `<name>@<version>`.
|
|
53
|
+
*
|
|
54
|
+
* @public
|
|
55
|
+
*/
|
|
56
|
+
export declare class ConfigDependencyLock extends ConfigDependencyLock_base {}
|
|
57
|
+
//#endregion
|
|
24
58
|
//#region src/ImporterDependency.d.ts
|
|
25
59
|
declare const ImporterDependency_base: Schema.Class<ImporterDependency, Schema.Struct<{
|
|
26
60
|
readonly name: Schema.NonEmptyString;
|
|
@@ -60,77 +94,6 @@ declare const ImporterDependency_base: Schema.Class<ImporterDependency, Schema.S
|
|
|
60
94
|
*/
|
|
61
95
|
export declare class ImporterDependency extends ImporterDependency_base {}
|
|
62
96
|
//#endregion
|
|
63
|
-
//#region src/LockfileFormat.d.ts
|
|
64
|
-
/**
|
|
65
|
-
* The lockfile formats this package parses: bun's `bun.lock` (JSONC), npm's
|
|
66
|
-
* `package-lock.json` (JSON), pnpm's `pnpm-lock.yaml` and yarn Berry's
|
|
67
|
-
* `yarn.lock` (both YAML).
|
|
68
|
-
*
|
|
69
|
-
* @remarks
|
|
70
|
-
* The literal names the *lockfile format*, not the package manager that
|
|
71
|
-
* happens to write it — this package models lockfiles. Yarn support is
|
|
72
|
-
* Berry only; classic (v1) `yarn.lock` is not YAML and fails
|
|
73
|
-
* `Lockfile.parse` with a typed `LockfileParseError`.
|
|
74
|
-
*
|
|
75
|
-
* The format literal says nothing about which *versions* of that format are
|
|
76
|
-
* supported: pnpm `lockfileVersion` 9+ and npm `lockfileVersion` 3+ parse,
|
|
77
|
-
* and older ones fail typed. See `Lockfile.parse`.
|
|
78
|
-
*
|
|
79
|
-
* @public
|
|
80
|
-
*/
|
|
81
|
-
export declare const LockfileFormat: Schema.Literals<readonly ["bun", "npm", "pnpm", "yarn"]>;
|
|
82
|
-
/**
|
|
83
|
-
* The union of supported lockfile format names.
|
|
84
|
-
*
|
|
85
|
-
* @public
|
|
86
|
-
*/
|
|
87
|
-
export type LockfileFormat = typeof LockfileFormat.Type;
|
|
88
|
-
/**
|
|
89
|
-
* The conventional lockfile filename for a format: `"bun.lock"`,
|
|
90
|
-
* `"package-lock.json"`, `"pnpm-lock.yaml"` or `"yarn.lock"`.
|
|
91
|
-
*
|
|
92
|
-
* @remarks
|
|
93
|
-
* The primary name only — the first element of {@link filenamesFor}, which is
|
|
94
|
-
* what detection that must also see the genuine alternates should use.
|
|
95
|
-
*
|
|
96
|
-
* @public
|
|
97
|
-
*/
|
|
98
|
-
export declare const filenameFor: (format: LockfileFormat) => string;
|
|
99
|
-
/**
|
|
100
|
-
* Every filename a format is genuinely written under, primary first.
|
|
101
|
-
*
|
|
102
|
-
* @remarks
|
|
103
|
-
* Real detection needs more than the conventional name: npm honours
|
|
104
|
-
* `npm-shrinkwrap.json` (and prefers it over `package-lock.json` when both
|
|
105
|
-
* exist — the ordering here is conventional-name-first, not npm's own
|
|
106
|
-
* precedence), and bun has shipped two formats, the current text `bun.lock`
|
|
107
|
-
* and the older **binary** `bun.lockb`. pnpm and yarn each have exactly one.
|
|
108
|
-
*
|
|
109
|
-
* Only genuine *lockfile* spellings appear here. Files that merely influence
|
|
110
|
-
* what an install resolves to — `pnpm-workspace.yaml`, `.pnpmfile.cjs`, yarn's
|
|
111
|
-
* PnP artifacts — are a consumer's cache policy, not alternates of the
|
|
112
|
-
* lockfile.
|
|
113
|
-
*
|
|
114
|
-
* Two deliberate asymmetries with the rest of this module: `bun.lockb` is a
|
|
115
|
-
* detection alternate but not a parse target (`Lockfile.parse` takes text; the
|
|
116
|
-
* binary format fails typed), and {@link fromFilename} keeps answering for the
|
|
117
|
-
* primary names only.
|
|
118
|
-
*
|
|
119
|
-
* @public
|
|
120
|
-
*/
|
|
121
|
-
export declare const filenamesFor: (format: LockfileFormat) => readonly [string, ...ReadonlyArray<string>];
|
|
122
|
-
/**
|
|
123
|
-
* The format a lockfile filename identifies, if any.
|
|
124
|
-
*
|
|
125
|
-
* @remarks
|
|
126
|
-
* Matches exact conventional filenames only (`"bun.lock"`,
|
|
127
|
-
* `"package-lock.json"`, `"pnpm-lock.yaml"`, `"yarn.lock"`) — paths, other
|
|
128
|
-
* spellings and the {@link filenamesFor} alternates return `Option.none()`.
|
|
129
|
-
*
|
|
130
|
-
* @public
|
|
131
|
-
*/
|
|
132
|
-
export declare const fromFilename: (name: string) => Option.Option<LockfileFormat>;
|
|
133
|
-
//#endregion
|
|
134
97
|
//#region src/LockfileImporter.d.ts
|
|
135
98
|
declare const LockfileImporter_base: Schema.Class<LockfileImporter, Schema.Struct<{
|
|
136
99
|
readonly path: Schema.NonEmptyString;
|
|
@@ -317,6 +280,77 @@ declare const WorkspaceDependency_base: Schema.Class<WorkspaceDependency, Schema
|
|
|
317
280
|
*/
|
|
318
281
|
export declare class WorkspaceDependency extends WorkspaceDependency_base {}
|
|
319
282
|
//#endregion
|
|
283
|
+
//#region src/LockfileFormat.d.ts
|
|
284
|
+
/**
|
|
285
|
+
* The lockfile formats this package parses: bun's `bun.lock` (JSONC), npm's
|
|
286
|
+
* `package-lock.json` (JSON), pnpm's `pnpm-lock.yaml` and yarn Berry's
|
|
287
|
+
* `yarn.lock` (both YAML).
|
|
288
|
+
*
|
|
289
|
+
* @remarks
|
|
290
|
+
* The literal names the *lockfile format*, not the package manager that
|
|
291
|
+
* happens to write it — this package models lockfiles. Yarn support is
|
|
292
|
+
* Berry only; classic (v1) `yarn.lock` is not YAML and fails
|
|
293
|
+
* `Lockfile.parse` with a typed `LockfileParseError`.
|
|
294
|
+
*
|
|
295
|
+
* The format literal says nothing about which *versions* of that format are
|
|
296
|
+
* supported: pnpm `lockfileVersion` 9+ and npm `lockfileVersion` 3+ parse,
|
|
297
|
+
* and older ones fail typed. See `Lockfile.parse`.
|
|
298
|
+
*
|
|
299
|
+
* @public
|
|
300
|
+
*/
|
|
301
|
+
export declare const LockfileFormat: Schema.Literals<readonly ["bun", "npm", "pnpm", "yarn"]>;
|
|
302
|
+
/**
|
|
303
|
+
* The union of supported lockfile format names.
|
|
304
|
+
*
|
|
305
|
+
* @public
|
|
306
|
+
*/
|
|
307
|
+
export type LockfileFormat = typeof LockfileFormat.Type;
|
|
308
|
+
/**
|
|
309
|
+
* The conventional lockfile filename for a format: `"bun.lock"`,
|
|
310
|
+
* `"package-lock.json"`, `"pnpm-lock.yaml"` or `"yarn.lock"`.
|
|
311
|
+
*
|
|
312
|
+
* @remarks
|
|
313
|
+
* The primary name only — the first element of {@link filenamesFor}, which is
|
|
314
|
+
* what detection that must also see the genuine alternates should use.
|
|
315
|
+
*
|
|
316
|
+
* @public
|
|
317
|
+
*/
|
|
318
|
+
export declare const filenameFor: (format: LockfileFormat) => string;
|
|
319
|
+
/**
|
|
320
|
+
* Every filename a format is genuinely written under, primary first.
|
|
321
|
+
*
|
|
322
|
+
* @remarks
|
|
323
|
+
* Real detection needs more than the conventional name: npm honours
|
|
324
|
+
* `npm-shrinkwrap.json` (and prefers it over `package-lock.json` when both
|
|
325
|
+
* exist — the ordering here is conventional-name-first, not npm's own
|
|
326
|
+
* precedence), and bun has shipped two formats, the current text `bun.lock`
|
|
327
|
+
* and the older **binary** `bun.lockb`. pnpm and yarn each have exactly one.
|
|
328
|
+
*
|
|
329
|
+
* Only genuine *lockfile* spellings appear here. Files that merely influence
|
|
330
|
+
* what an install resolves to — `pnpm-workspace.yaml`, `.pnpmfile.cjs`, yarn's
|
|
331
|
+
* PnP artifacts — are a consumer's cache policy, not alternates of the
|
|
332
|
+
* lockfile.
|
|
333
|
+
*
|
|
334
|
+
* Two deliberate asymmetries with the rest of this module: `bun.lockb` is a
|
|
335
|
+
* detection alternate but not a parse target (`Lockfile.parse` takes text; the
|
|
336
|
+
* binary format fails typed), and {@link fromFilename} keeps answering for the
|
|
337
|
+
* primary names only.
|
|
338
|
+
*
|
|
339
|
+
* @public
|
|
340
|
+
*/
|
|
341
|
+
export declare const filenamesFor: (format: LockfileFormat) => readonly [string, ...ReadonlyArray<string>];
|
|
342
|
+
/**
|
|
343
|
+
* The format a lockfile filename identifies, if any.
|
|
344
|
+
*
|
|
345
|
+
* @remarks
|
|
346
|
+
* Matches exact conventional filenames only (`"bun.lock"`,
|
|
347
|
+
* `"package-lock.json"`, `"pnpm-lock.yaml"`, `"yarn.lock"`) — paths, other
|
|
348
|
+
* spellings and the {@link filenamesFor} alternates return `Option.none()`.
|
|
349
|
+
*
|
|
350
|
+
* @public
|
|
351
|
+
*/
|
|
352
|
+
export declare const fromFilename: (name: string) => Option.Option<LockfileFormat>;
|
|
353
|
+
//#endregion
|
|
320
354
|
//#region src/Lockfile.d.ts
|
|
321
355
|
declare const LockfileParseError_base: Schema.Class<LockfileParseError, Schema.TaggedStruct<"LockfileParseError", {
|
|
322
356
|
readonly format: Schema.Literals<readonly ["bun", "npm", "pnpm", "yarn"]>;
|
|
@@ -371,9 +405,14 @@ declare const LockfileFramingError_base: Schema.Class<LockfileFramingError, Sche
|
|
|
371
405
|
* such a file as having no lockfile.
|
|
372
406
|
* - `"noImporters"` — the located document declares no importers, so it
|
|
373
407
|
* describes no workspace. pnpm always records at least the root importer.
|
|
374
|
-
* - `"unexpectedDocuments"` — the stream carries
|
|
375
|
-
* format
|
|
376
|
-
*
|
|
408
|
+
* - `"unexpectedDocuments"` — the stream carries more documents than the
|
|
409
|
+
* format's framing defines: several in a format that defines none
|
|
410
|
+
* (yarn), or more than the env preamble and the lockfile when reading a
|
|
411
|
+
* pnpm env preamble. Rather than silently picking one, parsing refuses to
|
|
412
|
+
* guess. For pnpm the limit is the env reader's alone:
|
|
413
|
+
* `PnpmEnvLockfile` enforces at most two documents, while
|
|
414
|
+
* `Lockfile.parse` reads the last document of a stream of any count and
|
|
415
|
+
* never fails with this reason.
|
|
377
416
|
*
|
|
378
417
|
* It carries typed fields rather than a `cause`: unlike
|
|
379
418
|
* {@link LockfileParseError}, there is no underlying engine failure to wrap —
|
|
@@ -568,6 +607,131 @@ export declare class LockfileIntegrity extends LockfileIntegrity_base {
|
|
|
568
607
|
static compare(lockfile: Lockfile, manifests: ReadonlyArray<WorkspaceManifest>): LockfileIntegrity;
|
|
569
608
|
}
|
|
570
609
|
//#endregion
|
|
610
|
+
//#region src/PackageManagerLock.d.ts
|
|
611
|
+
declare const PackageManagerLock_base: Schema.Class<PackageManagerLock, Schema.Struct<{
|
|
612
|
+
readonly name: Schema.Literal<"pnpm">;
|
|
613
|
+
readonly specifier: Schema.String;
|
|
614
|
+
readonly version: Schema.String;
|
|
615
|
+
readonly integrity: Schema.String;
|
|
616
|
+
readonly nativeIntegrity: Schema.$Record<Schema.String, Schema.String>;
|
|
617
|
+
}>, {}>;
|
|
618
|
+
/**
|
|
619
|
+
* The package manager a `pnpm-lock.yaml` pins, with the integrity pnpm
|
|
620
|
+
* recorded for it — read from the lockfile's env preamble by
|
|
621
|
+
* `PnpmEnvLockfile.packageManager`.
|
|
622
|
+
*
|
|
623
|
+
* @remarks
|
|
624
|
+
* pnpm records the package manager a workspace declares in
|
|
625
|
+
* `devEngines.packageManager` as the root importer's
|
|
626
|
+
* `packageManagerDependencies` in the env preamble document, and resolves it
|
|
627
|
+
* like any other dependency: the preamble's `packages:` section carries its
|
|
628
|
+
* integrity, and pnpm refuses to run a manager whose identity does not match.
|
|
629
|
+
* That makes the lockfile, not the `+sha512.<hex>` suffix on the declared
|
|
630
|
+
* version, the checksum store for the manager itself.
|
|
631
|
+
*
|
|
632
|
+
* - `name` — the package-manager package, always `"pnpm"`.
|
|
633
|
+
* - `specifier` — the declared specifier, verbatim. It may still carry the
|
|
634
|
+
* corepack-style `+sha512.<hex>` suffix the manifest declared it with.
|
|
635
|
+
* - `version` — the exact version pnpm resolved the specifier to.
|
|
636
|
+
* - `integrity` — the SRI integrity (`sha512-<base64>`) of the
|
|
637
|
+
* `pnpm@<version>` package.
|
|
638
|
+
* - `nativeIntegrity` — SRI integrity per native package, keyed by package
|
|
639
|
+
* name with no version (`"@pnpm/exe.linux-x64"`): every package the lockfile
|
|
640
|
+
* records as an optional dependency of `pnpm@<version>`. pnpm 12 records one
|
|
641
|
+
* `@pnpm/exe.<target>` entry per platform there; pnpm 11 records none, so
|
|
642
|
+
* the record is empty — pnpm 11 hangs its platform binaries off a separate
|
|
643
|
+
* `@pnpm/exe` wrapper entry instead, which this model does not carry.
|
|
644
|
+
*
|
|
645
|
+
* @public
|
|
646
|
+
*/
|
|
647
|
+
export declare class PackageManagerLock extends PackageManagerLock_base {}
|
|
648
|
+
//#endregion
|
|
649
|
+
//#region src/PnpmEnvLockfile.d.ts
|
|
650
|
+
/**
|
|
651
|
+
* Readers over the env ("preamble") document of a `pnpm-lock.yaml`.
|
|
652
|
+
*
|
|
653
|
+
* @remarks
|
|
654
|
+
* A `pnpm-lock.yaml` is a YAML stream. When a workspace declares
|
|
655
|
+
* `devEngines.packageManager` or `configDependencies`, pnpm writes an env
|
|
656
|
+
* preamble document ahead of the lockfile proper: the preamble is always the
|
|
657
|
+
* **first** of two documents, the lockfile always the **last**.
|
|
658
|
+
* `Lockfile.parse` reads the last; these readers read the first.
|
|
659
|
+
*
|
|
660
|
+
* @public
|
|
661
|
+
*/
|
|
662
|
+
interface PnpmEnvLockfileReaders {
|
|
663
|
+
/**
|
|
664
|
+
* Read the package manager a `pnpm-lock.yaml` pins, with its recorded
|
|
665
|
+
* integrity. Pure: takes the lockfile text, performs no IO.
|
|
666
|
+
*
|
|
667
|
+
* @param content - The `pnpm-lock.yaml` text.
|
|
668
|
+
* @returns `Option.none()` when the lockfile records no package manager —
|
|
669
|
+
* a single-document lockfile (no preamble; pnpm writes none for a
|
|
670
|
+
* workspace declaring neither `devEngines.packageManager` nor
|
|
671
|
+
* `configDependencies`), or a preamble whose root importer declares no
|
|
672
|
+
* `pnpm` in `packageManagerDependencies`. Otherwise `Option.some` of the
|
|
673
|
+
* {@link PackageManagerLock}.
|
|
674
|
+
*
|
|
675
|
+
* Fails with {@link LockfileParseError} when the text is not well-formed
|
|
676
|
+
* YAML (`stage: "syntax"`), or when the preamble is malformed or records a
|
|
677
|
+
* claim it cannot back (`stage: "validation"`): `pnpm@<version>` with no
|
|
678
|
+
* `packages` entry, snapshot or SRI integrity, or a native optional
|
|
679
|
+
* dependency with no SRI integrity. A lockfile that names a version it
|
|
680
|
+
* cannot account for is a failure, never `none`. Fails with
|
|
681
|
+
* {@link LockfileFramingError} (`reason: "unexpectedDocuments"`) when the
|
|
682
|
+
* stream carries more than two documents, since no position then
|
|
683
|
+
* identifies the preamble.
|
|
684
|
+
*/
|
|
685
|
+
readonly packageManager: (content: string) => Effect.Effect<Option.Option<PackageManagerLock>, LockfileParseError | LockfileFramingError>;
|
|
686
|
+
/**
|
|
687
|
+
* Read the config dependencies a `pnpm-lock.yaml` records, each with its
|
|
688
|
+
* recorded integrity. Pure: takes the lockfile text, performs no IO.
|
|
689
|
+
*
|
|
690
|
+
* @remarks
|
|
691
|
+
* The integrity source for a `configDependencies` entry written as a bare
|
|
692
|
+
* version: pnpm 11 and 12 keep it here rather than inline in
|
|
693
|
+
* `pnpm-workspace.yaml`. Read by `@effected/workspaces` to verify a config
|
|
694
|
+
* dependency it fetches at a version this checkout never installed.
|
|
695
|
+
*
|
|
696
|
+
* @param content - The `pnpm-lock.yaml` text.
|
|
697
|
+
* @returns A map keyed by config-dependency name. Empty when the lockfile
|
|
698
|
+
* records none — a single-document lockfile (no preamble), or a preamble
|
|
699
|
+
* whose root importer declares no `configDependencies`.
|
|
700
|
+
*
|
|
701
|
+
* Fails exactly as {@link PnpmEnvLockfileReaders.packageManager} does, with
|
|
702
|
+
* {@link LockfileParseError} for broken YAML (`stage: "syntax"`) or a
|
|
703
|
+
* malformed preamble (`stage: "validation"`), and with
|
|
704
|
+
* {@link LockfileFramingError} (`reason: "unexpectedDocuments"`) for more
|
|
705
|
+
* than two documents. A recorded entry the lockfile cannot back fails at
|
|
706
|
+
* `stage: "validation"`: an empty version, or a `<name>@<version>` with no
|
|
707
|
+
* `packages` entry, no `resolution.integrity`, or an integrity that is not
|
|
708
|
+
* SRI. An entry is never silently dropped.
|
|
709
|
+
*/
|
|
710
|
+
readonly configDependencies: (content: string) => Effect.Effect<ReadonlyMap<string, ConfigDependencyLock>, LockfileParseError | LockfileFramingError>;
|
|
711
|
+
}
|
|
712
|
+
/**
|
|
713
|
+
* Readers over the env ("preamble") document of a `pnpm-lock.yaml` — see
|
|
714
|
+
* {@link PnpmEnvLockfileReaders}.
|
|
715
|
+
*
|
|
716
|
+
* @example
|
|
717
|
+
* ```typescript
|
|
718
|
+
* import { PnpmEnvLockfile } from "@effected/lockfiles";
|
|
719
|
+
* import { Effect, Option } from "effect";
|
|
720
|
+
*
|
|
721
|
+
* const program = Effect.gen(function* () {
|
|
722
|
+
* const lock = yield* PnpmEnvLockfile.packageManager(content);
|
|
723
|
+
* const configDependencies = yield* PnpmEnvLockfile.configDependencies(content);
|
|
724
|
+
* return {
|
|
725
|
+
* pnpm: Option.map(lock, (pm) => pm.integrity),
|
|
726
|
+
* plugin: configDependencies.get("@effected/pnpm-plugin-effect")?.integrity,
|
|
727
|
+
* };
|
|
728
|
+
* });
|
|
729
|
+
* ```
|
|
730
|
+
*
|
|
731
|
+
* @public
|
|
732
|
+
*/
|
|
733
|
+
export declare const PnpmEnvLockfile: PnpmEnvLockfileReaders;
|
|
734
|
+
//#endregion
|
|
571
735
|
//#region src/UnsupportedLockfileVersion.d.ts
|
|
572
736
|
/**
|
|
573
737
|
* The cause a {@link LockfileParseError} carries when a lockfile predates the
|
|
@@ -633,5 +797,5 @@ interface UnsupportedLockfileVersion {
|
|
|
633
797
|
*/
|
|
634
798
|
export declare const isUnsupportedLockfileVersion: (cause: unknown) => cause is UnsupportedLockfileVersion;
|
|
635
799
|
//#endregion
|
|
636
|
-
export type { PnpmCatalogs, UnsupportedLockfileVersion };
|
|
800
|
+
export type { PnpmCatalogs, PnpmEnvLockfileReaders, UnsupportedLockfileVersion };
|
|
637
801
|
//# sourceMappingURL=index.d.ts.map
|
package/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { BunExtension } from "./BunExtension.js";
|
|
2
|
+
import { ConfigDependencyLock } from "./ConfigDependencyLock.js";
|
|
2
3
|
import { ImporterDependency } from "./ImporterDependency.js";
|
|
3
4
|
import { LockfileImporter } from "./LockfileImporter.js";
|
|
4
5
|
import { ResolvedPackage } from "./ResolvedPackage.js";
|
|
@@ -7,6 +8,8 @@ import { PnpmExtension } from "./PnpmExtension.js";
|
|
|
7
8
|
import { LockfileFormat, filenameFor, filenamesFor, fromFilename } from "./LockfileFormat.js";
|
|
8
9
|
import { Lockfile, LockfileFramingError, LockfileParseError } from "./Lockfile.js";
|
|
9
10
|
import { LockfileIntegrity, WorkspaceManifest } from "./LockfileIntegrity.js";
|
|
11
|
+
import { PackageManagerLock } from "./PackageManagerLock.js";
|
|
12
|
+
import { PnpmEnvLockfile } from "./PnpmEnvLockfile.js";
|
|
10
13
|
import { isUnsupportedLockfileVersion } from "./UnsupportedLockfileVersion.js";
|
|
11
14
|
|
|
12
|
-
export { BunExtension, ImporterDependency, Lockfile, LockfileFormat, LockfileFramingError, LockfileImporter, LockfileIntegrity, LockfileParseError, PnpmExtension, ResolvedPackage, WorkspaceDependency, WorkspaceManifest, filenameFor, filenamesFor, fromFilename, isUnsupportedLockfileVersion };
|
|
15
|
+
export { BunExtension, ConfigDependencyLock, ImporterDependency, Lockfile, LockfileFormat, LockfileFramingError, LockfileImporter, LockfileIntegrity, LockfileParseError, PackageManagerLock, PnpmEnvLockfile, PnpmExtension, ResolvedPackage, WorkspaceDependency, WorkspaceManifest, filenameFor, filenamesFor, fromFilename, isUnsupportedLockfileVersion };
|
package/internal/documents.js
CHANGED
|
@@ -36,6 +36,10 @@ const isEmptyDocument = (document) => document === null || document === void 0;
|
|
|
36
36
|
* document. pnpm reads that as "no lockfile"; so do we, through the typed
|
|
37
37
|
* framing failure — never by silently falling back to the preamble.
|
|
38
38
|
*
|
|
39
|
+
* It takes the last document of a stream of **any** count and never fails
|
|
40
|
+
* with `unexpectedDocuments`; only {@link selectPnpmEnvDocument}, the env
|
|
41
|
+
* reader's selector, enforces the at-most-two limit.
|
|
42
|
+
*
|
|
39
43
|
* @internal
|
|
40
44
|
*/
|
|
41
45
|
const selectPnpmDocument = (content) => Effect.gen(function* () {
|
|
@@ -71,6 +75,46 @@ const selectSoleDocument = (content) => Effect.gen(function* () {
|
|
|
71
75
|
documents: documents.length
|
|
72
76
|
};
|
|
73
77
|
});
|
|
78
|
+
/**
|
|
79
|
+
* The number of documents pnpm's env-lockfile writer can compose: the env
|
|
80
|
+
* preamble and the main lockfile. See {@link selectPnpmEnvDocument}.
|
|
81
|
+
*
|
|
82
|
+
* @internal
|
|
83
|
+
*/
|
|
84
|
+
const MAX_PNPM_DOCUMENTS = 2;
|
|
85
|
+
/**
|
|
86
|
+
* Select the env ("preamble") document from a `pnpm-lock.yaml` YAML stream,
|
|
87
|
+
* or `undefined` when the stream carries none.
|
|
88
|
+
*
|
|
89
|
+
* @remarks
|
|
90
|
+
* The same position rule as {@link selectPnpmDocument}, read from the other
|
|
91
|
+
* end: pnpm's `writeEnvLockfile` composes `${env}---${main}`, so the preamble
|
|
92
|
+
* is the **first** of exactly two documents. A single-document stream has no
|
|
93
|
+
* preamble — that is how pnpm writes a lockfile for a workspace that declares
|
|
94
|
+
* neither `configDependencies` nor `devEngines.packageManager` — and neither
|
|
95
|
+
* does an empty one. An empty first document is no preamble either: there is
|
|
96
|
+
* nothing in it to read.
|
|
97
|
+
*
|
|
98
|
+
* A stream carrying **more** than two documents is outside the writer
|
|
99
|
+
* contract, so no position identifies the preamble in it. Rather than guess —
|
|
100
|
+
* this selection feeds integrity verification, where a guess is a trust
|
|
101
|
+
* decision — it fails through the typed framing channel. That limit is this
|
|
102
|
+
* selector's alone: {@link selectPnpmDocument}, behind `Lockfile.parse`, takes
|
|
103
|
+
* the last document of a stream of any count.
|
|
104
|
+
*
|
|
105
|
+
* @internal
|
|
106
|
+
*/
|
|
107
|
+
const selectPnpmEnvDocument = (content) => Effect.gen(function* () {
|
|
108
|
+
const documents = yield* Yaml.parseAll(content).pipe(Effect.mapError(syntaxFailure));
|
|
109
|
+
if (documents.length > MAX_PNPM_DOCUMENTS) return yield* Effect.fail(framingFailure("unexpectedDocuments", documents.length));
|
|
110
|
+
if (documents.length < MAX_PNPM_DOCUMENTS) return void 0;
|
|
111
|
+
const document = documents.at(0);
|
|
112
|
+
if (isEmptyDocument(document)) return void 0;
|
|
113
|
+
return {
|
|
114
|
+
document,
|
|
115
|
+
documents: documents.length
|
|
116
|
+
};
|
|
117
|
+
});
|
|
74
118
|
|
|
75
119
|
//#endregion
|
|
76
|
-
export { selectPnpmDocument, selectSoleDocument };
|
|
120
|
+
export { selectPnpmDocument, selectPnpmEnvDocument, selectSoleDocument };
|
package/internal/pnpm.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { LockfileImporter } from "../LockfileImporter.js";
|
|
2
2
|
import { ResolvedPackage } from "../ResolvedPackage.js";
|
|
3
|
-
import { extractWorkspaceDeps, framingFailure, importerDependencies, peerDeclarations,
|
|
3
|
+
import { extractWorkspaceDeps, framingFailure, gatePnpmVersion, importerDependencies, peerDeclarations, splitPeerSuffix, toIntegrityHash, validationFailure } from "./shared.js";
|
|
4
4
|
import { PnpmExtension } from "../PnpmExtension.js";
|
|
5
5
|
import { selectPnpmDocument } from "./documents.js";
|
|
6
6
|
import { Effect, Schema } from "effect";
|
|
@@ -17,18 +17,6 @@ const PnpmImporter = Schema.Struct({
|
|
|
17
17
|
optionalDependencies: PnpmImporterDeps,
|
|
18
18
|
publishDirectory: Schema.optionalKey(Schema.String)
|
|
19
19
|
});
|
|
20
|
-
/**
|
|
21
|
-
* The version gate's own input: `lockfileVersion` and nothing else.
|
|
22
|
-
*
|
|
23
|
-
* The gate has to read the version *before* the shape decode, because the
|
|
24
|
-
* shape it decodes against is the shape of a supported version. `importers` is
|
|
25
|
-
* a required key here and a pre-v9 single-project lockfile has none — it
|
|
26
|
-
* records its dependencies at the top level — so a shape-first order reports a
|
|
27
|
-
* lockfile we reject for being too old as merely malformed instead.
|
|
28
|
-
*
|
|
29
|
-
* @internal
|
|
30
|
-
*/
|
|
31
|
-
const PnpmVersionProbe = Schema.Struct({ lockfileVersion: Schema.Union([Schema.String, Schema.Number]) });
|
|
32
20
|
const PnpmLockfileRaw = Schema.Struct({
|
|
33
21
|
lockfileVersion: Schema.Union([Schema.String, Schema.Number]),
|
|
34
22
|
settings: Schema.optionalKey(Schema.Struct({
|
|
@@ -71,8 +59,7 @@ const PnpmLockfileRaw = Schema.Struct({
|
|
|
71
59
|
*/
|
|
72
60
|
const parsePnpm = (content) => Effect.gen(function* () {
|
|
73
61
|
const { document, documents } = yield* selectPnpmDocument(content);
|
|
74
|
-
|
|
75
|
-
yield* requireLockfileVersion("pnpm", probe.lockfileVersion);
|
|
62
|
+
yield* gatePnpmVersion(document);
|
|
76
63
|
const validated = yield* Schema.decodeUnknownEffect(PnpmLockfileRaw)(document).pipe(Effect.mapError(validationFailure));
|
|
77
64
|
if (Object.keys(validated.importers).length === 0) return yield* Effect.fail(framingFailure("noImporters", documents));
|
|
78
65
|
return yield* toFields(validated);
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { ConfigDependencyLock } from "../ConfigDependencyLock.js";
|
|
2
|
+
import { gatePnpmVersion, validationFailure } from "./shared.js";
|
|
3
|
+
import { selectPnpmEnvDocument } from "./documents.js";
|
|
4
|
+
import { PackageManagerLock } from "../PackageManagerLock.js";
|
|
5
|
+
import { Effect, Schema } from "effect";
|
|
6
|
+
import { IntegrityHash } from "@effected/npm";
|
|
7
|
+
|
|
8
|
+
//#region src/internal/pnpmEnv.ts
|
|
9
|
+
const PnpmEnvImporterDeps = Schema.optionalKey(Schema.Record(Schema.String, Schema.Struct({
|
|
10
|
+
specifier: Schema.String,
|
|
11
|
+
version: Schema.String
|
|
12
|
+
})));
|
|
13
|
+
const PnpmEnvRaw = Schema.Struct({
|
|
14
|
+
importers: Schema.optionalKey(Schema.Record(Schema.String, Schema.Struct({
|
|
15
|
+
packageManagerDependencies: PnpmEnvImporterDeps,
|
|
16
|
+
configDependencies: PnpmEnvImporterDeps
|
|
17
|
+
}))),
|
|
18
|
+
packages: Schema.optionalKey(Schema.Record(Schema.String, Schema.Struct({ resolution: Schema.optionalKey(Schema.Struct({ integrity: Schema.optionalKey(Schema.String) })) }))),
|
|
19
|
+
snapshots: Schema.optionalKey(Schema.Record(Schema.String, Schema.Struct({ optionalDependencies: Schema.optionalKey(Schema.Record(Schema.String, Schema.String)) })))
|
|
20
|
+
});
|
|
21
|
+
/** The package-manager package this reader resolves. */
|
|
22
|
+
const PNPM = "pnpm";
|
|
23
|
+
/** The importer pnpm records `packageManagerDependencies` under. */
|
|
24
|
+
const ROOT_IMPORTER = ".";
|
|
25
|
+
/**
|
|
26
|
+
* Own-property read of a decoded record. The records come from YAML, so a key
|
|
27
|
+
* such as `constructor` or `__proto__` must never be answered by
|
|
28
|
+
* `Object.prototype`.
|
|
29
|
+
*/
|
|
30
|
+
const own = (record, key) => record !== void 0 && Object.hasOwn(record, key) ? record[key] : void 0;
|
|
31
|
+
/**
|
|
32
|
+
* A located, well-formed preamble that does not hold what it promises: the
|
|
33
|
+
* importer names a package the `packages:` section cannot account for. A
|
|
34
|
+
* shape failure of a located document, so it rides the validation channel.
|
|
35
|
+
*/
|
|
36
|
+
const unaccounted = (message) => validationFailure(/* @__PURE__ */ new Error(`pnpm-lock.yaml env preamble: ${message}`));
|
|
37
|
+
/**
|
|
38
|
+
* The SRI integrity of `packages["<key>"]`, failing when the entry, its
|
|
39
|
+
* integrity, or the SRI form of that integrity is missing. Never `undefined`:
|
|
40
|
+
* the caller only asks for keys the lockfile's own graph names.
|
|
41
|
+
*/
|
|
42
|
+
const integrityOf = (raw, key) => {
|
|
43
|
+
const integrity = own(raw.packages, key)?.resolution?.integrity;
|
|
44
|
+
if (integrity === void 0) return Effect.fail(unaccounted(`packages[${JSON.stringify(key)}] records no resolution.integrity`));
|
|
45
|
+
if (!IntegrityHash.isSri(integrity)) return Effect.fail(unaccounted(`packages[${JSON.stringify(key)}].resolution.integrity is not an SRI integrity string`));
|
|
46
|
+
return Effect.succeed(integrity);
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* The SRI integrity of the `<name>@<version>` a preamble entry records,
|
|
50
|
+
* failing first when the version is empty: an empty version names no
|
|
51
|
+
* `packages` entry, and saying so beats reporting the malformed key.
|
|
52
|
+
* `subject` is the entry as the empty-version message names it.
|
|
53
|
+
*/
|
|
54
|
+
const recordedIntegrity = (raw, subject, name, version) => version === "" ? Effect.fail(unaccounted(`${subject} is recorded with an empty version`)) : integrityOf(raw, `${name}@${version}`);
|
|
55
|
+
/**
|
|
56
|
+
* Locate and decode the env preamble of a `pnpm-lock.yaml`: `undefined` when
|
|
57
|
+
* the stream carries none, typed when it is malformed or below the supported
|
|
58
|
+
* lockfile version. The one decode every preamble reader shares, so they
|
|
59
|
+
* cannot disagree about which document is the preamble or what shape it has.
|
|
60
|
+
*/
|
|
61
|
+
const decodePreamble = (content) => Effect.gen(function* () {
|
|
62
|
+
const selected = yield* selectPnpmEnvDocument(content);
|
|
63
|
+
if (selected === void 0) return void 0;
|
|
64
|
+
yield* gatePnpmVersion(selected.document);
|
|
65
|
+
return yield* Schema.decodeUnknownEffect(PnpmEnvRaw)(selected.document).pipe(Effect.mapError(validationFailure));
|
|
66
|
+
});
|
|
67
|
+
/**
|
|
68
|
+
* Read the package manager pinned by a `pnpm-lock.yaml`'s env preamble.
|
|
69
|
+
*
|
|
70
|
+
* @remarks
|
|
71
|
+
* `undefined` means the lockfile records no package manager: no preamble, or
|
|
72
|
+
* a preamble whose root importer declares no `pnpm` package-manager
|
|
73
|
+
* dependency. Everything past that point is a claim the lockfile made, so a
|
|
74
|
+
* claim it cannot back — a missing `pnpm@<version>` entry, a missing snapshot,
|
|
75
|
+
* a missing or non-SRI integrity for pnpm or any native it lists — fails typed
|
|
76
|
+
* rather than degrading to "nothing recorded".
|
|
77
|
+
*
|
|
78
|
+
* Native packages come from the lockfile's own graph — the optional
|
|
79
|
+
* dependencies of the `pnpm@<version>` snapshot — not from a name pattern.
|
|
80
|
+
*
|
|
81
|
+
* @internal
|
|
82
|
+
*/
|
|
83
|
+
const readPnpmPackageManager = (content) => Effect.gen(function* () {
|
|
84
|
+
const raw = yield* decodePreamble(content);
|
|
85
|
+
if (raw === void 0) return void 0;
|
|
86
|
+
const declared = own(own(raw.importers, ROOT_IMPORTER)?.packageManagerDependencies, PNPM);
|
|
87
|
+
if (declared === void 0) return void 0;
|
|
88
|
+
const integrity = yield* recordedIntegrity(raw, PNPM, PNPM, declared.version);
|
|
89
|
+
const key = `${PNPM}@${declared.version}`;
|
|
90
|
+
const snapshot = own(raw.snapshots, key);
|
|
91
|
+
if (snapshot === void 0) return yield* Effect.fail(unaccounted(`snapshots[${JSON.stringify(key)}] is missing`));
|
|
92
|
+
const natives = [];
|
|
93
|
+
for (const [name, version] of Object.entries(snapshot.optionalDependencies ?? {})) natives.push([name, yield* integrityOf(raw, `${name}@${version}`)]);
|
|
94
|
+
return PackageManagerLock.make({
|
|
95
|
+
name: PNPM,
|
|
96
|
+
specifier: declared.specifier,
|
|
97
|
+
version: declared.version,
|
|
98
|
+
integrity,
|
|
99
|
+
nativeIntegrity: Object.fromEntries(natives)
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
/**
|
|
103
|
+
* Read the config dependencies recorded by a `pnpm-lock.yaml`'s env preamble,
|
|
104
|
+
* keyed by name, each with the integrity pnpm recorded for it.
|
|
105
|
+
*
|
|
106
|
+
* @remarks
|
|
107
|
+
* An empty map means the lockfile records none: no preamble, or a preamble
|
|
108
|
+
* whose root importer declares no `configDependencies`. As with
|
|
109
|
+
* {@link readPnpmPackageManager}, every entry the importer names is a claim,
|
|
110
|
+
* so an entry whose `<name>@<version>` has no `packages` entry, no integrity,
|
|
111
|
+
* a non-SRI integrity, or an empty version fails typed rather than being
|
|
112
|
+
* dropped. The map is built from own keys only, so a hostile name such as
|
|
113
|
+
* `__proto__` is an ordinary entry.
|
|
114
|
+
*
|
|
115
|
+
* @internal
|
|
116
|
+
*/
|
|
117
|
+
const readPnpmConfigDependencies = (content) => Effect.gen(function* () {
|
|
118
|
+
const raw = yield* decodePreamble(content);
|
|
119
|
+
const declared = own(raw?.importers, ROOT_IMPORTER)?.configDependencies;
|
|
120
|
+
const locks = /* @__PURE__ */ new Map();
|
|
121
|
+
if (raw === void 0 || declared === void 0) return locks;
|
|
122
|
+
for (const [name, entry] of Object.entries(declared)) {
|
|
123
|
+
const integrity = yield* recordedIntegrity(raw, `config dependency ${JSON.stringify(name)}`, name, entry.version);
|
|
124
|
+
locks.set(name, ConfigDependencyLock.make({
|
|
125
|
+
name,
|
|
126
|
+
specifier: entry.specifier,
|
|
127
|
+
version: entry.version,
|
|
128
|
+
integrity
|
|
129
|
+
}));
|
|
130
|
+
}
|
|
131
|
+
return locks;
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
//#endregion
|
|
135
|
+
export { readPnpmConfigDependencies, readPnpmPackageManager };
|
package/internal/shared.js
CHANGED
|
@@ -187,6 +187,26 @@ const requireLockfileVersion = (format, raw) => {
|
|
|
187
187
|
};
|
|
188
188
|
return Effect.fail(validationFailure(cause));
|
|
189
189
|
};
|
|
190
|
+
/**
|
|
191
|
+
* The pnpm version gate's own input: `lockfileVersion` and nothing else.
|
|
192
|
+
*
|
|
193
|
+
* The gate has to read the version *before* the shape decode, because the
|
|
194
|
+
* shape it decodes against is the shape of a supported version. `importers` is
|
|
195
|
+
* a required key in the lockfile shape and a pre-v9 single-project lockfile has
|
|
196
|
+
* none — it records its dependencies at the top level — so a shape-first order
|
|
197
|
+
* reports a lockfile we reject for being too old as merely malformed instead.
|
|
198
|
+
*
|
|
199
|
+
* @internal
|
|
200
|
+
*/
|
|
201
|
+
const PnpmVersionProbe = Schema.Struct({ lockfileVersion: Schema.Union([Schema.String, Schema.Number]) });
|
|
202
|
+
/**
|
|
203
|
+
* Gate a located pnpm document (lockfile or env preamble) on its format
|
|
204
|
+
* version: decode {@link PnpmVersionProbe}, then {@link requireLockfileVersion}.
|
|
205
|
+
* Call it BEFORE the shape decode, for the reason the probe documents.
|
|
206
|
+
*
|
|
207
|
+
* @internal
|
|
208
|
+
*/
|
|
209
|
+
const gatePnpmVersion = (document) => Schema.decodeUnknownEffect(PnpmVersionProbe)(document).pipe(Effect.mapError(validationFailure), Effect.flatMap((probe) => requireLockfileVersion("pnpm", probe.lockfileVersion)));
|
|
190
210
|
/** @internal */
|
|
191
211
|
const syntaxFailure = (cause) => ({
|
|
192
212
|
stage: "syntax",
|
|
@@ -235,4 +255,4 @@ const extractWorkspaceDeps = (workspaces, workspaceNames) => {
|
|
|
235
255
|
};
|
|
236
256
|
|
|
237
257
|
//#endregion
|
|
238
|
-
export { DEP_TYPES, MINIMUM_LOCKFILE_VERSION, extractWorkspaceDeps, framingFailure, importerDependencies, isWorkspaceSpecifier, peerDeclarations, requireLockfileVersion, splitPeerSuffix, syntaxFailure, toIntegrityHash, validationFailure };
|
|
258
|
+
export { DEP_TYPES, MINIMUM_LOCKFILE_VERSION, extractWorkspaceDeps, framingFailure, gatePnpmVersion, importerDependencies, isWorkspaceSpecifier, peerDeclarations, requireLockfileVersion, splitPeerSuffix, syntaxFailure, toIntegrityHash, validationFailure };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@effected/lockfiles",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0-next-20260927062352",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Pure lockfile parsing for bun, npm, pnpm and yarn Berry into one unified Effect schema model, with pure integrity checking against workspace manifests.",
|
|
6
6
|
"keywords": [
|
|
@@ -40,11 +40,11 @@
|
|
|
40
40
|
"./package.json": "./package.json"
|
|
41
41
|
},
|
|
42
42
|
"peerDependencies": {
|
|
43
|
-
"@effected/jsonc": "^0.
|
|
44
|
-
"@effected/npm": "^0.
|
|
45
|
-
"@effected/semver": "^0.
|
|
46
|
-
"@effected/yaml": "^0.
|
|
47
|
-
"effect": "4.0.0-rc.
|
|
43
|
+
"@effected/jsonc": "^0.13.0",
|
|
44
|
+
"@effected/npm": "^0.17.0-next-20260927062352",
|
|
45
|
+
"@effected/semver": "^0.9.0",
|
|
46
|
+
"@effected/yaml": "^0.17.0",
|
|
47
|
+
"effect": "4.0.0-rc.117"
|
|
48
48
|
},
|
|
49
49
|
"engines": {
|
|
50
50
|
"node": ">=24.11.0"
|