@vzn/vx 0.0.474 → 0.0.476

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.474",
3
+ "version": "0.0.476",
4
4
  "description": "The fastest task runner and build cache for JavaScript monorepos.",
5
5
  "type": "module",
6
6
  "exports": {
@@ -21,10 +21,10 @@
21
21
  "node": ">=18"
22
22
  },
23
23
  "optionalDependencies": {
24
- "@vzn/vx-linux-x64": "0.0.474",
25
- "@vzn/vx-linux-arm64": "0.0.474",
26
- "@vzn/vx-darwin-x64": "0.0.474",
27
- "@vzn/vx-darwin-arm64": "0.0.474"
24
+ "@vzn/vx-linux-x64": "0.0.476",
25
+ "@vzn/vx-linux-arm64": "0.0.476",
26
+ "@vzn/vx-darwin-x64": "0.0.476",
27
+ "@vzn/vx-darwin-arm64": "0.0.476"
28
28
  },
29
29
  "dependencies": {
30
30
  "@anthropic-ai/sandbox-runtime": "^0.0.76"
@@ -70,7 +70,14 @@ export function completionScript(shell: CompletionShell, verbs: readonly string[
70
70
  ...table.map((t) => ` ${t.verb}) compadd -- ${t.words.join(' ')} ;;`),
71
71
  ' esac',
72
72
  '}',
73
- 'compdef _vx vx',
73
+ // Autoloaded from $fpath (the `#compdef` file), this body runs on
74
+ // the first Tab: it must complete then, not only define `_vx`, or
75
+ // that Tab rings the bell. Sourced, it registers instead.
76
+ 'if [[ "${funcstack[1]}" == _vx ]]; then',
77
+ ' _vx "$@"',
78
+ 'else',
79
+ ' compdef _vx vx',
80
+ 'fi',
74
81
  '',
75
82
  ].join('\n')
76
83
  case 'fish':
package/src/cli/index.ts CHANGED
@@ -165,9 +165,15 @@ export async function run(argv: readonly string[]): Promise<number> {
165
165
  }
166
166
  // A task typed where the verb goes (`turbo build`, `nx build app`),
167
167
  // `turbo dev` included: a repo's own `dev` task beats the no-service note.
168
+ const declaredVerbs =
169
+ resolved !== null && 'declaredVerbs' in resolved ? resolved.declaredVerbs : []
170
+ const guess = didYouMeanVerb(command, declaredVerbs)
171
+ // A typo of a task (`vx biuld`) names the task, unless a verb is closer.
168
172
  const task =
169
173
  loadNote === ''
170
- ? await (await import('./task-verb.js')).taskVerbHint(command, rest, process.cwd())
174
+ ? await (
175
+ await import('./task-verb.js')
176
+ ).taskVerbHint(command, rest, process.cwd(), guess === '' && !command.startsWith('-'))
171
177
  : null
172
178
  if (task !== null) {
173
179
  process.stderr.write(`vx: ${task}\n`)
@@ -207,9 +213,6 @@ export async function run(argv: readonly string[]): Promise<number> {
207
213
  )
208
214
  return 1
209
215
  }
210
- const declaredVerbs =
211
- resolved !== null && 'declaredVerbs' in resolved ? resolved.declaredVerbs : []
212
- const guess = didYouMeanVerb(command, declaredVerbs)
213
216
  process.stderr.write(
214
217
  `vx: unknown command: ${command}${guess}${loadNote} (see \`vx help\`)\n` +
215
218
  (guess === '' && loadNote === '' ? verbSourceNote(resolved, declaredVerbs) : ''),
@@ -8,8 +8,9 @@
8
8
  // before this is asked, so a plugin cannot shadow `run`.
9
9
 
10
10
  import type { VxPlugin, PluginCommand, CommandContext } from '../orchestrator/index.js'
11
- import { findWorkspaceRoot } from '../workspace/index.js'
12
- import { loadCliWorkspace } from './workspace-config.js'
11
+ import { existsSync } from 'node:fs'
12
+ import path from 'node:path'
13
+ import { findWorkspaceRoot, WORKSPACE_CONFIG_FILENAMES } from '../workspace/index.js'
13
14
  import { machineParallelism, maskedLine } from '../util/index.js'
14
15
 
15
16
  export interface ResolvedPluginCommand {
@@ -52,8 +53,14 @@ async function workspacePlugins(
52
53
  } catch {
53
54
  return null
54
55
  }
56
+ // Plugins are declared only in a workspace file. Without one, the load
57
+ // below is the orchestrator's import graph (~50 ms) for an empty list,
58
+ // paid by `vx help` and by the `vx completions` a shell sources at start.
59
+ if (!WORKSPACE_CONFIG_FILENAMES.some((f) => existsSync(path.join(workspaceRoot, f))))
60
+ return { workspaceRoot, cacheDir: '', concurrency: 0, plugins: [] }
55
61
  let ws
56
62
  try {
63
+ const { loadCliWorkspace } = await import('./workspace-config.js')
57
64
  ws = await loadCliWorkspace(workspaceRoot)
58
65
  } catch (err) {
59
66
  return { loadError: err instanceof Error ? err.message : String(err) }
@@ -6,28 +6,39 @@
6
6
 
7
7
  import { nxProjectTarget, projectNamed } from '../orchestrator/index.js'
8
8
  import { findWorkspaceRoot, loadWorkspace } from '../workspace/index.js'
9
+ import { nearest } from '../util/index.js'
9
10
  import { findCwdProject } from './select.js'
10
11
  import { discoverCliProjects, loadCliProjects } from './workspace-config.js'
11
12
 
12
- /** The `vx run` a task typed as a verb means, or null when no project declares it. */
13
+ /**
14
+ * The `vx run` a task typed as a verb means, or null when no project
15
+ * declares it. With `typo`, a word a few edits from a task (`vx biuld`)
16
+ * names that task: the caller passes it only when no verb is closer.
17
+ */
13
18
  export async function taskVerbHint(
14
19
  command: string,
15
20
  rest: readonly string[],
16
21
  cwd: string,
22
+ typo = false,
17
23
  ): Promise<string | null> {
18
24
  if (command.includes('#')) return `\`${command}\` is a task: vx run ${command}`
19
25
  const projects = await workspaceProjects(cwd)
20
26
  if (projects === null) return null
21
- if (![...projects.values()].some((p) => p.config.tasks?.[command] !== undefined)) return null
27
+ const tasks = new Set<string>()
28
+ for (const p of projects.values()) for (const t of Object.keys(p.config.tasks ?? {})) tasks.add(t)
29
+ const task = tasks.has(command) ? command : typo ? nearest(command, tasks) : undefined
30
+ if (task === undefined) return null
22
31
  // Nx's `nx build app`: the word after the target is its project.
23
32
  const project = rest[0] === undefined ? undefined : projectNamed(rest[0], projects)
24
33
  const run =
25
34
  project !== undefined
26
- ? `vx run ${command} --filter ${project}`
35
+ ? `vx run ${task} --filter ${project}`
27
36
  : (await findCwdProject(cwd)) === null
28
- ? `vx run ${command} --all`
29
- : `vx run ${command}`
30
- return `\`${command}\` is a task here, not a command: ${run}`
37
+ ? `vx run ${task} --all`
38
+ : `vx run ${task}`
39
+ return task === command
40
+ ? `\`${command}\` is a task here, not a command: ${run}`
41
+ : `unknown command: ${command}; did you mean the task \`${task}\`? ${run}`
31
42
  }
32
43
 
33
44
  /**
@@ -13,6 +13,9 @@ const ABSENT = -1n
13
13
  function settledState(abs: string): bigint {
14
14
  let st: fs.Stats
15
15
  try {
16
+ // A link is its target string, as the key folds it: following it saw
17
+ // a retarget to equal bytes as "the same" and ran nothing.
18
+ if (fs.lstatSync(abs).isSymbolicLink()) return xxh3(`link:${fs.readlinkSync(abs)}`)
16
19
  st = fs.statSync(abs)
17
20
  } catch {
18
21
  return ABSENT
@@ -484,9 +484,16 @@ export function validateProjectConfig(config: ProjectConfig, configPath: string)
484
484
  assertKnownFields(cache, CACHE_FIELDS, `${where}.cache`)
485
485
  const inputs = (cache as { inputs?: unknown }).inputs
486
486
  const outputs = (cache as { outputs?: unknown }).outputs
487
- if (!inputs || typeof inputs !== 'object') {
487
+ // A present field of the wrong shape (`outputs: 'dist'`) was told it
488
+ // was missing; an array is named in `assertKnownFields`.
489
+ if (inputs === undefined || inputs === null) {
488
490
  throw new UserError(`${where}.cache.inputs is required when \`cache\` is set`)
489
491
  }
492
+ if (typeof inputs !== 'object') {
493
+ throw new UserError(
494
+ `${where}.cache.inputs must be an object — \`inputs: { files: [...] }\``,
495
+ )
496
+ }
490
497
  assertKnownFields(inputs, CACHE_INPUT_FIELDS, `${where}.cache.inputs`)
491
498
  if (!Array.isArray((inputs as { files?: unknown }).files)) {
492
499
  throw new UserError(`${where}.cache.inputs.files must be an array of glob strings`)
@@ -520,9 +527,14 @@ export function validateProjectConfig(config: ProjectConfig, configPath: string)
520
527
  }
521
528
  }
522
529
  }
523
- if (!outputs || typeof outputs !== 'object') {
530
+ if (outputs === undefined || outputs === null) {
524
531
  throw new UserError(`${where}.cache.outputs is required when \`cache\` is set`)
525
532
  }
533
+ if (typeof outputs !== 'object') {
534
+ throw new UserError(
535
+ `${where}.cache.outputs must be an object — \`outputs: { files: [...] }\``,
536
+ )
537
+ }
526
538
  assertKnownFields(outputs, CACHE_OUTPUT_FIELDS, `${where}.cache.outputs`)
527
539
  const outFiles = (outputs as { files?: unknown }).files
528
540
  if (!Array.isArray(outFiles)) {