@solidrt/cli 0.0.17 → 0.0.18

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.17",
3
+ "version": "0.0.18",
4
4
  "license": "MIT",
5
5
  "author": "Antoine van Wel",
6
6
  "type": "module",
@@ -14,20 +14,20 @@
14
14
  "AGENTS.md"
15
15
  ],
16
16
  "dependencies": {
17
- "@babel/core": "^8.0.1",
18
- "@babel/plugin-syntax-jsx": "^8.0.1",
19
- "@babel/preset-typescript": "^8.0.1",
17
+ "@babel/core": "^7.0.0",
18
+ "@babel/plugin-syntax-jsx": "^7.0.0",
19
+ "@babel/preset-typescript": "^7.0.0",
20
20
  "babel-preset-solid": "2.0.0-beta.15",
21
21
  "bonjour-service": "^1.4.0",
22
22
  "qrcode-generator": "^2.0.4"
23
23
  },
24
24
  "optionalDependencies": {
25
- "@solidrt/darwin-arm64": "0.0.17",
26
- "@solidrt/linux-x64-gnu": "0.0.17",
27
- "@solidrt/win32-x64-msvc": "0.0.17"
25
+ "@solidrt/darwin-arm64": "0.0.18",
26
+ "@solidrt/linux-x64-gnu": "0.0.18",
27
+ "@solidrt/win32-x64-msvc": "0.0.18"
28
28
  },
29
29
  "peerDependencies": {
30
- "@solidrt/core": "0.0.17",
30
+ "@solidrt/core": "0.0.18",
31
31
  "typescript": "^6"
32
32
  },
33
33
  "devDependencies": {
@@ -7,11 +7,11 @@
7
7
  "bundle": "srt bundle src/index.tsx"
8
8
  },
9
9
  "dependencies": {
10
- "@solidrt/core": "latest"
10
+ "@solidrt/core": "0.0.18"
11
11
  },
12
12
  "devDependencies": {
13
- "@solidrt/cli": "latest",
14
- "@solidrt/flux-types": "latest",
13
+ "@solidrt/cli": "0.0.0",
14
+ "@solidrt/flux-types": "0.0.0",
15
15
  "typescript": "^6"
16
16
  }
17
17
  }
@@ -1,5 +1,7 @@
1
1
  {
2
2
  "compilerOptions": {
3
+ "target": "ESNext",
4
+ "lib": ["ESNext"],
3
5
  "jsx": "preserve",
4
6
  "jsxImportSource": "@solidrt/core",
5
7
  "moduleResolution": "bundler",
package/src/bundler.ts CHANGED
@@ -2,10 +2,65 @@ import { transformAsync } from "@babel/core"
2
2
  import jsx from "@babel/plugin-syntax-jsx"
3
3
  import ts from "@babel/preset-typescript"
4
4
  import solid from "babel-preset-solid"
5
- import { type BunPlugin } from "bun"
5
+ import { type BunPlugin, type BuildArtifact } from "bun"
6
+ import { readFileSync } from "node:fs"
7
+ import { dirname, resolve as resolvePath } from "node:path"
6
8
  import { values, source } from "./args"
7
9
  import { state, print, requireBinary } from "./util"
8
10
 
11
+ // Babel plugin: rewrite `import data from "./x" with { type: "binary" }` into an
12
+ // inline Uint8Array of the file's bytes. The import attribute is invisible to
13
+ // Bun's bundler and its plugins in this Bun version, so we handle it here in the
14
+ // transform where the AST still carries it. Inlining (rather than emitting a
15
+ // separate asset) keeps a single bundle output and hands JS a Uint8Array, which
16
+ // is what createImage and friends expect. Decoded at runtime via the global
17
+ // atob; for ASCII-extension files (.jpg/.png/...) we may add an attribute-free
18
+ // path later.
19
+ function binaryImport({ types: t }: { types: any }) {
20
+ return {
21
+ visitor: {
22
+ ImportDeclaration(path: any, pluginState: any) {
23
+ let attrs = path.node.attributes ?? path.node.assertions
24
+ let isBinary = attrs?.some((a: any) => a.key.name === "type" && a.value.value === "binary")
25
+ if (!isBinary) return
26
+
27
+ let def = path.node.specifiers.find((s: any) => s.type === "ImportDefaultSpecifier")
28
+ if (!def) {
29
+ throw path.buildCodeFrameError(
30
+ 'A binary import needs a default import: import data from "./file" with { type: "binary" }',
31
+ )
32
+ }
33
+
34
+ let importer = pluginState.file.opts.filename as string
35
+ let abs = resolvePath(dirname(importer), path.node.source.value)
36
+ let b64 = readFileSync(abs).toString("base64")
37
+
38
+ // var <local> = Uint8Array.from(atob("<b64>"), c => c.charCodeAt(0))
39
+ let expr = t.callExpression(t.memberExpression(t.identifier("Uint8Array"), t.identifier("from")), [
40
+ t.callExpression(t.identifier("atob"), [t.stringLiteral(b64)]),
41
+ t.arrowFunctionExpression(
42
+ [t.identifier("c")],
43
+ t.callExpression(t.memberExpression(t.identifier("c"), t.identifier("charCodeAt")), [t.numericLiteral(0)]),
44
+ ),
45
+ ])
46
+ path.replaceWith(t.variableDeclaration("var", [t.variableDeclarator(t.identifier(def.local.name), expr)]))
47
+ },
48
+ },
49
+ }
50
+ }
51
+
52
+ // Concatenate only the JS outputs (entry point plus any code-split chunks) of a
53
+ // build, skipping emitted asset outputs. Bun's file loader emits binary assets
54
+ // as extra outputs; the callers that flatten outputs into a single code string
55
+ // must not glue those raw bytes onto the program.
56
+ export async function codeFromOutputs(outputs: BuildArtifact[]): Promise<string> {
57
+ let code = ""
58
+ for (let o of outputs) {
59
+ if (o.kind === "entry-point" || o.kind === "chunk") code += await o.text()
60
+ }
61
+ return code
62
+ }
63
+
9
64
  // Bun build plugin that runs JSX/TSX through babel-preset-solid (universal
10
65
  // generate, targeting @solidrt/core) plus the TS preset.
11
66
  function solidPlugin(): BunPlugin {
@@ -18,7 +73,7 @@ function solidPlugin(): BunPlugin {
18
73
  let transforms = await transformAsync(code, {
19
74
  filename: args.path,
20
75
  presets: [[solid, { moduleName: "@solidrt/core", generate: "universal" }], [ts]],
21
- plugins: [jsx],
76
+ plugins: [jsx, binaryImport],
22
77
  })
23
78
  return { contents: transforms?.code ?? "", loader: "js" }
24
79
  })
@@ -31,7 +86,8 @@ export async function bundle(entry = source) {
31
86
 
32
87
  let devBase = state.serverUrl ?? undefined
33
88
  let dev = !!devBase || values.dev
34
- print(`[cli] Bundling (${dev ? "development" : "production"})`)
89
+ // Keep stdout clean when the bundle itself is written to stdout.
90
+ if (!values.stdout) print(`[cli] Bundling (${dev ? "development" : "production"})`)
35
91
  let define: Record<string, string> = {
36
92
  "process.env.NODE_ENV": dev ? "development" : "production",
37
93
  }
@@ -45,6 +101,7 @@ export async function bundle(entry = source) {
45
101
  minify: values.minify,
46
102
  external: ["flux:*", "srt:*"],
47
103
  define,
104
+ loader: { ".svg": "text" },
48
105
  plugins: [solidPlugin()],
49
106
  })
50
107
  } catch (e) {
@@ -68,9 +125,7 @@ export async function bundleTo(outfile: string) {
68
125
  console.error("Build failed")
69
126
  process.exit(1)
70
127
  }
71
- for (let output of result.outputs) {
72
- await Bun.write(outfile, output)
73
- }
128
+ await Bun.write(outfile, await codeFromOutputs(result.outputs))
74
129
  return result
75
130
  }
76
131
 
@@ -88,9 +143,7 @@ export async function bundleFlux(entry: string): Promise<string> {
88
143
  console.error("Build failed")
89
144
  process.exit(1)
90
145
  }
91
- let jsCode = ""
92
- for (let output of result.outputs) jsCode += await output.text()
93
- return jsCode
146
+ return codeFromOutputs(result.outputs)
94
147
  }
95
148
 
96
149
  // Bundle for the SolidRT runtime via the standard Solid-aware bundler.
@@ -100,9 +153,7 @@ export async function bundleSolid(): Promise<string> {
100
153
  console.error("Build failed")
101
154
  process.exit(1)
102
155
  }
103
- let jsCode = ""
104
- for (let output of result.outputs) jsCode += await output.text()
105
- return jsCode
156
+ return codeFromOutputs(result.outputs)
106
157
  }
107
158
 
108
159
  // Compile JS source to QuickJS bytecode via the fluxc binary.
@@ -1,5 +1,5 @@
1
1
  import { values, source, isPrebuilt } from "../args"
2
- import { bundle, bundleTo, bundleFlux, compileToBytecode } from "../bundler"
2
+ import { bundle, bundleTo, bundleFlux, compileToBytecode, codeFromOutputs } from "../bundler"
3
3
  import { resolve } from "path"
4
4
 
5
5
  // Write to stdout and resolve only once the whole payload is flushed.
@@ -55,9 +55,7 @@ export async function runBundleCommand() {
55
55
  console.error("Build failed")
56
56
  process.exit(1)
57
57
  }
58
- for (let output of result.outputs) {
59
- await writeStdout(await output.text())
60
- }
58
+ await writeStdout(await codeFromOutputs(result.outputs))
61
59
  process.exit()
62
60
  }
63
61
 
@@ -67,10 +65,7 @@ export async function runBundleCommand() {
67
65
  console.error("Build failed")
68
66
  process.exit(1)
69
67
  }
70
- let jsCode = ""
71
- for (let output of result.outputs) {
72
- jsCode += await output.text()
73
- }
68
+ let jsCode = await codeFromOutputs(result.outputs)
74
69
  await writeBytecode(jsCode, baseName + ".srt.bin")
75
70
  process.exit()
76
71
  }
@@ -1,8 +1,8 @@
1
1
  import pkg from "../../package.json"
2
2
  import { source, isSource, isPrebuilt, values } from "../args"
3
3
  import { state, shutdown } from "../util"
4
- import { bundle } from "../bundler"
5
- import { startServer } from "../dev-server"
4
+ import { bundle, codeFromOutputs } from "../bundler"
5
+ import { startServer, showBuildFailure } from "../dev-server"
6
6
  import { startRepl } from "../repl"
7
7
  import { startWatcher } from "../watcher"
8
8
  import * as cache from "../cache"
@@ -28,9 +28,9 @@ export async function runServerCommand() {
28
28
  if (source && isSource) {
29
29
  let initialResult = await bundle()
30
30
  if (initialResult) {
31
- for (let output of initialResult.outputs) {
32
- state.currentCode = await output.text()
33
- }
31
+ state.currentCode = await codeFromOutputs(initialResult.outputs)
32
+ } else {
33
+ showBuildFailure()
34
34
  }
35
35
  } else if (source && isPrebuilt && source.endsWith(".srt.js")) {
36
36
  state.currentCode = await Bun.file(resolve(source)).text()
package/src/dev-client.ts CHANGED
@@ -17,11 +17,11 @@ function pipeAbovePrompt(stream: ReadableStream<Uint8Array>, out: NodeJS.WriteSt
17
17
 
18
18
  export function spawnClient() {
19
19
  let runner = requireBinary("solidrt-go")
20
- let args: string[] = []
20
+ // The local client and dev server share this machine, so connect straight to
21
+ // the loopback server: no mDNS discovery or recents lookup is needed for `run`.
22
+ let args: string[] = ["--dev-server", `${DEV_HOST}:${DEV_PORT}`]
21
23
  if (values.size) args.push("--size", values.size)
22
24
  state.child = Bun.spawn([runner, ...args], {
23
- //TODO implement dev server connection
24
- // state.child = Bun.spawn([runner, "--dev-server", `${DEV_HOST}:${DEV_PORT}`], {
25
25
  stdio: ["ignore", "pipe", "pipe"],
26
26
  })
27
27
 
package/src/dev-server.ts CHANGED
@@ -22,6 +22,23 @@ export function broadcast(msg: object) {
22
22
  }
23
23
  }
24
24
 
25
+ // Reload code that fails to start the engine on purpose. The runtime treats a
26
+ // startup error like any app that never called render() and falls back to its
27
+ // baked-in BSOD screen, so a build that doesn't compile shows the BSOD instead
28
+ // of leaving the previous app frozen on screen.
29
+ const BSOD_TRIGGER = `throw new Error("SolidRT: build failed")`
30
+
31
+ // Called when a bundle fails to compile. Latches the BSOD trigger as the
32
+ // current code (so a client connecting after the failure gets it too) and
33
+ // pushes it to every connected client.
34
+ export function showBuildFailure() {
35
+ state.currentCode = BSOD_TRIGGER
36
+ let msg = buildReload({ code: BSOD_TRIGGER })
37
+ for (let ws of state.clients.keys()) {
38
+ ws.send(msg)
39
+ }
40
+ }
41
+
25
42
  function headersToObject(h: Headers): Record<string, string> {
26
43
  let out: Record<string, string> = {}
27
44
  h.forEach((v, k) => {
@@ -51,6 +68,7 @@ async function handleProxy(req: Request): Promise<Response> {
51
68
  print("[cli] proxy %s %s [cache hit]", req.method, target)
52
69
  let respHeaders = new Headers(hit.headers)
53
70
  respHeaders.set("x-srt-cache", "hit")
71
+ // await new Promise(resolve => setTimeout(resolve, 1000))
54
72
  return new Response(hit.body, { status: hit.status, headers: respHeaders })
55
73
  }
56
74
  }
package/src/repl.ts CHANGED
@@ -2,8 +2,8 @@ import { createInterface } from "node:readline"
2
2
  import { resolve, dirname } from "path"
3
3
  import { readdirSync } from "node:fs"
4
4
  import { state, print, printErr, shutdown } from "./util"
5
- import { buildReload, broadcast } from "./dev-server"
6
- import { bundle } from "./bundler"
5
+ import { buildReload, broadcast, showBuildFailure } from "./dev-server"
6
+ import { bundle, codeFromOutputs } from "./bundler"
7
7
  import { startWatcher, stopWatcher } from "./watcher"
8
8
 
9
9
  function cmdStop(args: string) {
@@ -32,11 +32,10 @@ async function cmdReload(args: string) {
32
32
  let result = await bundle(state.source)
33
33
  if (!result) {
34
34
  printErr("[cli] Build failed, reload aborted")
35
+ showBuildFailure()
35
36
  return
36
37
  }
37
- for (let output of result.outputs) {
38
- state.currentCode = await output.text()
39
- }
38
+ state.currentCode = await codeFromOutputs(result.outputs)
40
39
  }
41
40
  let msg = buildReload({ code: state.currentCode })
42
41
  if (!args) {
@@ -95,9 +94,7 @@ async function cmdLoad(file: string) {
95
94
  printErr("[cli] Build failed")
96
95
  return
97
96
  }
98
- for (let output of result.outputs) {
99
- state.currentCode = await output.text()
100
- }
97
+ state.currentCode = await codeFromOutputs(result.outputs)
101
98
  } else if (file.endsWith(".srt.js")) {
102
99
  state.currentCode = await Bun.file(path).text()
103
100
  } else if (file.endsWith(".srt.bin")) {
package/src/watcher.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import { watch } from "node:fs"
2
2
  import { resolve, dirname } from "path"
3
3
  import { state, print, printErr } from "./util"
4
- import { buildReload } from "./dev-server"
5
- import { bundle } from "./bundler"
4
+ import { buildReload, showBuildFailure } from "./dev-server"
5
+ import { bundle, codeFromOutputs } from "./bundler"
6
6
 
7
7
  let currentWatcher: ReturnType<typeof watch> | null = null
8
8
 
@@ -28,11 +28,10 @@ export function startWatcher() {
28
28
  let result = await bundle(state.source)
29
29
  if (!result) {
30
30
  printErr("[cli] Build failed, waiting for changes...")
31
+ showBuildFailure()
31
32
  return
32
33
  }
33
- for (let output of result.outputs) {
34
- state.currentCode = await output.text()
35
- }
34
+ state.currentCode = await codeFromOutputs(result.outputs)
36
35
  let msg = buildReload({ code: state.currentCode })
37
36
  for (let ws of state.clients.keys()) {
38
37
  ws.send(msg)