@vzn/vx 0.0.609 → 0.0.610

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.610",
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.610",
25
+ "@vzn/vx-linux-arm64": "0.0.610",
26
+ "@vzn/vx-darwin-x64": "0.0.610",
27
+ "@vzn/vx-darwin-arm64": "0.0.610"
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/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)