@bun-native/cli 0.0.0-bootstrap.0 → 0.33.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.
@@ -0,0 +1,103 @@
1
+ import { createHash } from 'node:crypto'
2
+ import { gunzipSync } from 'node:zlib'
3
+ import type { FileDigest } from './artifact.ts'
4
+ import { packageArchivePath } from './paths.ts'
5
+
6
+ const block = 512
7
+ const maximumBytes = 64 * 1024 * 1024
8
+
9
+ function text(field: Buffer): string {
10
+ const end = field.indexOf(0)
11
+ if (end !== -1 && field.subarray(end).some((byte) => byte !== 0))
12
+ throw new Error('Invalid package tar text padding')
13
+ return field.subarray(0, end === -1 ? undefined : end).toString('latin1')
14
+ }
15
+
16
+ function octal(field: Buffer): number {
17
+ const value = field.toString('latin1')
18
+ if (!/^[\0 ]*[0-7]+[\0 ]*$/.test(value)) throw new Error('Invalid package tar numeric field')
19
+ const number = Number.parseInt(value.replaceAll('\0', '').trim(), 8)
20
+ if (!Number.isSafeInteger(number)) throw new Error('Invalid package tar size')
21
+ return number
22
+ }
23
+
24
+ /** Inspect original USTAR members before any map can erase duplicates or link metadata. */
25
+ export async function verifyPackageArchive(
26
+ path: string,
27
+ expected: Record<string, FileDigest>
28
+ ): Promise<void> {
29
+ const files = new Map<string, FileDigest>()
30
+ const directories = new Set<string>()
31
+ let paddedContents = 0
32
+ for (const [name, digest] of Object.entries(expected)) {
33
+ if (!packageArchivePath(name) || !Number.isSafeInteger(digest.bytes) || digest.bytes < 0)
34
+ throw new Error('Invalid expected package archive inventory')
35
+ const full = `package/${name}`
36
+ files.set(full, digest)
37
+ const parts = full.split('/')
38
+ for (let index = 1; index < parts.length; index += 1)
39
+ directories.add(parts.slice(0, index).join('/'))
40
+ paddedContents += Math.ceil(digest.bytes / block) * block
41
+ }
42
+ // Permit the declared files, directory headers, end blocks and one conventional
43
+ // 10 KiB blocking pad. Bound decompression before parsing any member contents.
44
+ const limit = paddedContents + (files.size + directories.size + 2) * block + 10_240
45
+ if (!Number.isSafeInteger(limit) || limit > maximumBytes || Bun.file(path).size > maximumBytes)
46
+ throw new Error('Package archive exceeds the bounded source-package inspection limit')
47
+ const data = gunzipSync(await Bun.file(path).bytes(), { maxOutputLength: limit })
48
+ if (data.length % block !== 0) throw new Error('Truncated package tar blocks')
49
+ const seen = new Set<string>()
50
+ const regular = new Set<string>()
51
+ let ended = false
52
+ for (let offset = 0; offset + block <= data.length;) {
53
+ const header = data.subarray(offset, offset + block)
54
+ if (header.every((byte) => byte === 0)) {
55
+ if (data.length - offset < block * 2 || data.subarray(offset).some((byte) => byte !== 0))
56
+ throw new Error('Invalid package tar end records or trailing data')
57
+ ended = true
58
+ break
59
+ }
60
+ const checksum = octal(header.subarray(148, 156))
61
+ let sum = 0
62
+ for (let index = 0; index < block; index += 1)
63
+ sum += index >= 148 && index < 156 ? 32 : header[index]!
64
+ if (sum !== checksum) throw new Error('Invalid package tar header checksum')
65
+ if (!['ustar\0', 'ustar '].includes(header.subarray(257, 263).toString('latin1')))
66
+ throw new Error('Package archive must use the supported USTAR format')
67
+ const kind = header[156] === 0 ? '0' : String.fromCharCode(header[156]!)
68
+ if (!['0', '5'].includes(kind) || text(header.subarray(157, 257)) !== '')
69
+ throw new Error(`Unsupported package tar member type or link: ${kind}`)
70
+ const size = octal(header.subarray(124, 136))
71
+ const prefix = text(header.subarray(345, 500))
72
+ const raw = (prefix ? prefix + '/' : '') + text(header.subarray(0, 100))
73
+ const name = kind === '5' ? raw.replace(/\/$/, '') : raw
74
+ if (!packageArchivePath(name))
75
+ throw new Error(`Package archive inventory contains an invalid path: ${raw}`)
76
+ if (seen.has(name)) throw new Error(`Duplicate package tar member: ${name}`)
77
+ seen.add(name)
78
+ const start = offset + block
79
+ const end = start + size
80
+ const next = start + Math.ceil(size / block) * block
81
+ if (!Number.isSafeInteger(next) || next > data.length)
82
+ throw new Error('Truncated package tar member')
83
+ if (data.subarray(end, next).some((byte) => byte !== 0))
84
+ throw new Error('Invalid package tar member padding')
85
+ if (kind === '5') {
86
+ if (size !== 0 || !directories.has(name))
87
+ throw new Error(`Unexpected package tar directory: ${name}`)
88
+ } else {
89
+ const expected = files.get(name)
90
+ if (!expected) throw new Error(`Packed archive file inventory differs: ${name}`)
91
+ if (
92
+ size !== expected.bytes ||
93
+ createHash('sha256').update(data.subarray(start, end)).digest('hex') !== expected.sha256
94
+ )
95
+ throw new Error(`Packed archive digest mismatch: ${name}`)
96
+ regular.add(name)
97
+ }
98
+ offset = next
99
+ }
100
+ if (!ended) throw new Error('Package tar has no end records')
101
+ if (regular.size !== files.size)
102
+ throw new Error('Packed archive file inventory differs from the staged package')
103
+ }
@@ -0,0 +1,220 @@
1
+ import { isDeepStrictEqual } from 'node:util'
2
+ import { packagePath } from './paths.ts'
3
+
4
+ type LockedPackage = [string, string, Record<string, unknown>, string]
5
+ export type DependencyLock = {
6
+ lockfileVersion: 2
7
+ configVersion: 1
8
+ packages: Record<string, LockedPackage>
9
+ }
10
+ type ArchiveInfo = { dependencies?: Record<string, string>; bin?: Record<string, string> }
11
+ type LocalArchiveLock = {
12
+ packages: Record<string, [string, ArchiveInfo, string]>
13
+ overrides: Record<string, string>
14
+ }
15
+
16
+ /** Pinned Bun text locks use raw tarball paths; file: denotes a folder in this field. */
17
+ export function localArchiveLock(
18
+ archives: readonly {
19
+ name: string
20
+ file: string
21
+ sha256: string
22
+ dependencies?: Record<string, string>
23
+ bin?: Record<string, string>
24
+ }[]
25
+ ): LocalArchiveLock {
26
+ const packages: LocalArchiveLock['packages'] = Object.create(null)
27
+ const overrides: Record<string, string> = Object.create(null)
28
+ for (const archive of archives) {
29
+ if (
30
+ !archive.name.startsWith('@bun-native/') ||
31
+ !packagePath(archive.file) ||
32
+ !archive.file.endsWith('.tgz') ||
33
+ !/^[a-f0-9]{64}$/.test(archive.sha256) ||
34
+ Object.hasOwn(packages, archive.name)
35
+ )
36
+ throw new Error(`Invalid or duplicate local archive lock input: ${archive.name}`)
37
+ const info: ArchiveInfo = {
38
+ ...(archive.dependencies ? { dependencies: archive.dependencies } : {}),
39
+ ...(archive.bin ? { bin: archive.bin } : {}),
40
+ }
41
+ packages[archive.name] = [
42
+ `${archive.name}@${archive.file}`,
43
+ info,
44
+ `sha256-${Buffer.from(archive.sha256, 'hex').toString('base64')}`,
45
+ ]
46
+ overrides[archive.name] = `file:${archive.file}`
47
+ }
48
+ return {
49
+ packages: Object.fromEntries(Object.entries(packages)),
50
+ overrides: Object.fromEntries(Object.entries(overrides)),
51
+ }
52
+ }
53
+
54
+ function record(value: unknown): Record<string, unknown> {
55
+ if (!value || typeof value !== 'object' || Array.isArray(value))
56
+ throw new Error('Invalid dependency lock record')
57
+ return value as Record<string, unknown>
58
+ }
59
+
60
+ export function lockedPackages(value: unknown, local?: LocalArchiveLock): DependencyLock {
61
+ const lock = record(value)
62
+ if (lock['lockfileVersion'] !== 2 || lock['configVersion'] !== 1)
63
+ throw new Error('Expected the pinned Bun dependency lock format')
64
+ const supplied = record(lock['packages'])
65
+ if (local)
66
+ for (const [name, expected] of Object.entries(local.packages))
67
+ if (!Object.hasOwn(supplied, name) || !isDeepStrictEqual(supplied[name], expected))
68
+ throw new Error(`Installed local archive lock differs: ${name}`)
69
+ const packages: DependencyLock['packages'] = Object.create(null)
70
+ for (const [key, value] of Object.entries(supplied)) {
71
+ if (local && Object.hasOwn(local.packages, key)) continue
72
+ if (!Array.isArray(value) || typeof value[0] !== 'string')
73
+ throw new Error(`Invalid locked package: ${key}`)
74
+ if (value[0].includes('@workspace:') || value[0].includes('@file:')) {
75
+ if (local || !key.startsWith('@bun-native/') || value[0].includes('@file:'))
76
+ throw new Error(`Unexpected local dependency in lock: ${key}`)
77
+ continue
78
+ }
79
+ if (
80
+ value.length !== 4 ||
81
+ value[1] !== '' ||
82
+ typeof value[3] !== 'string' ||
83
+ !/^sha512-[A-Za-z0-9+/]+={0,2}$/.test(value[3])
84
+ )
85
+ throw new Error(`Expected an integrity-bound registry dependency: ${key}`)
86
+ packages[key] = [value[0], value[1], record(value[2]), value[3]]
87
+ }
88
+ return {
89
+ lockfileVersion: 2,
90
+ configVersion: 1,
91
+ packages: Object.fromEntries(Object.entries(packages)),
92
+ }
93
+ }
94
+
95
+ function identity(entry: LockedPackage): { name: string; version: string } {
96
+ const separator = entry[0].lastIndexOf('@')
97
+ const name = entry[0].slice(0, separator)
98
+ const version = entry[0].slice(separator + 1)
99
+ if (separator <= 0 || !/^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/.test(version))
100
+ throw new Error(`Expected an exact registry version: ${entry[0]}`)
101
+ return { name, version }
102
+ }
103
+
104
+ /** Preserve nested resolutions; a global name override would collapse distinct versions. */
105
+ export function dependencyClosure(
106
+ lock: DependencyLock,
107
+ roots: Record<string, string>,
108
+ includeInstalledOptionalPeers = false
109
+ ): DependencyLock {
110
+ const packages: DependencyLock['packages'] = Object.create(null)
111
+ const pending: string[] = []
112
+ for (const [name, version] of Object.entries(roots)) {
113
+ const entry = Object.hasOwn(lock.packages, name) ? lock.packages[name] : undefined
114
+ if (!entry || identity(entry).name !== name || identity(entry).version !== version)
115
+ throw new Error(`Source lock does not pin ${name}@${version}`)
116
+ pending.push(name)
117
+ }
118
+ const resolve = (from: string, name: string, required: boolean): string | undefined => {
119
+ let ancestor = from
120
+ while (ancestor) {
121
+ const candidate = `${ancestor}/${name}`
122
+ if (Object.hasOwn(lock.packages, candidate)) return candidate
123
+ const current = Object.hasOwn(lock.packages, ancestor) ? lock.packages[ancestor] : undefined
124
+ if (!current || !ancestor.endsWith(identity(current).name))
125
+ throw new Error(`Invalid nested dependency locator: ${ancestor}`)
126
+ ancestor = ancestor.slice(0, -identity(current).name.length).replace(/\/$/, '')
127
+ }
128
+ if (Object.hasOwn(lock.packages, name)) return name
129
+ if (required) throw new Error(`Source lock is missing ${name}, required by ${from}`)
130
+ return undefined
131
+ }
132
+ for (let index = 0; index < pending.length; index += 1) {
133
+ const key = pending[index]!
134
+ if (packages[key]) continue
135
+ const entry = lock.packages[key]!
136
+ identity(entry)
137
+ packages[key] = entry
138
+ const optionalPeers = entry[2]['optionalPeers'] ?? []
139
+ if (!Array.isArray(optionalPeers) || optionalPeers.some((name) => typeof name !== 'string'))
140
+ throw new Error(`Invalid optional peer metadata: ${key}`)
141
+ for (const field of ['dependencies', 'peerDependencies', 'optionalDependencies']) {
142
+ const dependencies = entry[2][field]
143
+ if (dependencies === undefined) continue
144
+ for (const [name, version] of Object.entries(record(dependencies))) {
145
+ if (typeof version !== 'string') throw new Error(`Invalid ${key} dependency ${name}`)
146
+ // Optional peer declarations remain byte-identical in the retained
147
+ // tuple. They do not add an installation root that the source lacked.
148
+ const optionalPeer = field === 'peerDependencies' && optionalPeers.includes(name)
149
+ if (optionalPeer && !includeInstalledOptionalPeers) continue
150
+ const target = resolve(key, name, !optionalPeer && field !== 'optionalDependencies')
151
+ if (target) pending.push(target)
152
+ }
153
+ }
154
+ }
155
+ return {
156
+ lockfileVersion: 2,
157
+ configVersion: 1,
158
+ packages: Object.fromEntries(
159
+ Object.entries(packages).sort(([left], [right]) => (left < right ? -1 : left > right ? 1 : 0))
160
+ ),
161
+ }
162
+ }
163
+
164
+ /** Keep a complete lock graph; Bun rejects missing local roots before installation. */
165
+ export function seedConsumerLock(
166
+ lock: DependencyLock,
167
+ manifest: {
168
+ name: string
169
+ dependencies: Record<string, string>
170
+ devDependencies: Record<string, string>
171
+ },
172
+ local: LocalArchiveLock = { packages: {}, overrides: {} }
173
+ ) {
174
+ const packages = { ...lock.packages, ...local.packages }
175
+ for (const name of Object.keys({ ...manifest.dependencies, ...manifest.devDependencies }))
176
+ if (!Object.hasOwn(packages, name))
177
+ throw new Error(`Consumer lock is missing a root package: ${name}`)
178
+ return {
179
+ lockfileVersion: lock.lockfileVersion,
180
+ configVersion: lock.configVersion,
181
+ workspaces: { '': manifest },
182
+ overrides: local.overrides,
183
+ packages,
184
+ }
185
+ }
186
+
187
+ /** No new tuple, changed version/integrity/edge, or omitted required nested tuple is accepted. */
188
+ export function verifyConsumerLock(
189
+ value: unknown,
190
+ expected: DependencyLock,
191
+ allowAdditional = false,
192
+ local?: LocalArchiveLock
193
+ ): void {
194
+ // Applications may add unrelated dependencies. The fixed qualification cohort
195
+ // uses the exact comparison; runtime validation still pins every required tuple.
196
+ const packages = record(record(value)['packages'])
197
+ const localRoots = Object.keys(local?.packages ?? {})
198
+ if (Object.keys(packages).some((key) => localRoots.some((root) => key.startsWith(root + '/'))))
199
+ throw new Error(
200
+ 'Installed dependency lock introduces a nested locator beneath a required local package'
201
+ )
202
+ const supplied = lockedPackages(value, local)
203
+ const roots = Object.fromEntries(
204
+ Object.entries(expected.packages)
205
+ .filter(([key, entry]) => key === identity(entry).name)
206
+ .map(([key, entry]) => [key, identity(entry).version])
207
+ )
208
+ const actual = allowAdditional ? dependencyClosure(supplied, roots, true) : supplied
209
+ if (!isDeepStrictEqual(actual, expected))
210
+ throw new Error('Installed dependency lock differs from the supplied pinned package set')
211
+ if (local) {
212
+ const overrides = record(record(value)['overrides'])
213
+ // Selector/parent override forms can affect required packages too. Keep the
214
+ // complete override map exact, while permitting unrelated registry roots.
215
+ if (!isDeepStrictEqual(overrides, local.overrides))
216
+ throw new Error(
217
+ 'Installed local archive overrides differ from the supplied pinned package set'
218
+ )
219
+ }
220
+ }