@solidrt/cli 0.0.8 → 0.0.10

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solidrt/cli",
3
- "version": "0.0.8",
3
+ "version": "0.0.10",
4
4
  "license": "MIT",
5
5
  "author": "Antoine van Wel",
6
6
  "type": "module",
@@ -16,15 +16,16 @@
16
16
  "@babel/core": "^7.28.5",
17
17
  "@babel/preset-typescript": "^7.28.5",
18
18
  "babel-preset-solid": "2.0.0-beta.14",
19
+ "bonjour-service": "^1.4.0",
19
20
  "qrcode-generator": "^2.0.4"
20
21
  },
21
22
  "optionalDependencies": {
22
- "@solidrt/darwin-arm64": "0.0.8",
23
- "@solidrt/linux-x64-gnu": "0.0.8",
24
- "@solidrt/win32-x64-msvc": "0.0.8"
23
+ "@solidrt/darwin-arm64": "0.0.10",
24
+ "@solidrt/linux-x64-gnu": "0.0.10",
25
+ "@solidrt/win32-x64-msvc": "0.0.10"
25
26
  },
26
27
  "peerDependencies": {
27
- "@solidrt/core": "0.0.8",
28
+ "@solidrt/core": "0.0.10",
28
29
  "typescript": "^5"
29
30
  },
30
31
  "devDependencies": {
package/src/args.ts CHANGED
@@ -5,6 +5,7 @@ export let { values, positionals } = parseArgs({
5
5
  dev: { type: "boolean", short: "d", default: false },
6
6
  minify: { type: "boolean", short: "m", default: false },
7
7
  compile: { type: "boolean", short: "c", default: false },
8
+ flux: { type: "boolean", short: "f", default: false },
8
9
  stdout: { type: "boolean", default: false },
9
10
  output: { type: "string", short: "o" },
10
11
  "proxy-files": { type: "boolean", default: false },
@@ -12,6 +13,8 @@ export let { values, positionals } = parseArgs({
12
13
  fps: { type: "string" },
13
14
  duration: { type: "string" },
14
15
  size: { type: "string" },
16
+ android: { type: "boolean", default: false },
17
+ device: { type: "string" },
15
18
  },
16
19
  allowPositionals: true,
17
20
  })
@@ -23,6 +26,40 @@ export let isTs = source?.endsWith(".ts") || source?.endsWith(".js")
23
26
  export let isSource = isTsx || isTs
24
27
  export let isPrebuilt = source?.endsWith(".srt.js") || source?.endsWith(".srt.bin")
25
28
 
29
+ function usage(line: string): never {
30
+ console.error("Usage: " + line)
31
+ process.exit(1)
32
+ }
33
+
34
+ // Per-command argument requirements. Called once before dispatch.
35
+ export function validateArgs() {
36
+ switch (command) {
37
+ case "bundle":
38
+ if (values.flux) {
39
+ if (!source || !isTs) usage("srt bundle --flux [options] <entry.[ts|js]>")
40
+ } else if (!source || (!isSource && !isPrebuilt)) {
41
+ usage("srt bundle [options] <entry.[tsx|jsx|ts|js|srt.js|srt.bin]>")
42
+ }
43
+ break
44
+ case "record":
45
+ if (!source || !isTsx) usage("srt record <entry.[tsx|jsx]>")
46
+ break
47
+ case "pack":
48
+ if (values.flux) {
49
+ if (!source || !isTs) usage("srt pack --flux [options] <entry.[ts|js]>")
50
+ } else if (!source || !isSource) {
51
+ usage("srt pack [options] <entry.[tsx|jsx|ts|js]>")
52
+ }
53
+ break
54
+ }
55
+
56
+ // --android installs/launches the client on a device; it is a client action,
57
+ // so it is only valid for `client`.
58
+ if (values.android && command !== "client") {
59
+ usage("srt client --android (--android is only valid with the client command)")
60
+ }
61
+ }
62
+
26
63
  export function printUsage() {
27
64
  console.error(`Usage: srt <command> [options] [file]
28
65
 
@@ -32,7 +69,7 @@ Commands:
32
69
  client Start solidrt-go client only
33
70
  bundle <file> Transpile TS/JS/TSX/JSX to JS or bytecode
34
71
  record <file.tsx|jsx> Capture frames for video generation
35
- pack <file.ts|js> Bundle + compile to a standalone executable (experimental, Flux only)
72
+ pack <file> Bundle + compile to a standalone executable (experimental)
36
73
 
37
74
  run/server options:
38
75
  --proxy-files Route file/dir access through the dev server
@@ -41,13 +78,23 @@ run/server options:
41
78
  run/client options:
42
79
  --size <WxH> Window size (default: 1280x720)
43
80
 
81
+ client options:
82
+ --android Install and launch the client on a connected Android device
83
+ --device <serial> Target a specific adb device by serial or unique prefix
84
+
44
85
  bundle options:
86
+ -f, --flux Bundle for the bare Flux runtime, without SolidJS (entry must be .ts|.js)
45
87
  -d, --dev Use development build of SolidJS (default: production)
46
88
  -m, --minify Minify the output
47
89
  -c, --compile Compile to bytecode
48
90
  -o, --output <name> Output filename
49
91
  --stdout Write bundle to stdout
50
92
 
93
+ pack options:
94
+ -f, --flux Pack for the bare Flux runtime instead of SolidRT (entry must be .ts|.js)
95
+ -m, --minify Minify the output
96
+ -o, --output <name> Output filename
97
+
51
98
  record options:
52
99
  --fps <N> Frames per second (default: 60)
53
100
  --duration <N> Duration in seconds (default: 1)
@@ -41,5 +41,30 @@ export function resolveBinary(name: string) {
41
41
  } catch {}
42
42
  }
43
43
 
44
+ return null
45
+ }
46
+
47
+ // The Android client APK is per-ABI (it bundles native .so for one architecture)
48
+ // and host-independent, so it lives under dist/android/<abi>/ rather than the
49
+ // host triple map. Only arm64-v8a is supported for now.
50
+ let ANDROID_ABI = "arm64-v8a"
51
+ let ANDROID_PKG = "@solidrt/android-arm64-v8a"
52
+
53
+ export function resolveApk() {
54
+ // 1. SRT_HOME: contributor checkout, where `make dist-android` stages the APK
55
+ // under dist/android/<abi>/.
56
+ let srtRoot = process.env.SRT_HOME
57
+ if (srtRoot) {
58
+ let apk = resolve(srtRoot, "dist/android", ANDROID_ABI, "solidrt-go.apk")
59
+ if (existsSync(apk)) return apk
60
+ }
61
+
62
+ // 2. Platform npm package
63
+ try {
64
+ let pkgDir = dirname(require.resolve(`${ANDROID_PKG}/package.json`))
65
+ let apk = resolve(pkgDir, "solidrt-go.apk")
66
+ if (existsSync(apk)) return apk
67
+ } catch {}
68
+
44
69
  return null
45
70
  }
package/src/bundler.ts ADDED
@@ -0,0 +1,117 @@
1
+ import { transformAsync } from "@babel/core"
2
+ import ts from "@babel/preset-typescript"
3
+ import solid from "babel-preset-solid"
4
+ import { type BunPlugin } from "bun"
5
+ import { values, source } from "./args"
6
+ import { state, print, requireBinary } from "./util"
7
+
8
+ // Bun build plugin that runs JSX/TSX through babel-preset-solid (universal
9
+ // generate, targeting @solidrt/core) plus the TS preset.
10
+ function solidPlugin(): BunPlugin {
11
+ return {
12
+ name: "bun-plugin-solid",
13
+ setup: (build) => {
14
+ build.onLoad({ filter: /\.(js|ts)x$/ }, async (args) => {
15
+ let file = Bun.file(args.path)
16
+ let code = await file.text()
17
+ let transforms = await transformAsync(code, {
18
+ filename: args.path,
19
+ presets: [[solid, { moduleName: "@solidrt/core", generate: "universal" }], [ts]],
20
+ })
21
+ return { contents: transforms?.code ?? "", loader: "js" }
22
+ })
23
+ },
24
+ }
25
+ }
26
+
27
+ export async function bundle(entry = source) {
28
+ let result = null
29
+
30
+ let devBase = state.serverUrl ?? undefined
31
+ let dev = !!devBase || values.dev
32
+ print(`[cli] Bundling (${dev ? "development" : "production"})`)
33
+ let define: Record<string, string> = {
34
+ "process.env.NODE_ENV": dev ? "development" : "production",
35
+ }
36
+ if (devBase) define.__SRT_DEV_BASE__ = devBase
37
+
38
+ try {
39
+ result = await Bun.build({
40
+ entrypoints: [entry!],
41
+ target: "browser",
42
+ format: "esm",
43
+ minify: values.minify,
44
+ external: ["flux:*", "srt:*"],
45
+ define,
46
+ plugins: [solidPlugin()],
47
+ })
48
+ } catch (e) {
49
+ console.error("[cli] compile error:\n", e)
50
+ return null
51
+ }
52
+
53
+ if (result?.success) {
54
+ return result
55
+ }
56
+
57
+ if (result) {
58
+ for (let msg of result?.logs) console.error(msg)
59
+ }
60
+ return null
61
+ }
62
+
63
+ export async function bundleTo(outfile: string) {
64
+ let result = await bundle()
65
+ if (!result) {
66
+ console.error("Build failed")
67
+ process.exit(1)
68
+ }
69
+ for (let output of result.outputs) {
70
+ await Bun.write(outfile, output)
71
+ }
72
+ return result
73
+ }
74
+
75
+ // Bundle for the bare Flux runtime: no Solid plugin, flux: modules stay external.
76
+ export async function bundleFlux(entry: string): Promise<string> {
77
+ let result = await Bun.build({
78
+ entrypoints: [entry],
79
+ target: "browser",
80
+ format: "esm",
81
+ minify: values.minify,
82
+ external: ["flux:*"],
83
+ })
84
+ if (!result.success) {
85
+ for (let msg of result.logs) console.error(msg)
86
+ console.error("Build failed")
87
+ process.exit(1)
88
+ }
89
+ let jsCode = ""
90
+ for (let output of result.outputs) jsCode += await output.text()
91
+ return jsCode
92
+ }
93
+
94
+ // Bundle for the SolidRT runtime via the standard Solid-aware bundler.
95
+ export async function bundleSolid(): Promise<string> {
96
+ let result = await bundle()
97
+ if (!result) {
98
+ console.error("Build failed")
99
+ process.exit(1)
100
+ }
101
+ let jsCode = ""
102
+ for (let output of result.outputs) jsCode += await output.text()
103
+ return jsCode
104
+ }
105
+
106
+ // Compile JS source to QuickJS bytecode via the fluxc binary.
107
+ export async function compileToBytecode(jsCode: string): Promise<Buffer> {
108
+ let compiler = requireBinary("fluxc")
109
+ let proc = Bun.spawn([compiler], {
110
+ stdin: new Blob([jsCode]),
111
+ stdout: "pipe",
112
+ stderr: "inherit",
113
+ })
114
+ let [bytecode, code] = await Promise.all([new Response(proc.stdout).arrayBuffer(), proc.exited])
115
+ if (code !== 0) process.exit(code)
116
+ return Buffer.from(bytecode)
117
+ }
package/src/cache.ts CHANGED
@@ -46,6 +46,21 @@ export function isEnabled(): boolean {
46
46
  return enabled
47
47
  }
48
48
 
49
+ // Strings returned by bun:sqlite carry an internal representation that
50
+ // Headers.set() rejects, even when value-identical to an acceptable string
51
+ // (Bun bug, present through 1.3.14). Rebuilding each char yields a clean string.
52
+ function reflatten(s: string): string {
53
+ return Array.from(s, (c) => String.fromCharCode(c.charCodeAt(0))).join("")
54
+ }
55
+
56
+ function reflattenHeaders(obj: Record<string, string>): Record<string, string> {
57
+ let out: Record<string, string> = {}
58
+ for (let key in obj) {
59
+ out[reflatten(key)] = reflatten(obj[key]!)
60
+ }
61
+ return out
62
+ }
63
+
49
64
  function keyFor(method: string, url: string): string {
50
65
  let h = createHash("sha256")
51
66
  h.update(method)
@@ -101,7 +116,7 @@ export function get(method: string, url: string): Entry | null {
101
116
  method: row.method,
102
117
  url: row.url,
103
118
  status: row.status,
104
- headers: JSON.parse(row.headers),
119
+ headers: reflattenHeaders(JSON.parse(row.headers)),
105
120
  body: row.body,
106
121
  cachedAt: row.cached_at,
107
122
  }
@@ -0,0 +1,84 @@
1
+ import { values, source, isPrebuilt } from "../args"
2
+ import { bundle, bundleTo, bundleFlux, compileToBytecode } from "../bundler"
3
+ import { resolve } from "path"
4
+
5
+ // Write to stdout and resolve only once the whole payload is flushed.
6
+ // process.stdout.write to a pipe is async and applies backpressure; the
7
+ // callback fires after every byte is drained, so it is safe to exit after.
8
+ function writeStdout(data: string): Promise<void> {
9
+ return new Promise((resolve, reject) => {
10
+ process.stdout.write(data, (err) => (err ? reject(err) : resolve()))
11
+ })
12
+ }
13
+
14
+ // Compile JS to a .srt.bin file and report its size.
15
+ async function writeBytecode(jsCode: string, outfile: string) {
16
+ let bytecode = await compileToBytecode(jsCode)
17
+ await Bun.write(outfile, bytecode)
18
+ let binSize = (await Bun.file(outfile).stat()).size
19
+ console.log(`>> wrote ${binSize} bytes to ${outfile}`)
20
+ }
21
+
22
+ export async function runBundleCommand() {
23
+ if (values.flux) {
24
+ let baseName = values.output ?? source!.replace(/\.[jt]s$/, "")
25
+ let jsCode = await bundleFlux(source!)
26
+
27
+ if (values.stdout) {
28
+ await writeStdout(jsCode)
29
+ } else if (values.compile) {
30
+ await writeBytecode(jsCode, baseName + ".flux.bin")
31
+ } else {
32
+ let outfile = baseName + ".flux.js"
33
+ await Bun.write(outfile, jsCode)
34
+ console.log(`>> wrote ${jsCode.length} bytes to ${outfile}`)
35
+ }
36
+ process.exit()
37
+ }
38
+
39
+ if (isPrebuilt) {
40
+ if (!source!.endsWith(".srt.js")) {
41
+ console.error("Can only compile .srt.js files. .srt.bin is already compiled.")
42
+ process.exit(1)
43
+ }
44
+ let jsFile = resolve(source!)
45
+ let binOut = jsFile.replace(/\.srt\.js$/, ".srt.bin").replace(/\.js$/, ".bin")
46
+ await writeBytecode(await Bun.file(jsFile).text(), binOut)
47
+ process.exit()
48
+ }
49
+
50
+ let baseName = values.output ?? source!.replace(/\.[jt]sx?$/, "")
51
+
52
+ if (values.stdout) {
53
+ let result = await bundle()
54
+ if (!result) {
55
+ console.error("Build failed")
56
+ process.exit(1)
57
+ }
58
+ for (let output of result.outputs) {
59
+ await writeStdout(await output.text())
60
+ }
61
+ process.exit()
62
+ }
63
+
64
+ if (values.compile) {
65
+ let result = await bundle()
66
+ if (!result) {
67
+ console.error("Build failed")
68
+ process.exit(1)
69
+ }
70
+ let jsCode = ""
71
+ for (let output of result.outputs) {
72
+ jsCode += await output.text()
73
+ }
74
+ await writeBytecode(jsCode, baseName + ".srt.bin")
75
+ process.exit()
76
+ }
77
+
78
+ let jsOutfile = baseName + ".srt.js"
79
+ let result = await bundleTo(jsOutfile)
80
+ for (let output of result.outputs) {
81
+ console.log(`>> wrote ${output.size} bytes to ${jsOutfile}`)
82
+ }
83
+ process.exit()
84
+ }
@@ -0,0 +1,22 @@
1
+ import { values } from "../args"
2
+ import { requireBinary, run } from "../util"
3
+ import { spawnAndroidClient } from "../dev-android"
4
+
5
+ // Standalone solidrt-go client (no dev server). The `run` command instead uses
6
+ // spawnClient() to launch a client tied to the dev-server lifecycle. Either way
7
+ // the client discovers a dev server over the LAN; with --android it is installed
8
+ // and launched on a connected Android device instead of run locally.
9
+ export async function runClientCommand() {
10
+ if (values.android) {
11
+ await spawnAndroidClient()
12
+ return
13
+ }
14
+
15
+ let runner = requireBinary("solidrt-go")
16
+ let args: string[] = []
17
+ if (values.size) args.push("--size", values.size)
18
+ //TODO add dev server connection
19
+ // if (source) args.push("--dev-server", source)
20
+ let exit = await run(runner, args)
21
+ process.exit(exit)
22
+ }
@@ -0,0 +1,25 @@
1
+ import { values, source } from "../args"
2
+ import { bundleFlux, bundleSolid } from "../bundler"
3
+ import { packRunner } from "../packer"
4
+
5
+ // Write the packed executable, mark it runnable, and report its size.
6
+ async function writeExecutable(packed: Buffer, outfile: string) {
7
+ await Bun.write(outfile, packed)
8
+ if (process.platform !== "win32") {
9
+ Bun.spawnSync(["chmod", "+x", outfile])
10
+ }
11
+ console.log(`>> wrote ${packed.length} bytes to ${outfile}`)
12
+ }
13
+
14
+ export async function runPackCommand() {
15
+ let outfile = values.output ?? source!.replace(/\.[jt]sx?$/, "")
16
+ // On Windows the packed image is a PE executable; it needs a .exe name to run.
17
+ if (process.platform === "win32" && !outfile.toLowerCase().endsWith(".exe")) {
18
+ outfile += ".exe"
19
+ }
20
+ let packed = values.flux
21
+ ? await packRunner("fluxrt", await bundleFlux(source!))
22
+ : await packRunner("solidrt", await bundleSolid())
23
+ await writeExecutable(packed, outfile)
24
+ process.exit()
25
+ }
@@ -0,0 +1,16 @@
1
+ import { source, values } from "../args"
2
+ import { requireBinary, run } from "../util"
3
+ import { bundleTo } from "../bundler"
4
+ import { resolve } from "path"
5
+
6
+ export async function runRecordCommand() {
7
+ let jsOutfile = source!.replace(/\.[jt]sx$/, "") + ".srt.js"
8
+ await bundleTo(jsOutfile)
9
+ let runner = requireBinary("solidrt-go")
10
+ let recordArgs = ["--record", resolve(jsOutfile)]
11
+ if (values.fps) recordArgs.push("--fps", values.fps)
12
+ if (values.duration) recordArgs.push("--duration", values.duration)
13
+ if (values.size) recordArgs.push("--size", values.size)
14
+ let exit = await run(runner, recordArgs)
15
+ process.exit(exit)
16
+ }
@@ -0,0 +1,45 @@
1
+ import pkg from "../../package.json"
2
+ import { source, isSource, isPrebuilt, values } from "../args"
3
+ import { state, shutdown } from "../util"
4
+ import { bundle } from "../bundler"
5
+ import { startServer } from "../dev-server"
6
+ import { startRepl } from "../repl"
7
+ import { startWatcher } from "../watcher"
8
+ import * as cache from "../cache"
9
+ import { resolve, dirname } from "path"
10
+
11
+ // Brings up the dev server (HTTP/WS + initial bundle + repl + watcher). The
12
+ // `run` command spawns a local client on top of this from main.ts.
13
+ export async function runServerCommand() {
14
+ // Initialize state from args
15
+ state.source = source
16
+ state.sourceDir = source ? dirname(resolve(source)) : process.cwd()
17
+
18
+ if (values["proxy-http"]) {
19
+ cache.initCache({ dir: process.cwd() })
20
+ console.log("[cli] HTTP cache enabled")
21
+ }
22
+
23
+ startServer()
24
+
25
+ // Bundle initial code if source file given (after server start so the
26
+ // dev base URL is available to the bundler).
27
+ if (source && isSource) {
28
+ let initialResult = await bundle()
29
+ if (initialResult) {
30
+ for (let output of initialResult.outputs) {
31
+ state.currentCode = await output.text()
32
+ }
33
+ }
34
+ } else if (source && isPrebuilt && source.endsWith(".srt.js")) {
35
+ state.currentCode = await Bun.file(resolve(source)).text()
36
+ }
37
+
38
+ process.on("SIGINT", shutdown)
39
+ process.on("SIGTERM", shutdown)
40
+
41
+ let version = pkg.version === "0.0.0" ? "" : " version " + pkg.version
42
+ console.log(`[cli] Welcome to SolidRT${version}!`)
43
+ startRepl()
44
+ startWatcher()
45
+ }
@@ -0,0 +1,108 @@
1
+ import { print, requireAdb } from "./util"
2
+ import { resolveApk } from "./artifacts"
3
+ import { values } from "./args"
4
+ import { DEV_PORT } from "./dev-server"
5
+
6
+ // Launch component of the "go" dev-client flavor (see lattice/Makefile.android).
7
+ let PACKAGE_ACTIVITY = "com.solidrt.go/com.solidrt.app.MainActivity"
8
+
9
+ // Forward the device's loopback DEV_PORT to the host dev server, so the client
10
+ // reaches it at 127.0.0.1:DEV_PORT (see lattice/src/go/connection.rs). This is
11
+ // the adb-reverse path: it works for the emulator (behind NAT, cannot reach the
12
+ // host via LAN mDNS discovery) and for USB-tethered devices alike, and is
13
+ // harmless on any adb connection. (Android only uses this path; the client's
14
+ // mDNS discovery is desktop-only -- see lattice/src/go/connection.rs.)
15
+ function setupAdbReverse(adb: string, target: string) {
16
+ print(`[cli] Forwarding 127.0.0.1:${DEV_PORT} on ${target} to host dev server`)
17
+ let res = Bun.spawnSync([adb, "-s", target, "reverse", `tcp:${DEV_PORT}`, `tcp:${DEV_PORT}`], {
18
+ stdout: "pipe",
19
+ stderr: "pipe",
20
+ })
21
+ if (res.exitCode !== 0) {
22
+ print(`[cli] adb reverse failed (client will fall back to discovery):\n${res.stderr.toString()}`)
23
+ }
24
+ }
25
+
26
+ // Serials of connected, authorized devices (excludes offline/unauthorized).
27
+ function listDevices(adb: string): string[] {
28
+ let listed = Bun.spawnSync([adb, "devices"], { stdout: "pipe", stderr: "pipe" })
29
+ return listed.stdout
30
+ .toString()
31
+ .split("\n")
32
+ .slice(1)
33
+ .map((l) => l.trim())
34
+ .filter(Boolean)
35
+ .filter((l) => l.endsWith("\tdevice"))
36
+ .map((l) => l.split("\t")[0])
37
+ .filter((s): s is string => Boolean(s))
38
+ }
39
+
40
+ // Resolve the target device serial. With --device, treat the value as a serial
41
+ // prefix and require it to match exactly one connected device; without it, use
42
+ // the sole connected device. Exits with a clear message on any ambiguity.
43
+ function resolveTarget(adb: string): string {
44
+ let devices = listDevices(adb)
45
+
46
+ if (values.device) {
47
+ let prefix = values.device
48
+ let matches = devices.filter((d) => d.startsWith(prefix))
49
+ if (matches.length > 1) {
50
+ console.error(`--device "${prefix}" is ambiguous; matches: ${matches.join(", ")}`)
51
+ process.exit(1)
52
+ }
53
+ let [match] = matches
54
+ if (!match) {
55
+ console.error(`No connected device matches --device "${prefix}". Connected: ${devices.join(", ") || "none"}`)
56
+ process.exit(1)
57
+ }
58
+ return match
59
+ }
60
+
61
+ if (devices.length > 1) {
62
+ console.error(`Multiple devices connected (${devices.join(", ")}); pick one with --device <serial or prefix>.`)
63
+ process.exit(1)
64
+ }
65
+ let [only] = devices
66
+ if (!only) {
67
+ console.error("No authorized Android device found. Enable USB debugging and check `adb devices`.")
68
+ process.exit(1)
69
+ }
70
+ return only
71
+ }
72
+
73
+ // Install + launch the Android client on a connected device over adb, forwarding
74
+ // its loopback to the host dev server so the client connects at 127.0.0.1 (see
75
+ // setupAdbReverse). Fire-and-forget: the client's lifecycle is tracked via WS
76
+ // connect/disconnect in dev-server.ts, not as a child process here.
77
+ export async function spawnAndroidClient() {
78
+ let adb = requireAdb()
79
+
80
+ let apk = resolveApk()
81
+ if (!apk) {
82
+ console.error("Could not find the SolidRT-Go APK.")
83
+ console.error("Add it with: bun add -d @solidrt/android-arm64-v8a")
84
+ process.exit(1)
85
+ }
86
+
87
+ let target = resolveTarget(adb)
88
+
89
+ print(`[cli] Installing SolidRT-Go on ${target}`)
90
+ let install = Bun.spawn([adb, "-s", target, "install", "-r", apk], { stdout: "pipe", stderr: "pipe" })
91
+ if ((await install.exited) !== 0) {
92
+ console.error("adb install failed:\n" + (await new Response(install.stderr).text()))
93
+ process.exit(1)
94
+ }
95
+
96
+ setupAdbReverse(adb, target)
97
+
98
+ let start = Bun.spawn([adb, "-s", target, "shell", "am", "start", "-n", PACKAGE_ACTIVITY], {
99
+ stdout: "pipe",
100
+ stderr: "pipe",
101
+ })
102
+ if ((await start.exited) !== 0) {
103
+ console.error("adb start failed:\n" + (await new Response(start.stderr).text()))
104
+ process.exit(1)
105
+ }
106
+
107
+ print(`[cli] Launched SolidRT-Go on ${target}; waiting for it to connect to the dev server...`)
108
+ }
@@ -1,4 +1,5 @@
1
- import { DEV_HOST, DEV_PORT, state, print, requireBinary } from "./util"
1
+ import { state, print, requireBinary } from "./util"
2
+ import { DEV_HOST, DEV_PORT } from "./dev-server"
2
3
  import { values } from "./args"
3
4
 
4
5
  function pipeAbovePrompt(stream: ReadableStream<Uint8Array>, out: NodeJS.WriteStream) {
@@ -1,11 +1,26 @@
1
1
  import { resolve } from "path"
2
2
  import { stat as fsStat, readdir } from "node:fs/promises"
3
3
  import { networkInterfaces } from "node:os"
4
- import { createSocket } from "node:dgram"
4
+ import { Bonjour } from "bonjour-service"
5
5
  import qrcode from "qrcode-generator"
6
- import { DEV_HOST, DEV_PORT, state, print, buildReload } from "./util"
6
+ import { state, print } from "./util"
7
+ import { values } from "./args"
7
8
  import * as cache from "./cache"
8
9
 
10
+ export const DEV_HOST = "127.0.0.1"
11
+ export const DEV_PORT = 15194
12
+
13
+ // Dev-server WS protocol helpers: the reload message shape and the stop broadcast.
14
+ export function buildReload(payload: { code?: string | null; bytecode?: string }) {
15
+ return JSON.stringify({ type: "reload", proxyFiles: values["proxy-files"], proxyHttp: values["proxy-http"], ...payload })
16
+ }
17
+
18
+ export function broadcastStop() {
19
+ for (let ws of state.clients.keys()) {
20
+ ws.send(JSON.stringify({ type: "stop" }))
21
+ }
22
+ }
23
+
9
24
  function headersToObject(h: Headers): Record<string, string> {
10
25
  let out: Record<string, string> = {}
11
26
  h.forEach((v, k) => {
@@ -13,7 +28,6 @@ function headersToObject(h: Headers): Record<string, string> {
13
28
  })
14
29
  return out
15
30
  }
16
-
17
31
  async function handleProxy(req: Request): Promise<Response> {
18
32
  let target = req.headers.get("x-srt-proxy-url")
19
33
  if (!target) {
@@ -141,6 +155,9 @@ export function startServer() {
141
155
  open(ws) {
142
156
  state.clients.set(ws, { platform: "unknown", version: "unknown" })
143
157
  print(`[cli] Client connected ${ws.remoteAddress}`)
158
+ // Advertise our real LAN address so clients dialed over the adb loopback
159
+ // can show/remember the directly reachable address (see connection.rs).
160
+ ws.send(JSON.stringify({ type: "welcome", address: state.serverUrl }))
144
161
  if (state.currentCode) {
145
162
  ws.send(buildReload({ code: state.currentCode }))
146
163
  }
@@ -184,31 +201,43 @@ export function startServer() {
184
201
  qr.addData(serverUrl)
185
202
  qr.make()
186
203
  let modCount = qr.getModuleCount()
187
- for (let y = 0; y < modCount; y += 2) {
188
- let row = " "
189
- for (let x = 0; x < modCount; x++) {
190
- let top = qr.isDark(y, x)
191
- let bot = y + 1 < modCount && qr.isDark(y + 1, x)
204
+ // Render as a white tile with black modules (explicit ANSI colors) plus the
205
+ // 4-module quiet zone the QR spec requires. Drawing with the terminal's
206
+ // default foreground inverts the code on dark themes, which standard
207
+ // decoders reject.
208
+ const QR_INK = "\x1b[30;107m" // black modules on bright-white tile (tile = background)
209
+ const QR_TILE_FG = "\x1b[97m" // bright-white as foreground over the default background
210
+ const QR_RESET = "\x1b[0m"
211
+ const QUIET_ZONE = 2 // modules (spec says 4, but scanners cope and it reads tighter)
212
+ let qrWidth = modCount + 2 * QUIET_ZONE
213
+ let dark = (y: number, x: number) => y >= 0 && y < modCount && x >= 0 && x < modCount && qr.isDark(y, x)
214
+ // modCount is always odd, so the tile is a half-line taller than an even row
215
+ // count. The loop packs two module-rows per line via half-blocks and stops on
216
+ // the last content row, leaving the bottom quiet zone half a line short of the
217
+ // full-line top quiet zone.
218
+ for (let y = -QUIET_ZONE; y < modCount + QUIET_ZONE - 1; y += 2) {
219
+ let row = " " + QR_INK
220
+ for (let x = -QUIET_ZONE; x < modCount + QUIET_ZONE; x++) {
221
+ let top = dark(y, x)
222
+ let bot = dark(y + 1, x)
192
223
  row += top && bot ? "\u2588" : top ? "\u2580" : bot ? "\u2584" : " "
193
224
  }
194
- console.log(row)
225
+ console.log(row + QR_RESET)
195
226
  }
227
+ // Close that gap with a half-height tile line: upper half painted in the tile
228
+ // color (foreground), lower half the terminal background. The 0.5 here plus
229
+ // the 0.5 already under the last content row equal the full-line top margin.
230
+ console.log(" " + QR_TILE_FG + "\u2580".repeat(qrWidth) + QR_RESET)
196
231
 
197
232
  console.log("")
198
233
  console.log(`[cli] WebSocket server on ws://${serverUrl}`)
199
234
 
200
- // UDP discovery
201
- let udp = createSocket("udp4")
202
- udp.on("message", (msg, rinfo) => {
203
- if (msg.toString() === "SRT_DISCOVER") {
204
- print(`[cli] Discovery request from ${rinfo.address}:${rinfo.port}`)
205
- udp.send("SRT_SERVER", rinfo.port, rinfo.address)
206
- }
207
- })
208
- udp.bind(DEV_PORT, () => {
209
- udp.setBroadcast(true)
210
- print("[cli] UDP discovery listener on port " + DEV_PORT)
211
- })
235
+ // LAN discovery: advertise the dev server as a DNS-SD service so go clients on
236
+ // the same network can find it (see lattice/src/go/connection.rs). Stored on
237
+ // state so shutdown() can send the mDNS goodbye.
238
+ state.bonjour = new Bonjour()
239
+ state.bonjour.publish({ name: "SolidRT Dev Server", type: "solidrt", protocol: "tcp", port: DEV_PORT })
240
+ print(`[cli] Advertising _solidrt._tcp on port ${DEV_PORT} via mDNS`)
212
241
 
213
242
  // Keepalive
214
243
  setInterval(() => {
package/src/main.ts CHANGED
@@ -1,52 +1,16 @@
1
1
  #!/usr/bin/env bun
2
2
 
3
- // Build/run script that bundles/runs a Solid-RT app for the QuickJS runtime.
4
- //
5
- // Usage:
6
- // srt run - start dev server + client
7
- // srt run examples/hello.tsx - start dev server + client, bundle + push via WS
8
- // srt server examples/hello.tsx - start dev server only, no client
9
- // srt client - start dev client only (connects to WS server)
10
- // srt bundle examples/hello.tsx - bundle TSX to .srt.js
11
- // srt bundle -c examples/hello.tsx - bundle TSX to .srt.js + compile to .srt.bin
12
- // srt bundle examples/hello.srt.js - compile .srt.js to .srt.bin
13
- // srt record examples/hello.tsx - bundle TSX and run with frame capture
14
-
15
- import pkg from "../package.json"
16
- import { values, command, source, isTsx, isTs, isSource, isPrebuilt, printUsage } from "./args"
17
- import { state, requireBinary, run, shutdown } from "./util"
18
- import { bundle, bundleTo, runBundleCommand } from "./bundle"
19
- import { runPackCommand } from "./pack"
20
- import { startServer } from "./server"
21
- import { spawnClient } from "./client"
22
- import { startRepl } from "./repl"
23
- import { startWatcher } from "./watcher"
24
- import * as cache from "./cache"
25
- import { resolve, dirname } from "path"
3
+ import { values, command, validateArgs, printUsage } from "./args"
4
+ import { runBundleCommand } from "./commands/bundle"
5
+ import { runPackCommand } from "./commands/pack"
6
+ import { runRecordCommand } from "./commands/record"
7
+ import { runServerCommand } from "./commands/server"
8
+ import { runClientCommand } from "./commands/client"
9
+ import { spawnClient } from "./dev-client"
26
10
 
27
11
  // -- Validate args --
28
12
 
29
- let COMMANDS = ["run", "server", "client", "bundle", "record", "pack"]
30
-
31
- if (!command || !COMMANDS.includes(command)) {
32
- printUsage()
33
- process.exit(1)
34
- }
35
-
36
- if (command === "bundle" && (!source || (!isSource && !isPrebuilt))) {
37
- console.error("Usage: srt bundle [options] <entry.[tsx|jsx|ts|js|srt.js|srt.bin]>")
38
- process.exit(1)
39
- }
40
-
41
- if (command === "record" && (!source || !isTsx)) {
42
- console.error("Usage: srt record <entry.[tsx|jsx]>")
43
- process.exit(1)
44
- }
45
-
46
- if (command === "pack" && (!source || !isTs)) {
47
- console.error("Usage: srt pack [options] <entry.[ts|js]>")
48
- process.exit(1)
49
- }
13
+ validateArgs()
50
14
 
51
15
  // Force the production export condition for prod bundles. Bun auto-activates the
52
16
  // "development" condition whenever NODE_ENV != "production" (read once at startup),
@@ -69,76 +33,22 @@ if (isProdBuild && process.env.NODE_ENV !== "production") {
69
33
  process.exit(proc.exitCode ?? 0)
70
34
  }
71
35
 
72
- // -- Bundle command --
36
+ // -- Dispatch --
73
37
 
74
38
  if (command === "bundle") {
75
39
  await runBundleCommand()
76
- }
77
-
78
- if (command === "pack") {
40
+ } else if (command === "pack") {
79
41
  await runPackCommand()
80
- }
81
-
82
- // -- Record command --
83
-
84
- if (command === "record") {
85
- let jsOutfile = source!.replace(/\.[jt]sx$/, "") + ".srt.js"
86
- await bundleTo(jsOutfile)
87
- let runner = requireBinary("solidrt-go")
88
- let recordArgs = ["--record", resolve(jsOutfile)]
89
- if (values.fps) recordArgs.push("--fps", values.fps)
90
- if (values.duration) recordArgs.push("--duration", values.duration)
91
- if (values.size) recordArgs.push("--size", values.size)
92
- let exit = await run(runner, recordArgs)
93
- process.exit(exit)
94
- }
95
-
96
- // -- Client command --
97
-
98
- if (command === "client") {
99
- let runner = requireBinary("solidrt-go")
100
- let args: string[] = []
101
- if (values.size) args.push("--size", values.size)
102
- //TODO add dev server connection
103
- // if (source) args.push("--dev-server", source)
104
- let exit = await run(runner, args)
105
- process.exit(exit)
106
- }
107
-
108
- // -- Server / Run command --
109
-
110
- // Initialize state from args
111
- state.source = source
112
- state.sourceDir = source ? dirname(resolve(source)) : process.cwd()
113
-
114
- if (values["proxy-http"]) {
115
- cache.initCache({ dir: process.cwd() })
116
- console.log("[cli] HTTP cache enabled")
117
- }
118
-
119
- startServer()
120
-
121
- // Bundle initial code if source file given (after server start so the
122
- // dev base URL is available to the bundler).
123
- if (source && isSource) {
124
- let initialResult = await bundle()
125
- if (initialResult) {
126
- for (let output of initialResult.outputs) {
127
- state.currentCode = await output.text()
128
- }
129
- }
130
- } else if (source && isPrebuilt && source.endsWith(".srt.js")) {
131
- state.currentCode = await Bun.file(resolve(source)).text()
132
- }
133
-
134
- if (command === "run") {
42
+ } else if (command === "record") {
43
+ await runRecordCommand()
44
+ } else if (command === "client") {
45
+ await runClientCommand()
46
+ } else if (command === "server") {
47
+ await runServerCommand()
48
+ } else if (command === "run") {
49
+ await runServerCommand()
135
50
  spawnClient()
136
- }
137
-
138
- process.on("SIGINT", shutdown)
139
- process.on("SIGTERM", shutdown)
140
-
141
- let version = pkg.version === "0.0.0" ? "" : " version " + pkg.version
142
- console.log(`[cli] Welcome to SolidRT${version}!`)
143
- startRepl()
144
- startWatcher()
51
+ } else {
52
+ printUsage()
53
+ process.exit(1)
54
+ }
package/src/packer.ts ADDED
@@ -0,0 +1,27 @@
1
+ import { requireBinary } from "./util"
2
+ import { compileToBytecode } from "./bundler"
3
+
4
+ // Trailer magic identifying the runner an embedded payload belongs to. Must match
5
+ // the runner-side checks: fluxrt -> flux/src/bin/fluxrt.rs, solidrt ->
6
+ // lattice/src/main.rs (load_embedded_bytecode).
7
+ const MAGIC = {
8
+ fluxrt: Buffer.from([0x46, 0x4c, 0x55, 0x58, 0x52, 0x54, 0x88, 0x44]), // "FLUXRT\x88\x44"
9
+ solidrt: Buffer.from([0x53, 0x4f, 0x4c, 0x49, 0x44, 0x52, 0x54, 0x88, 0x44]), // "SOLIDRT\x88\x44"
10
+ }
11
+
12
+ export type Runner = keyof typeof MAGIC
13
+
14
+ // Compile JS to bytecode and append it to the runner binary, followed by a
15
+ // trailer of [u64 offset LE][8-byte magic]. The runner reads its own image at
16
+ // startup, validates the magic, and slices the bytecode back out.
17
+ export async function packRunner(runner: Runner, jsCode: string): Promise<Buffer> {
18
+ let bytecode = await compileToBytecode(jsCode)
19
+
20
+ let runnerPath = requireBinary(runner)
21
+ let runnerBytes = Buffer.from(await Bun.file(runnerPath).arrayBuffer())
22
+
23
+ let offsetBuf = Buffer.allocUnsafe(8)
24
+ offsetBuf.writeBigUInt64LE(BigInt(runnerBytes.length))
25
+
26
+ return Buffer.concat([runnerBytes, bytecode, offsetBuf, MAGIC[runner]])
27
+ }
package/src/repl.ts CHANGED
@@ -1,8 +1,9 @@
1
1
  import { createInterface } from "node:readline"
2
2
  import { resolve, dirname } from "path"
3
3
  import { readdirSync } from "node:fs"
4
- import { state, print, printErr, broadcastStop, buildReload, shutdown } from "./util"
5
- import { bundle } from "./bundle"
4
+ import { state, print, printErr, shutdown } from "./util"
5
+ import { buildReload, broadcastStop } from "./dev-server"
6
+ import { bundle } from "./bundler"
6
7
  import { startWatcher, stopWatcher } from "./watcher"
7
8
 
8
9
  function cmdStop(args: string) {
package/src/util.ts CHANGED
@@ -1,10 +1,9 @@
1
- import { resolveBinary } from "./native"
2
- import { values } from "./args"
1
+ import { resolveBinary } from "./artifacts"
2
+ import { existsSync } from "node:fs"
3
+ import { resolve } from "node:path"
3
4
  import type { Interface as ReadlineInterface } from "node:readline"
4
5
  import type { Server as BunServer } from "bun"
5
-
6
- export const DEV_HOST = "127.0.0.1"
7
- export const DEV_PORT = 15194
6
+ import type { Bonjour } from "bonjour-service"
8
7
 
9
8
  export let state = {
10
9
  clients: new Map<any, { platform: string; version: string }>(),
@@ -15,13 +14,53 @@ export let state = {
15
14
  server: null as BunServer<undefined> | null,
16
15
  serverUrl: null as string | null,
17
16
  rl: null as ReadlineInterface | null,
17
+ bonjour: null as Bonjour | null,
18
+ }
19
+
20
+ // Build target per binary, for the "not found" hint. Run from the repo root.
21
+ let BUILD_HINTS: Record<string, string> = {
22
+ "solidrt-go": "make client",
23
+ solidrt: "make runtime",
24
+ flux: "make -C flux flux",
25
+ fluxc: "make -C flux fluxc",
26
+ fluxrt: "make -C flux fluxrt PROFILE=release-opt",
18
27
  }
19
28
 
20
29
  export function requireBinary(name: string) {
21
30
  let path = resolveBinary(name)
22
31
  if (path) return path
32
+ let hint = BUILD_HINTS[name]
23
33
  console.error(`Could not find ${name} binary.`)
24
- console.error("Build from source: run make solidrt-go, then set SRT_HOME=<SolidRT project home>")
34
+ if (hint) {
35
+ console.error(`Build it from source: run ${hint}, with SRT_HOME pointing at your SolidRT checkout.`)
36
+ } else {
37
+ console.error("Build it from source, with SRT_HOME pointing at your SolidRT checkout.")
38
+ }
39
+ process.exit(1)
40
+ }
41
+
42
+ // adb is a system tool (Android Platform Tools), never bundled. Look on PATH
43
+ // first, then the standard SDK location.
44
+ export function resolveAdb() {
45
+ let exe = process.platform === "win32" ? "adb.exe" : "adb"
46
+ let onPath = Bun.which(exe)
47
+ if (onPath) return onPath
48
+ for (let root of [process.env.ANDROID_HOME, process.env.ANDROID_SDK_ROOT]) {
49
+ if (!root) continue
50
+ let candidate = resolve(root, "platform-tools", exe)
51
+ if (existsSync(candidate)) return candidate
52
+ }
53
+ return null
54
+ }
55
+
56
+ export function requireAdb() {
57
+ let path = resolveAdb()
58
+ if (path) return path
59
+ console.error("Could not find adb (Android Platform Tools).")
60
+ console.error("Install it:")
61
+ console.error(" Windows: winget install Google.PlatformTools")
62
+ console.error(" macOS: brew install android-platform-tools")
63
+ console.error(" Linux: install your distro's android-tools / adb package")
25
64
  process.exit(1)
26
65
  }
27
66
 
@@ -42,18 +81,9 @@ export function printErr(...args: any[]) {
42
81
  state.rl?.prompt(true)
43
82
  }
44
83
 
45
- export function buildReload(payload: { code?: string | null; bytecode?: string }) {
46
- return JSON.stringify({ type: "reload", proxyFiles: values["proxy-files"], proxyHttp: values["proxy-http"], ...payload })
47
- }
48
-
49
- export function broadcastStop() {
50
- for (let ws of state.clients.keys()) {
51
- ws.send(JSON.stringify({ type: "stop" }))
52
- }
53
- }
54
-
55
84
  export function shutdown() {
56
85
  if (state.child) state.child.kill()
57
86
  if (state.server) state.server.stop()
87
+ if (state.bonjour) state.bonjour.destroy()
58
88
  process.exit(0)
59
89
  }
package/src/watcher.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  import { watch } from "node:fs"
2
2
  import { resolve, dirname } from "path"
3
- import { state, print, printErr, buildReload } from "./util"
4
- import { bundle } from "./bundle"
3
+ import { state, print, printErr } from "./util"
4
+ import { buildReload } from "./dev-server"
5
+ import { bundle } from "./bundler"
5
6
 
6
7
  let currentWatcher: ReturnType<typeof watch> | null = null
7
8
 
@@ -1,21 +0,0 @@
1
- import { transformAsync } from "@babel/core"
2
- import ts from "@babel/preset-typescript"
3
- import solid from "babel-preset-solid"
4
- import { type BunPlugin } from "bun"
5
-
6
- export function solidPlugin(): BunPlugin {
7
- return {
8
- name: "bun-plugin-solid",
9
- setup: (build) => {
10
- build.onLoad({ filter: /\.(js|ts)x$/ }, async (args) => {
11
- let file = Bun.file(args.path)
12
- let code = await file.text()
13
- let transforms = await transformAsync(code, {
14
- filename: args.path,
15
- presets: [[solid, { moduleName: "@solidrt/core", generate: "universal" }], [ts]],
16
- })
17
- return { contents: transforms?.code ?? "", loader: "js" }
18
- })
19
- },
20
- }
21
- }
package/src/bundle.ts DELETED
@@ -1,126 +0,0 @@
1
- import { solidPlugin } from "./bun-plugin-solid"
2
- import { values, source, isPrebuilt } from "./args"
3
- import { requireBinary, state, print } from "./util"
4
- import { resolve } from "path"
5
-
6
- export async function bundle(entry = source) {
7
- let result = null
8
-
9
- let devBase = state.serverUrl ?? undefined
10
- let dev = !!devBase || values.dev
11
- print(`[cli] Bundling (${dev ? "development" : "production"})`)
12
- let define: Record<string, string> = {
13
- "process.env.NODE_ENV": dev ? "development" : "production",
14
- }
15
- if (devBase) define.__SRT_DEV_BASE__ = devBase
16
-
17
- try {
18
- result = await Bun.build({
19
- entrypoints: [entry!],
20
- target: "browser",
21
- format: "esm",
22
- minify: values.minify,
23
- external: ["flux:*"],
24
- define,
25
- plugins: [solidPlugin()],
26
- })
27
- } catch (e) {
28
- console.error("[cli] compile error:\n", e)
29
- return null
30
- }
31
-
32
- if (result?.success) {
33
- return result
34
- }
35
-
36
- if (result) {
37
- for (let msg of result?.logs) console.error(msg)
38
- }
39
- return null
40
- }
41
-
42
- export async function bundleTo(outfile: string) {
43
- let result = await bundle()
44
- if (!result) {
45
- console.error("Build failed")
46
- process.exit(1)
47
- }
48
- for (let output of result.outputs) {
49
- await Bun.write(outfile, output)
50
- }
51
- return result
52
- }
53
-
54
- async function compileJs(jsCode: string, outfile: string) {
55
- let compiler = requireBinary("fluxc")
56
- let proc = Bun.spawn([compiler], {
57
- stdin: new Blob([jsCode]),
58
- stdout: "pipe",
59
- stderr: "inherit",
60
- })
61
- let [bytecode, code] = await Promise.all([new Response(proc.stdout).arrayBuffer(), proc.exited])
62
- if (code !== 0) process.exit(code)
63
- await Bun.write(outfile, bytecode)
64
- return outfile
65
- }
66
-
67
- async function compileToBytecode(jsFile: string, outFile?: string) {
68
- let jsCode = await Bun.file(jsFile).text()
69
- let dest = outFile ?? jsFile.replace(/\.srt\.js$/, ".srt.bin").replace(/\.js$/, ".bin")
70
- return compileJs(jsCode, dest)
71
- }
72
-
73
- async function compileFromStdin(jsCode: string, outfile: string) {
74
- return compileJs(jsCode, outfile)
75
- }
76
-
77
- export async function runBundleCommand() {
78
- if (isPrebuilt) {
79
- if (!source!.endsWith(".srt.js")) {
80
- console.error("Can only compile .srt.js files. .srt.bin is already compiled.")
81
- process.exit(1)
82
- }
83
- let binOut = await compileToBytecode(resolve(source!))
84
- let binSize = (await Bun.file(binOut).stat()).size
85
- console.log(`>> wrote ${binSize} bytes to ${binOut}`)
86
- process.exit()
87
- }
88
-
89
- let baseName = values.output ?? source!.replace(/\.[jt]sx?$/, "")
90
-
91
- if (values.stdout) {
92
- let result = await bundle()
93
- if (!result) {
94
- console.error("Build failed")
95
- process.exit(1)
96
- }
97
- for (let output of result.outputs) {
98
- process.stdout.write(await output.text())
99
- }
100
- process.exit()
101
- }
102
-
103
- if (values.compile) {
104
- let result = await bundle()
105
- if (!result) {
106
- console.error("Build failed")
107
- process.exit(1)
108
- }
109
- let jsCode = ""
110
- for (let output of result.outputs) {
111
- jsCode += await output.text()
112
- }
113
- let binOutfile = baseName + ".srt.bin"
114
- await compileFromStdin(jsCode, binOutfile)
115
- let binSize = (await Bun.file(binOutfile).stat()).size
116
- console.log(`>> wrote ${binSize} bytes to ${binOutfile}`)
117
- process.exit()
118
- }
119
-
120
- let jsOutfile = baseName + ".srt.js"
121
- let result = await bundleTo(jsOutfile)
122
- for (let output of result.outputs) {
123
- console.log(`>> wrote ${output.size} bytes to ${jsOutfile}`)
124
- }
125
- process.exit()
126
- }
package/src/pack.ts DELETED
@@ -1,58 +0,0 @@
1
- import { values, source } from "./args"
2
- import { requireBinary } from "./util"
3
-
4
- const MAGIC = Buffer.from([0x46, 0x4c, 0x55, 0x58, 0x52, 0x54, 0x00, 0x01]) // "FLUXRT\x00\x01"
5
-
6
- async function bundleFlux(entry: string): Promise<string> {
7
- let result = await Bun.build({
8
- entrypoints: [entry],
9
- target: "browser",
10
- format: "esm",
11
- minify: values.minify,
12
- external: ["flux:*"],
13
- })
14
- if (!result.success) {
15
- for (let msg of result.logs) console.error(msg)
16
- console.error("Build failed")
17
- process.exit(1)
18
- }
19
- let jsCode = ""
20
- for (let output of result.outputs) jsCode += await output.text()
21
- return jsCode
22
- }
23
-
24
- async function compileToBytecode(jsCode: string): Promise<Buffer> {
25
- let compiler = requireBinary("fluxc")
26
- let proc = Bun.spawn([compiler], {
27
- stdin: new Blob([jsCode]),
28
- stdout: "pipe",
29
- stderr: "inherit",
30
- })
31
- let [bytecode, code] = await Promise.all([new Response(proc.stdout).arrayBuffer(), proc.exited])
32
- if (code !== 0) process.exit(code)
33
- return Buffer.from(bytecode)
34
- }
35
-
36
- export async function runPackCommand() {
37
- let outfile = values.output ?? source!.replace(/\.[jt]s$/, "")
38
-
39
- let jsCode = await bundleFlux(source!)
40
-
41
- let bytecode = await compileToBytecode(jsCode)
42
-
43
- let runner = requireBinary("fluxrt")
44
- let runnerBytes = Buffer.from(await Bun.file(runner).arrayBuffer())
45
-
46
- let offsetBuf = Buffer.allocUnsafe(8)
47
- offsetBuf.writeBigUInt64LE(BigInt(runnerBytes.length))
48
-
49
- let packed = Buffer.concat([runnerBytes, bytecode, offsetBuf, MAGIC])
50
- await Bun.write(outfile, packed)
51
-
52
- if (process.platform !== "win32") {
53
- Bun.spawnSync(["chmod", "+x", outfile])
54
- }
55
-
56
- console.log(`>> wrote ${packed.length} bytes to ${outfile}`)
57
- process.exit()
58
- }