@effected/lockfiles 0.1.10 → 0.2.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.
@@ -12,11 +12,20 @@ import { DependencyField, DependencySpecifier } from "@effected/npm";
12
12
  * `ClassifiedSpecifier` (`catalog:` / `workspace:` / range / dist-tag / raw),
13
13
  * while encoding round-trips the **exact original string byte-for-byte** — the
14
14
  * guarantee a before/after lockfile diff relies on.
15
- * - `version` — the concrete resolved version, **populated by pnpm only**. pnpm
16
- * records `{ specifier, version }` per importer dependency; bun and npm record
17
- * resolved versions on their package entries instead, so for those formats
18
- * `version` is absent and a consumer joins by `name` against
19
- * `Lockfile.packages`.
15
+ * - `version` — the concrete resolved version, **populated by pnpm only**:
16
+ * comparable across refs and printable. pnpm records `{ specifier, version }`
17
+ * per importer dependency, and the parser splits pnpm's peer-disambiguation
18
+ * suffix off into `peerSuffix`, so `version` is always the plain version or
19
+ * a non-registry resolution (`link:../utils`, `file:...`), passed through
20
+ * verbatim. bun and npm record resolved versions on their package entries
21
+ * instead, so for those formats `version` is absent and a consumer joins by
22
+ * `name` against `Lockfile.packages`.
23
+ * - `peerSuffix` — pnpm's peer-disambiguation context: the raw parenthesized
24
+ * chain the lockfile recorded after the version, e.g.
25
+ * `(effect@4.0.0-beta.101)(ioredis@5.11.1(supports-color@8.1.1))`. Present
26
+ * only when the lockfile recorded one — never for suffix-free pnpm entries,
27
+ * and never for the other formats, which do not record peer context per
28
+ * importer dependency.
20
29
  * - `depType` — which dependency map declared it, spelled with `@effected/npm`'s
21
30
  * kit-wide `DependencyField` vocabulary.
22
31
  *
@@ -26,6 +35,7 @@ var ImporterDependency = class extends Schema.Class("ImporterDependency")({
26
35
  name: Schema.NonEmptyString,
27
36
  specifier: DependencySpecifier.FromString,
28
37
  version: Schema.optionalKey(Schema.String),
38
+ peerSuffix: Schema.optionalKey(Schema.String),
29
39
  depType: DependencyField
30
40
  }) {};
31
41
 
package/index.d.ts CHANGED
@@ -26,6 +26,7 @@ declare const ImporterDependency_base: Schema.Class<ImporterDependency, Schema.S
26
26
  readonly name: Schema.NonEmptyString;
27
27
  readonly specifier: Schema.Codec<import("@effected/npm").ClassifiedSpecifier, string, never, never>;
28
28
  readonly version: Schema.optionalKey<Schema.String>;
29
+ readonly peerSuffix: Schema.optionalKey<Schema.String>;
29
30
  readonly depType: Schema.Literals<readonly ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]>;
30
31
  }>, {}>;
31
32
  /**
@@ -38,11 +39,20 @@ declare const ImporterDependency_base: Schema.Class<ImporterDependency, Schema.S
38
39
  * `ClassifiedSpecifier` (`catalog:` / `workspace:` / range / dist-tag / raw),
39
40
  * while encoding round-trips the **exact original string byte-for-byte** — the
40
41
  * 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`.
42
+ * - `version` — the concrete resolved version, **populated by pnpm only**:
43
+ * comparable across refs and printable. pnpm records `{ specifier, version }`
44
+ * per importer dependency, and the parser splits pnpm's peer-disambiguation
45
+ * suffix off into `peerSuffix`, so `version` is always the plain version or
46
+ * a non-registry resolution (`link:../utils`, `file:...`), passed through
47
+ * verbatim. bun and npm record resolved versions on their package entries
48
+ * instead, so for those formats `version` is absent and a consumer joins by
49
+ * `name` against `Lockfile.packages`.
50
+ * - `peerSuffix` — pnpm's peer-disambiguation context: the raw parenthesized
51
+ * chain the lockfile recorded after the version, e.g.
52
+ * `(effect@4.0.0-beta.101)(ioredis@5.11.1(supports-color@8.1.1))`. Present
53
+ * only when the lockfile recorded one — never for suffix-free pnpm entries,
54
+ * and never for the other formats, which do not record peer context per
55
+ * importer dependency.
46
56
  * - `depType` — which dependency map declared it, spelled with `@effected/npm`'s
47
57
  * kit-wide `DependencyField` vocabulary.
48
58
  *
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, toIntegrityHash, validationFailure } from "./shared.js";
3
+ import { extractWorkspaceDeps, framingFailure, importerDependencies, 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";
@@ -102,8 +102,7 @@ const toFields = (raw) => Effect.gen(function* () {
102
102
  }));
103
103
  }
104
104
  if (raw.packages) for (const [key, pkg] of Object.entries(raw.packages)) {
105
- const parenIndex = key.indexOf("(");
106
- const bare = parenIndex === -1 ? key : key.slice(0, parenIndex);
105
+ const { plain: bare } = splitPeerSuffix(key);
107
106
  const atIndex = bare.lastIndexOf("@");
108
107
  if (atIndex <= 0) continue;
109
108
  const name = bare.slice(0, atIndex);
@@ -42,22 +42,54 @@ const toIntegrityHash = (raw) => {
42
42
  };
43
43
  const decodeSpecifier = Schema.decodeUnknownExit(DependencySpecifier.FromString);
44
44
  /**
45
+ * Split pnpm's peer-disambiguation suffix off a recorded string: everything
46
+ * from the first `(` on is suffix, since package names and versions never
47
+ * contain `(`. Handles both shapes pnpm records — a bare version
48
+ * (`"1.0.0(effect@4.0.0)"`, importer entries) and a `name@version` snapshot key
49
+ * (`"fdir@6.5.0(picomatch@4.0.4)"`). A string with no `(` — including
50
+ * `link:...` / `file:...` resolutions — passes through untouched. So does a
51
+ * protocol-bearing string (a `:` ahead of the first `(`), where a parenthesis
52
+ * is path or URL text rather than a peer suffix: pnpm attaches suffixes to
53
+ * registry versions only. This is the single stripping implementation — do not
54
+ * hand-roll an `indexOf("(")` split elsewhere.
55
+ *
56
+ * @internal
57
+ */
58
+ const splitPeerSuffix = (raw) => {
59
+ const parenIndex = raw.indexOf("(");
60
+ if (parenIndex === -1) return { plain: raw };
61
+ if (raw.lastIndexOf(":", parenIndex) !== -1) return { plain: raw };
62
+ return {
63
+ plain: raw.slice(0, parenIndex),
64
+ peerSuffix: raw.slice(parenIndex)
65
+ };
66
+ };
67
+ /**
45
68
  * Build one {@link ImporterDependency}, decoding the raw specifier string into
46
69
  * the `@effected/npm` `ClassifiedSpecifier` tagged union. Returns `undefined`
47
70
  * — a skip, never a throw — when the name is empty or the specifier does not
48
71
  * classify (e.g. an empty specifier), per the total-string-surgery discipline.
49
72
  *
73
+ * A pnpm-recorded version is normalized through {@link splitPeerSuffix}: the
74
+ * `version` field carries the plain version and the split-off peer chain lands
75
+ * in `peerSuffix`. A version that is *only* a peer chain (empty plain part) is
76
+ * treated as no concrete version — a skip of both fields, never a throw.
77
+ *
50
78
  * @internal
51
79
  */
52
80
  const buildImporterDependency = (name, specifier, depType, version) => {
53
81
  if (name === "") return void 0;
54
82
  const exit = decodeSpecifier(specifier);
55
83
  if (Exit.isFailure(exit)) return void 0;
84
+ const resolved = version !== void 0 && version !== "" ? splitPeerSuffix(version) : void 0;
56
85
  return ImporterDependency.make({
57
86
  name,
58
87
  specifier: exit.value,
59
88
  depType,
60
- ...version !== void 0 && version !== "" ? { version } : {}
89
+ ...resolved !== void 0 && resolved.plain !== "" ? {
90
+ version: resolved.plain,
91
+ ...resolved.peerSuffix !== void 0 ? { peerSuffix: resolved.peerSuffix } : {}
92
+ } : {}
61
93
  });
62
94
  };
63
95
  /**
@@ -130,4 +162,4 @@ const extractWorkspaceDeps = (workspaces, workspaceNames) => {
130
162
  };
131
163
 
132
164
  //#endregion
133
- export { DEP_TYPES, extractWorkspaceDeps, framingFailure, importerDependencies, isWorkspaceSpecifier, syntaxFailure, toIntegrityHash, validationFailure };
165
+ export { DEP_TYPES, extractWorkspaceDeps, framingFailure, importerDependencies, isWorkspaceSpecifier, splitPeerSuffix, syntaxFailure, toIntegrityHash, validationFailure };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effected/lockfiles",
3
- "version": "0.1.10",
3
+ "version": "0.2.0",
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": [
@@ -41,9 +41,9 @@
41
41
  },
42
42
  "peerDependencies": {
43
43
  "@effected/jsonc": "~0.5.1",
44
- "@effected/npm": "~0.3.1",
44
+ "@effected/npm": "~0.4.0",
45
45
  "@effected/semver": "~0.2.1",
46
- "@effected/yaml": "~0.5.1",
46
+ "@effected/yaml": "~0.6.0",
47
47
  "effect": "4.0.0-beta.101"
48
48
  },
49
49
  "engines": {