pixelkiln 0.22.0 → 0.24.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 +1 -1
- package/dist/cli.js +684 -292
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +429 -84
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +238 -105
- package/dist/index.d.ts +238 -105
- package/dist/index.js +417 -72
- package/dist/index.js.map +1 -1
- package/docs/ARTIFACTS.md +10 -5
- package/docs/CLI.md +42 -5
- package/docs/MANIFEST.md +5 -2
- package/package.json +1 -1
- package/schema/manifest.schema.json +8 -0
- package/skills/pixelkiln/SKILL.md +4 -1
package/dist/index.cjs
CHANGED
|
@@ -482,6 +482,15 @@ var AssetSchema = import_zod.z.object({
|
|
|
482
482
|
* that wasn't. `prompt` still records what was asked for.
|
|
483
483
|
*/
|
|
484
484
|
source: import_zod.z.string().optional(),
|
|
485
|
+
/**
|
|
486
|
+
* Per-style replacement for `source`, keyed by style id.
|
|
487
|
+
*
|
|
488
|
+
* A hand edit belongs to one generation: the same asset generated in two
|
|
489
|
+
* styles is two different pictures, so the touch-up of one must not be
|
|
490
|
+
* placed for the other. `pixelkiln edit` writes here when an asset is in
|
|
491
|
+
* more than one style, and to `source` when it is in exactly one.
|
|
492
|
+
*/
|
|
493
|
+
sourceByStyle: import_zod.z.record(import_zod.z.string().min(1)).default({}),
|
|
485
494
|
/** Controlled generation derived from another asset in the same style. */
|
|
486
495
|
revision: RevisionSchema.optional(),
|
|
487
496
|
/**
|
|
@@ -516,7 +525,7 @@ var AssetSchema = import_zod.z.object({
|
|
|
516
525
|
import_zod.z.array(import_zod.z.union([import_zod.z.string(), import_zod.z.number().finite(), import_zod.z.boolean()])).min(2).max(64)
|
|
517
526
|
])).default({})
|
|
518
527
|
}).strict().superRefine((asset, context) => {
|
|
519
|
-
if (asset.source && asset.revision) {
|
|
528
|
+
if ((asset.source || Object.keys(asset.sourceByStyle).length) && asset.revision) {
|
|
520
529
|
context.addIssue({
|
|
521
530
|
code: import_zod.z.ZodIssueCode.custom,
|
|
522
531
|
message: "source and revision are mutually exclusive",
|
|
@@ -759,11 +768,11 @@ var PixelLabClient = class {
|
|
|
759
768
|
* common than the failure mode of retrying (a duplicate object), and a
|
|
760
769
|
* duplicate is visible and free to delete whereas a silent gap is neither.
|
|
761
770
|
*/
|
|
762
|
-
async request(
|
|
771
|
+
async request(path27, init, attempt = 0) {
|
|
763
772
|
const auth = this.apiKey.startsWith("Bearer ") ? this.apiKey : `Bearer ${this.apiKey}`;
|
|
764
773
|
let res;
|
|
765
774
|
try {
|
|
766
|
-
res = await fetch(`${BASE}${
|
|
775
|
+
res = await fetch(`${BASE}${path27}`, {
|
|
767
776
|
...init,
|
|
768
777
|
signal: init?.signal ?? AbortSignal.timeout(this.timeoutMs),
|
|
769
778
|
headers: {
|
|
@@ -775,24 +784,24 @@ var PixelLabClient = class {
|
|
|
775
784
|
} catch (err) {
|
|
776
785
|
if (attempt < MAX_RETRIES) {
|
|
777
786
|
await sleep(backoffMs(attempt));
|
|
778
|
-
return this.request(
|
|
787
|
+
return this.request(path27, init, attempt + 1);
|
|
779
788
|
}
|
|
780
789
|
throw err;
|
|
781
790
|
}
|
|
782
791
|
if (!res.ok && shouldRetry(res.status) && attempt < MAX_RETRIES) {
|
|
783
792
|
const waitMs = retryAfterMs(res.headers.get("retry-after")) ?? backoffMs(attempt);
|
|
784
793
|
await sleep(waitMs);
|
|
785
|
-
return this.request(
|
|
794
|
+
return this.request(path27, init, attempt + 1);
|
|
786
795
|
}
|
|
787
796
|
const text = await res.text();
|
|
788
797
|
if (!res.ok) {
|
|
789
|
-
throw new PixelLabError(`${init?.method ?? "GET"} ${
|
|
798
|
+
throw new PixelLabError(`${init?.method ?? "GET"} ${path27} \u2192 ${res.status}`, res.status, text);
|
|
790
799
|
}
|
|
791
800
|
if (!text) return {};
|
|
792
801
|
try {
|
|
793
802
|
return JSON.parse(text);
|
|
794
803
|
} catch {
|
|
795
|
-
throw new Error(`${init?.method ?? "GET"} ${
|
|
804
|
+
throw new Error(`${init?.method ?? "GET"} ${path27} returned invalid JSON`);
|
|
796
805
|
}
|
|
797
806
|
}
|
|
798
807
|
async balance() {
|
|
@@ -1839,9 +1848,9 @@ var RetroDiffusionClient = class {
|
|
|
1839
1848
|
}
|
|
1840
1849
|
return balance;
|
|
1841
1850
|
}
|
|
1842
|
-
async call(
|
|
1851
|
+
async call(path27, init = {}) {
|
|
1843
1852
|
if (!this.token) throw new Error("RD_API_KEY is not set");
|
|
1844
|
-
const response = await this.request(`${this.baseUrl}${
|
|
1853
|
+
const response = await this.request(`${this.baseUrl}${path27}`, {
|
|
1845
1854
|
...init,
|
|
1846
1855
|
headers: {
|
|
1847
1856
|
"Content-Type": "application/json",
|
|
@@ -2644,10 +2653,10 @@ var import_node_fs2 = require("fs");
|
|
|
2644
2653
|
function sha256(data) {
|
|
2645
2654
|
return (0, import_node_crypto2.createHash)("sha256").update(data).digest("hex");
|
|
2646
2655
|
}
|
|
2647
|
-
async function sha256File(
|
|
2656
|
+
async function sha256File(path27) {
|
|
2648
2657
|
return new Promise((resolve, reject) => {
|
|
2649
2658
|
const hash = (0, import_node_crypto2.createHash)("sha256");
|
|
2650
|
-
const stream = (0, import_node_fs2.createReadStream)(
|
|
2659
|
+
const stream = (0, import_node_fs2.createReadStream)(path27);
|
|
2651
2660
|
stream.on("data", (chunk2) => hash.update(chunk2));
|
|
2652
2661
|
stream.on("error", reject);
|
|
2653
2662
|
stream.on("end", () => resolve(hash.digest("hex")));
|
|
@@ -4102,6 +4111,11 @@ ${issues}`);
|
|
|
4102
4111
|
unknownReferences.push(`assets.${assetId}.promptByStyle: unknown style "${styleId}"`);
|
|
4103
4112
|
}
|
|
4104
4113
|
}
|
|
4114
|
+
for (const styleId of Object.keys(asset.sourceByStyle)) {
|
|
4115
|
+
if (!styleIds.has(styleId)) {
|
|
4116
|
+
unknownReferences.push(`assets.${assetId}.sourceByStyle: unknown style "${styleId}"`);
|
|
4117
|
+
}
|
|
4118
|
+
}
|
|
4105
4119
|
if (asset.revision) {
|
|
4106
4120
|
if (!parsed.data.assets[asset.revision.from]) {
|
|
4107
4121
|
unknownReferences.push(
|
|
@@ -4366,7 +4380,7 @@ async function resolveSpecs(loaded, filter) {
|
|
|
4366
4380
|
}
|
|
4367
4381
|
} : {},
|
|
4368
4382
|
tags,
|
|
4369
|
-
source: asset.source,
|
|
4383
|
+
source: asset.sourceByStyle[styleId] ?? asset.source,
|
|
4370
4384
|
// Revision identity is attached after every dependency in this style
|
|
4371
4385
|
// has a concrete output target. Finalization below computes the hash.
|
|
4372
4386
|
specHash: ""
|
|
@@ -6971,10 +6985,10 @@ function isSha256Hash(value) {
|
|
|
6971
6985
|
function parseCache(value) {
|
|
6972
6986
|
return HashCacheSchema.parse(value);
|
|
6973
6987
|
}
|
|
6974
|
-
async function loadCache(
|
|
6975
|
-
if (!(0, import_node_fs11.existsSync)(
|
|
6988
|
+
async function loadCache(path27) {
|
|
6989
|
+
if (!(0, import_node_fs11.existsSync)(path27)) return { version: 1, hashes: {} };
|
|
6976
6990
|
try {
|
|
6977
|
-
const parsed = HashCacheSchema.safeParse(JSON.parse(await (0, import_promises9.readFile)(
|
|
6991
|
+
const parsed = HashCacheSchema.safeParse(JSON.parse(await (0, import_promises9.readFile)(path27, "utf8")));
|
|
6978
6992
|
if (!parsed.success) return { version: 1, hashes: {} };
|
|
6979
6993
|
return {
|
|
6980
6994
|
version: 1,
|
|
@@ -6986,18 +7000,18 @@ async function loadCache(path25) {
|
|
|
6986
7000
|
return { version: 1, hashes: {} };
|
|
6987
7001
|
}
|
|
6988
7002
|
}
|
|
6989
|
-
async function saveCache(
|
|
7003
|
+
async function saveCache(path27, cache) {
|
|
6990
7004
|
const sorted = {};
|
|
6991
7005
|
for (const key of Object.keys(cache.hashes).sort()) {
|
|
6992
7006
|
const hash = cache.hashes[key];
|
|
6993
7007
|
if (!isSha256Hash(hash)) throw new Error(`Refusing to cache invalid SHA-256 for ${key}`);
|
|
6994
7008
|
sorted[key] = hash;
|
|
6995
7009
|
}
|
|
6996
|
-
await (0, import_promises9.mkdir)(import_node_path14.default.dirname(import_node_path14.default.resolve(
|
|
6997
|
-
const tmp = `${
|
|
7010
|
+
await (0, import_promises9.mkdir)(import_node_path14.default.dirname(import_node_path14.default.resolve(path27)), { recursive: true });
|
|
7011
|
+
const tmp = `${path27}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
6998
7012
|
try {
|
|
6999
7013
|
await (0, import_promises9.writeFile)(tmp, JSON.stringify({ version: 1, hashes: sorted }, null, 2) + "\n");
|
|
7000
|
-
await (0, import_promises9.rename)(tmp,
|
|
7014
|
+
await (0, import_promises9.rename)(tmp, path27);
|
|
7001
7015
|
} finally {
|
|
7002
7016
|
await (0, import_promises9.rm)(tmp, { force: true });
|
|
7003
7017
|
}
|
|
@@ -7244,16 +7258,24 @@ function packStyle(lock, styleId, manifestDir, options = {}) {
|
|
|
7244
7258
|
}
|
|
7245
7259
|
const prefix = `${styleId}/`;
|
|
7246
7260
|
const entries = Object.entries(lock.entries).filter(([key]) => key.startsWith(prefix));
|
|
7247
|
-
|
|
7261
|
+
const sources = options.sources ?? {};
|
|
7262
|
+
if (!entries.length && !Object.keys(sources).length) {
|
|
7248
7263
|
throw new Error(
|
|
7249
7264
|
`No locked assets for style "${styleId}". Run \`pixelkiln gen --style ${styleId}\` first.`
|
|
7250
7265
|
);
|
|
7251
7266
|
}
|
|
7252
7267
|
const inputs = [];
|
|
7253
7268
|
const noOutput = [];
|
|
7269
|
+
const locked = /* @__PURE__ */ new Set();
|
|
7254
7270
|
for (const [key, entry] of entries) {
|
|
7255
7271
|
const id = key.slice(prefix.length);
|
|
7272
|
+
locked.add(id);
|
|
7256
7273
|
const outputs = resolveEntryOutputs(entry, id, manifestDir);
|
|
7274
|
+
const source = sources[id];
|
|
7275
|
+
if (source && outputs.length <= 1) {
|
|
7276
|
+
inputs.push({ id, path: import_node_path16.default.resolve(manifestDir, source) });
|
|
7277
|
+
continue;
|
|
7278
|
+
}
|
|
7257
7279
|
let selected = outputs;
|
|
7258
7280
|
if (options.outputRoles?.length) {
|
|
7259
7281
|
selected = outputs.filter(
|
|
@@ -7270,6 +7292,9 @@ function packStyle(lock, styleId, manifestDir, options = {}) {
|
|
|
7270
7292
|
}
|
|
7271
7293
|
for (const output of selected) inputs.push({ id: output.id, path: output.absolutePath });
|
|
7272
7294
|
}
|
|
7295
|
+
for (const [id, source] of Object.entries(sources)) {
|
|
7296
|
+
if (!locked.has(id)) inputs.push({ id, path: import_node_path16.default.resolve(manifestDir, source) });
|
|
7297
|
+
}
|
|
7273
7298
|
if (!inputs.length) {
|
|
7274
7299
|
throw new Error(`No readable sprites for style "${styleId}" \u2014 ${noOutput.length} skipped.`);
|
|
7275
7300
|
}
|
|
@@ -8515,6 +8540,7 @@ function buildManifest(name, styleId, generator, outDir, scanned) {
|
|
|
8515
8540
|
assets[asset.id] = {
|
|
8516
8541
|
prompt: "",
|
|
8517
8542
|
promptByStyle: {},
|
|
8543
|
+
sourceByStyle: {},
|
|
8518
8544
|
category: asset.category || void 0,
|
|
8519
8545
|
file: asset.file,
|
|
8520
8546
|
tags: [],
|
|
@@ -9202,9 +9228,9 @@ async function runPicker(provider, lock, lockPath, opts = {}) {
|
|
|
9202
9228
|
|
|
9203
9229
|
// src/gallery/snapshot.ts
|
|
9204
9230
|
var import_node_crypto6 = require("crypto");
|
|
9205
|
-
var
|
|
9206
|
-
var
|
|
9207
|
-
var
|
|
9231
|
+
var import_node_fs21 = require("fs");
|
|
9232
|
+
var import_promises20 = require("fs/promises");
|
|
9233
|
+
var import_node_path25 = __toESM(require("path"), 1);
|
|
9208
9234
|
|
|
9209
9235
|
// src/workspace.ts
|
|
9210
9236
|
var import_promises17 = require("fs/promises");
|
|
@@ -9340,6 +9366,10 @@ function validateWorkspace(ws, dir) {
|
|
|
9340
9366
|
}
|
|
9341
9367
|
|
|
9342
9368
|
// src/gallery/edit.ts
|
|
9369
|
+
var import_node_path24 = __toESM(require("path"), 1);
|
|
9370
|
+
var import_zod8 = require("zod");
|
|
9371
|
+
|
|
9372
|
+
// src/manifest-edit.ts
|
|
9343
9373
|
var import_promises18 = require("fs/promises");
|
|
9344
9374
|
var import_node_path22 = __toESM(require("path"), 1);
|
|
9345
9375
|
var import_zod7 = require("zod");
|
|
@@ -9386,6 +9416,22 @@ var ManifestEditSchema = import_zod7.z.discriminatedUnion("action", [
|
|
|
9386
9416
|
expectedSha256: HexSha,
|
|
9387
9417
|
asset: NewAssetSchema
|
|
9388
9418
|
}).strict(),
|
|
9419
|
+
import_zod7.z.object({
|
|
9420
|
+
action: import_zod7.z.literal("set-source"),
|
|
9421
|
+
project: import_zod7.z.string().min(1).optional(),
|
|
9422
|
+
assetId: import_zod7.z.string().min(1),
|
|
9423
|
+
styleId: import_zod7.z.string().min(1),
|
|
9424
|
+
/** Manifest-relative path of the art that stands in for this asset in this style. */
|
|
9425
|
+
source: import_zod7.z.string().min(1),
|
|
9426
|
+
expectedSha256: HexSha
|
|
9427
|
+
}).strict(),
|
|
9428
|
+
import_zod7.z.object({
|
|
9429
|
+
action: import_zod7.z.literal("clear-source"),
|
|
9430
|
+
project: import_zod7.z.string().min(1).optional(),
|
|
9431
|
+
assetId: import_zod7.z.string().min(1),
|
|
9432
|
+
styleId: import_zod7.z.string().min(1),
|
|
9433
|
+
expectedSha256: HexSha
|
|
9434
|
+
}).strict(),
|
|
9389
9435
|
import_zod7.z.object({
|
|
9390
9436
|
action: import_zod7.z.literal("patch-style"),
|
|
9391
9437
|
project: import_zod7.z.string().min(1).optional(),
|
|
@@ -9398,7 +9444,11 @@ var ManifestEditSchema = import_zod7.z.discriminatedUnion("action", [
|
|
|
9398
9444
|
promptPrefix: import_zod7.z.string().optional(),
|
|
9399
9445
|
promptSuffix: import_zod7.z.string().optional(),
|
|
9400
9446
|
/** `#rrggbb` values; null or empty clears the style's own palette. */
|
|
9401
|
-
palette: import_zod7.z.array(import_zod7.z.string().regex(/^#?[0-9a-f]{6}$/i, "expected a six-digit hex colour")).max(256).nullable().optional()
|
|
9447
|
+
palette: import_zod7.z.array(import_zod7.z.string().regex(/^#?[0-9a-f]{6}$/i, "expected a six-digit hex colour")).max(256).nullable().optional(),
|
|
9448
|
+
/** Provider view name (PixelLab: low top-down, high top-down, side); empty clears. */
|
|
9449
|
+
view: import_zod7.z.string().max(64).nullable().optional(),
|
|
9450
|
+
/** pixflux only; null clears the style's own value. */
|
|
9451
|
+
noBackground: import_zod7.z.boolean().nullable().optional()
|
|
9402
9452
|
}).strict().refine((patch) => Object.keys(patch).length > 0, { message: "nothing to change" })
|
|
9403
9453
|
}).strict()
|
|
9404
9454
|
]);
|
|
@@ -9432,8 +9482,38 @@ function setOrDelete(target, key, value) {
|
|
|
9432
9482
|
if (value === null || value === void 0) delete target[key];
|
|
9433
9483
|
else target[key] = value;
|
|
9434
9484
|
}
|
|
9485
|
+
function assetStyles(raw, asset) {
|
|
9486
|
+
const own = Array.isArray(asset.styles) ? asset.styles : [];
|
|
9487
|
+
return own.length ? own : Object.keys(raw.styles ?? {});
|
|
9488
|
+
}
|
|
9435
9489
|
function applyEdit(raw, edit) {
|
|
9436
9490
|
raw.assets ??= {};
|
|
9491
|
+
if (edit.action === "set-source" || edit.action === "clear-source") {
|
|
9492
|
+
const asset2 = Object.hasOwn(raw.assets, edit.assetId) ? raw.assets[edit.assetId] : void 0;
|
|
9493
|
+
if (!asset2) throw new ManifestEditError(`asset "${edit.assetId}" is not declared by the manifest`);
|
|
9494
|
+
if (!raw.styles || !Object.hasOwn(raw.styles, edit.styleId)) {
|
|
9495
|
+
throw new ManifestEditError(`unknown style "${edit.styleId}"`);
|
|
9496
|
+
}
|
|
9497
|
+
if (!assetStyles(raw, asset2).includes(edit.styleId)) {
|
|
9498
|
+
throw new ManifestEditError(`asset "${edit.assetId}" is not in style "${edit.styleId}"`);
|
|
9499
|
+
}
|
|
9500
|
+
const single = assetStyles(raw, asset2).length === 1;
|
|
9501
|
+
const byStyle = { ...asset2.sourceByStyle ?? {} };
|
|
9502
|
+
if (edit.action === "set-source") {
|
|
9503
|
+
if (single) {
|
|
9504
|
+
asset2.source = edit.source;
|
|
9505
|
+
delete byStyle[edit.styleId];
|
|
9506
|
+
} else {
|
|
9507
|
+
byStyle[edit.styleId] = edit.source;
|
|
9508
|
+
}
|
|
9509
|
+
} else {
|
|
9510
|
+
delete byStyle[edit.styleId];
|
|
9511
|
+
if (single) delete asset2.source;
|
|
9512
|
+
}
|
|
9513
|
+
if (Object.keys(byStyle).length) asset2.sourceByStyle = byStyle;
|
|
9514
|
+
else delete asset2.sourceByStyle;
|
|
9515
|
+
return;
|
|
9516
|
+
}
|
|
9437
9517
|
if (edit.action === "patch-style") {
|
|
9438
9518
|
const style = raw.styles && Object.hasOwn(raw.styles, edit.styleId) ? raw.styles[edit.styleId] : void 0;
|
|
9439
9519
|
if (!style) throw new ManifestEditError(`style "${edit.styleId}" is not declared by the manifest`);
|
|
@@ -9456,6 +9536,8 @@ function applyEdit(raw, edit) {
|
|
|
9456
9536
|
const colors = (patch2.palette ?? []).map((color) => "#" + color.replace(/^#/, "").toLowerCase());
|
|
9457
9537
|
setOrDelete(style, "palette", colors.length ? colors : null);
|
|
9458
9538
|
}
|
|
9539
|
+
if (patch2.view !== void 0) setOrDelete(style, "view", patch2.view?.trim() || null);
|
|
9540
|
+
if (patch2.noBackground !== void 0) setOrDelete(style, "noBackground", patch2.noBackground);
|
|
9459
9541
|
return;
|
|
9460
9542
|
}
|
|
9461
9543
|
if (edit.action === "add-asset") {
|
|
@@ -9535,10 +9617,134 @@ async function applyManifestEdit(manifestPath, edit) {
|
|
|
9535
9617
|
}
|
|
9536
9618
|
return { manifestPath: absolute, sha256: sha256(next), changed: true };
|
|
9537
9619
|
}
|
|
9620
|
+
|
|
9621
|
+
// src/pipeline/hand-edit.ts
|
|
9622
|
+
var import_node_child_process3 = require("child_process");
|
|
9623
|
+
var import_node_fs20 = require("fs");
|
|
9624
|
+
var import_promises19 = require("fs/promises");
|
|
9625
|
+
var import_node_path23 = __toESM(require("path"), 1);
|
|
9626
|
+
var HAND_EDIT_DIR = "edits";
|
|
9627
|
+
function handEditPath(loaded, spec) {
|
|
9628
|
+
const style = loaded.manifest.styles[spec.styleId];
|
|
9629
|
+
const outDir = import_node_path23.default.resolve(loaded.root, style?.outDir ?? ".");
|
|
9630
|
+
const relative = import_node_path23.default.relative(outDir, spec.outFile);
|
|
9631
|
+
const inside = relative && !relative.startsWith("..") && !import_node_path23.default.isAbsolute(relative);
|
|
9632
|
+
const file = inside ? relative : import_node_path23.default.basename(spec.outFile);
|
|
9633
|
+
return import_node_path23.default.join(outDir, HAND_EDIT_DIR, file.replace(/\.gif$/i, ".png"));
|
|
9634
|
+
}
|
|
9635
|
+
function handEditBase(spec, lock) {
|
|
9636
|
+
const entry = lock.entries[lockKey(spec.styleId, spec.assetId)];
|
|
9637
|
+
if (entry) {
|
|
9638
|
+
if (entry.outputs.length !== 1) return null;
|
|
9639
|
+
const primary = primaryOutput(entry);
|
|
9640
|
+
if (!primary || primary.mediaType && primary.mediaType !== "image/png") return null;
|
|
9641
|
+
const file = currentEntryOutputPath(entry, spec, 0);
|
|
9642
|
+
return (0, import_node_fs20.existsSync)(file) ? file : null;
|
|
9643
|
+
}
|
|
9644
|
+
return (0, import_node_fs20.existsSync)(spec.outFile) ? spec.outFile : null;
|
|
9645
|
+
}
|
|
9646
|
+
async function startHandEdit(loaded, lock, spec, opts = {}) {
|
|
9647
|
+
const entry = lock.entries[lockKey(spec.styleId, spec.assetId)];
|
|
9648
|
+
if (entry && entry.outputs.length > 1) {
|
|
9649
|
+
throw new ManifestEditError(
|
|
9650
|
+
`${spec.styleId}/${spec.assetId} has ${entry.outputs.length} outputs; hand edits currently cover single-image assets`
|
|
9651
|
+
);
|
|
9652
|
+
}
|
|
9653
|
+
const existing = spec.source ? import_node_path23.default.resolve(loaded.root, spec.source) : null;
|
|
9654
|
+
const editPath = existing ?? handEditPath(loaded, spec);
|
|
9655
|
+
let created = false;
|
|
9656
|
+
if (!(0, import_node_fs20.existsSync)(editPath)) {
|
|
9657
|
+
const base = handEditBase(spec, lock);
|
|
9658
|
+
if (!base) {
|
|
9659
|
+
throw new ManifestEditError(
|
|
9660
|
+
`${spec.styleId}/${spec.assetId} has no generated PNG on disk to start from; generate or restore it first`
|
|
9661
|
+
);
|
|
9662
|
+
}
|
|
9663
|
+
await (0, import_promises19.mkdir)(import_node_path23.default.dirname(editPath), { recursive: true });
|
|
9664
|
+
await (0, import_promises19.copyFile)(base, editPath);
|
|
9665
|
+
created = true;
|
|
9666
|
+
}
|
|
9667
|
+
const source = portableOutputPath(editPath, loaded.root);
|
|
9668
|
+
let declared = false;
|
|
9669
|
+
let manifestSha256 = opts.expectedSha256 ?? await sha256File(loaded.path);
|
|
9670
|
+
if (spec.source !== source) {
|
|
9671
|
+
const result = await applyManifestEdit(loaded.path, {
|
|
9672
|
+
action: "set-source",
|
|
9673
|
+
assetId: spec.assetId,
|
|
9674
|
+
styleId: spec.styleId,
|
|
9675
|
+
source,
|
|
9676
|
+
expectedSha256: manifestSha256
|
|
9677
|
+
});
|
|
9678
|
+
declared = result.changed;
|
|
9679
|
+
manifestSha256 = result.sha256;
|
|
9680
|
+
}
|
|
9681
|
+
return { editPath, source, created, declared, manifestSha256 };
|
|
9682
|
+
}
|
|
9683
|
+
async function detachHandEdit(loaded, spec, opts = {}) {
|
|
9684
|
+
if (!spec.source) {
|
|
9685
|
+
return { manifestSha256: opts.expectedSha256 ?? await sha256File(loaded.path), changed: false };
|
|
9686
|
+
}
|
|
9687
|
+
const result = await applyManifestEdit(loaded.path, {
|
|
9688
|
+
action: "clear-source",
|
|
9689
|
+
assetId: spec.assetId,
|
|
9690
|
+
styleId: spec.styleId,
|
|
9691
|
+
expectedSha256: opts.expectedSha256 ?? await sha256File(loaded.path)
|
|
9692
|
+
});
|
|
9693
|
+
return { manifestSha256: result.sha256, changed: result.changed };
|
|
9694
|
+
}
|
|
9695
|
+
function openInEditor(file, editor = process.env.PIXELKILN_EDITOR) {
|
|
9696
|
+
const command = editor?.trim() ? editor.trim().split(/\s+/) : process.platform === "darwin" ? ["open"] : process.platform === "win32" ? ["cmd", "/c", "start", ""] : ["xdg-open"];
|
|
9697
|
+
const [program, ...args] = command;
|
|
9698
|
+
const child = (0, import_node_child_process3.spawn)(program, [...args, file], { stdio: "ignore", detached: true });
|
|
9699
|
+
child.on("error", () => {
|
|
9700
|
+
});
|
|
9701
|
+
child.unref();
|
|
9702
|
+
return command.join(" ");
|
|
9703
|
+
}
|
|
9704
|
+
|
|
9705
|
+
// src/gallery/edit.ts
|
|
9706
|
+
var HandEditRequestSchema = import_zod8.z.discriminatedUnion("action", [
|
|
9707
|
+
import_zod8.z.object({
|
|
9708
|
+
action: import_zod8.z.literal("start-edit"),
|
|
9709
|
+
project: import_zod8.z.string().min(1).optional(),
|
|
9710
|
+
assetId: import_zod8.z.string().min(1),
|
|
9711
|
+
styleId: import_zod8.z.string().min(1),
|
|
9712
|
+
expectedSha256: import_zod8.z.string().regex(/^[0-9a-f]{64}$/),
|
|
9713
|
+
/** Also launch the author's editor on the file. */
|
|
9714
|
+
open: import_zod8.z.boolean().optional()
|
|
9715
|
+
}).strict(),
|
|
9716
|
+
import_zod8.z.object({
|
|
9717
|
+
action: import_zod8.z.literal("detach-edit"),
|
|
9718
|
+
project: import_zod8.z.string().min(1).optional(),
|
|
9719
|
+
assetId: import_zod8.z.string().min(1),
|
|
9720
|
+
styleId: import_zod8.z.string().min(1),
|
|
9721
|
+
expectedSha256: import_zod8.z.string().regex(/^[0-9a-f]{64}$/)
|
|
9722
|
+
}).strict()
|
|
9723
|
+
]);
|
|
9538
9724
|
function createGalleryEditHandler(opts) {
|
|
9539
9725
|
const log = opts.onProgress ?? (() => {
|
|
9540
9726
|
});
|
|
9541
9727
|
return async (body) => {
|
|
9728
|
+
const handEdit = HandEditRequestSchema.safeParse(body);
|
|
9729
|
+
if (handEdit.success) {
|
|
9730
|
+
if (!opts.loadProject) throw new ManifestEditError("hand edits are not available in this gallery");
|
|
9731
|
+
const request = handEdit.data;
|
|
9732
|
+
const ctx = await opts.loadProject(request.project);
|
|
9733
|
+
const spec = ctx.specs.find((candidate) => lockKey(candidate.styleId, candidate.assetId) === lockKey(request.styleId, request.assetId));
|
|
9734
|
+
if (!spec) throw new ManifestEditError(`"${request.styleId}/${request.assetId}" is not declared by the manifest`);
|
|
9735
|
+
if (request.action === "start-edit") {
|
|
9736
|
+
const started = await startHandEdit(ctx.loaded, ctx.lock, spec, { expectedSha256: request.expectedSha256 });
|
|
9737
|
+
log(` hand edit ${started.created ? "created" : "found"}: ${started.source}${started.declared ? " (declared in the manifest)" : ""}`);
|
|
9738
|
+
if (request.open) {
|
|
9739
|
+
const command = (opts.openEditor ?? openInEditor)(started.editPath);
|
|
9740
|
+
log(` opened with: ${command}`);
|
|
9741
|
+
}
|
|
9742
|
+
} else {
|
|
9743
|
+
const result2 = await detachHandEdit(ctx.loaded, spec, { expectedSha256: request.expectedSha256 });
|
|
9744
|
+
if (result2.changed) log(` hand edit detached: ${request.styleId}/${request.assetId} (file kept)`);
|
|
9745
|
+
}
|
|
9746
|
+
return opts.reload();
|
|
9747
|
+
}
|
|
9542
9748
|
const parsed = ManifestEditSchema.safeParse(body);
|
|
9543
9749
|
if (!parsed.success) {
|
|
9544
9750
|
throw new ManifestEditError(
|
|
@@ -9553,9 +9759,9 @@ function createGalleryEditHandler(opts) {
|
|
|
9553
9759
|
}
|
|
9554
9760
|
const result = await applyManifestEdit(manifestPath, parsed.data);
|
|
9555
9761
|
if (result.changed) {
|
|
9556
|
-
const relative =
|
|
9762
|
+
const relative = import_node_path24.default.relative(process.cwd(), result.manifestPath);
|
|
9557
9763
|
const shown = !relative || relative.startsWith("..") ? result.manifestPath : relative;
|
|
9558
|
-
const subject = parsed.data.action === "patch-style" ? `style ${parsed.data.styleId}` : parsed.data.assetId;
|
|
9764
|
+
const subject = parsed.data.action === "patch-style" ? `style ${parsed.data.styleId}` : parsed.data.action === "set-source" || parsed.data.action === "clear-source" ? `${parsed.data.styleId}/${parsed.data.assetId} source` : parsed.data.assetId;
|
|
9559
9765
|
log(` manifest edited: ${parsed.data.action === "add-asset" ? "added" : "changed"} ${subject} in ${shown}`);
|
|
9560
9766
|
}
|
|
9561
9767
|
return opts.reload();
|
|
@@ -9564,7 +9770,7 @@ function createGalleryEditHandler(opts) {
|
|
|
9564
9770
|
|
|
9565
9771
|
// src/gallery/snapshot.ts
|
|
9566
9772
|
function galleryMediaId(absolutePath) {
|
|
9567
|
-
return (0, import_node_crypto6.createHash)("sha256").update(
|
|
9773
|
+
return (0, import_node_crypto6.createHash)("sha256").update(import_node_path25.default.resolve(absolutePath)).digest("hex").slice(0, 24);
|
|
9568
9774
|
}
|
|
9569
9775
|
function galleryMediaRoute(id) {
|
|
9570
9776
|
return `/media/${id}`;
|
|
@@ -9589,23 +9795,24 @@ function metadataFps(entry) {
|
|
|
9589
9795
|
}
|
|
9590
9796
|
return null;
|
|
9591
9797
|
}
|
|
9592
|
-
async function
|
|
9798
|
+
async function fileInfo(absolutePath) {
|
|
9593
9799
|
try {
|
|
9594
|
-
const info = await (0,
|
|
9595
|
-
return info.isFile() ? info.size : null;
|
|
9800
|
+
const info = await (0, import_promises20.stat)(absolutePath);
|
|
9801
|
+
return info.isFile() ? { bytes: info.size, modifiedAt: info.mtime.toISOString() } : null;
|
|
9596
9802
|
} catch {
|
|
9597
9803
|
return null;
|
|
9598
9804
|
}
|
|
9599
9805
|
}
|
|
9600
9806
|
async function describeOutput(media, root, absolutePath, recorded = {}) {
|
|
9601
|
-
const
|
|
9602
|
-
const exists =
|
|
9807
|
+
const info = await fileInfo(absolutePath);
|
|
9808
|
+
const exists = info !== null;
|
|
9603
9809
|
const mediaType = recorded.mediaType ?? mediaTypeFromExtension(absolutePath);
|
|
9604
9810
|
let url = null;
|
|
9605
9811
|
if (exists && mediaType) {
|
|
9606
9812
|
const id = galleryMediaId(absolutePath);
|
|
9607
9813
|
media.set(id, { path: absolutePath, contentType: mediaType });
|
|
9608
|
-
|
|
9814
|
+
const version = recorded.sha256 ?? `${info.bytes}-${Date.parse(info.modifiedAt).toString(36)}`;
|
|
9815
|
+
url = `${galleryMediaRoute(id)}?v=${version.slice(0, 24)}`;
|
|
9609
9816
|
}
|
|
9610
9817
|
return {
|
|
9611
9818
|
path: portableOutputPath(absolutePath, root),
|
|
@@ -9614,17 +9821,18 @@ async function describeOutput(media, root, absolutePath, recorded = {}) {
|
|
|
9614
9821
|
role: recorded.role ?? null,
|
|
9615
9822
|
mediaType,
|
|
9616
9823
|
exists,
|
|
9617
|
-
bytes,
|
|
9824
|
+
bytes: info?.bytes ?? null,
|
|
9825
|
+
modifiedAt: info?.modifiedAt ?? null,
|
|
9618
9826
|
url
|
|
9619
9827
|
};
|
|
9620
9828
|
}
|
|
9621
9829
|
async function describeQuality(media, root, inspection) {
|
|
9622
9830
|
const outputPaths = inspection.outputs ?? [inspection.output];
|
|
9623
9831
|
const outputs = await Promise.all(
|
|
9624
|
-
outputPaths.map((file) => describeOutput(media, root,
|
|
9832
|
+
outputPaths.map((file) => describeOutput(media, root, import_node_path25.default.resolve(root, file)))
|
|
9625
9833
|
);
|
|
9626
|
-
const recordPath =
|
|
9627
|
-
const recordExists = (0,
|
|
9834
|
+
const recordPath = import_node_path25.default.resolve(root, inspection.record);
|
|
9835
|
+
const recordExists = (0, import_node_fs21.existsSync)(recordPath);
|
|
9628
9836
|
let options = null;
|
|
9629
9837
|
let check = null;
|
|
9630
9838
|
if (recordExists) {
|
|
@@ -9643,7 +9851,7 @@ async function describeQuality(media, root, inspection) {
|
|
|
9643
9851
|
return {
|
|
9644
9852
|
state: inspection.state,
|
|
9645
9853
|
reason: inspection.reason,
|
|
9646
|
-
output: portableOutputPath(
|
|
9854
|
+
output: portableOutputPath(import_node_path25.default.resolve(root, inspection.output), root),
|
|
9647
9855
|
record: portableOutputPath(recordPath, root),
|
|
9648
9856
|
recordExists,
|
|
9649
9857
|
outputs,
|
|
@@ -9680,7 +9888,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
9680
9888
|
const media = /* @__PURE__ */ new Map();
|
|
9681
9889
|
const plan = await buildPlan(specs, lock);
|
|
9682
9890
|
const items = [];
|
|
9683
|
-
const manifestText = await (0,
|
|
9891
|
+
const manifestText = await (0, import_promises20.readFile)(loaded.path, "utf8");
|
|
9684
9892
|
const manifestSha256 = sha256(manifestText);
|
|
9685
9893
|
let rawStyles = {};
|
|
9686
9894
|
try {
|
|
@@ -9693,15 +9901,24 @@ async function buildGallerySnapshot(opts) {
|
|
|
9693
9901
|
const entry = lock.entries[key];
|
|
9694
9902
|
const asset = loaded.manifest.assets[spec.assetId] ?? null;
|
|
9695
9903
|
let outputs;
|
|
9904
|
+
let edit = null;
|
|
9905
|
+
let editStatus = null;
|
|
9696
9906
|
if (entry && entry.outputs.length) {
|
|
9697
9907
|
outputs = await Promise.all(
|
|
9698
9908
|
entry.outputs.map(
|
|
9699
9909
|
(output, index) => describeOutput(media, root, currentEntryOutputPath(entry, spec, index), output)
|
|
9700
9910
|
)
|
|
9701
9911
|
);
|
|
9912
|
+
if (spec.source) {
|
|
9913
|
+
const editPath = import_node_path25.default.resolve(root, spec.source);
|
|
9914
|
+
const editSha = (0, import_node_fs21.existsSync)(editPath) ? await sha256File(editPath) : null;
|
|
9915
|
+
edit = await describeOutput(media, root, editPath, { sha256: editSha });
|
|
9916
|
+
const generated = outputs[0];
|
|
9917
|
+
editStatus = !edit.exists ? "missing" : generated && editSha === generated.sha256 ? "same" : generated?.modifiedAt && edit.modifiedAt && generated.modifiedAt > edit.modifiedAt && generated.exists ? "regenerated-since" : "edited";
|
|
9918
|
+
}
|
|
9702
9919
|
} else if (spec.source) {
|
|
9703
|
-
outputs = [await describeOutput(media, root,
|
|
9704
|
-
} else if (!entry && (0,
|
|
9920
|
+
outputs = [await describeOutput(media, root, import_node_path25.default.resolve(root, spec.source))];
|
|
9921
|
+
} else if (!entry && (0, import_node_fs21.existsSync)(spec.outFile)) {
|
|
9705
9922
|
outputs = [await describeOutput(media, root, spec.outFile)];
|
|
9706
9923
|
} else {
|
|
9707
9924
|
outputs = [];
|
|
@@ -9745,6 +9962,8 @@ async function buildGallerySnapshot(opts) {
|
|
|
9745
9962
|
providerMetadata: entry?.providerMetadata ?? {},
|
|
9746
9963
|
asset,
|
|
9747
9964
|
source: spec.source ?? null,
|
|
9965
|
+
edit,
|
|
9966
|
+
editStatus,
|
|
9748
9967
|
tags: spec.tags,
|
|
9749
9968
|
category: asset?.category ?? null
|
|
9750
9969
|
});
|
|
@@ -9797,6 +10016,8 @@ async function buildGallerySnapshot(opts) {
|
|
|
9797
10016
|
providerMetadata: entry.providerMetadata ?? {},
|
|
9798
10017
|
asset: null,
|
|
9799
10018
|
source: null,
|
|
10019
|
+
edit: null,
|
|
10020
|
+
editStatus: null,
|
|
9800
10021
|
tags: [],
|
|
9801
10022
|
category: null
|
|
9802
10023
|
});
|
|
@@ -9835,6 +10056,8 @@ async function buildGallerySnapshot(opts) {
|
|
|
9835
10056
|
promptPrefix: style?.promptPrefix ?? "",
|
|
9836
10057
|
promptSuffix: style?.promptSuffix ?? "",
|
|
9837
10058
|
palette: style?.palette ?? [],
|
|
10059
|
+
view: style?.view ?? null,
|
|
10060
|
+
noBackground: style?.noBackground ?? true,
|
|
9838
10061
|
quality: Boolean(style?.quality),
|
|
9839
10062
|
tags: style?.tags ?? [],
|
|
9840
10063
|
extends: typeof rawStyle?.extends === "string" ? rawStyle.extends : null,
|
|
@@ -9862,7 +10085,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
9862
10085
|
id: loaded.manifest.name,
|
|
9863
10086
|
name: loaded.manifest.name,
|
|
9864
10087
|
manifest: loaded.path,
|
|
9865
|
-
lock:
|
|
10088
|
+
lock: import_node_path25.default.resolve(opts.lockPath),
|
|
9866
10089
|
root,
|
|
9867
10090
|
provider: loaded.manifest.provider,
|
|
9868
10091
|
account: null,
|
|
@@ -9887,7 +10110,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
9887
10110
|
return { snapshot, media };
|
|
9888
10111
|
}
|
|
9889
10112
|
async function buildWorkspaceGallerySnapshot(opts) {
|
|
9890
|
-
const dir =
|
|
10113
|
+
const dir = import_node_path25.default.dirname(import_node_path25.default.resolve(opts.workspacePath));
|
|
9891
10114
|
const media = /* @__PURE__ */ new Map();
|
|
9892
10115
|
const projects = [];
|
|
9893
10116
|
const items = [];
|
|
@@ -9930,11 +10153,11 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
9930
10153
|
id: project.id,
|
|
9931
10154
|
name: loaded.manifest.name,
|
|
9932
10155
|
manifest: manifestPath,
|
|
9933
|
-
lock:
|
|
10156
|
+
lock: import_node_path25.default.resolve(lockPath),
|
|
9934
10157
|
root: loaded.root,
|
|
9935
10158
|
provider: loaded.manifest.provider,
|
|
9936
10159
|
account: project.account ?? null,
|
|
9937
|
-
manifestSha256: manifestSha256 ?? sha256(await (0,
|
|
10160
|
+
manifestSha256: manifestSha256 ?? sha256(await (0, import_promises20.readFile)(manifestPath, "utf8")),
|
|
9938
10161
|
entries: Object.keys(lock.entries).length,
|
|
9939
10162
|
items: projectItems.length,
|
|
9940
10163
|
spendByUnit: spendByUnit(lock),
|
|
@@ -9945,8 +10168,8 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
9945
10168
|
id: project.id,
|
|
9946
10169
|
name: project.id,
|
|
9947
10170
|
manifest: manifestPath,
|
|
9948
|
-
lock:
|
|
9949
|
-
root:
|
|
10171
|
+
lock: import_node_path25.default.resolve(lockPath),
|
|
10172
|
+
root: import_node_path25.default.dirname(manifestPath),
|
|
9950
10173
|
provider: project.provider,
|
|
9951
10174
|
account: project.account ?? null,
|
|
9952
10175
|
manifestSha256: null,
|
|
@@ -9972,7 +10195,7 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
9972
10195
|
version: 1,
|
|
9973
10196
|
generatedAt: (opts.now ?? (() => /* @__PURE__ */ new Date()))().toISOString(),
|
|
9974
10197
|
project: null,
|
|
9975
|
-
workspace: { path:
|
|
10198
|
+
workspace: { path: import_node_path25.default.resolve(opts.workspacePath), dir, projects },
|
|
9976
10199
|
filter,
|
|
9977
10200
|
totals: {
|
|
9978
10201
|
entries: projects.reduce((sum, project) => sum + project.entries, 0),
|
|
@@ -10259,6 +10482,14 @@ function renderGallery(snapshot, opts = {}) {
|
|
|
10259
10482
|
.tray > button { padding:5px 11px; font-size:12.5px; }
|
|
10260
10483
|
@media (max-width: 900px) { .sheet.review-host, .sheet.compare-host { width:100vw; } }
|
|
10261
10484
|
.drawer .gen { display:flex; gap:8px; flex-wrap:wrap; margin-top:12px; }
|
|
10485
|
+
.pair { display:grid; grid-template-columns:1fr 1fr; gap:8px; margin:8px 0 10px; }
|
|
10486
|
+
.pair figure { margin:0; border:1px solid var(--line); background:var(--panel-deep); background-image:var(--checker);
|
|
10487
|
+
background-size:12px 12px; background-position:0 0,6px 6px; display:grid; place-items:center; padding:8px; min-height:96px; }
|
|
10488
|
+
.pair figure img { image-rendering:pixelated; display:block; max-width:100%; }
|
|
10489
|
+
.pair figcaption { font:11px/1.4 var(--mono); color:var(--dim); background:var(--panel); padding:2px 6px; margin-top:6px; }
|
|
10490
|
+
.card .pen { position:absolute; right:6px; top:6px; background:var(--panel); color:var(--accent-soft); border:1px solid var(--accent);
|
|
10491
|
+
font:700 11px/1 var(--mono); padding:3px 5px; }
|
|
10492
|
+
.hand-actions { display:flex; gap:8px; flex-wrap:wrap; margin-top:8px; }
|
|
10262
10493
|
.drawer .gen button.primary { padding:7px 14px; }
|
|
10263
10494
|
@media (max-width: 720px) {
|
|
10264
10495
|
.bar, .totals, .chips, .style, footer { padding-inline:14px; }
|
|
@@ -10309,6 +10540,7 @@ function renderGallery(snapshot, opts = {}) {
|
|
|
10309
10540
|
</footer>
|
|
10310
10541
|
<div id="drawer-host"></div>
|
|
10311
10542
|
<div id="dialog-host"></div>
|
|
10543
|
+
<datalist id="view-options"><option value="low top-down"><option value="high top-down"><option value="side"><option value="sidescroller"></datalist>
|
|
10312
10544
|
<script>
|
|
10313
10545
|
const INITIAL = ${data};
|
|
10314
10546
|
const SESSION = ${session};
|
|
@@ -10704,7 +10936,8 @@ function clearFilters() {
|
|
|
10704
10936
|
|
|
10705
10937
|
function thumb(item) {
|
|
10706
10938
|
const cell = el('div', 'cell');
|
|
10707
|
-
|
|
10939
|
+
// A hand edit is what ships, so it is what the card shows.
|
|
10940
|
+
const shown = item.edit && item.edit.url && item.editStatus === 'edited' ? [item.edit] : item.outputs.filter((o) => o.url);
|
|
10708
10941
|
if (!shown.length) {
|
|
10709
10942
|
cell.classList.add('none');
|
|
10710
10943
|
cell.append(el('span', null, item.state === 'missing' ? 'not generated yet'
|
|
@@ -10755,6 +10988,9 @@ function card(item) {
|
|
|
10755
10988
|
body.append(meta);
|
|
10756
10989
|
const cell = thumb(item);
|
|
10757
10990
|
if (slot >= 0) cell.append(el('span', 'slot', String(slot + 1)));
|
|
10991
|
+
if (item.editStatus === 'edited' || item.editStatus === 'regenerated-since') {
|
|
10992
|
+
const pen = el('span', 'pen', '\u270E'); pen.title = 'hand-edited'; cell.append(pen);
|
|
10993
|
+
}
|
|
10758
10994
|
c.append(cell, body);
|
|
10759
10995
|
c.onclick = (e) => { if (e.shiftKey) toggleCompare(item.id); else openItem(item.id); };
|
|
10760
10996
|
return c;
|
|
@@ -11081,6 +11317,78 @@ function openCompare() {
|
|
|
11081
11317
|
close.focus({ preventScroll: true });
|
|
11082
11318
|
}
|
|
11083
11319
|
|
|
11320
|
+
// ---- hand edits -------------------------------------------------------------
|
|
11321
|
+
|
|
11322
|
+
const EDIT_STATUS_TEXT = {
|
|
11323
|
+
same: 'A copy of the generated art, not changed yet. Open it in your editor and save.',
|
|
11324
|
+
edited: 'Differs from the generated art. mount and pack place this file; the generated file stays as the record.',
|
|
11325
|
+
'regenerated-since': 'The generated art changed after this edit was saved \u2014 the edit is based on an older generation.',
|
|
11326
|
+
missing: 'The declared file is not on disk.',
|
|
11327
|
+
};
|
|
11328
|
+
async function postHandEdit(item, action, extra) {
|
|
11329
|
+
const pr = projectOf(item);
|
|
11330
|
+
const body = { action, assetId: item.assetId, styleId: item.styleId, expectedSha256: pr.manifestSha256, ...extra };
|
|
11331
|
+
if (item.project) body.project = item.project;
|
|
11332
|
+
snap = await postEdit(body);
|
|
11333
|
+
}
|
|
11334
|
+
function handEditSection(item) {
|
|
11335
|
+
const canStart = EDITABLE && item.declared && item.asset && projectOf(item)?.manifestSha256 &&
|
|
11336
|
+
item.outputs.length === 1 && item.outputs[0].exists && item.outputs[0].mediaType === 'image/png';
|
|
11337
|
+
if (!item.edit && !canStart) return null;
|
|
11338
|
+
const s = el('section', 'meta');
|
|
11339
|
+
s.append(el('h3', null, 'Hand edit'));
|
|
11340
|
+
if (item.edit) {
|
|
11341
|
+
const pair = el('div', 'pair');
|
|
11342
|
+
const shared = displayScale(item.width, item.height, 200, 160);
|
|
11343
|
+
const fig = (out, label) => {
|
|
11344
|
+
const f = el('figure');
|
|
11345
|
+
if (out && out.url) {
|
|
11346
|
+
const img = el('img'); img.src = out.url; img.alt = label;
|
|
11347
|
+
if (shared >= 1) { img.width = item.width * shared; img.height = item.height * shared; }
|
|
11348
|
+
f.append(img);
|
|
11349
|
+
} else f.append(el('span', 'state-dim', 'not on disk'));
|
|
11350
|
+
f.append(el('figcaption', null, label));
|
|
11351
|
+
return f;
|
|
11352
|
+
};
|
|
11353
|
+
pair.append(fig(item.outputs[0], 'generated'), fig(item.edit, 'edit'));
|
|
11354
|
+
s.append(pair);
|
|
11355
|
+
const dl = el('dl');
|
|
11356
|
+
const tone = item.editStatus === 'edited' ? 'ok' : item.editStatus === 'same' ? 'dim' : 'warn';
|
|
11357
|
+
const st = el('span', 'state-' + tone); st.append(el('i', 'dot ' + tone), document.createTextNode(' ' + item.editStatus));
|
|
11358
|
+
row(dl, 'status', st);
|
|
11359
|
+
row(dl, 'why', EDIT_STATUS_TEXT[item.editStatus] || '');
|
|
11360
|
+
row(dl, 'file', item.edit.path, { mono: true, copy: item.edit.absolutePath });
|
|
11361
|
+
if (item.edit.sha256) row(dl, 'sha256', item.edit.sha256.slice(0, 16) + '\u2026', { mono: true, copy: item.edit.sha256 });
|
|
11362
|
+
row(dl, 'saved', fmtWhen(item.edit.modifiedAt));
|
|
11363
|
+
s.append(dl);
|
|
11364
|
+
} else {
|
|
11365
|
+
s.append(el('div', 'state-dim', 'Touch it up in your own editor. The generated file stays untouched; the edit is a sibling file the manifest points at, and mount and pack place that instead.'));
|
|
11366
|
+
}
|
|
11367
|
+
if (EDITABLE && item.declared && projectOf(item)?.manifestSha256) {
|
|
11368
|
+
const acts = el('div', 'hand-actions');
|
|
11369
|
+
const msg = el('span', 'msg');
|
|
11370
|
+
const go = async (label, action, extra, done) => {
|
|
11371
|
+
const b = el('button', label === 'Detach edit' ? null : 'primary', label); b.type = 'button';
|
|
11372
|
+
b.onclick = async () => {
|
|
11373
|
+
b.disabled = true; msg.className = 'msg'; msg.textContent = '\u2026';
|
|
11374
|
+
try { await postHandEdit(item, action, extra); ui.notice = { id: item.id, text: done }; render(); }
|
|
11375
|
+
catch (err) { b.disabled = false; msg.className = 'msg bad'; msg.textContent = err.message + (err.status === 409 ? ' \u2014 press Refresh.' : ''); }
|
|
11376
|
+
};
|
|
11377
|
+
acts.append(b);
|
|
11378
|
+
};
|
|
11379
|
+
if (item.edit) {
|
|
11380
|
+
go('Open in editor', 'start-edit', { open: true }, 'Opened ' + item.edit.path + ' in your editor. Save there, then Refresh.');
|
|
11381
|
+
go('Detach edit', 'detach-edit', {}, 'Detached. The file is still at ' + item.edit.path + '; the generated art is placed again.');
|
|
11382
|
+
} else if (canStart) {
|
|
11383
|
+
go('Edit by hand', 'start-edit', { open: true }, 'Created the edit file and opened it in your editor. Save there, then Refresh to see it here.');
|
|
11384
|
+
go('Create edit file only', 'start-edit', { open: false }, 'Created the edit file and declared it in the manifest.');
|
|
11385
|
+
}
|
|
11386
|
+
acts.append(msg);
|
|
11387
|
+
s.append(acts);
|
|
11388
|
+
}
|
|
11389
|
+
return s;
|
|
11390
|
+
}
|
|
11391
|
+
|
|
11084
11392
|
// ---- editing (only when the server minted a session) ---------------------
|
|
11085
11393
|
|
|
11086
11394
|
// Styles a parent edit reaches: every descendant (via extends) that does not
|
|
@@ -11100,9 +11408,18 @@ function inheritorsOf(style, field) {
|
|
|
11100
11408
|
}
|
|
11101
11409
|
return out;
|
|
11102
11410
|
}
|
|
11411
|
+
// noBackground reaches the request only for pixflux and non-PixelLab
|
|
11412
|
+
// providers; elsewhere a change is recorded but alters nothing.
|
|
11413
|
+
const fieldReaches = (field, style) =>
|
|
11414
|
+
field !== 'noBackground' || style.generator === 'pixflux' || style.provider !== 'pixellab';
|
|
11103
11415
|
function blastRadius(style, fields) {
|
|
11104
|
-
const
|
|
11105
|
-
|
|
11416
|
+
const byId = new Map(snap.styles.filter((x) => x.project === style.project).map((x) => [x.id, x]));
|
|
11417
|
+
const styleIds = new Set();
|
|
11418
|
+
for (const field of fields) {
|
|
11419
|
+
for (const id of [style.id, ...inheritorsOf(style, field)]) {
|
|
11420
|
+
if (fieldReaches(field, byId.get(id) || style)) styleIds.add(id);
|
|
11421
|
+
}
|
|
11422
|
+
}
|
|
11106
11423
|
const affected = snap.items.filter((i) => i.project === style.project && i.declared && styleIds.has(i.styleId));
|
|
11107
11424
|
const units = new Map();
|
|
11108
11425
|
for (const i of affected) if (i.estimatedCost !== null) units.set(i.costUnit, (units.get(i.costUnit) || 0) + i.estimatedCost);
|
|
@@ -11127,6 +11444,11 @@ function styleForm(style) {
|
|
|
11127
11444
|
const prefix = el('input'); prefix.type = 'text'; prefix.value = style.promptPrefix; prefix.placeholder = 'none';
|
|
11128
11445
|
const suffix = el('input'); suffix.type = 'text'; suffix.value = style.promptSuffix; suffix.placeholder = 'none';
|
|
11129
11446
|
const palette = el('input'); palette.type = 'text'; palette.value = style.palette.join(', '); palette.placeholder = '#rrggbb, #rrggbb \u2014 leave empty for no forced palette';
|
|
11447
|
+
const view = el('input'); view.type = 'text'; view.value = style.view || ''; view.placeholder = 'provider default';
|
|
11448
|
+
view.setAttribute('list', 'view-options');
|
|
11449
|
+
const noBg = el('select');
|
|
11450
|
+
noBg.append(new Option(style.extends ? 'inherit from ' + style.extends : 'default (on)', 'default'), new Option('on \u2014 strip the generated background', 'on'), new Option('off \u2014 keep the background (scenes, banners)', 'off'));
|
|
11451
|
+
noBg.value = own('noBackground') ? (style.noBackground ? 'on' : 'off') : 'default';
|
|
11130
11452
|
const swatches = el('div', 'pal');
|
|
11131
11453
|
const drawSwatches = () => {
|
|
11132
11454
|
swatches.textContent = '';
|
|
@@ -11139,12 +11461,21 @@ function styleForm(style) {
|
|
|
11139
11461
|
field('palette', palette, 'Forced colours where the provider supports them (pixflux). ' + provenance('palette')),
|
|
11140
11462
|
swatches,
|
|
11141
11463
|
);
|
|
11464
|
+
const row = el('div', 'row');
|
|
11465
|
+
row.append(
|
|
11466
|
+
field('view', view, 'PixelLab accepts low top-down, high top-down, side; Retro Diffusion reads sidescroller. ' + provenance('view')),
|
|
11467
|
+
field('background', noBg, (fieldReaches('noBackground', style) ? 'Sent for this style. ' : 'Not sent for ' + style.provider + ' ' + style.generator + '; recorded only. ') + provenance('noBackground')),
|
|
11468
|
+
);
|
|
11469
|
+
form.append(row);
|
|
11142
11470
|
const blast = el('div', 'blast');
|
|
11143
11471
|
const changed = () => {
|
|
11144
11472
|
const out = [];
|
|
11145
11473
|
if (prefix.value !== style.promptPrefix) out.push('promptPrefix');
|
|
11146
11474
|
if (suffix.value !== style.promptSuffix) out.push('promptSuffix');
|
|
11147
11475
|
if (parsePalette(palette.value).join(',') !== style.palette.map((c) => c.toLowerCase()).join(',')) out.push('palette');
|
|
11476
|
+
if (view.value.trim() !== (style.view || '')) out.push('view');
|
|
11477
|
+
const bgNow = own('noBackground') ? (style.noBackground ? 'on' : 'off') : 'default';
|
|
11478
|
+
if (noBg.value !== bgNow) out.push('noBackground');
|
|
11148
11479
|
return out;
|
|
11149
11480
|
};
|
|
11150
11481
|
const updateBlast = () => {
|
|
@@ -11152,6 +11483,11 @@ function styleForm(style) {
|
|
|
11152
11483
|
drawSwatches();
|
|
11153
11484
|
if (!fields.length) { blast.className = 'blast'; blast.textContent = 'No changes yet. A style change alters the request for every asset that uses it.'; return; }
|
|
11154
11485
|
const r = blastRadius(style, fields);
|
|
11486
|
+
if (!r.assets) {
|
|
11487
|
+
blast.className = 'blast';
|
|
11488
|
+
blast.textContent = 'Recorded in the manifest only: nothing in ' + style.id + ' sends this field, so no request changes.';
|
|
11489
|
+
return;
|
|
11490
|
+
}
|
|
11155
11491
|
blast.className = 'blast hot';
|
|
11156
11492
|
blast.textContent = '';
|
|
11157
11493
|
const others = r.styles.filter((id) => id !== style.id);
|
|
@@ -11161,7 +11497,8 @@ function styleForm(style) {
|
|
|
11161
11497
|
: ' in ' + style.id + '.'));
|
|
11162
11498
|
blast.append(document.createTextNode(' Generated ones become stale; regenerating all of them is about ' + (r.cost || 'nothing') + '. Nothing is spent until you generate.'));
|
|
11163
11499
|
};
|
|
11164
|
-
for (const input of [prefix, suffix, palette]) input.addEventListener('input', updateBlast);
|
|
11500
|
+
for (const input of [prefix, suffix, palette, view]) input.addEventListener('input', updateBlast);
|
|
11501
|
+
noBg.addEventListener('change', updateBlast);
|
|
11165
11502
|
updateBlast();
|
|
11166
11503
|
form.append(blast);
|
|
11167
11504
|
const actions = el('div', 'actions');
|
|
@@ -11180,6 +11517,8 @@ function styleForm(style) {
|
|
|
11180
11517
|
if (fields.includes('promptPrefix')) patch.promptPrefix = prefix.value;
|
|
11181
11518
|
if (fields.includes('promptSuffix')) patch.promptSuffix = suffix.value;
|
|
11182
11519
|
if (fields.includes('palette')) patch.palette = colors;
|
|
11520
|
+
if (fields.includes('view')) patch.view = view.value.trim();
|
|
11521
|
+
if (fields.includes('noBackground')) patch.noBackground = noBg.value === 'default' ? null : noBg.value === 'on';
|
|
11183
11522
|
save.disabled = true; msg.className = 'msg'; msg.textContent = 'saving\u2026';
|
|
11184
11523
|
const r = blastRadius(style, fields);
|
|
11185
11524
|
try {
|
|
@@ -11188,7 +11527,9 @@ function styleForm(style) {
|
|
|
11188
11527
|
snap = await postEdit(body);
|
|
11189
11528
|
ui.editing = null;
|
|
11190
11529
|
ui.notice = { id: 'style:' + (style.project || '') + ':' + style.id,
|
|
11191
|
-
text:
|
|
11530
|
+
text: r.assets
|
|
11531
|
+
? 'Saved. The request for ' + r.assets + (r.assets === 1 ? ' asset' : ' assets') + ' changed' + (r.cost ? ' \u2014 about ' + r.cost + ' to regenerate.' : '.') + ' Nothing is spent until you generate.'
|
|
11532
|
+
: 'Saved. Recorded in the manifest; no request changed.' };
|
|
11192
11533
|
render();
|
|
11193
11534
|
} catch (err) {
|
|
11194
11535
|
save.disabled = false;
|
|
@@ -11539,6 +11880,9 @@ function renderDrawer() {
|
|
|
11539
11880
|
body.append(s);
|
|
11540
11881
|
}
|
|
11541
11882
|
|
|
11883
|
+
const hand = handEditSection(item);
|
|
11884
|
+
if (hand) body.append(hand);
|
|
11885
|
+
|
|
11542
11886
|
if (item.revision || item.revisionParentKey) {
|
|
11543
11887
|
const { s, dl } = section('Lineage');
|
|
11544
11888
|
row(dl, 'mode', item.revision ? item.revision.mode : (item.asset && item.asset.revision && item.asset.revision.mode));
|
|
@@ -11787,10 +12131,10 @@ if (ui.compare.length >= 2 && !ui.open) openCompare();
|
|
|
11787
12131
|
var escapeHtml2 = (s) => s.replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
11788
12132
|
|
|
11789
12133
|
// src/gallery/server.ts
|
|
11790
|
-
var
|
|
12134
|
+
var import_node_child_process4 = require("child_process");
|
|
11791
12135
|
var import_node_crypto7 = require("crypto");
|
|
11792
12136
|
var import_node_http2 = require("http");
|
|
11793
|
-
var
|
|
12137
|
+
var import_promises21 = require("fs/promises");
|
|
11794
12138
|
var MAX_EDIT_BYTES = 64 * 1024;
|
|
11795
12139
|
async function readJsonBody(req) {
|
|
11796
12140
|
const chunks = [];
|
|
@@ -11929,7 +12273,7 @@ async function serveGallery(opts) {
|
|
|
11929
12273
|
const asset = reviewAssets.get(jobId)?.get(url2.pathname);
|
|
11930
12274
|
if (!asset) return fail(404, "no such review asset");
|
|
11931
12275
|
try {
|
|
11932
|
-
const bytes = await (0,
|
|
12276
|
+
const bytes = await (0, import_promises21.readFile)(asset.path);
|
|
11933
12277
|
res.writeHead(200, {
|
|
11934
12278
|
"Content-Type": asset.contentType,
|
|
11935
12279
|
"Cache-Control": "no-store",
|
|
@@ -11978,7 +12322,7 @@ async function serveGallery(opts) {
|
|
|
11978
12322
|
if (!asset) return fail(404, "no such gallery media");
|
|
11979
12323
|
let bytes;
|
|
11980
12324
|
try {
|
|
11981
|
-
bytes = await (0,
|
|
12325
|
+
bytes = await (0, import_promises21.readFile)(asset.path);
|
|
11982
12326
|
} catch {
|
|
11983
12327
|
return fail(404, "gallery media is no longer on disk; refresh the page");
|
|
11984
12328
|
}
|
|
@@ -12010,7 +12354,7 @@ async function serveGallery(opts) {
|
|
|
12010
12354
|
});
|
|
12011
12355
|
opts.onReady?.(url);
|
|
12012
12356
|
if (opts.open !== false && process.platform === "darwin") {
|
|
12013
|
-
(0,
|
|
12357
|
+
(0, import_node_child_process4.spawn)("open", [url], { stdio: "ignore", detached: true }).unref();
|
|
12014
12358
|
}
|
|
12015
12359
|
return {
|
|
12016
12360
|
url,
|
|
@@ -12024,14 +12368,14 @@ async function serveGallery(opts) {
|
|
|
12024
12368
|
|
|
12025
12369
|
// src/gallery/generate.ts
|
|
12026
12370
|
var import_node_crypto8 = require("crypto");
|
|
12027
|
-
var
|
|
12028
|
-
var GenerateRequestSchema =
|
|
12029
|
-
project:
|
|
12030
|
-
keys:
|
|
12371
|
+
var import_zod9 = require("zod");
|
|
12372
|
+
var GenerateRequestSchema = import_zod9.z.object({
|
|
12373
|
+
project: import_zod9.z.string().min(1).optional(),
|
|
12374
|
+
keys: import_zod9.z.array(import_zod9.z.string().min(1)).min(1).max(500),
|
|
12031
12375
|
/** Regenerate work that is up to date, as `gen --force` does. */
|
|
12032
|
-
force:
|
|
12376
|
+
force: import_zod9.z.boolean().optional(),
|
|
12033
12377
|
/** Advance in-flight, review, or selected work without submitting anything. */
|
|
12034
|
-
resume:
|
|
12378
|
+
resume: import_zod9.z.boolean().optional()
|
|
12035
12379
|
}).strict();
|
|
12036
12380
|
var GenerateRequestError = class extends Error {
|
|
12037
12381
|
constructor(message6, status = 400) {
|
|
@@ -12268,19 +12612,19 @@ function createGenerateHandlers(opts) {
|
|
|
12268
12612
|
}
|
|
12269
12613
|
|
|
12270
12614
|
// src/pipeline/salvage.ts
|
|
12271
|
-
var
|
|
12272
|
-
var
|
|
12273
|
-
var
|
|
12615
|
+
var import_promises22 = require("fs/promises");
|
|
12616
|
+
var import_node_fs22 = require("fs");
|
|
12617
|
+
var import_node_path26 = __toESM(require("path"), 1);
|
|
12274
12618
|
function providerClaimId(provider, objectId) {
|
|
12275
12619
|
return `${encodeURIComponent(provider)}:${encodeURIComponent(objectId)}`;
|
|
12276
12620
|
}
|
|
12277
12621
|
async function loadClaims(lockPaths, opts = {}) {
|
|
12278
12622
|
const claimed = /* @__PURE__ */ new Set();
|
|
12279
12623
|
for (const p of lockPaths) {
|
|
12280
|
-
if (!(0,
|
|
12624
|
+
if (!(0, import_node_fs22.existsSync)(p)) throw new Error(`Claim lockfile not found: ${p}`);
|
|
12281
12625
|
let parsed;
|
|
12282
12626
|
try {
|
|
12283
|
-
parsed = parseLock(JSON.parse(await (0,
|
|
12627
|
+
parsed = parseLock(JSON.parse(await (0, import_promises22.readFile)(p, "utf8")));
|
|
12284
12628
|
} catch {
|
|
12285
12629
|
throw new Error(`Claim lockfile is malformed: ${p}`);
|
|
12286
12630
|
}
|
|
@@ -12333,20 +12677,20 @@ function matchOrphanStyle(prompt, manifest) {
|
|
|
12333
12677
|
return matchStyleByPattern(prompt, manifest);
|
|
12334
12678
|
}
|
|
12335
12679
|
async function loadSiblingManifests(ownManifestPath, workspaceManifestPaths, claimPaths) {
|
|
12336
|
-
const own =
|
|
12680
|
+
const own = import_node_path26.default.resolve(ownManifestPath);
|
|
12337
12681
|
const siblingManifestPaths = [
|
|
12338
12682
|
.../* @__PURE__ */ new Set([
|
|
12339
12683
|
...workspaceManifestPaths,
|
|
12340
|
-
...claimPaths.map((c) =>
|
|
12684
|
+
...claimPaths.map((c) => import_node_path26.default.join(import_node_path26.default.dirname(import_node_path26.default.resolve(c)), "pixelkiln.manifest.json"))
|
|
12341
12685
|
])
|
|
12342
12686
|
];
|
|
12343
12687
|
const siblings = [];
|
|
12344
12688
|
for (const siblingManifestPath of siblingManifestPaths) {
|
|
12345
|
-
if (
|
|
12346
|
-
if (!(0,
|
|
12689
|
+
if (import_node_path26.default.resolve(siblingManifestPath) === own) continue;
|
|
12690
|
+
if (!(0, import_node_fs22.existsSync)(siblingManifestPath)) continue;
|
|
12347
12691
|
try {
|
|
12348
12692
|
const { manifest } = await loadManifest(siblingManifestPath);
|
|
12349
|
-
siblings.push({ label:
|
|
12693
|
+
siblings.push({ label: import_node_path26.default.basename(import_node_path26.default.dirname(siblingManifestPath)), manifest });
|
|
12350
12694
|
} catch {
|
|
12351
12695
|
}
|
|
12352
12696
|
}
|
|
@@ -12462,8 +12806,8 @@ async function applyTags(provider, decisions, existing, opts = {}) {
|
|
|
12462
12806
|
}
|
|
12463
12807
|
|
|
12464
12808
|
// src/pick/salvage-server.ts
|
|
12465
|
-
var
|
|
12466
|
-
var
|
|
12809
|
+
var import_promises23 = require("fs/promises");
|
|
12810
|
+
var import_node_path27 = __toESM(require("path"), 1);
|
|
12467
12811
|
|
|
12468
12812
|
// src/pick/salvage-sheet.ts
|
|
12469
12813
|
var escapeHtml3 = (s) => s.replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
@@ -12628,7 +12972,7 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
12628
12972
|
});
|
|
12629
12973
|
const html = renderSalvageSheet(orphans, {
|
|
12630
12974
|
styleId: ctx.styleId,
|
|
12631
|
-
importDir:
|
|
12975
|
+
importDir: import_node_path27.default.relative(process.cwd(), ctx.importDir) || "."
|
|
12632
12976
|
});
|
|
12633
12977
|
const byId = new Map(orphans.map((o) => [o.id, o]));
|
|
12634
12978
|
const existingTags = new Map(orphans.map((o) => [o.id, o.tags]));
|
|
@@ -12657,13 +13001,14 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
12657
13001
|
if (!buf.subarray(0, 8).equals(PNG_SIGNATURE2)) throw new Error("not a PNG");
|
|
12658
13002
|
decodePng(buf);
|
|
12659
13003
|
const assetId = idFromPrompt(orphan.prompt, taken);
|
|
12660
|
-
const rel =
|
|
12661
|
-
const outFile =
|
|
12662
|
-
await (0,
|
|
12663
|
-
await (0,
|
|
13004
|
+
const rel = import_node_path27.default.join("_salvaged", `${assetId}.png`);
|
|
13005
|
+
const outFile = import_node_path27.default.resolve(ctx.importDir, rel);
|
|
13006
|
+
await (0, import_promises23.mkdir)(import_node_path27.default.dirname(outFile), { recursive: true });
|
|
13007
|
+
await (0, import_promises23.writeFile)(outFile, buf);
|
|
12664
13008
|
ctx.manifest.assets[assetId] = {
|
|
12665
13009
|
prompt: orphan.prompt,
|
|
12666
13010
|
promptByStyle: {},
|
|
13011
|
+
sourceByStyle: {},
|
|
12667
13012
|
category: "_salvaged",
|
|
12668
13013
|
file: rel,
|
|
12669
13014
|
tags: ["salvaged"],
|
|
@@ -12691,7 +13036,7 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
12691
13036
|
sourceUrl: durableSource,
|
|
12692
13037
|
sourceUrls: durableSource ? [{ url: durableSource }] : [],
|
|
12693
13038
|
outputs: [{
|
|
12694
|
-
path: portableOutputPath(outFile,
|
|
13039
|
+
path: portableOutputPath(outFile, import_node_path27.default.dirname(ctx.manifestPath)),
|
|
12695
13040
|
sha256: sha256(buf)
|
|
12696
13041
|
}],
|
|
12697
13042
|
submittedAt: orphan.createdAt,
|
|
@@ -12715,9 +13060,9 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
12715
13060
|
}
|
|
12716
13061
|
}
|
|
12717
13062
|
await applyTags(provider, decisions, existingTags, { onProgress: log });
|
|
12718
|
-
const raw = JSON.parse(await (0,
|
|
13063
|
+
const raw = JSON.parse(await (0, import_promises23.readFile)(ctx.manifestPath, "utf8"));
|
|
12719
13064
|
for (const id of importedAssetIds) raw.assets[id] = ctx.manifest.assets[id];
|
|
12720
|
-
await (0,
|
|
13065
|
+
await (0, import_promises23.writeFile)(ctx.manifestPath, JSON.stringify(raw, null, 2) + "\n");
|
|
12721
13066
|
await saveLock(ctx.lockPath, ctx.lock);
|
|
12722
13067
|
return result;
|
|
12723
13068
|
}
|