@vzn/vx 0.0.580 → 0.0.581

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.580",
3
+ "version": "0.0.581",
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.580",
25
- "@vzn/vx-linux-arm64": "0.0.580",
26
- "@vzn/vx-darwin-x64": "0.0.580",
27
- "@vzn/vx-darwin-arm64": "0.0.580"
24
+ "@vzn/vx-linux-x64": "0.0.581",
25
+ "@vzn/vx-linux-arm64": "0.0.581",
26
+ "@vzn/vx-darwin-x64": "0.0.581",
27
+ "@vzn/vx-darwin-arm64": "0.0.581"
28
28
  },
29
29
  "dependencies": {
30
30
  "@anthropic-ai/sandbox-runtime": "^0.0.76"
@@ -553,6 +553,7 @@ export function checkGraph(
553
553
  delete node.addsToOutputsOf
554
554
  delete node.outputsAddedToBy
555
555
  }
556
+ for (const node of nodes.values()) refuseSelfClean(node)
556
557
  detectCycle(nodes)
557
558
  detectOutputCollisions(nodes, workspaceRoot, rules?.exclusiveOutputs !== false)
558
559
  if (rules?.upfrontKeys !== false) detectInputOverlaps(nodes, workspaceRoot)
@@ -1123,6 +1124,44 @@ export function outputTakenBack(output: string, negatives: readonly string[]): b
1123
1124
  )
1124
1125
  }
1125
1126
 
1127
+ /**
1128
+ * A task whose input entry its own outputs take back whole cleans its own
1129
+ * sources: vx removes a task's outputs before it runs, so a formatter
1130
+ * declaring `src/**` as both deleted every committed file under `src`,
1131
+ * and its key read nothing (hunt 8). Always refused, whatever the rules.
1132
+ */
1133
+ function refuseSelfClean(node: TaskNode): void {
1134
+ const cache = node.config.cache
1135
+ if (cache === undefined) return
1136
+ for (const field of ['files', 'workspaceFiles'] as const) {
1137
+ const inputs = splitNegations(cache.inputs[field] ?? [])
1138
+ const out = splitNegations(cache.outputs[field] ?? [])
1139
+ const outputs = out.positive
1140
+ if (outputs.length === 0) continue
1141
+ const roots = outputs.map((g) => rootless(staticPrefix(g)))
1142
+ for (const gi of inputs.positive) {
1143
+ // Only an output rooted at or above the input's prefix can cover it;
1144
+ // the prefix test keeps the glob matchers off the common disjoint pair.
1145
+ const pi = rootless(staticPrefix(gi))
1146
+ const near = outputs.filter((_, k) => {
1147
+ const r = roots[k]!
1148
+ return r === '' || pi === r || pi.startsWith(`${r}/`)
1149
+ })
1150
+ // Taken back by the task's own `!` inputs, or by an output `!` (no
1151
+ // output, so never cleaned: A-44), the entry reads what it says.
1152
+ if (near.length === 0 || outputTakenBack(gi, inputs.negative)) continue
1153
+ if (outputTakenBack(gi, out.negative)) continue
1154
+ if (!outputTakenBack(gi, near)) continue
1155
+ throw new UserError(
1156
+ `${node.id}: every file ${JSON.stringify(gi)} in cache.inputs.${field} selects is also ` +
1157
+ `its own output — vx removes a task's outputs before it runs, so the task would ` +
1158
+ `delete its own sources. A task that rewrites files in place (a formatter) declares ` +
1159
+ `no outputs.`,
1160
+ )
1161
+ }
1162
+ }
1163
+ }
1164
+
1126
1165
  /** One task's inputs (`reads`) or outputs in one namespace, for `detectInputOverlaps`. */
1127
1166
  interface Side {
1128
1167
  node: TaskNode