@solidrt/cli 0.0.32 → 0.0.33

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/src/repl.ts CHANGED
@@ -4,6 +4,7 @@ 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
6
  import { bundle } from "./bundler"
7
+ import { buildManifest, projectDirFor } from "./project"
7
8
  import { startWatcher, stopWatcher } from "./watcher"
8
9
 
9
10
  // Resolve repl client indexes ("0 2") against the server's client list,
@@ -28,6 +29,7 @@ async function cmdStop(args: string) {
28
29
  stopWatcher()
29
30
  state.currentCode = null
30
31
  state.currentMap = null
32
+ state.currentManifest = null
31
33
  state.source = undefined
32
34
  await sendStop()
33
35
  print("[cli] Sent stop to all clients")
@@ -50,8 +52,9 @@ async function cmdReload(args: string) {
50
52
  }
51
53
  state.currentCode = result.code
52
54
  state.currentMap = result.map
55
+ state.currentManifest = result.manifest
53
56
  }
54
- let msg = buildReload({ code: state.currentCode })
57
+ let msg = buildReload({ code: state.currentCode, manifest: state.currentManifest })
55
58
  if (!args) {
56
59
  await sendReload(msg, { latch: true, map: state.currentMap })
57
60
  print("[cli] Sent reload to all clients")
@@ -106,9 +109,11 @@ async function cmdLoad(file: string) {
106
109
  }
107
110
  state.currentCode = result.code
108
111
  state.currentMap = result.map
112
+ state.currentManifest = result.manifest
109
113
  } else if (file.endsWith(".srt.js")) {
110
114
  state.currentCode = await Bun.file(path).text()
111
115
  state.currentMap = null
116
+ state.currentManifest = buildManifest(state.currentCode, path)
112
117
  } else if (file.endsWith(".srt.bin")) {
113
118
  let bytes = await Bun.file(path).arrayBuffer()
114
119
  // One-shot: bytecode loads are pushed but not latched for late joiners.
@@ -121,12 +126,15 @@ async function cmdLoad(file: string) {
121
126
  }
122
127
  state.source = path
123
128
  state.sourceDir = dirname(path)
129
+ state.projectDir = projectDirFor(path)
124
130
  startWatcher()
125
131
  // The load also moves the server's file-serving root to the new source dir,
126
- // and its rebuild entry to the new file (for a later MCP reload).
127
- await sendReload(buildReload({ code: state.currentCode }), {
132
+ // its /assets/ root to the new project dir, and its rebuild entry to the
133
+ // new file (for a later MCP reload).
134
+ await sendReload(buildReload({ code: state.currentCode, manifest: state.currentManifest }), {
128
135
  latch: true,
129
136
  sourceDir: state.sourceDir,
137
+ projectDir: state.projectDir,
130
138
  entry: file.endsWith(".tsx") ? path : undefined,
131
139
  map: state.currentMap,
132
140
  })
package/src/util.ts CHANGED
@@ -11,8 +11,14 @@ export let state = {
11
11
  // The bundle's composed sourcemap (dev builds), sent to the server alongside
12
12
  // reloads so it can remap logged stack traces to .tsx positions.
13
13
  currentMap: null as string | null,
14
+ // The bundle's version manifest (canonical JSON string, see buildManifest);
15
+ // travels with code reloads so clients install the push as a version.
16
+ currentManifest: null as string | null,
14
17
  source: undefined as string | undefined,
15
18
  sourceDir: process.cwd(),
19
+ // The project root the assets/ convention hangs off (see project.ts
20
+ // projectDirFor); the server's /assets/ route serves from under it.
21
+ projectDir: process.cwd(),
16
22
  child: null as ReturnType<typeof Bun.spawn> | null,
17
23
  // The spawned flux dev-server process (see dev-server.ts startServer).
18
24
  serverProc: null as ReturnType<typeof Bun.spawn> | null,
package/src/watcher.ts CHANGED
@@ -1,43 +1,69 @@
1
- import { watch } from "node:fs"
2
- import { resolve, dirname } from "path"
1
+ import { existsSync, watch } from "node:fs"
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
5
  import { bundle } from "./bundler"
6
6
 
7
- let currentWatcher: ReturnType<typeof watch> | null = null
7
+ let watchers: ReturnType<typeof watch>[] = []
8
8
 
9
9
  export function stopWatcher() {
10
- if (currentWatcher) {
11
- currentWatcher.close()
12
- currentWatcher = null
10
+ for (let w of watchers) w.close()
11
+ watchers = []
12
+ }
13
+
14
+ async function rebuild(filename: string) {
15
+ if (!(await watchAllowed())) {
16
+ print(`[cli] Change detected: ${filename} (auto-reload paused by agent; "watch on" resumes)`)
17
+ return
13
18
  }
19
+
20
+ print(`[cli] Change detected: ${filename}`)
21
+ let result = await bundle(state.source)
22
+ if (!result) {
23
+ printErr("[cli] Build failed, waiting for changes...")
24
+ await showBuildFailure()
25
+ return
26
+ }
27
+ state.currentCode = result.code
28
+ state.currentMap = result.map
29
+ state.currentManifest = result.manifest
30
+ await sendReload(buildReload({ code: state.currentCode, manifest: state.currentManifest }), {
31
+ latch: true,
32
+ map: state.currentMap,
33
+ })
14
34
  }
15
35
 
16
36
  export function startWatcher() {
17
37
  if (!state.source) return
18
38
  let watchDir = dirname(resolve(state.source))
19
39
 
20
- if (currentWatcher) currentWatcher.close()
40
+ stopWatcher()
41
+
42
+ // An asset edit re-pushes too: the assets/ tree is part of the manifest, so
43
+ // clients install the new version. The tree roots at the project dir; the
44
+ // project dir is an ancestor of (or equal to) the entry's dir, so assets/
45
+ // is inside watchDir exactly when they are the same dir. An assets/ folder
46
+ // created after startup needs a restart or `load` to be picked up.
47
+ let assetsDir = resolve(state.projectDir, "assets")
48
+ let covered = resolve(state.projectDir) === watchDir
49
+ let isAsset = (filename: string) =>
50
+ (filename === "assets" || filename.startsWith("assets" + sep)) && !basename(filename).startsWith(".")
21
51
 
22
52
  print(`[cli] Watching ${watchDir} for changes...`)
23
- currentWatcher = watch(watchDir, { recursive: true }, async (_event, filename) => {
24
- if (!filename) return
25
- if (!/\.(tsx?|jsx?)$/.test(filename)) return
26
-
27
- if (!(await watchAllowed())) {
28
- print(`[cli] Change detected: ${filename} (auto-reload paused by agent; "watch on" resumes)`)
29
- return
30
- }
31
-
32
- print(`[cli] Change detected: ${filename}`)
33
- let result = await bundle(state.source)
34
- if (!result) {
35
- printErr("[cli] Build failed, waiting for changes...")
36
- await showBuildFailure()
37
- return
38
- }
39
- state.currentCode = result.code
40
- state.currentMap = result.map
41
- await sendReload(buildReload({ code: state.currentCode }), { latch: true, map: state.currentMap })
42
- })
53
+ watchers.push(
54
+ watch(watchDir, { recursive: true }, (_event, filename) => {
55
+ if (!filename) return
56
+ if (!/\.(tsx?|jsx?)$/.test(filename) && !(covered && isAsset(filename))) return
57
+ rebuild(filename)
58
+ }),
59
+ )
60
+
61
+ if (!covered && existsSync(assetsDir)) {
62
+ watchers.push(
63
+ watch(assetsDir, { recursive: true }, (_event, filename) => {
64
+ if (!filename || basename(filename).startsWith(".")) return
65
+ rebuild("assets" + sep + filename)
66
+ }),
67
+ )
68
+ }
43
69
  }