@solidrt/cli 0.0.50 → 0.0.52
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/AGENTS.md +79 -36
- package/README.md +86 -15
- package/agents/assets.md +46 -0
- package/agents/debugging.md +294 -0
- package/dist/console.srtapp +94123 -73
- package/dist/server.js +3936 -0
- package/package.json +11 -10
- package/src/android/docs.md +21 -0
- package/src/android/main.ts +286 -0
- package/src/{bundler.ts → bundle/bundler.ts} +163 -76
- package/src/bundle/docs.md +12 -0
- package/src/bundle/main.ts +203 -0
- package/src/check/docs.md +10 -0
- package/src/check/main.ts +85 -0
- package/src/{commands/check.ts → check/typecheck.ts} +11 -34
- package/src/client/docs.md +9 -0
- package/src/client/main.ts +33 -0
- package/src/console/docs.md +14 -0
- package/src/console/main.ts +21 -0
- package/src/demo/docs.md +27 -0
- package/src/demo/main.ts +67 -0
- package/src/init/docs.md +11 -0
- package/src/{commands/init.ts → init/main.ts} +31 -25
- package/src/init/scaffold/AGENTS.md +98 -0
- package/src/init/scaffold/package.json +23 -0
- package/{scaffold → src/init/scaffold}/templates/components/index.tsx +2 -3
- package/{scaffold → src/init/scaffold}/templates/default/index.tsx +2 -3
- package/src/lib/args.ts +194 -0
- package/src/{artifacts.ts → lib/artifacts.ts} +41 -1
- package/src/{dev-dir.ts → lib/dev-dir.ts} +10 -8
- package/src/{fonts.ts → lib/fonts.ts} +12 -26
- package/src/lib/mode.ts +77 -0
- package/src/{project.ts → lib/project.ts} +109 -61
- package/src/lib/registry.ts +120 -0
- package/src/lib/server-bundle.ts +24 -0
- package/src/lib/usage.ts +117 -0
- package/src/lib/util.ts +36 -0
- package/src/main.ts +109 -31
- package/src/mcp/docs.md +22 -0
- package/src/mcp/main.ts +719 -0
- package/src/pack/docs.md +18 -0
- package/src/{pack-folder.ts → pack/layout.ts} +13 -22
- package/src/pack/main.ts +86 -0
- package/src/pack/trailer.ts +97 -0
- package/src/render/docs.md +19 -0
- package/src/render/main.ts +49 -0
- package/src/server/args.ts +126 -0
- package/src/server/binaries.ts +47 -0
- package/src/server/config.ts +54 -0
- package/{server → src/server}/control.ts +246 -78
- package/src/server/docs.md +51 -0
- package/src/server/line-editor.ts +200 -0
- package/src/server/main.ts +473 -0
- package/src/server/mode.ts +92 -0
- package/src/server/rebuild.ts +90 -0
- package/src/server/registry.ts +138 -0
- package/src/server/remap.ts +60 -0
- package/src/server/repl.ts +223 -0
- package/src/server/state.ts +54 -0
- package/{server → src/server}/tsconfig.json +1 -1
- package/{server → src/server}/tunnel.ts +6 -6
- package/src/server/watcher.ts +121 -0
- package/src/tool/main.ts +70 -0
- package/src/types/bundle.d.ts +24 -0
- package/src/types/control.d.ts +90 -0
- package/src/types/registry.d.ts +16 -0
- package/scaffold/AGENTS.md +0 -630
- package/scaffold/package.json +0 -22
- package/scaffold/templates/components/icon.tsx +0 -48
- package/scaffold/templates/default/icon.tsx +0 -48
- package/server/main.ts +0 -308
- package/server/rebuild.ts +0 -68
- package/server/remap.ts +0 -47
- package/server/state.ts +0 -93
- package/src/args.ts +0 -212
- package/src/bundle-cli.ts +0 -13
- package/src/commands/bundle.ts +0 -76
- package/src/commands/client.ts +0 -34
- package/src/commands/mcp.ts +0 -603
- package/src/commands/pack.ts +0 -65
- package/src/commands/render.ts +0 -24
- package/src/commands/server.ts +0 -73
- package/src/dev-android.ts +0 -176
- package/src/dev-client.ts +0 -29
- package/src/dev-server.ts +0 -302
- package/src/packer.ts +0 -103
- package/src/repl.ts +0 -233
- package/src/util.ts +0 -121
- package/src/watcher.ts +0 -69
- /package/src/{untyped-deps.d.ts → bundle/untyped-deps.d.ts} +0 -0
- /package/src/{prompt.ts → init/prompt.ts} +0 -0
- /package/{scaffold → src/init/scaffold}/gitignore +0 -0
- /package/{scaffold → src/init/scaffold}/icon.svg +0 -0
- /package/{scaffold → src/init/scaffold}/mcp.json +0 -0
- /package/{scaffold → src/init/scaffold}/tsconfig.json +0 -0
- /package/{server → src/server}/cache.ts +0 -0
- /package/{server → src/server}/proxy.ts +0 -0
- /package/{server → src/server}/qr.ts +0 -0
package/src/packer.ts
DELETED
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from "node:fs"
|
|
2
|
-
import { dirname, join } from "node:path"
|
|
3
|
-
import { requireBinary } from "./util"
|
|
4
|
-
import { compileToBytecode } from "./bundler"
|
|
5
|
-
import type { PackFolder } from "./pack-folder"
|
|
6
|
-
|
|
7
|
-
// Trailer magic identifying the runner an embedded payload belongs to. Must match
|
|
8
|
-
// the runner-side checks: fluxrt -> flux/src/bin/fluxrt.rs, solidrt ->
|
|
9
|
-
// lattice/src/main.rs (load_embedded_payload).
|
|
10
|
-
const MAGIC = {
|
|
11
|
-
fluxrt: Buffer.from([0x46, 0x4c, 0x55, 0x58, 0x52, 0x54, 0x88, 0x44]), // "FLUXRT\x88\x44"
|
|
12
|
-
solidrt: Buffer.from([0x53, 0x4f, 0x4c, 0x49, 0x44, 0x52, 0x54, 0x88, 0x44]), // "SOLIDRT\x88\x44"
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
// Section kinds in the solidrt trailer. Must match lattice/src/main.rs.
|
|
16
|
-
const SECTION_MANIFEST = 1
|
|
17
|
-
const SECTION_FILE = 2
|
|
18
|
-
const SECTION_GL_LIB = 3
|
|
19
|
-
|
|
20
|
-
// The GL libraries the runner needs next to it (or, single-file, embedded as
|
|
21
|
-
// kind-3 sections it extracts at boot): ANGLE's libraries on Windows and
|
|
22
|
-
// macOS, nothing on platforms with a system GL. Order matters and the runner
|
|
23
|
-
// preloads in section order: libGLESv2 must load before libEGL so libEGL's
|
|
24
|
-
// import of it resolves against the already-loaded module instead of a
|
|
25
|
-
// directory search.
|
|
26
|
-
const GL_LIB_NAMES: Partial<Record<NodeJS.Platform, string[]>> = {
|
|
27
|
-
win32: ["libGLESv2.dll", "libEGL.dll"],
|
|
28
|
-
darwin: ["libGLESv2.dylib", "libEGL.dylib"],
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// The GL libraries shipped next to the runner binary, resolved to their paths.
|
|
32
|
-
// Missing files are fatal: a pack without them cannot create a window.
|
|
33
|
-
export function runnerGlLibs(runnerPath: string): Array<{ name: string; path: string }> {
|
|
34
|
-
let names = GL_LIB_NAMES[process.platform] ?? []
|
|
35
|
-
let dir = dirname(runnerPath)
|
|
36
|
-
return names.map((name) => {
|
|
37
|
-
let path = join(dir, name)
|
|
38
|
-
if (!existsSync(path)) {
|
|
39
|
-
console.error(`Could not find ${name} next to the runner (${dir}); the packed app needs it to create a GL context.`)
|
|
40
|
-
process.exit(1)
|
|
41
|
-
}
|
|
42
|
-
return { name, path }
|
|
43
|
-
})
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
type Section = { kind: number; bytes: Buffer; name?: string }
|
|
47
|
-
|
|
48
|
-
// Append sections to the runner image: each section's bytes, then a table of
|
|
49
|
-
// section entries, then [table offset u64 LE][entry count u32 LE][magic].
|
|
50
|
-
// Table entry: [kind u32 LE][offset u64 LE][len u64 LE][name len u16 LE][name].
|
|
51
|
-
// Offsets are absolute file offsets. The runner reads its own image at
|
|
52
|
-
// startup, validates the magic, and slices the sections back out.
|
|
53
|
-
function packSections(runnerBytes: Buffer, sections: Section[], magic: Buffer): Buffer {
|
|
54
|
-
let parts: Buffer[] = [runnerBytes]
|
|
55
|
-
let entries: Buffer[] = []
|
|
56
|
-
let offset = runnerBytes.length
|
|
57
|
-
for (let section of sections) {
|
|
58
|
-
parts.push(section.bytes)
|
|
59
|
-
let name = Buffer.from(section.name ?? "", "utf8")
|
|
60
|
-
let entry = Buffer.allocUnsafe(22 + name.length)
|
|
61
|
-
entry.writeUInt32LE(section.kind, 0)
|
|
62
|
-
entry.writeBigUInt64LE(BigInt(offset), 4)
|
|
63
|
-
entry.writeBigUInt64LE(BigInt(section.bytes.length), 12)
|
|
64
|
-
entry.writeUInt16LE(name.length, 20)
|
|
65
|
-
name.copy(entry, 22)
|
|
66
|
-
entries.push(entry)
|
|
67
|
-
offset += section.bytes.length
|
|
68
|
-
}
|
|
69
|
-
let tail = Buffer.allocUnsafe(12)
|
|
70
|
-
tail.writeBigUInt64LE(BigInt(offset), 0) // the table starts where the sections end
|
|
71
|
-
tail.writeUInt32LE(sections.length, 8)
|
|
72
|
-
return Buffer.concat([...parts, ...entries, tail, magic])
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// The single-file solidrt executable: the runner image plus the pack folder in
|
|
76
|
-
// section form - the canonical manifest verbatim, then every manifest-listed
|
|
77
|
-
// file named by its manifest path. Bundle, fonts, and identity all come from
|
|
78
|
-
// the manifest; assets are read in place via ranged reads at their section
|
|
79
|
-
// offsets, so nothing is unpacked at runtime. GL libraries ride along as
|
|
80
|
-
// kind-3 sections (runtime freight, deliberately outside the manifest); the
|
|
81
|
-
// runner extracts those to its cache and preloads them before window setup.
|
|
82
|
-
export function packSolid(folder: PackFolder, bytecode: Buffer): Buffer {
|
|
83
|
-
let runnerPath = requireBinary("solidrt")
|
|
84
|
-
let runnerBytes = readFileSync(runnerPath)
|
|
85
|
-
let sections: Section[] = [
|
|
86
|
-
{ kind: SECTION_MANIFEST, bytes: Buffer.from(folder.manifest, "utf8") },
|
|
87
|
-
{ kind: SECTION_FILE, bytes: bytecode, name: "bundle.bin" },
|
|
88
|
-
...folder.copies.map((c) => ({ kind: SECTION_FILE, bytes: readFileSync(c.from), name: c.to })),
|
|
89
|
-
...folder.files.map((f) => ({ kind: SECTION_FILE, bytes: f.bytes, name: f.to })),
|
|
90
|
-
...runnerGlLibs(runnerPath).map((lib) => ({ kind: SECTION_GL_LIB, bytes: readFileSync(lib.path), name: lib.name })),
|
|
91
|
-
]
|
|
92
|
-
return packSections(runnerBytes, sections, MAGIC.solidrt)
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// Compile JS to bytecode and append it to the fluxrt runner as its
|
|
96
|
-
// single-payload trailer: [bytecode][u64 offset LE][8-byte magic].
|
|
97
|
-
export async function packFlux(jsCode: string): Promise<Buffer> {
|
|
98
|
-
let bytecode = await compileToBytecode(jsCode)
|
|
99
|
-
let runnerBytes = readFileSync(requireBinary("fluxrt"))
|
|
100
|
-
let offsetBuf = Buffer.allocUnsafe(8)
|
|
101
|
-
offsetBuf.writeBigUInt64LE(BigInt(runnerBytes.length))
|
|
102
|
-
return Buffer.concat([runnerBytes, bytecode, offsetBuf, MAGIC.fluxrt])
|
|
103
|
-
}
|
package/src/repl.ts
DELETED
|
@@ -1,233 +0,0 @@
|
|
|
1
|
-
import { createInterface } from "node:readline"
|
|
2
|
-
import { resolve, dirname } from "path"
|
|
3
|
-
import { readdirSync } from "node:fs"
|
|
4
|
-
import { state, print, printErr, shutdown } from "./util"
|
|
5
|
-
import { buildReload, getClients, sendReload, sendStop, sendStats, sendWatch, showBuildFailure } from "./dev-server"
|
|
6
|
-
import { bundle } from "./bundler"
|
|
7
|
-
import { buildManifest } from "./project"
|
|
8
|
-
import { startWatcher, stopWatcher } from "./watcher"
|
|
9
|
-
|
|
10
|
-
// Resolve repl client indexes ("0 2") against the server's client list,
|
|
11
|
-
// printing a complaint for each invalid token. The list order is connect
|
|
12
|
-
// order, matching what `list` shows.
|
|
13
|
-
async function indexesToIds(args: string): Promise<number[]> {
|
|
14
|
-
let clients = await getClients()
|
|
15
|
-
let ids: number[] = []
|
|
16
|
-
for (let token of args.split(/\s+/)) {
|
|
17
|
-
let idx = parseInt(token, 10)
|
|
18
|
-
if (isNaN(idx) || idx < 0 || idx >= clients.length) {
|
|
19
|
-
print(`Invalid client index: ${token}`)
|
|
20
|
-
continue
|
|
21
|
-
}
|
|
22
|
-
ids.push(clients[idx]!.id)
|
|
23
|
-
}
|
|
24
|
-
return ids
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
async function cmdStop(args: string) {
|
|
28
|
-
if (!args) {
|
|
29
|
-
stopWatcher()
|
|
30
|
-
state.currentCode = null
|
|
31
|
-
state.currentMap = null
|
|
32
|
-
state.currentManifest = null
|
|
33
|
-
state.source = undefined
|
|
34
|
-
await sendStop()
|
|
35
|
-
print("[cli] Sent stop to all clients")
|
|
36
|
-
return
|
|
37
|
-
}
|
|
38
|
-
let ids = await indexesToIds(args)
|
|
39
|
-
if (ids.length) {
|
|
40
|
-
await sendStop(ids)
|
|
41
|
-
print(`[cli] Sent stop to client(s) ${ids.join(", ")}`)
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
async function cmdReload(args: string) {
|
|
46
|
-
if (state.source && state.source.endsWith(".tsx")) {
|
|
47
|
-
let result = await bundle(state.source)
|
|
48
|
-
if (!result) {
|
|
49
|
-
printErr("[cli] Build failed, reload aborted")
|
|
50
|
-
await showBuildFailure()
|
|
51
|
-
return
|
|
52
|
-
}
|
|
53
|
-
state.currentCode = result.code
|
|
54
|
-
state.currentMap = result.map
|
|
55
|
-
state.currentManifest = result.manifest
|
|
56
|
-
}
|
|
57
|
-
let msg = buildReload({ code: state.currentCode, manifest: state.currentManifest })
|
|
58
|
-
if (!args) {
|
|
59
|
-
await sendReload(msg, { latch: true, map: state.currentMap })
|
|
60
|
-
print("[cli] Sent reload to all clients")
|
|
61
|
-
return
|
|
62
|
-
}
|
|
63
|
-
let ids = await indexesToIds(args)
|
|
64
|
-
if (ids.length) {
|
|
65
|
-
await sendReload(msg, { clients: ids, map: state.currentMap })
|
|
66
|
-
print(`[cli] Sent reload to client(s) ${ids.join(", ")}`)
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
async function cmdStats(args: string) {
|
|
71
|
-
if (args === "on") {
|
|
72
|
-
state.stats = true
|
|
73
|
-
} else if (args === "off") {
|
|
74
|
-
state.stats = false
|
|
75
|
-
} else if (!args) {
|
|
76
|
-
state.stats = !state.stats
|
|
77
|
-
} else {
|
|
78
|
-
print("Usage: stats [on|off]")
|
|
79
|
-
return
|
|
80
|
-
}
|
|
81
|
-
await sendStats(state.stats)
|
|
82
|
-
print(`[cli] Stats overlay ${state.stats ? "on" : "off"}`)
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
async function cmdList() {
|
|
86
|
-
let clients = await getClients()
|
|
87
|
-
if (clients.length === 0) {
|
|
88
|
-
print("No connected clients")
|
|
89
|
-
return
|
|
90
|
-
}
|
|
91
|
-
print(`${clients.length} connected client(s):`)
|
|
92
|
-
let i = 0
|
|
93
|
-
for (let c of clients) {
|
|
94
|
-
print(` ${i++}: ${c.address ?? "unknown"} [${c.platform}, ${c.version}]`)
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
async function cmdLoad(file: string) {
|
|
99
|
-
if (!file) {
|
|
100
|
-
print("Usage: load <file.tsx|.srt.js|.srt.bin>")
|
|
101
|
-
return
|
|
102
|
-
}
|
|
103
|
-
let path = resolve(file)
|
|
104
|
-
// Same rule as /__control__/load (control.ts): a server run serves the
|
|
105
|
-
// project it started in, and an entry outside the project root cannot
|
|
106
|
-
// resolve the project's dependencies anyway.
|
|
107
|
-
let norm = (p: string) => p.replace(/\\/g, "/")
|
|
108
|
-
let root = norm(state.projectDir).replace(/\/+$/, "") + "/"
|
|
109
|
-
if (!norm(path).startsWith(root)) {
|
|
110
|
-
printErr(`[cli] Entry is outside the project root: ${path} is not under ${state.projectDir}. Restart srt in that project to work on it.`)
|
|
111
|
-
return
|
|
112
|
-
}
|
|
113
|
-
if (file.endsWith(".tsx")) {
|
|
114
|
-
let result = await bundle(path)
|
|
115
|
-
if (!result) {
|
|
116
|
-
printErr("[cli] Build failed")
|
|
117
|
-
return
|
|
118
|
-
}
|
|
119
|
-
state.currentCode = result.code
|
|
120
|
-
state.currentMap = result.map
|
|
121
|
-
state.currentManifest = result.manifest
|
|
122
|
-
} else if (file.endsWith(".srt.js")) {
|
|
123
|
-
state.currentCode = await Bun.file(path).text()
|
|
124
|
-
state.currentMap = null
|
|
125
|
-
state.currentManifest = buildManifest(state.currentCode, path)
|
|
126
|
-
} else if (file.endsWith(".srt.bin")) {
|
|
127
|
-
let bytes = await Bun.file(path).arrayBuffer()
|
|
128
|
-
// One-shot: bytecode loads are pushed but not latched for late joiners.
|
|
129
|
-
await sendReload(buildReload({ bytecode: Buffer.from(bytes).toString("base64") }))
|
|
130
|
-
print(`[cli] Loaded ${file} (bytecode, ${bytes.byteLength} bytes)`)
|
|
131
|
-
return
|
|
132
|
-
} else {
|
|
133
|
-
print("Unsupported file type. Use .tsx, .srt.js, or .srt.bin")
|
|
134
|
-
return
|
|
135
|
-
}
|
|
136
|
-
state.source = path
|
|
137
|
-
state.sourceDir = dirname(path)
|
|
138
|
-
startWatcher()
|
|
139
|
-
// The load also moves the server's file-serving root to the new source dir
|
|
140
|
-
// and its rebuild entry to the new file (for a later MCP reload). The
|
|
141
|
-
// project root - and with it the /assets/ root - is fixed for the life of
|
|
142
|
-
// the run.
|
|
143
|
-
await sendReload(buildReload({ code: state.currentCode, manifest: state.currentManifest }), {
|
|
144
|
-
latch: true,
|
|
145
|
-
sourceDir: state.sourceDir,
|
|
146
|
-
entry: file.endsWith(".tsx") ? path : undefined,
|
|
147
|
-
map: state.currentMap,
|
|
148
|
-
})
|
|
149
|
-
print(`[cli] Loaded ${file}`)
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
let COMMANDS = ["load ", "stop", "reload", "list", "stats", "watch ", "quit", "exit", "help"]
|
|
153
|
-
let LOAD_EXTENSIONS = [".tsx", ".srt.js", ".srt.bin"]
|
|
154
|
-
|
|
155
|
-
function completer(line: string): [string[], string] {
|
|
156
|
-
if (line.startsWith("load ")) {
|
|
157
|
-
let partial = line.slice(5)
|
|
158
|
-
let dir = partial.includes("/") ? partial.slice(0, partial.lastIndexOf("/") + 1) : ""
|
|
159
|
-
let prefix = partial.slice(dir.length)
|
|
160
|
-
let absDir = resolve(dir || ".")
|
|
161
|
-
try {
|
|
162
|
-
let entries = readdirSync(absDir, { withFileTypes: true })
|
|
163
|
-
let matches: string[] = []
|
|
164
|
-
for (let entry of entries) {
|
|
165
|
-
if (!entry.name.startsWith(prefix)) continue
|
|
166
|
-
if (entry.isDirectory()) {
|
|
167
|
-
matches.push(entry.name + "/")
|
|
168
|
-
} else if (LOAD_EXTENSIONS.some((ext) => entry.name.endsWith(ext))) {
|
|
169
|
-
matches.push(entry.name)
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
return [matches, prefix]
|
|
173
|
-
} catch {
|
|
174
|
-
return [[], line]
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
let matches = COMMANDS.filter((c) => c.startsWith(line))
|
|
178
|
-
return [matches, line]
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
// Run a repl command, reporting a failed server round-trip instead of leaving
|
|
182
|
-
// an unhandled rejection (e.g. the server process died mid-command).
|
|
183
|
-
function guard(p: Promise<void>) {
|
|
184
|
-
p.catch((e) => printErr(`[cli] ${String(e)}`))
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
export function startRepl() {
|
|
188
|
-
state.rl = createInterface({ input: process.stdin, output: process.stdout, completer })
|
|
189
|
-
state.rl.setPrompt("srt> ")
|
|
190
|
-
|
|
191
|
-
state.rl.on("close", shutdown)
|
|
192
|
-
|
|
193
|
-
state.rl.on("line", (line) => {
|
|
194
|
-
let cmd = line.trim()
|
|
195
|
-
if (cmd === "stop" || cmd.startsWith("stop ")) {
|
|
196
|
-
guard(cmdStop(cmd.slice(5).trim()))
|
|
197
|
-
} else if (cmd === "reload" || cmd.startsWith("reload ")) {
|
|
198
|
-
guard(cmdReload(cmd.slice(7).trim()))
|
|
199
|
-
} else if (cmd.startsWith("load ")) {
|
|
200
|
-
guard(cmdLoad(cmd.slice(5).trim()))
|
|
201
|
-
} else if (cmd === "list") {
|
|
202
|
-
guard(cmdList())
|
|
203
|
-
} else if (cmd === "stats" || cmd.startsWith("stats ")) {
|
|
204
|
-
guard(cmdStats(cmd.slice(6).trim()))
|
|
205
|
-
} else if (cmd === "watch on" || cmd === "watch off") {
|
|
206
|
-
// Manual override for the agent-latched auto-reload pause (an agent
|
|
207
|
-
// that died mid-edit leaves it off; its reload normally restores it).
|
|
208
|
-
let enabled = cmd === "watch on"
|
|
209
|
-
guard(sendWatch(enabled).then(() => print(`[cli] Auto-reload on change ${enabled ? "on" : "off"}`)))
|
|
210
|
-
} else if (cmd === "quit" || cmd === "exit") {
|
|
211
|
-
shutdown()
|
|
212
|
-
} else if (cmd.startsWith("!")) {
|
|
213
|
-
let shell = cmd.slice(1)
|
|
214
|
-
if (shell) {
|
|
215
|
-
Bun.$`${{ raw: shell }}`.quiet().then(
|
|
216
|
-
(r) => {
|
|
217
|
-
if (r.stdout.length) print(r.text())
|
|
218
|
-
},
|
|
219
|
-
(e) => {
|
|
220
|
-
printErr(e.stderr.toString())
|
|
221
|
-
},
|
|
222
|
-
)
|
|
223
|
-
}
|
|
224
|
-
} else if (cmd === "help") {
|
|
225
|
-
print("Commands: load, stop, reload, list, stats, watch on|off, !<cmd>, quit, help")
|
|
226
|
-
} else if (cmd) {
|
|
227
|
-
print(`Unknown command: ${cmd}`)
|
|
228
|
-
}
|
|
229
|
-
state.rl!.prompt()
|
|
230
|
-
})
|
|
231
|
-
|
|
232
|
-
state.rl.prompt()
|
|
233
|
-
}
|
package/src/util.ts
DELETED
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
import { resolveBinary } from "./artifacts"
|
|
2
|
-
import { existsSync } from "node:fs"
|
|
3
|
-
import { resolve } from "node:path"
|
|
4
|
-
import type { Interface as ReadlineInterface } from "node:readline"
|
|
5
|
-
// import type { Bonjour } from "bonjour-service" (mDNS advertise, kept - see dev-server.ts)
|
|
6
|
-
|
|
7
|
-
export let state = {
|
|
8
|
-
// What srt believes the current bundle is; the server process keeps its own
|
|
9
|
-
// latched copy for late-joining clients (see packages/cli/server/).
|
|
10
|
-
currentCode: null as string | null,
|
|
11
|
-
// The bundle's composed sourcemap (dev builds), sent to the server alongside
|
|
12
|
-
// reloads so it can remap logged stack traces to .tsx positions.
|
|
13
|
-
currentMap: null as string | null,
|
|
14
|
-
// The bundle's version manifest (canonical JSON string, see buildManifest);
|
|
15
|
-
// travels with code reloads so clients install the push as a version.
|
|
16
|
-
currentManifest: null as string | null,
|
|
17
|
-
source: undefined as string | undefined,
|
|
18
|
-
sourceDir: process.cwd(),
|
|
19
|
-
// The project root the assets/ convention hangs off (see project.ts
|
|
20
|
-
// projectDirFor); the server's /assets/ route serves from under it.
|
|
21
|
-
projectDir: process.cwd(),
|
|
22
|
-
child: null as ReturnType<typeof Bun.spawn> | null,
|
|
23
|
-
// The spawned flux dev-server process (see dev-server.ts startServer).
|
|
24
|
-
serverProc: null as ReturnType<typeof Bun.spawn> | null,
|
|
25
|
-
shuttingDown: false,
|
|
26
|
-
serverUrl: null as string | null,
|
|
27
|
-
rl: null as ReadlineInterface | null,
|
|
28
|
-
// bonjour: null as Bonjour | null, (mDNS advertise, kept - see dev-server.ts)
|
|
29
|
-
stats: false,
|
|
30
|
-
// --capture <file>: destination for captured key events, or undefined when
|
|
31
|
-
// off; the server process owns the capture file and clock (see
|
|
32
|
-
// packages/cli/server/main.ts's "capture" message handling).
|
|
33
|
-
capture: undefined as string | undefined,
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// Build target per binary, for the "not found" hint. Run from the repo root.
|
|
37
|
-
let BUILD_HINTS: Record<string, string> = {
|
|
38
|
-
"solidrt-go": "make client",
|
|
39
|
-
solidrt: "make runtime",
|
|
40
|
-
flux: "make -C flux flux",
|
|
41
|
-
fluxc: "make -C flux fluxc",
|
|
42
|
-
fluxrt: "make -C flux fluxrt PROFILE=release-opt",
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export function requireBinary(name: string) {
|
|
46
|
-
let path = resolveBinary(name)
|
|
47
|
-
if (path) return path
|
|
48
|
-
let hint = BUILD_HINTS[name]
|
|
49
|
-
console.error(`Could not find ${name} binary.`)
|
|
50
|
-
if (hint) {
|
|
51
|
-
console.error(`Build it from source: run ${hint}, with SRT_HOME pointing at your SolidRT checkout.`)
|
|
52
|
-
} else {
|
|
53
|
-
console.error("Build it from source, with SRT_HOME pointing at your SolidRT checkout.")
|
|
54
|
-
}
|
|
55
|
-
process.exit(1)
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// adb is a system tool (Android Platform Tools), never bundled. Look on PATH
|
|
59
|
-
// first, then the standard SDK location.
|
|
60
|
-
export function resolveAdb() {
|
|
61
|
-
let exe = process.platform === "win32" ? "adb.exe" : "adb"
|
|
62
|
-
let onPath = Bun.which(exe)
|
|
63
|
-
if (onPath) return onPath
|
|
64
|
-
for (let root of [process.env.ANDROID_HOME, process.env.ANDROID_SDK_ROOT]) {
|
|
65
|
-
if (!root) continue
|
|
66
|
-
let candidate = resolve(root, "platform-tools", exe)
|
|
67
|
-
if (existsSync(candidate)) return candidate
|
|
68
|
-
}
|
|
69
|
-
return null
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export function requireAdb() {
|
|
73
|
-
let path = resolveAdb()
|
|
74
|
-
if (path) return path
|
|
75
|
-
console.error("Could not find adb (Android Platform Tools).")
|
|
76
|
-
console.error("Install it:")
|
|
77
|
-
console.error(" Windows: winget install Google.PlatformTools")
|
|
78
|
-
console.error(" macOS: brew install android-platform-tools")
|
|
79
|
-
console.error(" Linux: install your distro's android-tools / adb package")
|
|
80
|
-
process.exit(1)
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
export async function run(binary: string, args: string[]) {
|
|
84
|
-
let proc = Bun.spawn([binary, ...args], { stdio: ["inherit", "inherit", "inherit"] })
|
|
85
|
-
return proc.exited
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
export function print(...args: any[]) {
|
|
89
|
-
process.stdout.write("\r\x1b[K")
|
|
90
|
-
console.log(...args)
|
|
91
|
-
state.rl?.prompt(true)
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
export function printErr(...args: any[]) {
|
|
95
|
-
process.stdout.write("\r\x1b[K")
|
|
96
|
-
console.error(...args)
|
|
97
|
-
state.rl?.prompt(true)
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// Pipe a child stream to `out` without mangling the repl prompt: clear the
|
|
101
|
-
// prompt line, write the chunk, redraw the prompt.
|
|
102
|
-
export function pipeAbovePrompt(stream: ReadableStream<Uint8Array>, out: NodeJS.WriteStream) {
|
|
103
|
-
let reader = stream.getReader()
|
|
104
|
-
;(async () => {
|
|
105
|
-
while (true) {
|
|
106
|
-
let { done, value } = await reader.read()
|
|
107
|
-
if (done || !value) break
|
|
108
|
-
process.stdout.write("\r\x1b[K")
|
|
109
|
-
out.write(value)
|
|
110
|
-
state.rl?.prompt(true)
|
|
111
|
-
}
|
|
112
|
-
})()
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
export function shutdown() {
|
|
116
|
-
state.shuttingDown = true
|
|
117
|
-
if (state.child) state.child.kill()
|
|
118
|
-
if (state.serverProc) state.serverProc.kill()
|
|
119
|
-
// if (state.bonjour) state.bonjour.destroy() (mDNS advertise, kept - see dev-server.ts)
|
|
120
|
-
process.exit(0)
|
|
121
|
-
}
|
package/src/watcher.ts
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import { existsSync, watch } from "node:fs"
|
|
2
|
-
import { resolve, dirname, sep, basename } from "path"
|
|
3
|
-
import { state, print, printErr } from "./util"
|
|
4
|
-
import { buildReload, sendReload, showBuildFailure, watchAllowed } from "./dev-server"
|
|
5
|
-
import { bundle } from "./bundler"
|
|
6
|
-
|
|
7
|
-
let watchers: ReturnType<typeof watch>[] = []
|
|
8
|
-
|
|
9
|
-
export function stopWatcher() {
|
|
10
|
-
for (let w of watchers) w.close()
|
|
11
|
-
watchers = []
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
async function rebuild(filename: string) {
|
|
15
|
-
if (!(await watchAllowed())) {
|
|
16
|
-
print(`[cli] Change detected: ${filename} (auto-reload paused by agent; "watch on" resumes)`)
|
|
17
|
-
return
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
print(`[cli] Change detected: ${filename}`)
|
|
21
|
-
let result = await bundle(state.source)
|
|
22
|
-
if (!result) {
|
|
23
|
-
printErr("[cli] Build failed, waiting for changes...")
|
|
24
|
-
await showBuildFailure()
|
|
25
|
-
return
|
|
26
|
-
}
|
|
27
|
-
state.currentCode = result.code
|
|
28
|
-
state.currentMap = result.map
|
|
29
|
-
state.currentManifest = result.manifest
|
|
30
|
-
await sendReload(buildReload({ code: state.currentCode, manifest: state.currentManifest }), {
|
|
31
|
-
latch: true,
|
|
32
|
-
map: state.currentMap,
|
|
33
|
-
})
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export function startWatcher() {
|
|
37
|
-
if (!state.source) return
|
|
38
|
-
let watchDir = dirname(resolve(state.source))
|
|
39
|
-
|
|
40
|
-
stopWatcher()
|
|
41
|
-
|
|
42
|
-
// An asset edit re-pushes too: the assets/ tree is part of the manifest, so
|
|
43
|
-
// clients install the new version. The tree roots at the project dir; the
|
|
44
|
-
// project dir is an ancestor of (or equal to) the entry's dir, so assets/
|
|
45
|
-
// is inside watchDir exactly when they are the same dir. An assets/ folder
|
|
46
|
-
// created after startup needs a restart or `load` to be picked up.
|
|
47
|
-
let assetsDir = resolve(state.projectDir, "assets")
|
|
48
|
-
let covered = resolve(state.projectDir) === watchDir
|
|
49
|
-
let isAsset = (filename: string) =>
|
|
50
|
-
(filename === "assets" || filename.startsWith("assets" + sep)) && !basename(filename).startsWith(".")
|
|
51
|
-
|
|
52
|
-
print(`[cli] Watching ${watchDir} for changes...`)
|
|
53
|
-
watchers.push(
|
|
54
|
-
watch(watchDir, { recursive: true }, (_event, filename) => {
|
|
55
|
-
if (!filename) return
|
|
56
|
-
if (!/\.(tsx?|jsx?)$/.test(filename) && !(covered && isAsset(filename))) return
|
|
57
|
-
rebuild(filename)
|
|
58
|
-
}),
|
|
59
|
-
)
|
|
60
|
-
|
|
61
|
-
if (!covered && existsSync(assetsDir)) {
|
|
62
|
-
watchers.push(
|
|
63
|
-
watch(assetsDir, { recursive: true }, (_event, filename) => {
|
|
64
|
-
if (!filename || basename(filename).startsWith(".")) return
|
|
65
|
-
rebuild("assets" + sep + filename)
|
|
66
|
-
}),
|
|
67
|
-
)
|
|
68
|
-
}
|
|
69
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|