@solidrt/cli 0.0.17 → 0.0.19
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 +8 -8
- package/scaffold/AGENTS.md +52 -42
- package/scaffold/package.json +7 -4
- package/scaffold/src/index.tsx +54 -13
- package/scaffold/tsconfig.json +2 -0
- package/src/bundler.ts +63 -12
- package/src/commands/bundle.ts +3 -8
- package/src/commands/init.ts +5 -5
- package/src/commands/server.ts +5 -5
- package/src/dev-android.ts +41 -23
- package/src/dev-client.ts +3 -3
- package/src/dev-server.ts +18 -0
- package/src/repl.ts +5 -8
- package/src/watcher.ts +4 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solidrt/cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.19",
|
|
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": "^
|
|
18
|
-
"@babel/plugin-syntax-jsx": "^
|
|
19
|
-
"@babel/preset-typescript": "^
|
|
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.
|
|
26
|
-
"@solidrt/linux-x64-gnu": "0.0.
|
|
27
|
-
"@solidrt/win32-x64-msvc": "0.0.
|
|
25
|
+
"@solidrt/darwin-arm64": "0.0.19",
|
|
26
|
+
"@solidrt/linux-x64-gnu": "0.0.19",
|
|
27
|
+
"@solidrt/win32-x64-msvc": "0.0.19"
|
|
28
28
|
},
|
|
29
29
|
"peerDependencies": {
|
|
30
|
-
"@solidrt/core": "0.0.
|
|
30
|
+
"@solidrt/core": "0.0.19",
|
|
31
31
|
"typescript": "^6"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
package/scaffold/AGENTS.md
CHANGED
|
@@ -5,58 +5,68 @@ runtime. No DOM, no HTML, no CSS cascade. If you are an AI assistant, read this
|
|
|
5
5
|
before writing or editing code here.
|
|
6
6
|
|
|
7
7
|
Authoritative references ship inside the installed packages - read them:
|
|
8
|
-
- node_modules/solid-js/CHEATSHEET.md
|
|
9
|
-
- node_modules/@solidrt/
|
|
10
|
-
- node_modules/@solidrt/
|
|
11
|
-
- node_modules/@solidrt/
|
|
8
|
+
- node_modules/solid-js/CHEATSHEET.md - SolidJS 2.0 reactivity/control-flow model
|
|
9
|
+
- node_modules/@solidrt/components/AGENTS.md - the component vocabulary; build UI from these
|
|
10
|
+
- node_modules/@solidrt/components/README.md - full prop tables for every component
|
|
11
|
+
- node_modules/@solidrt/core/AGENTS.md - the underlying element/prop/reactivity model
|
|
12
|
+
- node_modules/@solidrt/core/examples/ - single-concept usage patterns to copy (see its README.md index)
|
|
13
|
+
- node_modules/@solidrt/cli/AGENTS.md - running, bundling, headless verify
|
|
12
14
|
- node_modules/@solidrt/core/src/types.d.ts and jsx-runtime.d.ts - source of truth
|
|
13
15
|
|
|
14
16
|
<!-- Claude Code auto-imports these; other tools read the paths above. -->
|
|
15
17
|
@./node_modules/solid-js/CHEATSHEET.md
|
|
18
|
+
@./node_modules/@solidrt/components/AGENTS.md
|
|
16
19
|
@./node_modules/@solidrt/core/AGENTS.md
|
|
17
20
|
@./node_modules/@solidrt/cli/AGENTS.md
|
|
18
21
|
|
|
19
22
|
## The things assistants get wrong (this is not React/DOM)
|
|
20
23
|
|
|
21
24
|
1. This is SolidJS 2.0 (see CHEATSHEET.md for the reactivity/control-flow
|
|
22
|
-
model), rendering through a custom Rust runtime instead of the DOM.
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
2.
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
25
|
+
model), rendering through a custom Rust runtime instead of the DOM. Build
|
|
26
|
+
UI from @solidrt/components (Window, View, Text, Image, TextInput,
|
|
27
|
+
ScrollView, Pressable, Button, SafeArea, theme/setTheme) - it is the
|
|
28
|
+
higher-level, batteries-included vocabulary and is where most app code
|
|
29
|
+
should live.
|
|
30
|
+
2. Most components split their props into two objects: `layout={{...}}` for
|
|
31
|
+
anything that feeds the layout engine (flex/grid, sizing, padding/margin,
|
|
32
|
+
position; font fields for Text) and `style={{...}}` for paint-only
|
|
33
|
+
properties that never relayout (`backgroundColor`, `borderColor`,
|
|
34
|
+
`borderWidth`, `borderRadius`, `color`, and the transform `x`/`y`/
|
|
35
|
+
`rotate`/`scale`). Event handlers (`onPointerDown`, `onKeyDown`, ...) are
|
|
36
|
+
top-level props, not inside `layout`/`style`.
|
|
37
|
+
3. `render(() => <App/>)` once, top level. The root MUST be a `<Window>`
|
|
38
|
+
(from @solidrt/components) or the core `<window>` - it throws otherwise.
|
|
39
|
+
4. `Window`/`View` do not paint on their own; they only paint when you set
|
|
40
|
+
`style.backgroundColor`/`borderColor` etc - there is no separate
|
|
41
|
+
background element to place by hand.
|
|
42
|
+
5. There is no onClick/onPress on host elements. Use `Pressable`/`Button`
|
|
43
|
+
from components (`onPress`), or `onPointerDown` on a `View` for anything
|
|
44
|
+
custom.
|
|
45
|
+
6. Reactive window state: prefer the accessors windowSize(), safeArea(),
|
|
46
|
+
displayScale(), windowFocused(), keyboardHeight() (re-exported from
|
|
47
|
+
@solidrt/core) over onResize/onLayout callbacks for reading layout and
|
|
48
|
+
window state. SafeArea (the component) is usually the simpler fix for
|
|
49
|
+
avoiding notches/system UI.
|
|
50
|
+
7. Per-frame animation: onFrame((tick, frame) => {}) is the native hook
|
|
51
|
+
(runtime-paced, auto-cleans), re-exported from @solidrt/core.
|
|
52
|
+
requestAnimationFrame(t => {}) exists as a web-standard one-shot but is
|
|
53
|
+
not the preferred animation driver.
|
|
54
|
+
8. Reach for @solidrt/core directly only for what components doesn't wrap:
|
|
55
|
+
raw host intrinsics and the `d-` (detached, non-layout) primitives like
|
|
56
|
+
`d-rect`/`d-path`/`d-oval` for vector art or perf-sensitive positioned
|
|
57
|
+
drawing, device/GPU subpath imports (@solidrt/core/camera, /microphone,
|
|
58
|
+
/gpu), gradients (createLinearGradient/createRadialGradient), and
|
|
59
|
+
createImage/decodeImage for images below the `Image` component's level.
|
|
60
|
+
Components and core primitives compose freely in the same tree - a
|
|
61
|
+
components-based app can drop to a `<d-path>` for one custom shape without
|
|
62
|
+
giving up `View`/`Text` everywhere else.
|
|
63
|
+
9. tsconfig needs jsx:"preserve" + jsxImportSource:"@solidrt/core" (still
|
|
64
|
+
true even when you build almost entirely with @solidrt/components -
|
|
65
|
+
components are plain functions returning core JSX). Solid peer deps are
|
|
66
|
+
pinned betas - do not bump them casually.
|
|
67
|
+
10. Use ASCII characters whenever possible in code and text - for example, no
|
|
52
68
|
em-dashes (use a hyphen), no smart/curly quotes, no unicode symbols.
|
|
53
|
-
|
|
54
|
-
returning { top, left, right, bottom } insets (like CSS
|
|
55
|
-
env(safe-area-inset-*)). It is fine to place content outside the safe-area
|
|
56
|
-
zone (e.g. full-bleed backgrounds), just be aware it may not be visible and
|
|
57
|
-
cannot be interacted with (touch gestures). Keep interactive or essential
|
|
58
|
-
content inside the safe area by padding the edges.
|
|
59
|
-
14. Prefer let over const. Use const only for real constants - a single fixed
|
|
69
|
+
11. Prefer let over const. Use const only for real constants - a single fixed
|
|
60
70
|
string or number value - and name those in ALL_CAPS.
|
|
61
71
|
|
|
62
72
|
## Run / verify
|
|
@@ -65,4 +75,4 @@ Authoritative references ship inside the installed packages - read them:
|
|
|
65
75
|
- bunx srt bundle src/index.tsx - exit 0 means it compiles
|
|
66
76
|
- bunx srt record src/index.tsx --size 480x640 --duration 1 --fps 2 - headless
|
|
67
77
|
render to PNG frames (proves it renders; see the cli AGENTS.md for where the
|
|
68
|
-
frames land)
|
|
78
|
+
frames land)
|
package/scaffold/package.json
CHANGED
|
@@ -4,14 +4,17 @@
|
|
|
4
4
|
"private": true,
|
|
5
5
|
"scripts": {
|
|
6
6
|
"dev": "srt run src/index.tsx",
|
|
7
|
-
"
|
|
7
|
+
"server": "srt server",
|
|
8
|
+
"client": "srt client",
|
|
9
|
+
"android": "srt client --android"
|
|
8
10
|
},
|
|
9
11
|
"dependencies": {
|
|
10
|
-
"@solidrt/core": "
|
|
12
|
+
"@solidrt/core": "0.0.19",
|
|
13
|
+
"@solidrt/components": "0.0.19"
|
|
11
14
|
},
|
|
12
15
|
"devDependencies": {
|
|
13
|
-
"@solidrt/cli": "
|
|
14
|
-
"@solidrt/flux-types": "
|
|
16
|
+
"@solidrt/cli": "0.0.0",
|
|
17
|
+
"@solidrt/flux-types": "0.0.0",
|
|
15
18
|
"typescript": "^6"
|
|
16
19
|
}
|
|
17
20
|
}
|
package/scaffold/src/index.tsx
CHANGED
|
@@ -1,25 +1,66 @@
|
|
|
1
|
-
import { render } from "@solidrt/core"
|
|
1
|
+
import { render, For, onFrame, createSignal, createLinearGradient } from "@solidrt/core"
|
|
2
|
+
import { View, Window } from "@solidrt/components"
|
|
3
|
+
|
|
4
|
+
const FADE = 360
|
|
5
|
+
|
|
6
|
+
let SEGMENTS = [
|
|
7
|
+
{ base: 0, light: "#3f5494", dark: "#162b6c", d: "M50.000 50.000 L28.330 50.000 C28.330 48.810 27.695 47.711 26.665 47.116 C25.635 46.521 24.365 46.521 23.335 47.116 C22.305 47.711 21.670 48.810 21.670 50.000 L0.000 50.000 L50.000 0.000 L50.000 9.170 C48.810 9.170 47.711 9.805 47.116 10.835 C46.521 11.865 46.521 13.135 47.116 14.165 C47.711 15.195 48.810 15.830 50.000 15.830 L50.000 25.000 L50.000 34.170 C48.810 34.170 47.711 34.805 47.116 35.835 C46.521 36.865 46.521 38.135 47.116 39.165 C47.711 40.195 48.810 40.830 50.000 40.830 L50.000 50.000 Z" },
|
|
8
|
+
{ base: 90, light: "#547ebf", dark: "#2b5696", d: "M50.000 50.000 L50.000 59.170 C48.810 59.170 47.711 59.805 47.116 60.835 C46.521 61.865 46.521 63.135 47.116 64.165 C47.711 65.195 48.810 65.830 50.000 65.830 L50.000 75.000 L50.000 84.170 C48.810 84.170 47.711 84.805 47.116 85.835 C46.521 86.865 46.521 88.135 47.116 89.165 C47.711 90.195 48.810 90.830 50.000 90.830 L50.000 100.000 L0.000 50.000 L21.670 50.000 C21.670 48.810 22.305 47.711 23.335 47.116 C24.365 46.521 25.635 46.521 26.665 47.116 C27.695 47.711 28.330 48.810 28.330 50.000 L50.000 50.000 Z" },
|
|
9
|
+
{ base: 180, light: "#7ea9ea", dark: "#5681c1", d: "M50.000 25.000 L50.000 15.830 C48.810 15.830 47.711 15.195 47.116 14.165 C46.521 13.135 46.521 11.865 47.116 10.835 C47.711 9.805 48.810 9.170 50.000 9.170 L50.000 0.000 L75.000 25.000 L65.830 25.000 C65.830 26.190 65.195 27.289 64.165 27.884 C63.135 28.479 61.865 28.479 60.835 27.884 C59.805 27.289 59.170 26.190 59.170 25.000 L50.000 25.000 Z" },
|
|
10
|
+
{ base: 270, light: "#547ebf", dark: "#2b5696", d: "M50.000 25.000 L59.170 25.000 C59.170 26.190 59.805 27.289 60.835 27.884 C61.865 28.479 63.135 28.479 64.165 27.884 C65.195 27.289 65.830 26.190 65.830 25.000 L75.000 25.000 L75.000 34.170 C73.810 34.170 72.711 34.805 72.116 35.835 C71.521 36.865 71.521 38.135 72.116 39.165 C72.711 40.195 73.810 40.830 75.000 40.830 L75.000 50.000 L65.830 50.000 C65.830 48.810 65.195 47.711 64.165 47.116 C63.135 46.521 61.865 46.521 60.835 47.116 C59.805 47.711 59.170 48.810 59.170 50.000 L50.000 50.000 L50.000 40.830 C48.810 40.830 47.711 40.195 47.116 39.165 C46.521 38.135 46.521 36.865 47.116 35.835 C47.711 34.805 48.810 34.170 50.000 34.170 L50.000 25.000 Z" },
|
|
11
|
+
{ base: 360, light: "#7ea9ea", dark: "#5681c1", d: "M50.000 50.000 L59.170 50.000 C59.170 48.810 59.805 47.711 60.835 47.116 C61.865 46.521 63.135 46.521 64.165 47.116 C65.195 47.711 65.830 48.810 65.830 50.000 L75.000 50.000 L64.855 60.145 C64.013 59.304 62.787 58.976 61.638 59.283 C60.489 59.591 59.591 60.489 59.283 61.638 C58.976 62.787 59.304 64.013 60.145 64.855 L50.000 75.000 L50.000 65.830 C48.810 65.830 47.711 65.195 47.116 64.165 C46.521 63.135 46.521 61.865 47.116 60.835 C47.711 59.805 48.810 59.170 50.000 59.170 L50.000 50.000 Z" },
|
|
12
|
+
{ base: 450, light: "#3f5494", dark: "#162b6c", d: "M75.000 50.000 L75.000 59.170 C73.810 59.170 72.711 59.805 72.116 60.835 C71.521 61.865 71.521 63.135 72.116 64.165 C72.711 65.195 73.810 65.830 75.000 65.830 L75.000 75.000 L50.000 100.000 L50.000 90.830 C48.810 90.830 47.711 90.195 47.116 89.165 C46.521 88.135 46.521 86.865 47.116 85.835 C47.711 84.805 48.810 84.170 50.000 84.170 L50.000 75.000 L60.145 64.855 C59.304 64.013 58.976 62.787 59.283 61.638 C59.591 60.489 60.489 59.591 61.638 59.283 C62.787 58.976 64.013 59.304 64.855 60.145 L75.000 50.000 Z" },
|
|
13
|
+
{ base: 540, light: "#7ea9ea", dark: "#5681c1", d: "M100.000 50.000 L75.000 75.000 L75.000 65.830 C73.810 65.830 72.711 65.195 72.116 64.165 C71.521 63.135 71.521 61.865 72.116 60.835 C72.711 59.805 73.810 59.170 75.000 59.170 L75.000 50.000 L75.000 40.830 C73.810 40.830 72.711 40.195 72.116 39.165 C71.521 38.135 71.521 36.865 72.116 35.835 C72.711 34.805 73.810 34.170 75.000 34.170 L75.000 25.000 L100.000 50.000 Z" },
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
let LAST = SEGMENTS[SEGMENTS.length - 1].base
|
|
17
|
+
let IN_DONE = LAST + FADE
|
|
18
|
+
let CYCLE = IN_DONE + LAST + FADE
|
|
19
|
+
|
|
20
|
+
let clamp = (x: number) => (x < 0 ? 0 : x > 1 ? 1 : x)
|
|
21
|
+
let ease = (t: number) => 1 - (1 - t) * (1 - t)
|
|
22
|
+
let byte = (x: number) => Math.round(Math.max(0, Math.min(255, x))).toString(16).padStart(2, "0")
|
|
23
|
+
|
|
24
|
+
let fill = (seg: (typeof SEGMENTS)[number], a: number) =>
|
|
25
|
+
createLinearGradient(0, 0, 1, 1, [
|
|
26
|
+
{ offset: 0, color: seg.light + byte(a * 255) },
|
|
27
|
+
{ offset: 1, color: seg.dark + byte(a * 255) },
|
|
28
|
+
])
|
|
2
29
|
|
|
3
30
|
function Icon() {
|
|
31
|
+
let [clock, setClock] = createSignal(0)
|
|
32
|
+
let start = -1
|
|
33
|
+
onFrame((t) => (start < 0 && (start = t), setClock((t - start) % CYCLE)))
|
|
4
34
|
return (
|
|
5
|
-
<
|
|
6
|
-
<
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
35
|
+
<View layout={{ width: 100, height: 100 }} style={{ scale: 3 }}>
|
|
36
|
+
<For each={SEGMENTS}>
|
|
37
|
+
{(seg) => {
|
|
38
|
+
let a = () => {
|
|
39
|
+
let t = clock(), end = IN_DONE + seg.base + FADE
|
|
40
|
+
if (t < seg.base || t >= end) return 0
|
|
41
|
+
return ease(Math.min(clamp((t - seg.base) / FADE), clamp((end - t) / FADE)))
|
|
42
|
+
}
|
|
43
|
+
return <d-path d={seg.d} color={fill(seg, a())} />
|
|
44
|
+
}}
|
|
45
|
+
</For>
|
|
46
|
+
</View>
|
|
14
47
|
)
|
|
15
48
|
}
|
|
16
49
|
|
|
17
50
|
function App() {
|
|
51
|
+
let backgroundColor = createLinearGradient(0, 0, 1, 1, [
|
|
52
|
+
{ offset: 0, color: "#080b16" },
|
|
53
|
+
{ offset: 1, color: "#1d2a52" },
|
|
54
|
+
])
|
|
55
|
+
|
|
18
56
|
return (
|
|
19
|
-
<
|
|
57
|
+
<Window
|
|
58
|
+
layout={{ alignItems: "center", justifyContent: "center" }}
|
|
59
|
+
style={{ backgroundColor }}
|
|
60
|
+
>
|
|
20
61
|
<Icon />
|
|
21
|
-
</
|
|
62
|
+
</Window>
|
|
22
63
|
)
|
|
23
64
|
}
|
|
24
65
|
|
|
25
|
-
render(() => <App />)
|
|
66
|
+
render(() => <App />)
|
package/scaffold/tsconfig.json
CHANGED
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
package/src/commands/bundle.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
}
|
package/src/commands/init.ts
CHANGED
|
@@ -33,12 +33,12 @@ export async function runInitCommand() {
|
|
|
33
33
|
process.exit(1)
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
console.log(`>>
|
|
36
|
+
console.log(`>> Scaffolding SolidRT project in ${resolve(dir)}`)
|
|
37
37
|
for (let { from, to } of TEMPLATE_FILES) {
|
|
38
38
|
let dest = join(dir, to)
|
|
39
39
|
await mkdir(dirname(dest), { recursive: true })
|
|
40
40
|
await writeFile(dest, await readFile(join(SCAFFOLD_DIR, from)))
|
|
41
|
-
console.log(`
|
|
41
|
+
console.log(` Write ${to}`)
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
// The scaffold package.json carries a placeholder name; set it from the
|
|
@@ -50,18 +50,18 @@ export async function runInitCommand() {
|
|
|
50
50
|
|
|
51
51
|
// Deps are declared in scaffold/package.json (Solid peers resolve via
|
|
52
52
|
// @solidrt/core's peerDependencies), so a plain install is enough.
|
|
53
|
-
console.log("\n>>
|
|
53
|
+
console.log("\n>> Installing dependencies")
|
|
54
54
|
let install = Bun.spawnSync(["bun", "install"], {
|
|
55
55
|
cwd: dir,
|
|
56
56
|
stdout: "inherit",
|
|
57
57
|
stderr: "inherit",
|
|
58
58
|
})
|
|
59
59
|
if (install.exitCode !== 0) {
|
|
60
|
-
console.error("\n!!
|
|
60
|
+
console.error("\n!! Dependency install failed; retry with `bun install` in the project")
|
|
61
61
|
process.exit(1)
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
let prefix = dir === "." ? "" : `cd ${dir} && `
|
|
65
|
-
console.log(`\n>>
|
|
65
|
+
console.log(`\n>> Done. Next:\n ${prefix}bun run dev\n`)
|
|
66
66
|
process.exit()
|
|
67
67
|
}
|
package/src/commands/server.ts
CHANGED
|
@@ -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
|
-
|
|
32
|
-
|
|
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-android.ts
CHANGED
|
@@ -65,11 +65,43 @@ function listDevices(adb: string): string[] {
|
|
|
65
65
|
.filter((s): s is string => Boolean(s))
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
//
|
|
69
|
-
//
|
|
70
|
-
//
|
|
71
|
-
function
|
|
68
|
+
// Primary ABI of the connected device (e.g. "arm64-v8a", "armeabi-v7a"), used
|
|
69
|
+
// to pick the matching APK -- a fat (multi-ABI) APK reports its 64-bit ABI
|
|
70
|
+
// here and still installs fine, so this only matters for single-ABI builds.
|
|
71
|
+
function deviceAbi(adb: string, target: string): string {
|
|
72
|
+
let res = Bun.spawnSync([adb, "-s", target, "shell", "getprop", "ro.product.cpu.abi"], {
|
|
73
|
+
stdout: "pipe",
|
|
74
|
+
stderr: "pipe",
|
|
75
|
+
})
|
|
76
|
+
return res.stdout.toString().trim()
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Always shown up front, regardless of how many devices are connected or
|
|
80
|
+
// whether --device narrows it down: one line per device with the ABI its APK
|
|
81
|
+
// build would target, then a dev-dependency hint for each ABI that isn't
|
|
82
|
+
// resolvable locally yet. Returns the device->abi map so callers don't have
|
|
83
|
+
// to re-query it.
|
|
84
|
+
function printDeviceStatus(adb: string, devices: string[]): Map<string, string> {
|
|
85
|
+
let abiByDevice = new Map<string, string>()
|
|
86
|
+
let missingAbis = new Set<string>()
|
|
87
|
+
for (let d of devices) {
|
|
88
|
+
let abi = deviceAbi(adb, d)
|
|
89
|
+
abiByDevice.set(d, abi)
|
|
90
|
+
print(`${d} - ${abi}`)
|
|
91
|
+
if (!resolveApk(abi)) missingAbis.add(abi)
|
|
92
|
+
}
|
|
93
|
+
let missingPkgs = [...missingAbis].map((abi) => ANDROID_PKG_MAP[abi]).filter((pkg): pkg is string => Boolean(pkg))
|
|
94
|
+
if (missingPkgs.length > 0) print(`Add dev dependencies with: bun add -d ${missingPkgs.join(" ")}`)
|
|
95
|
+
return abiByDevice
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Resolve the target device serial (and its ABI). With --device, treat the
|
|
99
|
+
// value as a serial prefix and require it to match exactly one connected
|
|
100
|
+
// device; without it, use the sole connected device. Exits with a clear
|
|
101
|
+
// message on any ambiguity.
|
|
102
|
+
function resolveTarget(adb: string): { target: string; abi: string } {
|
|
72
103
|
let devices = listDevices(adb)
|
|
104
|
+
let abiByDevice = printDeviceStatus(adb, devices)
|
|
73
105
|
|
|
74
106
|
if (values.device) {
|
|
75
107
|
let prefix = values.device
|
|
@@ -80,14 +112,14 @@ function resolveTarget(adb: string): string {
|
|
|
80
112
|
}
|
|
81
113
|
let [match] = matches
|
|
82
114
|
if (!match) {
|
|
83
|
-
console.error(`No connected device matches --device "${prefix}"
|
|
115
|
+
console.error(`No connected device matches --device "${prefix}".`)
|
|
84
116
|
process.exit(1)
|
|
85
117
|
}
|
|
86
|
-
return match
|
|
118
|
+
return { target: match, abi: abiByDevice.get(match)! }
|
|
87
119
|
}
|
|
88
120
|
|
|
89
121
|
if (devices.length > 1) {
|
|
90
|
-
console.error(
|
|
122
|
+
console.error("Pick one with --device <serial or prefix>.")
|
|
91
123
|
process.exit(1)
|
|
92
124
|
}
|
|
93
125
|
let [only] = devices
|
|
@@ -95,18 +127,7 @@ function resolveTarget(adb: string): string {
|
|
|
95
127
|
console.error("No authorized Android device found. Enable USB debugging and check `adb devices`.")
|
|
96
128
|
process.exit(1)
|
|
97
129
|
}
|
|
98
|
-
return only
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// Primary ABI of the connected device (e.g. "arm64-v8a", "armeabi-v7a"), used
|
|
102
|
-
// to pick the matching APK -- a fat (multi-ABI) APK reports its 64-bit ABI
|
|
103
|
-
// here and still installs fine, so this only matters for single-ABI builds.
|
|
104
|
-
function deviceAbi(adb: string, target: string): string {
|
|
105
|
-
let res = Bun.spawnSync([adb, "-s", target, "shell", "getprop", "ro.product.cpu.abi"], {
|
|
106
|
-
stdout: "pipe",
|
|
107
|
-
stderr: "pipe",
|
|
108
|
-
})
|
|
109
|
-
return res.stdout.toString().trim()
|
|
130
|
+
return { target: only, abi: abiByDevice.get(only)! }
|
|
110
131
|
}
|
|
111
132
|
|
|
112
133
|
// Install + launch the Android client on a connected device over adb, passing it
|
|
@@ -116,16 +137,13 @@ function deviceAbi(adb: string, target: string): string {
|
|
|
116
137
|
export async function spawnAndroidClient() {
|
|
117
138
|
let adb = requireAdb()
|
|
118
139
|
|
|
119
|
-
let target = resolveTarget(adb)
|
|
120
|
-
let abi = deviceAbi(adb, target)
|
|
140
|
+
let { target, abi } = resolveTarget(adb)
|
|
121
141
|
|
|
122
142
|
let apk = resolveApk(abi)
|
|
123
143
|
if (!apk) {
|
|
124
144
|
console.error(`Could not find a SolidRT-Go APK for ABI "${abi}".`)
|
|
125
145
|
let pkg = ANDROID_PKG_MAP[abi]
|
|
126
146
|
if (pkg) console.error(`Add it with: bun add -d ${pkg}`)
|
|
127
|
-
let target = abi === "armeabi-v7a" ? "make dist-android-armeabi-v7a" : "make dist-android"
|
|
128
|
-
console.error(`Or build one locally (from lattice/): ${target}`)
|
|
129
147
|
process.exit(1)
|
|
130
148
|
}
|
|
131
149
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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)
|