@bun-native/cli 0.33.0 → 0.33.1
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/README.md +1 -1
- package/bun-native-source.json +64 -44
- package/package.json +31 -1
- package/src/experiments/aot-gpui/compile.ts +12 -9
- package/src/experiments/aot-gpui/inspect-runtime.ts +3 -2
- package/src/experiments/aot-gpui/inspect.ts +3 -2
- package/src/scripts/native/artifact.ts +44 -3
- package/src/scripts/native/build.ts +22 -41
- package/src/scripts/native/bundle.ts +121 -0
- package/src/scripts/native/consumer-files.ts +46 -0
- package/src/scripts/native/context.ts +95 -0
- package/src/scripts/native/doctor.ts +3 -2
- package/src/scripts/native/host-download.ts +64 -24
- package/src/scripts/native/inputs.ts +50 -20
- package/src/scripts/native/lint.ts +2 -2
- package/src/scripts/native/package-archive.ts +7 -16
- package/src/scripts/native/package-set.ts +24 -15
- package/src/scripts/native/package-stamp.ts +209 -0
- package/src/scripts/native/project.ts +44 -24
- package/src/scripts/native/quality.ts +36 -21
- package/src/scripts/native/registry-project.ts +90 -0
- package/src/scripts/native/session.ts +4 -1
- package/src/scripts/native/standalone.ts +8 -27
- package/src/scripts/native/ustar.ts +29 -0
- package/src/scripts/native.ts +62 -44
- package/src/toolchain.json +12 -2
- package/src/scripts/solid-build.ts +0 -43
package/README.md
CHANGED
|
@@ -2,4 +2,4 @@
|
|
|
2
2
|
|
|
3
3
|
Create, check, run and AOT-build Bun Native desktop applications.
|
|
4
4
|
|
|
5
|
-
Part of [Bun Native](https://github.com/0xPlayerOne/bun-native), an experimental developer preview. Version 0.33.
|
|
5
|
+
Part of [Bun Native](https://github.com/0xPlayerOne/bun-native), an experimental developer preview. Version 0.33.1 was built from [c56fdd675365](https://github.com/0xPlayerOne/bun-native/tree/c56fdd6753653a10144a4167c1d3dfb3c38a0647) and published from GitHub Actions with npm provenance. Use the Bun revision pinned in that commit's toolchain.json. Start with the [getting-started guide](https://github.com/0xPlayerOne/bun-native/blob/c56fdd6753653a10144a4167c1d3dfb3c38a0647/docs/getting-started.md); `bun-native host install` fetches the matching release host. The license notice is in LICENSE.
|
package/bun-native-source.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"commit": "
|
|
4
|
-
"tree": "
|
|
3
|
+
"commit": "c56fdd6753653a10144a4167c1d3dfb3c38a0647",
|
|
4
|
+
"tree": "c6e81b3f9d041e11dd0413b5fdd7a50910383854",
|
|
5
5
|
"files": {
|
|
6
6
|
"package.json": {
|
|
7
|
-
"bytes":
|
|
8
|
-
"sha256": "
|
|
7
|
+
"bytes": 2115,
|
|
8
|
+
"sha256": "caf866d56f3cd7530d5735ecbf62512e9e22526bc44c75a4b48a18c26352c0b8"
|
|
9
9
|
},
|
|
10
10
|
".oxfmtrc.json": {
|
|
11
11
|
"bytes": 402,
|
|
@@ -16,36 +16,48 @@
|
|
|
16
16
|
"sha256": "25e27847d6fd50c27314506d50c83a68764489b84bf91f9c35dceb937134e882"
|
|
17
17
|
},
|
|
18
18
|
"experiments/aot-gpui/compile.ts": {
|
|
19
|
-
"bytes":
|
|
20
|
-
"sha256": "
|
|
19
|
+
"bytes": 1947,
|
|
20
|
+
"sha256": "3617b537e53c90759591b2240c94f65aae29ca04d5ce3dcb661ec0e26609cc2a"
|
|
21
21
|
},
|
|
22
22
|
"experiments/aot-gpui/inspect-runtime.ts": {
|
|
23
|
-
"bytes":
|
|
24
|
-
"sha256": "
|
|
23
|
+
"bytes": 12612,
|
|
24
|
+
"sha256": "d1b2bebca588db846fc964177ae8b42fc5ae0474bfa0ebc9ee833a41e240c39d"
|
|
25
25
|
},
|
|
26
26
|
"experiments/aot-gpui/inspect.ts": {
|
|
27
|
-
"bytes":
|
|
28
|
-
"sha256": "
|
|
27
|
+
"bytes": 2504,
|
|
28
|
+
"sha256": "e24851ad70d48df0160ed0ace6d39ebcbf7b7845d793cbd3b0b8a42e9736b499"
|
|
29
29
|
},
|
|
30
30
|
"scripts/native.ts": {
|
|
31
|
-
"bytes":
|
|
32
|
-
"sha256": "
|
|
31
|
+
"bytes": 10591,
|
|
32
|
+
"sha256": "8e100fe4929660a5415571b8ec7658074cfaa3c60d2c54fbbed6fb3b2a19be42"
|
|
33
33
|
},
|
|
34
34
|
"scripts/native/artifact.ts": {
|
|
35
|
-
"bytes":
|
|
36
|
-
"sha256": "
|
|
35
|
+
"bytes": 2745,
|
|
36
|
+
"sha256": "a64e82d74f56b05d01fb9ea954414f39c6a94f4a298515aff9c05b0dd257da4d"
|
|
37
37
|
},
|
|
38
38
|
"scripts/native/build.ts": {
|
|
39
|
-
"bytes":
|
|
40
|
-
"sha256": "
|
|
39
|
+
"bytes": 9504,
|
|
40
|
+
"sha256": "42cae4ccfb02ade4c170c1a8b1cc2fc364a7bbdf99c2fc39d1118f00e88dcd55"
|
|
41
|
+
},
|
|
42
|
+
"scripts/native/bundle.ts": {
|
|
43
|
+
"bytes": 4215,
|
|
44
|
+
"sha256": "b29afed376f6fd55ac345bf68a16418301191b89d81e9f0cf52cbab0c21e567f"
|
|
45
|
+
},
|
|
46
|
+
"scripts/native/consumer-files.ts": {
|
|
47
|
+
"bytes": 1599,
|
|
48
|
+
"sha256": "5176aeffd47dc99b9cc5d5f6d17dfcfdb982365d2f9fbb7d2260e26fbd5c18a9"
|
|
49
|
+
},
|
|
50
|
+
"scripts/native/context.ts": {
|
|
51
|
+
"bytes": 3416,
|
|
52
|
+
"sha256": "4ca3e7ce77331bec10783c9c9b744f7065363baac7b5888d845062677f5ff524"
|
|
41
53
|
},
|
|
42
54
|
"scripts/native/doctor.ts": {
|
|
43
|
-
"bytes":
|
|
44
|
-
"sha256": "
|
|
55
|
+
"bytes": 2748,
|
|
56
|
+
"sha256": "425c1d2bb309556ae4b1a7798130361d09e49b6ce2db7493c241e1fb38737f2c"
|
|
45
57
|
},
|
|
46
58
|
"scripts/native/host-download.ts": {
|
|
47
|
-
"bytes":
|
|
48
|
-
"sha256": "
|
|
59
|
+
"bytes": 7676,
|
|
60
|
+
"sha256": "2850dcbd88c377dfb85160af90514fb57ef646f1545dbf89b28d604d87b6041d"
|
|
49
61
|
},
|
|
50
62
|
"scripts/native/host-header.ts": {
|
|
51
63
|
"bytes": 1248,
|
|
@@ -56,60 +68,68 @@
|
|
|
56
68
|
"sha256": "5e790bbf787bc22e37aecf031520942d96677717b201fd9889ddcb8eaa2715e1"
|
|
57
69
|
},
|
|
58
70
|
"scripts/native/inputs.ts": {
|
|
59
|
-
"bytes":
|
|
60
|
-
"sha256": "
|
|
71
|
+
"bytes": 3783,
|
|
72
|
+
"sha256": "4b817e8c697e64bdf669e072df91dc1ac61400cb45c91fdefd4269f9d1be4b73"
|
|
61
73
|
},
|
|
62
74
|
"scripts/native/lint.ts": {
|
|
63
|
-
"bytes":
|
|
64
|
-
"sha256": "
|
|
75
|
+
"bytes": 1842,
|
|
76
|
+
"sha256": "201e66c00c8c1d861c67a2ed686fb71e9f7830356db83701a1ce851d72f41b95"
|
|
65
77
|
},
|
|
66
78
|
"scripts/native/package-archive.ts": {
|
|
67
|
-
"bytes":
|
|
68
|
-
"sha256": "
|
|
79
|
+
"bytes": 4610,
|
|
80
|
+
"sha256": "faea43432b0f85f315f0dc17ad9b0ca3bdabcd5324ce942577a1ac9940a49d65"
|
|
69
81
|
},
|
|
70
82
|
"scripts/native/package-lock.ts": {
|
|
71
83
|
"bytes": 9244,
|
|
72
84
|
"sha256": "ad8f2b73ca5b88075b503a18c2f0405f13ec60a6c1d9a9d172056d707c0b5e10"
|
|
73
85
|
},
|
|
74
86
|
"scripts/native/package-set.ts": {
|
|
75
|
-
"bytes":
|
|
76
|
-
"sha256": "
|
|
87
|
+
"bytes": 16516,
|
|
88
|
+
"sha256": "79fd8a3a0f4e74f4e312cb511beec9e4893eb18b7ddef8c1179dcff47fc3e858"
|
|
89
|
+
},
|
|
90
|
+
"scripts/native/package-stamp.ts": {
|
|
91
|
+
"bytes": 7982,
|
|
92
|
+
"sha256": "b622a5c2e60da5f804425b37181e573008b1c468afbf9cfe6cab472145cc6e3a"
|
|
77
93
|
},
|
|
78
94
|
"scripts/native/paths.ts": {
|
|
79
95
|
"bytes": 806,
|
|
80
96
|
"sha256": "6ccf669071878d65dbe4686e9b8585bbb217b4a9cd834f7c1866c9448c0c9a55"
|
|
81
97
|
},
|
|
82
98
|
"scripts/native/project.ts": {
|
|
83
|
-
"bytes":
|
|
84
|
-
"sha256": "
|
|
99
|
+
"bytes": 12188,
|
|
100
|
+
"sha256": "80464428b3efd46253e94cb0ab2a7041ba31753fb97f29dde8acba86fa3c0b21"
|
|
85
101
|
},
|
|
86
102
|
"scripts/native/quality.ts": {
|
|
87
|
-
"bytes":
|
|
88
|
-
"sha256": "
|
|
103
|
+
"bytes": 2917,
|
|
104
|
+
"sha256": "f68f6730c1371cfd63763716256f717b3f52581ee4b07f4bfa2572873ab49ff9"
|
|
105
|
+
},
|
|
106
|
+
"scripts/native/registry-project.ts": {
|
|
107
|
+
"bytes": 4651,
|
|
108
|
+
"sha256": "4a265b611dce5a7d2614abeb04ca010433f75be80837ae997706ae48f03c3814"
|
|
89
109
|
},
|
|
90
110
|
"scripts/native/session.ts": {
|
|
91
|
-
"bytes":
|
|
92
|
-
"sha256": "
|
|
111
|
+
"bytes": 9768,
|
|
112
|
+
"sha256": "d1132c54f9e11286198be5ac10cd68d388616ea90b2a400181c578faebfc1a2c"
|
|
93
113
|
},
|
|
94
114
|
"scripts/native/standalone.ts": {
|
|
95
|
-
"bytes":
|
|
96
|
-
"sha256": "
|
|
115
|
+
"bytes": 5235,
|
|
116
|
+
"sha256": "1830b5f41cfa7516560a5b6d1908899dfb2e93425e6c0a09d8cef608d9490517"
|
|
117
|
+
},
|
|
118
|
+
"scripts/native/ustar.ts": {
|
|
119
|
+
"bytes": 1264,
|
|
120
|
+
"sha256": "c8011ecb1de6e4c1065c1e90cede7cc12a1cf7344231267d01472dfacd9d9d88"
|
|
97
121
|
},
|
|
98
122
|
"scripts/native/watch.ts": {
|
|
99
123
|
"bytes": 3793,
|
|
100
124
|
"sha256": "b5d7f98516135ae1577823ccd6d4647f53d84df7a0f1fa68d4cad5a2c62cdd0f"
|
|
101
125
|
},
|
|
102
|
-
"scripts/solid-build.ts": {
|
|
103
|
-
"bytes": 1615,
|
|
104
|
-
"sha256": "5f263562cc299642e2971cce1dd913533ff092bc5543abd11d07048e8b3e5908"
|
|
105
|
-
},
|
|
106
126
|
"scripts/solid/transform.ts": {
|
|
107
127
|
"bytes": 642,
|
|
108
128
|
"sha256": "281fdbd71b10e613d9080e0494320161e51cfaf9a1c7b9966707eba0dd24ef1c"
|
|
109
129
|
},
|
|
110
130
|
"toolchain.json": {
|
|
111
|
-
"bytes":
|
|
112
|
-
"sha256": "
|
|
131
|
+
"bytes": 3234,
|
|
132
|
+
"sha256": "9f4d9362cb4cc38c0f4e705295789a0cbfec46ffdd007e0541b9441a7c3003a7"
|
|
113
133
|
},
|
|
114
134
|
"tsconfig.json": {
|
|
115
135
|
"bytes": 1104,
|
|
@@ -121,8 +141,8 @@
|
|
|
121
141
|
}
|
|
122
142
|
},
|
|
123
143
|
"toolchain": {
|
|
124
|
-
"bytes":
|
|
125
|
-
"sha256": "
|
|
144
|
+
"bytes": 3234,
|
|
145
|
+
"sha256": "9f4d9362cb4cc38c0f4e705295789a0cbfec46ffdd007e0541b9441a7c3003a7"
|
|
126
146
|
},
|
|
127
147
|
"dependencyLock": {
|
|
128
148
|
"bytes": 28357,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bun-native/cli",
|
|
3
|
-
"version": "0.33.
|
|
3
|
+
"version": "0.33.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"files": [
|
|
@@ -31,5 +31,35 @@
|
|
|
31
31
|
"publishConfig": {
|
|
32
32
|
"access": "public",
|
|
33
33
|
"provenance": true
|
|
34
|
+
},
|
|
35
|
+
"bunNative": {
|
|
36
|
+
"templates": {
|
|
37
|
+
"solid": {
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"@bun-native/core": "0.33.1",
|
|
40
|
+
"@bun-native/solid": "0.33.1",
|
|
41
|
+
"solid-js": "1.9.18"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@bun-native/cli": "0.33.1",
|
|
45
|
+
"@types/bun": "1.4.4",
|
|
46
|
+
"oxlint": "1.92.0"
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
"react": {
|
|
50
|
+
"dependencies": {
|
|
51
|
+
"@bun-native/core": "0.33.1",
|
|
52
|
+
"@bun-native/react": "0.33.1",
|
|
53
|
+
"react": "19.3.1"
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@bun-native/cli": "0.33.1",
|
|
57
|
+
"@types/bun": "1.4.4",
|
|
58
|
+
"oxlint": "1.92.0",
|
|
59
|
+
"@types/react": "19.3.0",
|
|
60
|
+
"@types/react-reconciler": "0.34.1"
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
34
64
|
}
|
|
35
65
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { writeFileSync } from 'node:fs'
|
|
1
|
+
import { readFileSync, writeFileSync } from 'node:fs'
|
|
2
2
|
import { dirname, join } from 'node:path'
|
|
3
3
|
import { inspectBunExecutable } from './inspect'
|
|
4
4
|
import { inspectBunRuntime } from './inspect-runtime'
|
|
@@ -7,14 +7,11 @@ import toolchain from '../../toolchain.json'
|
|
|
7
7
|
/** Shared real AOT compiler and executable checks for fixtures and application tooling. */
|
|
8
8
|
export function compileApplication(input: string, output: string, cwd: string, trim = false) {
|
|
9
9
|
if (Bun.revision !== toolchain.bun.revision) throw new Error('Use the pinned Bun AOT compiler')
|
|
10
|
+
// toolchain.json is the one record of the compiler arguments (with the pinned revision).
|
|
10
11
|
const compileCommand = [
|
|
11
12
|
process.execPath,
|
|
12
|
-
|
|
13
|
-
'compile',
|
|
14
|
-
'--no-script',
|
|
15
|
-
'--production',
|
|
13
|
+
...toolchain.bun.compileArguments,
|
|
16
14
|
...(trim ? ['--trim'] : []),
|
|
17
|
-
'--explain=json',
|
|
18
15
|
input,
|
|
19
16
|
'--outfile',
|
|
20
17
|
output,
|
|
@@ -31,11 +28,17 @@ export function compileApplication(input: string, output: string, cwd: string, t
|
|
|
31
28
|
if (result.exitCode !== 0)
|
|
32
29
|
throw new Error(`compile-aot failed (${result.exitCode}): ${result.stderr}`)
|
|
33
30
|
const buildMs = performance.now() - start
|
|
34
|
-
|
|
31
|
+
// Read the executable once for both inspectors and its digest.
|
|
32
|
+
const bytes = readFileSync(output)
|
|
33
|
+
const inspection = inspectBunExecutable(bytes)
|
|
35
34
|
if (!inspection.hasCompiledProgram)
|
|
36
35
|
throw new Error('Compiler output has no compiled-program record')
|
|
37
|
-
const runtimeFeatures = inspectBunRuntime(
|
|
36
|
+
const runtimeFeatures = inspectBunRuntime(bytes)
|
|
38
37
|
if (runtimeFeatures.jit === 'present' || runtimeFeatures.jitRemovedRanges.count === 0)
|
|
39
38
|
throw new Error('Compiler output does not record removal of the JIT compiler')
|
|
40
|
-
|
|
39
|
+
const executable = {
|
|
40
|
+
bytes: bytes.length,
|
|
41
|
+
sha256: new Bun.CryptoHasher('sha256').update(bytes).digest('hex'),
|
|
42
|
+
}
|
|
43
|
+
return { compileCommand, buildMs, inspection, runtimeFeatures, executable }
|
|
41
44
|
}
|
|
@@ -218,8 +218,9 @@ function overlappingMappings(table: Section[], start: bigint, length: bigint): S
|
|
|
218
218
|
)
|
|
219
219
|
}
|
|
220
220
|
|
|
221
|
-
|
|
222
|
-
|
|
221
|
+
/** Inspect an executable by path, or bytes a caller already read. */
|
|
222
|
+
export function inspectBunRuntime(source: string | Buffer) {
|
|
223
|
+
const bytes = typeof source === 'string' ? readFileSync(source) : source
|
|
223
224
|
const { format, name, table } = imageSections(bytes)
|
|
224
225
|
const candidates = table.filter((section) => section.name === name)
|
|
225
226
|
const section = candidates[0]
|
|
@@ -8,14 +8,15 @@ const offsetsSize = 32
|
|
|
8
8
|
const moduleRecordSize = 52
|
|
9
9
|
const compiledProgramFlag = 1 << 14
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
/** Inspect an executable by path, or bytes a caller already read. */
|
|
12
|
+
export function inspectBunExecutable(source: string | Buffer): {
|
|
12
13
|
format: 'elf' | 'macho' | 'pe'
|
|
13
14
|
payloadOffset: number
|
|
14
15
|
payloadBytes: number
|
|
15
16
|
flags: number
|
|
16
17
|
hasCompiledProgram: boolean
|
|
17
18
|
} {
|
|
18
|
-
const bytes = readFileSync(
|
|
19
|
+
const bytes = typeof source === 'string' ? readFileSync(source) : source
|
|
19
20
|
const magic = bytes.length >= 4 ? bytes.readUInt32LE(0) : 0
|
|
20
21
|
const format =
|
|
21
22
|
magic === 0x464c457f
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { mkdir, open, readFile, rename, rm, stat, writeFile } from 'node:fs/promises'
|
|
2
|
+
import { dirname } from 'node:path'
|
|
3
3
|
|
|
4
4
|
export type FileDigest = { bytes: number; sha256: string }
|
|
5
5
|
|
|
@@ -8,8 +8,13 @@ export type FileDigest = { bytes: number; sha256: string }
|
|
|
8
8
|
const WHOLE_FILE_BYTES = 4 * 1024 * 1024
|
|
9
9
|
const CHUNK_BYTES = 1024 * 1024
|
|
10
10
|
|
|
11
|
+
/** SHA-256 hex through Bun's native hasher; avoids loading node:crypto on CLI paths. */
|
|
12
|
+
export function sha256(data: string | Uint8Array): string {
|
|
13
|
+
return new Bun.CryptoHasher('sha256').update(data).digest('hex')
|
|
14
|
+
}
|
|
15
|
+
|
|
11
16
|
export async function fileDigest(path: string): Promise<FileDigest> {
|
|
12
|
-
const hash =
|
|
17
|
+
const hash = new Bun.CryptoHasher('sha256')
|
|
13
18
|
// The size only picks a strategy; both read to EOF and count what they read.
|
|
14
19
|
if ((await stat(path)).size <= WHOLE_FILE_BYTES) {
|
|
15
20
|
const data = await readFile(path)
|
|
@@ -30,3 +35,39 @@ export async function fileDigest(path: string): Promise<FileDigest> {
|
|
|
30
35
|
}
|
|
31
36
|
return { bytes, sha256: hash.digest('hex') }
|
|
32
37
|
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* The digest of a large, rarely changing file (the compiler), reused from `cache`
|
|
41
|
+
* while the file keeps its device, inode, size, mtime and ctime. Any write or
|
|
42
|
+
* replacement changes one of those, and the file is hashed again.
|
|
43
|
+
*/
|
|
44
|
+
export async function cachedFileDigest(path: string, cache: string): Promise<FileDigest> {
|
|
45
|
+
const identity = async () => {
|
|
46
|
+
const stats = await stat(path, { bigint: true })
|
|
47
|
+
return [stats.dev, stats.ino, stats.size, stats.mtimeNs, stats.ctimeNs].join(':')
|
|
48
|
+
}
|
|
49
|
+
const before = await identity()
|
|
50
|
+
try {
|
|
51
|
+
const cached = await Bun.file(cache).json()
|
|
52
|
+
if (
|
|
53
|
+
cached?.path === path &&
|
|
54
|
+
cached.identity === before &&
|
|
55
|
+
Number.isSafeInteger(cached.digest?.bytes) &&
|
|
56
|
+
/^[0-9a-f]{64}$/.test(cached.digest?.sha256)
|
|
57
|
+
)
|
|
58
|
+
return { bytes: cached.digest.bytes, sha256: cached.digest.sha256 }
|
|
59
|
+
} catch {}
|
|
60
|
+
const digest = await fileDigest(path)
|
|
61
|
+
if ((await identity()) === before) {
|
|
62
|
+
const partial = `${cache}.${process.pid}.partial`
|
|
63
|
+
try {
|
|
64
|
+
await mkdir(dirname(cache), { recursive: true })
|
|
65
|
+
await writeFile(partial, JSON.stringify({ path, identity: before, digest }) + '\n')
|
|
66
|
+
await rename(partial, cache)
|
|
67
|
+
} catch {
|
|
68
|
+
// The cache is optional; a read-only project hashes every time.
|
|
69
|
+
await rm(partial, { force: true }).catch(() => {})
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return digest
|
|
73
|
+
}
|
|
@@ -3,48 +3,20 @@ import { chmod, copyFile, mkdir, readFile, readdir, writeFile } from 'node:fs/pr
|
|
|
3
3
|
import { dirname, isAbsolute, join, relative, resolve } from 'node:path'
|
|
4
4
|
import { compileApplication } from '../../experiments/aot-gpui/compile.ts'
|
|
5
5
|
import type { Project } from './project.ts'
|
|
6
|
-
import {
|
|
6
|
+
import { bundleApplication } from './bundle.ts'
|
|
7
|
+
import { cachedFileDigest, fileDigest, sha256 } from './artifact.ts'
|
|
8
|
+
import { userCache } from './context.ts'
|
|
7
9
|
import toolchain from '../../toolchain.json'
|
|
8
10
|
|
|
9
|
-
/**
|
|
10
|
-
* Bun.build names each module in its `// path` comments relative to the process
|
|
11
|
-
* working directory, so bundling from another cwd changed the module bytes. Anchor
|
|
12
|
-
* it to the project for the bundle's duration; every path used here is absolute.
|
|
13
|
-
*/
|
|
14
|
-
async function inProjectDirectory<T>(project: Project, bundle: () => Promise<T>): Promise<T> {
|
|
15
|
-
const previous = process.cwd()
|
|
16
|
-
process.chdir(project.directory)
|
|
17
|
-
try {
|
|
18
|
-
return await bundle()
|
|
19
|
-
} finally {
|
|
20
|
-
process.chdir(previous)
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
11
|
+
/** Bundle a project's entry into `outdir/application.js` with the shared configuration. */
|
|
24
12
|
export async function bundleProject(project: Project, outdir: string): Promise<string> {
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
async function bundleFromProject(project: Project, outdir: string): Promise<string> {
|
|
30
|
-
if (project.framework === 'solid') {
|
|
31
|
-
// Only Solid needs the Babel universal transform; React builds skip loading it.
|
|
32
|
-
const { buildSolid } = await import('../solid-build.ts')
|
|
33
|
-
return buildSolid(project.entry, outdir)
|
|
34
|
-
}
|
|
35
|
-
await mkdir(outdir, { recursive: true })
|
|
36
|
-
const result = await Bun.build({
|
|
37
|
-
entrypoints: [project.entry],
|
|
13
|
+
const { module } = await bundleApplication({
|
|
14
|
+
framework: project.framework,
|
|
15
|
+
entry: project.entry,
|
|
38
16
|
outdir,
|
|
39
|
-
|
|
40
|
-
target: 'bun',
|
|
41
|
-
format: 'esm',
|
|
42
|
-
jsx: { runtime: 'automatic', importSource: '@bun-native/react' },
|
|
43
|
-
define: { 'process.env.NODE_ENV': '"production"' },
|
|
44
|
-
sourcemap: 'external',
|
|
17
|
+
cwd: project.directory,
|
|
45
18
|
})
|
|
46
|
-
|
|
47
|
-
return join(outdir, 'application.js')
|
|
19
|
+
return module
|
|
48
20
|
}
|
|
49
21
|
|
|
50
22
|
/** The application bundle that holds the host in a macOS build. */
|
|
@@ -148,7 +120,10 @@ export async function buildProject(
|
|
|
148
120
|
`import { dirname, join } from 'node:path'\nprocess.env['BUN_NATIVE_HOST'] ??= join(dirname(process.execPath), ${JSON.stringify(hostFile)})\nawait import('./development/application.js')\n`,
|
|
149
121
|
{ flag: 'wx' }
|
|
150
122
|
)
|
|
151
|
-
|
|
123
|
+
// The compiler names the program by its path relative to its working directory,
|
|
124
|
+
// so compiling the relative launcher from the output directory keeps the
|
|
125
|
+
// executable independent of where that directory is.
|
|
126
|
+
const compiled = compileApplication('launcher.ts', join(outdir, applicationFile), outdir)
|
|
152
127
|
if (layout.infoPlist && infoPlist) {
|
|
153
128
|
await mkdir(dirname(join(outdir, hostFile)), { recursive: true })
|
|
154
129
|
await writeFile(join(outdir, layout.infoPlist), infoPlist, { flag: 'wx' })
|
|
@@ -165,7 +140,7 @@ export async function buildProject(
|
|
|
165
140
|
throw new Error('Application module changed during AOT compilation')
|
|
166
141
|
// Keep machine-local paths and wall-clock timing out of the manifest so equal
|
|
167
142
|
// inputs produce an identical manifest; timing goes to a separate diagnostic.
|
|
168
|
-
const { compileCommand, buildMs, ...inspected } = compiled
|
|
143
|
+
const { compileCommand, buildMs, executable, ...inspected } = compiled
|
|
169
144
|
await writeFile(
|
|
170
145
|
join(outdir, 'build-metrics.json'),
|
|
171
146
|
JSON.stringify({ schemaVersion: 1, compileMs: buildMs }, null, 2) + '\n',
|
|
@@ -180,13 +155,19 @@ export async function buildProject(
|
|
|
180
155
|
toolchain,
|
|
181
156
|
application: {
|
|
182
157
|
file: applicationFile,
|
|
183
|
-
...
|
|
158
|
+
...executable,
|
|
184
159
|
// Absolute arguments are recorded relative to this artifact directory; the
|
|
185
160
|
// compiler is identified by its digest instead of its install path.
|
|
186
161
|
compileCommand: compileCommand.map((argument, index) =>
|
|
187
162
|
index === 0 ? 'bun' : isAbsolute(argument) ? relative(outdir, argument) : argument
|
|
188
163
|
),
|
|
189
|
-
compiler: {
|
|
164
|
+
compiler: {
|
|
165
|
+
revision: Bun.revision,
|
|
166
|
+
executable: await cachedFileDigest(
|
|
167
|
+
process.execPath,
|
|
168
|
+
join(userCache(), 'compiler', `${sha256(process.execPath).slice(0, 32)}.json`)
|
|
169
|
+
),
|
|
170
|
+
},
|
|
190
171
|
...inspected,
|
|
191
172
|
},
|
|
192
173
|
developmentModule: { file: 'development/application.js', ...developmentModule },
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import type { BuildConfig, BunPlugin } from 'bun'
|
|
2
|
+
import { mkdir, realpath, stat } from 'node:fs/promises'
|
|
3
|
+
import { join, resolve, sep } from 'node:path'
|
|
4
|
+
|
|
5
|
+
export type Framework = 'solid' | 'react'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The one bundler configuration for a Bun Native application module. `native dev`,
|
|
9
|
+
* `native build`, the CI fixtures and the benchmarks all bundle through it, so CI
|
|
10
|
+
* qualifies the bytes users build. The module is not minified: `native dev` runs it
|
|
11
|
+
* as is (readable stacks), and the AOT compiler's `--production` minifies the
|
|
12
|
+
* compiled program anyway.
|
|
13
|
+
*/
|
|
14
|
+
export function bundleConfig(
|
|
15
|
+
framework: Framework,
|
|
16
|
+
entry: string,
|
|
17
|
+
outdir: string,
|
|
18
|
+
plugins: BunPlugin[] = []
|
|
19
|
+
): BuildConfig & { outdir: string } {
|
|
20
|
+
return {
|
|
21
|
+
entrypoints: [entry],
|
|
22
|
+
outdir,
|
|
23
|
+
naming: 'application.js',
|
|
24
|
+
target: 'bun',
|
|
25
|
+
format: 'esm',
|
|
26
|
+
define: { 'process.env.NODE_ENV': '"production"' },
|
|
27
|
+
sourcemap: 'external',
|
|
28
|
+
metafile: true,
|
|
29
|
+
plugins,
|
|
30
|
+
// Solid's universal renderer is selected through the `browser` export condition;
|
|
31
|
+
// React uses its automatic JSX runtime from the adapter.
|
|
32
|
+
...(framework === 'solid'
|
|
33
|
+
? { conditions: ['browser'] }
|
|
34
|
+
: { jsx: { runtime: 'automatic', importSource: '@bun-native/react' } }),
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Solid's universal JSX transform as a bundler plugin; Babel loads only for Solid builds. */
|
|
39
|
+
async function solidPlugin(): Promise<BunPlugin> {
|
|
40
|
+
const { transformSolid } = await import('../solid/transform.ts')
|
|
41
|
+
return {
|
|
42
|
+
name: 'solid-native-universal',
|
|
43
|
+
setup(build) {
|
|
44
|
+
build.onLoad({ filter: /\.[jt]sx$/ }, async ({ path }) => ({
|
|
45
|
+
contents: transformSolid(await Bun.file(path).text(), path),
|
|
46
|
+
loader: 'ts',
|
|
47
|
+
}))
|
|
48
|
+
},
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export type Bundle = {
|
|
53
|
+
/** The absolute path of `application.js`; its source map is next to it. */
|
|
54
|
+
module: string
|
|
55
|
+
/** Every module the bundle read, relative to `cwd`, `/`-separated and sorted (from the metafile). */
|
|
56
|
+
inputs: string[]
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Bundle one application entry into `outdir/application.js`.
|
|
61
|
+
*
|
|
62
|
+
* Bun.build names each module in its `// path` comments relative to the process
|
|
63
|
+
* working directory, so the bundle runs with the working directory set to `cwd` (the
|
|
64
|
+
* project, or the checkout for fixtures) and equal sources give equal bytes from any
|
|
65
|
+
* caller directory.
|
|
66
|
+
*/
|
|
67
|
+
export async function bundleApplication(options: {
|
|
68
|
+
framework: Framework
|
|
69
|
+
entry: string
|
|
70
|
+
outdir: string
|
|
71
|
+
cwd: string
|
|
72
|
+
plugins?: BunPlugin[]
|
|
73
|
+
}): Promise<Bundle> {
|
|
74
|
+
const entry = await realpath(options.entry)
|
|
75
|
+
if (!(await stat(entry)).isFile() || !/\.[jt]sx?$/.test(entry))
|
|
76
|
+
throw new Error(
|
|
77
|
+
`${options.framework === 'solid' ? 'Solid' : 'React'} entry must be a TypeScript/JavaScript source file`
|
|
78
|
+
)
|
|
79
|
+
const outdir = resolve(options.outdir)
|
|
80
|
+
await mkdir(outdir, { recursive: true })
|
|
81
|
+
const plugins =
|
|
82
|
+
options.plugins ?? (options.framework === 'solid' ? [await solidPlugin()] : undefined)
|
|
83
|
+
const previous = process.cwd()
|
|
84
|
+
process.chdir(options.cwd)
|
|
85
|
+
let result: Awaited<ReturnType<typeof Bun.build>>
|
|
86
|
+
try {
|
|
87
|
+
result = await Bun.build(bundleConfig(options.framework, entry, outdir, plugins))
|
|
88
|
+
} finally {
|
|
89
|
+
process.chdir(previous)
|
|
90
|
+
}
|
|
91
|
+
if (!result.success)
|
|
92
|
+
throw new AggregateError(
|
|
93
|
+
result.logs,
|
|
94
|
+
`${options.framework === 'solid' ? 'Solid' : 'React'} native build failed`
|
|
95
|
+
)
|
|
96
|
+
return {
|
|
97
|
+
module: join(outdir, 'application.js'),
|
|
98
|
+
inputs: Object.keys(result.metafile?.inputs ?? {})
|
|
99
|
+
.map((path) => path.split(sep).join('/'))
|
|
100
|
+
.sort(),
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* The modules an entry that is compiled directly (without a framework bundle) reads,
|
|
106
|
+
* from the same resolution `bun compile` uses; nothing is written.
|
|
107
|
+
*/
|
|
108
|
+
export async function moduleInputs(entry: string, cwd: string): Promise<string[]> {
|
|
109
|
+
const previous = process.cwd()
|
|
110
|
+
process.chdir(cwd)
|
|
111
|
+
let result: Awaited<ReturnType<typeof Bun.build>>
|
|
112
|
+
try {
|
|
113
|
+
result = await Bun.build({ entrypoints: [entry], target: 'bun', format: 'esm', metafile: true })
|
|
114
|
+
} finally {
|
|
115
|
+
process.chdir(previous)
|
|
116
|
+
}
|
|
117
|
+
if (!result.success) throw new AggregateError(result.logs, `Cannot resolve ${entry}`)
|
|
118
|
+
return Object.keys(result.metafile?.inputs ?? {})
|
|
119
|
+
.map((path) => path.split(sep).join('/'))
|
|
120
|
+
.sort()
|
|
121
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import compilerConfig from '../../tsconfig.json'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Files shared by every project that installs Bun Native from packages (a local
|
|
5
|
+
* package set or the npm registry) instead of using a checkout's workspace.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/** The installed CLI, run without auto-install so a missing package fails instead of downloading. */
|
|
9
|
+
export const installedCli = 'bun --no-install ./node_modules/@bun-native/cli/src/scripts/native.ts'
|
|
10
|
+
|
|
11
|
+
export const json = (value: unknown) => JSON.stringify(value, null, 2) + '\n'
|
|
12
|
+
|
|
13
|
+
/** The template formatter keeps short string arrays on one line, so format checks pass as generated. */
|
|
14
|
+
const formattedJSON = (value: unknown) =>
|
|
15
|
+
json(value).replace(
|
|
16
|
+
/\[\n\s*("[^"\n]*"(?:,\n\s*"[^"\n]*")*)\n\s*\]/g,
|
|
17
|
+
(_, items: string) => `[${items.split(/,\n\s*/).join(', ')}]`
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
export function consumerScripts(): Record<string, string> {
|
|
21
|
+
return {
|
|
22
|
+
// The persistent watch controller returns freed bundler pages promptly;
|
|
23
|
+
// see docs/developer-tooling.md "Controller memory".
|
|
24
|
+
dev: `MIMALLOC_PURGE_DELAY=0 ${installedCli} dev .`,
|
|
25
|
+
build: `${installedCli} build .`,
|
|
26
|
+
check: 'bun --no-install check --no-script --no-pretty',
|
|
27
|
+
format: `${installedCli} format .`,
|
|
28
|
+
'format:check': `${installedCli} format . --check`,
|
|
29
|
+
lint: `${installedCli} lint .`,
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function consumerTsconfig(
|
|
34
|
+
framework: 'solid' | 'react',
|
|
35
|
+
entry: string,
|
|
36
|
+
exclude: string[]
|
|
37
|
+
): string {
|
|
38
|
+
return formattedJSON({
|
|
39
|
+
compilerOptions: {
|
|
40
|
+
...compilerConfig.compilerOptions,
|
|
41
|
+
jsxImportSource: `@bun-native/${framework}`,
|
|
42
|
+
},
|
|
43
|
+
include: [entry],
|
|
44
|
+
exclude,
|
|
45
|
+
})
|
|
46
|
+
}
|