@vzn/vx 0.0.609 → 0.0.611

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.609",
3
+ "version": "0.0.611",
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.609",
25
- "@vzn/vx-linux-arm64": "0.0.609",
26
- "@vzn/vx-darwin-x64": "0.0.609",
27
- "@vzn/vx-darwin-arm64": "0.0.609"
24
+ "@vzn/vx-linux-x64": "0.0.611",
25
+ "@vzn/vx-linux-arm64": "0.0.611",
26
+ "@vzn/vx-darwin-x64": "0.0.611",
27
+ "@vzn/vx-darwin-arm64": "0.0.611"
28
28
  },
29
29
  "dependencies": {
30
30
  "@anthropic-ai/sandbox-runtime": "^0.0.76"
@@ -28,6 +28,7 @@ import {
28
28
  isLiteralPattern,
29
29
  normalizeGlob,
30
30
  outputMatcher,
31
+ realpathOf,
31
32
  relPosix,
32
33
  shellArgv,
33
34
  slashBraceExpansions,
@@ -1142,7 +1143,7 @@ export async function cleanWorkspaceOutputs(args: {
1142
1143
 
1143
1144
  function realOrNull(p: string): string | null {
1144
1145
  try {
1145
- return realpathSync(p)
1146
+ return realpathOf(p)
1146
1147
  } catch {
1147
1148
  return null
1148
1149
  }
package/src/cli/lock.ts CHANGED
@@ -8,7 +8,6 @@ import { relPosix, secretMask, xxh3hex } from '../util/index.js'
8
8
  import {
9
9
  findWorkspaceRoot,
10
10
  type LoadReads,
11
- listProjects,
12
11
  loadProjectConfig,
13
12
  loadWorkspace,
14
13
  LOCKFILE_NAME,
@@ -19,6 +18,7 @@ import {
19
18
  type LockfileEntry,
20
19
  type ProjectMeta,
21
20
  } from '../workspace/index.js'
21
+ import { discoverCliProjects } from './workspace-config.js'
22
22
 
23
23
  interface LockArgs {
24
24
  check: boolean
@@ -49,7 +49,13 @@ export async function lockCmd(args: readonly string[]): Promise<number> {
49
49
  const reads: LoadReads = new Map()
50
50
  const root = await findWorkspaceRoot(process.cwd(), reads)
51
51
  const workspace = await loadWorkspace(root, reads)
52
- const all = await listProjects(workspace)
52
+ // As a run discovers them: a project a plugin's `discover` stage adds
53
+ // was left out of the lock, and every `--frozen` run then refused it
54
+ // with "run vx lock", which changed nothing (X-143).
55
+ // Sorted by name, as core's discovery is, for stable lockfile diffs.
56
+ const all = (await discoverCliProjects(workspace)).sort((a, b) =>
57
+ a.name < b.name ? -1 : a.name > b.name ? 1 : 0,
58
+ )
53
59
  const metas = all.filter((m): m is ConfiguredMeta => m.configPath !== null)
54
60
  // A project with no vx.config has nothing to freeze: its tasks (a plugin's,
55
61
  // or none) load live under --frozen too, so both verbs say how many the
@@ -83,7 +89,7 @@ function bareNote(bare: number): string {
83
89
 
84
90
  async function writeLock(root: string, metas: ConfiguredMeta[], bare: number): Promise<number> {
85
91
  const entries = await Promise.all(metas.map((m) => evaluateEntry(root, m)))
86
- // `listProjects` sorts by name — stable lockfile diffs for free. Defined,
92
+ // Sorted by name above — stable lockfile diffs. Defined,
87
93
  // not assigned: a project named `__proto__` set the map's prototype (D-152).
88
94
  const projects: Record<string, LockfileEntry> = Object.fromEntries(
89
95
  metas.map((m, i) => [m.name, entries[i]!]),
package/src/cli/show.ts CHANGED
@@ -154,13 +154,17 @@ export async function showCmd(args: readonly string[]): Promise<number> {
154
154
  }
155
155
 
156
156
  if (bareTask) {
157
- const declaring = [...projects.values()].filter(
158
- (p) => declaredTask(p.config, projectName!) !== undefined,
157
+ // As `vx run <task> --all` takes it: the default build is declared by no one (X-145).
158
+ const declares = (task: TaskConfig | undefined): boolean =>
159
+ task !== undefined && !isDefaultBuild(task)
160
+ const declaring = [...projects.values()].filter((p) =>
161
+ declares(declaredTask(p.config, projectName!)),
159
162
  )
160
163
  if (declaring.length === 0) {
161
164
  const names = new Set<string>()
162
165
  for (const p of projects.values())
163
- for (const t of Object.keys(p.config.tasks ?? {})) names.add(t)
166
+ for (const [t, task] of Object.entries(p.config.tasks ?? {}))
167
+ if (declares(task)) names.add(t)
164
168
  // `nx show projects` lists them; here a bare `vx show` does.
165
169
  const nx = projectName === 'projects' ? ' (`nx show projects` is `vx show` here)' : ''
166
170
  throw new UserError(
@@ -25,7 +25,10 @@ export async function taskVerbHint(
25
25
  const projects = await workspaceProjects(cwd)
26
26
  if (projects === null) return null
27
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)
28
+ // The default build is no task `vx run` takes by name (X-145).
29
+ for (const p of projects.values())
30
+ for (const [t, task] of Object.entries(p.config.tasks ?? {}))
31
+ if (!isDefaultBuild(task)) tasks.add(t)
29
32
  const task = tasks.has(command) ? command : typo ? nearest(command, tasks) : undefined
30
33
  if (task === undefined) return null
31
34
  // Nx's `nx build app`: the word after the target is its project.
package/src/util/index.ts CHANGED
@@ -74,7 +74,7 @@ export { isUnsupportedBun, MIN_BUN, unsupportedBunMessage } from './bun-version.
74
74
  export { executablePath, shellArgv, taskShell } from './which.js'
75
75
  export { procfsIsOwn } from './procfs.js'
76
76
  export { hangupIgnored } from './hangup.js'
77
- export { realPath } from './real-path.js'
77
+ export { realPath, realpathOf } from './real-path.js'
78
78
  export {
79
79
  maskedCommand,
80
80
  maskedEmitter,
@@ -1,4 +1,5 @@
1
- import { realpathSync } from 'node:fs'
1
+ import { readlinkSync, realpathSync } from 'node:fs'
2
+ import path from 'node:path'
2
3
 
3
4
  /**
4
5
  * A path's canonical spelling: the OS's own answer for the final path,
@@ -7,3 +8,50 @@ import { realpathSync } from 'node:fs'
7
8
  export function realPath(p: string): string {
8
9
  return realpathSync.native(p)
9
10
  }
11
+
12
+ /**
13
+ * `realpathSync`, for a path holding a backslash too: Bun's answers ENOENT
14
+ * for any such path (1.4.2), so an output under `dist/a\b/` was dropped
15
+ * from its artifact. Such a path's links are read one by one. Throws as
16
+ * `realpathSync` does when the path is not there.
17
+ */
18
+ export function realpathOf(p: string): string {
19
+ try {
20
+ return realpathSync(p)
21
+ } catch (err) {
22
+ if (!p.includes('\\')) throw err
23
+ return resolveLinks(p)
24
+ }
25
+ }
26
+
27
+ function resolveLinks(p: string): string {
28
+ // Not `path.resolve`: it folds `link/..` lexically, and the kernel
29
+ // takes `..` from where the link leads.
30
+ const abs = path.isAbsolute(p) ? p : `${process.cwd()}${path.sep}${p}`
31
+ let at = path.parse(abs).root
32
+ const pending = abs.slice(at.length).split(path.sep).filter(Boolean).reverse()
33
+ let hops = 0
34
+ while (pending.length > 0) {
35
+ const next = path.join(at, pending.pop()!)
36
+ let link: string
37
+ try {
38
+ link = readlinkSync(next)
39
+ } catch (err) {
40
+ if ((err as NodeJS.ErrnoException).code !== 'EINVAL') throw err
41
+ at = next
42
+ continue
43
+ }
44
+ // Linux's own bound on links followed in one lookup (MAXSYMLINKS).
45
+ if (++hops > 40) {
46
+ throw Object.assign(
47
+ new Error(`ELOOP: too many symbolic links encountered, realpath '${p}'`),
48
+ {
49
+ code: 'ELOOP',
50
+ },
51
+ )
52
+ }
53
+ if (path.isAbsolute(link)) at = path.parse(link).root
54
+ pending.push(...link.split('/').filter(Boolean).reverse())
55
+ }
56
+ return at
57
+ }
@@ -12,6 +12,7 @@ import {
12
12
  slashBraceExpansions,
13
13
  UserError,
14
14
  normalizeBunGlob,
15
+ realpathOf,
15
16
  } from '../util/index.js'
16
17
  import { type LoadReads, readOnce, unreadable } from './load-reads.js'
17
18
  import { repoIdOf } from './repo-id.js'
@@ -897,7 +898,7 @@ async function oneEntryPerPackage<T extends { dir: string }>(
897
898
  ): Promise<T[]> {
898
899
  const byReal = new Map<string, T[]>()
899
900
  for (const entry of group) {
900
- const real = await realpath(entry.dir)
901
+ const real = realpathOf(entry.dir)
901
902
  const same = byReal.get(real)
902
903
  if (same === undefined) byReal.set(real, [entry])
903
904
  else same.push(entry)