@vzn/vx 0.0.282 → 0.0.284

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.282",
3
+ "version": "0.0.284",
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.282",
21
- "@vzn/vx-linux-arm64": "0.0.282",
22
- "@vzn/vx-darwin-x64": "0.0.282",
23
- "@vzn/vx-darwin-arm64": "0.0.282"
20
+ "@vzn/vx-linux-x64": "0.0.284",
21
+ "@vzn/vx-linux-arm64": "0.0.284",
22
+ "@vzn/vx-darwin-x64": "0.0.284",
23
+ "@vzn/vx-darwin-arm64": "0.0.284"
24
24
  },
25
25
  "dependencies": {
26
26
  "@anthropic-ai/sandbox-runtime": "^0.0.76"
@@ -37,7 +37,7 @@ import {
37
37
  type MigrationPlan,
38
38
  PERSISTENT_TASK_NAMES,
39
39
  PERSISTENT_TODO,
40
- CACHE_TODO,
40
+ cacheTodo,
41
41
  } from './migration.js'
42
42
 
43
43
  // `lint` is not here: a linter reads sources, and an edge to `build`
@@ -238,7 +238,8 @@ function upstreamBuildOnWorker(tasks: GeneratedTask[]): void {
238
238
  }
239
239
  const deps = Array.isArray(at.task['dependsOn']) ? (at.task['dependsOn'] as string[]) : []
240
240
  if (!deps.includes('^build')) at.task['dependsOn'] = ['^build', ...deps]
241
- if (!at.todos.includes(CACHE_TODO)) at.todos.push(CACHE_TODO)
241
+ const todo = cacheTodo(String((at.task['exec'] as { command?: unknown }).command))
242
+ if (!at.todos.includes(todo)) at.todos.push(todo)
242
243
  }
243
244
 
244
245
  export function migrateScripts(metas: readonly ProjectMeta[]): MigrationPlan {
@@ -335,7 +336,7 @@ export function migrateScripts(metas: readonly ProjectMeta[]): MigrationPlan {
335
336
  }
336
337
  if (name === 'build') {
337
338
  task['dependsOn'] = ['^build']
338
- todos.push(CACHE_TODO)
339
+ todos.push(cacheTodo(own))
339
340
  } else if (AFTER_BUILD.has(name) && hasBuild) {
340
341
  task['dependsOn'] = ['build']
341
342
  }
@@ -68,9 +68,29 @@ export function foldScriptHooks(
68
68
  return `vx_script() {\n${parts.join(' && ')}\n}\nvx_script`
69
69
  }
70
70
 
71
- /** The cache block the task that builds should declare, as a TODO on it. */
72
- export const CACHE_TODO =
73
- "cache: add `cache: { inputs: { files: ['src/**'] }, outputs: { files: ['dist/**'] } }` with this package's real inputs and outputs — without it the task always runs and every file here, what it writes included, folds into the key its dependents fold; a block with EMPTY outputs would be a cached no-op, not an uncached task"
71
+ /**
72
+ * Where a framework's build writes by default, for the cache TODO: the
73
+ * hint said `dist/**` to every build, `next build` included, which writes
74
+ * `.next`. Each tool's documented default; anything else keeps `dist/**`.
75
+ */
76
+ const BUILD_OUTPUTS: readonly [RegExp, readonly string[]][] = [
77
+ [/\snext build\s/, ['.next/**', '!.next/cache/**']],
78
+ [/\snux[ti] build\s/, ['.output/**']],
79
+ [/\s(?:remix|react-router|react-scripts|docusaurus) build\s/, ['build/**']],
80
+ [/\sgatsby build\s/, ['public/**']],
81
+ [/\s(?:storybook build|build-storybook)\s/, ['storybook-static/**']],
82
+ ]
83
+
84
+ /** The cache block the task that builds `command` should declare, as a TODO on it. */
85
+ export function cacheTodo(command: string): string {
86
+ // Padded, separators as spaces: `next build && …` and `(next build)` match.
87
+ const words = ` ${command.replace(/[;&|()]/g, ' ')} `
88
+ const hit = BUILD_OUTPUTS.find(([re]) => re.test(words))
89
+ const outputs = (hit?.[1] ?? ['dist/**']).map((o) => `'${o}'`).join(', ')
90
+ return `cache: add \`cache: { inputs: { files: ['src/**'] }, outputs: { files: [${outputs}] } }\` with this package's real inputs and outputs — without it the task always runs and every file here, what it writes included, folds into the key its dependents fold; a block with EMPTY outputs would be a cached no-op, not an uncached task`
91
+ }
92
+
93
+ const isCacheTodo = (todo: string): boolean => todo.startsWith('cache: add `cache: {')
74
94
 
75
95
  /** The one wording every mapper emits for a task it made persistent. */
76
96
  export const PERSISTENT_TODO =
@@ -302,7 +322,7 @@ export async function applyMigration(args: ApplyMigrationArgs): Promise<number>
302
322
  'build'
303
323
  // A run of tasks none of which caches is never a hit, and a first try
304
324
  // that runs twice to see the cache work saw it run twice.
305
- if (!cached && todos.has(CACHE_TODO)) {
325
+ if (!cached && [...todos.keys()].some(isCacheTodo)) {
306
326
  report.push('', 'no task caches yet: add the cache block a TODO shows, and a second run hits')
307
327
  }
308
328
  const installed = existsSync(path.join(root, 'node_modules', '@vzn', 'vx', 'package.json'))