@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.
Files changed (98) hide show
  1. package/AGENTS.md +79 -36
  2. package/README.md +86 -15
  3. package/agents/assets.md +46 -0
  4. package/agents/debugging.md +294 -0
  5. package/dist/console.srtapp +94123 -73
  6. package/dist/server.js +3936 -0
  7. package/package.json +11 -10
  8. package/src/android/docs.md +21 -0
  9. package/src/android/main.ts +286 -0
  10. package/src/{bundler.ts → bundle/bundler.ts} +163 -76
  11. package/src/bundle/docs.md +12 -0
  12. package/src/bundle/main.ts +203 -0
  13. package/src/check/docs.md +10 -0
  14. package/src/check/main.ts +85 -0
  15. package/src/{commands/check.ts → check/typecheck.ts} +11 -34
  16. package/src/client/docs.md +9 -0
  17. package/src/client/main.ts +33 -0
  18. package/src/console/docs.md +14 -0
  19. package/src/console/main.ts +21 -0
  20. package/src/demo/docs.md +27 -0
  21. package/src/demo/main.ts +67 -0
  22. package/src/init/docs.md +11 -0
  23. package/src/{commands/init.ts → init/main.ts} +31 -25
  24. package/src/init/scaffold/AGENTS.md +98 -0
  25. package/src/init/scaffold/package.json +23 -0
  26. package/{scaffold → src/init/scaffold}/templates/components/index.tsx +2 -3
  27. package/{scaffold → src/init/scaffold}/templates/default/index.tsx +2 -3
  28. package/src/lib/args.ts +194 -0
  29. package/src/{artifacts.ts → lib/artifacts.ts} +41 -1
  30. package/src/{dev-dir.ts → lib/dev-dir.ts} +10 -8
  31. package/src/{fonts.ts → lib/fonts.ts} +12 -26
  32. package/src/lib/mode.ts +77 -0
  33. package/src/{project.ts → lib/project.ts} +109 -61
  34. package/src/lib/registry.ts +120 -0
  35. package/src/lib/server-bundle.ts +24 -0
  36. package/src/lib/usage.ts +117 -0
  37. package/src/lib/util.ts +36 -0
  38. package/src/main.ts +109 -31
  39. package/src/mcp/docs.md +22 -0
  40. package/src/mcp/main.ts +719 -0
  41. package/src/pack/docs.md +18 -0
  42. package/src/{pack-folder.ts → pack/layout.ts} +13 -22
  43. package/src/pack/main.ts +86 -0
  44. package/src/pack/trailer.ts +97 -0
  45. package/src/render/docs.md +19 -0
  46. package/src/render/main.ts +49 -0
  47. package/src/server/args.ts +126 -0
  48. package/src/server/binaries.ts +47 -0
  49. package/src/server/config.ts +54 -0
  50. package/{server → src/server}/control.ts +246 -78
  51. package/src/server/docs.md +51 -0
  52. package/src/server/line-editor.ts +200 -0
  53. package/src/server/main.ts +473 -0
  54. package/src/server/mode.ts +92 -0
  55. package/src/server/rebuild.ts +90 -0
  56. package/src/server/registry.ts +138 -0
  57. package/src/server/remap.ts +60 -0
  58. package/src/server/repl.ts +223 -0
  59. package/src/server/state.ts +54 -0
  60. package/{server → src/server}/tsconfig.json +1 -1
  61. package/{server → src/server}/tunnel.ts +6 -6
  62. package/src/server/watcher.ts +121 -0
  63. package/src/tool/main.ts +70 -0
  64. package/src/types/bundle.d.ts +24 -0
  65. package/src/types/control.d.ts +90 -0
  66. package/src/types/registry.d.ts +16 -0
  67. package/scaffold/AGENTS.md +0 -630
  68. package/scaffold/package.json +0 -22
  69. package/scaffold/templates/components/icon.tsx +0 -48
  70. package/scaffold/templates/default/icon.tsx +0 -48
  71. package/server/main.ts +0 -308
  72. package/server/rebuild.ts +0 -68
  73. package/server/remap.ts +0 -47
  74. package/server/state.ts +0 -93
  75. package/src/args.ts +0 -212
  76. package/src/bundle-cli.ts +0 -13
  77. package/src/commands/bundle.ts +0 -76
  78. package/src/commands/client.ts +0 -34
  79. package/src/commands/mcp.ts +0 -603
  80. package/src/commands/pack.ts +0 -65
  81. package/src/commands/render.ts +0 -24
  82. package/src/commands/server.ts +0 -73
  83. package/src/dev-android.ts +0 -176
  84. package/src/dev-client.ts +0 -29
  85. package/src/dev-server.ts +0 -302
  86. package/src/packer.ts +0 -103
  87. package/src/repl.ts +0 -233
  88. package/src/util.ts +0 -121
  89. package/src/watcher.ts +0 -69
  90. /package/src/{untyped-deps.d.ts → bundle/untyped-deps.d.ts} +0 -0
  91. /package/src/{prompt.ts → init/prompt.ts} +0 -0
  92. /package/{scaffold → src/init/scaffold}/gitignore +0 -0
  93. /package/{scaffold → src/init/scaffold}/icon.svg +0 -0
  94. /package/{scaffold → src/init/scaffold}/mcp.json +0 -0
  95. /package/{scaffold → src/init/scaffold}/tsconfig.json +0 -0
  96. /package/{server → src/server}/cache.ts +0 -0
  97. /package/{server → src/server}/proxy.ts +0 -0
  98. /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