@solidrt/cli 0.0.51 → 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 (93) hide show
  1. package/AGENTS.md +68 -38
  2. package/README.md +86 -15
  3. package/agents/assets.md +19 -5
  4. package/agents/debugging.md +169 -17
  5. package/dist/console.srtapp +94123 -73
  6. package/dist/server.js +3936 -0
  7. package/package.json +10 -11
  8. package/src/android/docs.md +21 -0
  9. package/src/android/main.ts +286 -0
  10. package/src/{bundler.ts → bundle/bundler.ts} +67 -55
  11. package/src/bundle/docs.md +12 -0
  12. package/src/{commands/bundle.ts → bundle/main.ts} +67 -25
  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 -41
  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} +21 -15
  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/{commands/pack.ts → pack/main.ts} +28 -17
  44. package/src/{packer.ts → pack/trailer.ts} +32 -42
  45. package/src/render/docs.md +19 -0
  46. package/src/{commands/render.ts → render/main.ts} +17 -15
  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 +239 -81
  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/repl.ts +223 -0
  58. package/src/server/state.ts +54 -0
  59. package/{server → src/server}/tsconfig.json +1 -1
  60. package/{server → src/server}/tunnel.ts +6 -6
  61. package/src/server/watcher.ts +121 -0
  62. package/src/tool/main.ts +70 -0
  63. package/src/types/bundle.d.ts +24 -0
  64. package/src/types/control.d.ts +90 -0
  65. package/src/types/registry.d.ts +16 -0
  66. package/scaffold/AGENTS.md +0 -185
  67. package/scaffold/package.json +0 -22
  68. package/scaffold/templates/components/icon.tsx +0 -48
  69. package/scaffold/templates/default/icon.tsx +0 -48
  70. package/server/main.ts +0 -308
  71. package/server/rebuild.ts +0 -76
  72. package/server/state.ts +0 -94
  73. package/src/args.ts +0 -210
  74. package/src/bundle-cli.ts +0 -13
  75. package/src/commands/client.ts +0 -34
  76. package/src/commands/mcp.ts +0 -617
  77. package/src/commands/server.ts +0 -73
  78. package/src/dev-android.ts +0 -176
  79. package/src/dev-client.ts +0 -29
  80. package/src/dev-server.ts +0 -302
  81. package/src/repl.ts +0 -249
  82. package/src/util.ts +0 -122
  83. package/src/watcher.ts +0 -73
  84. /package/src/{untyped-deps.d.ts → bundle/untyped-deps.d.ts} +0 -0
  85. /package/src/{prompt.ts → init/prompt.ts} +0 -0
  86. /package/{scaffold → src/init/scaffold}/gitignore +0 -0
  87. /package/{scaffold → src/init/scaffold}/icon.svg +0 -0
  88. /package/{scaffold → src/init/scaffold}/mcp.json +0 -0
  89. /package/{scaffold → src/init/scaffold}/tsconfig.json +0 -0
  90. /package/{server → src/server}/cache.ts +0 -0
  91. /package/{server → src/server}/proxy.ts +0 -0
  92. /package/{server → src/server}/qr.ts +0 -0
  93. /package/{server → src/server}/remap.ts +0 -0
@@ -0,0 +1,18 @@
1
+ # srt pack
2
+
3
+ {{ usage pack }}
4
+
5
+ Bundles, compiles to bytecode and appends the result to the runner as one
6
+ standalone executable; `--folder` writes the flat runner + manifest +
7
+ bundle + assets folder to `dist/pack/` instead. `--flux` packs a script for
8
+ the bare [Flux runtime](/runtime/). Experimental.
9
+
10
+ `--app` writes the app alone, without a runner: one `<entry>.srtapp` holding
11
+ the manifest, the bytecode and the assets, which any `solidrt` runner of the
12
+ same version runs as `solidrt <file>.srtapp`. The runner is used in place,
13
+ so a signed runner stays signed; the file is platform-independent. This is
14
+ how the CLI ships the [console](../console/docs.md).
15
+
16
+ Set a stable `appId` in the `solidrt` key of package.json before
17
+ distributing (it keys the app's storage folder); `pack` warns while it is
18
+ defaulted from the package name.
@@ -1,17 +1,11 @@
1
1
  import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs"
2
2
  import { basename, dirname, join, resolve } from "node:path"
3
- import {
4
- assetPathFor,
5
- collectAssets,
6
- loadAppIdentity,
7
- projectDirFor,
8
- RUNTIME_VERSION,
9
- SOLIDRT_VERSION,
10
- type ManifestFont,
11
- } from "./project"
12
- import { resolvePackFonts } from "./fonts"
13
- import { runnerGlLibs } from "./packer"
14
- import { isolateAssetPath } from "./bundler"
3
+ import { assetPathFor, collectAssets, loadAppIdentity, RUNTIME_VERSION, SOLIDRT_VERSION, type ManifestFont } from "../lib/project"
4
+ import { resolvePackFonts } from "../lib/fonts"
5
+ import type { Mode } from "../lib/mode"
6
+ import { runnerGlLibs } from "../lib/artifacts"
7
+ import { fail } from "../lib/util"
8
+ import { isolateAssetPath } from "../bundle/bundler"
15
9
 
16
10
  // The canonical flat pack folder (okf/plans/client-storage-updates.md, Pack
17
11
  // output): runner + manifest.json + bundle.bin + assets/. The manifest
@@ -20,11 +14,6 @@ import { isolateAssetPath } from "./bundler"
20
14
  // identity (org, displayName) and the complete font set with the default
21
15
  // fonts materialized under assets/fonts/.
22
16
 
23
- function fail(message: string): never {
24
- console.error(message)
25
- process.exit(1)
26
- }
27
-
28
17
  function hashHex(bytes: Uint8Array): string {
29
18
  return new Bun.CryptoHasher("sha256").update(bytes).digest("hex")
30
19
  }
@@ -41,10 +30,12 @@ export type PackFolder = {
41
30
  // `isolates` are the app's isolate bundles compiled to bytecode; they ship as
42
31
  // the manifest assets isolates/<id>.bin (the production runtime has no
43
32
  // compiler, so pack never ships isolate source).
44
- export function buildPackFolder(entry: string, bytecode: Buffer, isolates: { id: string; bytecode: Buffer }[]): PackFolder {
45
- let identity = loadAppIdentity(entry)
46
- let projectDir = projectDirFor(resolve(entry))
47
- let { assets, icon } = collectAssets(entry)
33
+ export function buildPackFolder(mode: Mode, bytecode: Buffer, isolates: { id: string; bytecode: Buffer }[]): PackFolder {
34
+ let identity = loadAppIdentity(mode.entry, mode.projectDir)
35
+ // File mode collects no assets, so the dir is only ever joined in project
36
+ // mode; the entry's dir stands in to keep the paths absolute.
37
+ let projectDir = mode.projectDir ?? dirname(mode.entry)
38
+ let { assets, icon } = collectAssets(mode.projectDir)
48
39
  let copies = assets.map((a) => ({ from: join(projectDir, a.path), to: a.path }))
49
40
  let files = isolates.map((i) => ({ to: isolateAssetPath(i.id, "bin"), bytes: i.bytecode }))
50
41
  for (let f of files) assets.push({ path: f.to, sha256: hashHex(f.bytes), size: f.bytes.length })
@@ -53,7 +44,7 @@ export function buildPackFolder(entry: string, bytecode: Buffer, isolates: { id:
53
44
  // defaults materialize under assets/fonts/ (a user file already at that
54
45
  // path must be the same bytes, otherwise the layout is ambiguous).
55
46
  let fonts: ManifestFont[] = []
56
- for (let font of resolvePackFonts(entry)) {
47
+ for (let font of resolvePackFonts(mode.projectDir)) {
57
48
  if (font.isDefault) {
58
49
  let path = "assets/fonts/" + basename(font.path)
59
50
  let bytes = readFileSync(font.path)
@@ -1,10 +1,11 @@
1
- import { values, source } from "../args"
2
- import { bundleFlux, bundleSolid, compileToBytecode, findFluxIsolates } from "../bundler"
3
- import { resolvePackFonts } from "../fonts"
4
- import { loadAppIdentity } from "../project"
5
- import { packFlux, packSolid } from "../packer"
6
- import { buildPackFolder, writePackFolder } from "../pack-folder"
7
- import { requireBinary } from "../util"
1
+ import { values, source } from "../lib/args"
2
+ import { bundleFlux, bundleSolid, compileToBytecode, findFluxIsolates } from "../bundle/bundler"
3
+ import { resolvePackFonts } from "../lib/fonts"
4
+ import { loadAppIdentity } from "../lib/project"
5
+ import { resolveMode } from "../lib/mode"
6
+ import { packApp, packFlux, packSolid } from "./trailer"
7
+ import { buildPackFolder, writePackFolder } from "./layout"
8
+ import { requireBinary } from "../lib/util"
8
9
  import { dirname, join, resolve } from "node:path"
9
10
 
10
11
  // Windows executables need the suffix; a user-given --output may already
@@ -22,10 +23,10 @@ async function writeExecutable(packed: Buffer, outfile: string) {
22
23
  console.log(`>> wrote ${packed.length} bytes to ${outfile}`)
23
24
  }
24
25
 
25
- export async function runPackCommand() {
26
+ export async function main() {
26
27
  if (values.flux) {
27
- if (values.folder) {
28
- console.error("--folder is for app packs; flux scripts have no folder output")
28
+ if (values.folder || values.app) {
29
+ console.error("--folder and --app are for app packs; flux scripts have no folder or .srtapp output")
29
30
  process.exit(1)
30
31
  }
31
32
  let outfile = exeName(values.output ?? source!.replace(/\.[jt]s$/, ""))
@@ -40,23 +41,25 @@ export async function runPackCommand() {
40
41
  process.exit()
41
42
  }
42
43
 
43
- // Both solidrt outputs are the same canonical pack: manifest + bundle.bin +
44
+ // All solidrt outputs are the same canonical pack: manifest + bundle.bin +
44
45
  // assets (fonts included). --folder writes it as a flat folder next to a
45
- // bare runner; the default single-file exe carries it as trailer sections.
46
- let identity = loadAppIdentity(source!)
46
+ // bare runner; --app writes it alone as one .srtapp for a runner to load;
47
+ // the default single-file exe carries it as trailer sections.
48
+ let mode = resolveMode()
49
+ let identity = loadAppIdentity(mode.entry, mode.projectDir)
47
50
  console.log(`>> app: ${identity.appId} (${identity.org} / ${identity.displayName})`)
48
51
  if (identity.defaulted) {
49
52
  console.warn('>> warning: no "solidrt.appId" in package.json; set a stable reverse-DNS id before distributing')
50
53
  }
51
- let fonts = resolvePackFonts(source!)
54
+ let fonts = resolvePackFonts(mode.projectDir)
52
55
  console.log(`>> fonts: ${fonts.length ? fonts.map((f) => f.alias).join(", ") : "none"}`)
53
56
 
54
- let bundled = await bundleSolid()
57
+ let bundled = await bundleSolid(mode)
55
58
  let bytecode = await compileToBytecode(bundled.code)
56
59
  let isolates = []
57
60
  for (let i of bundled.isolates) isolates.push({ id: i.id, bytecode: await compileToBytecode(i.code, i.id) })
58
61
  if (isolates.length) console.log(`>> isolates: ${isolates.map((i) => i.id).join(", ")}`)
59
- let folder = buildPackFolder(source!, bytecode, isolates)
62
+ let folder = buildPackFolder(mode, bytecode, isolates)
60
63
 
61
64
  if (values.folder) {
62
65
  let outDir = values.output ?? join("dist", "pack")
@@ -65,7 +68,15 @@ export async function runPackCommand() {
65
68
  process.exit()
66
69
  }
67
70
 
68
- let outfile = values.output ?? source!.replace(/\.[jt]sx?$/, "")
71
+ if (values.app) {
72
+ let outfile = values.output ?? mode.entry.replace(/\.[jt]sx?$/, ".srtapp")
73
+ let packed = packApp(folder, bytecode)
74
+ await Bun.write(outfile, packed)
75
+ console.log(`>> wrote ${packed.length} bytes to ${outfile}`)
76
+ process.exit()
77
+ }
78
+
79
+ let outfile = values.output ?? mode.entry.replace(/\.[jt]sx?$/, "")
69
80
  // On Windows the packed image is a PE executable; it needs a .exe name to run.
70
81
  if (process.platform === "win32" && !outfile.toLowerCase().endsWith(".exe")) {
71
82
  outfile += ".exe"
@@ -1,7 +1,7 @@
1
- import { existsSync, readFileSync } from "node:fs"
2
- import { dirname, join } from "node:path"
3
- import { requireBinary } from "./util"
4
- import type { PackFolder } from "./pack-folder"
1
+ import { readFileSync } from "node:fs"
2
+ import { runnerGlLibs } from "../lib/artifacts"
3
+ import { requireBinary } from "../lib/util"
4
+ import type { PackFolder } from "./layout"
5
5
 
6
6
  // Trailer magic identifying the runner an embedded payload belongs to. Must
7
7
  // match the runner-side checks: both runners parse the trailer through
@@ -17,32 +17,6 @@ const SECTION_MANIFEST = 1
17
17
  const SECTION_FILE = 2
18
18
  const SECTION_GL_LIB = 3
19
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
20
  type Section = { kind: number; bytes: Buffer; name?: string }
47
21
 
48
22
  // Append sections to the runner image: each section's bytes, then a table of
@@ -72,24 +46,40 @@ function packSections(runnerBytes: Buffer, sections: Section[], magic: Buffer):
72
46
  return Buffer.concat([...parts, ...entries, tail, magic])
73
47
  }
74
48
 
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[] = [
49
+ // The pack folder in section form - the canonical manifest verbatim, then
50
+ // every manifest-listed file named by its manifest path. Bundle, fonts, and
51
+ // identity all come from the manifest; assets are read in place via ranged
52
+ // reads at their section offsets, so nothing is unpacked at runtime.
53
+ function appSections(folder: PackFolder, bytecode: Buffer): Section[] {
54
+ return [
86
55
  { kind: SECTION_MANIFEST, bytes: Buffer.from(folder.manifest, "utf8") },
87
56
  { kind: SECTION_FILE, bytes: bytecode, name: "bundle.bin" },
88
57
  ...folder.copies.map((c) => ({ kind: SECTION_FILE, bytes: readFileSync(c.from), name: c.to })),
89
58
  ...folder.files.map((f) => ({ kind: SECTION_FILE, bytes: f.bytes, name: f.to })),
59
+ ]
60
+ }
61
+
62
+ // The single-file solidrt executable: the runner image plus the app sections.
63
+ // GL libraries ride along as kind-3 sections (runtime freight, deliberately
64
+ // outside the manifest); the runner extracts those to its cache and preloads
65
+ // them before window setup.
66
+ export function packSolid(folder: PackFolder, bytecode: Buffer): Buffer {
67
+ let runnerPath = requireBinary("solidrt")
68
+ let sections: Section[] = [
69
+ ...appSections(folder, bytecode),
90
70
  ...runnerGlLibs(runnerPath).map((lib) => ({ kind: SECTION_GL_LIB, bytes: readFileSync(lib.path), name: lib.name })),
91
71
  ]
92
- return packSections(runnerBytes, sections, MAGIC.solidrt)
72
+ return packSections(readFileSync(runnerPath), sections, MAGIC.solidrt)
73
+ }
74
+
75
+ // A standalone .srtapp: the app sections alone, no runner in front and no GL
76
+ // libraries (the runner that loads it brings its own). The runner parses it
77
+ // exactly as it parses its own image (lattice/src/main.rs, load_payload), so
78
+ // `solidrt <file>.srtapp` runs it with the runner used in place - nothing is
79
+ // copied or appended to, and a signed runner stays signed. The extension is
80
+ // a convention; the magic is the contract.
81
+ export function packApp(folder: PackFolder, bytecode: Buffer): Buffer {
82
+ return packSections(Buffer.alloc(0), appSections(folder, bytecode), MAGIC.solidrt)
93
83
  }
94
84
 
95
85
  // The single-file flux executable: the fluxrt runner plus the program in the
@@ -0,0 +1,19 @@
1
+ # srt render
2
+
3
+ {{ usage render }}
4
+
5
+ Replays an optional recorded script and writes frames, which is how an app
6
+ produces video or deterministic screenshots with no display attached:
7
+ rendering uses SDL's offscreen driver, or alloy's own EGL pbuffer where that
8
+ driver cannot go headless.
9
+
10
+ ```sh
11
+ srt run --capture session.json
12
+ srt render --script session.json --fps 60 --duration 5
13
+ ```
14
+
15
+ `--capture` records key events from connected clients to a script (pointer
16
+ input is not captured yet). `--size` is physical output pixels: layout runs
17
+ at exactly that size, so frames are identical on every machine. Frames land
18
+ in the directory the command runs from; `-o <path>` picks another (or a
19
+ path prefix for the `-NNNNNN.png` names).
@@ -1,20 +1,22 @@
1
- import { appArgs, source, values } from "../args"
2
- import { requireBinary, run } from "../util"
3
- import { bundleSolid, writeIsolates } from "../bundler"
4
- import { collectAssets, projectDirFor } from "../project"
1
+ import { appArgs, values } from "../lib/args"
2
+ import { requireBinary, run } from "../lib/util"
3
+ import { bundleSolid, writeIsolates } from "../bundle/bundler"
4
+ import { collectAssets } from "../lib/project"
5
+ import { resolveMode } from "../lib/mode"
5
6
  import { cpSync, mkdirSync, rmSync } from "node:fs"
6
7
  import { basename, dirname, join, resolve } from "path"
7
8
 
8
- export async function runRenderCommand() {
9
- let entry = resolve(source!)
10
- let projectDir = projectDirFor(entry)
11
- let result = await bundleSolid()
9
+ export async function main() {
10
+ let mode = resolveMode()
11
+ let entry = mode.entry
12
+ let result = await bundleSolid(mode)
12
13
  // The staged run dir (okf/backlog/build-output-dirs.md): bundle +
13
14
  // isolates/ + assets/ under one root - the shape of an installed version
14
- // dir, so the runtime's assets mount resolves both trees. Wiped first so
15
- // removed isolates and deleted assets cannot go stale; render owns this
16
- // subdir and nothing else under dist/.
17
- let outDir = join(projectDir, "dist", "render")
15
+ // dir, so the runtime's assets mount resolves both trees. Under the cwd's
16
+ // dist/ (the build root, see the bundle command). Wiped first so removed
17
+ // isolates and deleted assets cannot go stale; render owns this subdir
18
+ // and nothing else under dist/.
19
+ let outDir = resolve("dist", "render")
18
20
  rmSync(outDir, { recursive: true, force: true })
19
21
  let jsOutfile = join(outDir, basename(entry).replace(/\.[jt]sx?$/, "") + ".srt.js")
20
22
  await Bun.write(jsOutfile, result.code)
@@ -22,11 +24,11 @@ export async function runRenderCommand() {
22
24
  // The project's assets/ tree, copied in (dotfiles filtered, like a pack)
23
25
  // so `assets/...` resolves like it does under the dev server and in a
24
26
  // packed app (the runtime's cwd is the data sandbox, which holds no
25
- // assets).
26
- for (let asset of collectAssets(entry).assets) {
27
+ // assets). File mode collects none.
28
+ for (let asset of collectAssets(mode.projectDir).assets) {
27
29
  let dest = join(outDir, asset.path)
28
30
  mkdirSync(dirname(dest), { recursive: true })
29
- cpSync(join(projectDir, asset.path), dest)
31
+ cpSync(join(mode.projectDir!, asset.path), dest)
30
32
  }
31
33
  let runner = requireBinary("solidrt-go")
32
34
  let playbackArgs = ["--playback"]
@@ -0,0 +1,126 @@
1
+ // The server's command line: the interface `srt run` / `srt server` (a bun
2
+ // launcher that spawns this process) and the console (a flux app that spawns
3
+ // it or imports it) share, so it takes flags and never a config blob
4
+ // (okf/done/srt-command-folders.md):
5
+ //
6
+ // flux server.js [file] [--project|--file] [--port N] [--lan] [--proxy-http]
7
+ // [--capture f] [--tunnel] [--stats] [--minify]
8
+ // [--client N [--data-root d] [--size WxH]] [-- args]
9
+ //
10
+ // --client N spawns the local solidrt-go client with data slot N (`srt run`);
11
+ // without it the server runs alone (`srt server`). Everything after a bare
12
+ // "--" is the app's argument vector (flux:process argv on every client).
13
+ // flux:process argv is the tail after the script, so a small parser covers
14
+ // it: boolean flags, valued flags, one positional, the "--" tail.
15
+
16
+ import { argv } from "flux:process"
17
+
18
+ export type ServerArgs = {
19
+ /** The entry file argument, or none (the project at the cwd). */
20
+ entry: string | undefined
21
+ project: boolean
22
+ file: boolean
23
+ lan: boolean
24
+ proxyHttp: boolean
25
+ tunnel: boolean
26
+ stats: boolean
27
+ minify: boolean
28
+ port: number | undefined
29
+ capture: string | undefined
30
+ /** The local client's data slot, or null for no local client. */
31
+ client: number | null
32
+ dataRoot: string | undefined
33
+ size: string | undefined
34
+ appArgs: string[]
35
+ }
36
+
37
+ // A fatal usage or configuration error. flux has no exit call: an uncaught
38
+ // error at startup ends the process with a nonzero status and the message
39
+ // on stderr, which is what a failed launch needs.
40
+ export function fail(message: string): never {
41
+ throw new Error(message)
42
+ }
43
+
44
+ function integer(name: string, raw: string, min: number, max: number): number {
45
+ let value = Number(raw)
46
+ if (!/^\d+$/.test(raw) || value < min || value > max) {
47
+ fail(`Invalid --${name} value "${raw}": expected an integer between ${min} and ${max}`)
48
+ }
49
+ return value
50
+ }
51
+
52
+ export function parseArgs(raw: string[] = argv): ServerArgs {
53
+ let sep = raw.indexOf("--")
54
+ let own = sep === -1 ? raw : raw.slice(0, sep)
55
+ let args: ServerArgs = {
56
+ entry: undefined,
57
+ project: false,
58
+ file: false,
59
+ lan: false,
60
+ proxyHttp: false,
61
+ tunnel: false,
62
+ stats: false,
63
+ minify: false,
64
+ port: undefined,
65
+ capture: undefined,
66
+ client: null,
67
+ dataRoot: undefined,
68
+ size: undefined,
69
+ appArgs: sep === -1 ? [] : raw.slice(sep + 1),
70
+ }
71
+ let value = (name: string, i: number): string => {
72
+ let v = own[i]
73
+ if (v === undefined || v.startsWith("--")) fail(`--${name} needs a value`)
74
+ return v
75
+ }
76
+ for (let i = 0; i < own.length; i++) {
77
+ let arg = own[i]!
78
+ if (!arg.startsWith("--")) {
79
+ if (args.entry !== undefined) fail(`Unexpected argument: ${arg}`)
80
+ args.entry = arg
81
+ continue
82
+ }
83
+ let name = arg.slice(2)
84
+ switch (name) {
85
+ case "project":
86
+ args.project = true
87
+ break
88
+ case "file":
89
+ args.file = true
90
+ break
91
+ case "lan":
92
+ args.lan = true
93
+ break
94
+ case "proxy-http":
95
+ args.proxyHttp = true
96
+ break
97
+ case "tunnel":
98
+ args.tunnel = true
99
+ break
100
+ case "stats":
101
+ args.stats = true
102
+ break
103
+ case "minify":
104
+ args.minify = true
105
+ break
106
+ case "port":
107
+ args.port = integer(name, value(name, ++i), 1, 65535)
108
+ break
109
+ case "capture":
110
+ args.capture = value(name, ++i)
111
+ break
112
+ case "client":
113
+ args.client = integer(name, value(name, ++i), 0, Number.MAX_SAFE_INTEGER)
114
+ break
115
+ case "data-root":
116
+ args.dataRoot = value(name, ++i)
117
+ break
118
+ case "size":
119
+ args.size = value(name, ++i)
120
+ break
121
+ default:
122
+ fail(`Unknown flag: ${arg}`)
123
+ }
124
+ }
125
+ return args
126
+ }
@@ -0,0 +1,47 @@
1
+ // Where the server finds what it spawns (okf/done/srt-command-folders.md):
2
+ //
3
+ // - the platform binaries (solidrt-go) in SRT_PLATFORM_DIR, which srt sets to
4
+ // the platform package it resolved, else a checkout's SRT_HOME/dist/<triple>;
5
+ // - srt itself, run as `bun <SRT_CLI>/bin/srt`, so the bundle and the startup
6
+ // typecheck run by command name (`srt bundle --json`, `srt check`): SRT_CLI
7
+ // is the @solidrt/cli package root (srt sets it; a checkout's is
8
+ // SRT_HOME/packages/cli) and SRT_BUN the bun to use (srt's own; else PATH).
9
+ //
10
+ // So `flux server.js` started by hand needs SRT_HOME (a built checkout) or
11
+ // the three variables, and started by srt needs nothing.
12
+
13
+ import { file } from "flux:fs"
14
+ import { join } from "flux:path"
15
+ import { arch, env, platform } from "flux:process"
16
+ import { fail } from "./args"
17
+
18
+ // Host platform -> dist triple, as src/artifacts.ts maps it for bun.
19
+ const TRIPLES: Record<string, string> = {
20
+ "linux-x64": "linux-x64-gnu",
21
+ "linux-arm64": "linux-arm64-gnu",
22
+ "darwin-arm64": "darwin-arm64",
23
+ "win32-x64": "win32-x64-msvc",
24
+ }
25
+
26
+ function platformDir(): string | null {
27
+ if (env.SRT_PLATFORM_DIR) return env.SRT_PLATFORM_DIR
28
+ let triple = TRIPLES[`${platform}-${arch}`]
29
+ return env.SRT_HOME && triple ? join(env.SRT_HOME, "dist", triple) : null
30
+ }
31
+
32
+ /** The absolute path of a platform binary, or a failed launch. */
33
+ export async function requireBinary(name: string): Promise<string> {
34
+ let dir = platformDir()
35
+ let path = dir ? join(dir, name + (platform === "win32" ? ".exe" : "")) : null
36
+ if (path && (await file(path).exists())) return path
37
+ fail(
38
+ `Could not find the ${name} binary${dir ? ` in ${dir}` : ""}. Run through srt, or set SRT_HOME to a SolidRT checkout built with make client.`,
39
+ )
40
+ }
41
+
42
+ /** The srt command prefix, `[bun, <cli>/bin/srt]`, for the bun-side commands. */
43
+ export function srtCommand(): string[] {
44
+ let cli = env.SRT_CLI ?? (env.SRT_HOME ? join(env.SRT_HOME, "packages", "cli") : undefined)
45
+ if (!cli) fail("Could not find srt: set SRT_CLI to the @solidrt/cli package root, or SRT_HOME to a SolidRT checkout.")
46
+ return [env.SRT_BUN ?? "bun", join(cli, "bin", "srt")]
47
+ }
@@ -0,0 +1,54 @@
1
+ // What the server resolved at startup from its flags (args.ts), its mode
2
+ // (mode.ts) and its environment (binaries.ts), shared with the route
3
+ // handlers through state.ts.
4
+
5
+ export type ServerConfig = {
6
+ /** Project mode serves the project at its root; file mode serves one file. */
7
+ mode: "project" | "file"
8
+ /** The canonical project root (project mode) or file path (file mode): the
9
+ * registry key, and what every control response names. */
10
+ key: string
11
+ /** This server's folder under ~/.solidrt/servers/: the registry record,
12
+ * the remembered port, the tunnel key. */
13
+ serverDir: string
14
+ /** The app entry (absolute path) rebuilt on a reload. */
15
+ entry: string
16
+ /** Directory served by the file routes (the entry's directory). */
17
+ sourceDir: string
18
+ /** Project root whose assets/ folder the /assets/ route serves; null in
19
+ * file mode (no assets). */
20
+ projectDir: string | null
21
+ /** Where the bun-side commands run and the entry arguments they take
22
+ * (`<entry> --project` in the project root, `<entry> --file` in the
23
+ * entry's directory), so they resolve the same mode this server did. */
24
+ cwd: string
25
+ entryArgs: string[]
26
+ /** The srt command prefix (binaries.ts) the bundle and the startup
27
+ * typecheck run through. */
28
+ srt: string[]
29
+ /** An explicit --port; otherwise the remembered port is tried, then the
30
+ * first free one. */
31
+ port?: number
32
+ /** Bind every interface (and announce the LAN address); default loopback. */
33
+ lan: boolean
34
+ /** The address clients are told to reach this server on. */
35
+ address: string
36
+ proxyHttp: boolean
37
+ /** The session's app arguments (the tail after a bare "--"), included in
38
+ * every reload push as flux:process argv. */
39
+ args: string[]
40
+ /** Minify the rebuild output. */
41
+ minify: boolean
42
+ /** Enable the sqlite-backed proxy cache. */
43
+ cache: boolean
44
+ /** Build outputs and the proxy cache: the project's .srt-data, or the
45
+ * server folder in file mode. */
46
+ cacheDir: string
47
+ /** Destination for captured key events, or unset when off. */
48
+ capture?: string
49
+ stats: boolean
50
+ /** Accept ticket-paired clients through the p2p tunnel. */
51
+ tunnel: boolean
52
+ /** The local client to spawn once the port is bound, or null. */
53
+ client: { cmd: string; args: string[] } | null
54
+ }