@solidrt/cli 0.0.49 → 0.0.51
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 +31 -5
- package/agents/assets.md +32 -0
- package/agents/debugging.md +142 -0
- package/package.json +9 -6
- package/scaffold/AGENTS.md +102 -541
- package/scaffold/package.json +5 -4
- package/server/control.ts +15 -5
- package/server/main.ts +8 -8
- package/server/rebuild.ts +10 -2
- package/server/remap.ts +46 -33
- package/server/state.ts +6 -5
- package/src/args.ts +5 -7
- package/src/bundler.ts +112 -37
- package/src/commands/bundle.ts +106 -21
- package/src/commands/check.ts +7 -0
- package/src/commands/init.ts +61 -77
- package/src/commands/mcp.ts +63 -12
- package/src/commands/pack.ts +18 -8
- package/src/commands/render.ts +28 -5
- package/src/commands/server.ts +5 -5
- package/src/dev-server.ts +7 -7
- package/src/packer.ts +16 -12
- package/src/prompt.ts +46 -72
- package/src/repl.ts +27 -11
- package/src/util.ts +4 -3
- package/src/watcher.ts +7 -3
- package/scaffold/templates/components/template.json +0 -4
- package/scaffold/templates/default/template.json +0 -4
package/src/commands/server.ts
CHANGED
|
@@ -2,8 +2,8 @@ import pkg from "../../package.json"
|
|
|
2
2
|
import { source, isSource, isPrebuilt, values } from "../args"
|
|
3
3
|
import { state, shutdown, print, printErr } from "../util"
|
|
4
4
|
import { findProjectRoot, typecheck, reportTypes } from "./check"
|
|
5
|
-
import { bundle } from "../bundler"
|
|
6
|
-
import {
|
|
5
|
+
import { bundle, bundleMaps, prebuiltManifest } from "../bundler"
|
|
6
|
+
import { projectDirFor } from "../project"
|
|
7
7
|
import { startServer, buildReload, sendReload, showBuildFailure } from "../dev-server"
|
|
8
8
|
import { startRepl } from "../repl"
|
|
9
9
|
import { startWatcher } from "../watcher"
|
|
@@ -34,11 +34,11 @@ export async function runServerCommand() {
|
|
|
34
34
|
let initialResult = await bundle()
|
|
35
35
|
if (initialResult) {
|
|
36
36
|
state.currentCode = initialResult.code
|
|
37
|
-
state.
|
|
37
|
+
state.currentMaps = bundleMaps(initialResult)
|
|
38
38
|
state.currentManifest = initialResult.manifest
|
|
39
39
|
await sendReload(buildReload({ code: state.currentCode, manifest: state.currentManifest }), {
|
|
40
40
|
latch: true,
|
|
41
|
-
|
|
41
|
+
maps: state.currentMaps,
|
|
42
42
|
})
|
|
43
43
|
} else {
|
|
44
44
|
await showBuildFailure()
|
|
@@ -46,7 +46,7 @@ export async function runServerCommand() {
|
|
|
46
46
|
} else if (source && isPrebuilt && source.endsWith(".srt.js")) {
|
|
47
47
|
let path = resolve(source)
|
|
48
48
|
state.currentCode = await Bun.file(path).text()
|
|
49
|
-
state.currentManifest =
|
|
49
|
+
state.currentManifest = prebuiltManifest(state.currentCode, path, state.projectDir)
|
|
50
50
|
await sendReload(buildReload({ code: state.currentCode, manifest: state.currentManifest }), { latch: true })
|
|
51
51
|
}
|
|
52
52
|
|
package/src/dev-server.ts
CHANGED
|
@@ -28,8 +28,7 @@ export let DEV_PORT = resolveDevPort()
|
|
|
28
28
|
|
|
29
29
|
// The dev server itself is a flux script (packages/cli/server/), spawned by
|
|
30
30
|
// srt: bundling, file watching, and the repl stay here and drive the server
|
|
31
|
-
// process over its loopback-only /__internal__/ routes.
|
|
32
|
-
// docs/flux-dev-server-plan.md.
|
|
31
|
+
// process over its loopback-only /__internal__/ routes.
|
|
33
32
|
|
|
34
33
|
const INTERNAL_BASE = `http://${DEV_HOST}:${DEV_PORT}/__internal__`
|
|
35
34
|
|
|
@@ -62,9 +61,10 @@ async function post(path: string, body: object) {
|
|
|
62
61
|
* or to every client when omitted. `latch` keeps the message for late-joining
|
|
63
62
|
* clients (code reloads latch, one-shot bytecode loads do not); `sourceDir`
|
|
64
63
|
* moves the server's file-serving root (repl `load`; the project root is
|
|
65
|
-
* fixed for the life of the run); `
|
|
66
|
-
* server-side for stack-trace
|
|
67
|
-
*
|
|
64
|
+
* fixed for the life of the run); `maps` is the bundle's sourcemaps keyed by
|
|
65
|
+
* module name ("main", each isolate id), kept server-side for stack-trace
|
|
66
|
+
* remapping (omitting it clears the server's maps, so a mapless reload never
|
|
67
|
+
* remaps against stale ones).
|
|
68
68
|
*/
|
|
69
69
|
export async function sendReload(
|
|
70
70
|
message: object,
|
|
@@ -73,7 +73,7 @@ export async function sendReload(
|
|
|
73
73
|
latch?: boolean
|
|
74
74
|
sourceDir?: string
|
|
75
75
|
entry?: string
|
|
76
|
-
|
|
76
|
+
maps?: Record<string, string> | null
|
|
77
77
|
} = {},
|
|
78
78
|
) {
|
|
79
79
|
await post("/reload", { message, ...opts })
|
|
@@ -291,7 +291,7 @@ export async function startServer() {
|
|
|
291
291
|
process.on("exit", removeLiveRecord)
|
|
292
292
|
|
|
293
293
|
// mDNS advertise (dropped, code kept for future use - see
|
|
294
|
-
//
|
|
294
|
+
// okf/backlog/mdns-discovery.md): the p2p ticket is the cross-device connect
|
|
295
295
|
// story now. If advertise returns, it belongs next to the server (a flux
|
|
296
296
|
// capability), not here.
|
|
297
297
|
//
|
package/src/packer.ts
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs"
|
|
2
2
|
import { dirname, join } from "node:path"
|
|
3
3
|
import { requireBinary } from "./util"
|
|
4
|
-
import { compileToBytecode } from "./bundler"
|
|
5
4
|
import type { PackFolder } from "./pack-folder"
|
|
6
5
|
|
|
7
|
-
// Trailer magic identifying the runner an embedded payload belongs to. Must
|
|
8
|
-
// the runner-side checks:
|
|
9
|
-
// lattice/src/main.rs
|
|
6
|
+
// Trailer magic identifying the runner an embedded payload belongs to. Must
|
|
7
|
+
// match the runner-side checks: both runners parse the trailer through
|
|
8
|
+
// forge/src/trailer.rs (readers: lattice/src/main.rs, flux/src/bin/fluxrt.rs).
|
|
10
9
|
const MAGIC = {
|
|
11
10
|
fluxrt: Buffer.from([0x46, 0x4c, 0x55, 0x58, 0x52, 0x54, 0x88, 0x44]), // "FLUXRT\x88\x44"
|
|
12
11
|
solidrt: Buffer.from([0x53, 0x4f, 0x4c, 0x49, 0x44, 0x52, 0x54, 0x88, 0x44]), // "SOLIDRT\x88\x44"
|
|
13
12
|
}
|
|
14
13
|
|
|
15
|
-
// Section kinds in the
|
|
14
|
+
// Section kinds in the trailer. Must match forge/src/trailer.rs (fluxrt only
|
|
15
|
+
// consumes kind-2 file sections; solidrt consumes all three).
|
|
16
16
|
const SECTION_MANIFEST = 1
|
|
17
17
|
const SECTION_FILE = 2
|
|
18
18
|
const SECTION_GL_LIB = 3
|
|
@@ -92,12 +92,16 @@ export function packSolid(folder: PackFolder, bytecode: Buffer): Buffer {
|
|
|
92
92
|
return packSections(runnerBytes, sections, MAGIC.solidrt)
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
-
//
|
|
96
|
-
//
|
|
97
|
-
|
|
98
|
-
|
|
95
|
+
// The single-file flux executable: the fluxrt runner plus the program in the
|
|
96
|
+
// same section trailer packSolid uses, kind-2 file sections only -
|
|
97
|
+
// "bundle.bin" is the program, each isolate module "isolates/<id>.bin".
|
|
98
|
+
// Like packSolid, this assembles precompiled bytecode; the pack command
|
|
99
|
+
// compiles.
|
|
100
|
+
export function packFlux(bytecode: Buffer, isolates: { id: string; bytecode: Buffer }[] = []): Buffer {
|
|
99
101
|
let runnerBytes = readFileSync(requireBinary("fluxrt"))
|
|
100
|
-
let
|
|
101
|
-
|
|
102
|
-
|
|
102
|
+
let sections: Section[] = [
|
|
103
|
+
{ kind: SECTION_FILE, bytes: bytecode, name: "bundle.bin" },
|
|
104
|
+
...isolates.map((i) => ({ kind: SECTION_FILE, bytes: i.bytecode, name: `isolates/${i.id}.bin` })),
|
|
105
|
+
]
|
|
106
|
+
return packSections(runnerBytes, sections, MAGIC.fluxrt)
|
|
103
107
|
}
|
package/src/prompt.ts
CHANGED
|
@@ -1,18 +1,21 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as clack from "@clack/prompts"
|
|
2
|
+
|
|
3
|
+
// Thin wrappers over @clack/prompts. Every prompt guards on a TTY: a non-TTY
|
|
4
|
+
// stdin resolves the default rather than blocking on input that will never
|
|
5
|
+
// arrive. Cancelling (ctrl-c) exits the process.
|
|
6
|
+
|
|
7
|
+
function unwrap<T>(value: T | symbol): T {
|
|
8
|
+
if (clack.isCancel(value)) {
|
|
9
|
+
clack.cancel("Cancelled")
|
|
10
|
+
process.exit(130)
|
|
11
|
+
}
|
|
12
|
+
return value as T
|
|
13
|
+
}
|
|
2
14
|
|
|
3
|
-
// Single-line text prompt
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
return new Promise<string>((resolve) => {
|
|
8
|
-
if (!process.stdin.isTTY) return resolve(def)
|
|
9
|
-
let rl = createInterface({ input: process.stdin, output: process.stdout })
|
|
10
|
-
let suffix = def ? ` (${def})` : ""
|
|
11
|
-
rl.question(`? ${message}${suffix}: `, (answer) => {
|
|
12
|
-
rl.close()
|
|
13
|
-
resolve(answer.trim() || def)
|
|
14
|
-
})
|
|
15
|
-
})
|
|
15
|
+
// Single-line text prompt; a blank answer resolves the default.
|
|
16
|
+
export async function text(message: string, def = ""): Promise<string> {
|
|
17
|
+
if (!process.stdin.isTTY) return def
|
|
18
|
+
return unwrap(await clack.text({ message, defaultValue: def, placeholder: def }))
|
|
16
19
|
}
|
|
17
20
|
|
|
18
21
|
export interface SelectOption {
|
|
@@ -20,65 +23,36 @@ export interface SelectOption {
|
|
|
20
23
|
value: string
|
|
21
24
|
}
|
|
22
25
|
|
|
23
|
-
//
|
|
24
|
-
|
|
25
|
-
// highlight on up/down, resolves the chosen value on enter. Options are plain
|
|
26
|
-
// strings or { label, value } pairs when the display text differs from the
|
|
27
|
-
// resolved value. Callers guard on process.stdin.isTTY; a non-TTY stdin here
|
|
28
|
-
// resolves the first option rather than hanging on input that will never
|
|
29
|
-
// arrive.
|
|
30
|
-
export function select(message: string, options: Array<string | SelectOption>): Promise<string> {
|
|
26
|
+
// Arrow-key single-select; non-TTY resolves the first option.
|
|
27
|
+
export async function select(message: string, options: Array<string | SelectOption>): Promise<string> {
|
|
31
28
|
let items = options.map((o) => (typeof o === "string" ? { label: o, value: o } : o))
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
if (!input.isTTY) return resolve(items[0]!.value)
|
|
36
|
-
|
|
37
|
-
let selected = 0
|
|
38
|
-
emitKeypressEvents(input)
|
|
39
|
-
let wasRaw = input.isRaw
|
|
40
|
-
input.setRawMode(true)
|
|
41
|
-
|
|
42
|
-
let render = (first = false) => {
|
|
43
|
-
// After the first paint the cursor sits below the block; move it back up
|
|
44
|
-
// to the message line so the list redraws in place.
|
|
45
|
-
if (!first) output.write(`\x1b[${items.length + 1}A`)
|
|
46
|
-
output.write(`\x1b[K? ${message}\n`)
|
|
47
|
-
for (let i = 0; i < items.length; i++) {
|
|
48
|
-
let active = i === selected
|
|
49
|
-
let pointer = active ? "\x1b[36m> " : " "
|
|
50
|
-
let reset = active ? "\x1b[0m" : ""
|
|
51
|
-
output.write(`\x1b[K${pointer}${items[i]!.label}${reset}\n`)
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
let cleanup = () => {
|
|
56
|
-
input.off("keypress", onKey)
|
|
57
|
-
input.setRawMode(wasRaw)
|
|
58
|
-
input.pause()
|
|
59
|
-
}
|
|
29
|
+
if (!process.stdin.isTTY) return items[0]!.value
|
|
30
|
+
return unwrap(await clack.select({ message, options: items }))
|
|
31
|
+
}
|
|
60
32
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
} else if (key.name === "down") {
|
|
67
|
-
selected = (selected + 1) % items.length
|
|
68
|
-
render()
|
|
69
|
-
} else if (key.name === "return" || key.name === "enter") {
|
|
70
|
-
cleanup()
|
|
71
|
-
output.write("\n")
|
|
72
|
-
resolve(items[selected]!.value)
|
|
73
|
-
} else if (key.ctrl && (key.name === "c" || key.name === "d")) {
|
|
74
|
-
cleanup()
|
|
75
|
-
output.write("\n")
|
|
76
|
-
process.exit(130)
|
|
77
|
-
}
|
|
78
|
-
}
|
|
33
|
+
export interface MultiSelectOption {
|
|
34
|
+
label: string
|
|
35
|
+
value: string
|
|
36
|
+
checked?: boolean
|
|
37
|
+
}
|
|
79
38
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
39
|
+
// Space toggles, enter confirms; resolves the selected values in option
|
|
40
|
+
// order. Non-TTY resolves the preselected values.
|
|
41
|
+
export async function multiselect(message: string, options: MultiSelectOption[]): Promise<string[]> {
|
|
42
|
+
let preset = options.filter((o) => o.checked).map((o) => o.value)
|
|
43
|
+
if (!process.stdin.isTTY) return preset
|
|
44
|
+
let picked = unwrap(
|
|
45
|
+
await clack.multiselect({
|
|
46
|
+
message,
|
|
47
|
+
options: options.map((o) => ({ label: o.label, value: o.value })),
|
|
48
|
+
initialValues: preset,
|
|
49
|
+
required: false,
|
|
50
|
+
}),
|
|
51
|
+
)
|
|
52
|
+
return options.filter((o) => picked.includes(o.value)).map((o) => o.value)
|
|
84
53
|
}
|
|
54
|
+
|
|
55
|
+
// Boxed informational message; silent on a non-TTY.
|
|
56
|
+
export function note(message: string, title?: string) {
|
|
57
|
+
if (process.stdin.isTTY) clack.note(message, title)
|
|
58
|
+
}
|
package/src/repl.ts
CHANGED
|
@@ -3,8 +3,7 @@ import { resolve, dirname } from "path"
|
|
|
3
3
|
import { readdirSync } from "node:fs"
|
|
4
4
|
import { state, print, printErr, shutdown } from "./util"
|
|
5
5
|
import { buildReload, getClients, sendReload, sendStop, sendStats, sendWatch, showBuildFailure } from "./dev-server"
|
|
6
|
-
import { bundle } from "./bundler"
|
|
7
|
-
import { buildManifest } from "./project"
|
|
6
|
+
import { bundle, bundleMaps, prebuiltManifest } from "./bundler"
|
|
8
7
|
import { startWatcher, stopWatcher } from "./watcher"
|
|
9
8
|
|
|
10
9
|
// Resolve repl client indexes ("0 2") against the server's client list,
|
|
@@ -28,7 +27,7 @@ async function cmdStop(args: string) {
|
|
|
28
27
|
if (!args) {
|
|
29
28
|
stopWatcher()
|
|
30
29
|
state.currentCode = null
|
|
31
|
-
state.
|
|
30
|
+
state.currentMaps = null
|
|
32
31
|
state.currentManifest = null
|
|
33
32
|
state.source = undefined
|
|
34
33
|
await sendStop()
|
|
@@ -51,18 +50,18 @@ async function cmdReload(args: string) {
|
|
|
51
50
|
return
|
|
52
51
|
}
|
|
53
52
|
state.currentCode = result.code
|
|
54
|
-
state.
|
|
53
|
+
state.currentMaps = bundleMaps(result)
|
|
55
54
|
state.currentManifest = result.manifest
|
|
56
55
|
}
|
|
57
56
|
let msg = buildReload({ code: state.currentCode, manifest: state.currentManifest })
|
|
58
57
|
if (!args) {
|
|
59
|
-
await sendReload(msg, { latch: true,
|
|
58
|
+
await sendReload(msg, { latch: true, maps: state.currentMaps })
|
|
60
59
|
print("[cli] Sent reload to all clients")
|
|
61
60
|
return
|
|
62
61
|
}
|
|
63
62
|
let ids = await indexesToIds(args)
|
|
64
63
|
if (ids.length) {
|
|
65
|
-
await sendReload(msg, { clients: ids,
|
|
64
|
+
await sendReload(msg, { clients: ids, maps: state.currentMaps })
|
|
66
65
|
print(`[cli] Sent reload to client(s) ${ids.join(", ")}`)
|
|
67
66
|
}
|
|
68
67
|
}
|
|
@@ -104,7 +103,13 @@ async function cmdLoad(file: string) {
|
|
|
104
103
|
// Same rule as /__control__/load (control.ts): a server run serves the
|
|
105
104
|
// project it started in, and an entry outside the project root cannot
|
|
106
105
|
// resolve the project's dependencies anyway.
|
|
107
|
-
|
|
106
|
+
// Windows paths are case-insensitive and the same drive shows up as both
|
|
107
|
+
// `c:` and `C:` (an editor-spawned bridge keeps its parent's spelling), so a
|
|
108
|
+
// drive-letter path folds case; a POSIX path stays exact.
|
|
109
|
+
let norm = (p: string) => {
|
|
110
|
+
let s = p.replace(/\\/g, "/")
|
|
111
|
+
return /^[a-zA-Z]:\//.test(s) ? s.toLowerCase() : s
|
|
112
|
+
}
|
|
108
113
|
let root = norm(state.projectDir).replace(/\/+$/, "") + "/"
|
|
109
114
|
if (!norm(path).startsWith(root)) {
|
|
110
115
|
printErr(`[cli] Entry is outside the project root: ${path} is not under ${state.projectDir}. Restart srt in that project to work on it.`)
|
|
@@ -117,12 +122,12 @@ async function cmdLoad(file: string) {
|
|
|
117
122
|
return
|
|
118
123
|
}
|
|
119
124
|
state.currentCode = result.code
|
|
120
|
-
state.
|
|
125
|
+
state.currentMaps = bundleMaps(result)
|
|
121
126
|
state.currentManifest = result.manifest
|
|
122
127
|
} else if (file.endsWith(".srt.js")) {
|
|
123
128
|
state.currentCode = await Bun.file(path).text()
|
|
124
|
-
state.
|
|
125
|
-
state.currentManifest =
|
|
129
|
+
state.currentMaps = null
|
|
130
|
+
state.currentManifest = prebuiltManifest(state.currentCode, path, state.projectDir)
|
|
126
131
|
} else if (file.endsWith(".srt.bin")) {
|
|
127
132
|
let bytes = await Bun.file(path).arrayBuffer()
|
|
128
133
|
// One-shot: bytecode loads are pushed but not latched for late joiners.
|
|
@@ -144,7 +149,7 @@ async function cmdLoad(file: string) {
|
|
|
144
149
|
latch: true,
|
|
145
150
|
sourceDir: state.sourceDir,
|
|
146
151
|
entry: file.endsWith(".tsx") ? path : undefined,
|
|
147
|
-
|
|
152
|
+
maps: state.currentMaps,
|
|
148
153
|
})
|
|
149
154
|
print(`[cli] Loaded ${file}`)
|
|
150
155
|
}
|
|
@@ -185,6 +190,17 @@ function guard(p: Promise<void>) {
|
|
|
185
190
|
}
|
|
186
191
|
|
|
187
192
|
export function startRepl() {
|
|
193
|
+
// Without a terminal there is nobody to prompt, and stdin is at EOF from the
|
|
194
|
+
// start: readline would fire `close` immediately and shutdown() would tear
|
|
195
|
+
// down the server, the client and the registry record about a second after
|
|
196
|
+
// boot. A backgrounded or supervisor-launched srt therefore runs with no
|
|
197
|
+
// repl at all, kept alive by the server process and the watcher, and stopped
|
|
198
|
+
// with a signal. See okf/backlog/srt-run-exits-on-stdin-eof.md.
|
|
199
|
+
if (!process.stdin.isTTY) {
|
|
200
|
+
print("[cli] No terminal on stdin, running without the repl")
|
|
201
|
+
return
|
|
202
|
+
}
|
|
203
|
+
|
|
188
204
|
state.rl = createInterface({ input: process.stdin, output: process.stdout, completer })
|
|
189
205
|
state.rl.setPrompt("srt> ")
|
|
190
206
|
|
package/src/util.ts
CHANGED
|
@@ -8,9 +8,10 @@ export let state = {
|
|
|
8
8
|
// What srt believes the current bundle is; the server process keeps its own
|
|
9
9
|
// latched copy for late-joining clients (see packages/cli/server/).
|
|
10
10
|
currentCode: null as string | null,
|
|
11
|
-
// The bundle's composed
|
|
12
|
-
//
|
|
13
|
-
|
|
11
|
+
// The bundle's composed sourcemaps (dev builds), keyed by module name
|
|
12
|
+
// ("main", each isolate id), sent to the server alongside reloads so it can
|
|
13
|
+
// remap logged stack traces to .tsx positions.
|
|
14
|
+
currentMaps: null as Record<string, string> | null,
|
|
14
15
|
// The bundle's version manifest (canonical JSON string, see buildManifest);
|
|
15
16
|
// travels with code reloads so clients install the push as a version.
|
|
16
17
|
currentManifest: null as string | null,
|
package/src/watcher.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { existsSync, watch } from "node:fs"
|
|
|
2
2
|
import { resolve, dirname, sep, basename } from "path"
|
|
3
3
|
import { state, print, printErr } from "./util"
|
|
4
4
|
import { buildReload, sendReload, showBuildFailure, watchAllowed } from "./dev-server"
|
|
5
|
-
import { bundle } from "./bundler"
|
|
5
|
+
import { bundle, bundleMaps } from "./bundler"
|
|
6
6
|
|
|
7
7
|
let watchers: ReturnType<typeof watch>[] = []
|
|
8
8
|
|
|
@@ -25,11 +25,11 @@ async function rebuild(filename: string) {
|
|
|
25
25
|
return
|
|
26
26
|
}
|
|
27
27
|
state.currentCode = result.code
|
|
28
|
-
state.
|
|
28
|
+
state.currentMaps = bundleMaps(result)
|
|
29
29
|
state.currentManifest = result.manifest
|
|
30
30
|
await sendReload(buildReload({ code: state.currentCode, manifest: state.currentManifest }), {
|
|
31
31
|
latch: true,
|
|
32
|
-
|
|
32
|
+
maps: state.currentMaps,
|
|
33
33
|
})
|
|
34
34
|
}
|
|
35
35
|
|
|
@@ -53,6 +53,10 @@ export function startWatcher() {
|
|
|
53
53
|
watchers.push(
|
|
54
54
|
watch(watchDir, { recursive: true }, (_event, filename) => {
|
|
55
55
|
if (!filename) return
|
|
56
|
+
// Dot directories are never sources: .srt-data in particular is this
|
|
57
|
+
// server's own output (isolate bundles are .js files in there), and
|
|
58
|
+
// rebuilding on it would rebuild forever.
|
|
59
|
+
if (filename.split(sep).some((part, i, parts) => i < parts.length - 1 && part.startsWith("."))) return
|
|
56
60
|
if (!/\.(tsx?|jsx?)$/.test(filename) && !(covered && isAsset(filename))) return
|
|
57
61
|
rebuild(filename)
|
|
58
62
|
}),
|