@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.
- package/AGENTS.md +68 -38
- package/README.md +86 -15
- package/agents/assets.md +19 -5
- package/agents/debugging.md +169 -17
- package/dist/console.srtapp +94123 -73
- package/dist/server.js +3936 -0
- package/package.json +10 -11
- package/src/android/docs.md +21 -0
- package/src/android/main.ts +286 -0
- package/src/{bundler.ts → bundle/bundler.ts} +67 -55
- package/src/bundle/docs.md +12 -0
- package/src/{commands/bundle.ts → bundle/main.ts} +67 -25
- package/src/check/docs.md +10 -0
- package/src/check/main.ts +85 -0
- package/src/{commands/check.ts → check/typecheck.ts} +11 -41
- 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} +21 -15
- 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/{commands/pack.ts → pack/main.ts} +28 -17
- package/src/{packer.ts → pack/trailer.ts} +32 -42
- package/src/render/docs.md +19 -0
- package/src/{commands/render.ts → render/main.ts} +17 -15
- 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 +239 -81
- 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/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 -185
- 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 -76
- package/server/state.ts +0 -94
- package/src/args.ts +0 -210
- package/src/bundle-cli.ts +0 -13
- package/src/commands/client.ts +0 -34
- package/src/commands/mcp.ts +0 -617
- 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/repl.ts +0 -249
- package/src/util.ts +0 -122
- package/src/watcher.ts +0 -73
- /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/{server → src/server}/remap.ts +0 -0
|
@@ -1,15 +1,18 @@
|
|
|
1
|
-
import { values, source, isPrebuilt } from "../args"
|
|
1
|
+
import { values, source, isPrebuilt } from "../lib/args"
|
|
2
2
|
import {
|
|
3
3
|
bundleFlux,
|
|
4
|
-
bundleIsolatesDir,
|
|
5
4
|
bundleSolid,
|
|
5
|
+
bundleWith,
|
|
6
6
|
compileToBytecode,
|
|
7
7
|
findFluxIsolates,
|
|
8
|
+
isolateManifestAssets,
|
|
8
9
|
readPrebuiltIsolates,
|
|
9
10
|
walkFiles,
|
|
10
11
|
writeIsolates,
|
|
11
|
-
} from "
|
|
12
|
-
import {
|
|
12
|
+
} from "./bundler"
|
|
13
|
+
import { resolveMode } from "../lib/mode"
|
|
14
|
+
import { buildManifest } from "../lib/project"
|
|
15
|
+
import type { BundleOutput } from "../types/bundle"
|
|
13
16
|
import { existsSync, mkdirSync, readdirSync, rmSync } from "node:fs"
|
|
14
17
|
import { basename, dirname, join, resolve } from "node:path"
|
|
15
18
|
|
|
@@ -23,12 +26,13 @@ function writeStdout(data: string): Promise<void> {
|
|
|
23
26
|
}
|
|
24
27
|
|
|
25
28
|
// The bundle output dir (okf/backlog/build-output-dirs.md): the bundle flow's
|
|
26
|
-
// subdir of the build root, or an explicit --output dir.
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
// subdir of the build root, or an explicit --output dir. The build root is
|
|
30
|
+
// the cwd: a project command runs in its root (mode.ts), and a file on its
|
|
31
|
+
// own builds where it is run from. Only reused when it is empty or already a
|
|
32
|
+
// bundle output (a *.srt.* or *.flux.* bundle at top level) - the
|
|
33
|
+
// writePackFolder rule - so it never writes into an unrelated directory.
|
|
34
|
+
function ensureOutDir(defaultDir = join("dist", "bundle")): string {
|
|
35
|
+
let outDir = values.output ?? defaultDir
|
|
32
36
|
let existing = existsSync(outDir) ? readdirSync(outDir) : null
|
|
33
37
|
if (existing && existing.length > 0 && !existing.some((name) => /\.(srt|flux)\.(js|bin)$/.test(name))) {
|
|
34
38
|
console.error(`${resolve(outDir)} exists and is not a bundle output; choose another --output`)
|
|
@@ -64,7 +68,7 @@ async function writeBytecode(jsCode: string, outfile: string) {
|
|
|
64
68
|
console.log(`>> wrote ${binSize} bytes to ${outfile}`)
|
|
65
69
|
}
|
|
66
70
|
|
|
67
|
-
export async function
|
|
71
|
+
export async function main() {
|
|
68
72
|
if (values.flux) {
|
|
69
73
|
let entry = resolve(source!)
|
|
70
74
|
let name = basename(entry).replace(/\.[jt]s$/, "")
|
|
@@ -83,7 +87,7 @@ export async function runBundleCommand() {
|
|
|
83
87
|
await writeStdout(jsCode)
|
|
84
88
|
process.exit()
|
|
85
89
|
}
|
|
86
|
-
let outDir = ensureOutDir(
|
|
90
|
+
let outDir = ensureOutDir()
|
|
87
91
|
if (values.compile) {
|
|
88
92
|
await writeBytecode(jsCode, join(outDir, name + ".flux.bin"))
|
|
89
93
|
} else {
|
|
@@ -110,27 +114,65 @@ export async function runBundleCommand() {
|
|
|
110
114
|
process.exit()
|
|
111
115
|
}
|
|
112
116
|
|
|
117
|
+
// --json: the dev server's rebuild (server/rebuild.ts spawns it in the
|
|
118
|
+
// project root or the entry's directory with the entry and --project or
|
|
119
|
+
// --file, so the mode resolves the same). One BundleOutput object on
|
|
120
|
+
// stdout (types/bundle.d.ts), diagnostics on stderr, exit 1 with an empty
|
|
121
|
+
// stdout on a build failure. A prebuilt .srt.js is read as-is with its
|
|
122
|
+
// sibling isolate bundles.
|
|
123
|
+
if (values.json) {
|
|
124
|
+
let mode = resolveMode()
|
|
125
|
+
let result: BundleOutput | null
|
|
126
|
+
if (mode.entry.endsWith(".srt.js")) {
|
|
127
|
+
let code = await Bun.file(mode.entry).text()
|
|
128
|
+
let isolates = readPrebuiltIsolates(mode.entry).map((i) => ({ ...i, map: null }))
|
|
129
|
+
result = {
|
|
130
|
+
code,
|
|
131
|
+
map: null,
|
|
132
|
+
manifest: buildManifest(code, mode.entry, isolateManifestAssets(isolates), mode.projectDir),
|
|
133
|
+
isolates,
|
|
134
|
+
inputs: [mode.entry],
|
|
135
|
+
}
|
|
136
|
+
} else {
|
|
137
|
+
result = await bundleWith({
|
|
138
|
+
entry: mode.entry,
|
|
139
|
+
project: mode.projectDir,
|
|
140
|
+
dev: values.dev,
|
|
141
|
+
minify: values.minify,
|
|
142
|
+
})
|
|
143
|
+
}
|
|
144
|
+
if (!result) process.exit(1)
|
|
145
|
+
await writeStdout(JSON.stringify(result))
|
|
146
|
+
process.exit()
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// A prebuilt .srt.js (validateArgs admits no other prebuilt form) is
|
|
150
|
+
// compiled to bytecode: the only step left, so --compile is implied. The
|
|
151
|
+
// output lands in the bundle's own dir unless --output says otherwise; its
|
|
152
|
+
// isolate bundles compile along into the output's isolates/ (the ids match,
|
|
153
|
+
// the extension picks the form).
|
|
113
154
|
if (isPrebuilt) {
|
|
114
|
-
|
|
115
|
-
|
|
155
|
+
let jsFile = resolve(source!)
|
|
156
|
+
if (!existsSync(jsFile)) {
|
|
157
|
+
console.error(`Entry not found: ${source}`)
|
|
116
158
|
process.exit(1)
|
|
117
159
|
}
|
|
118
|
-
let
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
// isolates/ dir (the ids match, the extension picks the form).
|
|
160
|
+
let outDir = ensureOutDir(dirname(jsFile))
|
|
161
|
+
await writeBytecode(await Bun.file(jsFile).text(), join(outDir, basename(jsFile).replace(/\.js$/, ".bin")))
|
|
162
|
+
let isolatesDir = join(outDir, "isolates")
|
|
163
|
+
clearIsolates(isolatesDir, ".bin")
|
|
123
164
|
for (let isolate of readPrebuiltIsolates(jsFile)) {
|
|
124
|
-
await writeIsolateBytecode(
|
|
165
|
+
await writeIsolateBytecode(isolatesDir, isolate)
|
|
125
166
|
}
|
|
126
167
|
process.exit()
|
|
127
168
|
}
|
|
128
169
|
|
|
129
|
-
let
|
|
170
|
+
let mode = resolveMode()
|
|
171
|
+
let entry = mode.entry
|
|
130
172
|
let name = basename(entry).replace(/\.[jt]sx?$/, "")
|
|
131
173
|
|
|
132
174
|
if (values.stdout) {
|
|
133
|
-
let result = await bundleSolid()
|
|
175
|
+
let result = await bundleSolid(mode)
|
|
134
176
|
if (result.isolates.length) {
|
|
135
177
|
console.error("[cli] Warning: this app has isolate modules; --stdout carries only the main bundle")
|
|
136
178
|
}
|
|
@@ -138,11 +180,11 @@ export async function runBundleCommand() {
|
|
|
138
180
|
process.exit()
|
|
139
181
|
}
|
|
140
182
|
|
|
141
|
-
let outDir = ensureOutDir(
|
|
183
|
+
let outDir = ensureOutDir()
|
|
142
184
|
let isolatesDir = join(outDir, "isolates")
|
|
143
185
|
|
|
144
186
|
if (values.compile) {
|
|
145
|
-
let result = await bundleSolid()
|
|
187
|
+
let result = await bundleSolid(mode)
|
|
146
188
|
await writeBytecode(result.code, join(outDir, name + ".srt.bin"))
|
|
147
189
|
clearIsolates(isolatesDir, ".bin")
|
|
148
190
|
for (let isolate of result.isolates) {
|
|
@@ -151,7 +193,7 @@ export async function runBundleCommand() {
|
|
|
151
193
|
process.exit()
|
|
152
194
|
}
|
|
153
195
|
|
|
154
|
-
let result = await bundleSolid()
|
|
196
|
+
let result = await bundleSolid(mode)
|
|
155
197
|
let jsOutfile = join(outDir, name + ".srt.js")
|
|
156
198
|
await Bun.write(jsOutfile, result.code)
|
|
157
199
|
clearIsolates(isolatesDir, ".js")
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# srt check
|
|
2
|
+
|
|
3
|
+
{{ usage check }}
|
|
4
|
+
|
|
5
|
+
The gate worth wiring into CI: it builds and typechecks the app without
|
|
6
|
+
producing build output. With a folder it covers every entry under it
|
|
7
|
+
(`src/index.tsx`, `examples/*`, and in a monorepo every example app,
|
|
8
|
+
package example and package demo), so one call at the repo root answers
|
|
9
|
+
"did I break any example" before pushing. The dev server runs the same check once at
|
|
10
|
+
startup, without gating on it.
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { existsSync, statSync } from "node:fs"
|
|
2
|
+
import { Glob } from "bun"
|
|
3
|
+
import { join, resolve } from "node:path"
|
|
4
|
+
import { source } from "../lib/args"
|
|
5
|
+
import { bundleWith } from "../bundle/bundler"
|
|
6
|
+
import { findProject } from "../lib/project"
|
|
7
|
+
import { findProjectRoot, reportTypes, typecheck } from "./typecheck"
|
|
8
|
+
|
|
9
|
+
// srt check: verify the app without side effects. Bundles in memory (nothing
|
|
10
|
+
// written, so no dev-server reload fires and no build outputs land in the
|
|
11
|
+
// project) and typechecks with the project's own tsc (typecheck.ts).
|
|
12
|
+
|
|
13
|
+
// Check one entry: bundle in memory, then typecheck. Returns whether it passed.
|
|
14
|
+
async function checkEntry(entry: string): Promise<boolean> {
|
|
15
|
+
let failed = false
|
|
16
|
+
// check verifies trees of entries from one cwd, so it is the one command
|
|
17
|
+
// that walks up from each entry to its project.
|
|
18
|
+
let result = await bundleWith({ entry, dev: true, minify: false, project: findProject(entry)?.dir ?? null })
|
|
19
|
+
if (!result) {
|
|
20
|
+
// bundleWith already printed the compile errors.
|
|
21
|
+
failed = true
|
|
22
|
+
}
|
|
23
|
+
let root = findProjectRoot(entry)
|
|
24
|
+
if (!root) {
|
|
25
|
+
console.warn("Typecheck skipped: no tsconfig.json or package.json above the entry")
|
|
26
|
+
} else {
|
|
27
|
+
let types = await typecheck(root, entry)
|
|
28
|
+
if (types && reportTypes(types)) failed = true
|
|
29
|
+
}
|
|
30
|
+
return !failed
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// The entries `srt check <folder>` covers, relative to the folder (a bare
|
|
34
|
+
// `srt check` is `srt check .`): the app itself, its own examples, and in
|
|
35
|
+
// a monorepo every example app, package example and package demo. The same
|
|
36
|
+
// set CI gates, so one call at the repo root answers "did I break any
|
|
37
|
+
// example" before pushing. Entries, not files: a source no entry imports is
|
|
38
|
+
// not checked.
|
|
39
|
+
const CHECK_ALL_GLOBS = [
|
|
40
|
+
"src/index.tsx",
|
|
41
|
+
"examples/*.tsx",
|
|
42
|
+
"examples/*/src/index.tsx",
|
|
43
|
+
"packages/*/examples/*.tsx",
|
|
44
|
+
"packages/*/demos/src/*.tsx",
|
|
45
|
+
]
|
|
46
|
+
function discoverEntries(root: string): string[] {
|
|
47
|
+
let entries: string[] = []
|
|
48
|
+
for (let pattern of CHECK_ALL_GLOBS) {
|
|
49
|
+
entries.push(...[...new Glob(pattern).scanSync({ cwd: root })].map((e) => join(root, e)))
|
|
50
|
+
}
|
|
51
|
+
return entries.sort()
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export async function main() {
|
|
55
|
+
let target = source ?? "."
|
|
56
|
+
if (!existsSync(target)) {
|
|
57
|
+
// Without this, the missing file surfaces later as an internal ENOENT
|
|
58
|
+
// stack trace (scandir/Bun.build), which reads as a CLI bug - the common
|
|
59
|
+
// cause is just running from the wrong directory.
|
|
60
|
+
console.error(`No such entry: ${target} (resolved from ${process.cwd()})`)
|
|
61
|
+
process.exit(1)
|
|
62
|
+
}
|
|
63
|
+
if (!statSync(target).isDirectory()) {
|
|
64
|
+
if (!(await checkEntry(target))) process.exit(1)
|
|
65
|
+
console.log("Check passed")
|
|
66
|
+
process.exit(0)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
let entries = discoverEntries(target)
|
|
70
|
+
if (entries.length === 0) {
|
|
71
|
+
console.error(`No entries found under ${resolve(target)} (looked for ${CHECK_ALL_GLOBS.join(", ")})`)
|
|
72
|
+
process.exit(1)
|
|
73
|
+
}
|
|
74
|
+
let failures: string[] = []
|
|
75
|
+
for (let entry of entries) {
|
|
76
|
+
console.log(`== ${entry}`)
|
|
77
|
+
if (!(await checkEntry(entry))) failures.push(entry)
|
|
78
|
+
}
|
|
79
|
+
if (failures.length > 0) {
|
|
80
|
+
console.error(`${failures.length} of ${entries.length} entries failed:\n ${failures.join("\n ")}`)
|
|
81
|
+
process.exit(1)
|
|
82
|
+
}
|
|
83
|
+
console.log(`Check passed (${entries.length} entries)`)
|
|
84
|
+
process.exit(0)
|
|
85
|
+
}
|
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, rmSync } from "node:fs"
|
|
2
2
|
import { dirname, join, resolve } from "node:path"
|
|
3
|
-
import {
|
|
4
|
-
import { bundleWith, findIsolateModules } from "../bundler"
|
|
3
|
+
import { findIsolateModules } from "../bundle/bundler"
|
|
5
4
|
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
// hidden, not the caller's problem to wade through.
|
|
5
|
+
// Typechecking an app entry with the project's own tsc, reporting only
|
|
6
|
+
// diagnostics in app code. Used by `srt check` (the hard gate) and by the dev
|
|
7
|
+
// server's startup check (it spawns `srt check <entry>`). @solidrt packages ship
|
|
8
|
+
// raw .ts sources, so a strict consumer config surfaces their internal errors
|
|
9
|
+
// too; those are counted and hidden, not the caller's problem to wade through.
|
|
12
10
|
|
|
13
11
|
// Walk up from the entry to the enclosing project (tsconfig.json or, failing
|
|
14
12
|
// that, package.json).
|
|
@@ -27,7 +25,7 @@ export function findProjectRoot(entry: string): string | null {
|
|
|
27
25
|
|
|
28
26
|
// One tsc --pretty false diagnostic: the "path(line,col): error TS...: ..."
|
|
29
27
|
// head line plus any indented continuation lines.
|
|
30
|
-
type Diagnostic = { head: string; lines: string[]; inDependencies: boolean }
|
|
28
|
+
export type Diagnostic = { head: string; lines: string[]; inDependencies: boolean }
|
|
31
29
|
|
|
32
30
|
function parseDiagnostics(output: string): Diagnostic[] {
|
|
33
31
|
let diagnostics: Diagnostic[] = []
|
|
@@ -58,6 +56,8 @@ function findTsc(fromDir: string): string | null {
|
|
|
58
56
|
}
|
|
59
57
|
}
|
|
60
58
|
|
|
59
|
+
export type TypecheckResult = { app: Diagnostic[]; hidden: number }
|
|
60
|
+
|
|
61
61
|
// Typecheck the entry's program, not the enclosing project: a transient
|
|
62
62
|
// config extends the project's tsconfig and roots the program at the entry
|
|
63
63
|
// alone (plus the project's ambient declarations), so tsc checks exactly the
|
|
@@ -65,7 +65,7 @@ function findTsc(fromDir: string): string | null {
|
|
|
65
65
|
// config lives in the project-local .srt-data (the dev-artifact dir; absolute
|
|
66
66
|
// paths inside, so its location only matters for type-package resolution,
|
|
67
67
|
// which walks up to the project's node_modules from there).
|
|
68
|
-
export async function typecheck(root: string, entry: string): Promise<
|
|
68
|
+
export async function typecheck(root: string, entry: string): Promise<TypecheckResult | null> {
|
|
69
69
|
let tsconfig = join(root, "tsconfig.json")
|
|
70
70
|
if (!existsSync(tsconfig)) {
|
|
71
71
|
console.warn("Typecheck skipped: no tsconfig.json above the entry")
|
|
@@ -111,7 +111,7 @@ export async function typecheck(root: string, entry: string): Promise<{ app: Dia
|
|
|
111
111
|
// whether app-code errors were found. Callers pass repl-aware printers when
|
|
112
112
|
// the output lands over a live prompt (the dev-server startup check).
|
|
113
113
|
export function reportTypes(
|
|
114
|
-
types:
|
|
114
|
+
types: TypecheckResult,
|
|
115
115
|
log: (...args: any[]) => void = console.log,
|
|
116
116
|
error: (...args: any[]) => void = console.error,
|
|
117
117
|
): boolean {
|
|
@@ -125,33 +125,3 @@ export function reportTypes(
|
|
|
125
125
|
else log("Types OK")
|
|
126
126
|
return false
|
|
127
127
|
}
|
|
128
|
-
|
|
129
|
-
export async function runCheckCommand() {
|
|
130
|
-
let entry = source!
|
|
131
|
-
if (!existsSync(entry)) {
|
|
132
|
-
// Without this, the missing file surfaces later as an internal ENOENT
|
|
133
|
-
// stack trace (scandir/Bun.build), which reads as a CLI bug - the common
|
|
134
|
-
// cause is just running from the wrong directory.
|
|
135
|
-
console.error(`No such entry: ${entry} (resolved from ${process.cwd()})`)
|
|
136
|
-
process.exit(1)
|
|
137
|
-
}
|
|
138
|
-
let failed = false
|
|
139
|
-
|
|
140
|
-
let result = await bundleWith({ entry, dev: true, minify: false })
|
|
141
|
-
if (!result) {
|
|
142
|
-
// bundleWith already printed the compile errors.
|
|
143
|
-
failed = true
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
let root = findProjectRoot(entry)
|
|
147
|
-
if (!root) {
|
|
148
|
-
console.warn("Typecheck skipped: no tsconfig.json or package.json above the entry")
|
|
149
|
-
} else {
|
|
150
|
-
let types = await typecheck(root, entry)
|
|
151
|
-
if (types && reportTypes(types)) failed = true
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
if (failed) process.exit(1)
|
|
155
|
-
console.log("Check passed")
|
|
156
|
-
process.exit(0)
|
|
157
|
-
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# srt client
|
|
2
|
+
|
|
3
|
+
{{ usage client }}
|
|
4
|
+
|
|
5
|
+
The client half of [srt run](../server/docs.md), on its own. Without flags
|
|
6
|
+
it attaches to the dev server of the project (or file) in the current
|
|
7
|
+
directory; `--port` picks a local server by port and `--server` names any
|
|
8
|
+
address, which is how a second machine joins a server started with `--lan`.
|
|
9
|
+
A phone or tablet is [srt android](../android/docs.md).
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { values, port, clientStorageArgs } from "../lib/args"
|
|
2
|
+
import { requireBinary, run } from "../lib/util"
|
|
3
|
+
import { resolveFromCwd } from "../lib/registry"
|
|
4
|
+
|
|
5
|
+
// Standalone solidrt-go client (no dev server of its own). Without flags it
|
|
6
|
+
// attaches to the dev server of the project (or file) in the current
|
|
7
|
+
// directory, resolved from the registry; --port picks a local server by port
|
|
8
|
+
// and --server names any address. A device is `srt android`.
|
|
9
|
+
export async function main() {
|
|
10
|
+
let runner = requireBinary("solidrt-go")
|
|
11
|
+
let args: string[] = [...clientStorageArgs()]
|
|
12
|
+
if (values.size) args.push("--size", values.size)
|
|
13
|
+
let address: string
|
|
14
|
+
if (values.server) {
|
|
15
|
+
if (!values.server.includes(":")) {
|
|
16
|
+
console.error(`--server needs host:port (got "${values.server}"); dev servers have no fixed port`)
|
|
17
|
+
process.exit(1)
|
|
18
|
+
}
|
|
19
|
+
address = values.server
|
|
20
|
+
} else if (port !== undefined) {
|
|
21
|
+
address = `127.0.0.1:${port}`
|
|
22
|
+
} else {
|
|
23
|
+
let resolved = await resolveFromCwd(process.cwd())
|
|
24
|
+
if (!resolved.ok) {
|
|
25
|
+
console.error(resolved.message)
|
|
26
|
+
process.exit(1)
|
|
27
|
+
}
|
|
28
|
+
address = `127.0.0.1:${resolved.record.port}`
|
|
29
|
+
}
|
|
30
|
+
args.push("--dev-server", address)
|
|
31
|
+
let exit = await run(runner, args)
|
|
32
|
+
process.exit(exit)
|
|
33
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# srt console
|
|
2
|
+
|
|
3
|
+
{{ usage console }}
|
|
4
|
+
|
|
5
|
+
The dev console: the dev servers running on this machine, the clients
|
|
6
|
+
attached to each, and a button to start a local client for one. It is not
|
|
7
|
+
served by a dev server: the CLI ships the console pre-compiled
|
|
8
|
+
(`dist/console.srtapp`, see [srt pack --app](../pack/docs.md)) and this
|
|
9
|
+
command starts the `solidrt` runner on that file, so it opens at once and
|
|
10
|
+
runs the same whether a project is open or not.
|
|
11
|
+
|
|
12
|
+
In a checkout the file is built with `make -C packages/cli
|
|
13
|
+
dist/console.srtapp`; rerun it after editing `apps/console`. Everything
|
|
14
|
+
after `--` reaches the console as its arguments.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { existsSync } from "node:fs"
|
|
2
|
+
import { fileURLToPath } from "node:url"
|
|
3
|
+
import { appArgs } from "../lib/args"
|
|
4
|
+
import { requireBinary, run } from "../lib/util"
|
|
5
|
+
|
|
6
|
+
// srt console: the dev console (apps/console) as a packed app on the plain
|
|
7
|
+
// runner. The console is pre-compiled: dist/console.srtapp (manifest +
|
|
8
|
+
// bytecode + assets, `srt pack --app`), built by `make -C packages/cli
|
|
9
|
+
// dist/console.srtapp` - the release workflow before publishing, a checkout
|
|
10
|
+
// after editing the console. This command builds nothing: it points the
|
|
11
|
+
// runner at the file, and the runner is used in place, so a signed runner
|
|
12
|
+
// stays signed.
|
|
13
|
+
export async function main() {
|
|
14
|
+
let app = fileURLToPath(new URL("../../dist/console.srtapp", import.meta.url))
|
|
15
|
+
if (!existsSync(app)) {
|
|
16
|
+
console.error("Console not built: run make -C packages/cli dist/console.srtapp")
|
|
17
|
+
process.exit(1)
|
|
18
|
+
}
|
|
19
|
+
let exit = await run(requireBinary("solidrt"), [app, ...appArgs])
|
|
20
|
+
process.exit(exit)
|
|
21
|
+
}
|
package/src/demo/docs.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# srt demo
|
|
2
|
+
|
|
3
|
+
{{ usage demo }}
|
|
4
|
+
|
|
5
|
+
The demos that come with the packages this project installs. Without an
|
|
6
|
+
argument it lists them, numbered:
|
|
7
|
+
|
|
8
|
+
```
|
|
9
|
+
1 3d/the-third-dimension
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
`srt demo 1` runs that one, exactly as [srt run](../server/docs.md) runs any
|
|
13
|
+
project: a dev server, a local client window, reload on save, and the same
|
|
14
|
+
server options (`--lan` to reach it from a device, `--port`, `--tunnel`). The qualified
|
|
15
|
+
name works too (`srt demo 3d/the-third-dimension`), which is the stable way to
|
|
16
|
+
name one in a script - the numbers follow the list, and the list follows what
|
|
17
|
+
is installed.
|
|
18
|
+
|
|
19
|
+
A package keeps its demos in `demos/`, and that folder is one project: its own
|
|
20
|
+
package.json, one `assets/` folder, and `src/<name>.tsx` per demo. So the
|
|
21
|
+
demos of one package share a dev server and a port - start a second one while
|
|
22
|
+
the first is up and it says so. Demos from different packages run side by
|
|
23
|
+
side.
|
|
24
|
+
|
|
25
|
+
The demos ship inside the packages, so `node_modules` is where they are found:
|
|
26
|
+
`srt demo` looks in the current directory and nowhere above it. Run it from
|
|
27
|
+
the project root.
|
package/src/demo/main.ts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { readdirSync } from "node:fs"
|
|
2
|
+
import { join, resolve } from "node:path"
|
|
3
|
+
import { source } from "../lib/args"
|
|
4
|
+
|
|
5
|
+
// srt demo: the demos the installed @solidrt packages ship. A package's
|
|
6
|
+
// demos are ONE project (`<package>/demos/`: package.json, one assets/, and
|
|
7
|
+
// src/<name>.tsx per demo), so a demo runs as the project it lives in - this
|
|
8
|
+
// file only lists and resolves, and main.ts starts the ordinary dev server
|
|
9
|
+
// with its cwd set to that project. Nothing downstream knows about demos.
|
|
10
|
+
|
|
11
|
+
export type Demo = { name: string; cwd: string; entry: string }
|
|
12
|
+
|
|
13
|
+
const SCOPE = join("node_modules", "@solidrt")
|
|
14
|
+
|
|
15
|
+
/** Every demo installed here, sorted so the printed numbers are stable.
|
|
16
|
+
* The cwd and nothing above it - the same rule the server's mode resolution
|
|
17
|
+
* follows (server/mode.ts), so this lists what THIS project installed. */
|
|
18
|
+
function discover(): Demo[] {
|
|
19
|
+
let demos: Demo[] = []
|
|
20
|
+
for (let pkg of names(SCOPE)) {
|
|
21
|
+
let dir = join(SCOPE, pkg, "demos")
|
|
22
|
+
for (let file of names(join(dir, "src"))) {
|
|
23
|
+
if (!file.endsWith(".tsx")) continue
|
|
24
|
+
demos.push({ name: `${pkg}/${file.slice(0, -".tsx".length)}`, cwd: resolve(dir), entry: join("src", file) })
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return demos
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// A missing folder is the normal case (most packages ship no demos), so it
|
|
31
|
+
// reads as an empty one rather than an error.
|
|
32
|
+
function names(dir: string): string[] {
|
|
33
|
+
try {
|
|
34
|
+
return readdirSync(dir).sort()
|
|
35
|
+
} catch {
|
|
36
|
+
return []
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function list(demos: Demo[]) {
|
|
41
|
+
for (let [i, demo] of demos.entries()) console.log(` ${String(i + 1).padStart(2)} ${demo.name}`)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Lists (no argument) or resolves one demo for the dev server to serve. */
|
|
45
|
+
export async function main(): Promise<{ cwd: string; entry: string } | undefined> {
|
|
46
|
+
let demos = discover()
|
|
47
|
+
if (demos.length === 0) {
|
|
48
|
+
console.error(`No demos installed in ${process.cwd()} (looked in ${SCOPE}/*/demos/src/)`)
|
|
49
|
+
process.exit(1)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (source === undefined) {
|
|
53
|
+
list(demos)
|
|
54
|
+
console.log("\nRun one with: srt demo <number>")
|
|
55
|
+
return undefined
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// A number picks from the printed list; the qualified name it printed
|
|
59
|
+
// works too, so a demo can be named in a script without a fixed index.
|
|
60
|
+
let picked = /^\d+$/.test(source) ? demos[Number(source) - 1] : demos.find((d) => d.name === source)
|
|
61
|
+
if (!picked) {
|
|
62
|
+
console.error(`No such demo: ${source}`)
|
|
63
|
+
list(demos)
|
|
64
|
+
process.exit(1)
|
|
65
|
+
}
|
|
66
|
+
return { cwd: picked.cwd, entry: picked.entry }
|
|
67
|
+
}
|
package/src/init/docs.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# srt init
|
|
2
|
+
|
|
3
|
+
{{ usage init }}
|
|
4
|
+
|
|
5
|
+
Scaffolds a new SolidRT project into a new (empty) folder: package.json,
|
|
6
|
+
tsconfig.json, AGENTS.md, a starter src/index.tsx, an empty assets/
|
|
7
|
+
(everything in it ships with the app) and an `.mcp.json`, then installs the
|
|
8
|
+
dependencies. The picker offers extensions on an interactive terminal.
|
|
9
|
+
|
|
10
|
+
The public entry point is `bun create solidrt <dir>`, which forwards here and
|
|
11
|
+
needs nothing installed first.
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { cp, mkdir, readFile, readdir, writeFile } from "node:fs/promises"
|
|
2
2
|
import { basename, dirname, join, resolve } from "node:path"
|
|
3
|
-
import { source, values } from "../args"
|
|
4
|
-
import { multiselect, note, text } from "
|
|
3
|
+
import { source, values } from "../lib/args"
|
|
4
|
+
import { multiselect, note, text } from "./prompt"
|
|
5
5
|
|
|
6
6
|
const DEFAULT_NAME = "solidrt-app"
|
|
7
7
|
|
|
8
|
-
const SCAFFOLD_DIR = join(import.meta.dir, "
|
|
8
|
+
const SCAFFOLD_DIR = join(import.meta.dir, "scaffold")
|
|
9
9
|
const TEMPLATES_DIR = join(SCAFFOLD_DIR, "templates")
|
|
10
10
|
|
|
11
11
|
// Shared project files written for every template. Sources live in
|
|
12
|
-
// cli/scaffold/. The .gitignore is stored there as `gitignore` because npm
|
|
12
|
+
// cli/src/init/scaffold/. The .gitignore is stored there as `gitignore` because npm
|
|
13
13
|
// strips files literally named `.gitignore` from published packages, so it is
|
|
14
14
|
// renamed on the way out. The per-template src/ comes from scaffold/templates/.
|
|
15
15
|
const TEMPLATE_FILES: Array<{ from: string; to: string }> = [
|
|
@@ -31,22 +31,26 @@ function packageName(dir: string): string {
|
|
|
31
31
|
const DEFAULT_TEMPLATE = "default"
|
|
32
32
|
|
|
33
33
|
// One AGENTS.md serves every template, so the lines that point an agent at
|
|
34
|
-
//
|
|
35
|
-
// selected only the markers go, without it the block goes
|
|
36
|
-
// app never ships references to files that are not installed.
|
|
37
|
-
const MARKED_BLOCK = /^<!-- components:begin -->\n[\s\S]*?^<!-- components:end -->\n/gm
|
|
38
|
-
const MARKER = /^<!-- components:(?:begin|end) -->\n/gm
|
|
39
|
-
|
|
34
|
+
// an extension's docs are fenced between `<!-- <key>:begin/end -->` markers:
|
|
35
|
+
// with the extension selected only the markers go, without it the block goes
|
|
36
|
+
// too, so an app never ships references to files that are not installed.
|
|
40
37
|
function resolveMarkers(text: string, extensions: Extension[]): string {
|
|
41
|
-
let
|
|
42
|
-
|
|
38
|
+
for (let ext of EXTENSIONS) {
|
|
39
|
+
let selected = extensions.includes(ext)
|
|
40
|
+
let block = new RegExp(`^<!-- ${ext.key}:begin -->\\n[\\s\\S]*?^<!-- ${ext.key}:end -->\\n`, "gm")
|
|
41
|
+
let marker = new RegExp(`^<!-- ${ext.key}:(?:begin|end) -->\\n`, "gm")
|
|
42
|
+
text = selected ? text.replace(marker, "") : text.replace(block, "")
|
|
43
|
+
}
|
|
44
|
+
return text
|
|
43
45
|
}
|
|
44
46
|
|
|
45
47
|
// Optional packages an app can opt into on top of core. Each maps to a
|
|
46
48
|
// dependency in the scaffold package.json (kept when selected, removed
|
|
47
|
-
// otherwise)
|
|
49
|
+
// otherwise), to a marker key fencing its lines in scaffold/AGENTS.md, and
|
|
50
|
+
// optionally to a starter under scaffold/templates/.
|
|
48
51
|
interface Extension {
|
|
49
52
|
pkg: string
|
|
53
|
+
key: string
|
|
50
54
|
template?: string
|
|
51
55
|
description: string
|
|
52
56
|
}
|
|
@@ -54,10 +58,12 @@ interface Extension {
|
|
|
54
58
|
const EXTENSIONS: Extension[] = [
|
|
55
59
|
{
|
|
56
60
|
pkg: "@solidrt/components",
|
|
61
|
+
key: "components",
|
|
57
62
|
template: "components",
|
|
58
63
|
description: "component framework: widgets, theming, navigation",
|
|
59
64
|
},
|
|
60
|
-
{ pkg: "@solidrt/
|
|
65
|
+
{ pkg: "@solidrt/2d", key: "2d", description: "general purpose 2D library" },
|
|
66
|
+
{ pkg: "@solidrt/3d", key: "3d", description: "general purpose 3D library" },
|
|
61
67
|
]
|
|
62
68
|
|
|
63
69
|
// Resolve which extensions the app takes: an interactive picker on a TTY,
|
|
@@ -80,7 +86,7 @@ function resolveTemplate(extensions: Extension[]): string {
|
|
|
80
86
|
return extensions.find((e) => e.template)?.template ?? DEFAULT_TEMPLATE
|
|
81
87
|
}
|
|
82
88
|
|
|
83
|
-
export async function
|
|
89
|
+
export async function main() {
|
|
84
90
|
// The target folder comes from the positional arg, or an interactive prompt
|
|
85
91
|
// (defaulting to a suggested name) when omitted.
|
|
86
92
|
let dir = source
|