@vzn/vx 0.0.104 → 0.0.106

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vzn/vx",
3
- "version": "0.0.104",
3
+ "version": "0.0.106",
4
4
  "description": "A fast, correct task runner and build cache for JavaScript monorepos.",
5
5
  "type": "module",
6
6
  "exports": {
@@ -17,10 +17,10 @@
17
17
  "node": ">=18"
18
18
  },
19
19
  "optionalDependencies": {
20
- "@vzn/vx-linux-x64": "0.0.104",
21
- "@vzn/vx-linux-arm64": "0.0.104",
22
- "@vzn/vx-darwin-x64": "0.0.104",
23
- "@vzn/vx-darwin-arm64": "0.0.104"
20
+ "@vzn/vx-linux-x64": "0.0.106",
21
+ "@vzn/vx-linux-arm64": "0.0.106",
22
+ "@vzn/vx-darwin-x64": "0.0.106",
23
+ "@vzn/vx-darwin-arm64": "0.0.106"
24
24
  },
25
25
  "dependencies": {
26
26
  "@anthropic-ai/sandbox-runtime": "^0.0.76"
package/src/cli/watch.ts CHANGED
@@ -54,6 +54,11 @@ import {
54
54
  } from './watch-fs.js'
55
55
  import { memberEntries, sameMembers, sweepConfigs, watchedProjects } from './watch-set.js'
56
56
 
57
+ /** One line for a watcher or re-read the OS refused; the loop goes on without it. */
58
+ function sayCannot(what: string, err: unknown): void {
59
+ process.stderr.write(`vx watch: ${what}: ${err instanceof Error ? err.message : String(err)}\n`)
60
+ }
61
+
57
62
  /** Wait this long after the last filesystem event before re-running. */
58
63
  const DEBOUNCE_MS = 150
59
64
 
@@ -643,8 +648,7 @@ async function runWatchLoop(args: WatchLoopArgs): Promise<number> {
643
648
  })
644
649
  perProject.set(proj.dir, handle)
645
650
  } catch (err) {
646
- const msg = err instanceof Error ? err.message : String(err)
647
- process.stderr.write(`vx watch: cannot watch ${proj.dir}: ${msg}\n`)
651
+ sayCannot(`cannot watch ${proj.dir}`, err)
648
652
  }
649
653
  }
650
654
  const armRoot = (): void => {
@@ -677,8 +681,7 @@ async function runWatchLoop(args: WatchLoopArgs): Promise<number> {
677
681
  }
678
682
  })
679
683
  } catch (err) {
680
- const msg = err instanceof Error ? err.message : String(err)
681
- process.stderr.write(`vx watch: cannot watch workspace root: ${msg}\n`)
684
+ sayCannot(`cannot watch workspace root`, err)
682
685
  }
683
686
  }
684
687
  // One recursive watcher per project, unless workspace-wide: each project
@@ -733,8 +736,7 @@ async function runWatchLoop(args: WatchLoopArgs): Promise<number> {
733
736
  }),
734
737
  )
735
738
  } catch (err) {
736
- const msg = err instanceof Error ? err.message : String(err)
737
- process.stderr.write(`vx watch: cannot watch ${dir}: ${msg}\n`)
739
+ sayCannot(`cannot watch ${dir}`, err)
738
740
  continue
739
741
  }
740
742
  if (afterReread && fs.existsSync(manifest)) arrived()
@@ -791,8 +793,7 @@ async function runWatchLoop(args: WatchLoopArgs): Promise<number> {
791
793
  }),
792
794
  )
793
795
  } catch (err) {
794
- const msg = err instanceof Error ? err.message : String(err)
795
- process.stderr.write(`vx watch: cannot watch ${dir}: ${msg}\n`)
796
+ sayCannot(`cannot watch ${dir}`, err)
796
797
  }
797
798
  }
798
799
  }
@@ -807,8 +808,7 @@ async function runWatchLoop(args: WatchLoopArgs): Promise<number> {
807
808
  try {
808
809
  next = await args.rediscover()
809
810
  } catch (err) {
810
- const msg = err instanceof Error ? err.message : String(err)
811
- process.stderr.write(`vx watch: cannot re-read the workspace: ${msg}\n`)
811
+ sayCannot(`cannot re-read the workspace`, err)
812
812
  return
813
813
  }
814
814
  projectDirs = next.projects.map((p) => p.dir)
@@ -893,8 +893,7 @@ async function runWatchLoop(args: WatchLoopArgs): Promise<number> {
893
893
  }),
894
894
  )
895
895
  } catch (err) {
896
- const msg = err instanceof Error ? err.message : String(err)
897
- process.stderr.write(`vx watch: cannot watch ${base}: ${msg}\n`)
896
+ sayCannot(`cannot watch ${base}`, err)
898
897
  }
899
898
  }
900
899
  }
@@ -15,6 +15,25 @@ import { executablePath, procfsIsOwn } from '../util/index.js'
15
15
 
16
16
  export type Child = ReturnType<typeof Bun.spawn>
17
17
 
18
+ const WIN32 = process.platform === 'win32'
19
+
20
+ /**
21
+ * Windows has no process groups (`kill(-pid)` is ESRCH, which read as
22
+ * "gone" and killed nothing) and no polite signal a console task hears
23
+ * from another process, so every stop is `taskkill /T /F`: the child and
24
+ * every descendant it still parents. A grandchild whose parent already
25
+ * exited is out of the tree's reach (docs/design/windows-2026-09.md).
26
+ */
27
+ function killTreeWin32(child: Child): void {
28
+ try {
29
+ Bun.spawn(['taskkill', '/T', '/F', '/PID', String(child.pid)], {
30
+ stdio: ['ignore', 'ignore', 'ignore'],
31
+ })
32
+ } catch {
33
+ child.kill()
34
+ }
35
+ }
36
+
18
37
  /**
19
38
  * A child whose polite signals go down a pipe instead of to its group: a
20
39
  * Linux sandboxed task. The group there is bwrap's, whose monitor dies of
@@ -208,6 +227,7 @@ export function killTree(child: Child, signal: 'SIGINT' | 'SIGTERM' | 'SIGKILL')
208
227
  // A pid of 0 would name OUR group (kill(0)): a child that never
209
228
  // spawned has nothing to kill.
210
229
  if (!(child.pid > 0) || goneGroups.has(child)) return
230
+ if (WIN32) return killTreeWin32(child)
211
231
  const fd = signal === 'SIGKILL' ? undefined : channels.get(child)
212
232
  if (fd !== undefined) {
213
233
  try {
@@ -277,6 +297,8 @@ export async function untilGroupsGone(
277
297
  */
278
298
  function groupAlive(child: Child): boolean {
279
299
  if (!(child.pid > 0) || goneGroups.has(child)) return false
300
+ // No groups: the tree kill is `taskkill /T`, and the leader is what is left to ask.
301
+ if (WIN32) return child.exitCode === null && child.signalCode === null
280
302
  try {
281
303
  process.kill(-child.pid, 0)
282
304
  } catch (err) {
@@ -71,7 +71,10 @@ const SPECIFIER =
71
71
 
72
72
  /** Whether a specifier is one the walk would have to look up at all. */
73
73
  function needsLookup(spec: string): boolean {
74
- if (spec.includes(':') || BUILTINS.has(spec)) return false
74
+ // `#x` is a package.json `imports` entry: Bun maps it inside the
75
+ // package and never asks the registry, as no npm name opens with `#`
76
+ // (D-28).
77
+ if (spec.startsWith('#') || spec.includes(':') || BUILTINS.has(spec)) return false
75
78
  return !(spec === '@vzn/vx' || spec.startsWith('@vzn/vx/'))
76
79
  }
77
80