@bun-native/runtime 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.
@@ -0,0 +1,46 @@
1
+ import { writeFile } from 'node:fs/promises'
2
+
3
+ /**
4
+ * The installed release this process was started as, from the variables that the verifying
5
+ * launcher sets (see docs/update-lifecycle.md). Undefined when the application was not started
6
+ * by that launcher, such as during development.
7
+ */
8
+ export interface InstalledRelease {
9
+ /** The preserved per-user data directory, kept across updates and rollbacks. */
10
+ readonly dataDirectory: string
11
+ /** The installation root. */
12
+ readonly root: string
13
+ /** The release's manifest SHA-256. */
14
+ readonly release: string
15
+ /** True while this release awaits its health check; see `reportHealthy`. */
16
+ readonly healthPending: boolean
17
+ }
18
+
19
+ const sha256 = /^[0-9a-f]{64}$/
20
+
21
+ export function installedRelease(): InstalledRelease | undefined {
22
+ const { BUN_NATIVE_DATA_DIR, BUN_NATIVE_INSTALL_ROOT, BUN_NATIVE_RELEASE } = process.env
23
+ if (!BUN_NATIVE_DATA_DIR || !BUN_NATIVE_INSTALL_ROOT || !sha256.test(BUN_NATIVE_RELEASE ?? ''))
24
+ return undefined
25
+ return {
26
+ dataDirectory: BUN_NATIVE_DATA_DIR,
27
+ root: BUN_NATIVE_INSTALL_ROOT,
28
+ release: BUN_NATIVE_RELEASE!,
29
+ healthPending: Boolean(process.env['BUN_NATIVE_HEALTH_FILE']),
30
+ }
31
+ }
32
+
33
+ /**
34
+ * Report that this release passed the application's own health check, such as a first
35
+ * window opening with its data loaded. The launcher then confirms the release, so a later
36
+ * start no longer counts toward the automatic rollback. Returns false when no health check
37
+ * is pending. Call it once the application is known to work; an update that never reports
38
+ * is rolled back after its allowed starts.
39
+ */
40
+ export async function reportHealthy(): Promise<boolean> {
41
+ const installed = installedRelease()
42
+ const file = process.env['BUN_NATIVE_HEALTH_FILE']
43
+ if (!installed?.healthPending || !file) return false
44
+ await writeFile(file, installed.release + '\n')
45
+ return true
46
+ }
@@ -0,0 +1,95 @@
1
+ import type { Command, NativeEvent } from '@bun-native/protocol'
2
+ import { appendFileSync } from 'node:fs'
3
+ import { isAbsolute } from 'node:path'
4
+
5
+ const MAX_RECORDS = 256
6
+
7
+ /** Explicit diagnostic mode only. This is not a request scheduler or transport proxy. */
8
+ export class StartupTrace {
9
+ private readonly started = process.hrtime.bigint()
10
+ private records = 0
11
+ private unavailable = false
12
+
13
+ private constructor(
14
+ private readonly path: string,
15
+ private readonly nonce: string
16
+ ) {}
17
+
18
+ static fromEnvironment(): StartupTrace | undefined {
19
+ if (process.env['BUN_NATIVE_STARTUP_TRACE'] !== '1') return
20
+ const path = process.env['BUN_NATIVE_EVIDENCE_LOG']
21
+ const nonce = process.env['BUN_NATIVE_EVIDENCE_NONCE']
22
+ if (
23
+ !path ||
24
+ !isAbsolute(path) ||
25
+ !nonce ||
26
+ Buffer.byteLength(nonce) > 128 ||
27
+ [...nonce].some((character) => {
28
+ const code = character.charCodeAt(0)
29
+ return code < 32 || (code >= 127 && code <= 159)
30
+ })
31
+ )
32
+ throw new Error('Native startup tracing requires an absolute evidence path and valid nonce')
33
+ return new StartupTrace(path, nonce)
34
+ }
35
+
36
+ request(phase: string, command: Command, hostPid: number): void {
37
+ this.write(phase, {
38
+ host_pid: hostPid,
39
+ request_id: command.id,
40
+ command: command.type,
41
+ ...('window_id' in command ? { window_id: command.window_id } : {}),
42
+ })
43
+ }
44
+
45
+ response(event: NativeEvent, hostPid: number): void {
46
+ if (!('id' in event)) return
47
+ this.write('response_received', {
48
+ host_pid: hostPid,
49
+ request_id: event.id,
50
+ response: event.type,
51
+ })
52
+ }
53
+
54
+ failure(error: Error, stderr: string, hostPid: number): void {
55
+ this.write(
56
+ 'transport_failed',
57
+ { host_pid: hostPid, error: error.message.slice(-1_024), stderr_tail: stderr },
58
+ true
59
+ )
60
+ }
61
+
62
+ finished(exitCode: number | null, stderr: string, hostPid: number): void {
63
+ this.write(
64
+ 'transport_finished',
65
+ { host_pid: hostPid, exit_code: exitCode, stderr_tail: stderr },
66
+ true
67
+ )
68
+ }
69
+
70
+ private write(phase: string, fields: Record<string, unknown>, terminal = false): void {
71
+ if (this.unavailable || (!terminal && this.records >= MAX_RECORDS)) return
72
+ if (!terminal && this.records === MAX_RECORDS - 1) {
73
+ phase = 'record_limit'
74
+ fields = { host_pid: fields['host_pid'], maximum_records: MAX_RECORDS }
75
+ }
76
+ const record = {
77
+ nonce: this.nonce,
78
+ stage: 'startup-diagnostic',
79
+ source: 'transport',
80
+ pid: process.pid,
81
+ clock: 'process_monotonic_relative',
82
+ elapsed_ns: String(process.hrtime.bigint() - this.started),
83
+ index: ++this.records,
84
+ phase,
85
+ ...fields,
86
+ }
87
+ try {
88
+ appendFileSync(this.path, JSON.stringify(record) + '\n', { mode: 0o600 })
89
+ } catch {
90
+ this.unavailable = true
91
+ // Do not replace the original transport result with a diagnostic I/O error.
92
+ console.error('Bun Native startup trace unavailable; evidence file could not be appended')
93
+ }
94
+ }
95
+ }