@vzn/vx 0.0.455 → 0.0.457

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.455",
3
+ "version": "0.0.457",
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.455",
25
- "@vzn/vx-linux-arm64": "0.0.455",
26
- "@vzn/vx-darwin-x64": "0.0.455",
27
- "@vzn/vx-darwin-arm64": "0.0.455"
24
+ "@vzn/vx-linux-x64": "0.0.457",
25
+ "@vzn/vx-linux-arm64": "0.0.457",
26
+ "@vzn/vx-darwin-x64": "0.0.457",
27
+ "@vzn/vx-darwin-arm64": "0.0.457"
28
28
  },
29
29
  "dependencies": {
30
30
  "@anthropic-ai/sandbox-runtime": "^0.0.76"
@@ -41,6 +41,7 @@ import { chmodSync, renameSync, statSync, utimesSync } from 'node:fs'
41
41
  import { lstat, mkdir, readlink, realpath, rmdir, stat, unlink, writeFile } from 'node:fs/promises'
42
42
  import path from 'node:path'
43
43
  import { UserError } from '../util/index.js'
44
+ import { WORKSPACE_OUTPUT_PREFIX } from './layer.js'
44
45
  import { TarFormatError, type TarInput, tarEntries, tarPack, tarSize } from './tar-stream.js'
45
46
 
46
47
  /** Archive entry name carrying the per-output mode/mtime sidecar. */
@@ -236,11 +237,17 @@ export async function planArtifact(args: PackArgs): Promise<ArtifactPlan> {
236
237
  // file's bytes and comes back as a regular file. A link to a
237
238
  // directory, or a dangling one, has no bytes to pack: refuse loudly
238
239
  // rather than store an entry that restores to nothing.
239
- const shown = name.startsWith('outputs/') ? name.slice('outputs/'.length) : name
240
+ // The path as the config spells it: a root-anchored output's entry
241
+ // name is vx's own `workspace-outputs/…` (M-66).
242
+ const shown = name.startsWith('outputs/')
243
+ ? `output ${name.slice('outputs/'.length)}`
244
+ : name.startsWith(WORKSPACE_OUTPUT_PREFIX)
245
+ ? `workspaceFiles output ${name.slice(WORKSPACE_OUTPUT_PREFIX.length)}`
246
+ : `output ${name}`
240
247
  const dangling = (err: NodeJS.ErrnoException): never => {
241
248
  if (err.code === 'ENOENT') {
242
249
  throw new UserError(
243
- `output ${shown} is a dangling symlink: vx stores regular files only — emit a file there, or narrow cache.outputs.files to the files the task produces, or take it back with a '!' entry`,
250
+ `${shown} is a dangling symlink: vx stores regular files only — emit a file there, or narrow cache.outputs.files to the files the task produces, or take it back with a '!' entry`,
244
251
  )
245
252
  }
246
253
  throw err
@@ -256,7 +263,7 @@ export async function planArtifact(args: PackArgs): Promise<ArtifactPlan> {
256
263
  const root = await withinReal
257
264
  if (!src.startsWith(root + path.sep) && !(await ownOutputs()).has(src)) {
258
265
  throw new UserError(
259
- `output ${shown} is a symlink to ${src}, outside the project: vx packs a symlinked output as its target's bytes, and a project's outputs come from its own directory — emit a file there, or take it back with a '!' entry`,
266
+ `${shown} is a symlink to ${src}, outside the project: vx packs a symlinked output as its target's bytes, and a project's outputs come from its own directory — emit a file there, or take it back with a '!' entry`,
260
267
  )
261
268
  }
262
269
  }
@@ -264,7 +271,7 @@ export async function planArtifact(args: PackArgs): Promise<ArtifactPlan> {
264
271
  }
265
272
  if (!st.isFile()) {
266
273
  throw new UserError(
267
- `output ${shown} is not a regular file (a symlink to a directory?): vx stores regular files only — emit a file there, or narrow cache.outputs.files to the files the task produces, or take it back with a '!' entry`,
274
+ `${shown} is not a regular file (a symlink to a directory?): vx stores regular files only — emit a file there, or narrow cache.outputs.files to the files the task produces, or take it back with a '!' entry`,
268
275
  )
269
276
  }
270
277
  const mode = st.mode & 0o777
@@ -5,7 +5,8 @@
5
5
  // project names are not (a completion that evaluates configs on every
6
6
  // Tab is the wrong price).
7
7
 
8
- import { acceptedFlags, CORE_VERBS, seeHelp } from './help.js'
8
+ import { acceptedFlags, CORE_VERBS, flagHint, seeHelp } from './help.js'
9
+ import { nearest } from '../util/index.js'
9
10
 
10
11
  export type CompletionShell = 'bash' | 'zsh' | 'fish'
11
12
  const SHELLS: readonly CompletionShell[] = ['bash', 'zsh', 'fish']
@@ -95,8 +96,17 @@ export async function completionsCmd(
95
96
  ): Promise<number> {
96
97
  const shell = args[0]
97
98
  if (args.length !== 1 || !(SHELLS as readonly string[]).includes(shell ?? '')) {
99
+ const flag = args.find((a) => a.startsWith('-'))
100
+ if (flag !== undefined) {
101
+ process.stderr.write(
102
+ `vx completions: unknown flag: ${flag}${flagHint('completions', flag)}${seeHelp('completions')}\n`,
103
+ )
104
+ return 1
105
+ }
106
+ const one = shell !== undefined && args.length === 1
107
+ const best = one ? nearest(shell, SHELLS) : undefined
98
108
  process.stderr.write(
99
- `vx completions: expected one shell — bash, zsh or fish${shell !== undefined && args.length === 1 ? ` (got ${shell})` : ''}${seeHelp('completions')}\n`,
109
+ `vx completions: expected one shell — bash, zsh or fish${one ? ` (got ${shell})` : ''}${best === undefined ? '' : `. Did you mean ${best}?`}${seeHelp('completions')}\n`,
100
110
  )
101
111
  return 1
102
112
  }
package/src/cli/help.ts CHANGED
@@ -216,7 +216,8 @@ export function refusedWord(arg: string): string {
216
216
  export function flagHint(verb: string, arg: string): string {
217
217
  if (!arg.startsWith('-')) return ''
218
218
  const name = arg.replace(/=.*$/, '')
219
- const flags = acceptedFlags(verb)
219
+ // Every core verb answers `--help`, which no usage line spells.
220
+ const flags = ['--help', ...acceptedFlags(verb)]
220
221
  // `--json` is gh's and Nx's spelling; a verb with `--format` reads it
221
222
  // there, and no edit budget reaches `--format json` from `--json`.
222
223
  if (name === '--json' && flags.includes('--format')) return ' (did you mean --format json?)'
package/src/cli/index.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  // handler lives in a sibling `<name>.ts`; tests import its parsers there.
3
3
 
4
4
  import { VERSION } from '../version.js'
5
- import { CORE_VERBS, printHelp } from './help.js'
5
+ import { CORE_VERBS, flagHint, printHelp, refusedWord, seeHelp } from './help.js'
6
6
  import { FOREIGN_VERBS } from './foreign-flags.js'
7
7
  import { isUserError, MOVED_VERBS, nearest, UserError } from '../util/index.js'
8
8
 
@@ -28,7 +28,20 @@ export async function run(argv: readonly string[]): Promise<number> {
28
28
  }
29
29
 
30
30
  switch (command) {
31
- case undefined:
31
+ case undefined: {
32
+ // A bare `vx` where no workspace is printed 151 lines of help, none
33
+ // of which says the one thing wrong: there is nothing here to run.
34
+ const { findWorkspaceRoot } = await import('../workspace/index.js')
35
+ try {
36
+ await findWorkspaceRoot(process.cwd())
37
+ } catch (err) {
38
+ if (!isUserError(err)) throw err
39
+ process.stderr.write(`vx: ${err.message}; \`vx help\` lists the verbs\n`)
40
+ return 1
41
+ }
42
+ printHelp(await (await plugins()).pluginCommandHelp())
43
+ return 0
44
+ }
32
45
  case '--help':
33
46
  case '-h':
34
47
  printHelp(await (await plugins()).pluginCommandHelp())
@@ -49,6 +62,12 @@ export async function run(argv: readonly string[]): Promise<number> {
49
62
  process.stderr.write(`${pointer}\n`)
50
63
  return 1
51
64
  }
65
+ if (unknown && verb.startsWith('-')) {
66
+ process.stderr.write(
67
+ `vx help: unknown flag: ${verb}${flagHint('help', verb)} (see \`vx help\`)\n`,
68
+ )
69
+ return 1
70
+ }
52
71
  if (unknown) {
53
72
  const declared = resolved === null ? [] : resolved.declaredVerbs
54
73
  process.stderr.write(
@@ -61,9 +80,19 @@ export async function run(argv: readonly string[]): Promise<number> {
61
80
  return 0
62
81
  }
63
82
  case '--version':
64
- case 'version':
83
+ case 'version': {
84
+ // `vx version --hlp` printed the version and exited 0: a word it
85
+ // takes no more of is refused, as every other verb refuses one.
86
+ const extra = rest[0]
87
+ if (extra !== undefined) {
88
+ process.stderr.write(
89
+ `vx version: ${refusedWord(extra)}: ${extra}${flagHint('version', extra)}${seeHelp('version')}\n`,
90
+ )
91
+ return 1
92
+ }
65
93
  process.stdout.write(`vx ${VERSION}\n`)
66
94
  return 0
95
+ }
67
96
  case 'run':
68
97
  return await (await import('./run.js')).runCmd(rest)
69
98
  case 'watch':
@@ -13,7 +13,7 @@
13
13
  // deno upgrade — "update" is what package managers do to indexes.
14
14
 
15
15
  import { chmod, chown, link, rename, rm, stat } from 'node:fs/promises'
16
- import { seeHelp } from './help.js'
16
+ import { flagHint, seeHelp } from './help.js'
17
17
  import { UserError } from '../util/index.js'
18
18
  import { VERSION } from '../version.js'
19
19
 
@@ -275,7 +275,9 @@ export async function replaceBinary(
275
275
  export async function upgradeCmd(args: readonly string[]): Promise<number> {
276
276
  const unknown = args.find((a) => a.startsWith('-'))
277
277
  if (unknown !== undefined) {
278
- process.stderr.write(`vx upgrade: unknown flag: ${unknown}${seeHelp('upgrade')}\n`)
278
+ process.stderr.write(
279
+ `vx upgrade: unknown flag: ${unknown}${flagHint('upgrade', unknown)}${seeHelp('upgrade')}\n`,
280
+ )
279
281
  return 1
280
282
  }
281
283
  // One release is installed; a second tag was dropped without a word and
@@ -36,7 +36,16 @@ import {
36
36
  sandboxReads,
37
37
  } from '../exec/index.js'
38
38
  import { isGroupTask, RestoreDemoted, type TaskNode, type TaskOutcome } from '../graph/index.js'
39
- import { killGraceMs, maskedEmitter, printable, relPosix, secretMask, span } from '../util/index.js'
39
+ import {
40
+ killGraceMs,
41
+ MASKED,
42
+ maskedEmitter,
43
+ printable,
44
+ relPosix,
45
+ secretMask,
46
+ secretNamed,
47
+ span,
48
+ } from '../util/index.js'
40
49
  import { forwardedSignal, SIGNAL_SHUTDOWN_GRACE_MS } from './signals.js'
41
50
  import { executorLabel, nameExecutorFailure } from './plugin-host.js'
42
51
  import {
@@ -778,6 +787,13 @@ async function executeCachedTask(args: ExecuteArgs): Promise<TaskOutcome> {
778
787
  ...(args.hashCache !== undefined ? { hashCache: args.hashCache } : {}),
779
788
  })
780
789
  : undefined
790
+ // A name only `exec.env.secret` makes secret is not one the name rule
791
+ // sees later: its row carries the mark, so `vx why` hides its hash (M-63).
792
+ const named = node.config.exec?.env?.secret
793
+ if (named !== undefined)
794
+ for (const c of captured)
795
+ if (c.kind === 'env' && named.includes(c.name) && !secretNamed(c.name))
796
+ c.hash = MASKED + c.hash
781
797
  const inputs: TaskInputs | undefined = described?.inputs
782
798
  // A declared input set that resolves to NOTHING is the quiet stale hit:
783
799
  // the key stops moving with this project's source and every later run
@@ -557,11 +557,12 @@ export function diffKeyComponents(
557
557
  const old = new Map(before.map((c) => [id(c), c]))
558
558
  const entries: InputDiffEntry[] = []
559
559
  let unchangedCount = 0
560
- // A secret-named env input's hash is its value, unsalted: a short one is
560
+ // A secret env input's hash is its value, unsalted: a short one is
561
561
  // recoverable from a log that printed `vx why` (M-62). The diff says it
562
- // changed and never shows the hash.
562
+ // changed and never shows the hash. Secret by name, or by the mark a
563
+ // task's `exec.env.secret` put on its row (M-63).
563
564
  const shown = (c: TaskInputComponent): string =>
564
- c.kind === 'env' && secretNamed(c.name) ? MASKED : c.hash
565
+ c.kind === 'env' && (secretNamed(c.name) || c.hash.startsWith(MASKED)) ? MASKED : c.hash
565
566
  const keys = new Set<string>([...cur.keys(), ...old.keys()])
566
567
  for (const key of keys) {
567
568
  const a = cur.get(key)