pixelkiln 0.24.0 → 0.25.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/dist/cli.d.ts +2 -0
- package/dist/cli.js +140 -46
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +130 -42
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +32 -11
- package/dist/index.d.ts +32 -11
- package/dist/index.js +130 -42
- package/dist/index.js.map +1 -1
- package/docs/CLI.md +18 -0
- package/docs/PIXELLAB.md +12 -0
- package/package.json +1 -1
package/dist/cli.d.ts
CHANGED
|
@@ -42,6 +42,8 @@ interface Args {
|
|
|
42
42
|
tag: boolean;
|
|
43
43
|
/** gallery: allow the page to edit manifest intent (prompts, sizes, tags, new assets). */
|
|
44
44
|
edit: boolean;
|
|
45
|
+
/** fetch: re-download downloaded outputs and replace files whose object changed upstream. */
|
|
46
|
+
refresh: boolean;
|
|
45
47
|
from?: string;
|
|
46
48
|
out?: string;
|
|
47
49
|
generator?: string;
|
package/dist/cli.js
CHANGED
|
@@ -2289,6 +2289,10 @@ function lockKey(styleId, assetId) {
|
|
|
2289
2289
|
}
|
|
2290
2290
|
|
|
2291
2291
|
// src/providers/pixellab.ts
|
|
2292
|
+
function pixelLabObjectUrl(generator, objectId) {
|
|
2293
|
+
if (!objectId || generator !== "map" && generator !== "1dir") return null;
|
|
2294
|
+
return `https://www.pixellab.ai/objects/${encodeURIComponent(objectId)}`;
|
|
2295
|
+
}
|
|
2292
2296
|
var PixelLabProvider = class _PixelLabProvider {
|
|
2293
2297
|
constructor(client) {
|
|
2294
2298
|
this.client = client;
|
|
@@ -6448,14 +6452,16 @@ function shouldPersistSourceUrl(value) {
|
|
|
6448
6452
|
async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
6449
6453
|
const log2 = opts.onProgress ?? (() => {
|
|
6450
6454
|
});
|
|
6451
|
-
const result = { downloaded: 0, skipped: 0, failed: 0 };
|
|
6455
|
+
const result = { downloaded: 0, skipped: 0, failed: 0, ...opts.refresh ? { unchanged: 0 } : {} };
|
|
6452
6456
|
const specByKey = new Map(specs.map((s) => [lockKey(s.styleId, s.assetId), s]));
|
|
6453
6457
|
normalizeLockOutputPaths(lock, specs);
|
|
6454
6458
|
const cacheDir = opts.cacheDir === false ? null : path14.resolve(opts.cacheDir ?? path14.join(path14.dirname(lockPath), ".pixelkiln", "cache"));
|
|
6455
6459
|
const pending = Object.entries(lock.entries).filter(([key, e]) => {
|
|
6456
6460
|
if (e.provider !== provider.id || !specByKey.has(key)) return false;
|
|
6457
6461
|
if (e.status === "selected" || e.status === "download-failed") return true;
|
|
6458
|
-
if (
|
|
6462
|
+
if (e.status !== "downloaded") return false;
|
|
6463
|
+
if (opts.refresh) return e.outputs.length > 0 && Boolean(e.sourceUrls?.length || e.sourceUrl);
|
|
6464
|
+
if (!opts.repair) return false;
|
|
6459
6465
|
const spec = specByKey.get(key);
|
|
6460
6466
|
return e.outputs.length === 0 || !spec || e.outputs.some((output) => !existsSync9(resolveOutputPath(output.path, spec.root)));
|
|
6461
6467
|
});
|
|
@@ -6487,6 +6493,7 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
6487
6493
|
continue;
|
|
6488
6494
|
}
|
|
6489
6495
|
const outputs = [];
|
|
6496
|
+
let wrote = false;
|
|
6490
6497
|
try {
|
|
6491
6498
|
for (let index = 0; index < sources.length; index++) {
|
|
6492
6499
|
const source = sources[index];
|
|
@@ -6494,41 +6501,55 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
6494
6501
|
(o) => source.role ? o.role === source.role : !o.role && sources.length === 1
|
|
6495
6502
|
);
|
|
6496
6503
|
const target = recorded ? resolveOutputPath(recorded.path, spec.root) : expectedOutputPath(spec, source.role, index, sources.length, source.mediaType);
|
|
6504
|
+
let fresh = null;
|
|
6505
|
+
if (opts.refresh) {
|
|
6506
|
+
if (!source.url) throw new Error(`no durable source URL to refresh ${source.role ?? "asset"} from`);
|
|
6507
|
+
fresh = await provider.download(source.url);
|
|
6508
|
+
if (recorded && sha256(fresh) === recorded.sha256) {
|
|
6509
|
+
log2(` current ${path14.relative(process.cwd(), target)}`);
|
|
6510
|
+
outputs.push({ ...recorded, path: portableOutputPath(target, spec.root) });
|
|
6511
|
+
continue;
|
|
6512
|
+
}
|
|
6513
|
+
}
|
|
6497
6514
|
if (existsSync9(target)) {
|
|
6498
6515
|
const currentHash = await sha256File(target);
|
|
6499
6516
|
if (recorded && currentHash === recorded.sha256) {
|
|
6500
|
-
if (
|
|
6501
|
-
|
|
6502
|
-
|
|
6503
|
-
|
|
6504
|
-
|
|
6505
|
-
|
|
6506
|
-
|
|
6517
|
+
if (!fresh) {
|
|
6518
|
+
if (cacheDir) {
|
|
6519
|
+
await cacheMedia(
|
|
6520
|
+
cacheDir,
|
|
6521
|
+
await readFile7(target),
|
|
6522
|
+
recorded.mediaType ?? MediaType.PNG,
|
|
6523
|
+
recorded.sha256
|
|
6524
|
+
);
|
|
6525
|
+
}
|
|
6526
|
+
outputs.push({ ...recorded, path: portableOutputPath(target, spec.root) });
|
|
6527
|
+
continue;
|
|
6507
6528
|
}
|
|
6508
|
-
|
|
6509
|
-
|
|
6510
|
-
|
|
6511
|
-
|
|
6512
|
-
|
|
6513
|
-
|
|
6514
|
-
|
|
6515
|
-
|
|
6516
|
-
|
|
6517
|
-
|
|
6518
|
-
|
|
6519
|
-
|
|
6520
|
-
|
|
6521
|
-
|
|
6522
|
-
|
|
6523
|
-
|
|
6529
|
+
log2(` changed ${path14.relative(process.cwd(), target)} (upstream)`);
|
|
6530
|
+
} else {
|
|
6531
|
+
const superseded = (entry.supersededOutputs ?? []).find(
|
|
6532
|
+
(output, oldIndex, all) => currentOutputPath(output, spec, oldIndex, all.length) === target
|
|
6533
|
+
);
|
|
6534
|
+
if (!opts.force) {
|
|
6535
|
+
if (!recorded && superseded && currentHash === superseded.sha256) {
|
|
6536
|
+
} else if (recorded || superseded) {
|
|
6537
|
+
throw new Error(
|
|
6538
|
+
`refusing to overwrite modified output ${target}; pass --force to replace it`
|
|
6539
|
+
);
|
|
6540
|
+
} else {
|
|
6541
|
+
throw new Error(
|
|
6542
|
+
`refusing to overwrite untracked output ${target}; pass --force to replace it`
|
|
6543
|
+
);
|
|
6544
|
+
}
|
|
6524
6545
|
}
|
|
6546
|
+
log2(
|
|
6547
|
+
` replace ${path14.relative(process.cwd(), target)}` + (opts.force ? " (--force)" : " (previous tracked generation)")
|
|
6548
|
+
);
|
|
6525
6549
|
}
|
|
6526
|
-
log2(
|
|
6527
|
-
` replace ${path14.relative(process.cwd(), target)}` + (opts.force ? " (--force)" : " (previous tracked generation)")
|
|
6528
|
-
);
|
|
6529
6550
|
}
|
|
6530
6551
|
const expectedMediaType = source.mediaType ?? recorded?.mediaType ?? MediaType.PNG;
|
|
6531
|
-
let buf = recorded && cacheDir ? await readCachedMedia(cacheDir, recorded.sha256, expectedMediaType) : null;
|
|
6552
|
+
let buf = fresh ?? (recorded && cacheDir ? await readCachedMedia(cacheDir, recorded.sha256, expectedMediaType) : null);
|
|
6532
6553
|
if (buf) {
|
|
6533
6554
|
log2(` cached ${path14.relative(process.cwd(), target)}`);
|
|
6534
6555
|
} else {
|
|
@@ -6560,8 +6581,13 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
6560
6581
|
upsert(lock, key, { outputs: mergeOutputs(entry.outputs, outputs) });
|
|
6561
6582
|
await saveLock(lockPath, lock);
|
|
6562
6583
|
await rename3(tmp, target);
|
|
6584
|
+
wrote = true;
|
|
6563
6585
|
log2(` wrote ${path14.relative(process.cwd(), target)}`);
|
|
6564
6586
|
}
|
|
6587
|
+
if (opts.refresh && !wrote) {
|
|
6588
|
+
result.unchanged = (result.unchanged ?? 0) + 1;
|
|
6589
|
+
continue;
|
|
6590
|
+
}
|
|
6565
6591
|
const persistentSources = sources.filter((source) => shouldPersistSourceUrl(source.url));
|
|
6566
6592
|
upsert(lock, key, {
|
|
6567
6593
|
status: "downloaded",
|
|
@@ -8551,6 +8577,8 @@ async function buildGallerySnapshot(opts) {
|
|
|
8551
8577
|
source: spec.source ?? null,
|
|
8552
8578
|
edit,
|
|
8553
8579
|
editStatus,
|
|
8580
|
+
upstreamUrl: entry?.provider === "pixellab" ? pixelLabObjectUrl(entry.generator, entry.objectId) : null,
|
|
8581
|
+
refreshable: Boolean(entry && entry.status === "downloaded" && entry.outputs.length && (entry.sourceUrls?.length || entry.sourceUrl)),
|
|
8554
8582
|
tags: spec.tags,
|
|
8555
8583
|
category: asset?.category ?? null
|
|
8556
8584
|
});
|
|
@@ -8605,6 +8633,8 @@ async function buildGallerySnapshot(opts) {
|
|
|
8605
8633
|
source: null,
|
|
8606
8634
|
edit: null,
|
|
8607
8635
|
editStatus: null,
|
|
8636
|
+
upstreamUrl: entry.provider === "pixellab" ? pixelLabObjectUrl(entry.generator, entry.objectId) : null,
|
|
8637
|
+
refreshable: false,
|
|
8608
8638
|
tags: [],
|
|
8609
8639
|
category: null
|
|
8610
8640
|
});
|
|
@@ -9282,7 +9312,7 @@ function renderJobs() {
|
|
|
9282
9312
|
const row = el('div', 'job');
|
|
9283
9313
|
const ph = el('span', 'ph');
|
|
9284
9314
|
ph.append(el('i', 'dot ' + PHASE_TONE[job.phase]), document.createTextNode(job.phase));
|
|
9285
|
-
const what = (job.mode === 'resume' ? 'resume ' : 'generate ') + job.keys.length + (job.keys.length === 1 ? ' asset' : ' assets') +
|
|
9315
|
+
const what = (job.mode === 'refresh' ? 'pull upstream for ' : job.mode === 'resume' ? 'resume ' : 'generate ') + job.keys.length + (job.keys.length === 1 ? ' asset' : ' assets') +
|
|
9286
9316
|
(job.project ? ' in ' + job.project : '');
|
|
9287
9317
|
const last = el('span', 'last', what + (job.messages.length ? ' \u2014 ' + job.messages[job.messages.length - 1].trim() : ''));
|
|
9288
9318
|
last.title = job.keys.join('\\n');
|
|
@@ -9321,13 +9351,15 @@ function budgetLine() {
|
|
|
9321
9351
|
|
|
9322
9352
|
// The confirm step: what will be sent, what it is estimated to cost, and what
|
|
9323
9353
|
// this session may still spend. Mirrors gen's "Spend \u2026 on N asset(s)?" prompt.
|
|
9324
|
-
function generateDialog(items, { project = null, force = false, resume = false } = {}) {
|
|
9354
|
+
function generateDialog(items, { project = null, force = false, resume = false, refresh = false } = {}) {
|
|
9325
9355
|
const host = $('dialog-host');
|
|
9326
9356
|
host.textContent = '';
|
|
9327
9357
|
const wrap = el('div', 'dialog');
|
|
9328
9358
|
const form = el('form');
|
|
9329
|
-
|
|
9330
|
-
|
|
9359
|
+
const noun = items.length + (items.length === 1 ? ' asset' : ' assets');
|
|
9360
|
+
form.append(el('h3', null, refresh ? 'Pull upstream changes for ' + noun
|
|
9361
|
+
: resume ? 'Resume ' + noun
|
|
9362
|
+
: (force ? 'Regenerate ' : 'Generate ') + noun));
|
|
9331
9363
|
const table = el('table');
|
|
9332
9364
|
const thead = el('tr'); thead.append(el('th', null, 'asset'), el('th', null, 'state'), el('th', 'n', 'candidates'), el('th', 'n', 'estimate'));
|
|
9333
9365
|
table.append(thead);
|
|
@@ -9335,16 +9367,18 @@ function generateDialog(items, { project = null, force = false, resume = false }
|
|
|
9335
9367
|
for (const item of items) {
|
|
9336
9368
|
const tr = el('tr');
|
|
9337
9369
|
tr.append(el('td', 'mono', item.key), el('td', null, item.state), el('td', 'n', item.candidates ?? '\u2014'),
|
|
9338
|
-
el('td', 'n', resume ? 'no cost' : fmtCost(item.costUnit, item.estimatedCost ?? 0)));
|
|
9370
|
+
el('td', 'n', resume || refresh ? 'no cost' : fmtCost(item.costUnit, item.estimatedCost ?? 0)));
|
|
9339
9371
|
table.append(tr);
|
|
9340
|
-
if (!resume) {
|
|
9372
|
+
if (!resume && !refresh) {
|
|
9341
9373
|
const g = byProvider.get(item.provider) || { cost: 0, unit: item.costUnit };
|
|
9342
9374
|
g.cost += item.estimatedCost ?? 0; byProvider.set(item.provider, g);
|
|
9343
9375
|
}
|
|
9344
9376
|
}
|
|
9345
9377
|
form.append(table);
|
|
9346
9378
|
const sum = el('div', 'sum');
|
|
9347
|
-
if (
|
|
9379
|
+
if (refresh) {
|
|
9380
|
+
sum.append(el('div', null, 'Re-downloads each object from the provider and replaces the local file only when the object changed upstream \u2014 for example after editing it in the provider\u2019s own editor. Unchanged objects are left alone; a file you changed locally is refused. Nothing is submitted.'));
|
|
9381
|
+
} else if (resume) {
|
|
9348
9382
|
sum.append(el('div', null, 'Polls, reviews, and downloads existing provider work. Nothing is submitted.'));
|
|
9349
9383
|
} else {
|
|
9350
9384
|
for (const [provider, g] of byProvider) {
|
|
@@ -9361,7 +9395,7 @@ function generateDialog(items, { project = null, force = false, resume = false }
|
|
|
9361
9395
|
}
|
|
9362
9396
|
form.append(sum);
|
|
9363
9397
|
const actions = el('div', 'actions');
|
|
9364
|
-
const go = el('button', 'primary', resume ? 'Resume' : 'Generate');
|
|
9398
|
+
const go = el('button', 'primary', refresh ? 'Pull changes' : resume ? 'Resume' : 'Generate');
|
|
9365
9399
|
go.type = 'submit';
|
|
9366
9400
|
const cancel = el('button', null, 'Cancel'); cancel.type = 'button'; cancel.onclick = () => { host.textContent = ''; };
|
|
9367
9401
|
const msg = el('span', 'msg');
|
|
@@ -9375,6 +9409,7 @@ function generateDialog(items, { project = null, force = false, resume = false }
|
|
|
9375
9409
|
if (project) body.project = project;
|
|
9376
9410
|
if (force) body.force = true;
|
|
9377
9411
|
if (resume) body.resume = true;
|
|
9412
|
+
if (refresh) body.refresh = true;
|
|
9378
9413
|
const job = await postGenerate(body);
|
|
9379
9414
|
host.textContent = '';
|
|
9380
9415
|
GEN.jobs.unshift(job);
|
|
@@ -9664,6 +9699,13 @@ function renderMain(items) {
|
|
|
9664
9699
|
g.onclick = () => generateDialog(snap.items.filter((i) => i.project === s.project && s.actionable.keys.includes(i.key)), { project: s.project });
|
|
9665
9700
|
tools.append(g);
|
|
9666
9701
|
}
|
|
9702
|
+
const refreshable = snap.items.filter((i) => i.project === s.project && i.styleId === s.id && i.refreshable && i.upstreamUrl);
|
|
9703
|
+
if (GENERATION && refreshable.length) {
|
|
9704
|
+
const r = el('button', null, 'Pull upstream ' + refreshable.length); r.type = 'button';
|
|
9705
|
+
r.title = 'Re-download objects edited in the provider\u2019s own editor; no cost';
|
|
9706
|
+
r.onclick = () => generateDialog(refreshable, { project: s.project, refresh: true });
|
|
9707
|
+
tools.append(r);
|
|
9708
|
+
}
|
|
9667
9709
|
const styleKey = 'style:' + (s.project || '') + ':' + s.id;
|
|
9668
9710
|
if (EDITABLE && s.outDir) {
|
|
9669
9711
|
const es = el('button', null, ui.editing === styleKey ? 'Cancel' : 'Edit style');
|
|
@@ -9757,6 +9799,30 @@ function candidatesControl(style) {
|
|
|
9757
9799
|
return wrap;
|
|
9758
9800
|
}
|
|
9759
9801
|
|
|
9802
|
+
function upstreamSection(item) {
|
|
9803
|
+
if (!item.upstreamUrl && !(GENERATION && item.refreshable)) return null;
|
|
9804
|
+
const s = el('section', 'meta');
|
|
9805
|
+
s.append(el('h3', null, 'Upstream'));
|
|
9806
|
+
const dl = el('dl');
|
|
9807
|
+
if (item.upstreamUrl) {
|
|
9808
|
+
const a = el('a', null, 'Open in ' + item.provider + ' \u2197');
|
|
9809
|
+
a.href = item.upstreamUrl; a.target = '_blank'; a.rel = 'noopener';
|
|
9810
|
+
row(dl, 'object', a);
|
|
9811
|
+
row(dl, 'note', item.provider === 'pixellab'
|
|
9812
|
+
? 'The account object this generation came from. Edit it there with PixelLab\u2019s editor, then pull the changes back here; the lockfile records the new bytes as this generation.'
|
|
9813
|
+
: 'The provider\u2019s page for this object.');
|
|
9814
|
+
}
|
|
9815
|
+
s.append(dl);
|
|
9816
|
+
if (GENERATION && item.refreshable) {
|
|
9817
|
+
const acts = el('div', 'hand-actions');
|
|
9818
|
+
const b = el('button', null, 'Pull upstream changes \xB7 no cost'); b.type = 'button';
|
|
9819
|
+
b.onclick = () => generateDialog([item], { project: item.project, refresh: true });
|
|
9820
|
+
acts.append(b);
|
|
9821
|
+
s.append(acts);
|
|
9822
|
+
}
|
|
9823
|
+
return s;
|
|
9824
|
+
}
|
|
9825
|
+
|
|
9760
9826
|
function generateActions(item) {
|
|
9761
9827
|
const row = el('div', 'gen');
|
|
9762
9828
|
if (!GENERATION || !item.declared || item.currentSpecHash === null) return row;
|
|
@@ -10475,6 +10541,8 @@ function renderDrawer() {
|
|
|
10475
10541
|
|
|
10476
10542
|
const hand = handEditSection(item);
|
|
10477
10543
|
if (hand) body.append(hand);
|
|
10544
|
+
const upstream = upstreamSection(item);
|
|
10545
|
+
if (upstream) body.append(upstream);
|
|
10478
10546
|
|
|
10479
10547
|
if (item.revision || item.revisionParentKey) {
|
|
10480
10548
|
const { s, dl } = section('Lineage');
|
|
@@ -10964,8 +11032,10 @@ var GenerateRequestSchema = z7.object({
|
|
|
10964
11032
|
/** Regenerate work that is up to date, as `gen --force` does. */
|
|
10965
11033
|
force: z7.boolean().optional(),
|
|
10966
11034
|
/** Advance in-flight, review, or selected work without submitting anything. */
|
|
10967
|
-
resume: z7.boolean().optional()
|
|
10968
|
-
|
|
11035
|
+
resume: z7.boolean().optional(),
|
|
11036
|
+
/** Re-download downloaded outputs and replace files whose object changed upstream. */
|
|
11037
|
+
refresh: z7.boolean().optional()
|
|
11038
|
+
}).strict().refine((request) => !(request.resume && request.refresh), { message: "resume and refresh are exclusive" });
|
|
10969
11039
|
var GenerateRequestError = class extends Error {
|
|
10970
11040
|
constructor(message6, status = 400) {
|
|
10971
11041
|
super(message6);
|
|
@@ -11023,6 +11093,20 @@ function createGenerateHandlers(opts) {
|
|
|
11023
11093
|
}
|
|
11024
11094
|
return groups;
|
|
11025
11095
|
};
|
|
11096
|
+
async function refresh(job, ctx, specs) {
|
|
11097
|
+
job.phase = "fetching";
|
|
11098
|
+
for (const [providerId, providerSpecs] of groupByRecordedProvider(specs, ctx.lock)) {
|
|
11099
|
+
const provider = opts.providerFor(job.project ?? void 0, providerId);
|
|
11100
|
+
const res = await fetchAssets(provider, providerSpecs, ctx.lock, ctx.lockPath, {
|
|
11101
|
+
onProgress: (line) => say(job, line),
|
|
11102
|
+
refresh: true
|
|
11103
|
+
});
|
|
11104
|
+
job.counts.downloaded += res.downloaded;
|
|
11105
|
+
say(job, `${providerId}: ${res.downloaded} changed upstream and replaced, ${res.unchanged ?? 0} unchanged, ${res.skipped} skipped, ${res.failed} failed`);
|
|
11106
|
+
}
|
|
11107
|
+
job.phase = "done";
|
|
11108
|
+
job.finishedAt = now().toISOString();
|
|
11109
|
+
}
|
|
11026
11110
|
async function settle(job, ctx, specs) {
|
|
11027
11111
|
job.phase = "polling";
|
|
11028
11112
|
for (const [providerId, providerSpecs] of groupByRecordedProvider(specs, ctx.lock)) {
|
|
@@ -11056,6 +11140,10 @@ function createGenerateHandlers(opts) {
|
|
|
11056
11140
|
}
|
|
11057
11141
|
async function run(job, ctx, specs, groups) {
|
|
11058
11142
|
try {
|
|
11143
|
+
if (job.mode === "refresh") {
|
|
11144
|
+
await refresh(job, ctx, specs);
|
|
11145
|
+
return;
|
|
11146
|
+
}
|
|
11059
11147
|
if (job.mode === "generate") {
|
|
11060
11148
|
job.phase = "submitting";
|
|
11061
11149
|
for (const group of groups) {
|
|
@@ -11119,7 +11207,7 @@ function createGenerateHandlers(opts) {
|
|
|
11119
11207
|
if (holder) throw new GenerateRequestError(`"${key}" is already being worked on by job ${holder}`, 409);
|
|
11120
11208
|
}
|
|
11121
11209
|
let groups = [];
|
|
11122
|
-
if (!request.resume) {
|
|
11210
|
+
if (!request.resume && !request.refresh) {
|
|
11123
11211
|
const plan = await buildPlan(specs, ctx.lock, { force: request.force });
|
|
11124
11212
|
if (!plan.actionable.length) {
|
|
11125
11213
|
const reasons = plan.items.map((item) => `${item.key}: ${item.state} \u2014 ${item.reason}`).slice(0, 5);
|
|
@@ -11139,7 +11227,7 @@ function createGenerateHandlers(opts) {
|
|
|
11139
11227
|
id: randomBytes2(8).toString("hex"),
|
|
11140
11228
|
project: request.project ?? null,
|
|
11141
11229
|
keys: request.keys,
|
|
11142
|
-
mode: request.resume ? "resume" : "generate",
|
|
11230
|
+
mode: request.refresh ? "refresh" : request.resume ? "resume" : "generate",
|
|
11143
11231
|
phase: "queued",
|
|
11144
11232
|
startedAt: now().toISOString(),
|
|
11145
11233
|
finishedAt: null,
|
|
@@ -11151,7 +11239,7 @@ function createGenerateHandlers(opts) {
|
|
|
11151
11239
|
};
|
|
11152
11240
|
jobs.set(job.id, job);
|
|
11153
11241
|
contexts.set(job.id, ctx);
|
|
11154
|
-
say(job, request.resume ? `resuming ${request.keys.length} asset(s) at no cost` : `generating ${groups.reduce((n, g) => n + g.actionable.length, 0)} asset(s): ` + groups.map((g) => `${g.provider} ${formatCost(g.costUnit, g.cost)}`).join("; "));
|
|
11242
|
+
say(job, request.refresh ? `checking ${request.keys.length} asset(s) upstream at no cost` : request.resume ? `resuming ${request.keys.length} asset(s) at no cost` : `generating ${groups.reduce((n, g) => n + g.actionable.length, 0)} asset(s): ` + groups.map((g) => `${g.provider} ${formatCost(g.costUnit, g.cost)}`).join("; "));
|
|
11155
11243
|
void run(job, ctx, specs, groups);
|
|
11156
11244
|
return job;
|
|
11157
11245
|
},
|
|
@@ -13042,7 +13130,8 @@ var BOOL_FLAGS = [
|
|
|
13042
13130
|
"--write-prompts",
|
|
13043
13131
|
"--primary-only",
|
|
13044
13132
|
"--prune",
|
|
13045
|
-
"--edit"
|
|
13133
|
+
"--edit",
|
|
13134
|
+
"--refresh"
|
|
13046
13135
|
];
|
|
13047
13136
|
var COMMANDS = [
|
|
13048
13137
|
"init",
|
|
@@ -13275,6 +13364,7 @@ function parseArgs(argv) {
|
|
|
13275
13364
|
noOpen: rest.includes("--no-open"),
|
|
13276
13365
|
tag: rest.includes("--tag"),
|
|
13277
13366
|
edit: rest.includes("--edit"),
|
|
13367
|
+
refresh: rest.includes("--refresh"),
|
|
13278
13368
|
from: get("--from"),
|
|
13279
13369
|
out: get("--out"),
|
|
13280
13370
|
generator: get("--generator"),
|
|
@@ -13389,6 +13479,8 @@ Options
|
|
|
13389
13479
|
--no-open Do not auto-open the browser (pick, salvage, gallery) or editor (edit)
|
|
13390
13480
|
--edit gallery: allow manifest edits from the page (never spends)
|
|
13391
13481
|
--tag Also push tags upstream after fetch
|
|
13482
|
+
--refresh fetch: re-download and replace files whose object changed
|
|
13483
|
+
upstream (e.g. edited in PixelLab's editor); no generation
|
|
13392
13484
|
--claims a.json,b Other projects' lockfiles (salvage; required if account is shared)
|
|
13393
13485
|
--workspace <path> Workspace catalog (default: pixelkiln.workspace.json). Also
|
|
13394
13486
|
derives salvage's claim set instead of repeated --claims,
|
|
@@ -15033,17 +15125,19 @@ async function main() {
|
|
|
15033
15125
|
if (args.command === "fetch" || args.command === "restore" || args.command === "gen") {
|
|
15034
15126
|
log(`
|
|
15035
15127
|
downloading\u2026`);
|
|
15036
|
-
const total = { downloaded: 0, skipped: 0, failed: 0, tagged: 0 };
|
|
15128
|
+
const total = { downloaded: 0, skipped: 0, failed: 0, tagged: 0, unchanged: 0 };
|
|
15037
15129
|
for (const [providerId, providerSpecsForRun] of specsByRecordedProvider(specs, lock)) {
|
|
15038
15130
|
const groupProvider = providerFor(providerId);
|
|
15039
15131
|
const res = await fetchAssets(groupProvider, providerSpecsForRun, lock, args.lock, {
|
|
15040
15132
|
onProgress: log,
|
|
15041
15133
|
repair: args.command === "restore",
|
|
15042
|
-
force: args.force
|
|
15134
|
+
force: args.force,
|
|
15135
|
+
refresh: args.command === "fetch" && args.refresh
|
|
15043
15136
|
});
|
|
15044
15137
|
total.downloaded += res.downloaded;
|
|
15045
15138
|
total.skipped += res.skipped;
|
|
15046
15139
|
total.failed += res.failed;
|
|
15140
|
+
total.unchanged += res.unchanged ?? 0;
|
|
15047
15141
|
if (args.tag) {
|
|
15048
15142
|
total.tagged += await pushTags(groupProvider, providerSpecsForRun, lock, {
|
|
15049
15143
|
onProgress: log
|
|
@@ -15052,7 +15146,7 @@ async function main() {
|
|
|
15052
15146
|
}
|
|
15053
15147
|
log(
|
|
15054
15148
|
`
|
|
15055
|
-
downloaded ${total.downloaded}, skipped ${total.skipped}, failed ${total.failed}`
|
|
15149
|
+
downloaded ${total.downloaded}, skipped ${total.skipped}, failed ${total.failed}` + (args.refresh ? `, unchanged upstream ${total.unchanged}` : "")
|
|
15056
15150
|
);
|
|
15057
15151
|
if (total.failed) process.exitCode = 1;
|
|
15058
15152
|
if (args.tag) log(` tagged ${total.tagged} object(s) upstream`);
|