@vzn/vx 0.0.596 → 0.0.597

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.
Files changed (64) hide show
  1. package/launcher.cjs +7 -3
  2. package/package.json +5 -5
  3. package/src/cache/archive.ts +35 -6
  4. package/src/cache/cache.ts +31 -15
  5. package/src/cache/git-inputs.ts +23 -0
  6. package/src/cache/index.ts +2 -0
  7. package/src/cache/inputs.ts +87 -32
  8. package/src/cache/key-fold.ts +1 -1
  9. package/src/cache/layer.ts +2 -1
  10. package/src/cli/cache.ts +2 -2
  11. package/src/cli/foreign-flags.ts +56 -2
  12. package/src/cli/info.ts +1 -1
  13. package/src/cli/init.ts +2 -2
  14. package/src/cli/last.ts +6 -3
  15. package/src/cli/lock.ts +5 -3
  16. package/src/cli/plan-format.ts +4 -4
  17. package/src/cli/run.ts +42 -15
  18. package/src/cli/select.ts +6 -3
  19. package/src/cli/show.ts +8 -2
  20. package/src/cli/upgrade.ts +1 -1
  21. package/src/cli/watch-fs.ts +11 -3
  22. package/src/cli/watch.ts +14 -7
  23. package/src/cli/workspace-config.ts +3 -1
  24. package/src/config.ts +21 -0
  25. package/src/exec/executor.ts +1 -1
  26. package/src/exec/kill-tree.ts +89 -18
  27. package/src/exec/runner.ts +74 -36
  28. package/src/exec/sandbox-paths.ts +11 -0
  29. package/src/exec/sandbox-runtime.ts +168 -49
  30. package/src/exec/sandbox-violations.ts +59 -6
  31. package/src/graph/scheduler.ts +2 -2
  32. package/src/graph/task-graph.ts +145 -33
  33. package/src/orchestrator/affected-tasks.ts +6 -2
  34. package/src/orchestrator/deferred-outputs.ts +3 -2
  35. package/src/orchestrator/doctor.ts +2 -2
  36. package/src/orchestrator/excluded-keys.ts +2 -13
  37. package/src/orchestrator/execute-task.ts +49 -26
  38. package/src/orchestrator/failure-mode.ts +38 -29
  39. package/src/orchestrator/framed-output.ts +5 -5
  40. package/src/orchestrator/index.ts +0 -1
  41. package/src/orchestrator/keyed-projects.ts +6 -3
  42. package/src/orchestrator/logger.ts +10 -3
  43. package/src/orchestrator/miss-save.ts +7 -11
  44. package/src/orchestrator/plan.ts +4 -20
  45. package/src/orchestrator/run-artifacts.ts +2 -2
  46. package/src/orchestrator/run-lock.ts +25 -24
  47. package/src/orchestrator/run.ts +2 -1
  48. package/src/orchestrator/sandbox-request.ts +31 -9
  49. package/src/orchestrator/signals.ts +22 -4
  50. package/src/orchestrator/stable-keys.ts +3 -11
  51. package/src/orchestrator/summary.ts +5 -9
  52. package/src/orchestrator/task-hash.ts +13 -3
  53. package/src/orchestrator/upstream.ts +11 -0
  54. package/src/util/hangup.ts +42 -0
  55. package/src/util/index.ts +2 -0
  56. package/src/util/paths.ts +1 -1
  57. package/src/workspace/affected.ts +79 -42
  58. package/src/workspace/config-eval.ts +27 -6
  59. package/src/workspace/config-imports.ts +8 -55
  60. package/src/workspace/config-schema.ts +30 -6
  61. package/src/workspace/lockfile.ts +2 -1
  62. package/src/workspace/migrate-scripts.ts +1 -1
  63. package/src/workspace/project-loader.ts +15 -2
  64. package/src/workspace/workspace.ts +36 -14
@@ -847,7 +847,7 @@ export function migrateScripts(
847
847
  const names = runnable.filter((n) => taskNameProblem(n) === null)
848
848
  if (meta !== rootMeta && !('build' in scripts)) {
849
849
  const hook = LIFECYCLE_BUILD_HOOKS.find(
850
- (h) => typeof scripts[h] === 'string' && BUILDER.test(scripts[h] as string),
850
+ (h) => typeof scripts[h] === 'string' && BUILDER.test(scripts[h]),
851
851
  )
852
852
  if (hook !== undefined) lifecycleBuilds.push([meta.name, hook, scripts[hook] as string])
853
853
  }
@@ -9,6 +9,7 @@ import {
9
9
  CONFIG_EXIT,
10
10
  evalBudgetMs,
11
11
  evaluateConfigFresh,
12
+ thrownValueMessage,
12
13
  WATCHED_BUILTIN_NAMES,
13
14
  } from './config-eval.js'
14
15
  import { hasEsmExport, unprovidedBareImports } from './config-imports.js'
@@ -191,7 +192,7 @@ async function loadDefaultExport(
191
192
  if (err instanceof Error && err.stack !== undefined) {
192
193
  err.stack = err.stack.replaceAll(specifier, configPath).replace(BUST_QUERY, '')
193
194
  }
194
- throw configLoadError(err, configPath, kind) ?? err
195
+ throw loadFailure(err, configPath, kind)
195
196
  } finally {
196
197
  clearTimeout(deadline)
197
198
  unguard()
@@ -341,6 +342,16 @@ export function configLoadError(err: unknown, configPath: string, kind: string):
341
342
  return null
342
343
  }
343
344
 
345
+ /** What a failed load throws: Bun's own errors and non-Error throws as user errors, the rest as thrown. */
346
+ function loadFailure(err: unknown, configPath: string, kind: string): unknown {
347
+ const user = configLoadError(err, configPath, kind)
348
+ if (user !== null) return user
349
+ if (err !== null && typeof err === 'object' && typeof (err as Error).message === 'string') {
350
+ return err
351
+ }
352
+ return new UserError(thrownValueMessage(kind, configPath, Bun.inspect(err, { compact: true })))
353
+ }
354
+
344
355
  export interface LoadProjectConfigOptions {
345
356
  /** Observe the CURRENT environment: no eval cache, even when `evalCache` is passed. */
346
357
  fresh?: boolean
@@ -523,7 +534,7 @@ export async function loadProjectConfigs(
523
534
  if (repeat) refuseUnprovidedImports(bytes!, configPath, 'Project')
524
535
  const mod = repeat
525
536
  ? await evaluateConfigFresh(configPath).catch((err: unknown) => {
526
- throw configLoadError(err, configPath, 'Project') ?? err
537
+ throw loadFailure(err, configPath, 'Project')
527
538
  })
528
539
  : await loadDefaultExport(configPath, 'Project', bytes!)
529
540
  // Before anything reads through them: a replaced `Array.prototype.includes`
@@ -812,6 +823,7 @@ const BUN_MEMBERS_VX_READS: readonly PropertyKey[] = [
812
823
  'env',
813
824
  'file',
814
825
  'hash',
826
+ 'inspect',
815
827
  'main',
816
828
  'nanoseconds',
817
829
  'plugin',
@@ -820,6 +832,7 @@ const BUN_MEMBERS_VX_READS: readonly PropertyKey[] = [
820
832
  'semver',
821
833
  'serve',
822
834
  'sleep',
835
+ 'sleepSync',
823
836
  'spawn',
824
837
  'spawnSync',
825
838
  'stderr',
@@ -290,13 +290,17 @@ async function readRootManifests(
290
290
  )
291
291
  }
292
292
  if (ws === undefined || ws === null) return { globs: ['.'], catalogs }
293
+ // bun's `{ catalog }` and yarn's `{ nohoist }` name no members, and both
294
+ // managers run the root alone; vx refused them as no array (D-151).
295
+ if (typeof ws === 'object' && !Array.isArray(ws) && !('packages' in ws)) {
296
+ return { globs: ['.'], catalogs }
297
+ }
293
298
  if (ws && typeof ws === 'object' && !Array.isArray(ws) && 'packages' in ws) {
294
- const globs = assertGlobList(
295
- (ws as { packages?: unknown }).packages ?? [],
296
- pkgPath,
297
- 'workspaces.packages',
298
- )
299
- return { globs, catalogs }
299
+ const packages = (ws as { packages?: unknown }).packages
300
+ // Yarn 1 and 4 run the root alone under `packages: null`; read as an
301
+ // empty list, the root was no project and every verb ran nothing (D-150).
302
+ if (packages === null) return { globs: ['.'], catalogs }
303
+ return { globs: assertGlobList(packages, pkgPath, 'workspaces.packages'), catalogs }
300
304
  }
301
305
  return { globs: assertGlobList(ws, pkgPath, 'workspaces'), catalogs }
302
306
  }
@@ -454,13 +458,27 @@ function extglobRefusal(pattern: string, file: string, field: string): UserError
454
458
  * to the workspace root.
455
459
  */
456
460
  export function resolveCacheDir(root: string, config: WorkspaceConfig | null): string {
457
- const rel = config?.cacheDir ?? (process.env['VX_CACHE_DIR'] || path.join('.vx', 'cache'))
461
+ const env = process.env['VX_CACHE_DIR']
462
+ // `cacheDir: ' '` is refused at load (X-22); the variable made the same
463
+ // directory named in spaces at the root (D-160).
464
+ if (config?.cacheDir === undefined && env !== undefined && env !== '' && env.trim() === '') {
465
+ throw new UserError('VX_CACHE_DIR is only whitespace — name a directory, or unset it')
466
+ }
467
+ const rel = config?.cacheDir ?? (env || path.join('.vx', 'cache'))
468
+ const home = process.env['HOME'] || homedir()
458
469
  // No shell expands `~` in a config string or a quoted variable, and
459
470
  // `'~/.cache/vx'` made a directory named `~` in the workspace.
460
- if (rel.startsWith('~/')) {
461
- return path.join(process.env['HOME'] || homedir(), rel.slice(1))
471
+ const dir =
472
+ rel === '~' || rel.startsWith('~/') ? path.join(home, rel.slice(1)) : path.resolve(root, rel)
473
+ // The cache writes a `*` .gitignore beside its index: a bare `~` made a
474
+ // directory named `~`, and expanded it would land in home itself (D-153).
475
+ if (path.resolve(dir) === path.resolve(home)) {
476
+ const source = config?.cacheDir !== undefined ? 'cacheDir' : 'VX_CACHE_DIR'
477
+ throw new UserError(
478
+ `${source} ${JSON.stringify(rel)} is the home directory itself, where the cache's \`*\` .gitignore and index would land — name a directory under it, like '~/.cache/vx'`,
479
+ )
462
480
  }
463
- return path.resolve(root, rel)
481
+ return dir
464
482
  }
465
483
 
466
484
  /**
@@ -711,12 +729,17 @@ async function readManifest(root: string, dir: string, file: string): Promise<st
711
729
  )
712
730
  if (searchable) unreadable(err, file)
713
731
  process.stderr.write(
714
- `vx: ${relPosix(root, dir)} is not readable by this user — skipped, with any project in it\n`,
732
+ `vx: ${shownDir(root, dir)} is not readable by this user — skipped, with any project in it\n`,
715
733
  )
716
734
  return null
717
735
  }
718
736
  }
719
737
 
738
+ /** `dir` as a discovery line names it: the root's own path is '' (D-127, D-154). */
739
+ function shownDir(root: string, dir: string): string {
740
+ return relPosix(root, dir) || 'the workspace root'
741
+ }
742
+
720
743
  export async function listProjects(workspace: Workspace): Promise<ProjectMeta[]> {
721
744
  return discoverProjects(workspace)
722
745
  }
@@ -769,7 +792,7 @@ export async function discoverProjects(
769
792
  // same flight as the failed read, so naming it costs nothing.
770
793
  if (configPath !== null) {
771
794
  process.stderr.write(
772
- `vx: ${relPosix(workspace.root, dir)} has a vx config but no package.json — skipped: vx names a project by its package.json "name"\n`,
795
+ `vx: ${shownDir(workspace.root, dir)} has a vx config but no package.json — skipped: vx names a project by its package.json "name"\n`,
773
796
  )
774
797
  }
775
798
  return null
@@ -800,9 +823,8 @@ export async function discoverProjects(
800
823
  // fine for a dir that declares no tasks; a dir with a vx config was
801
824
  // meant to run.
802
825
  if (configPath !== null) {
803
- const rel = relPosix(workspace.root, dir)
804
826
  process.stderr.write(
805
- `vx: ${rel === '' ? 'the workspace root' : rel} has a vx config but its package.json has no "name" — skipped\n`,
827
+ `vx: ${shownDir(workspace.root, dir)} has a vx config but its package.json has no "name" — skipped\n`,
806
828
  )
807
829
  }
808
830
  continue