@vzn/vx 0.0.553 → 0.0.555

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.553",
3
+ "version": "0.0.555",
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.553",
25
- "@vzn/vx-linux-arm64": "0.0.553",
26
- "@vzn/vx-darwin-x64": "0.0.553",
27
- "@vzn/vx-darwin-arm64": "0.0.553"
24
+ "@vzn/vx-linux-x64": "0.0.555",
25
+ "@vzn/vx-linux-arm64": "0.0.555",
26
+ "@vzn/vx-darwin-x64": "0.0.555",
27
+ "@vzn/vx-darwin-arm64": "0.0.555"
28
28
  },
29
29
  "dependencies": {
30
30
  "@anthropic-ai/sandbox-runtime": "^0.0.76"
@@ -329,7 +329,8 @@ export interface PruneResult {
329
329
  * the full `workspace-outputs/<rel-to-root>` tar entry name as the
330
330
  * discriminator — least-invasive row format, no schema change. A
331
331
  * project output dir literally named `workspace-outputs/` would
332
- * collide with the namespace; the name is reserved.
332
+ * collide with the namespace; the name is reserved, and a project
333
+ * output glob under it is refused at load (config-schema.ts).
333
334
  */
334
335
  export const WORKSPACE_OUTPUT_PREFIX = 'workspace-outputs/'
335
336
 
@@ -24,6 +24,9 @@ import {
24
24
  UserError,
25
25
  BUN_GLOB_WILDCARDS,
26
26
  EXTGLOB,
27
+ anyTaskGlob,
28
+ asTrees,
29
+ isLiteralPattern,
27
30
  } from '../util/index.js'
28
31
  import { nonJsonMessage, nonJsonPaths } from './json-data.js'
29
32
 
@@ -650,6 +653,15 @@ export function validateProjectConfig(config: ProjectConfig, configPath: string)
650
653
  `name the directory the task writes, such as "dist/**"`,
651
654
  )
652
655
  }
656
+ // An artifact stores workspace outputs under `workspace-outputs/`,
657
+ // and its index read a project output there as one: every hit was
658
+ // a "corrupt artifact", dropped and run again.
659
+ if (!g.startsWith('!') && normalizeGlob(g).split('/')[0] === 'workspace-outputs') {
660
+ throw new UserError(
661
+ `${where}.cache.outputs.files: "${g}" is under workspace-outputs/, a name vx's ` +
662
+ `artifacts reserve for outputs.workspaceFiles — write the task's files to another directory`,
663
+ )
664
+ }
653
665
  // A `!` entry takes a path back from the outputs (A-44): it is not
654
666
  // cleaned, saved or restored, and it stays an input.
655
667
  assertNotDoubleNegated(g, `${where}.cache.outputs.files`)
@@ -713,6 +725,7 @@ export function validateProjectConfig(config: ProjectConfig, configPath: string)
713
725
  // error for the same reason. Refusing costs nothing: such a config was
714
726
  // already silently broken, so no working key changes.
715
727
  assertNotNegationOnly((inputs as { files: string[] }).files, `${where}.cache.inputs.files`)
728
+ assertNoLiteralTakenBack((inputs as { files: string[] }).files, `${where}.cache.inputs.files`)
716
729
  // The only CacheInputs field that reached the run unvalidated: a
717
730
  // non-string entry crashes deep in `filterUpstreamHashes` /
718
731
  // `parseDependencySpec` with a raw TypeError naming neither the task
@@ -1329,6 +1342,28 @@ function assertNotNegationOnly(globs: readonly string[], where: string): void {
1329
1342
  )
1330
1343
  }
1331
1344
 
1345
+ /**
1346
+ * A `!` entry subtracts wherever it sits in the list, so a file named
1347
+ * outright after it (`['src/**', '!src/gen/**', 'src/gen/keep.ts']`) never
1348
+ * entered the key, and an edit to it replayed the old output. Refusing is
1349
+ * free: the literal already selected nothing.
1350
+ */
1351
+ function assertNoLiteralTakenBack(globs: readonly string[], where: string): void {
1352
+ const negative = globs.filter((g) => g.startsWith('!')).map((g) => g.slice(1))
1353
+ if (negative.length === 0) return
1354
+ const taken = anyTaskGlob(asTrees(negative))
1355
+ for (const g of globs) {
1356
+ if (g.startsWith('!')) continue
1357
+ const lit = normalizeGlob(g).replace(/\/+$/, '')
1358
+ if (!isLiteralPattern(lit) || !taken(lit)) continue
1359
+ const by = negative.find((n) => anyTaskGlob(asTrees([n]))(lit))
1360
+ throw new UserError(
1361
+ `${where}: "${g}" is taken back by "!${by}" — a \`!\` entry subtracts wherever it sits in ` +
1362
+ `the list, so the file never enters the key. Narrow the negation so it leaves "${g}".`,
1363
+ )
1364
+ }
1365
+ }
1366
+
1332
1367
  /**
1333
1368
  * `negation: false` for OUTPUT globs: a `!` entry takes a path back (A-44),
1334
1369
  * and a list of only negations selects nothing and is refused.
@@ -1361,8 +1396,10 @@ function validateWorkspaceGlobs(v: unknown, where: string, negation: boolean): v
1361
1396
  }
1362
1397
  assertNotDoubleNegated(g, where)
1363
1398
  }
1364
- if (negation) assertNotNegationOnly(v as string[], where)
1365
- else assertOutputsNotNegationOnly(v as string[], where)
1399
+ if (negation) {
1400
+ assertNotNegationOnly(v as string[], where)
1401
+ assertNoLiteralTakenBack(v as string[], where)
1402
+ } else assertOutputsNotNegationOnly(v as string[], where)
1366
1403
  }
1367
1404
 
1368
1405
  /**