@vzn/vx 0.0.114 → 0.0.115

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.114",
3
+ "version": "0.0.115",
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.114",
21
- "@vzn/vx-linux-arm64": "0.0.114",
22
- "@vzn/vx-darwin-x64": "0.0.114",
23
- "@vzn/vx-darwin-arm64": "0.0.114"
20
+ "@vzn/vx-linux-x64": "0.0.115",
21
+ "@vzn/vx-linux-arm64": "0.0.115",
22
+ "@vzn/vx-darwin-x64": "0.0.115",
23
+ "@vzn/vx-darwin-arm64": "0.0.115"
24
24
  },
25
25
  "dependencies": {
26
26
  "@anthropic-ai/sandbox-runtime": "^0.0.76"
@@ -101,7 +101,8 @@ export async function completionsCmd(
101
101
  return 1
102
102
  }
103
103
  const verbs = [...CORE_VERBS.filter((v) => v !== 'help' && v !== 'version'), 'help', 'version']
104
- for (const v of pluginVerbs) if (!verbs.includes(v)) verbs.push(v)
104
+ // No overlap to skip: the workspace load refuses a plugin verb named like a core one.
105
+ verbs.push(...pluginVerbs)
105
106
  process.stdout.write(completionScript(shell as CompletionShell, verbs))
106
107
  return 0
107
108
  }
@@ -23,7 +23,11 @@
23
23
  // hook is usually `rimraf dist`); and a script that is nothing but
24
24
  // `<pm> run <other>` becomes a GROUP over `<other>`, so the graph sees the
25
25
  // dependency instead of a package-manager subprocess it cannot cache.
26
+ // Yarn 2+ runs no such hooks (probed: `yarn run build` printed BUILD alone),
27
+ // so under it `prebuild` is a script like any other (D-31).
26
28
 
29
+ import { existsSync, readFileSync } from 'node:fs'
30
+ import path from 'node:path'
27
31
  import { taskNameProblem } from './config-schema.js'
28
32
  import type { ProjectMeta } from './workspace.js'
29
33
  import {
@@ -82,6 +86,40 @@ const OWN_COMMANDS: Readonly<Record<string, ReadonlySet<string>>> = {
82
86
  ),
83
87
  }
84
88
 
89
+ /**
90
+ * Whether the package manager that owns `dir` is Yarn 2+: the nearest
91
+ * `packageManager: yarn@<2+>`, or the nearest lockfile being a Berry
92
+ * `yarn.lock` (it opens with `__metadata:`). Another lockfile, or none,
93
+ * is a manager that runs `pre` / `post` hooks.
94
+ */
95
+ function isYarnBerry(dir: string, memo: Map<string, boolean>): boolean {
96
+ const known = memo.get(dir)
97
+ if (known !== undefined) return known
98
+ let answer: boolean | undefined
99
+ const manifest = path.join(dir, 'package.json')
100
+ try {
101
+ const pm = (JSON.parse(readFileSync(manifest, 'utf8')) as { packageManager?: unknown })
102
+ .packageManager
103
+ if (typeof pm === 'string') answer = /^yarn@([2-9]|\d{2,})/.test(pm)
104
+ } catch {}
105
+ if (answer === undefined) {
106
+ const lock = path.join(dir, 'yarn.lock')
107
+ if (existsSync(lock)) answer = readFileSync(lock, 'utf8').includes('\n__metadata:')
108
+ else if (
109
+ ['package-lock.json', 'pnpm-lock.yaml', 'bun.lock', 'bun.lockb'].some((f) =>
110
+ existsSync(path.join(dir, f)),
111
+ )
112
+ )
113
+ answer = false
114
+ }
115
+ if (answer === undefined) {
116
+ const up = path.dirname(dir)
117
+ answer = up === dir ? false : isYarnBerry(up, memo)
118
+ }
119
+ memo.set(dir, answer)
120
+ return answer
121
+ }
122
+
85
123
  function scriptsOf(meta: ProjectMeta): Record<string, unknown> {
86
124
  // package.json is a boundary: `scripts` is whatever the file holds. A
87
125
  // string or an array would enumerate its indices as script names.
@@ -132,8 +170,10 @@ function upstreamBuildOnWorker(tasks: GeneratedTask[]): void {
132
170
 
133
171
  export function migrateScripts(metas: readonly ProjectMeta[]): MigrationPlan {
134
172
  const projects: GeneratedProject[] = []
173
+ const berry = new Map<string, boolean>()
135
174
  for (const meta of metas) {
136
175
  const scripts = scriptsOf(meta)
176
+ const runsHooks = !isYarnBerry(meta.dir, berry)
137
177
  const runnable = Object.keys(scripts).filter(
138
178
  (n) => typeof scripts[n] === 'string' && scripts[n] !== '',
139
179
  )
@@ -153,7 +193,7 @@ export function migrateScripts(metas: readonly ProjectMeta[]): MigrationPlan {
153
193
  // of a script that exists rides inside that script's command.
154
194
  const isTask = (n: string): boolean => {
155
195
  if (!has(n) || LIFECYCLE.test(n)) return false
156
- const hookOf = /^(pre|post)(.+)$/.exec(n)
196
+ const hookOf = runsHooks ? /^(pre|post)(.+)$/.exec(n) : null
157
197
  return hookOf === null || !has(hookOf[2]!) || LIFECYCLE.test(hookOf[2]!)
158
198
  }
159
199
  const tasks: GeneratedTask[] = []
@@ -165,7 +205,7 @@ export function migrateScripts(metas: readonly ProjectMeta[]): MigrationPlan {
165
205
  const delegate = delegatedScript(own)
166
206
  // npm lifecycle hooks (`prepack`, `prepublishOnly`, …) belong to the
167
207
  // package manager and never ride inside a task — `pack` stays alone.
168
- const hook = (h: string): boolean => has(h) && !LIFECYCLE.test(h)
208
+ const hook = (h: string): boolean => runsHooks && has(h) && !LIFECYCLE.test(h)
169
209
  const hooks = [`pre${name}`, `post${name}`].filter(hook)
170
210
  // A group over a script that is no task (`setup: npm run prepare`)
171
211
  // named a task nothing defines, and the run `vx init` suggested