@cliangdev/conductor 0.25.0 → 0.26.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.
@@ -0,0 +1,211 @@
1
+ /* file-transport.mjs — a transport (see transport.mjs) that talks to NO backend at all: it renders a
2
+ * DRAFT spec (the backend's `draft-spec` response, which persists nothing) and leaves the frames in a
3
+ * local directory, so a person can look at the artwork before anything is uploaded or saved.
4
+ *
5
+ * It implements the same interface `run()` in render.mjs calls: getSpec / putFrame / putPoster /
6
+ * complete / fail, plus `getRenderId()` (run() asks for it before reporting a failure).
7
+ *
8
+ * getSpec() the draft spec, with every `local:<key>` string replaced by an http URL the headless
9
+ * browser (and ffmpeg, for audio) can load. The local files are served by a small
10
+ * loopback server (job/server.mjs, cors + Range) rooted at a temp dir that holds a
11
+ * symlink (or copy) of each file — the server never exposes anything but those files.
12
+ * putFrame writes `sheet.jpg` (the contact sheet; named by the frame's content type),
13
+ * `<placement>[-<index>].jpg|.mp4`
14
+ * putPoster writes `poster-<placement>[-<index>].jpg`
15
+ * complete writes `manifest.json` { ok: true, frames, warnings } — plus, when run() was asked to
16
+ * check placements, `checks` (the full render's per-placement assertion results) and
17
+ * `passed` (no check has severity "error")
18
+ * fail writes `manifest.json` { ok: false, error, frames, warnings }
19
+ *
20
+ * The asset server is started lazily by getSpec() and closed by complete()/fail()/close() (all
21
+ * idempotent), so a caller that never reaches either still has `close()`.
22
+ */
23
+ import { copyFile, mkdir, mkdtemp, rm, symlink, writeFile } from 'node:fs/promises';
24
+ import { tmpdir } from 'node:os';
25
+ import { extname, join, resolve } from 'node:path';
26
+ import { startServer } from './server.mjs';
27
+
28
+ const LOCAL_REF = /^local:([a-z0-9][a-z0-9-]{0,63})$/;
29
+
30
+ const EXT_BY_TYPE = {
31
+ 'image/jpeg': '.jpg',
32
+ 'image/png': '.png',
33
+ 'image/webp': '.webp',
34
+ 'video/mp4': '.mp4',
35
+ };
36
+
37
+ /** A filesystem-safe fragment of a placement key / index. */
38
+ function safe(part) {
39
+ return String(part).replace(/[^A-Za-z0-9._-]/g, '_');
40
+ }
41
+
42
+ /** Returns a copy of `value` with every string of the exact form `local:<key>` replaced by
43
+ * `urlFor(key)`. Walks objects and arrays; leaves every other value untouched. */
44
+ function replaceLocalRefs(value, urlFor) {
45
+ if (typeof value === 'string') {
46
+ const m = LOCAL_REF.exec(value);
47
+ return m ? urlFor(m[1]) : value;
48
+ }
49
+ if (Array.isArray(value)) return value.map((v) => replaceLocalRefs(v, urlFor));
50
+ if (value && typeof value === 'object') {
51
+ const out = {};
52
+ for (const [k, v] of Object.entries(value)) out[k] = replaceLocalRefs(v, urlFor);
53
+ return out;
54
+ }
55
+ return value;
56
+ }
57
+
58
+ /** Collects every `local:<key>` key a spec references. */
59
+ export function localKeysIn(spec) {
60
+ const keys = new Set();
61
+ replaceLocalRefs(spec, (key) => {
62
+ keys.add(key);
63
+ return key;
64
+ });
65
+ return [...keys];
66
+ }
67
+
68
+ /**
69
+ * @param {object} opts
70
+ * @param {object} opts.spec the draft spec (`{renderId, previewOnly, creative, brand, placements}`)
71
+ * @param {Record<string,string>} [opts.localFiles] key -> absolute path of the local file behind `local:<key>`
72
+ * @param {string} opts.outDir directory the frames and manifest.json are written into (created if absent)
73
+ */
74
+ export function createFileTransport({ spec, localFiles = {}, outDir }) {
75
+ if (!spec) throw new Error('createFileTransport: `spec` is required');
76
+ if (!outDir) throw new Error('createFileTransport: `outDir` is required');
77
+ const out = resolve(outDir);
78
+
79
+ let assetDir;
80
+ let assetServer;
81
+ let renderId;
82
+ let closed = false;
83
+ let error;
84
+ const frames = [];
85
+ let warnings = [];
86
+ let checks;
87
+
88
+ async function startAssetServer(keys) {
89
+ assetDir = await mkdtemp(join(tmpdir(), 'cc-draft-assets-'));
90
+ const files = {};
91
+ for (const key of keys) {
92
+ const source = localFiles[key];
93
+ if (!source) {
94
+ throw new Error(`the draft references local:${key} but no local file was given for "${key}"`);
95
+ }
96
+ const name = `${key}${extname(source).toLowerCase()}`;
97
+ const dest = join(assetDir, name);
98
+ try {
99
+ await symlink(resolve(source), dest);
100
+ } catch {
101
+ await copyFile(source, dest); // symlinks can be unavailable (Windows without privileges)
102
+ }
103
+ files[key] = name;
104
+ }
105
+ assetServer = await startServer(assetDir, 0, { cors: true });
106
+ return files;
107
+ }
108
+
109
+ async function close() {
110
+ if (closed) return;
111
+ closed = true;
112
+ if (assetServer) await new Promise((r) => assetServer.server.close(() => r()));
113
+ if (assetDir) await rm(assetDir, { recursive: true, force: true }).catch(() => {});
114
+ }
115
+
116
+ async function writeManifest(extra) {
117
+ await mkdir(out, { recursive: true });
118
+ const manifest = {
119
+ ok: !error,
120
+ ...(error ? { error } : {}),
121
+ renderId: renderId || 'draft',
122
+ previewOnly: !!spec.previewOnly,
123
+ frames,
124
+ warnings,
125
+ ...(checks ? { checks, passed: !checks.some((c) => c.severity === 'error') } : {}),
126
+ ...extra,
127
+ };
128
+ await writeFile(join(out, 'manifest.json'), JSON.stringify(manifest, null, 2));
129
+ return manifest;
130
+ }
131
+
132
+ return {
133
+ async getSpec() {
134
+ let resolved = spec;
135
+ const keys = localKeysIn(spec);
136
+ if (keys.length) {
137
+ try {
138
+ const files = await startAssetServer(keys);
139
+ resolved = replaceLocalRefs(spec, (key) => `${assetServer.origin}/${files[key]}`);
140
+ } catch (err) {
141
+ error = err.message; // run() does not call fail() when the spec never resolved; keep the reason
142
+ await close();
143
+ throw err;
144
+ }
145
+ }
146
+ renderId = spec.renderId || 'draft';
147
+ return resolved;
148
+ },
149
+
150
+ async putFrame(placementKey, { index, width, height, bytes, contentType = 'image/jpeg', durationSeconds, hasAudio } = {}) {
151
+ const ext = EXT_BY_TYPE[contentType] || '.bin';
152
+ const hasIndex = index !== undefined && index !== null;
153
+ const file = `${safe(placementKey)}${hasIndex ? `-${safe(index)}` : ''}${ext}`;
154
+ await mkdir(out, { recursive: true });
155
+ await writeFile(join(out, file), bytes);
156
+ frames.push({
157
+ placementKey,
158
+ ...(hasIndex ? { index } : {}),
159
+ file,
160
+ contentType,
161
+ width,
162
+ height,
163
+ sizeBytes: bytes.length,
164
+ ...(durationSeconds !== undefined && durationSeconds !== null ? { durationSeconds } : {}),
165
+ ...(hasAudio !== undefined && hasAudio !== null ? { hasAudio } : {}),
166
+ });
167
+ },
168
+
169
+ async putPoster(placementKey, bytes, { index } = {}) {
170
+ const hasIndex = index !== undefined && index !== null;
171
+ const file = `poster-${safe(placementKey)}${hasIndex ? `-${safe(index)}` : ''}.jpg`;
172
+ await mkdir(out, { recursive: true });
173
+ await writeFile(join(out, file), bytes);
174
+ const frame = frames.find((f) => f.placementKey === placementKey && (f.index ?? null) === (hasIndex ? index : null));
175
+ if (frame) frame.poster = file;
176
+ },
177
+
178
+ async complete(w, c) {
179
+ warnings = Array.isArray(w) ? w : [];
180
+ checks = Array.isArray(c) ? c : undefined;
181
+ try {
182
+ return await writeManifest();
183
+ } finally {
184
+ await close();
185
+ }
186
+ },
187
+
188
+ async fail(message) {
189
+ error = message || 'render failed';
190
+ try {
191
+ return await writeManifest();
192
+ } finally {
193
+ await close();
194
+ }
195
+ },
196
+
197
+ /** 'draft' once getSpec() has succeeded, `undefined` before — run() uses this to decide whether
198
+ * there is a render to mark failed. */
199
+ getRenderId() {
200
+ return renderId;
201
+ },
202
+
203
+ /** The error `fail()` (or a failed getSpec()) recorded, if any. */
204
+ getError() {
205
+ return error;
206
+ },
207
+
208
+ /** Stops the asset server and removes the temp asset dir. Safe to call more than once. */
209
+ close,
210
+ };
211
+ }
@@ -297,6 +297,73 @@ async function renderMotionPlacement({ browser, origin, spec, placementKey, ffmp
297
297
  }
298
298
  }
299
299
 
300
+ /* ── Draft preview: the full render's per-placement checks, without the frames ───────────────── */
301
+
302
+ /* A draft preview renders only the contact sheet (sheet.html), which never runs the assertions — a
303
+ * bad frame still shows up in a sheet so a human can see what broke. The full render DOES run them
304
+ * (spill, the bottom safe zone, contrast, fonts, images, the artboard size) on every placement's
305
+ * frame.html and fails the render, so a design the person approved from the sheet could still fail
306
+ * afterwards. This runs those very same checks (assertions.js's runAssertions, via frame.html) for
307
+ * each placement the full render would produce, in the one already-open browser against the
308
+ * already-served pages, and throws the screenshot away. A MOTION creative is checked the way its
309
+ * full render checks it: once, on the end-card (final) frame — no video is encoded.
310
+ *
311
+ * Resolves `[{ placementKey, index?, rule, message, severity: 'error' | 'warning' }]`; a board that
312
+ * would not even build (or a page error) is reported as rule "render". */
313
+ async function checkFrame({ browser, origin, spec, frame, fps, log }) {
314
+ const label = frame.index !== undefined ? `${frame.placementKey}[${frame.index}]` : frame.placementKey;
315
+ const tag = (check) => ({
316
+ placementKey: frame.placementKey,
317
+ ...(frame.index !== undefined ? { index: frame.index } : {}),
318
+ rule: check.rule,
319
+ message: check.message,
320
+ severity: check.severity,
321
+ });
322
+ const isMotion = spec.creative && spec.creative.kind === 'MOTION' && spec.creative.motion;
323
+ const page = await browser.newPage({ deviceScaleFactor: 1 });
324
+ try {
325
+ const motion = isMotion ? spec.creative.motion : undefined;
326
+ const durationSec = (motion && motion.durationSec) || 8;
327
+ await page.addInitScript((s) => {
328
+ window.__RENDER_SPEC__ = s;
329
+ }, {
330
+ creative: spec.creative,
331
+ brand: spec.brand,
332
+ placementKey: frame.placementKey,
333
+ sequenceIndex: frame.index,
334
+ ...(isMotion ? { motion, time: 0, clipStartSec: spec.creative.clipStartSec } : {}),
335
+ });
336
+ await page.goto(`${origin}/frame.html`, { waitUntil: 'load', timeout: PAGE_TIMEOUT_MS });
337
+ await page.waitForFunction(() => window.__ready === true, null, { timeout: PAGE_TIMEOUT_MS });
338
+
339
+ if (isMotion) {
340
+ const boot = await page.evaluate(() => window.__RENDER_RESULT);
341
+ if (!boot || !boot.ok) {
342
+ return [tag({ rule: 'render', severity: 'error', message: (boot && boot.error) || 'failed to build the MOTION board' })];
343
+ }
344
+ const frameCount = Math.max(1, Math.round(durationSec * fps));
345
+ const endTime = (frameCount - 1) / fps;
346
+ const seeked = await page.evaluate((t) => window.__seekMotion(t), endTime);
347
+ if (!seeked) return [tag({ rule: 'render', severity: 'error', message: `motion seek failed at t=${endTime.toFixed(2)}s` })];
348
+ const assertion = await page.evaluate(() => window.__assertBoard());
349
+ return ((assertion && assertion.checks) || []).map(tag);
350
+ }
351
+
352
+ const result = await page.evaluate(() => window.__RENDER_RESULT);
353
+ if (!result) return [tag({ rule: 'render', severity: 'error', message: 'render failed for an unknown reason' })];
354
+ if (result.checks) return result.checks.map(tag);
355
+ if (!result.ok) {
356
+ return [tag({ rule: 'render', severity: 'error', message: result.error || (result.errors || []).join('; ') || 'render failed for an unknown reason' })];
357
+ }
358
+ return [];
359
+ } catch (err) {
360
+ log(`check ${label}: ${err.message}`);
361
+ return [tag({ rule: 'render', severity: 'error', message: err.message })];
362
+ } finally {
363
+ await page.close().catch(() => {});
364
+ }
365
+ }
366
+
300
367
  /** Renders every frame a spec calls for, uploads each PNG (or, for a MOTION
301
368
  * creative that is not `previewOnly`, one MP4 + poster per placement — see
302
369
  * renderMotionPlacement above), and reports complete/fail. Options:
@@ -306,12 +373,15 @@ async function renderMotionPlacement({ browser, origin, spec, placementKey, ffmp
306
373
  * ffmpegPath a local ffmpeg binary's path. Required for a MOTION
307
374
  * render (not previewOnly) — ignored otherwise.
308
375
  * fps frames per second for a MOTION render (default 30).
376
+ * checkPlacements true: when the spec is `previewOnly`, also run the full render's per-placement
377
+ * assertions (see checkFrame above) and pass them to `transport.complete(warnings,
378
+ * checks)`. They never fail the run; the caller decides what a failing check means.
309
379
  * log (...args) => void (default: console.log).
310
380
  * Resolves `true` on success, `false` on a reported failure (never throws —
311
381
  * a thrown error from a truly unexpected place still gets caught and turned
312
382
  * into a `transport.fail()` call before resolving `false`, so a caller never
313
383
  * has to guess whether `transport.fail` was already called). */
314
- export async function run({ transport, packageRoot = PACKAGE_ROOT, browserFactory = defaultBrowserFactory, ffmpegPath, fps = DEFAULT_FPS, log = console.log }) {
384
+ export async function run({ transport, packageRoot = PACKAGE_ROOT, browserFactory = defaultBrowserFactory, ffmpegPath, fps = DEFAULT_FPS, checkPlacements = false, log = console.log }) {
315
385
  let spec;
316
386
  try {
317
387
  log('fetching spec');
@@ -408,7 +478,19 @@ export async function run({ transport, packageRoot = PACKAGE_ROOT, browserFactor
408
478
  }
409
479
  }
410
480
 
411
- await transport.complete(warnings);
481
+ if (checkPlacements && spec.previewOnly) {
482
+ const checks = [];
483
+ if ((spec.placements || []).length) {
484
+ for (const frame of framesFor({ ...spec, previewOnly: false })) {
485
+ checks.push(...(await checkFrame({ browser, origin, spec, frame, fps, log })));
486
+ }
487
+ }
488
+ const failed = checks.filter((c) => c.severity === 'error').length;
489
+ log(`checked placements: ${failed} failing, ${checks.length - failed} warning(s)`);
490
+ await transport.complete(warnings, checks);
491
+ } else {
492
+ await transport.complete(warnings);
493
+ }
412
494
  log(`complete (${warnings.length} warning(s))`);
413
495
  return true;
414
496
  } catch (err) {
@@ -12,7 +12,8 @@
12
12
  * job/.
13
13
  */
14
14
  import { createServer } from 'node:http';
15
- import { readFile, stat } from 'node:fs/promises';
15
+ import { createReadStream } from 'node:fs';
16
+ import { stat } from 'node:fs/promises';
16
17
  import { extname, join, normalize, sep } from 'node:path';
17
18
 
18
19
  const TYPES = {
@@ -26,9 +27,43 @@ const TYPES = {
26
27
  '.jpg': 'image/jpeg',
27
28
  '.jpeg': 'image/jpeg',
28
29
  '.svg': 'image/svg+xml',
30
+ '.gif': 'image/gif',
31
+ '.mp4': 'video/mp4',
32
+ '.m4v': 'video/mp4',
33
+ '.mov': 'video/quicktime',
34
+ '.webm': 'video/webm',
35
+ '.mp3': 'audio/mpeg',
36
+ '.m4a': 'audio/mp4',
37
+ '.aac': 'audio/aac',
38
+ '.wav': 'audio/wav',
39
+ '.ogg': 'audio/ogg',
29
40
  };
30
41
 
31
- function makeHandler(root) {
42
+ /* Parses a single `Range: bytes=a-b` header against a file of `size` bytes. Returns `{start, end}`
43
+ * (inclusive), `'unsatisfiable'`, or `null` when there is no usable range (serve the whole file). */
44
+ function parseRange(header, size) {
45
+ const m = /^bytes=(\d*)-(\d*)$/.exec(header || '');
46
+ if (!m || (m[1] === '' && m[2] === '')) return null;
47
+ let start;
48
+ let end;
49
+ if (m[1] === '') {
50
+ const suffix = Number(m[2]);
51
+ start = Math.max(0, size - suffix);
52
+ end = size - 1;
53
+ } else {
54
+ start = Number(m[1]);
55
+ end = m[2] === '' ? size - 1 : Math.min(Number(m[2]), size - 1);
56
+ }
57
+ if (start > end || start >= size) return 'unsatisfiable';
58
+ return { start, end };
59
+ }
60
+
61
+ /* `cors` adds a permissive Access-Control-Allow-Origin header. It is for a SECOND server that serves
62
+ * local media (job/file-transport.mjs) from a different loopback port than the page that embeds it:
63
+ * a cross-origin <img>/<video> needs no CORS to display, but a stricter load path (a fetch, a font)
64
+ * does, and the server only ever listens on 127.0.0.1. Range requests are always honoured — a video
65
+ * <video> element cannot seek without them. */
66
+ function makeHandler(root, { cors = false } = {}) {
32
67
  return async function handler(req, res) {
33
68
  const url = new URL(req.url, 'http://localhost');
34
69
  let rel = decodeURIComponent(url.pathname);
@@ -39,26 +74,48 @@ function makeHandler(root) {
39
74
  res.writeHead(403).end('forbidden');
40
75
  return;
41
76
  }
77
+ let info;
42
78
  try {
43
- const info = await stat(path);
79
+ info = await stat(path);
44
80
  if (!info.isFile()) throw new Error('not a file');
45
- const body = await readFile(path);
46
- res.writeHead(200, {
47
- 'content-type': TYPES[extname(path).toLowerCase()] || 'application/octet-stream',
48
- 'content-length': body.length,
49
- 'cache-control': 'no-store',
50
- });
51
- res.end(body);
52
81
  } catch {
53
82
  res.writeHead(404).end('not found');
83
+ return;
84
+ }
85
+ const headers = {
86
+ 'content-type': TYPES[extname(path).toLowerCase()] || 'application/octet-stream',
87
+ 'accept-ranges': 'bytes',
88
+ 'cache-control': 'no-store',
89
+ ...(cors ? { 'access-control-allow-origin': '*' } : {}),
90
+ };
91
+ const range = parseRange(req.headers.range, info.size);
92
+ if (range === 'unsatisfiable') {
93
+ res.writeHead(416, { ...headers, 'content-range': `bytes */${info.size}` }).end();
94
+ return;
95
+ }
96
+ const status = range ? 206 : 200;
97
+ const start = range ? range.start : 0;
98
+ const end = range ? range.end : info.size - 1;
99
+ res.writeHead(status, {
100
+ ...headers,
101
+ 'content-length': info.size === 0 ? 0 : end - start + 1,
102
+ ...(range ? { 'content-range': `bytes ${start}-${end}/${info.size}` } : {}),
103
+ });
104
+ if (req.method === 'HEAD' || info.size === 0) {
105
+ res.end();
106
+ return;
54
107
  }
108
+ const stream = createReadStream(path, { start, end });
109
+ stream.on('error', () => res.destroy());
110
+ stream.pipe(res);
55
111
  };
56
112
  }
57
113
 
58
114
  /** Starts a static server rooted at `root` on an ephemeral loopback port.
59
- * Resolves `{ server, origin }`; call `server.close()` when done. */
60
- export function startServer(root, port = 0) {
61
- const handle = makeHandler(root);
115
+ * Resolves `{ server, origin }`; call `server.close()` when done. `options.cors` adds an
116
+ * `Access-Control-Allow-Origin: *` header (see makeHandler). */
117
+ export function startServer(root, port = 0, options = {}) {
118
+ const handle = makeHandler(root, options);
62
119
  return new Promise((resolve) => {
63
120
  const server = createServer((req, res) => {
64
121
  handle(req, res).catch((err) => {
@@ -39,4 +39,66 @@ export interface RenderCreativeResult {
39
39
  * this resolution entirely. CLIP creatives never launch a browser — see {@link renderViaApi}.
40
40
  */
41
41
  export declare function renderCreative(params: RenderCreativeParams, config: Config, log?: (...args: unknown[]) => void): Promise<RenderCreativeResult>;
42
+ export interface DraftManifestFrame {
43
+ placementKey: string;
44
+ index?: number;
45
+ file: string;
46
+ contentType: string;
47
+ width?: number;
48
+ height?: number;
49
+ sizeBytes?: number;
50
+ durationSeconds?: number;
51
+ hasAudio?: boolean;
52
+ poster?: string;
53
+ }
54
+ /** One finding of the full render's per-placement assertions (spill, safe zone, contrast, fonts, images,
55
+ * artboard size), run on a placement's frame without keeping the frame. */
56
+ export interface DraftCheck {
57
+ placementKey: string;
58
+ index?: number;
59
+ /** spill | safeZone | contrast | font | image | accent | size | photoResolution | render */
60
+ rule: string;
61
+ message: string;
62
+ /** An "error" would fail the full render of that placement; a "warning" would only be reported. */
63
+ severity: 'error' | 'warning';
64
+ }
65
+ export interface DraftManifest {
66
+ ok: boolean;
67
+ error?: string;
68
+ renderId?: string;
69
+ previewOnly?: boolean;
70
+ frames: DraftManifestFrame[];
71
+ warnings: Array<{
72
+ placementKey?: string;
73
+ index?: number;
74
+ message: string;
75
+ }>;
76
+ /** Present on a preview: the per-placement assertion results the full render would produce. */
77
+ checks?: DraftCheck[];
78
+ /** Present with `checks`: true when none of them has severity "error". */
79
+ passed?: boolean;
80
+ }
81
+ export interface RenderDraftResult {
82
+ ok: boolean;
83
+ error?: string;
84
+ manifest?: DraftManifest;
85
+ }
86
+ /**
87
+ * Renders a DRAFT spec (the backend's `draft-spec` response — nothing about it is saved) with the same
88
+ * render core a saved Creative uses, but through the file transport: the local files behind
89
+ * `local:<key>` are served to the headless browser from this machine, and the frames plus a
90
+ * `manifest.json` land in `outDir`. A `previewOnly` spec also gets `manifest.checks` / `manifest.passed`:
91
+ * the full render's per-placement assertions, run without keeping their frames. Never talks to the Conductor API. A MOTION spec that is not
92
+ * `previewOnly` needs ffmpeg, resolved the same way {@link renderCreative} resolves it.
93
+ */
94
+ export declare function renderDraft(params: {
95
+ spec: {
96
+ creative?: {
97
+ kind?: string;
98
+ };
99
+ previewOnly?: boolean;
100
+ };
101
+ localFiles: Record<string, string>;
102
+ outDir: string;
103
+ }, log?: (...args: unknown[]) => void): Promise<RenderDraftResult>;
42
104
  //# sourceMappingURL=creative-render.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"creative-render.d.ts","sourceRoot":"","sources":["../../src/lib/creative-render.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AAS9C,MAAM,WAAW,WAAW;IAC1B,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,YAAY,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;IACnB,0GAA0G;IAC1G,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAED,MAAM,WAAW,oBAAoB;IACnC,UAAU,EAAE,MAAM,CAAA;IAClB,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,OAAO,CAAA;IACX,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,MAAM,EAAE,WAAW,EAAE,CAAA;CACtB;AAkFD;;;;;;;;;GASG;AACH,wBAAsB,cAAc,CAClC,MAAM,EAAE,oBAAoB,EAC5B,MAAM,EAAE,MAAM,EACd,GAAG,GAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAe,GAC3C,OAAO,CAAC,oBAAoB,CAAC,CAoD/B"}
1
+ {"version":3,"file":"creative-render.d.ts","sourceRoot":"","sources":["../../src/lib/creative-render.ts"],"names":[],"mappings":"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"}
@@ -8,6 +8,7 @@
8
8
  * launched or what browser it gets — that's `job/transport.mjs`'s and this file's job respectively.
9
9
  */
10
10
  import { existsSync } from 'node:fs';
11
+ import { readFile } from 'node:fs/promises';
11
12
  import { dirname, join } from 'node:path';
12
13
  import { fileURLToPath } from 'node:url';
13
14
  import { apiGet, apiPost, ApiError } from '../mcp/api.js';
@@ -110,4 +111,54 @@ export async function renderCreative(params, config, log = () => { }) {
110
111
  };
111
112
  }
112
113
  }
114
+ /**
115
+ * Renders a DRAFT spec (the backend's `draft-spec` response — nothing about it is saved) with the same
116
+ * render core a saved Creative uses, but through the file transport: the local files behind
117
+ * `local:<key>` are served to the headless browser from this machine, and the frames plus a
118
+ * `manifest.json` land in `outDir`. A `previewOnly` spec also gets `manifest.checks` / `manifest.passed`:
119
+ * the full render's per-placement assertions, run without keeping their frames. Never talks to the Conductor API. A MOTION spec that is not
120
+ * `previewOnly` needs ffmpeg, resolved the same way {@link renderCreative} resolves it.
121
+ */
122
+ export async function renderDraft(params, log = () => { }) {
123
+ const { spec, localFiles, outDir } = params;
124
+ let ffmpegPath;
125
+ if (spec.creative?.kind === 'MOTION' && !spec.previewOnly) {
126
+ try {
127
+ ffmpegPath = await resolveFfmpegPath();
128
+ }
129
+ catch (err) {
130
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
131
+ }
132
+ }
133
+ const { run } = await loadRenderCore();
134
+ const transportPath = join(jobDir(), 'file-transport.mjs');
135
+ if (!existsSync(transportPath)) {
136
+ throw new Error(`Creative draft transport not found at ${transportPath}. This is a packaging bug in @cliangdev/conductor — reinstall the CLI, or in this repo run \`npm run build\`.`);
137
+ }
138
+ const { createFileTransport } = (await import(transportPath));
139
+ const transport = createFileTransport({ spec, localFiles, outDir });
140
+ const browserFactory = await findBrowserFactory(log);
141
+ let ok;
142
+ try {
143
+ // A preview renders only the contact sheet, which never runs the assertions a full render fails on, so
144
+ // ask the core to run them per placement too (and drop the frames) — otherwise an approved preview can
145
+ // still fail its real render.
146
+ ok = await run({ transport, browserFactory, log, ffmpegPath, fps: MOTION_FPS, checkPlacements: !!spec.previewOnly });
147
+ }
148
+ finally {
149
+ await transport.close();
150
+ }
151
+ let manifest;
152
+ try {
153
+ manifest = JSON.parse(await readFile(join(outDir, 'manifest.json'), 'utf8'));
154
+ }
155
+ catch {
156
+ manifest = undefined;
157
+ }
158
+ if (!ok)
159
+ return { ok: false, error: transport.getError() ?? manifest?.error ?? 'The draft render failed.', manifest };
160
+ if (!manifest)
161
+ return { ok: false, error: 'The draft render finished but wrote no manifest.json.' };
162
+ return { ok: true, manifest };
163
+ }
113
164
  //# sourceMappingURL=creative-render.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"creative-render.js","sourceRoot":"","sources":["../../src/lib/creative-render.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"creative-render.js","sourceRoot":"","sources":["../../src/lib/creative-render.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"plugin-assets.d.ts","sourceRoot":"","sources":["../../src/lib/plugin-assets.ts"],"names":[],"mappings":"AAsDA,wBAAgB,cAAc,IAAI,MAAM,CAKvC;AAED;uEACuE;AACvE,wBAAgB,iBAAiB,IAAI,MAAM,CAI1C;AA2CD,MAAM,MAAM,aAAa,GAAG,WAAW,GAAG,SAAS,GAAG,SAAS,CAAA;AA6C/D;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,EACnB,WAAW,CAAC,EAAE,MAAM,GACnB,aAAa,CASf;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CACpC,WAAW,EAAE,MAAM,EACnB,eAAe,EAAE,MAAM,EACvB,cAAc,EAAE,MAAM,GACrB;IAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,GAAG,MAAM,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAA;CAAE,CA0B9D;AAED;;iGAEiG;AACjG,eAAO,MAAM,qBAAqB,8BAA8B,CAAA;AAEhE,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,CAAA;IACf;;0FAEsF;IACtF,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAC/B;AAMD;qGACqG;AACrG,wBAAgB,0BAA0B,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAQpF;AAED,wBAAgB,wBAAwB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAGjH;AAED;wGACwG;AACxG,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,MAAM,GAAG,mBAAmB,GAAG,SAAS,CAmB1F;AAyID,MAAM,WAAW,cAAc;IAC7B;qFACiF;IACjF,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;IAC/B,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,4EAA4E;IAC5E,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,sFAAsF;IACtF,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,GAAG,CAAC,EAAE,MAAM,CAAA;CACb;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,4BAA4B,CAAC,IAAI,GAAE,cAAmB,GAAG,IAAI,CAyB5E"}
1
+ {"version":3,"file":"plugin-assets.d.ts","sourceRoot":"","sources":["../../src/lib/plugin-assets.ts"],"names":[],"mappings":"AAuDA,wBAAgB,cAAc,IAAI,MAAM,CAKvC;AAED;uEACuE;AACvE,wBAAgB,iBAAiB,IAAI,MAAM,CAI1C;AA2CD,MAAM,MAAM,aAAa,GAAG,WAAW,GAAG,SAAS,GAAG,SAAS,CAAA;AA6C/D;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,EACnB,WAAW,CAAC,EAAE,MAAM,GACnB,aAAa,CASf;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CACpC,WAAW,EAAE,MAAM,EACnB,eAAe,EAAE,MAAM,EACvB,cAAc,EAAE,MAAM,GACrB;IAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,GAAG,MAAM,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAA;CAAE,CA0B9D;AAED;;iGAEiG;AACjG,eAAO,MAAM,qBAAqB,8BAA8B,CAAA;AAEhE,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,CAAA;IACf;;0FAEsF;IACtF,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAC/B;AAMD;qGACqG;AACrG,wBAAgB,0BAA0B,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAQpF;AAED,wBAAgB,wBAAwB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAGjH;AAED;wGACwG;AACxG,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,MAAM,GAAG,mBAAmB,GAAG,SAAS,CAmB1F;AAyID,MAAM,WAAW,cAAc;IAC7B;qFACiF;IACjF,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;IAC/B,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,4EAA4E;IAC5E,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,sFAAsF;IACtF,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,GAAG,CAAC,EAAE,MAAM,CAAA;CACb;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,4BAA4B,CAAC,IAAI,GAAE,cAAmB,GAAG,IAAI,CAyB5E"}
@@ -19,6 +19,7 @@ const PLUGIN_FILES = [
19
19
  'skills/conductor-coder/SKILL.md',
20
20
  'skills/conductor-publisher/SKILL.md',
21
21
  'skills/conductor-creative/SKILL.md',
22
+ 'skills/conductor-creative/references/brand-kit-setup.md',
22
23
  ];
23
24
  // Paths removed in previous versions — deleted on `conductor init` (and a refresh) to clean up stale installs.
24
25
  const LEGACY_PATHS = [