@drantoniou/uploadcheck 0.2.0 → 0.3.0

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": "@drantoniou/uploadcheck",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "The quality gate for AI-generated media. Check videos, podcasts, and clips before you publish — or `watch` a folder to auto-QC every render.",
5
5
  "homepage": "https://uploadcheck.app",
6
6
  "repository": {
@@ -190,6 +190,10 @@ export function parseArgs(argv) {
190
190
  options.settleMs = requireValue(arg, args.shift());
191
191
  } else if (arg === "--poll-ms") {
192
192
  options.pollMs = requireValue(arg, args.shift());
193
+ } else if (arg === "--on-pass") {
194
+ options.onPass = requireValue(arg, args.shift());
195
+ } else if (arg === "--on-fail") {
196
+ options.onFail = requireValue(arg, args.shift());
193
197
  } else {
194
198
  throw new Error(`Unknown option: ${arg}`);
195
199
  }
package/watch.mjs CHANGED
@@ -20,6 +20,7 @@
20
20
  import { readdirSync, statSync, existsSync, writeFileSync, readFileSync, watch as fsWatch } from "node:fs";
21
21
  import { join, extname, basename, dirname, resolve, relative } from "node:path";
22
22
  import { createHash } from "node:crypto";
23
+ import { spawn } from "node:child_process";
23
24
  import { buildJobRequest, CONTENT_TYPES } from "./request-builder.mjs";
24
25
  import { submitJob, pollJobUntilDone, fetchReport, fetchMarkerCsv, sleep } from "./api-client.mjs";
25
26
 
@@ -84,6 +85,23 @@ function appendAggregateRow(dir, row) {
84
85
  } catch { /* best-effort */ }
85
86
  }
86
87
 
88
+ // Run a user-supplied shell command after a verdict (--on-pass / --on-fail).
89
+ // The file path is exposed to the command as $UPLOADCHECK_FILE (and the verdict as
90
+ // $UPLOADCHECK_VERDICT), so `mv "$UPLOADCHECK_FILE" done/` style commands work.
91
+ // Failures are logged, never fatal.
92
+ function runHook(command, filePath, verdict) {
93
+ if (!command) return Promise.resolve();
94
+ return new Promise((resolve) => {
95
+ const child = spawn(command, {
96
+ shell: true,
97
+ stdio: "inherit",
98
+ env: { ...process.env, UPLOADCHECK_FILE: filePath, UPLOADCHECK_VERDICT: String(verdict || "") }
99
+ });
100
+ child.on("error", (e) => { console.error(`[watch] hook error: ${e.message}`); resolve(); });
101
+ child.on("exit", () => resolve());
102
+ });
103
+ }
104
+
87
105
  async function processFile(filePath, { apiBaseUrl, apiKey, options, dir, ledger, nowIso }) {
88
106
  const name = basename(filePath);
89
107
  // Build the job exactly like `uploadcheck check` (inline vs signed-upload auto-switch).
@@ -119,8 +137,15 @@ async function processFile(filePath, { apiBaseUrl, apiKey, options, dir, ledger,
119
137
  ledger.done[sourceKey] = { file: name, jobId, verdict, status: finished.status, at: nowIso };
120
138
  saveLedger(dir, ledger);
121
139
 
122
- const mark = finished.status !== "completed" ? "⚠️" : (String(verdict).toUpperCase().includes("BLOCK") ? "❌ BLOCK" : (String(verdict).toUpperCase().includes("WATCH") ? "⚠️ WATCH" : "✅"));
140
+ const verdictUpper = String(verdict).toUpperCase();
141
+ const passed = finished.status === "completed" && !verdictUpper.includes("BLOCK");
142
+ const mark = finished.status !== "completed" ? "⚠️" : (verdictUpper.includes("BLOCK") ? "❌ BLOCK" : (verdictUpper.includes("WATCH") ? "⚠️ WATCH" : "✅"));
123
143
  console.log(`[watch] ${name}: ${mark} (${flagCount} flag${flagCount === 1 ? "" : "s"}) → ${basename(reportPathFor(filePath))}`);
144
+
145
+ // Pipeline glue: run --on-pass when nothing blocks (a clean PASS or WATCH), or
146
+ // --on-fail on a BLOCK / failed job. WATCH counts as a pass — it ships with notes.
147
+ if (passed && options.onPass) await runHook(options.onPass, filePath, verdict);
148
+ else if (!passed && options.onFail) await runHook(options.onFail, filePath, verdict);
124
149
  }
125
150
 
126
151
  // Run a list of files through processFile with bounded concurrency.