@vzn/vx 0.0.642 → 0.0.644

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.
Files changed (52) hide show
  1. package/package.json +5 -5
  2. package/schemas/info.json +0 -11
  3. package/skills/vx/SKILL.md +1 -1
  4. package/src/cache/archive.ts +153 -2
  5. package/src/cache/cache.ts +417 -183
  6. package/src/cache/file-hashes.ts +2 -14
  7. package/src/cache/fold-memo.ts +7 -3
  8. package/src/cache/index.ts +10 -2
  9. package/src/cache/key-fold.ts +4 -1
  10. package/src/cache/layer.ts +11 -4
  11. package/src/cache/layered-cache.ts +82 -8
  12. package/src/cache/plugin-cache.ts +51 -43
  13. package/src/cache/remote-lane.ts +18 -13
  14. package/src/cache/run-store.ts +4 -1
  15. package/src/cache/scoped-cache.ts +74 -239
  16. package/src/cache/tar-stream.ts +12 -7
  17. package/src/cli/format.ts +1 -2
  18. package/src/cli/help.ts +8 -4
  19. package/src/cli/info.ts +2 -19
  20. package/src/cli/plugin-commands.ts +11 -6
  21. package/src/cli/run.ts +7 -12
  22. package/src/cli/select.ts +7 -9
  23. package/src/cli/upgrade.ts +38 -8
  24. package/src/cli/workspace-config.ts +8 -2
  25. package/src/config.ts +22 -26
  26. package/src/exec/sandbox-paths.ts +2 -2
  27. package/src/graph/scheduler.ts +3 -1
  28. package/src/graph/task-graph.ts +15 -11
  29. package/src/index.ts +5 -1
  30. package/src/orchestrator/affected-tasks.ts +23 -2
  31. package/src/orchestrator/doctor.ts +0 -17
  32. package/src/orchestrator/execute-task.ts +1 -1
  33. package/src/orchestrator/index.ts +4 -0
  34. package/src/orchestrator/options.ts +15 -1
  35. package/src/orchestrator/plugin-host.ts +11 -4
  36. package/src/orchestrator/plugin.ts +23 -2
  37. package/src/orchestrator/prepare.ts +81 -18
  38. package/src/orchestrator/projects.ts +26 -5
  39. package/src/orchestrator/remote-prefetch.ts +11 -4
  40. package/src/orchestrator/run-context.ts +85 -0
  41. package/src/orchestrator/run-settings.ts +3 -0
  42. package/src/orchestrator/run.ts +9 -26
  43. package/src/orchestrator/task-hash.ts +6 -0
  44. package/src/report/logger.ts +0 -4
  45. package/src/report/summary.ts +3 -11
  46. package/src/settings.ts +54 -25
  47. package/src/util/index.ts +1 -0
  48. package/src/util/paths.ts +42 -33
  49. package/src/util/size.ts +4 -5
  50. package/src/workspace/affected.ts +120 -63
  51. package/src/workspace/config-schema.ts +44 -52
  52. package/src/workspace/index.ts +3 -0
@@ -19,8 +19,6 @@ import {
19
19
  CORE_VERBS,
20
20
  nearest,
21
21
  normalizeGlob,
22
- parseDuration,
23
- parseSize,
24
22
  UserError,
25
23
  BUN_GLOB_WILDCARDS,
26
24
  EXTGLOB,
@@ -44,14 +42,39 @@ import { nonJsonMessage, nonJsonPaths } from './json-data.js'
44
42
  // declared no plugins and ran the workspace bare, `cacheDirectory` left the
45
43
  // cache where it was — a config that loads and quietly does nothing it says.
46
44
  const CACHE_SCOPE_RULE =
47
- "must be a scope name of letters, digits and . _ - / @ (at most 128), like 'pr-123'"
45
+ "must be a scope name of letters, digits and . _ - / @ : (at most 128), like 'vznjs/vx:feat-x'"
48
46
 
49
- /** `cache.scope`'s rule, shared with `VX_CACHE_SCOPE`. */
47
+ /** A cache scope's rule, for `VX_CACHE_SCOPE` and a plugin's `setBranch`. */
50
48
  export function isCacheScope(v: unknown): v is string {
51
- return typeof v === 'string' && /^[\w.@/-]{1,128}$/.test(v)
49
+ return typeof v === 'string' && /^[\w.@/:-]{1,128}$/.test(v)
52
50
  }
53
51
 
54
- /** Why a `VX_CACHE_SCOPE` or `--cache-scope` value is refused, in `cache.scope`'s words. */
52
+ /**
53
+ * A branch's cache scope, `<owner>/<repo>:<branch>`: what CI writes for a
54
+ * branch and a laptop on it reads (owner, 2026-10-17). The repository's
55
+ * identity, not a remote's local name, so a clone whose `origin` is a fork
56
+ * names the fork. A name may hold characters a scope may not; each
57
+ * becomes `_`.
58
+ */
59
+ const BASE_BRANCH_RULE = "must be a branch name like 'main'"
60
+
61
+ /**
62
+ * A branch name git takes after `origin/`: a leading '-' would reach git
63
+ * as an option, and whitespace, `..`, `~^:?*[\\` are no ref's.
64
+ */
65
+ function isBaseBranch(value: unknown): value is string {
66
+ return (
67
+ typeof value === 'string' &&
68
+ /^[^-\s\0~^:?*[\\][^\s\0~^:?*[\\]*$/.test(value) &&
69
+ !value.includes('..')
70
+ )
71
+ }
72
+
73
+ export function branchCacheScope(repo: string, branch: string): string {
74
+ return `${repo.toLowerCase()}:${branch}`.replace(/[^\w.@/:-]/g, '_').slice(0, 128)
75
+ }
76
+
77
+ /** Why a `VX_CACHE_SCOPE` value is refused. */
55
78
  export function cacheScopeError(source = 'VX_CACHE_SCOPE'): string {
56
79
  return `${source} ${CACHE_SCOPE_RULE}`
57
80
  }
@@ -61,6 +84,15 @@ export function cacheScopeEnvError(): UserError {
61
84
  return new UserError(cacheScopeError())
62
85
  }
63
86
 
87
+ /** `baseBranch`: `VX_BASE_BRANCH`, else the workspace's; undefined lets git name it. */
88
+ export function configuredBaseBranch(workspace: WorkspaceConfig | null): string | undefined {
89
+ const env = settingEnv('baseBranch')
90
+ if (env !== undefined && !isBaseBranch(env)) {
91
+ throw new UserError(`VX_BASE_BRANCH ${BASE_BRANCH_RULE}`, 'VX_E_USAGE')
92
+ }
93
+ return (env as string | undefined) ?? workspace?.baseBranch
94
+ }
95
+
64
96
  const STRATEGIES = ['immutable', 'mutable', 'unchecked'] as const
65
97
  const SEVERITIES = ['warn', 'error'] as const
66
98
  const INPUT_STABILITY_FIELDS = new Set(['strategy', 'severity'])
@@ -98,7 +130,7 @@ export function inputStabilityEnv(): InputStability {
98
130
  }
99
131
  }
100
132
 
101
- const WORKSPACE_FIELDS = new Set<string>([...SETTING_GROUPS, 'plugins'])
133
+ const WORKSPACE_FIELDS = new Set<string>([...SETTING_GROUPS, 'baseBranch', 'plugins'])
102
134
 
103
135
  /**
104
136
  * `.vx/workspace`'s settings, as `src/settings.ts` lists them: each group's
@@ -126,7 +158,7 @@ function validateSettingsAt(value: unknown, at: string, configPath: string): voi
126
158
  if (at === 'cache')
127
159
  // Turbo's and Nx's workspace `cache: true` is a task's own `cache` in vx (D-98).
128
160
  throw new UserError(
129
- `${where} must be an object like { dir, policy, scope }; a task caches through its own \`cache\` in its project's vx.config`,
161
+ `${where} must be an object like { dir, policy }; a task caches through its own \`cache\` in its project's vx.config`,
130
162
  )
131
163
  throw new UserError(`${where} must be an object (fields: ${[...fields].join(', ')})`)
132
164
  }
@@ -169,15 +201,6 @@ function validateDependencyRules(value: unknown, where: string): void {
169
201
  /** `value` as the setting at `path` takes it, refused in `where`'s words. */
170
202
  function validateSettingValue(value: unknown, path: string, where: string): void {
171
203
  const { kind } = setting(path)
172
- // The bound `vx cache clean` refuses too: each run would empty the cache,
173
- // the entries it just saved included, so nothing ever hits (item 969).
174
- if (
175
- path === 'cache.retention.olderThan' &&
176
- typeof value === 'string' &&
177
- parseDuration(value) === 0
178
- ) {
179
- throw new UserError(`${where} of 0 evicts every entry after every run`)
180
- }
181
204
  // A file's values are typed: a count or a switch is no string (only a
182
205
  // share of the cores is), as a variable's or a flag's must be.
183
206
  const untyped =
@@ -197,19 +220,12 @@ function validateSettingsExtra(config: WorkspaceConfig, configPath: string): voi
197
220
  // unwritable workspace.
198
221
  throw new UserError(`${configPath}: \`cache.dir\` holds a NUL, which no path can carry`)
199
222
  }
200
- const base = config.run?.affectedBase
201
- // A leading '-' would reach git as an option, not a ref; a NUL fails the
202
- // spawn with an argument error naming nothing in this file.
203
- if (base !== undefined && !/^[^-\s\0][^\s\0]*$/.test(base)) {
204
- throw new UserError(`${configPath}: \`run.affectedBase\` must be a git ref like 'origin/main'`)
223
+ const base = config.baseBranch
224
+ if (base !== undefined && !isBaseBranch(base)) {
225
+ throw new UserError(`${configPath}: \`baseBranch\` ${BASE_BRANCH_RULE}`)
205
226
  }
206
227
  // `policy`'s grammar is the cache module's (`parseCachePolicy`), which
207
228
  // this module may not import: the run parses it before any task (prepare.ts).
208
- const scope = config.cache?.scope
209
- if (scope !== undefined && !isCacheScope(scope)) {
210
- throw new UserError(`${configPath}: \`cache.scope\` ${CACHE_SCOPE_RULE}`)
211
- }
212
- if (config.cache?.retention !== undefined) validateRetention(config.cache.retention, configPath)
213
229
  }
214
230
 
215
231
  /**
@@ -222,30 +238,6 @@ function shown(v: unknown): string {
222
238
  return typeof v === 'bigint' ? `${v}n` : (JSON.stringify(v) ?? String(v))
223
239
  }
224
240
 
225
- function validateRetention(
226
- retention: NonNullable<NonNullable<WorkspaceConfig['cache']>['retention']>,
227
- configPath: string,
228
- ): void {
229
- const { olderThan, maxSize } = retention
230
- // An empty object would load and evict nothing, which reads as a policy.
231
- if (olderThan === undefined && maxSize === undefined) {
232
- throw new UserError(
233
- `${configPath}: \`cache.retention\` names neither \`olderThan\` nor \`maxSize\``,
234
- )
235
- }
236
- // As for olderThan (item 969).
237
- if (maxSize !== undefined && parseSize(maxSize) === 0) {
238
- throw new UserError(
239
- `${configPath}: \`cache.retention.maxSize\` of 0 evicts every entry after every run`,
240
- )
241
- }
242
- if (maxSize !== undefined && /^\d+$/.test(maxSize)) {
243
- throw new UserError(
244
- `${configPath}: \`cache.retention.maxSize\` '${maxSize}' reads as ${maxSize} bytes — give a unit (e.g. '${maxSize}MB', '${maxSize}GB')`,
245
- )
246
- }
247
- }
248
-
249
241
  /** A `plugins` entry that stands for no plugin: `undefined`, `null` or `false`. */
250
242
  export function isSkippedPlugin(p: unknown): p is undefined | null | false {
251
243
  return p === undefined || p === null || p === false
@@ -1103,7 +1095,7 @@ const FOREIGN_FIELDS: ReadonlyMap<ReadonlySet<string>, Readonly<Record<string, s
1103
1095
  'a cache plugin in `plugins` (`turboCache()` or `nxCache()` from `@vzn/vx-migrate`)',
1104
1096
  parallel: '`run.concurrency`',
1105
1097
  cacheDirectory: '`cache.dir`',
1106
- defaultBase: '`run.affectedBase`',
1098
+ defaultBase: '`baseBranch`',
1107
1099
  tasksRunnerOptions: 'a cache plugin in `plugins` (`nxCache()` from `@vzn/vx-migrate`)',
1108
1100
  globalPassThroughEnv: '`exec.env.passThrough` on the tasks it passes to',
1109
1101
  // pnpm-workspace.yaml's, Lerna's and the package.json's own keys
@@ -6,6 +6,7 @@ export {
6
6
  type AffectedChanges,
7
7
  affectedProjects,
8
8
  defaultAffectedBase,
9
+ branchChanges,
9
10
  type FingerprintClaims,
10
11
  refIsHead,
11
12
  } from './affected.js'
@@ -35,8 +36,10 @@ export {
35
36
  WORKSPACE_CONFIG_FILENAMES,
36
37
  } from './project-loader.js'
37
38
  export {
39
+ branchCacheScope,
38
40
  cacheScopeEnvError,
39
41
  cacheScopeError,
42
+ configuredBaseBranch,
40
43
  inputStabilityEnv,
41
44
  isCacheScope,
42
45
  taskNameProblem,