@bun-native/cli 0.0.0-stage → 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.
- package/LICENSE +21 -0
- package/README.md +4 -2
- package/bun-native-source.json +131 -0
- package/package.json +33 -4
- package/src/.oxfmtrc.json +20 -0
- package/src/.oxlintrc.json +12 -0
- package/src/experiments/aot-gpui/compile.ts +41 -0
- package/src/experiments/aot-gpui/inspect-runtime.ts +303 -0
- package/src/experiments/aot-gpui/inspect.ts +65 -0
- package/src/scripts/native/artifact.ts +32 -0
- package/src/scripts/native/build.ts +250 -0
- package/src/scripts/native/doctor.ts +68 -0
- package/src/scripts/native/host-download.ts +139 -0
- package/src/scripts/native/host-header.ts +33 -0
- package/src/scripts/native/host.ts +22 -0
- package/src/scripts/native/inputs.ts +65 -0
- package/src/scripts/native/lint.ts +43 -0
- package/src/scripts/native/package-archive.ts +103 -0
- package/src/scripts/native/package-lock.ts +220 -0
- package/src/scripts/native/package-set.ts +408 -0
- package/src/scripts/native/paths.ts +20 -0
- package/src/scripts/native/project.ts +251 -0
- package/src/scripts/native/quality.ts +61 -0
- package/src/scripts/native/session.ts +296 -0
- package/src/scripts/native/standalone.ts +83 -0
- package/src/scripts/native/watch.ts +104 -0
- package/src/scripts/native.ts +219 -0
- package/src/scripts/solid/transform.ts +16 -0
- package/src/scripts/solid-build.ts +43 -0
- package/src/toolchain.json +64 -0
- package/src/tsconfig.json +45 -0
|
@@ -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
|
+
}
|