@vzn/vx 0.0.197 → 0.0.199

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.197",
3
+ "version": "0.0.199",
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.197",
21
- "@vzn/vx-linux-arm64": "0.0.197",
22
- "@vzn/vx-darwin-x64": "0.0.197",
23
- "@vzn/vx-darwin-arm64": "0.0.197"
20
+ "@vzn/vx-linux-x64": "0.0.199",
21
+ "@vzn/vx-linux-arm64": "0.0.199",
22
+ "@vzn/vx-darwin-x64": "0.0.199",
23
+ "@vzn/vx-darwin-arm64": "0.0.199"
24
24
  },
25
25
  "dependencies": {
26
26
  "@anthropic-ai/sandbox-runtime": "^0.0.76"
package/src/cli/run.ts CHANGED
@@ -16,7 +16,7 @@ import {
16
16
  import type { ContinueMode } from '../graph/index.js'
17
17
  import { type CachePolicy, FULL_CACHE_POLICY, parseCachePolicy } from '../cache/index.js'
18
18
  import { findCwdProject, pickTask, resolveFilters } from './select.js'
19
- import { nxTargetHint } from './task-verb.js'
19
+ import { nxTargetHint, taskNamesHere } from './task-verb.js'
20
20
  import { MAX_TIMEOUT_MS, isUserError, parseDecimalInt, machineParallelism } from '../util/index.js'
21
21
  import { formatGraphDot, formatPlanJson, formatPlanText } from './plan-format.js'
22
22
 
@@ -555,6 +555,9 @@ export async function resolveRunOptions(
555
555
  return opts
556
556
  }
557
557
 
558
+ /** How many task names a run with no task names before `and N more`. */
559
+ const TASKS_SHOWN = 12
560
+
558
561
  export async function runCmd(args: readonly string[]): Promise<number> {
559
562
  const parsed = parseRunArgs(args)
560
563
  if (parsed.error) {
@@ -570,8 +573,15 @@ export async function runCmd(args: readonly string[]): Promise<number> {
570
573
  // explicit anchored positional.
571
574
  if (tasks.length === 0) {
572
575
  if (!process.stdin.isTTY) {
576
+ const names = await taskNamesHere(cwd)
577
+ const shown = (names ?? []).slice(0, TASKS_SHOWN)
578
+ const more = (names?.length ?? 0) - shown.length
579
+ const tasksHere =
580
+ shown.length === 0
581
+ ? 'vx run <task>, e.g. vx run build'
582
+ : `tasks here: ${shown.join(', ')}${more > 0 ? ` and ${more} more` : ''}`
573
583
  process.stderr.write(
574
- `vx run: missing task name (stdin is not a TTY, so no picker; vx run <task>, e.g. vx run build)${seeHelp('run')}\n`,
584
+ `vx run: missing task name (stdin is not a TTY, so no picker; ${tasksHere})${seeHelp('run')}\n`,
575
585
  )
576
586
  return 1
577
587
  }
@@ -43,6 +43,22 @@ export async function nxTargetHint(tasks: readonly string[], cwd: string): Promi
43
43
  return `\`${tasks.join(' ')}\` is Nx's project:target: vx run ${specs.join(' ')}`
44
44
  }
45
45
 
46
+ /**
47
+ * The task names `vx run <task>` can take here: the cwd project's, else
48
+ * every project's, sorted; null when no workspace loads. What a run with
49
+ * no task and no terminal for the picker names instead of a guess.
50
+ */
51
+ export async function taskNamesHere(cwd: string): Promise<string[] | null> {
52
+ const projects = await workspaceProjects(cwd)
53
+ if (projects === null) return null
54
+ const own = await findCwdProject(cwd)
55
+ const names = new Set<string>()
56
+ for (const p of projects.values())
57
+ if (own === null || p.name === own)
58
+ for (const t of Object.keys(p.config.tasks ?? {})) names.add(t)
59
+ return [...names].sort()
60
+ }
61
+
46
62
  async function workspaceProjects(
47
63
  cwd: string,
48
64
  ): Promise<Awaited<ReturnType<typeof loadCliProjects>> | null> {
@@ -387,6 +387,7 @@ export async function loadProjectConfigs(
387
387
  closure?: readonly [string, readonly string[]]
388
388
  }
389
389
  const builtins = builtinSnapshot()
390
+ const env = { ...process.env }
390
391
  const loadOne = async (entry: (typeof prepared)[number]): Promise<Loaded> => {
391
392
  const { configPath, cacheKey } = entry
392
393
  // A fast key that missed: the closure is stale or the file changed.
@@ -427,7 +428,7 @@ export async function loadProjectConfigs(
427
428
  : await loadDefaultExport(configPath, 'Project', bytes!)
428
429
  // Before anything reads through them: a replaced `Array.prototype.includes`
429
430
  // turned the JSON-data walk's own check into "a cyclic reference".
430
- const changed = repeat ? [] : restoreBuiltins(builtins)
431
+ const changed = repeat ? [] : [...restoreBuiltins(builtins), ...restoreEnv(env)]
431
432
  if (changed.length > 0) throw builtinsChanged(changed, configPath)
432
433
  assertDefaultObject(mod, 'Project', configPath)
433
434
  // Validation runs HERE, on whichever object we ended up with, so a
@@ -485,7 +486,7 @@ export async function loadProjectConfigs(
485
486
  if (r.closure !== undefined) learnedClosures.push(r.closure)
486
487
  }
487
488
  }
488
- const changed = restoreBuiltins(builtins)
489
+ const changed = [...restoreBuiltins(builtins), ...restoreEnv(env)]
489
490
  if (first !== undefined) throw first.failed
490
491
  if (changed.length > 0) throw builtinsChanged(changed)
491
492
  return results.map((r) => (r as Loaded).config)
@@ -539,8 +540,11 @@ function builtinSnapshot(): BuiltinSnapshot {
539
540
  /** Loads run together, so the config named is the one whose load saw the change. */
540
541
  function builtinsChanged(changed: readonly string[], configPath?: string): UserError {
541
542
  const who = configPath === undefined ? 'a project config' : configPath
543
+ const env = changed.some((c) => c.startsWith('process.env.'))
544
+ ? '; a task gets an env var through `exec.env.define` or `passThrough`'
545
+ : ''
542
546
  return new UserError(
543
- `${who} changed ${changed.join(', ')} while it was evaluated — a config must not change the built-ins vx runs on: other configs are read through them and cache keys are made with them`,
547
+ `${who} changed ${changed.join(', ')} while it was evaluated — a config must not change the built-ins vx runs on: other configs are read through them and cache keys are made with them${env}`,
544
548
  )
545
549
  }
546
550
 
@@ -567,6 +571,31 @@ function restoreBuiltins(before: BuiltinSnapshot): string[] {
567
571
  return changed
568
572
  }
569
573
 
574
+ /**
575
+ * A first load's `process.env.X = …` gave every other project's task that
576
+ * value through `passThrough` and reached vx's own `VX_*` reads, while a
577
+ * repeat load, in a worker, left both unchanged: one config, two
578
+ * environments, by load order (D-76). Assigned back, not defined:
579
+ * `process.env` refuses `defineProperty`.
580
+ */
581
+ function restoreEnv(before: Readonly<Record<string, string | undefined>>): string[] {
582
+ const changed: string[] = []
583
+ const live = process.env
584
+ for (const key of Object.keys(live)) {
585
+ if (live[key] === before[key]) continue
586
+ changed.push(`process.env.${key}`)
587
+ if (key in before) live[key] = before[key]
588
+ else delete live[key]
589
+ }
590
+ for (const key of Object.keys(before)) {
591
+ if (!(key in live)) {
592
+ changed.push(`process.env.${key}`)
593
+ live[key] = before[key]
594
+ }
595
+ }
596
+ return changed
597
+ }
598
+
570
599
  function sameDescriptor(a: PropertyDescriptor, b: PropertyDescriptor): boolean {
571
600
  return (
572
601
  a.value === b.value &&