@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,219 @@
1
+ #!/usr/bin/env bun
2
+ import { mkdir, mkdtemp, rm } from 'node:fs/promises'
3
+ import { join, resolve } from 'node:path'
4
+ import { pathToFileURL } from 'node:url'
5
+ import { parseArgs } from 'node:util'
6
+ import { prebuiltHost } from './native/host.ts'
7
+ import { createProject, readProject } from './native/project.ts'
8
+
9
+ const repository = resolve(import.meta.dir, '..')
10
+ const usage = `Bun Native application tooling
11
+
12
+ bun run native create apps/my-app [--framework solid|react] [--language typescript|javascript]
13
+ bun scripts/native.ts create /path/to/my-app --packages /path/to/package-set/manifest.json
14
+ bun run native dev apps/my-app --host /absolute/path/to/bun-native-host|auto [--watch]
15
+ bun run native build apps/my-app --host /absolute/path/to/bun-native-host|auto --host-profile dev|release [--outdir directory] [--bundle-id reverse.dns.id]
16
+ bun run native check apps/my-app
17
+ bun run native format apps/my-app [--check]
18
+ bun run native lint apps/my-app
19
+ bun run native doctor [apps/my-app] [--host /absolute/path/to/bun-native-host|auto]
20
+ bun run native host install [--version x.y.z] [--force]
21
+
22
+ Projects use checkout dependencies unless --packages selects a standalone local package set.
23
+ Standalone scripts use their own installed CLI. Native hosts are supplied explicitly: an absolute path,
24
+ or auto for this checkout version's released host (linux-x64, linux-arm64, darwin-arm64, darwin-x64).
25
+ dev starts once; --watch remounts the app after edits and resets state. build produces unsigned same-platform AOT artifacts.
26
+ check runs Bun's built-in type checker, formatter check and the pinned oxlint; format applies the formatter.
27
+ lint runs only the pinned oxlint, never on vendored packages or generated output.
28
+ doctor reports Bun, platform, host-header and project compatibility without launching anything.
29
+ host install and --host auto download the unsigned release host once, check it against the release's
30
+ SHA256SUMS and reuse it from .bun-native/hosts. No other command downloads a host; none runs Cargo.
31
+ See docs/developer-tooling.md.`
32
+
33
+ async function resolveHost(value: string | undefined): Promise<string> {
34
+ if (value !== 'auto') return prebuiltHost(value)
35
+ const { installReleaseHost } = await import('./native/host-download.ts')
36
+ return installReleaseHost({ version: await checkoutVersion(), cache: hostCache })
37
+ }
38
+
39
+ const hostCache = join(repository, '.bun-native', 'hosts')
40
+
41
+ async function checkoutVersion(): Promise<string> {
42
+ const manifest: { version?: unknown } = await Bun.file(join(repository, 'package.json')).json()
43
+ if (typeof manifest.version !== 'string') throw new Error('The checkout has no workspace version')
44
+ return manifest.version
45
+ }
46
+
47
+ async function main(args: string[]): Promise<void> {
48
+ if (args.length === 0 || (args.length === 1 && ['--help', '-h'].includes(args[0]!))) {
49
+ console.log(usage)
50
+ return
51
+ }
52
+ const [command, ...rest] = args
53
+ if (command === 'create') {
54
+ const { values, positionals } = parseArgs({
55
+ args: rest,
56
+ allowPositionals: true,
57
+ strict: true,
58
+ options: {
59
+ framework: { type: 'string', default: 'solid' },
60
+ language: { type: 'string', default: 'typescript' },
61
+ packages: { type: 'string' },
62
+ },
63
+ })
64
+ if (positionals.length !== 1)
65
+ throw new Error('create requires exactly one new project directory')
66
+ if (values.framework !== 'solid' && values.framework !== 'react')
67
+ throw new Error('--framework must be solid or react')
68
+ if (values.language !== 'typescript' && values.language !== 'javascript')
69
+ throw new Error('--language must be typescript or javascript')
70
+ const path = await createProject(
71
+ repository,
72
+ positionals[0]!,
73
+ values.framework,
74
+ values.language,
75
+ values.packages
76
+ )
77
+ console.log(`Created ${values.framework} ${values.language} project: ${path}`)
78
+ console.log(
79
+ values.packages
80
+ ? 'Follow the generated README to install the pinned dependencies, then run check and dev with a prebuilt host.'
81
+ : 'Run its check script, then dev with --host pointing to a prebuilt native host.'
82
+ )
83
+ return
84
+ }
85
+ if (command === 'check' || command === 'format' || command === 'lint') {
86
+ const { values, positionals } = parseArgs({
87
+ args: rest,
88
+ allowPositionals: true,
89
+ strict: true,
90
+ options: command === 'format' ? { check: { type: 'boolean' } } : {},
91
+ })
92
+ if (positionals.length !== 1)
93
+ throw new Error(`${command} requires exactly one project directory`)
94
+ const project = await readProject(repository, positionals[0]!)
95
+ const { checkProject, formatProject } = await import('./native/quality.ts')
96
+ const steps = []
97
+ if (command === 'format') steps.push(formatProject(project, values.check === true))
98
+ else {
99
+ if (command === 'check') steps.push(...checkProject(project))
100
+ // Only check and lint load the CLI's pinned oxlint; format never depends on it.
101
+ const { lintProject } = await import('./native/lint.ts')
102
+ steps.push(lintProject(project))
103
+ }
104
+ for (const step of steps) {
105
+ if (step.output) console.log(step.output)
106
+ console.log(`${step.name}: ${step.exitCode === 0 ? 'passed' : 'failed'}`)
107
+ }
108
+ if (steps.some((step) => step.exitCode !== 0)) {
109
+ const formatting = steps.find((step) => step.name === 'format')
110
+ if (formatting && formatting.exitCode !== 0 && (command === 'check' || values.check === true))
111
+ console.error(
112
+ 'Apply formatting with the project format script or the native format command.'
113
+ )
114
+ process.exitCode = 1
115
+ }
116
+ return
117
+ }
118
+ if (command === 'host') {
119
+ const { values, positionals } = parseArgs({
120
+ args: rest,
121
+ allowPositionals: true,
122
+ strict: true,
123
+ options: { version: { type: 'string' }, force: { type: 'boolean' } },
124
+ })
125
+ if (positionals.length !== 1 || positionals[0] !== 'install')
126
+ throw new Error('Usage: bun run native host install [--version x.y.z] [--force]')
127
+ const { installReleaseHost } = await import('./native/host-download.ts')
128
+ const host = await installReleaseHost({
129
+ version: values.version ?? (await checkoutVersion()),
130
+ cache: hostCache,
131
+ force: values.force === true,
132
+ })
133
+ console.log(host)
134
+ return
135
+ }
136
+ if (command === 'doctor') {
137
+ const { values, positionals } = parseArgs({
138
+ args: rest,
139
+ allowPositionals: true,
140
+ strict: true,
141
+ options: { host: { type: 'string' } },
142
+ })
143
+ if (positionals.length > 1) throw new Error('doctor accepts at most one project directory')
144
+ const { diagnose } = await import('./native/doctor.ts')
145
+ const host = values.host === 'auto' ? await resolveHost('auto') : values.host
146
+ const results = await diagnose(repository, { host, project: positionals[0] })
147
+ for (const result of results)
148
+ console.log(`${result.status.padEnd(7)} ${result.name}: ${result.detail}`)
149
+ if (results.some((result) => result.status === 'failed')) process.exitCode = 1
150
+ return
151
+ }
152
+ if (command !== 'dev' && command !== 'build')
153
+ throw new Error(`Unknown command: ${command}\n${usage}`)
154
+ const options: Record<string, { type: 'string' | 'boolean' }> = { host: { type: 'string' } }
155
+ if (command === 'build') {
156
+ options['outdir'] = { type: 'string' }
157
+ options['host-profile'] = { type: 'string' }
158
+ options['bundle-id'] = { type: 'string' }
159
+ } else options['watch'] = { type: 'boolean' }
160
+ const { values, positionals } = parseArgs({
161
+ args: rest,
162
+ allowPositionals: true,
163
+ strict: true,
164
+ options,
165
+ })
166
+ if (positionals.length !== 1) throw new Error(`${command} requires exactly one project directory`)
167
+ const host = await resolveHost(typeof values['host'] === 'string' ? values['host'] : undefined)
168
+ const project = await readProject(repository, positionals[0]!)
169
+ if (command === 'dev' && values.watch) {
170
+ const { watchProject } = await import('./native/watch.ts')
171
+ await watchProject(repository, project.directory, host)
172
+ return
173
+ }
174
+ const { buildProject, bundleProject } = await import('./native/build.ts')
175
+ if (command === 'build') {
176
+ const profile = values['host-profile']
177
+ if (profile !== 'dev' && profile !== 'release')
178
+ throw new Error(
179
+ 'build requires --host-profile dev or release (declared provenance, not a verified build profile)'
180
+ )
181
+ const outdir = resolve(
182
+ typeof values.outdir === 'string' ? values.outdir : join(project.directory, 'dist')
183
+ )
184
+ const bundleIdentifier = values['bundle-id']
185
+ await buildProject(
186
+ project,
187
+ host,
188
+ outdir,
189
+ profile,
190
+ typeof bundleIdentifier === 'string' ? { bundleIdentifier } : {}
191
+ )
192
+ console.log(`Built unsigned AOT application and supplied host in ${outdir}`)
193
+ return
194
+ }
195
+ const cache = join(project.directory, '.bun-native')
196
+ await mkdir(cache, { recursive: true })
197
+ const directory = await mkdtemp(join(cache, 'dev-'))
198
+ const originalHost = process.env['BUN_NATIVE_HOST']
199
+ try {
200
+ const entry = await bundleProject(project, directory)
201
+ process.env['BUN_NATIVE_HOST'] = host
202
+ console.log(`Starting ${project.framework} application with prebuilt host ${host}`)
203
+ await import(pathToFileURL(entry).href)
204
+ } finally {
205
+ if (originalHost === undefined) delete process.env['BUN_NATIVE_HOST']
206
+ else process.env['BUN_NATIVE_HOST'] = originalHost
207
+ await rm(directory, { recursive: true, force: true })
208
+ }
209
+ }
210
+
211
+ if (import.meta.main) {
212
+ try {
213
+ await main(process.argv.slice(2))
214
+ } catch (error) {
215
+ console.error(error instanceof Error ? error.message : String(error))
216
+ if (error instanceof AggregateError) for (const detail of error.errors) console.error(detail)
217
+ process.exitCode = 1
218
+ }
219
+ }
@@ -0,0 +1,16 @@
1
+ import { transformSync } from '@babel/core'
2
+ import solid from 'babel-preset-solid'
3
+
4
+ /** Official Solid universal transform; project/Babel configuration cannot select DOM output. */
5
+ export function transformSolid(source: string, filename: string): string {
6
+ const result = transformSync(source, {
7
+ filename,
8
+ babelrc: false,
9
+ configFile: false,
10
+ sourceMaps: false,
11
+ parserOpts: { plugins: ['typescript', 'jsx'] },
12
+ presets: [[solid, { generate: 'universal', moduleName: '@bun-native/solid', dev: false }]],
13
+ })
14
+ if (!result?.code) throw new Error('Solid JSX transform produced no application code')
15
+ return result.code
16
+ }
@@ -0,0 +1,43 @@
1
+ import { mkdir, realpath, stat } from 'node:fs/promises'
2
+ import { isAbsolute, resolve } from 'node:path'
3
+ import { transformSolid } from './solid/transform.ts'
4
+
5
+ /** Build-time JSX conversion only; the generated module is shared by dev and AOT. */
6
+ export async function buildSolid(entry: string, outdir: string): Promise<string> {
7
+ const source = await realpath(entry)
8
+ if (!(await stat(source)).isFile() || !/\.[jt]sx?$/.test(source))
9
+ throw new Error('Solid entry must be a TypeScript/JavaScript source file')
10
+ const destination = resolve(outdir)
11
+ await mkdir(destination, { recursive: true })
12
+ const result = await Bun.build({
13
+ entrypoints: [source],
14
+ outdir: destination,
15
+ naming: 'application.js',
16
+ target: 'bun',
17
+ format: 'esm',
18
+ conditions: ['browser'],
19
+ define: { 'process.env.NODE_ENV': '"production"' },
20
+ sourcemap: 'external',
21
+ plugins: [
22
+ {
23
+ name: 'solid-native-universal',
24
+ setup(build) {
25
+ build.onLoad({ filter: /\.[jt]sx$/ }, async ({ path }) => ({
26
+ contents: transformSolid(await Bun.file(path).text(), path),
27
+ loader: 'ts',
28
+ }))
29
+ },
30
+ },
31
+ ],
32
+ })
33
+ if (!result.success) throw new AggregateError(result.logs, 'Solid native build failed')
34
+ return resolve(destination, 'application.js')
35
+ }
36
+
37
+ if (import.meta.main) {
38
+ const entry = process.argv[2]
39
+ const outdir = process.argv[3]
40
+ if (!entry || !outdir || process.argv.length !== 4 || !isAbsolute(outdir))
41
+ throw new Error('Usage: bun scripts/solid-build.ts ENTRY ABSOLUTE_OUTPUT_DIRECTORY')
42
+ console.log(await buildSolid(entry, outdir))
43
+ }
@@ -0,0 +1,64 @@
1
+ {
2
+ "schemaVersion": 2,
3
+ "bun": {
4
+ "version": "1.4.4-canary.1",
5
+ "revision": "b1007f1177fd402fae67387b8548cae5ae9b5707",
6
+ "cliRevision": "1.4.4-canary.1+b1007f117",
7
+ "source": "https://github.com/oven-sh/bun/tree/b1007f1177fd402fae67387b8548cae5ae9b5707",
8
+ "compileArguments": ["compile", "--no-script", "--production"],
9
+ "distribution": "npm",
10
+ "packages": {
11
+ "linux-x64": {
12
+ "name": "@oven/bun-linux-x64",
13
+ "version": "1.4.4-canary.20261105.1+b1007f1",
14
+ "url": "https://registry.npmjs.org/@oven/bun-linux-x64/-/bun-linux-x64-1.4.4-canary.20261105.1.tgz",
15
+ "integrity": "sha512-eJqVFA3brmF4UPBynqjPbQEKvId1TvdThVuYUmtP4J/VVs42iKOd6M7mfp4DnRd4sOqY7FClDaPf04J4tun5Gw==",
16
+ "unpackedSize": 140680988
17
+ },
18
+ "linux-arm64": {
19
+ "name": "@oven/bun-linux-aarch64",
20
+ "version": "1.4.4-canary.20261105.1+b1007f1",
21
+ "url": "https://registry.npmjs.org/@oven/bun-linux-aarch64/-/bun-linux-aarch64-1.4.4-canary.20261105.1.tgz",
22
+ "integrity": "sha512-6ASK2HEbe9/DK7KUXVjRifcQUNY6Cykz/JP8Z1uoNOMlUlh7NeC5pWJA3qWsaWvB/sy3Ozl0fUNNaT9xt31ebw==",
23
+ "unpackedSize": 142145540
24
+ },
25
+ "darwin-x64": {
26
+ "name": "@oven/bun-darwin-x64",
27
+ "version": "1.4.4-canary.20261105.1+b1007f1",
28
+ "url": "https://registry.npmjs.org/@oven/bun-darwin-x64/-/bun-darwin-x64-1.4.4-canary.20261105.1.tgz",
29
+ "integrity": "sha512-QJLJxX78hR/rZTY/j/yQJ2Baf/x4m74HzDG5Z3ePGWM9f8gCIbVFd17KHEvH7F4VbrGZF+sLAyH/WXzVMkGuvA==",
30
+ "unpackedSize": 134455089
31
+ },
32
+ "darwin-arm64": {
33
+ "name": "@oven/bun-darwin-aarch64",
34
+ "version": "1.4.4-canary.20261105.1+b1007f1",
35
+ "url": "https://registry.npmjs.org/@oven/bun-darwin-aarch64/-/bun-darwin-aarch64-1.4.4-canary.20261105.1.tgz",
36
+ "integrity": "sha512-qEQgT/3BrHUFchVvCzEMIw/SrQTR+9waTTQo/FLdyOt5OiFUdTlKrHDjIS9w9XMK+b6UdA6iVJs+B2VVnWv6iw==",
37
+ "unpackedSize": 123685464
38
+ },
39
+ "win32-x64": {
40
+ "name": "@oven/bun-windows-x64",
41
+ "version": "1.4.4-canary.20261105.1+b1007f1",
42
+ "url": "https://registry.npmjs.org/@oven/bun-windows-x64/-/bun-windows-x64-1.4.4-canary.20261105.1.tgz",
43
+ "integrity": "sha512-cy0DX5IP29NyjhNvPyOYqlsThwJlaKOp/3YnT2W9CojTzgQm9leZHmNQEIu/Kbip4M3LTEWd6gtVL73HvCoR6Q==",
44
+ "unpackedSize": 149885955
45
+ },
46
+ "win32-arm64": {
47
+ "name": "@oven/bun-windows-aarch64",
48
+ "version": "1.4.4-canary.20261105.1+b1007f1",
49
+ "url": "https://registry.npmjs.org/@oven/bun-windows-aarch64/-/bun-windows-aarch64-1.4.4-canary.20261105.1.tgz",
50
+ "integrity": "sha512-CP7pwT7C7LzhiTBnPPzMpA9DQgP2fZBLuCweSJStZTuu3ig3qzwI3gvqZcInPD7ZtTLZ6oSXGCkSqBMlMpgJBw==",
51
+ "unpackedSize": 133801483
52
+ }
53
+ }
54
+ },
55
+ "rust": {
56
+ "version": "1.98.1",
57
+ "components": ["rustfmt", "clippy"]
58
+ },
59
+ "gpui": {
60
+ "repository": "https://github.com/zed-industries/zed",
61
+ "revision": "ba63a022c9a22ca66d68ef2a8f0bb9b5b0a9984d",
62
+ "version": "0.2.2"
63
+ }
64
+ }
@@ -0,0 +1,45 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2024",
4
+ "lib": ["ES2024"],
5
+ "module": "Preserve",
6
+ "moduleResolution": "Bundler",
7
+ "allowImportingTsExtensions": true,
8
+ "allowJs": true,
9
+ "checkJs": true,
10
+ "verbatimModuleSyntax": true,
11
+ "strict": true,
12
+ "noUncheckedIndexedAccess": true,
13
+ "exactOptionalPropertyTypes": true,
14
+ "useUnknownInCatchVariables": true,
15
+ "noImplicitOverride": true,
16
+ "noFallthroughCasesInSwitch": true,
17
+ "noUnusedLocals": true,
18
+ "noUnusedParameters": true,
19
+ "skipLibCheck": true,
20
+ "noEmit": true,
21
+ "types": ["bun"],
22
+ "jsx": "preserve",
23
+ "jsxImportSource": "@bun-native/solid"
24
+ },
25
+ "include": [
26
+ "packages/**/*.ts",
27
+ "scripts/**/*.ts",
28
+ "scripts/**/*.mjs",
29
+ "tests/**/*.ts",
30
+ "examples/**/*.ts",
31
+ "benchmarks/**/*.ts",
32
+ "tests/native/javascript.js",
33
+ "experiments/**/*.ts",
34
+ "packages/**/*.tsx",
35
+ "examples/**/*.tsx",
36
+ "tests/**/*.tsx",
37
+ "benchmarks/**/*.tsx"
38
+ ],
39
+ "exclude": [
40
+ "**/dist/**",
41
+ "**/node_modules/**",
42
+ "tests/fixtures/**",
43
+ "benchmarks/electrobun/app/**"
44
+ ]
45
+ }