@packall/core 0.0.1 → 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.
Files changed (126) hide show
  1. package/dist/{Archive.d.ts → archive.d.ts} +5 -5
  2. package/dist/archive.d.ts.map +1 -0
  3. package/dist/{Bundle.d.ts → bundle.d.ts} +51 -36
  4. package/dist/bundle.d.ts.map +1 -0
  5. package/dist/{DependencyRange.d.ts → dependency-range.d.ts} +2 -2
  6. package/dist/dependency-range.d.ts.map +1 -0
  7. package/dist/{Download.d.ts → download.d.ts} +7 -7
  8. package/dist/download.d.ts.map +1 -0
  9. package/dist/enums/artifact-kind.d.ts +8 -0
  10. package/dist/enums/artifact-kind.d.ts.map +1 -0
  11. package/dist/enums/edge-kind.d.ts +29 -0
  12. package/dist/enums/edge-kind.d.ts.map +1 -0
  13. package/dist/enums/input-file-kind.d.ts +12 -0
  14. package/dist/enums/input-file-kind.d.ts.map +1 -0
  15. package/dist/enums/layout.d.ts +23 -0
  16. package/dist/enums/layout.d.ts.map +1 -0
  17. package/dist/enums/lockfile-format.d.ts +9 -0
  18. package/dist/enums/lockfile-format.d.ts.map +1 -0
  19. package/dist/enums/phase.d.ts +11 -0
  20. package/dist/enums/phase.d.ts.map +1 -0
  21. package/dist/{Errors.d.ts → errors.d.ts} +49 -7
  22. package/dist/errors.d.ts.map +1 -0
  23. package/dist/index.d.ts +44 -28
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +2416 -1180
  26. package/dist/index.js.map +1 -1
  27. package/dist/input-file.d.ts +77 -0
  28. package/dist/input-file.d.ts.map +1 -0
  29. package/dist/{Integrity.d.ts → integrity.d.ts} +3 -3
  30. package/dist/integrity.d.ts.map +1 -0
  31. package/dist/{Layout.d.ts → layout.d.ts} +1 -1
  32. package/dist/{Layout.d.ts.map → layout.d.ts.map} +1 -1
  33. package/dist/locked-resolve.d.ts +47 -0
  34. package/dist/locked-resolve.d.ts.map +1 -0
  35. package/dist/lockfile/bun.d.ts +3 -0
  36. package/dist/lockfile/bun.d.ts.map +1 -0
  37. package/dist/lockfile/detect.d.ts +31 -0
  38. package/dist/lockfile/detect.d.ts.map +1 -0
  39. package/dist/lockfile/json.d.ts +23 -0
  40. package/dist/lockfile/json.d.ts.map +1 -0
  41. package/dist/lockfile/names.d.ts +16 -0
  42. package/dist/lockfile/names.d.ts.map +1 -0
  43. package/dist/lockfile/npm.d.ts +3 -0
  44. package/dist/lockfile/npm.d.ts.map +1 -0
  45. package/dist/lockfile/parse.d.ts +23 -0
  46. package/dist/lockfile/parse.d.ts.map +1 -0
  47. package/dist/lockfile/pnpm.d.ts +20 -0
  48. package/dist/lockfile/pnpm.d.ts.map +1 -0
  49. package/dist/lockfile/tree-builder.d.ts +70 -0
  50. package/dist/lockfile/tree-builder.d.ts.map +1 -0
  51. package/dist/lockfile/types.d.ts +70 -0
  52. package/dist/lockfile/types.d.ts.map +1 -0
  53. package/dist/{Manifest.d.ts → manifest.d.ts} +7 -15
  54. package/dist/manifest.d.ts.map +1 -0
  55. package/dist/{Options.d.ts → options.d.ts} +9 -27
  56. package/dist/options.d.ts.map +1 -0
  57. package/dist/{Platform.d.ts → platform.d.ts} +9 -9
  58. package/dist/platform.d.ts.map +1 -0
  59. package/dist/{Progress.d.ts → progress.d.ts} +10 -9
  60. package/dist/progress.d.ts.map +1 -0
  61. package/dist/{Registry.d.ts → registry.d.ts} +42 -44
  62. package/dist/registry.d.ts.map +1 -0
  63. package/dist/{Resolve.d.ts → resolve.d.ts} +25 -26
  64. package/dist/resolve.d.ts.map +1 -0
  65. package/dist/schemas/lenient.d.ts +38 -0
  66. package/dist/schemas/lenient.d.ts.map +1 -0
  67. package/dist/schemas/package-json.d.ts +44 -0
  68. package/dist/schemas/package-json.d.ts.map +1 -0
  69. package/dist/{Spec.d.ts → spec.d.ts} +4 -4
  70. package/dist/spec.d.ts.map +1 -0
  71. package/dist/types/value-of.d.ts +9 -0
  72. package/dist/types/value-of.d.ts.map +1 -0
  73. package/dist/utils/is-record.d.ts +8 -0
  74. package/dist/utils/is-record.d.ts.map +1 -0
  75. package/package.json +3 -2
  76. package/src/{Archive.ts → archive.ts} +92 -92
  77. package/src/{Bundle.ts → bundle.ts} +616 -570
  78. package/src/{DependencyRange.ts → dependency-range.ts} +115 -115
  79. package/src/{Download.ts → download.ts} +133 -132
  80. package/src/enums/artifact-kind.ts +9 -0
  81. package/src/enums/edge-kind.ts +32 -0
  82. package/src/enums/input-file-kind.ts +13 -0
  83. package/src/enums/layout.ts +25 -0
  84. package/src/enums/lockfile-format.ts +10 -0
  85. package/src/enums/phase.ts +12 -0
  86. package/src/{Errors.ts → errors.ts} +87 -5
  87. package/src/index.ts +133 -35
  88. package/src/{InputFile.ts → input-file.ts} +127 -44
  89. package/src/{Integrity.ts → integrity.ts} +121 -122
  90. package/src/locked-resolve.ts +461 -0
  91. package/src/lockfile/bun.ts +207 -0
  92. package/src/lockfile/detect.ts +80 -0
  93. package/src/lockfile/json.ts +132 -0
  94. package/src/lockfile/names.ts +20 -0
  95. package/src/lockfile/npm.ts +308 -0
  96. package/src/lockfile/parse.ts +56 -0
  97. package/src/lockfile/pnpm.ts +225 -0
  98. package/src/lockfile/tree-builder.ts +166 -0
  99. package/src/lockfile/types.ts +74 -0
  100. package/src/{Manifest.ts → manifest.ts} +133 -133
  101. package/src/{Options.ts → options.ts} +7 -27
  102. package/src/{Platform.ts → platform.ts} +23 -23
  103. package/src/{Progress.ts → progress.ts} +106 -106
  104. package/src/{Registry.ts → registry.ts} +115 -121
  105. package/src/{Resolve.ts → resolve.ts} +537 -528
  106. package/src/schemas/lenient.ts +123 -0
  107. package/src/schemas/package-json.ts +27 -0
  108. package/src/{Spec.ts → spec.ts} +11 -19
  109. package/src/types/value-of.ts +8 -0
  110. package/src/utils/is-record.ts +8 -0
  111. package/dist/Archive.d.ts.map +0 -1
  112. package/dist/Bundle.d.ts.map +0 -1
  113. package/dist/DependencyRange.d.ts.map +0 -1
  114. package/dist/Download.d.ts.map +0 -1
  115. package/dist/Errors.d.ts.map +0 -1
  116. package/dist/InputFile.d.ts +0 -46
  117. package/dist/InputFile.d.ts.map +0 -1
  118. package/dist/Integrity.d.ts.map +0 -1
  119. package/dist/Manifest.d.ts.map +0 -1
  120. package/dist/Options.d.ts.map +0 -1
  121. package/dist/Platform.d.ts.map +0 -1
  122. package/dist/Progress.d.ts.map +0 -1
  123. package/dist/Registry.d.ts.map +0 -1
  124. package/dist/Resolve.d.ts.map +0 -1
  125. package/dist/Spec.d.ts.map +0 -1
  126. /package/src/{Layout.ts → layout.ts} +0 -0
@@ -0,0 +1,225 @@
1
+ /**
2
+ * `pnpm-lock.yaml`.
3
+ *
4
+ * The easy one: pnpm's snapshots already name exact versions, so there is no
5
+ * nesting walk-up to reimplement. The fiddly parts are the three package-key
6
+ * encodings still in the wild and the parenthesised peer-resolution suffix.
7
+ */
8
+ import semver from "semver"
9
+ import { parse as parseYaml } from "yaml"
10
+ import { parseDependencyTarget } from "../dependency-range.js"
11
+ import { EdgeKind, LockedRootKind } from "../enums/edge-kind.js"
12
+ import { LockfileFormat } from "../enums/lockfile-format.js"
13
+ import { LockfileError } from "../errors.js"
14
+ import { describeError } from "./json.js"
15
+ import { isRecord } from "../utils/is-record.js"
16
+ import { fieldByRootKind, notCovered, TreeBuilder } from "./tree-builder.js"
17
+ import type { LockedEdge, LockedTree } from "./types.js"
18
+
19
+ /** `name@version`, as a snapshot or importer entry resolves to. */
20
+ type PnpmReference = {
21
+ readonly name: string
22
+ readonly version: string
23
+ }
24
+
25
+ /** pnpm never records peer dependencies against an importer. */
26
+ const ROOT_KINDS = [
27
+ LockedRootKind.Prod,
28
+ LockedRootKind.Optional,
29
+ LockedRootKind.Dev
30
+ ] as const
31
+
32
+ export const parsePnpmLockfile = (
33
+ path: string,
34
+ content: string,
35
+ importer: string | undefined
36
+ ): LockedTree => {
37
+ let root: unknown
38
+ try {
39
+ root = parseYaml(content)
40
+ } catch (error) {
41
+ throw new LockfileError(path, `not valid YAML — ${describeError(error)}`, { cause: error })
42
+ }
43
+ if (!isRecord(root)) {
44
+ throw new LockfileError(path, "top level is not a mapping")
45
+ }
46
+
47
+ const version = String(root["lockfileVersion"] ?? "?")
48
+ const builder = new TreeBuilder()
49
+
50
+ // v9 split the graph in two: `packages` keeps resolution metadata, and
51
+ // `snapshots` keeps the edges. Before that both lived in `packages`.
52
+ const packages = isRecord(root["packages"]) ? root["packages"] : {}
53
+ const snapshots = isRecord(root["snapshots"]) ? root["snapshots"] : packages
54
+
55
+ const known = new Map<string, PnpmReference>()
56
+ for (const key of Object.keys(packages)) {
57
+ const parsed = parsePnpmKey(key)
58
+ if (parsed === null) continue
59
+ known.set(key, parsed)
60
+ }
61
+
62
+ for (const [key, raw] of Object.entries(snapshots)) {
63
+ const parsed = parsePnpmKey(key) ?? known.get(key)
64
+ if (parsed === undefined || parsed === null) {
65
+ const described = describeKey(key)
66
+ if (described !== null) builder.skip(described.name, described.spec, described.reason)
67
+ continue
68
+ }
69
+ builder.add({
70
+ name: parsed.name,
71
+ version: parsed.version,
72
+ dependencies: edgesOf(builder, parsed, isRecord(raw) ? raw : {})
73
+ })
74
+ }
75
+
76
+ collectRoots(builder, root, path, importer)
77
+
78
+ return builder.finish({ format: LockfileFormat.Pnpm, lockfileVersion: version, path, importer })
79
+ }
80
+
81
+ const edgesOf = (
82
+ builder: TreeBuilder,
83
+ from: PnpmReference,
84
+ snapshot: Record<string, unknown>
85
+ ): ReadonlyArray<LockedEdge> => {
86
+ const edges: Array<LockedEdge> = []
87
+
88
+ const add = (field: string, kind: EdgeKind): void => {
89
+ const entries = snapshot[field]
90
+ if (!isRecord(entries)) return
91
+ for (const [name, raw] of Object.entries(entries)) {
92
+ if (typeof raw !== "string") continue
93
+ const target = parsePnpmReference(name, raw)
94
+ if (target === null) {
95
+ if (kind === EdgeKind.Prod) builder.dangling(`${from.name}@${from.version}`, name)
96
+ continue
97
+ }
98
+ edges.push({ ...target, kind })
99
+ }
100
+ }
101
+
102
+ add(fieldByRootKind[LockedRootKind.Prod], EdgeKind.Prod)
103
+ add(fieldByRootKind[LockedRootKind.Optional], EdgeKind.Optional)
104
+ add(fieldByRootKind[LockedRootKind.Peer], EdgeKind.Peer)
105
+
106
+ return edges
107
+ }
108
+
109
+ /**
110
+ * The importers' direct dependencies become the roots.
111
+ *
112
+ * pnpm is the format that makes this easy: `importers` is already keyed by the
113
+ * path each package.json sits at, so scoping to one member is a lookup rather
114
+ * than a reconstruction.
115
+ */
116
+ const collectRoots = (
117
+ builder: TreeBuilder,
118
+ root: Record<string, unknown>,
119
+ path: string,
120
+ member: string | undefined
121
+ ): void => {
122
+ // A single-project lockfile has no `importers` block; its dependencies sit at
123
+ // the top level, which is the same thing as an importer called ".".
124
+ const importers = isRecord(root["importers"]) ? root["importers"] : { ".": root }
125
+
126
+ const selected =
127
+ member === undefined
128
+ ? Object.values(importers)
129
+ : Object.hasOwn(importers, member)
130
+ ? [importers[member]]
131
+ : notCovered(path, member, Object.keys(importers))
132
+
133
+ for (const importer of selected) {
134
+ if (!isRecord(importer)) continue
135
+ for (const kind of ROOT_KINDS) {
136
+ const entries = importer[fieldByRootKind[kind]]
137
+ if (!isRecord(entries)) continue
138
+
139
+ for (const [name, raw] of Object.entries(entries)) {
140
+ // v5 wrote a bare version string; v6 and v9 write
141
+ // `{specifier, version}`.
142
+ const value = typeof raw === "string" ? raw : isRecord(raw) ? raw["version"] : undefined
143
+ if (typeof value !== "string") continue
144
+
145
+ const target = parsePnpmReference(name, value)
146
+ if (target === null) continue
147
+
148
+ const specifier =
149
+ isRecord(raw) && typeof raw["specifier"] === "string" ? raw["specifier"] : undefined
150
+ builder.addRoot({ ...target, kind, specifier })
151
+ }
152
+ }
153
+ }
154
+ }
155
+
156
+ /**
157
+ * Reads a dependency value from a snapshot or importer.
158
+ *
159
+ * The value is either a bare version (`1.4.0`) or a full package key
160
+ * (`/lodash@4.17.21`, used when the install name is an alias of another
161
+ * package). Both may carry a peer-resolution suffix in parentheses.
162
+ */
163
+ const parsePnpmReference = (name: string, raw: string): PnpmReference | null => {
164
+ const value = stripSuffix(raw)
165
+
166
+ const exact = semver.valid(value, { loose: true })
167
+ if (exact !== null) return { name, version: exact }
168
+
169
+ return parsePnpmKey(raw)
170
+ }
171
+
172
+ /**
173
+ * Splits a package key into name and version.
174
+ *
175
+ * Three encodings across the versions still in the wild:
176
+ * v5 `/@babel/core/7.0.0`
177
+ * v6 `/@babel/core@7.0.0`
178
+ * v9 `@babel/core@7.0.0`
179
+ *
180
+ * plus an optional `(peer@1.0.0)` or `(patch_hash=…)` suffix on any of them.
181
+ */
182
+ export const parsePnpmKey = (key: string): PnpmReference | null => {
183
+ const body = stripSuffix(key.startsWith("/") ? key.slice(1) : key)
184
+
185
+ // A scoped name's own `@` sits at index 0, so only a later one separates the
186
+ // version. When there is none, this is the v5 form and the last `/` does it.
187
+ const at = body.lastIndexOf("@")
188
+ const cut = at > 0 ? at : body.lastIndexOf("/")
189
+ if (cut <= 0) return null
190
+
191
+ const name = body.slice(0, cut)
192
+ const version = semver.valid(body.slice(cut + 1), { loose: true })
193
+ if (version === null || name.length === 0) return null
194
+
195
+ return { name, version }
196
+ }
197
+
198
+ /** Describes a key we cannot pin, so the skip warning can say why. */
199
+ const describeKey = (
200
+ key: string
201
+ ): { readonly name: string; readonly spec: string; readonly reason: string } | null => {
202
+ const body = key.startsWith("/") ? key.slice(1) : key
203
+ const at = body.lastIndexOf("@")
204
+ if (at <= 0) return null
205
+ const name = body.slice(0, at)
206
+ const spec = body.slice(at + 1)
207
+ const target = parseDependencyTarget(name, spec)
208
+ return {
209
+ name,
210
+ spec,
211
+ reason: target._tag === "Unsupported" ? target.reason : "not an exact version"
212
+ }
213
+ }
214
+
215
+ /**
216
+ * Drops the parenthesised peer-resolution suffix.
217
+ *
218
+ * pnpm distinguishes the same version installed against different peers —
219
+ * `react-dom@18.3.1(react@18.3.1)`. Those are the same published tarball, which
220
+ * is all this tool downloads, so the suffix is noise here.
221
+ */
222
+ const stripSuffix = (value: string): string => {
223
+ const open = value.indexOf("(")
224
+ return open === -1 ? value : value.slice(0, open)
225
+ }
@@ -0,0 +1,166 @@
1
+ /**
2
+ * The bookkeeping every lockfile parser shares.
3
+ *
4
+ * Packages, edges and skip warnings accumulate here while a parser walks a
5
+ * file, which is what lets each parser stay focused on its own encoding.
6
+ */
7
+ import semver from "semver"
8
+ import { parseDependencyTarget } from "../dependency-range.js"
9
+ import { LockedRootKind } from "../enums/edge-kind.js"
10
+ import type { LockfileFormat } from "../enums/lockfile-format.js"
11
+ import { LockfileError } from "../errors.js"
12
+ import { packageKey } from "../resolve.js"
13
+ import type { LockedEdge, LockedPackage, LockedRoot, LockedTree } from "./types.js"
14
+
15
+ /**
16
+ * Which manifest block each root kind is declared in.
17
+ *
18
+ * The block names are the same across npm, pnpm and bun; only which of them a
19
+ * given format writes differs, which is why each parser supplies its own order
20
+ * rather than iterating this.
21
+ */
22
+ export const fieldByRootKind = {
23
+ [LockedRootKind.Prod]: "dependencies",
24
+ [LockedRootKind.Optional]: "optionalDependencies",
25
+ [LockedRootKind.Dev]: "devDependencies",
26
+ [LockedRootKind.Peer]: "peerDependencies"
27
+ } as const satisfies Record<LockedRootKind, string>
28
+
29
+ export class TreeBuilder {
30
+ readonly packages = new Map<string, LockedPackage>()
31
+ readonly roots: Array<LockedRoot> = []
32
+ readonly warnings: Array<string> = []
33
+ readonly incomplete: Array<string> = []
34
+ /** Names dropped on purpose, so an edge into one is explained, not alarming. */
35
+ private readonly skipped = new Set<string>()
36
+ private readonly seenWarnings = new Set<string>()
37
+
38
+ add(pkg: LockedPackage): void {
39
+ const key = packageKey(pkg.name, pkg.version)
40
+ const existing = this.packages.get(key)
41
+ if (existing === undefined) {
42
+ this.packages.set(key, pkg)
43
+ return
44
+ }
45
+ // The same package at the same version can appear at several paths in a
46
+ // nested layout. They are the same bytes; merge the edge sets so a copy
47
+ // recorded with fewer dependencies does not lose any.
48
+ this.packages.set(key, {
49
+ ...existing,
50
+ dependencies: mergeEdges(existing.dependencies, pkg.dependencies)
51
+ })
52
+ }
53
+
54
+ addRoot(root: LockedRoot): void {
55
+ if (this.roots.some((existing) => existing.name === root.name)) return
56
+ this.roots.push(root)
57
+ }
58
+
59
+ warn(message: string): void {
60
+ if (this.seenWarnings.has(message)) return
61
+ this.seenWarnings.add(message)
62
+ this.warnings.push(message)
63
+ }
64
+
65
+ /** Notes an entry that is real but cannot come from a registry. */
66
+ skip(name: string, spec: string, reason: string): void {
67
+ this.skipped.add(name)
68
+ this.warn(`skipped ${name}@${spec} (${reason}) — not fetchable from a registry`)
69
+ }
70
+
71
+ /**
72
+ * Records a required edge that resolved to nothing.
73
+ *
74
+ * An edge into something skipped on purpose is expected — you cannot bundle a
75
+ * `workspace:` sibling from a registry — so it stays a warning. Anything else
76
+ * means the file does not pin what it claims to.
77
+ */
78
+ dangling(from: string, name: string): void {
79
+ if (this.skipped.has(name)) {
80
+ this.warn(`${from} requires ${name}, which is not fetchable from a registry`)
81
+ return
82
+ }
83
+ this.incomplete.push(`${name} (required by ${from})`)
84
+ }
85
+
86
+ finish(input: {
87
+ readonly format: LockfileFormat
88
+ readonly lockfileVersion: string
89
+ readonly path: string
90
+ readonly importer?: string | undefined
91
+ }): LockedTree {
92
+ if (this.packages.size === 0) {
93
+ throw new LockfileError(input.path, "lockfile pins no packages that can be bundled")
94
+ }
95
+ return {
96
+ format: input.format,
97
+ lockfileVersion: input.lockfileVersion,
98
+ path: input.path,
99
+ importer: input.importer,
100
+ packages: this.packages,
101
+ roots: this.roots,
102
+ warnings: this.warnings,
103
+ incomplete: this.incomplete
104
+ }
105
+ }
106
+ }
107
+
108
+ const mergeEdges = (
109
+ a: ReadonlyArray<LockedEdge>,
110
+ b: ReadonlyArray<LockedEdge>
111
+ ): ReadonlyArray<LockedEdge> => {
112
+ const merged = new Map<string, LockedEdge>()
113
+ for (const edge of [...a, ...b]) {
114
+ merged.set(`${edge.name}@${edge.version}|${edge.kind}`, edge)
115
+ }
116
+ return [...merged.values()]
117
+ }
118
+
119
+ /**
120
+ * Reads the right-hand side of a locked entry as an exact version.
121
+ *
122
+ * Returns the version when it is one, or the reason it is not — `file:../x`,
123
+ * `workspace:*`, a git URL. `parseDependencyTarget` already classifies every
124
+ * one of those for package.json parsing, so the vocabulary of skip reasons
125
+ * stays identical between the two paths.
126
+ */
127
+ export const asExactVersion = (
128
+ name: string,
129
+ raw: string
130
+ ):
131
+ | { readonly _tag: "Version"; readonly version: string }
132
+ | { readonly _tag: "Unsupported"; readonly reason: string } => {
133
+ const exact = semver.valid(raw, { loose: true })
134
+ if (exact !== null) return { _tag: "Version", version: exact }
135
+
136
+ const target = parseDependencyTarget(name, raw)
137
+ if (target._tag === "Unsupported") return { _tag: "Unsupported", reason: target.reason }
138
+ return { _tag: "Unsupported", reason: "not an exact version" }
139
+ }
140
+
141
+ /**
142
+ * Raised when a lockfile found by walking up turns out not to cover the
143
+ * package.json that went looking for it.
144
+ *
145
+ * Worth distinguishing from "no lockfile at all": the file exists and is
146
+ * readable, it simply belongs to a different project, and bundling it would
147
+ * produce someone else's dependency set.
148
+ */
149
+ export const notCovered = (
150
+ path: string,
151
+ importer: string,
152
+ known: ReadonlyArray<string>
153
+ ): never => {
154
+ const listed = known.slice(0, 8).map((entry) => ` ${entry}`).join("\n")
155
+ throw new LockfileError(
156
+ path,
157
+ `it does not cover ${importer}.\n` +
158
+ (known.length === 0
159
+ ? " It records no workspace members."
160
+ : ` Workspace members it does record:\n${listed}` +
161
+ (known.length > 8 ? `\n … and ${known.length - 8} more` : ""))
162
+ )
163
+ }
164
+
165
+ /** `""` is how npm and bun spell the project at the lockfile's own level. */
166
+ export const importerKey = (importer: string): string => (importer === "." ? "" : importer)
@@ -0,0 +1,74 @@
1
+ /**
2
+ * The one output shape every lockfile parser produces.
3
+ *
4
+ * npm, pnpm and bun encode "package X at path P depends on Y" in three
5
+ * different ways; once flattened into this graph, nothing downstream has to
6
+ * know which manager wrote the file.
7
+ */
8
+ import type { EdgeKind, LockedRootKind } from "../enums/edge-kind.js"
9
+ import type { LockfileFormat } from "../enums/lockfile-format.js"
10
+
11
+ /** One edge out of a locked package, already pinned to an exact version. */
12
+ export type LockedEdge = {
13
+ readonly name: string
14
+ readonly version: string
15
+ readonly kind: EdgeKind
16
+ }
17
+
18
+ /** One package pinned by a lockfile. */
19
+ export type LockedPackage = {
20
+ readonly name: string
21
+ readonly version: string
22
+ readonly dependencies: ReadonlyArray<LockedEdge>
23
+ }
24
+
25
+ /** A direct dependency of the locked project. */
26
+ export type LockedRoot = {
27
+ readonly name: string
28
+ readonly version: string
29
+ readonly kind: LockedRootKind
30
+ /** The range the manifest asked for, kept for reporting. */
31
+ readonly specifier?: string | undefined
32
+ }
33
+
34
+ /** Options for reading a lockfile. */
35
+ export type LockfileParseOptions = {
36
+ /**
37
+ * Which workspace member's dependencies become the roots, as a path relative
38
+ * to the lockfile in POSIX form — `"packages/core"`, or `"."` for the project
39
+ * at the lockfile's own level.
40
+ *
41
+ * Left undefined, every workspace member in the file contributes, which is
42
+ * what pointing `--file` straight at a lockfile should mean. It matters when
43
+ * a package.json found a lockfile by walking *up*: the file then pins the
44
+ * whole repository, and only one member of it was asked for.
45
+ */
46
+ readonly importer?: string | undefined
47
+ }
48
+
49
+ /** A whole lockfile, flattened. */
50
+ export type LockedTree = {
51
+ readonly format: LockfileFormat
52
+ /** As the file declares it: `3`, `'9.0'`, `1`. */
53
+ readonly lockfileVersion: string
54
+ /** Where it was read from, for error messages. */
55
+ readonly path: string
56
+ /** The workspace member the roots came from, when one was selected. */
57
+ readonly importer?: string | undefined
58
+ /** Every pinned package, keyed by `name@version`. */
59
+ readonly packages: ReadonlyMap<string, LockedPackage>
60
+ /** The project's direct dependencies. */
61
+ readonly roots: ReadonlyArray<LockedRoot>
62
+ /** Entries that were recognised but cannot be fetched from a registry. */
63
+ readonly warnings: ReadonlyArray<string>
64
+ /**
65
+ * Required edges pointing at nothing the file pins.
66
+ *
67
+ * Distinct from `warnings`: a `workspace:` dependency that cannot be bundled
68
+ * is expected and gets a warning, while an ordinary registry dependency with
69
+ * no entry means the lockfile is genuinely partial and the bundle it produces
70
+ * would be missing a package. The caller decides what to do about it, but the
71
+ * two must not be confused.
72
+ */
73
+ readonly incomplete: ReadonlyArray<string>
74
+ }