@solidrt/cli 0.0.36 → 0.0.38
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/package.json +8 -7
- package/scaffold/AGENTS.md +6 -0
- package/scaffold/package.json +4 -4
- package/server/tsconfig.json +1 -0
- package/src/args.ts +18 -8
- package/src/bundler.ts +5 -0
- package/src/commands/check.ts +72 -24
- package/src/commands/server.ts +15 -1
- package/src/dev-server.ts +3 -2
- package/src/pack-folder.ts +2 -0
- package/src/project.ts +12 -2
- package/src/untyped-deps.d.ts +18 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solidrt/cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.38",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "Antoine van Wel",
|
|
6
6
|
"type": "module",
|
|
@@ -28,17 +28,18 @@
|
|
|
28
28
|
"zod": "^4.4.3"
|
|
29
29
|
},
|
|
30
30
|
"optionalDependencies": {
|
|
31
|
-
"@solidrt/darwin-arm64": "0.0.
|
|
32
|
-
"@solidrt/linux-arm64-gnu": "0.0.
|
|
33
|
-
"@solidrt/linux-x64-gnu": "0.0.
|
|
34
|
-
"@solidrt/win32-x64-msvc": "0.0.
|
|
31
|
+
"@solidrt/darwin-arm64": "0.0.38",
|
|
32
|
+
"@solidrt/linux-arm64-gnu": "0.0.38",
|
|
33
|
+
"@solidrt/linux-x64-gnu": "0.0.38",
|
|
34
|
+
"@solidrt/win32-x64-msvc": "0.0.38"
|
|
35
35
|
},
|
|
36
36
|
"peerDependencies": {
|
|
37
|
-
"@solidrt/core": "0.0.
|
|
37
|
+
"@solidrt/core": "0.0.38",
|
|
38
38
|
"typescript": "^7"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
|
-
"@solidrt/flux-types": "0.0.
|
|
41
|
+
"@solidrt/flux-types": "0.0.38",
|
|
42
|
+
"@types/babel__core": "^7.20.5",
|
|
42
43
|
"@types/bun": "latest"
|
|
43
44
|
}
|
|
44
45
|
}
|
package/scaffold/AGENTS.md
CHANGED
|
@@ -117,6 +117,12 @@ Authoritative references ship inside the installed packages - read them:
|
|
|
117
117
|
mounted. To inspect children (a typeof probe, counting), resolve them
|
|
118
118
|
first with the children() helper (re-exported from @solidrt/core) and
|
|
119
119
|
probe the resolved memo - never `typeof props.children` on the raw prop.
|
|
120
|
+
18. Cover/contain images: give `Image` a `fit` prop ("fill" | "cover" |
|
|
121
|
+
"contain" | "none" | "scale-down", CSS object-fit semantics, centered)
|
|
122
|
+
plus a box via `layout` in any form - numbers, pct(), flex. Without
|
|
123
|
+
`fit`, only NUMERIC layout sizes reach the image; `width: pct(100)`
|
|
124
|
+
alone draws at intrinsic size. `fit="cover"` is the answer for the
|
|
125
|
+
ported-web hero-image/thumbnail pattern.
|
|
120
126
|
|
|
121
127
|
## Performance model (JS is the slow lane)
|
|
122
128
|
|
package/scaffold/package.json
CHANGED
|
@@ -9,12 +9,12 @@
|
|
|
9
9
|
"android": "srt client --android"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@solidrt/core": "0.0.
|
|
13
|
-
"@solidrt/components": "0.0.
|
|
12
|
+
"@solidrt/core": "0.0.38",
|
|
13
|
+
"@solidrt/components": "0.0.38"
|
|
14
14
|
},
|
|
15
15
|
"devDependencies": {
|
|
16
|
-
"@solidrt/cli": "0.0.
|
|
17
|
-
"@solidrt/flux-types": "0.0.
|
|
16
|
+
"@solidrt/cli": "0.0.38",
|
|
17
|
+
"@solidrt/flux-types": "0.0.38",
|
|
18
18
|
"typescript": "^7"
|
|
19
19
|
}
|
|
20
20
|
}
|
package/server/tsconfig.json
CHANGED
package/src/args.ts
CHANGED
|
@@ -28,13 +28,23 @@ export let { values, positionals } = parseArgs({
|
|
|
28
28
|
allowPositionals: true,
|
|
29
29
|
})
|
|
30
30
|
|
|
31
|
-
// Storage flags for a locally spawned client
|
|
32
|
-
//
|
|
33
|
-
//
|
|
34
|
-
//
|
|
31
|
+
// Storage flags for a locally spawned client, forwarded only when given: with
|
|
32
|
+
// no --data-root the client resolves its platform pref path (see
|
|
33
|
+
// lattice/src/storage.rs); --client <N> selects the client<N>/ tree under
|
|
34
|
+
// either root. An explicit root is passed absolute because the client chdirs
|
|
35
|
+
// into its app sandbox at startup.
|
|
35
36
|
export function clientStorageArgs(): string[] {
|
|
36
|
-
let args
|
|
37
|
-
|
|
37
|
+
let args: string[] = []
|
|
38
|
+
let root = values["data-root"]
|
|
39
|
+
if (root) args.push("--data-root", resolve(root))
|
|
40
|
+
let client = values.client
|
|
41
|
+
if (client !== undefined) {
|
|
42
|
+
if (!/^\d+$/.test(client)) {
|
|
43
|
+
console.error(`Invalid --client value "${client}": expected a non-negative integer`)
|
|
44
|
+
process.exit(1)
|
|
45
|
+
}
|
|
46
|
+
args.push("--client", client)
|
|
47
|
+
}
|
|
38
48
|
return args
|
|
39
49
|
}
|
|
40
50
|
|
|
@@ -112,8 +122,8 @@ run/server options:
|
|
|
112
122
|
run/client options:
|
|
113
123
|
--size <WxH> Window size (default: 1280x720)
|
|
114
124
|
--stats Show the debug stats overlay (FPS, memory, frame timings)
|
|
115
|
-
--data-root <dir> Client data root (default:
|
|
116
|
-
--client <
|
|
125
|
+
--data-root <dir> Client data root (default: the platform pref path)
|
|
126
|
+
--client <N> Client number: its own data tree under the data root (default: 0)
|
|
117
127
|
|
|
118
128
|
client options:
|
|
119
129
|
--server <host[:port]> Connect to a dev server at this address (default port: 34884)
|
package/src/bundler.ts
CHANGED
|
@@ -108,7 +108,12 @@ export type BundleResult = {
|
|
|
108
108
|
export async function bundleWith(opts: BundleOptions): Promise<BundleResult | null> {
|
|
109
109
|
// Define values are parsed as expressions, so string values need embedded
|
|
110
110
|
// quotes - a bare word substitutes as an identifier and crashes at runtime.
|
|
111
|
+
// import.meta.env.DEV is the solidrt build-mode constant (used by core's
|
|
112
|
+
// leak sentinel; typed in core's types.d.ts). NODE_ENV stays defined as
|
|
113
|
+
// ecosystem compat only: third-party libraries bundled into apps commonly
|
|
114
|
+
// read it, and an unresolved `process` crashes at import time.
|
|
111
115
|
let define: Record<string, string> = {
|
|
116
|
+
"import.meta.env.DEV": opts.dev ? "true" : "false",
|
|
112
117
|
"process.env.NODE_ENV": opts.dev ? '"development"' : '"production"',
|
|
113
118
|
}
|
|
114
119
|
if (opts.devBase) define.__SRT_DEV_BASE__ = opts.devBase
|
package/src/commands/check.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync } from "node:fs"
|
|
1
|
+
import { existsSync, mkdirSync, rmSync } from "node:fs"
|
|
2
2
|
import { dirname, join, resolve } from "node:path"
|
|
3
3
|
import { source } from "../args"
|
|
4
4
|
import { bundleWith } from "../bundler"
|
|
@@ -12,7 +12,7 @@ import { bundleWith } from "../bundler"
|
|
|
12
12
|
|
|
13
13
|
// Walk up from the entry to the enclosing project (tsconfig.json or, failing
|
|
14
14
|
// that, package.json).
|
|
15
|
-
function findProjectRoot(entry: string): string | null {
|
|
15
|
+
export function findProjectRoot(entry: string): string | null {
|
|
16
16
|
let dir = dirname(resolve(entry))
|
|
17
17
|
let byConfig: string | null = null
|
|
18
18
|
let byPackage: string | null = null
|
|
@@ -45,18 +45,77 @@ function parseDiagnostics(output: string): Diagnostic[] {
|
|
|
45
45
|
return diagnostics
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
// The project's tsc, found by walking up from the project root: an example
|
|
49
|
+
// app can carry a tsconfig without its own node_modules (monorepo case).
|
|
50
|
+
function findTsc(fromDir: string): string | null {
|
|
51
|
+
let dir = fromDir
|
|
52
|
+
while (true) {
|
|
53
|
+
let tsc = join(dir, "node_modules", ".bin", process.platform === "win32" ? "tsc.exe" : "tsc")
|
|
54
|
+
if (existsSync(tsc)) return tsc
|
|
55
|
+
let parent = dirname(dir)
|
|
56
|
+
if (parent === dir) return null
|
|
57
|
+
dir = parent
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Typecheck the entry's program, not the enclosing project: a transient
|
|
62
|
+
// config extends the project's tsconfig and roots the program at the entry
|
|
63
|
+
// alone, so tsc checks exactly the entry's import closure - unrelated files
|
|
64
|
+
// are excluded by construction. The config lives in the project-local
|
|
65
|
+
// .srt-data (the dev-artifact dir; absolute paths inside, so its location
|
|
66
|
+
// only matters for type-package resolution, which walks up to the project's
|
|
67
|
+
// node_modules from there).
|
|
68
|
+
export async function typecheck(root: string, entry: string): Promise<{ app: Diagnostic[]; hidden: number } | null> {
|
|
69
|
+
let tsconfig = join(root, "tsconfig.json")
|
|
70
|
+
if (!existsSync(tsconfig)) {
|
|
71
|
+
console.warn("Typecheck skipped: no tsconfig.json above the entry")
|
|
72
|
+
return null
|
|
73
|
+
}
|
|
74
|
+
let tsc = findTsc(root)
|
|
75
|
+
if (!tsc) {
|
|
51
76
|
console.warn("Typecheck skipped: no tsc in the project (add the typescript devDependency)")
|
|
52
77
|
return null
|
|
53
78
|
}
|
|
54
|
-
let
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
79
|
+
let dataDir = join(root, ".srt-data")
|
|
80
|
+
mkdirSync(dataDir, { recursive: true })
|
|
81
|
+
let config = join(dataDir, `typecheck-${process.pid}.tsconfig.json`)
|
|
82
|
+
// include: [] overrides any include inherited from the extended config -
|
|
83
|
+
// files and include are unioned, so without this a base config's include
|
|
84
|
+
// would drag the whole project back into the program.
|
|
85
|
+
await Bun.write(config, JSON.stringify({ extends: tsconfig, include: [], files: [resolve(entry)] }))
|
|
86
|
+
try {
|
|
87
|
+
let proc = Bun.spawn([tsc, "-p", config, "--noEmit", "--pretty", "false"], {
|
|
88
|
+
cwd: root,
|
|
89
|
+
stdout: "pipe",
|
|
90
|
+
stderr: "pipe",
|
|
91
|
+
})
|
|
92
|
+
let [out, err] = await Promise.all([new Response(proc.stdout).text(), new Response(proc.stderr).text()])
|
|
93
|
+
await proc.exited
|
|
94
|
+
let diagnostics = parseDiagnostics(out + err)
|
|
95
|
+
let app = diagnostics.filter((d) => !d.inDependencies)
|
|
96
|
+
return { app, hidden: diagnostics.length - app.length }
|
|
97
|
+
} finally {
|
|
98
|
+
rmSync(config, { force: true })
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Print a typecheck result (diagnostics, then the one-line verdict) and return
|
|
103
|
+
// whether app-code errors were found. Callers pass repl-aware printers when
|
|
104
|
+
// the output lands over a live prompt (the dev-server startup check).
|
|
105
|
+
export function reportTypes(
|
|
106
|
+
types: { app: Diagnostic[]; hidden: number },
|
|
107
|
+
log: (...args: any[]) => void = console.log,
|
|
108
|
+
error: (...args: any[]) => void = console.error,
|
|
109
|
+
): boolean {
|
|
110
|
+
for (let d of types.app) error(d.lines.join("\n"))
|
|
111
|
+
if (types.app.length > 0) {
|
|
112
|
+
let hidden = types.hidden > 0 ? ` (${types.hidden} in dependencies hidden)` : ""
|
|
113
|
+
error(`${types.app.length} type error${types.app.length === 1 ? "" : "s"} in app code${hidden}`)
|
|
114
|
+
return true
|
|
115
|
+
}
|
|
116
|
+
if (types.hidden > 0) log(`Types OK (${types.hidden} dependency-internal errors hidden)`)
|
|
117
|
+
else log("Types OK")
|
|
118
|
+
return false
|
|
60
119
|
}
|
|
61
120
|
|
|
62
121
|
export async function runCheckCommand() {
|
|
@@ -73,19 +132,8 @@ export async function runCheckCommand() {
|
|
|
73
132
|
if (!root) {
|
|
74
133
|
console.warn("Typecheck skipped: no tsconfig.json or package.json above the entry")
|
|
75
134
|
} else {
|
|
76
|
-
let types = await typecheck(root)
|
|
77
|
-
if (types)
|
|
78
|
-
for (let d of types.app) console.error(d.lines.join("\n"))
|
|
79
|
-
if (types.app.length > 0) {
|
|
80
|
-
failed = true
|
|
81
|
-
let hidden = types.hidden > 0 ? ` (${types.hidden} in dependencies hidden)` : ""
|
|
82
|
-
console.error(`${types.app.length} type error${types.app.length === 1 ? "" : "s"} in app code${hidden}`)
|
|
83
|
-
} else if (types.hidden > 0) {
|
|
84
|
-
console.log(`Types OK (${types.hidden} dependency-internal errors hidden)`)
|
|
85
|
-
} else {
|
|
86
|
-
console.log("Types OK")
|
|
87
|
-
}
|
|
88
|
-
}
|
|
135
|
+
let types = await typecheck(root, entry)
|
|
136
|
+
if (types && reportTypes(types)) failed = true
|
|
89
137
|
}
|
|
90
138
|
|
|
91
139
|
if (failed) process.exit(1)
|
package/src/commands/server.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import pkg from "../../package.json"
|
|
2
2
|
import { source, isSource, isPrebuilt, values } from "../args"
|
|
3
|
-
import { state, shutdown } from "../util"
|
|
3
|
+
import { state, shutdown, print, printErr } from "../util"
|
|
4
|
+
import { findProjectRoot, typecheck, reportTypes } from "./check"
|
|
4
5
|
import { bundle } from "../bundler"
|
|
5
6
|
import { buildManifest, projectDirFor } from "../project"
|
|
6
7
|
import { startServer, buildReload, sendReload, showBuildFailure } from "../dev-server"
|
|
@@ -53,4 +54,17 @@ export async function runServerCommand() {
|
|
|
53
54
|
console.log(`[cli] Welcome to SolidRT${version}!`)
|
|
54
55
|
startRepl()
|
|
55
56
|
startWatcher()
|
|
57
|
+
|
|
58
|
+
// Startup typecheck, deliberately not awaited: diagnostics print over the
|
|
59
|
+
// repl when tsc finishes, and a type error never gates the boot (srt check
|
|
60
|
+
// is the hard gate). Once per server lifetime; hot reloads never typecheck.
|
|
61
|
+
// Source builds only: a prebuilt .srt.js has no checkable project here.
|
|
62
|
+
if (source && isSource) startupTypecheck(source)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async function startupTypecheck(entry: string) {
|
|
66
|
+
let root = findProjectRoot(entry)
|
|
67
|
+
if (!root) return
|
|
68
|
+
let types = await typecheck(root, entry)
|
|
69
|
+
if (types) reportTypes(types, print, printErr)
|
|
56
70
|
}
|
package/src/dev-server.ts
CHANGED
|
@@ -82,7 +82,8 @@ export async function watchAllowed(): Promise<boolean> {
|
|
|
82
82
|
try {
|
|
83
83
|
let resp = await fetch(`${INTERNAL_BASE}/watch`)
|
|
84
84
|
if (!resp.ok) return true
|
|
85
|
-
|
|
85
|
+
let data = (await resp.json()) as { enabled?: boolean }
|
|
86
|
+
return data.enabled !== false
|
|
86
87
|
} catch {
|
|
87
88
|
return true
|
|
88
89
|
}
|
|
@@ -100,7 +101,7 @@ export type ClientEntry = {
|
|
|
100
101
|
export async function getClients(): Promise<ClientEntry[]> {
|
|
101
102
|
let resp = await fetch(`${INTERNAL_BASE}/clients`)
|
|
102
103
|
if (!resp.ok) throw new Error(`Dev server /clients failed: ${resp.status}`)
|
|
103
|
-
return resp.json()
|
|
104
|
+
return resp.json() as Promise<ClientEntry[]>
|
|
104
105
|
}
|
|
105
106
|
|
|
106
107
|
// Reload code that fails to start the engine on purpose. The runtime treats a
|
package/src/pack-folder.ts
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
loadAppIdentity,
|
|
7
7
|
projectDirFor,
|
|
8
8
|
RUNTIME_VERSION,
|
|
9
|
+
SOLIDRT_VERSION,
|
|
9
10
|
type ManifestFont,
|
|
10
11
|
} from "./project"
|
|
11
12
|
import { resolvePackFonts } from "./fonts"
|
|
@@ -70,6 +71,7 @@ export function buildPackFolder(entry: string, bytecode: Buffer): PackFolder {
|
|
|
70
71
|
org: identity.org,
|
|
71
72
|
displayName: identity.displayName,
|
|
72
73
|
runtimeVersion: RUNTIME_VERSION,
|
|
74
|
+
solidrtVersion: SOLIDRT_VERSION,
|
|
73
75
|
bundle: { path: "bundle.bin", sha256: hashHex(bytecode), size: bytecode.length },
|
|
74
76
|
...(assets.length ? { assets } : {}),
|
|
75
77
|
...(fonts.length ? { fonts } : {}),
|
package/src/project.ts
CHANGED
|
@@ -61,6 +61,13 @@ function checkField(value: string, what: string) {
|
|
|
61
61
|
// derivation question is settled (see plan).
|
|
62
62
|
export const RUNTIME_VERSION = 1
|
|
63
63
|
|
|
64
|
+
// The manifest's solidrtVersion: provenance, not a compat gate like
|
|
65
|
+
// runtimeVersion - the CLI release that built the version. Published CLIs
|
|
66
|
+
// carry the real version in their package.json; the in-repo 0.0.0
|
|
67
|
+
// placeholder stamps "unknown".
|
|
68
|
+
let pkgVersion = JSON.parse(readFileSync(join(import.meta.dir, "..", "package.json"), "utf8")).version
|
|
69
|
+
export const SOLIDRT_VERSION: string = pkgVersion === "0.0.0" ? "unknown" : pkgVersion
|
|
70
|
+
|
|
64
71
|
export function buildManifest(code: string, entry: string): string {
|
|
65
72
|
let identity = loadAppIdentity(entry)
|
|
66
73
|
let sha256 = new Bun.CryptoHasher("sha256").update(code).digest("hex")
|
|
@@ -68,6 +75,7 @@ export function buildManifest(code: string, entry: string): string {
|
|
|
68
75
|
return JSON.stringify({
|
|
69
76
|
appId: identity.appId,
|
|
70
77
|
runtimeVersion: RUNTIME_VERSION,
|
|
78
|
+
solidrtVersion: SOLIDRT_VERSION,
|
|
71
79
|
bundle: { path: "bundle.js", sha256, size: Buffer.byteLength(code, "utf8") },
|
|
72
80
|
...(assets.length ? { assets } : {}),
|
|
73
81
|
...(fonts.length ? { fonts } : {}),
|
|
@@ -146,7 +154,9 @@ export function collectAssets(entry: string): { assets: ManifestAsset[]; fonts:
|
|
|
146
154
|
export function loadAppIdentity(sourcePath: string): AppIdentity {
|
|
147
155
|
let project = findProjectPackage(sourcePath)
|
|
148
156
|
let config = project?.pkg.solidrt ?? {}
|
|
149
|
-
|
|
157
|
+
// A scoped package name (@org/name) defaults to its last segment: identity
|
|
158
|
+
// fields reject path separators, and derived defaults must never fail that.
|
|
159
|
+
let fallbackName = (project?.pkg.name ?? basename(sourcePath).replace(/\.[jt]sx?$/, "")).split("/").pop()!
|
|
150
160
|
|
|
151
161
|
for (let key of ["appId", "org", "displayName"]) {
|
|
152
162
|
if (key in config && typeof config[key] !== "string") fail(`"solidrt": "${key}" must be a string`)
|
|
@@ -162,7 +172,7 @@ export function loadAppIdentity(sourcePath: string): AppIdentity {
|
|
|
162
172
|
fail(`"solidrt": "appId" must match ${APP_ID_PATTERN} (reverse-DNS recommended, e.g. "com.example.app")`)
|
|
163
173
|
}
|
|
164
174
|
}
|
|
165
|
-
let displayName = config.displayName ??
|
|
175
|
+
let displayName = config.displayName ?? fallbackName
|
|
166
176
|
let org = config.org ?? displayName
|
|
167
177
|
checkField(appId, '"appId"')
|
|
168
178
|
checkField(displayName, '"displayName"')
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Packages with no bundled or DefinitelyTyped declarations. @babel/core is
|
|
2
|
+
// covered by @types/babel__core; plugins and presets have no dedicated types
|
|
3
|
+
// by convention - they are consumed as opaque PluginItem values.
|
|
4
|
+
declare module "@babel/plugin-syntax-jsx" {
|
|
5
|
+
import { type PluginItem } from "@babel/core"
|
|
6
|
+
let plugin: PluginItem
|
|
7
|
+
export default plugin
|
|
8
|
+
}
|
|
9
|
+
declare module "@babel/preset-typescript" {
|
|
10
|
+
import { type PluginItem } from "@babel/core"
|
|
11
|
+
let preset: PluginItem
|
|
12
|
+
export default preset
|
|
13
|
+
}
|
|
14
|
+
declare module "babel-preset-solid" {
|
|
15
|
+
import { type PluginItem } from "@babel/core"
|
|
16
|
+
let preset: PluginItem
|
|
17
|
+
export default preset
|
|
18
|
+
}
|