@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/lib/args.ts
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { existsSync, statSync } from "node:fs"
|
|
2
|
+
import { parseArgs } from "node:util"
|
|
3
|
+
import { resolve } from "node:path"
|
|
4
|
+
import { clientsRoot } from "./dev-dir"
|
|
5
|
+
import { hint } from "./usage"
|
|
6
|
+
|
|
7
|
+
// Everything after a bare "--" is the app's argument vector, kept out of
|
|
8
|
+
// parseArgs (which would fold it into positionals) and forwarded verbatim to
|
|
9
|
+
// the runner, where it becomes flux:process argv. srt's own flags may follow
|
|
10
|
+
// the source file, so the separator is required.
|
|
11
|
+
let rawArgs = process.argv.slice(2)
|
|
12
|
+
let appArgsSep = rawArgs.indexOf("--")
|
|
13
|
+
export let appArgs = appArgsSep === -1 ? [] : rawArgs.slice(appArgsSep + 1)
|
|
14
|
+
|
|
15
|
+
// `srt tool <package>/<name> ...`: everything after the tool name belongs to
|
|
16
|
+
// the tool (its own flags, its own usage), so only the command and the tool
|
|
17
|
+
// name go through parseArgs and the rest is forwarded verbatim (tool/main.ts).
|
|
18
|
+
let isTool = rawArgs[0] === "tool"
|
|
19
|
+
export let toolArgs = isTool ? rawArgs.slice(2) : []
|
|
20
|
+
|
|
21
|
+
// A parse failure (unknown flag, missing value) is a usage error, not a
|
|
22
|
+
// crash: the parser's first sentence names it, the hint says where to look.
|
|
23
|
+
function parse() {
|
|
24
|
+
try {
|
|
25
|
+
return parseArgs({
|
|
26
|
+
args: isTool ? rawArgs.slice(0, 2) : appArgsSep === -1 ? rawArgs : rawArgs.slice(0, appArgsSep),
|
|
27
|
+
options: OPTIONS,
|
|
28
|
+
allowPositionals: true,
|
|
29
|
+
})
|
|
30
|
+
} catch (e: any) {
|
|
31
|
+
hint(String(e?.message ?? e).split(". ")[0])
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const OPTIONS = {
|
|
36
|
+
help: { type: "boolean", default: false },
|
|
37
|
+
version: { type: "boolean", default: false },
|
|
38
|
+
dev: { type: "boolean", short: "d", default: false },
|
|
39
|
+
minify: { type: "boolean", short: "m", default: false },
|
|
40
|
+
compile: { type: "boolean", default: false },
|
|
41
|
+
flux: { type: "boolean", short: "f", default: false },
|
|
42
|
+
file: { type: "boolean", default: false },
|
|
43
|
+
project: { type: "boolean", default: false },
|
|
44
|
+
lan: { type: "boolean", default: false },
|
|
45
|
+
folder: { type: "boolean", default: false },
|
|
46
|
+
app: { type: "boolean", default: false },
|
|
47
|
+
stdout: { type: "boolean", default: false },
|
|
48
|
+
json: { type: "boolean", default: false },
|
|
49
|
+
output: { type: "string", short: "o" },
|
|
50
|
+
"proxy-http": { type: "boolean", default: false },
|
|
51
|
+
fps: { type: "string" },
|
|
52
|
+
duration: { type: "string" },
|
|
53
|
+
size: { type: "string" },
|
|
54
|
+
script: { type: "string" },
|
|
55
|
+
capture: { type: "string" },
|
|
56
|
+
tunnel: { type: "boolean", default: false },
|
|
57
|
+
stats: { type: "boolean", default: false },
|
|
58
|
+
"data-root": { type: "string" },
|
|
59
|
+
client: { type: "string", short: "c" },
|
|
60
|
+
server: { type: "string" },
|
|
61
|
+
port: { type: "string" },
|
|
62
|
+
device: { type: "string" },
|
|
63
|
+
install: { type: "boolean" },
|
|
64
|
+
} as const
|
|
65
|
+
|
|
66
|
+
export let { values, positionals } = parse()
|
|
67
|
+
|
|
68
|
+
// An explicit --port: the dev server binds it (run, server), or the caller
|
|
69
|
+
// picks the server on it (client, mcp). Without it the server binds its
|
|
70
|
+
// remembered port or a free one, and callers resolve by project (mode.ts,
|
|
71
|
+
// registry.ts).
|
|
72
|
+
function resolvePort(): number | undefined {
|
|
73
|
+
let raw = values.port
|
|
74
|
+
if (raw === undefined) return undefined
|
|
75
|
+
let port = Number(raw)
|
|
76
|
+
if (!/^\d+$/.test(raw) || port < 1 || port > 65535) {
|
|
77
|
+
console.error(`Invalid --port value "${raw}": expected a port number between 1 and 65535`)
|
|
78
|
+
process.exit(1)
|
|
79
|
+
}
|
|
80
|
+
return port
|
|
81
|
+
}
|
|
82
|
+
export let port = resolvePort()
|
|
83
|
+
|
|
84
|
+
// Storage flags for a locally spawned client. Dev client trees live in
|
|
85
|
+
// ~/.solidrt/clients/client<M>/, reached through --data-root so the client
|
|
86
|
+
// runtime keeps its single pref-path default rule (see lattice/src/storage.rs
|
|
87
|
+
// and okf/backlog/cli-flux-migration.md); an explicit --data-root wins.
|
|
88
|
+
// Storage is per app under a tree, so two projects share client 0 without
|
|
89
|
+
// colliding; only two clients of the same app need distinct slots. Roots are
|
|
90
|
+
// passed absolute because the client chdirs into its app sandbox at startup.
|
|
91
|
+
export function clientStorageArgs(): string[] {
|
|
92
|
+
let root = values["data-root"]
|
|
93
|
+
let args = ["--data-root", root ? resolve(root) : clientsRoot()]
|
|
94
|
+
let client = values.client ?? "0"
|
|
95
|
+
if (!/^\d+$/.test(client)) {
|
|
96
|
+
console.error(`Invalid --client value "${client}": expected a non-negative integer`)
|
|
97
|
+
process.exit(1)
|
|
98
|
+
}
|
|
99
|
+
args.push("--client", client)
|
|
100
|
+
return args
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export let command = positionals[0]
|
|
104
|
+
export let source = positionals[1]
|
|
105
|
+
export let isTsx = source?.endsWith(".tsx") || source?.endsWith(".jsx")
|
|
106
|
+
export let isPrebuilt = source?.endsWith(".srt.js") || source?.endsWith(".srt.bin")
|
|
107
|
+
// A .srt.js also ends with .js: prebuilt wins, or the server would re-bundle
|
|
108
|
+
// a prebuilt bundle as source and skip the prebuilt load path.
|
|
109
|
+
export let isTs = (source?.endsWith(".ts") || source?.endsWith(".js")) && !isPrebuilt
|
|
110
|
+
export let isSource = isTsx || isTs
|
|
111
|
+
|
|
112
|
+
function usage(line: string): never {
|
|
113
|
+
console.error("Usage: " + line)
|
|
114
|
+
process.exit(1)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Per-command argument requirements. Called once before dispatch. The app
|
|
118
|
+
// commands take an optional entry: none means the project at the cwd
|
|
119
|
+
// (mode.ts decides, and reports a missing or wrong entry itself).
|
|
120
|
+
export function validateArgs() {
|
|
121
|
+
switch (command) {
|
|
122
|
+
case "bundle":
|
|
123
|
+
// A prebuilt .srt.js compiles to bytecode (bundle.ts); a .srt.bin
|
|
124
|
+
// already is bytecode, so there is nothing to do with it.
|
|
125
|
+
if (values.flux) {
|
|
126
|
+
if (!source || !isTs) usage("srt bundle --flux [options] <entry.[ts|js]>")
|
|
127
|
+
} else if (source && !isSource && !source.endsWith(".srt.js")) {
|
|
128
|
+
usage("srt bundle [options] [entry.[tsx|jsx|ts|js|srt.js]]")
|
|
129
|
+
}
|
|
130
|
+
break
|
|
131
|
+
case "check":
|
|
132
|
+
// An entry file, or a folder whose entries are discovered (check.ts).
|
|
133
|
+
if (source && !isSource && !(existsSync(source) && statSync(source).isDirectory())) {
|
|
134
|
+
usage("srt check [entry.[tsx|jsx|ts|js] | folder]")
|
|
135
|
+
}
|
|
136
|
+
break
|
|
137
|
+
case "demo":
|
|
138
|
+
// A number from the printed list, or the qualified name beside it.
|
|
139
|
+
if (source && !/^\d+$/.test(source) && !/^[\w.-]+\/[\w.-]+$/.test(source)) {
|
|
140
|
+
usage("srt demo [<number> | <package>/<demo>]")
|
|
141
|
+
}
|
|
142
|
+
break
|
|
143
|
+
case "render":
|
|
144
|
+
if (source && !isTsx) usage("srt render [entry.[tsx|jsx]]")
|
|
145
|
+
break
|
|
146
|
+
case "pack":
|
|
147
|
+
if (values.flux) {
|
|
148
|
+
if (!source || !isTs) usage("srt pack --flux [options] <entry.[ts|js]>")
|
|
149
|
+
} else if (source && !isSource) {
|
|
150
|
+
usage("srt pack [options] [entry.[tsx|jsx|ts|js]]")
|
|
151
|
+
}
|
|
152
|
+
break
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// The commands that start a dev server; `demo` resolves its own project.
|
|
156
|
+
let serves = command === "run" || command === "server" || command === "demo"
|
|
157
|
+
// The commands that work on a project or a file (mode.ts).
|
|
158
|
+
let onApp = command === "run" || command === "server" || command === "bundle" || command === "pack" || command === "render"
|
|
159
|
+
// --folder and --app pick a pack output shape.
|
|
160
|
+
if ((values.folder || values.app) && command !== "pack") {
|
|
161
|
+
usage("srt pack --folder|--app (--folder and --app are only valid with the pack command)")
|
|
162
|
+
}
|
|
163
|
+
if (values.folder && values.app) usage("srt pack --folder|--app (--folder and --app exclude each other)")
|
|
164
|
+
// --install is the android command's APK install step.
|
|
165
|
+
if (values.install && command !== "android") {
|
|
166
|
+
usage("srt android --install (--install is only valid with the android command)")
|
|
167
|
+
}
|
|
168
|
+
// --device picks the adb device `android` installs on.
|
|
169
|
+
if (values.device && command !== "android") {
|
|
170
|
+
usage("srt android --device <serial> (--device is only valid with the android command)")
|
|
171
|
+
}
|
|
172
|
+
// --server points a standalone client at a dev server; `run` and `server`
|
|
173
|
+
// own their server side, so it is valid nowhere else.
|
|
174
|
+
if (values.server && command !== "client") {
|
|
175
|
+
usage("srt client --server <host:port> (--server is only valid with the client command)")
|
|
176
|
+
}
|
|
177
|
+
// --json is the dev server's rebuild contract (bundle.ts).
|
|
178
|
+
if (values.json && command !== "bundle") {
|
|
179
|
+
usage("srt bundle --json (--json is only valid with the bundle command)")
|
|
180
|
+
}
|
|
181
|
+
// --port binds the dev server (`run`, `server`) or picks one (`client`,
|
|
182
|
+
// `android`, `mcp`).
|
|
183
|
+
if (port !== undefined && !serves && command !== "client" && command !== "android" && command !== "mcp") {
|
|
184
|
+
usage("srt <run|server|demo|client|android|mcp> --port <N> (--port is only valid with the run, server, demo, client, android and mcp commands)")
|
|
185
|
+
}
|
|
186
|
+
// --file/--project resolve a file argument in a project directory.
|
|
187
|
+
if ((values.file || values.project) && !onApp) {
|
|
188
|
+
usage("srt <run|server|bundle|pack|render> <file> [--file|--project] (only valid with the run, server, bundle, pack and render commands)")
|
|
189
|
+
}
|
|
190
|
+
// --lan binds every interface of a server being started.
|
|
191
|
+
if (values.lan && !serves) {
|
|
192
|
+
usage("srt <run|server|demo> --lan (--lan is only valid with the run, server and demo commands)")
|
|
193
|
+
}
|
|
194
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createRequire } from "node:module"
|
|
2
2
|
import { existsSync } from "node:fs"
|
|
3
|
-
import { resolve, dirname } from "node:path"
|
|
3
|
+
import { resolve, dirname, join } from "node:path"
|
|
4
4
|
import process from "node:process"
|
|
5
5
|
|
|
6
6
|
let require = createRequire(import.meta.url)
|
|
@@ -46,6 +46,32 @@ export function resolveBinary(name: string) {
|
|
|
46
46
|
return null
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
// The GL libraries the runner needs next to it (or, single-file, embedded as
|
|
50
|
+
// kind-3 trailer sections it extracts at boot): ANGLE's libraries on Windows
|
|
51
|
+
// and macOS, nothing on platforms with a system GL. Order matters and the
|
|
52
|
+
// runner preloads in section order: libGLESv2 must load before libEGL so
|
|
53
|
+
// libEGL's import of it resolves against the already-loaded module instead
|
|
54
|
+
// of a directory search.
|
|
55
|
+
const GL_LIB_NAMES: Partial<Record<NodeJS.Platform, string[]>> = {
|
|
56
|
+
win32: ["libGLESv2.dll", "libEGL.dll"],
|
|
57
|
+
darwin: ["libGLESv2.dylib", "libEGL.dylib"],
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// The GL libraries shipped next to the runner binary, resolved to their paths.
|
|
61
|
+
// Missing files are fatal: a pack without them cannot create a window.
|
|
62
|
+
export function runnerGlLibs(runnerPath: string): Array<{ name: string; path: string }> {
|
|
63
|
+
let names = GL_LIB_NAMES[process.platform] ?? []
|
|
64
|
+
let dir = dirname(runnerPath)
|
|
65
|
+
return names.map((name) => {
|
|
66
|
+
let path = join(dir, name)
|
|
67
|
+
if (!existsSync(path)) {
|
|
68
|
+
console.error(`Could not find ${name} next to the runner (${dir}); the packed app needs it to create a GL context.`)
|
|
69
|
+
process.exit(1)
|
|
70
|
+
}
|
|
71
|
+
return { name, path }
|
|
72
|
+
})
|
|
73
|
+
}
|
|
74
|
+
|
|
49
75
|
// The Android client APK is host-independent (it bundles native .so for the
|
|
50
76
|
// device, not the host), so it lives under dist/android/<abi>/ rather than the
|
|
51
77
|
// host triple map. arm64-v8a (a fat APK: arm64-v8a + the x86_64 emulator) and
|
|
@@ -57,6 +83,20 @@ export let ANDROID_PKG_MAP: Record<string, string> = {
|
|
|
57
83
|
"armeabi-v7a": "@solidrt/android-armeabi-v7a",
|
|
58
84
|
}
|
|
59
85
|
|
|
86
|
+
// The client version the project expects on an `abi` device: the version of
|
|
87
|
+
// its @solidrt/android-<abi> dev dependency (the release action pins it to the
|
|
88
|
+
// runtime version). Null when the package is not installed.
|
|
89
|
+
export function androidPackageVersion(abi: string): string | null {
|
|
90
|
+
let pkg = ANDROID_PKG_MAP[abi]
|
|
91
|
+
if (!pkg) return null
|
|
92
|
+
try {
|
|
93
|
+
let version = require(`${pkg}/package.json`).version
|
|
94
|
+
return typeof version === "string" ? version : null
|
|
95
|
+
} catch {
|
|
96
|
+
return null
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
60
100
|
export function resolveApk(abi: string = DEFAULT_ANDROID_ABI) {
|
|
61
101
|
// 1. SRT_HOME: contributor checkout, where `make dist-android` stages the APK
|
|
62
102
|
// under dist/android/<abi>/.
|
|
@@ -6,20 +6,22 @@ import { join } from "node:path"
|
|
|
6
6
|
const DEV_DIR_NAME = ".solidrt"
|
|
7
7
|
|
|
8
8
|
// All dev-tooling state lives in one home dotdir, one rule on every platform
|
|
9
|
-
// (okf/backlog/
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
9
|
+
// (okf/backlog/cli-flux-migration.md): servers/<key hash>/ holds a dev
|
|
10
|
+
// server's registry record (live.json, written by the server itself, which
|
|
11
|
+
// also names the folder: server/registry.ts), its remembered port and its
|
|
12
|
+
// tunnel key; clients/ is the data root for every locally spawned client,
|
|
13
|
+
// so dev client trees land in clients/client<M>/. Deleting the dir resets
|
|
14
|
+
// every bit of dev state.
|
|
13
15
|
export function devDir(...parts: string[]): string {
|
|
14
16
|
return join(homedir(), DEV_DIR_NAME, ...parts)
|
|
15
17
|
}
|
|
16
18
|
|
|
17
|
-
/** `servers
|
|
18
|
-
export function
|
|
19
|
-
return devDir("servers"
|
|
19
|
+
/** `servers/` under the dev dir: one folder per server key. */
|
|
20
|
+
export function serversRoot(): string {
|
|
21
|
+
return devDir("servers")
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
/** `clients/` under the dev dir - the --data-root for locally spawned dev clients. */
|
|
23
25
|
export function clientsRoot(): string {
|
|
24
26
|
return devDir("clients")
|
|
25
|
-
}
|
|
27
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { existsSync
|
|
1
|
+
import { existsSync } from "node:fs"
|
|
2
2
|
import { resolve } from "node:path"
|
|
3
|
-
import {
|
|
3
|
+
import { loadProject } from "./project"
|
|
4
|
+
import { fail } from "./util"
|
|
4
5
|
|
|
5
6
|
// The fonts `srt pack` appends to a solidrt binary (see
|
|
6
7
|
// okf/plans/packaged-fonts.md). By default the three Noto role defaults;
|
|
@@ -30,46 +31,31 @@ let DEFAULT_FONTS: Record<string, string> = {
|
|
|
30
31
|
function defaultFontsDir(): string | null {
|
|
31
32
|
let candidates: string[] = []
|
|
32
33
|
if (process.env.SRT_HOME) candidates.push(resolve(process.env.SRT_HOME, "alloy/assets/fonts"))
|
|
33
|
-
candidates.push(resolve(import.meta.dir, "
|
|
34
|
-
candidates.push(resolve(import.meta.dir, "
|
|
34
|
+
candidates.push(resolve(import.meta.dir, "../../fonts"))
|
|
35
|
+
candidates.push(resolve(import.meta.dir, "../../../../alloy/assets/fonts"))
|
|
35
36
|
for (let dir of candidates) {
|
|
36
37
|
if (existsSync(resolve(dir, "NotoSans.ttf"))) return dir
|
|
37
38
|
}
|
|
38
39
|
return null
|
|
39
40
|
}
|
|
40
41
|
|
|
41
|
-
function findProjectConfig(sourcePath: string): { dir: string; fonts: unknown } | null {
|
|
42
|
-
let project = findProjectPackage(sourcePath)
|
|
43
|
-
return project && { dir: project.dir, fonts: project.pkg.solidrt?.fonts }
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function fail(message: string): never {
|
|
47
|
-
console.error(message)
|
|
48
|
-
process.exit(1)
|
|
49
|
-
}
|
|
50
|
-
|
|
51
42
|
// Resolve the font set for a pack as file paths: role defaults merged with the
|
|
52
|
-
// project's `solidrt.fonts` map
|
|
43
|
+
// project's `solidrt.fonts` map (shape-checked by loadProject; file mode has
|
|
44
|
+
// no project, so defaults only). Order is roles first (sans, serif, mono),
|
|
53
45
|
// then added aliases in config order.
|
|
54
|
-
export function resolvePackFonts(
|
|
55
|
-
let
|
|
56
|
-
let overrides = config
|
|
57
|
-
if (typeof overrides !== "object" || overrides === null || Array.isArray(overrides)) {
|
|
58
|
-
fail('The "solidrt.fonts" key in package.json must be a map of alias to font file path (or false)')
|
|
59
|
-
}
|
|
46
|
+
export function resolvePackFonts(projectDir: string | null): ResolvedFont[] {
|
|
47
|
+
let project = loadProject(projectDir)
|
|
48
|
+
let overrides = project?.config.fonts ?? {}
|
|
60
49
|
|
|
61
|
-
// alias -> path relative to the
|
|
50
|
+
// alias -> path relative to the project dir, or null for a role default.
|
|
62
51
|
let selected = new Map<string, string | null>()
|
|
63
52
|
for (let role of Object.keys(DEFAULT_FONTS)) selected.set(role, null)
|
|
64
53
|
for (let [alias, value] of Object.entries(overrides)) {
|
|
65
54
|
if (value === false) {
|
|
66
55
|
if (!(alias in DEFAULT_FONTS)) fail(`"solidrt.fonts": "${alias}": false drops a default, but "${alias}" is not one of ${Object.keys(DEFAULT_FONTS).join("/")}`)
|
|
67
56
|
selected.delete(alias)
|
|
68
|
-
} else if (typeof value === "string") {
|
|
69
|
-
if (Buffer.byteLength(alias, "utf8") > 255) fail(`"solidrt.fonts": alias "${alias}" is too long (max 255 bytes)`)
|
|
70
|
-
selected.set(alias, resolve(config!.dir, value))
|
|
71
57
|
} else {
|
|
72
|
-
|
|
58
|
+
selected.set(alias, resolve(project!.dir, value))
|
|
73
59
|
}
|
|
74
60
|
}
|
|
75
61
|
|
package/src/lib/mode.ts
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// What a command works on (run, server, bundle, pack, render), decided by
|
|
2
|
+
// the cwd and the argument and never by searching upward
|
|
3
|
+
// (okf/backlog/cli-flux-migration.md):
|
|
4
|
+
//
|
|
5
|
+
// cwd has package.json argument mode
|
|
6
|
+
// yes none project (entry = solidrt.entry, default src/index.tsx)
|
|
7
|
+
// no none error
|
|
8
|
+
// no file file
|
|
9
|
+
// yes file error, unless --project (project at cwd,
|
|
10
|
+
// entry overridden) or --file (ignore the project)
|
|
11
|
+
//
|
|
12
|
+
// Project mode hangs assets, fonts, identity, isolates and build outputs off
|
|
13
|
+
// the project root; file mode has none of those, the file stands alone. The
|
|
14
|
+
// key (the canonical project root, or the canonical file path) is what the
|
|
15
|
+
// dev server registry and every control response name.
|
|
16
|
+
|
|
17
|
+
import { existsSync, realpathSync } from "node:fs"
|
|
18
|
+
import { dirname, resolve } from "node:path"
|
|
19
|
+
import { values, source, isSource, isPrebuilt } from "./args"
|
|
20
|
+
import { loadProject } from "./project"
|
|
21
|
+
import { fail } from "./util"
|
|
22
|
+
|
|
23
|
+
export type Mode =
|
|
24
|
+
| { mode: "project"; key: string; projectDir: string; entry: string }
|
|
25
|
+
| { mode: "file"; key: string; projectDir: null; entry: string }
|
|
26
|
+
|
|
27
|
+
const DEFAULT_ENTRY = "src/index.tsx"
|
|
28
|
+
|
|
29
|
+
function canonical(path: string): string {
|
|
30
|
+
try {
|
|
31
|
+
return realpathSync.native(path)
|
|
32
|
+
} catch {
|
|
33
|
+
return resolve(path)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function resolveMode(): Mode {
|
|
38
|
+
let cwd = process.cwd()
|
|
39
|
+
let pkgPath = resolve(cwd, "package.json")
|
|
40
|
+
let hasPkg = existsSync(pkgPath)
|
|
41
|
+
|
|
42
|
+
if (source !== undefined && !isSource && !(isPrebuilt && source.endsWith(".srt.js"))) {
|
|
43
|
+
fail(`Not an app entry: ${source} (expected .tsx, .jsx, .ts, .js or .srt.js)`)
|
|
44
|
+
}
|
|
45
|
+
if (source !== undefined && !existsSync(source)) fail(`Entry not found: ${source}`)
|
|
46
|
+
if (values.file && values.project) fail("--file and --project exclude each other")
|
|
47
|
+
|
|
48
|
+
if (source === undefined) {
|
|
49
|
+
if (values.file || values.project) fail("--file and --project need an entry file")
|
|
50
|
+
if (!hasPkg) fail(`No package.json in ${cwd}. Run from the project root, or pass a file to use on its own.`)
|
|
51
|
+
let declared = loadProject(cwd)!.config.entry
|
|
52
|
+
let entry = resolve(cwd, declared ?? DEFAULT_ENTRY)
|
|
53
|
+
if (!existsSync(entry)) {
|
|
54
|
+
fail(`Entry not found: ${entry}${declared ? "" : ' (set "solidrt": { "entry": ... } in package.json)'}`)
|
|
55
|
+
}
|
|
56
|
+
let key = canonical(cwd)
|
|
57
|
+
return { mode: "project", key, projectDir: key, entry }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
let entry = canonical(source)
|
|
61
|
+
if (hasPkg && !values.file && !values.project) {
|
|
62
|
+
fail(
|
|
63
|
+
`${cwd} is a project (it has a package.json) and ${source} is a file: pass --project to use the project with this entry, or --file to use the file on its own.`,
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
if (hasPkg && values.project) {
|
|
67
|
+
let key = canonical(cwd)
|
|
68
|
+
return { mode: "project", key, projectDir: key, entry }
|
|
69
|
+
}
|
|
70
|
+
if (!hasPkg && values.project) fail(`--project needs a package.json in ${cwd}`)
|
|
71
|
+
return { mode: "file", key: entry, projectDir: null, entry }
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** The directory the file routes serve: the entry's directory. */
|
|
75
|
+
export function sourceDirOf(mode: Mode): string {
|
|
76
|
+
return dirname(mode.entry)
|
|
77
|
+
}
|