pixelkiln 0.30.0 → 0.32.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.js +119 -23
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +117 -21
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +19 -3
- package/dist/index.d.ts +19 -3
- package/dist/index.js +118 -22
- package/dist/index.js.map +1 -1
- package/docs/CLI.md +18 -5
- package/docs/GETTING_STARTED.md +2 -1
- package/docs/RECOVERY.md +3 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -8041,7 +8041,12 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
8041
8041
|
if (opts.refresh) return e.outputs.length > 0 && Boolean(e.sourceUrls?.length || e.sourceUrl);
|
|
8042
8042
|
if (!opts.repair) return false;
|
|
8043
8043
|
const spec = specByKey.get(key);
|
|
8044
|
-
|
|
8044
|
+
if (e.outputs.length === 0 || !spec) return true;
|
|
8045
|
+
return e.outputs.some((output) => {
|
|
8046
|
+
const file = resolveOutputPath(output.path, spec.root);
|
|
8047
|
+
if (!(0, import_node_fs15.existsSync)(file)) return true;
|
|
8048
|
+
return Boolean(opts.force) && sha256((0, import_node_fs15.readFileSync)(file)) !== output.sha256;
|
|
8049
|
+
});
|
|
8045
8050
|
});
|
|
8046
8051
|
const concurrency = Math.min(Math.max(1, opts.concurrency ?? 8), Math.max(1, pending.length));
|
|
8047
8052
|
let cursor = 0;
|
|
@@ -10810,6 +10815,7 @@ function renderGallery(snapshot, opts = {}) {
|
|
|
10810
10815
|
.sheet.editor-host { width:min(1400px, 96vw); grid-template-rows:auto 1fr; }
|
|
10811
10816
|
.sheet.editor-host .rbar b { font:650 12.5px/1.4 var(--mono); color:var(--text); }
|
|
10812
10817
|
.sheet.editor-host .rbar .acts { margin-left:auto; display:flex; gap:8px; align-items:center; }
|
|
10818
|
+
.sheet.editor-host .rbar .chip { color:var(--dim); font-size:12.5px; display:inline-flex; align-items:center; gap:5px; margin-right:6px; }
|
|
10813
10819
|
.sheet.editor-host .rbar .st { display:inline-flex; align-items:center; gap:6px; font:650 11.5px/1 var(--mono); }
|
|
10814
10820
|
.sheet.editor-host .rbar .msg { color:var(--bad); font-size:12.5px; }
|
|
10815
10821
|
.sheet.editor-host .stage { position:relative; min-height:0; }
|
|
@@ -10857,6 +10863,8 @@ function renderGallery(snapshot, opts = {}) {
|
|
|
10857
10863
|
.tray > button { padding:5px 11px; font-size:12.5px; }
|
|
10858
10864
|
@media (max-width: 900px) { .sheet.review-host, .sheet.compare-host, .sheet.editor-host { width:100vw; } }
|
|
10859
10865
|
.drawer .gen { display:flex; gap:8px; flex-wrap:wrap; margin-top:12px; }
|
|
10866
|
+
.drawer .readonly-hint { margin-top:10px; font-size:12.5px; line-height:1.5; }
|
|
10867
|
+
.drawer .readonly-hint code { font:12px var(--mono); color:var(--text); }
|
|
10860
10868
|
.pair { display:grid; grid-template-columns:1fr 1fr; gap:8px; margin:8px 0 10px; }
|
|
10861
10869
|
.pair figure { margin:0; border:1px solid var(--line); background:var(--panel-deep); background-image:var(--checker);
|
|
10862
10870
|
background-size:12px 12px; background-position:0 0,6px 6px; display:grid; place-items:center; padding:8px; min-height:96px; }
|
|
@@ -11066,7 +11074,7 @@ function renderJobs() {
|
|
|
11066
11074
|
const row = el('div', 'job');
|
|
11067
11075
|
const ph = el('span', 'ph');
|
|
11068
11076
|
ph.append(el('i', 'dot ' + PHASE_TONE[job.phase]), document.createTextNode(job.phase));
|
|
11069
|
-
const what = (job.mode === 'refresh' ? 'pull upstream for ' : job.mode === 'resume' ? 'resume ' : 'generate ') + job.keys.length + (job.keys.length === 1 ? ' asset' : ' assets') +
|
|
11077
|
+
const what = (job.mode === 'refresh' ? 'pull upstream for ' : job.mode === 'restore' ? 'restore ' : job.mode === 'resume' ? 'resume ' : 'generate ') + job.keys.length + (job.keys.length === 1 ? ' asset' : ' assets') +
|
|
11070
11078
|
(job.project ? ' in ' + job.project : '');
|
|
11071
11079
|
const last = el('span', 'last', what + (job.messages.length ? ' \u2014 ' + job.messages[job.messages.length - 1].trim() : ''));
|
|
11072
11080
|
last.title = job.keys.join('\\n');
|
|
@@ -11191,13 +11199,16 @@ function budgetLine() {
|
|
|
11191
11199
|
|
|
11192
11200
|
// The confirm step: what will be sent, what it is estimated to cost, and what
|
|
11193
11201
|
// this session may still spend. Mirrors gen's "Spend \u2026 on N asset(s)?" prompt.
|
|
11194
|
-
function generateDialog(items, { project = null, force = false, resume = false, refresh = false } = {}) {
|
|
11202
|
+
function generateDialog(items, { project = null, force = false, resume = false, refresh = false, restore = false } = {}) {
|
|
11195
11203
|
const host = $('dialog-host');
|
|
11196
11204
|
host.textContent = '';
|
|
11197
11205
|
const wrap = el('div', 'dialog');
|
|
11198
11206
|
const form = el('form');
|
|
11199
11207
|
const noun = items.length + (items.length === 1 ? ' asset' : ' assets');
|
|
11208
|
+
const free = resume || refresh || restore;
|
|
11209
|
+
const replacing = items.filter((i) => i.state === 'orphaned' && i.outputs.every((o) => o.exists) || i.state === 'untracked');
|
|
11200
11210
|
form.append(el('h3', null, refresh ? 'Pull upstream changes for ' + noun
|
|
11211
|
+
: restore ? 'Restore ' + noun
|
|
11201
11212
|
: resume ? 'Resume ' + noun
|
|
11202
11213
|
: (force ? 'Regenerate ' : 'Generate ') + noun));
|
|
11203
11214
|
const table = el('table');
|
|
@@ -11207,9 +11218,9 @@ function generateDialog(items, { project = null, force = false, resume = false,
|
|
|
11207
11218
|
for (const item of items) {
|
|
11208
11219
|
const tr = el('tr');
|
|
11209
11220
|
tr.append(el('td', 'mono', item.key), el('td', null, item.state), el('td', 'n', item.candidates ?? '\u2014'),
|
|
11210
|
-
el('td', 'n',
|
|
11221
|
+
el('td', 'n', free ? 'no cost' : fmtCost(item.costUnit, item.estimatedCost ?? 0)));
|
|
11211
11222
|
table.append(tr);
|
|
11212
|
-
if (!
|
|
11223
|
+
if (!free) {
|
|
11213
11224
|
const g = byProvider.get(item.provider) || { cost: 0, unit: item.costUnit };
|
|
11214
11225
|
g.cost += item.estimatedCost ?? 0; byProvider.set(item.provider, g);
|
|
11215
11226
|
}
|
|
@@ -11218,6 +11229,9 @@ function generateDialog(items, { project = null, force = false, resume = false,
|
|
|
11218
11229
|
const sum = el('div', 'sum');
|
|
11219
11230
|
if (refresh) {
|
|
11220
11231
|
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.'));
|
|
11232
|
+
} else if (restore) {
|
|
11233
|
+
sum.append(el('div', null, 'Puts the recorded bytes back on disk from the local cache or the provider object. Nothing is generated or submitted.'));
|
|
11234
|
+
if (force && replacing.length) sum.append(el('div', 'warn', 'The file on disk was changed after PixelKiln wrote it. Restoring discards that change. To keep it instead, cancel and use Edit by hand or Edit in browser first: the edit is copied beside the generated file, and the record stays intact.'));
|
|
11221
11235
|
} else if (resume) {
|
|
11222
11236
|
sum.append(el('div', null, 'Polls, reviews, and downloads existing provider work. Nothing is submitted.'));
|
|
11223
11237
|
} else {
|
|
@@ -11230,12 +11244,13 @@ function generateDialog(items, { project = null, force = false, resume = false,
|
|
|
11230
11244
|
if (left !== null && g.cost > left) line.className = 'warn';
|
|
11231
11245
|
sum.append(line);
|
|
11232
11246
|
}
|
|
11233
|
-
if (force) sum.append(el('div', 'warn', '
|
|
11247
|
+
if (force && replacing.length) sum.append(el('div', 'warn', (replacing.length === 1 ? 'This file' : replacing.length + ' of these files') + ' changed on disk after PixelKiln wrote it (or was never recorded). Generating replaces it and that change is lost. To keep it, cancel and use Edit by hand or Edit in browser first.'));
|
|
11248
|
+
else if (force) sum.append(el('div', 'warn', 'Regenerating replaces the current art. The previous file is replaced when the new result is fetched; the provider objects it came from are not deleted.'));
|
|
11234
11249
|
sum.append(el('div', null, 'Candidate sets land in review; you choose from them here before anything is downloaded.'));
|
|
11235
11250
|
}
|
|
11236
11251
|
form.append(sum);
|
|
11237
11252
|
const actions = el('div', 'actions');
|
|
11238
|
-
const go = el('button', 'primary', refresh ? 'Pull changes' : resume ? 'Resume' : 'Generate');
|
|
11253
|
+
const go = el('button', 'primary', refresh ? 'Pull changes' : restore ? (force && replacing.length ? 'Restore and discard my change' : 'Restore') : resume ? 'Resume' : force && replacing.length ? 'Generate and replace' : 'Generate');
|
|
11239
11254
|
go.type = 'submit';
|
|
11240
11255
|
const cancel = el('button', null, 'Cancel'); cancel.type = 'button'; cancel.onclick = () => { host.textContent = ''; };
|
|
11241
11256
|
const msg = el('span', 'msg');
|
|
@@ -11250,6 +11265,7 @@ function generateDialog(items, { project = null, force = false, resume = false,
|
|
|
11250
11265
|
if (force) body.force = true;
|
|
11251
11266
|
if (resume) body.resume = true;
|
|
11252
11267
|
if (refresh) body.refresh = true;
|
|
11268
|
+
if (restore) body.restore = true;
|
|
11253
11269
|
const job = await postGenerate(body);
|
|
11254
11270
|
host.textContent = '';
|
|
11255
11271
|
GEN.jobs.unshift(job);
|
|
@@ -11338,11 +11354,16 @@ function openEditorSheet(item) {
|
|
|
11338
11354
|
const status = el('span', 'st');
|
|
11339
11355
|
const acts = el('div', 'acts');
|
|
11340
11356
|
const msg = el('span', 'msg');
|
|
11357
|
+
const compare = el('label', 'chip'); compare.hidden = true;
|
|
11358
|
+
compare.title = 'The generated art on a locked layer at 50%, for comparison; never part of the saved file';
|
|
11359
|
+
const compareBox = el('input'); compareBox.type = 'checkbox'; compareBox.checked = true;
|
|
11360
|
+
compareBox.onchange = () => showReference(compareBox.checked);
|
|
11361
|
+
compare.append(compareBox, document.createTextNode(' show generated'));
|
|
11341
11362
|
const save = el('button', 'primary', 'Save to project'); save.type = 'button';
|
|
11342
11363
|
save.onclick = () => requestEditorSave(false);
|
|
11343
11364
|
const saveClose = el('button', null, 'Save & close'); saveClose.type = 'button';
|
|
11344
11365
|
saveClose.onclick = () => requestEditorSave(true);
|
|
11345
|
-
acts.append(msg, save, saveClose);
|
|
11366
|
+
acts.append(msg, compare, save, saveClose);
|
|
11346
11367
|
bar.append(close, title, status, acts);
|
|
11347
11368
|
const stage = el('div', 'stage');
|
|
11348
11369
|
const frame = el('iframe');
|
|
@@ -11355,7 +11376,7 @@ function openEditorSheet(item) {
|
|
|
11355
11376
|
stage.append(frame, loading);
|
|
11356
11377
|
panel.append(bar, stage);
|
|
11357
11378
|
host.append(scrim, panel);
|
|
11358
|
-
SHEET = { item, frame, loading, status, msg, save, saveClose, dirty: false, opened: false, ready: null, pending: null, requests: 0, source: src, sentProject: false, roles: null };
|
|
11379
|
+
SHEET = { item, frame, loading, status, msg, save, saveClose, compare, dirty: false, opened: false, ready: null, pending: null, requests: 0, source: src, sentProject: false, sentReference: false, reference: false, roles: null };
|
|
11359
11380
|
sheetStatus('cool', 'loading');
|
|
11360
11381
|
sheetButtons();
|
|
11361
11382
|
close.focus({ preventScroll: true });
|
|
@@ -11375,7 +11396,11 @@ async function sendOpen() {
|
|
|
11375
11396
|
sheetStatus('bad', 'unsupported');
|
|
11376
11397
|
return;
|
|
11377
11398
|
}
|
|
11399
|
+
// When an edit is being opened, the generated art rides along as a
|
|
11400
|
+
// reference layer: what the editor shows through at 50% for comparison.
|
|
11401
|
+
const referenceFiles = sources.fromEdit && SHEET.ready.version >= 4 && item.outputs.every((o) => o.exists && o.url) ? item.outputs : null;
|
|
11378
11402
|
const pngs = [];
|
|
11403
|
+
const reference = [];
|
|
11379
11404
|
let pxo = null;
|
|
11380
11405
|
try {
|
|
11381
11406
|
for (const file of sources.files) {
|
|
@@ -11387,6 +11412,10 @@ async function sendOpen() {
|
|
|
11387
11412
|
const p = await fetch(projectUrl, { cache: 'no-store' });
|
|
11388
11413
|
if (p.ok) pxo = await p.arrayBuffer();
|
|
11389
11414
|
}
|
|
11415
|
+
for (const file of referenceFiles || []) {
|
|
11416
|
+
const res = await fetch(file.url, { cache: 'no-store' });
|
|
11417
|
+
if (res.ok) reference.push({ role: file.role || null, png: await res.arrayBuffer() });
|
|
11418
|
+
}
|
|
11390
11419
|
} catch (err) {
|
|
11391
11420
|
if (SHEET) { SHEET.msg.textContent = 'Could not load the image: ' + err.message; sheetStatus('bad', 'failed'); }
|
|
11392
11421
|
return;
|
|
@@ -11411,9 +11440,18 @@ async function sendOpen() {
|
|
|
11411
11440
|
transfer.push(message.png);
|
|
11412
11441
|
}
|
|
11413
11442
|
if (pxo) { message.pxo = pxo; transfer.push(pxo); }
|
|
11443
|
+
if (reference.length === (referenceFiles || []).length && reference.length) {
|
|
11444
|
+
message.reference = reference;
|
|
11445
|
+
for (const r of reference) transfer.push(r.png);
|
|
11446
|
+
}
|
|
11447
|
+
SHEET.sentReference = !!message.reference;
|
|
11414
11448
|
SHEET.frame.contentWindow.postMessage(message, location.origin, transfer);
|
|
11415
11449
|
sheetStatus('cool', 'opening');
|
|
11416
11450
|
}
|
|
11451
|
+
function showReference(visible) {
|
|
11452
|
+
if (!SHEET || !SHEET.opened || !SHEET.reference) return;
|
|
11453
|
+
SHEET.frame.contentWindow.postMessage({ type: 'pixelkiln:reference', visible }, location.origin);
|
|
11454
|
+
}
|
|
11417
11455
|
function requestEditorSave(close) {
|
|
11418
11456
|
if (!SHEET || !SHEET.opened || SHEET.pending) return;
|
|
11419
11457
|
const request = 'save-' + (++SHEET.requests);
|
|
@@ -11437,10 +11475,13 @@ async function onEditorMessage(m) {
|
|
|
11437
11475
|
case 'pixelkiln:opened':
|
|
11438
11476
|
SHEET.opened = true;
|
|
11439
11477
|
SHEET.loading.remove();
|
|
11478
|
+
SHEET.reference = !!m.reference;
|
|
11479
|
+
SHEET.compare.hidden = !SHEET.reference;
|
|
11440
11480
|
if (m.source === 'pxo') sheetStatus('ok', 'editing the edit file' + (m.frames > 1 ? 's' : '') + ' with ' + (m.layers === 1 ? 'the layer' : m.layers + ' layers') + (m.frames > 1 ? ' and ' + m.frames + ' ' + memberNoun(SHEET.item, m.frames) : '') + ' restored');
|
|
11441
11481
|
else if (SHEET.sentProject) sheetStatus('warn', 'editing the flattened edit file' + (m.frames > 1 ? 's' : '') + '; the layer file could not be opened');
|
|
11442
11482
|
else if (m.source === 'frames') sheetStatus('ok', (SHEET.item.edits.length ? 'editing the edit files' : 'editing a copy of the generated ' + memberNoun(SHEET.item, m.frames)) + ' as ' + m.frames + ' frames' + (isFrameSet(SHEET.item) ? '' : ', one per ' + memberNoun(SHEET.item, 1)));
|
|
11443
11483
|
else sheetStatus('ok', SHEET.item.edit && SHEET.item.edit.exists ? 'editing the edit file' : 'editing a copy of the generated art');
|
|
11484
|
+
if (SHEET.reference) SHEET.status.append(document.createTextNode(' \xB7 generated art shown through at 50%'));
|
|
11444
11485
|
sheetButtons();
|
|
11445
11486
|
break;
|
|
11446
11487
|
case 'pixelkiln:dirty':
|
|
@@ -11883,6 +11924,13 @@ function generateActions(item) {
|
|
|
11883
11924
|
};
|
|
11884
11925
|
if (item.state === 'missing' || item.state === 'stale' || item.state === 'failed') add('Generate' + cost, 'primary', {});
|
|
11885
11926
|
else if (item.state === 'ok') add('Regenerate' + cost, null, { force: true });
|
|
11927
|
+
else if (item.state === 'orphaned' && item.outputs.every((o) => o.exists)) {
|
|
11928
|
+
// The file changed after download. Putting the recorded bytes back and
|
|
11929
|
+
// regenerating both discard that change, so both say so and ask.
|
|
11930
|
+
add('Restore recorded bytes \xB7 no cost', null, { restore: true, force: true });
|
|
11931
|
+
add('Regenerate' + cost, null, { force: true });
|
|
11932
|
+
} else if (item.state === 'orphaned') add('Restore \xB7 no cost', 'primary', { restore: true });
|
|
11933
|
+
else if (item.state === 'untracked') add('Generate' + cost + ' \xB7 replaces the file', null, { force: true });
|
|
11886
11934
|
if (item.state === 'in-flight' || item.state === 'recoverable') {
|
|
11887
11935
|
// A job already parked this asset in review: go straight to its sheet.
|
|
11888
11936
|
const holder = GEN.jobs.find((job) => job.phase === 'review' && job.project === item.project && job.review.includes(item.key));
|
|
@@ -12573,6 +12621,15 @@ function renderDrawer() {
|
|
|
12573
12621
|
if (ui.notice && ui.notice.id === item.id) body.append(el('div', 'notice', ui.notice.text));
|
|
12574
12622
|
body.append(stateNode(item.state, item.reason));
|
|
12575
12623
|
if (GENERATION) body.append(generateActions(item));
|
|
12624
|
+
// Writes are opt-in flags on the command line; say so where the buttons would be.
|
|
12625
|
+
if ((!EDITABLE || !GENERATION) && item.declared) {
|
|
12626
|
+
const missing = [];
|
|
12627
|
+
if (!EDITABLE) missing.push('--edit to change its prompt, size, and tags or open it in the browser editor');
|
|
12628
|
+
if (!GENERATION) missing.push('--budget <n> to generate or regenerate it from here');
|
|
12629
|
+
const hint = el('div', 'state-dim readonly-hint');
|
|
12630
|
+
hint.append(document.createTextNode('This gallery is read-only. Restart it with '), el('code', null, 'pixelkiln gallery'), document.createTextNode(' plus ' + missing.join(', and ') + '.'));
|
|
12631
|
+
body.append(hint);
|
|
12632
|
+
}
|
|
12576
12633
|
if (canEdit && ui.editing === item.id) body.append(editForm(item));
|
|
12577
12634
|
// Plan already quotes the error as the reason for a failed entry; only a
|
|
12578
12635
|
// stale or superseded failure needs its own line.
|
|
@@ -12883,8 +12940,8 @@ var pin_default = {
|
|
|
12883
12940
|
pixelorama: "v1.2.2",
|
|
12884
12941
|
godot: "4.7.2",
|
|
12885
12942
|
extensionsApi: 9,
|
|
12886
|
-
protocol:
|
|
12887
|
-
release: "editor-pixelorama-v1.2.2-pk.
|
|
12943
|
+
protocol: 4,
|
|
12944
|
+
release: "editor-pixelorama-v1.2.2-pk.18",
|
|
12888
12945
|
files: {
|
|
12889
12946
|
"index.apple-touch-icon.png": {
|
|
12890
12947
|
sha256: "ee5434873e0fed91e9a888c3ea5a8b79beac25999308be7a6eb41bf4ce640d27",
|
|
@@ -12899,7 +12956,7 @@ var pin_default = {
|
|
|
12899
12956
|
bytes: 7298
|
|
12900
12957
|
},
|
|
12901
12958
|
"index.html": {
|
|
12902
|
-
sha256: "
|
|
12959
|
+
sha256: "d9adf06036660c4b76ef5386e872bc03dc0d5b0628fd6ac660d480893ee1f47a",
|
|
12903
12960
|
bytes: 5442
|
|
12904
12961
|
},
|
|
12905
12962
|
"index.icon.png": {
|
|
@@ -12911,8 +12968,8 @@ var pin_default = {
|
|
|
12911
12968
|
bytes: 279815
|
|
12912
12969
|
},
|
|
12913
12970
|
"index.pck": {
|
|
12914
|
-
sha256: "
|
|
12915
|
-
bytes:
|
|
12971
|
+
sha256: "a41d9c3a39aa99ea5ee49ddcedfeb8b092006aa75c0de67139f13297c17f42e4",
|
|
12972
|
+
bytes: 6431692
|
|
12916
12973
|
},
|
|
12917
12974
|
"index.png": {
|
|
12918
12975
|
sha256: "6e3ca3eb4a99e4c07bdd89ebc873b440ec0ffd66ce1955160da9e7ed80d538d7",
|
|
@@ -13461,13 +13518,21 @@ var import_zod10 = require("zod");
|
|
|
13461
13518
|
var GenerateRequestSchema = import_zod10.z.object({
|
|
13462
13519
|
project: import_zod10.z.string().min(1).optional(),
|
|
13463
13520
|
keys: import_zod10.z.array(import_zod10.z.string().min(1)).min(1).max(500),
|
|
13464
|
-
/**
|
|
13521
|
+
/**
|
|
13522
|
+
* Regenerate work that is up to date, as `gen --force` does, and let the
|
|
13523
|
+
* fetch replace a locally modified or untracked file. With `restore`, put
|
|
13524
|
+
* the recorded bytes back over a modified file.
|
|
13525
|
+
*/
|
|
13465
13526
|
force: import_zod10.z.boolean().optional(),
|
|
13466
13527
|
/** Advance in-flight, review, or selected work without submitting anything. */
|
|
13467
13528
|
resume: import_zod10.z.boolean().optional(),
|
|
13468
13529
|
/** Re-download downloaded outputs and replace files whose object changed upstream. */
|
|
13469
|
-
refresh: import_zod10.z.boolean().optional()
|
|
13470
|
-
|
|
13530
|
+
refresh: import_zod10.z.boolean().optional(),
|
|
13531
|
+
/** Put recorded outputs back on disk from the cache or the provider, without generating. */
|
|
13532
|
+
restore: import_zod10.z.boolean().optional()
|
|
13533
|
+
}).strict().refine((request) => [request.resume, request.refresh, request.restore].filter(Boolean).length <= 1, {
|
|
13534
|
+
message: "resume, refresh, and restore are exclusive"
|
|
13535
|
+
});
|
|
13471
13536
|
var GenerateRequestError = class extends Error {
|
|
13472
13537
|
constructor(message6, status = 400) {
|
|
13473
13538
|
super(message6);
|
|
@@ -13555,7 +13620,9 @@ function createGenerateHandlers(opts) {
|
|
|
13555
13620
|
for (const [providerId, providerSpecs] of groupByRecordedProvider(specs, ctx.lock)) {
|
|
13556
13621
|
const provider = opts.providerFor(job.project ?? void 0, providerId);
|
|
13557
13622
|
const res = await fetchAssets(provider, providerSpecs, ctx.lock, ctx.lockPath, {
|
|
13558
|
-
onProgress: (line) => say(job, line)
|
|
13623
|
+
onProgress: (line) => say(job, line),
|
|
13624
|
+
// A forced regeneration replaces whatever is on disk, as `gen --force`.
|
|
13625
|
+
force: job.force
|
|
13559
13626
|
});
|
|
13560
13627
|
job.counts.downloaded += res.downloaded;
|
|
13561
13628
|
if (res.downloaded || res.failed) {
|
|
@@ -13570,12 +13637,32 @@ function createGenerateHandlers(opts) {
|
|
|
13570
13637
|
job.finishedAt = now().toISOString();
|
|
13571
13638
|
}
|
|
13572
13639
|
}
|
|
13640
|
+
async function restore(job, ctx, specs) {
|
|
13641
|
+
job.phase = "fetching";
|
|
13642
|
+
for (const [providerId, providerSpecs] of groupByRecordedProvider(specs, ctx.lock)) {
|
|
13643
|
+
const provider = opts.providerFor(job.project ?? void 0, providerId);
|
|
13644
|
+
const res = await fetchAssets(provider, providerSpecs, ctx.lock, ctx.lockPath, {
|
|
13645
|
+
onProgress: (line) => say(job, line),
|
|
13646
|
+
repair: true,
|
|
13647
|
+
force: job.force
|
|
13648
|
+
});
|
|
13649
|
+
job.counts.downloaded += res.downloaded;
|
|
13650
|
+
say(job, `${providerId}: restored ${res.downloaded}, skipped ${res.skipped}, failed ${res.failed}`);
|
|
13651
|
+
if (res.failed) throw new Error(`${res.failed} file(s) could not be restored`);
|
|
13652
|
+
}
|
|
13653
|
+
job.phase = "done";
|
|
13654
|
+
job.finishedAt = now().toISOString();
|
|
13655
|
+
}
|
|
13573
13656
|
async function run(job, ctx, specs, groups) {
|
|
13574
13657
|
try {
|
|
13575
13658
|
if (job.mode === "refresh") {
|
|
13576
13659
|
await refresh(job, ctx, specs);
|
|
13577
13660
|
return;
|
|
13578
13661
|
}
|
|
13662
|
+
if (job.mode === "restore") {
|
|
13663
|
+
await restore(job, ctx, specs);
|
|
13664
|
+
return;
|
|
13665
|
+
}
|
|
13579
13666
|
if (job.mode === "generate") {
|
|
13580
13667
|
job.phase = "submitting";
|
|
13581
13668
|
for (const group of groups) {
|
|
@@ -13638,8 +13725,16 @@ function createGenerateHandlers(opts) {
|
|
|
13638
13725
|
const holder = busy.get(`${request.project ?? ""}:${key}`);
|
|
13639
13726
|
if (holder) throw new GenerateRequestError(`"${key}" is already being worked on by job ${holder}`, 409);
|
|
13640
13727
|
}
|
|
13728
|
+
if (request.restore) {
|
|
13729
|
+
for (const key of request.keys) {
|
|
13730
|
+
const entry = ctx.lock.entries[key];
|
|
13731
|
+
if (!entry || entry.status !== "downloaded" || !entry.outputs.length) {
|
|
13732
|
+
throw new GenerateRequestError(`"${key}" has no downloaded output recorded to restore`);
|
|
13733
|
+
}
|
|
13734
|
+
}
|
|
13735
|
+
}
|
|
13641
13736
|
let groups = [];
|
|
13642
|
-
if (!request.resume && !request.refresh) {
|
|
13737
|
+
if (!request.resume && !request.refresh && !request.restore) {
|
|
13643
13738
|
const plan = await buildPlan(specs, ctx.lock, { force: request.force });
|
|
13644
13739
|
if (!plan.actionable.length) {
|
|
13645
13740
|
const reasons = plan.items.map((item) => `${item.key}: ${item.state} \u2014 ${item.reason}`).slice(0, 5);
|
|
@@ -13659,7 +13754,8 @@ function createGenerateHandlers(opts) {
|
|
|
13659
13754
|
id: (0, import_node_crypto9.randomBytes)(8).toString("hex"),
|
|
13660
13755
|
project: request.project ?? null,
|
|
13661
13756
|
keys: request.keys,
|
|
13662
|
-
mode: request.refresh ? "refresh" : request.resume ? "resume" : "generate",
|
|
13757
|
+
mode: request.refresh ? "refresh" : request.resume ? "resume" : request.restore ? "restore" : "generate",
|
|
13758
|
+
force: Boolean(request.force),
|
|
13663
13759
|
phase: "queued",
|
|
13664
13760
|
startedAt: now().toISOString(),
|
|
13665
13761
|
finishedAt: null,
|
|
@@ -13671,7 +13767,7 @@ function createGenerateHandlers(opts) {
|
|
|
13671
13767
|
};
|
|
13672
13768
|
jobs.set(job.id, job);
|
|
13673
13769
|
contexts.set(job.id, ctx);
|
|
13674
|
-
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("; "));
|
|
13770
|
+
say(job, request.refresh ? `checking ${request.keys.length} asset(s) upstream at no cost` : request.restore ? `restoring ${request.keys.length} asset(s) at no cost${request.force ? " (replacing modified files)" : ""}` : 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("; "));
|
|
13675
13771
|
void run(job, ctx, specs, groups);
|
|
13676
13772
|
return job;
|
|
13677
13773
|
},
|