@effected/lockfiles 0.1.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/BunExtension.js +25 -0
- package/ImporterDependency.js +33 -0
- package/LICENSE +21 -0
- package/Lockfile.js +258 -0
- package/LockfileFormat.js +52 -0
- package/LockfileImporter.js +29 -0
- package/LockfileIntegrity.js +123 -0
- package/PnpmExtension.js +29 -0
- package/README.md +109 -0
- package/ResolvedPackage.js +40 -0
- package/WorkspaceDependency.js +27 -0
- package/index.d.ts +457 -0
- package/index.js +11 -0
- package/internal/bun.js +102 -0
- package/internal/documents.js +76 -0
- package/internal/npm.js +105 -0
- package/internal/pnpm.js +135 -0
- package/internal/shared.js +133 -0
- package/internal/yarn.js +130 -0
- package/package.json +51 -0
- package/tsdoc-metadata.json +11 -0
package/README.md
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# @effected/lockfiles
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@effected/lockfiles)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[](https://nodejs.org/)
|
|
6
|
+
[](https://www.typescriptlang.org/)
|
|
7
|
+
|
|
8
|
+
Lockfile parsing for [Effect](https://effect.website) v4: bun (`bun.lock`), npm (`package-lock.json` v2/v3), pnpm (`pnpm-lock.yaml`) and yarn Berry (`yarn.lock`) all normalized into one `Lockfile` schema model, plus pure integrity checking of that model against workspace manifests. Four formats, one model, no IO and no external runtime dependencies.
|
|
9
|
+
|
|
10
|
+
> **Pre-release.** This package is part of the `@effected/*` kit, in pre-`1.0.0`
|
|
11
|
+
> development against a single pinned Effect v4 beta. Packages graduate to
|
|
12
|
+
> `1.0.0` once Effect `4.0.0` ships. To hold your own `effect` versions at
|
|
13
|
+
> exactly the ones the kit is built and tested against, install
|
|
14
|
+
> [`@effected/pnpm-plugin-effect`](https://www.npmjs.com/package/@effected/pnpm-plugin-effect).
|
|
15
|
+
>
|
|
16
|
+
> **Stability: unstable.** This package's API surface is not yet considered
|
|
17
|
+
> complete and may change across `0.x` releases. Pin an exact version — even a
|
|
18
|
+
> package marked *stable* before `1.0.0` can introduce a breaking change by
|
|
19
|
+
> accident, and an exact pin turns that into a type-check error rather than a
|
|
20
|
+
> runtime surprise. Full policy: [release strategy](https://github.com/spencerbeggs/effected#release-strategy).
|
|
21
|
+
|
|
22
|
+
## Why @effected/lockfiles
|
|
23
|
+
|
|
24
|
+
Every package manager writes its lockfile in a different dialect — JSONC for bun, JSON for npm, YAML for pnpm and yarn — and each encodes packages, workspace edges and integrity data differently. Tooling that wants to answer "which version of `typescript` is resolved here" ends up with four code paths and four sets of bugs. This package normalizes all four into one model, so the question is asked once regardless of which package manager produced the file.
|
|
25
|
+
|
|
26
|
+
Every entrypoint takes content as a **string**. The package performs no IO at all: reading files, finding workspace roots and detecting which package manager a repo uses belong to its consumers, and keeping them out means the parser is a pure function you can drive from a fixture, a network response or a git blob. Malformed input always exits through a typed error channel, never as a defect, and the two ways a lockfile can be unusable are distinct tags rather than one blurry `reason` string. Yarn support is Berry only, and that is enforced: classic v1 content fails typed instead of being mis-normalized into something that looks plausible.
|
|
27
|
+
|
|
28
|
+
## Install
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npm install @effected/lockfiles @effected/jsonc @effected/semver @effected/yaml effect
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pnpm add @effected/lockfiles @effected/jsonc @effected/semver @effected/yaml effect
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Requires Node.js >=24.11.0. `effect` v4, `@effected/jsonc`, `@effected/semver` and `@effected/yaml` are peer dependencies — the JSONC and YAML engines and the SemVer range checker all arrive through those siblings, so nothing outside `effect` and `@effected/*` reaches your tree. Package managers that install peers automatically will pull them in; add them to your manifest explicitly if yours does not.
|
|
39
|
+
|
|
40
|
+
## Quick start
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
import { Lockfile, LockfileIntegrity, WorkspaceManifest } from "@effected/lockfiles";
|
|
44
|
+
import { Effect } from "effect";
|
|
45
|
+
|
|
46
|
+
declare const content: string; // lockfile text, read by the caller
|
|
47
|
+
|
|
48
|
+
const program = Effect.gen(function* () {
|
|
49
|
+
// The only fallible boundary in the package.
|
|
50
|
+
const lockfile = yield* Lockfile.parse(content, { format: "pnpm" });
|
|
51
|
+
|
|
52
|
+
// pnpm workspace packages come back keyed by importer path; rewrite them
|
|
53
|
+
// once you have read the manifests. Total and pure — no error channel.
|
|
54
|
+
const named = lockfile.withImporterNames(new Map([["packages/core", "@acme/core"]]));
|
|
55
|
+
|
|
56
|
+
// Total lookups over the model.
|
|
57
|
+
const versions = named.packagesNamed("typescript").map((p) => p.version);
|
|
58
|
+
|
|
59
|
+
// Pure integrity checking — no Effect, no error channel, no IO.
|
|
60
|
+
const report = LockfileIntegrity.compare(named, [
|
|
61
|
+
WorkspaceManifest.make({ name: "@acme/core", dependencies: { lodash: "^4.17.0" } }),
|
|
62
|
+
]);
|
|
63
|
+
|
|
64
|
+
return { versions, workspaces: named.workspacePackages.length, valid: report.valid };
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
Effect.runPromise(program).then(console.log);
|
|
68
|
+
// { versions: [...resolved typescript versions], workspaces: <count>, valid: true | false }
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Formats
|
|
72
|
+
|
|
73
|
+
`format` is a literal, and `filenameFor` / `fromFilename` map between the literal and the file on disk so a consumer that detected a package manager never has to hard-code a filename.
|
|
74
|
+
|
|
75
|
+
| Format | Filename | Dialect | Notes |
|
|
76
|
+
| ------ | -------- | ------- | ----- |
|
|
77
|
+
| `"bun"` | `bun.lock` | JSONC | Trusted dependencies preserved on `BunExtension` |
|
|
78
|
+
| `"npm"` | `package-lock.json` | JSON | v2 and v3 |
|
|
79
|
+
| `"pnpm"` | `pnpm-lock.yaml` | YAML | Catalogs preserved on `PnpmExtension`; see below |
|
|
80
|
+
| `"yarn"` | `yarn.lock` | YAML | Berry only — classic v1 fails typed |
|
|
81
|
+
|
|
82
|
+
## Errors
|
|
83
|
+
|
|
84
|
+
`Lockfile.parse` is the only fallible entrypoint, and it fails two ways.
|
|
85
|
+
|
|
86
|
+
| Tag | Means | Fields |
|
|
87
|
+
| --- | ----- | ------ |
|
|
88
|
+
| `LockfileParseError` | The content is not a valid lockfile of that format. | `format`, `stage` (`"syntax"` when the text itself did not parse, `"validation"` when it parsed but did not have the expected shape), `cause` (structural, never stringified) |
|
|
89
|
+
| `LockfileFramingError` | The text parsed, but no single lockfile document could be located in it. | `format`, `documents`, `reason` (`"noLockfileDocument"`, `"noImporters"`, `"unexpectedDocuments"`) |
|
|
90
|
+
|
|
91
|
+
The framing error exists because `pnpm-lock.yaml` is a YAML **stream**, not a YAML document. pnpm 11 writes a config-dependencies preamble ahead of the lockfile whenever a workspace uses `configDependencies`, so the file carries two documents — and the preamble declares `lockfileVersion`, `importers` and `packages` too, so a parser that reads only the first document gets a lockfile that *validates* and describes an empty workspace. That is a wrong answer shaped exactly like a right one. The rule here is positional and deterministic rather than a heuristic: pnpm composes the preamble as a prefix, so the lockfile is the **last** document. A stream carrying no lockfile document fails typed. It never degrades into an empty `Lockfile`.
|
|
92
|
+
|
|
93
|
+
yarn defines no document framing at all, so multi-document `yarn.lock` content fails with `"unexpectedDocuments"` rather than being silently truncated to its first document. Where a format states no rule, this package refuses to guess.
|
|
94
|
+
|
|
95
|
+
## Features
|
|
96
|
+
|
|
97
|
+
- `Lockfile.parse(content, { format })` — the package's only fallible boundary; fails with `LockfileParseError` or `LockfileFramingError`.
|
|
98
|
+
- `Lockfile#withImporterNames(names)` — the pure second stage for pnpm: rewrites importer-path names and both ends of each dependency edge once the consumer has read the manifests.
|
|
99
|
+
- `Lockfile#packagesNamed(name)` and `Lockfile#workspacePackages` — total lookups over the model.
|
|
100
|
+
- `LockfileFormat` with `filenameFor` / `fromFilename` — the format literal and its mapping to lockfile filenames.
|
|
101
|
+
- `LockfileIntegrity.compare(lockfile, manifests)` — total, pure integrity checking with no error channel; the report carries `valid`, `missingWorkspaces`, `extraWorkspaces` and `unsatisfiedConstraints`. Constraint checking is best-effort by design: `workspace:` / `link:` / `file:` specifiers and rows whose range does not parse as SemVer are skipped.
|
|
102
|
+
- `WorkspaceManifest` — the manifest input shape for integrity checking: a package name plus four optional dependency records. Deliberately a plain value rather than a strict manifest model, so consumers can derive it from anything.
|
|
103
|
+
- `ResolvedPackage` — name, version, optional integrity hash, workspace flag and relative path, dependency map.
|
|
104
|
+
- `WorkspaceDependency` and `DependencyType` — a `from`/`to` edge between workspace packages with its dependency type and constraint.
|
|
105
|
+
- `PnpmExtension` and `BunExtension` — format-specific data such as pnpm catalogs and bun trusted dependencies, preserved on the model's optional `extension` field.
|
|
106
|
+
|
|
107
|
+
## License
|
|
108
|
+
|
|
109
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Effect, Schema } from "effect";
|
|
2
|
+
import { IntegrityHash } from "@effected/npm";
|
|
3
|
+
|
|
4
|
+
//#region src/ResolvedPackage.ts
|
|
5
|
+
const EMPTY_DEPENDENCIES = {};
|
|
6
|
+
/**
|
|
7
|
+
* A package resolved from a lockfile.
|
|
8
|
+
*
|
|
9
|
+
* @remarks
|
|
10
|
+
* The common shape every format's entries normalize into:
|
|
11
|
+
*
|
|
12
|
+
* - `name` — the resolved package name. For pnpm *workspace* packages
|
|
13
|
+
* straight out of `Lockfile.parse` this is the importer path until
|
|
14
|
+
* `Lockfile#withImporterNames` rewrites it.
|
|
15
|
+
* - `version` — the resolved version string (pnpm workspace packages carry
|
|
16
|
+
* `"0.0.0"`; the lockfile does not record their real versions).
|
|
17
|
+
* - `integrity` — optional `@effected/npm` `IntegrityHash`, covering npm/pnpm
|
|
18
|
+
* `sha512-...` SRI and yarn Berry's `10c0/...` cache checksums (the yarn
|
|
19
|
+
* textual form). An *absent* integrity is omitted, so a `ResolvedPackage` may
|
|
20
|
+
* carry none; a *present but unparseable* integrity fails the parse typed at
|
|
21
|
+
* validation rather than being silently dropped.
|
|
22
|
+
* - `isWorkspace` — `true` for workspace-local packages.
|
|
23
|
+
* - `relativePath` — the workspace-relative directory for workspace
|
|
24
|
+
* packages, when the lockfile records one.
|
|
25
|
+
* - `dependencies` — the package's own dependency map, defaulting to `{}`
|
|
26
|
+
* both at construction and when decoding serialized data.
|
|
27
|
+
*
|
|
28
|
+
* @public
|
|
29
|
+
*/
|
|
30
|
+
var ResolvedPackage = class extends Schema.Class("ResolvedPackage")({
|
|
31
|
+
name: Schema.NonEmptyString,
|
|
32
|
+
version: Schema.String,
|
|
33
|
+
integrity: Schema.optionalKey(IntegrityHash),
|
|
34
|
+
isWorkspace: Schema.Boolean,
|
|
35
|
+
relativePath: Schema.optionalKey(Schema.String),
|
|
36
|
+
dependencies: Schema.Record(Schema.String, Schema.String).pipe(Schema.withDecodingDefaultKey(Effect.succeed(EMPTY_DEPENDENCIES)), Schema.withConstructorDefault(Effect.succeed(EMPTY_DEPENDENCIES)))
|
|
37
|
+
}) {};
|
|
38
|
+
|
|
39
|
+
//#endregion
|
|
40
|
+
export { ResolvedPackage };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
import { DependencyField } from "@effected/npm";
|
|
3
|
+
|
|
4
|
+
//#region src/WorkspaceDependency.ts
|
|
5
|
+
/**
|
|
6
|
+
* A directed dependency edge between two workspace packages as recorded in
|
|
7
|
+
* the lockfile.
|
|
8
|
+
*
|
|
9
|
+
* @remarks
|
|
10
|
+
* - `from` — the workspace package declaring the dependency. For pnpm this
|
|
11
|
+
* is the importer path until `Lockfile#withImporterNames` rewrites it.
|
|
12
|
+
* - `to` — the workspace package depended upon.
|
|
13
|
+
* - `depType` — which dependency map holds the edge, spelled with
|
|
14
|
+
* `@effected/npm`'s kit-wide `DependencyField` vocabulary.
|
|
15
|
+
* - `constraint` — the declared specifier (e.g. `"workspace:*"`, `"^1.0.0"`).
|
|
16
|
+
*
|
|
17
|
+
* @public
|
|
18
|
+
*/
|
|
19
|
+
var WorkspaceDependency = class extends Schema.Class("WorkspaceDependency")({
|
|
20
|
+
from: Schema.NonEmptyString,
|
|
21
|
+
to: Schema.NonEmptyString,
|
|
22
|
+
depType: DependencyField,
|
|
23
|
+
constraint: Schema.String
|
|
24
|
+
}) {};
|
|
25
|
+
|
|
26
|
+
//#endregion
|
|
27
|
+
export { WorkspaceDependency };
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
import { Effect, Option, Schema } from "effect";
|
|
2
|
+
//#region src/BunExtension.d.ts
|
|
3
|
+
declare const BunExtension_base: Schema.Class<BunExtension, Schema.Struct<{
|
|
4
|
+
readonly _tag: Schema.tag<"bun">;
|
|
5
|
+
readonly catalog: Schema.optionalKey<Schema.$Record<Schema.String, Schema.Unknown>>;
|
|
6
|
+
readonly catalogs: Schema.optionalKey<Schema.$Record<Schema.String, Schema.$Record<Schema.String, Schema.Unknown>>>;
|
|
7
|
+
readonly overrides: Schema.optionalKey<Schema.$Record<Schema.String, Schema.String>>;
|
|
8
|
+
readonly trustedDependencies: Schema.optionalKey<Schema.$Array<Schema.String>>;
|
|
9
|
+
}>, {}>;
|
|
10
|
+
/**
|
|
11
|
+
* Extension data specific to bun lockfiles, attached to `Lockfile.extension`
|
|
12
|
+
* when the format is `"bun"`.
|
|
13
|
+
*
|
|
14
|
+
* @remarks
|
|
15
|
+
* - `catalog` — the default (unnamed) catalog.
|
|
16
|
+
* - `catalogs` — named catalog definitions.
|
|
17
|
+
* - `overrides` — the version override map.
|
|
18
|
+
* - `trustedDependencies` — packages allowed to run install scripts.
|
|
19
|
+
*
|
|
20
|
+
* @public
|
|
21
|
+
*/
|
|
22
|
+
declare class BunExtension extends BunExtension_base {}
|
|
23
|
+
//#endregion
|
|
24
|
+
//#region src/ImporterDependency.d.ts
|
|
25
|
+
declare const ImporterDependency_base: Schema.Class<ImporterDependency, Schema.Struct<{
|
|
26
|
+
readonly name: Schema.NonEmptyString;
|
|
27
|
+
readonly specifier: Schema.Codec<import("@effected/npm").ClassifiedSpecifier, string, never, never>;
|
|
28
|
+
readonly version: Schema.optionalKey<Schema.String>;
|
|
29
|
+
readonly depType: Schema.Literals<readonly ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]>;
|
|
30
|
+
}>, {}>;
|
|
31
|
+
/**
|
|
32
|
+
* One declared dependency of one workspace importer, as the lockfile records it.
|
|
33
|
+
*
|
|
34
|
+
* @remarks
|
|
35
|
+
* - `name` — the dependency's package name.
|
|
36
|
+
* - `specifier` — the declared range, typed through `@effected/npm`'s
|
|
37
|
+
* `DependencySpecifier.FromString` codec: the decoded value is a tag-matchable
|
|
38
|
+
* `ClassifiedSpecifier` (`catalog:` / `workspace:` / range / dist-tag / raw),
|
|
39
|
+
* while encoding round-trips the **exact original string byte-for-byte** — the
|
|
40
|
+
* guarantee a before/after lockfile diff relies on.
|
|
41
|
+
* - `version` — the concrete resolved version, **populated by pnpm only**. pnpm
|
|
42
|
+
* records `{ specifier, version }` per importer dependency; bun and npm record
|
|
43
|
+
* resolved versions on their package entries instead, so for those formats
|
|
44
|
+
* `version` is absent and a consumer joins by `name` against
|
|
45
|
+
* `Lockfile.packages`.
|
|
46
|
+
* - `depType` — which dependency map declared it, spelled with `@effected/npm`'s
|
|
47
|
+
* kit-wide `DependencyField` vocabulary.
|
|
48
|
+
*
|
|
49
|
+
* @public
|
|
50
|
+
*/
|
|
51
|
+
declare class ImporterDependency extends ImporterDependency_base {}
|
|
52
|
+
//#endregion
|
|
53
|
+
//#region src/LockfileFormat.d.ts
|
|
54
|
+
/**
|
|
55
|
+
* The lockfile formats this package parses: bun's `bun.lock` (JSONC), npm's
|
|
56
|
+
* `package-lock.json` (v2/v3 JSON), pnpm's `pnpm-lock.yaml` and yarn Berry's
|
|
57
|
+
* `yarn.lock` (both YAML).
|
|
58
|
+
*
|
|
59
|
+
* @remarks
|
|
60
|
+
* The literal names the *lockfile format*, not the package manager that
|
|
61
|
+
* happens to write it — this package models lockfiles. Yarn support is
|
|
62
|
+
* Berry only; classic (v1) `yarn.lock` is not YAML and fails
|
|
63
|
+
* `Lockfile.parse` with a typed `LockfileParseError`.
|
|
64
|
+
*
|
|
65
|
+
* @public
|
|
66
|
+
*/
|
|
67
|
+
declare const LockfileFormat: Schema.Literals<readonly ["bun", "npm", "pnpm", "yarn"]>;
|
|
68
|
+
/**
|
|
69
|
+
* The union of supported lockfile format names.
|
|
70
|
+
*
|
|
71
|
+
* @public
|
|
72
|
+
*/
|
|
73
|
+
type LockfileFormat = typeof LockfileFormat.Type;
|
|
74
|
+
/**
|
|
75
|
+
* The conventional lockfile filename for a format: `"bun.lock"`,
|
|
76
|
+
* `"package-lock.json"`, `"pnpm-lock.yaml"` or `"yarn.lock"`.
|
|
77
|
+
*
|
|
78
|
+
* @public
|
|
79
|
+
*/
|
|
80
|
+
declare const filenameFor: (format: LockfileFormat) => string;
|
|
81
|
+
/**
|
|
82
|
+
* The format a lockfile filename identifies, if any.
|
|
83
|
+
*
|
|
84
|
+
* @remarks
|
|
85
|
+
* Matches exact conventional filenames only (`"bun.lock"`,
|
|
86
|
+
* `"package-lock.json"`, `"pnpm-lock.yaml"`, `"yarn.lock"`) — paths and
|
|
87
|
+
* other spellings return `Option.none()`.
|
|
88
|
+
*
|
|
89
|
+
* @public
|
|
90
|
+
*/
|
|
91
|
+
declare const fromFilename: (name: string) => Option.Option<LockfileFormat>;
|
|
92
|
+
//#endregion
|
|
93
|
+
//#region src/LockfileImporter.d.ts
|
|
94
|
+
declare const LockfileImporter_base: Schema.Class<LockfileImporter, Schema.Struct<{
|
|
95
|
+
readonly path: Schema.NonEmptyString;
|
|
96
|
+
readonly dependencies: Schema.$Array<typeof ImporterDependency>;
|
|
97
|
+
}>, {}>;
|
|
98
|
+
/**
|
|
99
|
+
* One workspace importer's declared dependencies, as the lockfile records them.
|
|
100
|
+
*
|
|
101
|
+
* @remarks
|
|
102
|
+
* - `path` — the importer path relative to the workspace root, `"."` for the
|
|
103
|
+
* root package (never empty — a `NonEmptyString`) — the same keys as
|
|
104
|
+
* `WorkspaceDiscovery.importerMap()` in
|
|
105
|
+
* `@effected/workspaces`. This is the stable join key: `Lockfile#importer`
|
|
106
|
+
* looks importers up by it, and `Lockfile#withImporterNames` deliberately
|
|
107
|
+
* leaves importers untouched because the path — not a package name — keys
|
|
108
|
+
* them.
|
|
109
|
+
* - `dependencies` — each declared dependency as an {@link ImporterDependency}.
|
|
110
|
+
*
|
|
111
|
+
* Populated by the pnpm, bun and npm parsers. yarn does not record importers,
|
|
112
|
+
* so a yarn lockfile always yields an empty `importers` array.
|
|
113
|
+
*
|
|
114
|
+
* @public
|
|
115
|
+
*/
|
|
116
|
+
declare class LockfileImporter extends LockfileImporter_base {}
|
|
117
|
+
//#endregion
|
|
118
|
+
//#region src/PnpmExtension.d.ts
|
|
119
|
+
/**
|
|
120
|
+
* The pnpm `catalogs:` record shape as it appears in `pnpm-lock.yaml`:
|
|
121
|
+
* catalog name → package name → pinned version string or
|
|
122
|
+
* `{ specifier, version }` pair.
|
|
123
|
+
*
|
|
124
|
+
* @remarks
|
|
125
|
+
* Exported so consumers (e.g. a `CatalogSet.fromLockfileCatalogs`) can type
|
|
126
|
+
* against the lockfile's own shape instead of re-declaring it.
|
|
127
|
+
*
|
|
128
|
+
* @public
|
|
129
|
+
*/
|
|
130
|
+
type PnpmCatalogs = Record<string, Record<string, string | {
|
|
131
|
+
readonly specifier: string;
|
|
132
|
+
readonly version: string;
|
|
133
|
+
}>>;
|
|
134
|
+
declare const PnpmExtension_base: Schema.Class<PnpmExtension, Schema.Struct<{
|
|
135
|
+
readonly _tag: Schema.tag<"pnpm">;
|
|
136
|
+
readonly catalogs: Schema.optionalKey<Schema.$Record<Schema.String, Schema.$Record<Schema.String, Schema.Union<readonly [Schema.String, Schema.Struct<{
|
|
137
|
+
readonly specifier: Schema.String;
|
|
138
|
+
readonly version: Schema.String;
|
|
139
|
+
}>]>>>>;
|
|
140
|
+
readonly overrides: Schema.optionalKey<Schema.$Record<Schema.String, Schema.String>>;
|
|
141
|
+
readonly settings: Schema.optionalKey<Schema.Struct<{
|
|
142
|
+
readonly autoInstallPeers: Schema.optionalKey<Schema.Boolean>;
|
|
143
|
+
readonly excludeLinksFromLockfile: Schema.optionalKey<Schema.Boolean>;
|
|
144
|
+
}>>;
|
|
145
|
+
}>, {}>;
|
|
146
|
+
/**
|
|
147
|
+
* Extension data specific to pnpm lockfiles, attached to `Lockfile.extension`
|
|
148
|
+
* when the format is `"pnpm"`.
|
|
149
|
+
*
|
|
150
|
+
* @remarks
|
|
151
|
+
* - `catalogs` — pnpm catalog definitions ({@link PnpmCatalogs}).
|
|
152
|
+
* - `overrides` — the version override map recorded in the lockfile header.
|
|
153
|
+
* - `settings` — pnpm settings recorded in the lockfile header.
|
|
154
|
+
*
|
|
155
|
+
* @public
|
|
156
|
+
*/
|
|
157
|
+
declare class PnpmExtension extends PnpmExtension_base {}
|
|
158
|
+
//#endregion
|
|
159
|
+
//#region src/ResolvedPackage.d.ts
|
|
160
|
+
declare const ResolvedPackage_base: Schema.Class<ResolvedPackage, Schema.Struct<{
|
|
161
|
+
readonly name: Schema.NonEmptyString;
|
|
162
|
+
readonly version: Schema.String;
|
|
163
|
+
readonly integrity: Schema.optionalKey<Schema.brand<Schema.String, "IntegrityHash"> & {
|
|
164
|
+
isSri: (value: string) => boolean;
|
|
165
|
+
isCorepack: (value: string) => boolean;
|
|
166
|
+
isYarnChecksum: (value: string) => boolean;
|
|
167
|
+
isValid: (value: string) => boolean;
|
|
168
|
+
algorithmOf: (value: string) => import("effect/Option").Option<import("@effected/npm").IntegrityAlgorithm>;
|
|
169
|
+
decode: (input: string) => Effect.Effect<import("@effected/npm").IntegrityHashBrand, import("@effected/npm").InvalidIntegrityHashError>;
|
|
170
|
+
}>;
|
|
171
|
+
readonly isWorkspace: Schema.Boolean;
|
|
172
|
+
readonly relativePath: Schema.optionalKey<Schema.String>;
|
|
173
|
+
readonly dependencies: Schema.withConstructorDefault<Schema.withDecodingDefaultKey<Schema.$Record<Schema.String, Schema.String>, never>>;
|
|
174
|
+
}>, {}>;
|
|
175
|
+
/**
|
|
176
|
+
* A package resolved from a lockfile.
|
|
177
|
+
*
|
|
178
|
+
* @remarks
|
|
179
|
+
* The common shape every format's entries normalize into:
|
|
180
|
+
*
|
|
181
|
+
* - `name` — the resolved package name. For pnpm *workspace* packages
|
|
182
|
+
* straight out of `Lockfile.parse` this is the importer path until
|
|
183
|
+
* `Lockfile#withImporterNames` rewrites it.
|
|
184
|
+
* - `version` — the resolved version string (pnpm workspace packages carry
|
|
185
|
+
* `"0.0.0"`; the lockfile does not record their real versions).
|
|
186
|
+
* - `integrity` — optional `@effected/npm` `IntegrityHash`, covering npm/pnpm
|
|
187
|
+
* `sha512-...` SRI and yarn Berry's `10c0/...` cache checksums (the yarn
|
|
188
|
+
* textual form). An *absent* integrity is omitted, so a `ResolvedPackage` may
|
|
189
|
+
* carry none; a *present but unparseable* integrity fails the parse typed at
|
|
190
|
+
* validation rather than being silently dropped.
|
|
191
|
+
* - `isWorkspace` — `true` for workspace-local packages.
|
|
192
|
+
* - `relativePath` — the workspace-relative directory for workspace
|
|
193
|
+
* packages, when the lockfile records one.
|
|
194
|
+
* - `dependencies` — the package's own dependency map, defaulting to `{}`
|
|
195
|
+
* both at construction and when decoding serialized data.
|
|
196
|
+
*
|
|
197
|
+
* @public
|
|
198
|
+
*/
|
|
199
|
+
declare class ResolvedPackage extends ResolvedPackage_base {}
|
|
200
|
+
//#endregion
|
|
201
|
+
//#region src/WorkspaceDependency.d.ts
|
|
202
|
+
declare const WorkspaceDependency_base: Schema.Class<WorkspaceDependency, Schema.Struct<{
|
|
203
|
+
readonly from: Schema.NonEmptyString;
|
|
204
|
+
readonly to: Schema.NonEmptyString;
|
|
205
|
+
readonly depType: Schema.Literals<readonly ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]>;
|
|
206
|
+
readonly constraint: Schema.String;
|
|
207
|
+
}>, {}>;
|
|
208
|
+
/**
|
|
209
|
+
* A directed dependency edge between two workspace packages as recorded in
|
|
210
|
+
* the lockfile.
|
|
211
|
+
*
|
|
212
|
+
* @remarks
|
|
213
|
+
* - `from` — the workspace package declaring the dependency. For pnpm this
|
|
214
|
+
* is the importer path until `Lockfile#withImporterNames` rewrites it.
|
|
215
|
+
* - `to` — the workspace package depended upon.
|
|
216
|
+
* - `depType` — which dependency map holds the edge, spelled with
|
|
217
|
+
* `@effected/npm`'s kit-wide `DependencyField` vocabulary.
|
|
218
|
+
* - `constraint` — the declared specifier (e.g. `"workspace:*"`, `"^1.0.0"`).
|
|
219
|
+
*
|
|
220
|
+
* @public
|
|
221
|
+
*/
|
|
222
|
+
declare class WorkspaceDependency extends WorkspaceDependency_base {}
|
|
223
|
+
//#endregion
|
|
224
|
+
//#region src/Lockfile.d.ts
|
|
225
|
+
declare const LockfileParseError_base: Schema.Class<LockfileParseError, Schema.TaggedStruct<"LockfileParseError", {
|
|
226
|
+
readonly format: Schema.Literals<readonly ["bun", "npm", "pnpm", "yarn"]>;
|
|
227
|
+
readonly stage: Schema.Literals<readonly ["syntax", "validation"]>;
|
|
228
|
+
readonly cause: Schema.Defect;
|
|
229
|
+
}>, import("effect/Cause").YieldableError>;
|
|
230
|
+
/**
|
|
231
|
+
* Failure of `Lockfile.parse`: the given content is not a valid lockfile of
|
|
232
|
+
* the requested format.
|
|
233
|
+
*
|
|
234
|
+
* @remarks
|
|
235
|
+
* - `format` — which format was being parsed.
|
|
236
|
+
* - `stage` — `"syntax"` when the text itself failed to parse (YAML, JSON,
|
|
237
|
+
* JSONC), `"validation"` when the text parsed but did not have the
|
|
238
|
+
* format's expected shape.
|
|
239
|
+
* - `cause` — the underlying jsonc/yaml/JSON/Schema error, preserved
|
|
240
|
+
* structurally.
|
|
241
|
+
*
|
|
242
|
+
* Malformed input always exits through this typed failure, never as a
|
|
243
|
+
* defect. Parse takes content, not a path — the caller that did the IO owns
|
|
244
|
+
* any path context.
|
|
245
|
+
*
|
|
246
|
+
* @public
|
|
247
|
+
*/
|
|
248
|
+
declare class LockfileParseError extends LockfileParseError_base {
|
|
249
|
+
get message(): string;
|
|
250
|
+
}
|
|
251
|
+
declare const LockfileFramingError_base: Schema.Class<LockfileFramingError, Schema.TaggedStruct<"LockfileFramingError", {
|
|
252
|
+
readonly format: Schema.Literals<readonly ["bun", "npm", "pnpm", "yarn"]>;
|
|
253
|
+
readonly reason: Schema.Literals<readonly ["noLockfileDocument", "noImporters", "unexpectedDocuments"]>;
|
|
254
|
+
readonly documents: Schema.Int;
|
|
255
|
+
}>, import("effect/Cause").YieldableError>;
|
|
256
|
+
/**
|
|
257
|
+
* Failure of `Lockfile.parse`: the content parsed as text, but no single
|
|
258
|
+
* lockfile document could be located in it.
|
|
259
|
+
*
|
|
260
|
+
* @remarks
|
|
261
|
+
* `pnpm-lock.yaml` is a YAML **stream**. pnpm 11 writes a
|
|
262
|
+
* config-dependencies preamble document ahead of the lockfile whenever the
|
|
263
|
+
* workspace uses `configDependencies`, so the file holds two documents. The
|
|
264
|
+
* lockfile is the last one — pnpm composes the preamble as a prefix — and a
|
|
265
|
+
* parser that reads only the first document gets the preamble: a document
|
|
266
|
+
* that *validates*, and yields a lockfile with an empty workspace. This error
|
|
267
|
+
* exists so that case can never again succeed quietly.
|
|
268
|
+
*
|
|
269
|
+
* - `format` — which format was being parsed.
|
|
270
|
+
* - `documents` — how many YAML documents the stream carried.
|
|
271
|
+
* - `reason`:
|
|
272
|
+
* - `"noLockfileDocument"` — the stream carries no lockfile document. An
|
|
273
|
+
* env-only `pnpm-lock.yaml` (a config-dependencies preamble and nothing
|
|
274
|
+
* after it) reads this way, as does empty content. pnpm itself treats
|
|
275
|
+
* such a file as having no lockfile.
|
|
276
|
+
* - `"noImporters"` — the located document declares no importers, so it
|
|
277
|
+
* describes no workspace. pnpm always records at least the root importer.
|
|
278
|
+
* - `"unexpectedDocuments"` — the stream carries several documents in a
|
|
279
|
+
* format that defines no document framing (yarn). Rather than silently
|
|
280
|
+
* taking the first, parsing refuses to guess.
|
|
281
|
+
*
|
|
282
|
+
* It carries typed fields rather than a `cause`: unlike
|
|
283
|
+
* {@link LockfileParseError}, there is no underlying engine failure to wrap —
|
|
284
|
+
* the text parsed fine.
|
|
285
|
+
*
|
|
286
|
+
* @public
|
|
287
|
+
*/
|
|
288
|
+
declare class LockfileFramingError extends LockfileFramingError_base {
|
|
289
|
+
get message(): string;
|
|
290
|
+
}
|
|
291
|
+
declare const Lockfile_base: Schema.Class<Lockfile, Schema.Struct<{
|
|
292
|
+
readonly format: Schema.Literals<readonly ["bun", "npm", "pnpm", "yarn"]>;
|
|
293
|
+
readonly lockfileVersion: Schema.String;
|
|
294
|
+
readonly packages: Schema.$Array<typeof ResolvedPackage>;
|
|
295
|
+
readonly workspaceDependencies: Schema.$Array<typeof WorkspaceDependency>;
|
|
296
|
+
readonly importers: Schema.withConstructorDefault<Schema.withDecodingDefaultKey<Schema.$Array<typeof LockfileImporter>, never>>;
|
|
297
|
+
readonly extension: Schema.optionalKey<Schema.Union<readonly [typeof PnpmExtension, typeof BunExtension]>>;
|
|
298
|
+
}>, {}>;
|
|
299
|
+
/**
|
|
300
|
+
* The unified lockfile model all four formats normalize into.
|
|
301
|
+
*
|
|
302
|
+
* @remarks
|
|
303
|
+
* - `format` — which lockfile format produced the data.
|
|
304
|
+
* - `lockfileVersion` — the lockfile format version string.
|
|
305
|
+
* - `packages` — every resolved package.
|
|
306
|
+
* - `workspaceDependencies` — inter-workspace dependency edges.
|
|
307
|
+
* - `importers` — each workspace importer's declared dependencies
|
|
308
|
+
* ({@link LockfileImporter}), keyed by importer path. Populated by the pnpm,
|
|
309
|
+
* bun and npm parsers; always empty for yarn, which records no importers.
|
|
310
|
+
* - `extension` — format-specific residue (`PnpmExtension` or
|
|
311
|
+
* `BunExtension`) when the format records any.
|
|
312
|
+
*
|
|
313
|
+
* For pnpm, `Lockfile.parse` emits the honest importer-path-keyed model
|
|
314
|
+
* (workspace packages named by importer path, version `"0.0.0"`);
|
|
315
|
+
* {@link Lockfile.withImporterNames} is the explicit, pure second stage
|
|
316
|
+
* that rewrites those names once the caller has read the workspace
|
|
317
|
+
* manifests. npm, yarn and bun lockfiles carry real names and need no
|
|
318
|
+
* second stage.
|
|
319
|
+
*
|
|
320
|
+
* @public
|
|
321
|
+
*/
|
|
322
|
+
declare class Lockfile extends Lockfile_base {
|
|
323
|
+
#private;
|
|
324
|
+
/**
|
|
325
|
+
* Parse lockfile content of a known format into the unified model — the
|
|
326
|
+
* package's only fallible boundary.
|
|
327
|
+
*
|
|
328
|
+
* @param content - The lockfile text (this package does no IO; the caller
|
|
329
|
+
* reads the file).
|
|
330
|
+
* @param options - The lockfile format to parse as.
|
|
331
|
+
* @returns An `Effect` succeeding with the {@link Lockfile}, or failing
|
|
332
|
+
* with {@link LockfileParseError} (malformed text or the wrong shape) or
|
|
333
|
+
* {@link LockfileFramingError} (the text parsed, but no lockfile document
|
|
334
|
+
* could be located in the stream — see that error for why a
|
|
335
|
+
* multi-document `pnpm-lock.yaml` needs it).
|
|
336
|
+
*/
|
|
337
|
+
static readonly parse: (content: string, options: {
|
|
338
|
+
readonly format: LockfileFormat;
|
|
339
|
+
}) => Effect.Effect<Lockfile, LockfileFramingError | LockfileParseError, never>;
|
|
340
|
+
/**
|
|
341
|
+
* Rewrite pnpm importer-path names to real package names — the explicit
|
|
342
|
+
* second stage of pnpm parsing. Total and pure.
|
|
343
|
+
*
|
|
344
|
+
* @remarks
|
|
345
|
+
* Workspace packages whose `relativePath` appears in `names` are renamed;
|
|
346
|
+
* dependency edge ends are rewritten through the same map. Entries not in
|
|
347
|
+
* the map keep their path name, and non-pnpm lockfiles are unaffected (no
|
|
348
|
+
* key matches). Versions are not touched — pnpm workspace packages keep
|
|
349
|
+
* `"0.0.0"` (the lockfile does not record their real versions).
|
|
350
|
+
*
|
|
351
|
+
* @param names - Importer path → real package name.
|
|
352
|
+
* @returns A new {@link Lockfile} with names rewritten.
|
|
353
|
+
*/
|
|
354
|
+
withImporterNames(names: ReadonlyMap<string, string>): Lockfile;
|
|
355
|
+
/**
|
|
356
|
+
* Every resolved package with the given name — one entry per resolved
|
|
357
|
+
* version. Backed by a lazily built index, so repeated lookups are O(1).
|
|
358
|
+
*
|
|
359
|
+
* @param name - The package name to look up.
|
|
360
|
+
* @returns The matching packages, empty when the name is not in the
|
|
361
|
+
* lockfile.
|
|
362
|
+
*/
|
|
363
|
+
packagesNamed(name: string): ReadonlyArray<ResolvedPackage>;
|
|
364
|
+
/**
|
|
365
|
+
* The importer at the given path — `"."` for the workspace root — or
|
|
366
|
+
* `Option.none()` when the lockfile records no importer there. Backed by a
|
|
367
|
+
* lazily built index, so repeated lookups are O(1). The index is a `Map`,
|
|
368
|
+
* so an attacker-adjacent path (`__proto__`, `constructor`) neither pollutes
|
|
369
|
+
* nor collides.
|
|
370
|
+
*
|
|
371
|
+
* @param path - The importer path to look up.
|
|
372
|
+
* @returns The matching {@link LockfileImporter}, or `Option.none()`.
|
|
373
|
+
*/
|
|
374
|
+
importer(path: string): Option.Option<LockfileImporter>;
|
|
375
|
+
/** The workspace-local packages. */
|
|
376
|
+
get workspacePackages(): ReadonlyArray<ResolvedPackage>;
|
|
377
|
+
}
|
|
378
|
+
//#endregion
|
|
379
|
+
//#region src/LockfileIntegrity.d.ts
|
|
380
|
+
declare const WorkspaceManifest_base: Schema.Class<WorkspaceManifest, Schema.Struct<{
|
|
381
|
+
readonly name: Schema.NonEmptyString;
|
|
382
|
+
readonly dependencies: Schema.optionalKey<Schema.$Record<Schema.String, Schema.String>>;
|
|
383
|
+
readonly devDependencies: Schema.optionalKey<Schema.$Record<Schema.String, Schema.String>>;
|
|
384
|
+
readonly peerDependencies: Schema.optionalKey<Schema.$Record<Schema.String, Schema.String>>;
|
|
385
|
+
readonly optionalDependencies: Schema.optionalKey<Schema.$Record<Schema.String, Schema.String>>;
|
|
386
|
+
}>, {}>;
|
|
387
|
+
/**
|
|
388
|
+
* The minimal manifest shape {@link LockfileIntegrity.compare} checks a
|
|
389
|
+
* lockfile against: a package name plus the four optional dependency maps.
|
|
390
|
+
*
|
|
391
|
+
* @remarks
|
|
392
|
+
* Deliberately *not* a `@effected/package-json` type — this package takes
|
|
393
|
+
* manifests as plain values so its consumers own the manifest IO (and may
|
|
394
|
+
* derive these from any richer model).
|
|
395
|
+
*
|
|
396
|
+
* @public
|
|
397
|
+
*/
|
|
398
|
+
declare class WorkspaceManifest extends WorkspaceManifest_base {}
|
|
399
|
+
declare const LockfileIntegrity_base: Schema.Class<LockfileIntegrity, Schema.Struct<{
|
|
400
|
+
readonly valid: Schema.Boolean;
|
|
401
|
+
readonly missingWorkspaces: Schema.$Array<Schema.String>;
|
|
402
|
+
readonly extraWorkspaces: Schema.$Array<Schema.String>;
|
|
403
|
+
readonly unsatisfiedConstraints: Schema.$Array<Schema.Struct<{
|
|
404
|
+
readonly workspace: Schema.String;
|
|
405
|
+
readonly dependency: Schema.String;
|
|
406
|
+
readonly constraint: Schema.String;
|
|
407
|
+
readonly resolved: Schema.String;
|
|
408
|
+
readonly depType: Schema.Literals<readonly ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]>;
|
|
409
|
+
}>>;
|
|
410
|
+
}>, {}>;
|
|
411
|
+
/**
|
|
412
|
+
* Result of checking a parsed lockfile against the workspace's declared
|
|
413
|
+
* manifests.
|
|
414
|
+
*
|
|
415
|
+
* @remarks
|
|
416
|
+
* A data type, not an error: it reports *what* mismatches exist without
|
|
417
|
+
* failing anything.
|
|
418
|
+
*
|
|
419
|
+
* - `valid` — `true` when the lockfile is fully consistent.
|
|
420
|
+
* - `missingWorkspaces` — workspace names present in the manifests but
|
|
421
|
+
* absent from the lockfile.
|
|
422
|
+
* - `extraWorkspaces` — workspace names in the lockfile with no matching
|
|
423
|
+
* manifest.
|
|
424
|
+
* - `unsatisfiedConstraints` — declared constraints the lockfile's resolved
|
|
425
|
+
* versions do not satisfy.
|
|
426
|
+
*
|
|
427
|
+
* @public
|
|
428
|
+
*/
|
|
429
|
+
declare class LockfileIntegrity extends LockfileIntegrity_base {
|
|
430
|
+
/**
|
|
431
|
+
* Check a lockfile's consistency against workspace manifests — a total,
|
|
432
|
+
* pure function: no Effect, no error channel, no IO.
|
|
433
|
+
*
|
|
434
|
+
* @remarks
|
|
435
|
+
* Constraint checking is best-effort by design: `workspace:` / `link:` /
|
|
436
|
+
* `file:` specifiers and rows whose range (or every resolved version) does
|
|
437
|
+
* not parse as SemVer are skipped, exactly as in the v3 implementation.
|
|
438
|
+
* A lockfile may resolve the same package at several versions; a
|
|
439
|
+
* constraint is satisfied when *any* resolved version matches, and an
|
|
440
|
+
* unsatisfied row reports every candidate in `resolved`. The
|
|
441
|
+
* caller reads the manifests (this package does no IO). For pnpm, apply
|
|
442
|
+
* `Lockfile#withImporterNames` first so workspace names align with
|
|
443
|
+
* manifest names.
|
|
444
|
+
*
|
|
445
|
+
* (Named `compare`, not `check`: every v4 `Schema.Class` already carries
|
|
446
|
+
* a `static check(...checks)` for attaching schema checks, and statics
|
|
447
|
+
* cannot be shadowed with an incompatible signature.)
|
|
448
|
+
*
|
|
449
|
+
* @param lockfile - The parsed lockfile.
|
|
450
|
+
* @param manifests - The workspace manifests to compare against.
|
|
451
|
+
* @returns The integrity report.
|
|
452
|
+
*/
|
|
453
|
+
static compare(lockfile: Lockfile, manifests: ReadonlyArray<WorkspaceManifest>): LockfileIntegrity;
|
|
454
|
+
}
|
|
455
|
+
//#endregion
|
|
456
|
+
export { BunExtension, ImporterDependency, Lockfile, LockfileFormat, LockfileFramingError, LockfileImporter, LockfileIntegrity, LockfileParseError, type PnpmCatalogs, PnpmExtension, ResolvedPackage, WorkspaceDependency, WorkspaceManifest, filenameFor, fromFilename };
|
|
457
|
+
//# sourceMappingURL=index.d.ts.map
|
package/index.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { BunExtension } from "./BunExtension.js";
|
|
2
|
+
import { ImporterDependency } from "./ImporterDependency.js";
|
|
3
|
+
import { LockfileImporter } from "./LockfileImporter.js";
|
|
4
|
+
import { ResolvedPackage } from "./ResolvedPackage.js";
|
|
5
|
+
import { WorkspaceDependency } from "./WorkspaceDependency.js";
|
|
6
|
+
import { PnpmExtension } from "./PnpmExtension.js";
|
|
7
|
+
import { LockfileFormat, filenameFor, fromFilename } from "./LockfileFormat.js";
|
|
8
|
+
import { Lockfile, LockfileFramingError, LockfileParseError } from "./Lockfile.js";
|
|
9
|
+
import { LockfileIntegrity, WorkspaceManifest } from "./LockfileIntegrity.js";
|
|
10
|
+
|
|
11
|
+
export { BunExtension, ImporterDependency, Lockfile, LockfileFormat, LockfileFramingError, LockfileImporter, LockfileIntegrity, LockfileParseError, PnpmExtension, ResolvedPackage, WorkspaceDependency, WorkspaceManifest, filenameFor, fromFilename };
|