pixelkiln 0.28.0 → 0.30.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/README.md +44 -44
- package/dist/cli.js +398 -185
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +776 -207
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +439 -73
- package/dist/index.d.ts +439 -73
- package/dist/index.js +738 -198
- package/dist/index.js.map +1 -1
- package/docs/AGENTS.md +5 -0
- package/docs/ARCHITECTURE.md +14 -0
- package/docs/ARTIFACTS.md +19 -13
- package/docs/CLI.md +18 -9
- package/docs/GETTING_STARTED.md +45 -3
- package/docs/LIBRARY.md +48 -2
- package/docs/MANIFEST.md +2 -2
- package/docs/README.md +3 -3
- package/docs/TILES.md +3 -1
- package/package.json +1 -1
- package/skills/pixelkiln/SKILL.md +6 -2
package/dist/cli.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
4
|
import path30 from "path";
|
|
5
|
-
import { existsSync as
|
|
5
|
+
import { existsSync as existsSync24 } from "fs";
|
|
6
6
|
import { readFile as readFile25 } from "fs/promises";
|
|
7
7
|
|
|
8
8
|
// src/env.ts
|
|
@@ -872,12 +872,7 @@ var ComfyUIProvider = class _ComfyUIProvider {
|
|
|
872
872
|
}
|
|
873
873
|
validate(spec, styleImages) {
|
|
874
874
|
const options = resolvedOptions(spec);
|
|
875
|
-
if (spec.source && !spec.revision)
|
|
876
|
-
if (spec.generator === "frames") {
|
|
877
|
-
throw new Error("ComfyUI frame sets require generated provider outputs, not asset.source");
|
|
878
|
-
}
|
|
879
|
-
return;
|
|
880
|
-
}
|
|
875
|
+
if (spec.source && !spec.revision && spec.generator !== "frames") return;
|
|
881
876
|
if (spec.width < 16 || spec.height < 16 || spec.width > 4096 || spec.height > 4096) {
|
|
882
877
|
throw new Error("ComfyUI output dimensions must be between 16 and 4096 pixels");
|
|
883
878
|
}
|
|
@@ -3483,10 +3478,11 @@ registerProvider({
|
|
|
3483
3478
|
|
|
3484
3479
|
// src/manifest.ts
|
|
3485
3480
|
import { readFile as readFile3 } from "fs/promises";
|
|
3486
|
-
import { existsSync as
|
|
3481
|
+
import { existsSync as existsSync5 } from "fs";
|
|
3487
3482
|
import path6 from "path";
|
|
3488
3483
|
|
|
3489
3484
|
// src/outputs.ts
|
|
3485
|
+
import { existsSync as existsSync4 } from "fs";
|
|
3490
3486
|
import path5 from "path";
|
|
3491
3487
|
|
|
3492
3488
|
// src/lock.ts
|
|
@@ -3673,13 +3669,28 @@ function resolveOutputPath(recordedPath, manifestDir) {
|
|
|
3673
3669
|
return path5.resolve(manifestDir, recordedPath.split(/[\\/]/).join(path5.sep));
|
|
3674
3670
|
}
|
|
3675
3671
|
function expectedOutputPath(spec, role, index, total, mediaType) {
|
|
3676
|
-
|
|
3672
|
+
return memberPath(spec.outFile, role, index, total, mediaType);
|
|
3673
|
+
}
|
|
3674
|
+
function memberPath(file, role, index, total, mediaType) {
|
|
3675
|
+
const originalExt = path5.extname(file);
|
|
3677
3676
|
const ext = mediaType ? mediaExtension(mediaType) : originalExt || ".png";
|
|
3678
|
-
const stem = originalExt ?
|
|
3677
|
+
const stem = originalExt ? file.slice(0, -originalExt.length) : file;
|
|
3679
3678
|
if (total === 1) return `${stem}${ext}`;
|
|
3680
3679
|
const safeRole = (role ?? fallbackOutputRole(index)).replace(/[^a-zA-Z0-9_-]+/g, "-");
|
|
3681
3680
|
return `${stem}-${safeRole}${ext}`;
|
|
3682
3681
|
}
|
|
3682
|
+
function isMemberSetEntry(entry) {
|
|
3683
|
+
return entry.outputs.length > 1 && entry.outputs.every((output) => !output.mediaType || output.mediaType === "image/png");
|
|
3684
|
+
}
|
|
3685
|
+
function sourceIsStem(source, entry, manifestDir) {
|
|
3686
|
+
return isMemberSetEntry(entry) && !existsSync4(path5.resolve(manifestDir, source));
|
|
3687
|
+
}
|
|
3688
|
+
function sourceOutputPath(source, entry, index, manifestDir) {
|
|
3689
|
+
const absolute = path5.resolve(manifestDir, source);
|
|
3690
|
+
if (!sourceIsStem(source, entry, manifestDir)) return absolute;
|
|
3691
|
+
const output = entry.outputs[index];
|
|
3692
|
+
return memberPath(absolute, output.role, index, entry.outputs.length, output.mediaType);
|
|
3693
|
+
}
|
|
3683
3694
|
function currentOutputPath(output, spec, index, total) {
|
|
3684
3695
|
return expectedOutputPath(spec, output.role, index, total, output.mediaType);
|
|
3685
3696
|
}
|
|
@@ -3764,7 +3775,7 @@ function resolveSpecOutputs(spec, lock, manifestDir) {
|
|
|
3764
3775
|
// src/manifest.ts
|
|
3765
3776
|
async function loadManifest(manifestPath) {
|
|
3766
3777
|
const abs = path6.resolve(manifestPath);
|
|
3767
|
-
if (!
|
|
3778
|
+
if (!existsSync5(abs)) throw new Error(`No manifest at ${abs}`);
|
|
3768
3779
|
const input = ManifestInputSchema.safeParse(JSON.parse(await readFile3(abs, "utf8")));
|
|
3769
3780
|
if (!input.success) {
|
|
3770
3781
|
const issues = formatManifestIssues(input.error.issues);
|
|
@@ -3936,7 +3947,7 @@ async function resolveSpecs(loaded, filter) {
|
|
|
3936
3947
|
const abs = path6.resolve(root, rel);
|
|
3937
3948
|
let hit = styleImageCache.get(abs);
|
|
3938
3949
|
if (!hit) {
|
|
3939
|
-
if (!
|
|
3950
|
+
if (!existsSync5(abs)) throw new Error(`Style image not found: ${abs}`);
|
|
3940
3951
|
const buf = await readFile3(abs);
|
|
3941
3952
|
const metadata = imageMetadata(buf);
|
|
3942
3953
|
if (!metadata) throw new Error(`Style image is not a readable PNG or JPEG: ${abs}`);
|
|
@@ -4177,7 +4188,7 @@ function pngPath(file) {
|
|
|
4177
4188
|
return extension ? `${file.slice(0, -extension.length)}.png` : `${file}.png`;
|
|
4178
4189
|
}
|
|
4179
4190
|
async function optionalRevisionImage(file, label, requiredFormat) {
|
|
4180
|
-
if (!
|
|
4191
|
+
if (!existsSync5(file)) return null;
|
|
4181
4192
|
const bytes = await readFile3(file);
|
|
4182
4193
|
const metadata = imageMetadata(bytes);
|
|
4183
4194
|
if (!metadata) throw new Error(`${label} is not a readable PNG or JPEG: ${file}`);
|
|
@@ -4336,12 +4347,15 @@ function packStyle(lock, styleId, manifestDir, options = {}) {
|
|
|
4336
4347
|
for (const [key, entry] of entries) {
|
|
4337
4348
|
const id = key.slice(prefix.length);
|
|
4338
4349
|
locked.add(id);
|
|
4339
|
-
|
|
4350
|
+
let outputs = resolveEntryOutputs(entry, id, manifestDir);
|
|
4340
4351
|
const source = sources[id];
|
|
4341
4352
|
if (source && outputs.length <= 1) {
|
|
4342
4353
|
inputs.push({ id, path: path7.resolve(manifestDir, source) });
|
|
4343
4354
|
continue;
|
|
4344
4355
|
}
|
|
4356
|
+
if (source && sourceIsStem(source, entry, manifestDir)) {
|
|
4357
|
+
outputs = outputs.map((output) => ({ ...output, absolutePath: sourceOutputPath(source, entry, output.index, manifestDir) }));
|
|
4358
|
+
}
|
|
4345
4359
|
let selected = outputs;
|
|
4346
4360
|
if (options.outputRoles?.length) {
|
|
4347
4361
|
selected = outputs.filter(
|
|
@@ -4463,11 +4477,21 @@ function mountStyle(lock, styleId, manifestDir, mount, cells, sources = {}, outp
|
|
|
4463
4477
|
for (const id of ids) {
|
|
4464
4478
|
const cell = cells[id];
|
|
4465
4479
|
const source = sources[id];
|
|
4466
|
-
|
|
4480
|
+
const entry = lock.entries[`${styleId}/${id}`];
|
|
4481
|
+
if (source && !(entry && sourceIsStem(source, entry, manifestDir))) {
|
|
4467
4482
|
placements.push({ id, path: path7.resolve(manifestDir, source), cell });
|
|
4468
4483
|
continue;
|
|
4469
4484
|
}
|
|
4470
|
-
|
|
4485
|
+
if (source && entry) {
|
|
4486
|
+
const selection2 = selectEntryOutput(entry, outputRoles[id]);
|
|
4487
|
+
if (!selection2.ok) {
|
|
4488
|
+
skipped.push({ id, reason: selection2.reason });
|
|
4489
|
+
continue;
|
|
4490
|
+
}
|
|
4491
|
+
const index = entry.outputs.findIndex((output) => output.path === selection2.output.path);
|
|
4492
|
+
placements.push({ id, path: sourceOutputPath(source, entry, Math.max(index, 0), manifestDir), cell });
|
|
4493
|
+
continue;
|
|
4494
|
+
}
|
|
4471
4495
|
if (!entry) {
|
|
4472
4496
|
skipped.push({
|
|
4473
4497
|
id,
|
|
@@ -4508,11 +4532,11 @@ function mountStyle(lock, styleId, manifestDir, mount, cells, sources = {}, outp
|
|
|
4508
4532
|
}
|
|
4509
4533
|
|
|
4510
4534
|
// src/pipeline/plan.ts
|
|
4511
|
-
import { existsSync as
|
|
4535
|
+
import { existsSync as existsSync9 } from "fs";
|
|
4512
4536
|
import path13 from "path";
|
|
4513
4537
|
|
|
4514
4538
|
// src/pipeline/quality-profile.ts
|
|
4515
|
-
import { existsSync as
|
|
4539
|
+
import { existsSync as existsSync7 } from "fs";
|
|
4516
4540
|
import path11 from "path";
|
|
4517
4541
|
|
|
4518
4542
|
// src/pipeline/refine.ts
|
|
@@ -4980,7 +5004,7 @@ async function writeArtifactBundle(files, options = {}) {
|
|
|
4980
5004
|
|
|
4981
5005
|
// src/pipeline/audit.ts
|
|
4982
5006
|
import { readFile as readFile5 } from "fs/promises";
|
|
4983
|
-
import { existsSync as
|
|
5007
|
+
import { existsSync as existsSync6 } from "fs";
|
|
4984
5008
|
import path9 from "path";
|
|
4985
5009
|
function colorDistance(a, b) {
|
|
4986
5010
|
const rmean = (a.r + b.r) / 2;
|
|
@@ -5026,7 +5050,7 @@ async function auditStyle(loaded, specs, styleId, lock) {
|
|
|
5026
5050
|
const unreadable = [];
|
|
5027
5051
|
for (const spec of mine) {
|
|
5028
5052
|
for (const output of resolveSpecOutputs(spec, lock, loaded.root)) {
|
|
5029
|
-
if (!
|
|
5053
|
+
if (!existsSync6(output.absolutePath)) {
|
|
5030
5054
|
missing.push(output.id);
|
|
5031
5055
|
continue;
|
|
5032
5056
|
}
|
|
@@ -5056,7 +5080,7 @@ async function auditStyle(loaded, specs, styleId, lock) {
|
|
|
5056
5080
|
const refPalettes = [];
|
|
5057
5081
|
for (const img of style.styleImages) {
|
|
5058
5082
|
const abs = path9.resolve(loaded.root, img.path);
|
|
5059
|
-
if (!
|
|
5083
|
+
if (!existsSync6(abs)) continue;
|
|
5060
5084
|
try {
|
|
5061
5085
|
refPalettes.push(extractPalette(decodePng(await readFile5(abs)), 12));
|
|
5062
5086
|
} catch {
|
|
@@ -5685,7 +5709,7 @@ async function qualitySource(spec, lock) {
|
|
|
5685
5709
|
return { items: null, reason: "frame-set quality requires ordered downloaded provider outputs" };
|
|
5686
5710
|
}
|
|
5687
5711
|
const source = path11.resolve(spec.root, spec.source);
|
|
5688
|
-
return
|
|
5712
|
+
return existsSync7(source) ? { items: [{ role: "source", path: source }] } : { items: null, reason: `declared source is missing: ${spec.source}` };
|
|
5689
5713
|
}
|
|
5690
5714
|
const entry = lock.entries[lockKey(spec.styleId, spec.assetId)];
|
|
5691
5715
|
if (!entry) return { items: null, reason: "raw provider output is not in the lockfile" };
|
|
@@ -5714,7 +5738,7 @@ async function qualitySource(spec, lock) {
|
|
|
5714
5738
|
}
|
|
5715
5739
|
if (role) roles.add(role);
|
|
5716
5740
|
const source = currentEntryOutputPath(entry, spec, index);
|
|
5717
|
-
if (!
|
|
5741
|
+
if (!existsSync7(source)) return { items: null, reason: `raw provider output is missing: ${source}` };
|
|
5718
5742
|
if (await sha256File(source) !== output.sha256) {
|
|
5719
5743
|
return { items: null, reason: "raw provider output was modified after download" };
|
|
5720
5744
|
}
|
|
@@ -5733,7 +5757,7 @@ async function inspectQualityProfile(spec, lock) {
|
|
|
5733
5757
|
}
|
|
5734
5758
|
const sourcePaths = source.items.map((item) => item.path);
|
|
5735
5759
|
const expectedOutputs = spec.generator === "frames" ? source.items.map((item, index) => qualityFrameOutputPath(output, item.role, index)) : [output];
|
|
5736
|
-
if (!
|
|
5760
|
+
if (!existsSync7(record)) {
|
|
5737
5761
|
return {
|
|
5738
5762
|
key,
|
|
5739
5763
|
state: "needs-refinement",
|
|
@@ -5914,7 +5938,7 @@ async function requireApprovedQualitySources(specs, lock, assetIds, outputRoles)
|
|
|
5914
5938
|
}
|
|
5915
5939
|
|
|
5916
5940
|
// src/pipeline/revision.ts
|
|
5917
|
-
import { existsSync as
|
|
5941
|
+
import { existsSync as existsSync8 } from "fs";
|
|
5918
5942
|
import path12 from "path";
|
|
5919
5943
|
async function inspectRevisionReadiness(spec, lock) {
|
|
5920
5944
|
if (!spec.revision) return null;
|
|
@@ -5930,14 +5954,14 @@ async function inspectRevision(spec, lock, seen) {
|
|
|
5930
5954
|
reason: `revision source ${spec.styleId}/${revision.sourceAssetId} is not ready: ${dependency.reason}`
|
|
5931
5955
|
};
|
|
5932
5956
|
}
|
|
5933
|
-
if (!revision.sourceSha256 || !
|
|
5957
|
+
if (!revision.sourceSha256 || !existsSync8(revision.sourceFile)) {
|
|
5934
5958
|
return { ready: false, reason: `revision source is missing: ${revision.sourceFile}` };
|
|
5935
5959
|
}
|
|
5936
5960
|
if (await sha256File(revision.sourceFile) !== revision.sourceSha256) {
|
|
5937
5961
|
return { ready: false, reason: "revision source changed after the manifest was resolved" };
|
|
5938
5962
|
}
|
|
5939
5963
|
if (revision.maskFile) {
|
|
5940
|
-
if (!revision.maskSha256 || !
|
|
5964
|
+
if (!revision.maskSha256 || !existsSync8(revision.maskFile)) {
|
|
5941
5965
|
return { ready: false, reason: `revision mask is missing: ${revision.maskFile}` };
|
|
5942
5966
|
}
|
|
5943
5967
|
if (await sha256File(revision.maskFile) !== revision.maskSha256) {
|
|
@@ -5968,7 +5992,7 @@ async function inspectParent(spec, lock, seen) {
|
|
|
5968
5992
|
}
|
|
5969
5993
|
if (spec.source) {
|
|
5970
5994
|
const source = path12.resolve(spec.root, spec.source);
|
|
5971
|
-
return
|
|
5995
|
+
return existsSync8(source) ? { ready: true, reason: "declared source is present" } : { ready: false, reason: `declared source is missing: ${spec.source}` };
|
|
5972
5996
|
}
|
|
5973
5997
|
const entry = lock.entries[key];
|
|
5974
5998
|
if (!entry) return { ready: false, reason: "parent is not in the lockfile" };
|
|
@@ -5982,7 +6006,7 @@ async function inspectParent(spec, lock, seen) {
|
|
|
5982
6006
|
for (let index = 0; index < entry.outputs.length; index++) {
|
|
5983
6007
|
const output = entry.outputs[index];
|
|
5984
6008
|
const file = currentEntryOutputPath(entry, spec, index);
|
|
5985
|
-
if (!
|
|
6009
|
+
if (!existsSync8(file)) return { ready: false, reason: `parent output is missing: ${file}` };
|
|
5986
6010
|
if (await sha256File(file) !== output.sha256) {
|
|
5987
6011
|
return { ready: false, reason: `parent output was modified: ${file}` };
|
|
5988
6012
|
}
|
|
@@ -6036,8 +6060,8 @@ async function buildPlan(specs, lock, opts = {}) {
|
|
|
6036
6060
|
} else if (opts.force) {
|
|
6037
6061
|
state = "missing";
|
|
6038
6062
|
reason = "--force";
|
|
6039
|
-
} else if (!entry && spec.source) {
|
|
6040
|
-
if (
|
|
6063
|
+
} else if (!entry && spec.source && spec.generator !== "frames") {
|
|
6064
|
+
if (existsSync9(path13.resolve(spec.root, spec.source))) {
|
|
6041
6065
|
state = "ok";
|
|
6042
6066
|
reason = `placed from ${spec.source}; not generated`;
|
|
6043
6067
|
} else {
|
|
@@ -6045,7 +6069,7 @@ async function buildPlan(specs, lock, opts = {}) {
|
|
|
6045
6069
|
reason = `declared source is missing: ${spec.source}`;
|
|
6046
6070
|
}
|
|
6047
6071
|
} else if (!entry) {
|
|
6048
|
-
if (
|
|
6072
|
+
if (existsSync9(spec.outFile)) {
|
|
6049
6073
|
state = "untracked";
|
|
6050
6074
|
reason = "file on disk, no provenance recorded";
|
|
6051
6075
|
} else {
|
|
@@ -6077,9 +6101,9 @@ async function buildPlan(specs, lock, opts = {}) {
|
|
|
6077
6101
|
} else if (entry.status !== "downloaded") {
|
|
6078
6102
|
state = "in-flight";
|
|
6079
6103
|
reason = `awaiting ${entry.status}`;
|
|
6080
|
-
} else if (entry.outputs.length === 0 || entry.outputs.some((_output, index) => !
|
|
6104
|
+
} else if (entry.outputs.length === 0 || entry.outputs.some((_output, index) => !existsSync9(currentEntryOutputPath(entry, spec, index)))) {
|
|
6081
6105
|
state = "orphaned";
|
|
6082
|
-
reason = entry.outputs.length === 0 ? "no recorded output files" : `missing on disk: ${entry.outputs.filter((_output, index) => !
|
|
6106
|
+
reason = entry.outputs.length === 0 ? "no recorded output files" : `missing on disk: ${entry.outputs.filter((_output, index) => !existsSync9(currentEntryOutputPath(entry, spec, index))).map((o) => o.role ?? "asset").join(", ")}`;
|
|
6083
6107
|
} else if (await anyOutputModified(entry, spec)) {
|
|
6084
6108
|
state = "orphaned";
|
|
6085
6109
|
reason = "output modified since download";
|
|
@@ -6407,7 +6431,7 @@ async function poll(provider, lock, lockPath, opts = {}) {
|
|
|
6407
6431
|
}
|
|
6408
6432
|
|
|
6409
6433
|
// src/pipeline/fetch.ts
|
|
6410
|
-
import { existsSync as
|
|
6434
|
+
import { existsSync as existsSync10 } from "fs";
|
|
6411
6435
|
import { mkdir as mkdir3, readFile as readFile7, rename as rename3, rm as rm4, writeFile as writeFile3 } from "fs/promises";
|
|
6412
6436
|
import path14 from "path";
|
|
6413
6437
|
|
|
@@ -6463,7 +6487,7 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
6463
6487
|
if (opts.refresh) return e.outputs.length > 0 && Boolean(e.sourceUrls?.length || e.sourceUrl);
|
|
6464
6488
|
if (!opts.repair) return false;
|
|
6465
6489
|
const spec = specByKey.get(key);
|
|
6466
|
-
return e.outputs.length === 0 || !spec || e.outputs.some((output) => !
|
|
6490
|
+
return e.outputs.length === 0 || !spec || e.outputs.some((output) => !existsSync10(resolveOutputPath(output.path, spec.root)));
|
|
6467
6491
|
});
|
|
6468
6492
|
const concurrency = Math.min(Math.max(1, opts.concurrency ?? 8), Math.max(1, pending.length));
|
|
6469
6493
|
let cursor = 0;
|
|
@@ -6511,7 +6535,7 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
6511
6535
|
continue;
|
|
6512
6536
|
}
|
|
6513
6537
|
}
|
|
6514
|
-
if (
|
|
6538
|
+
if (existsSync10(target)) {
|
|
6515
6539
|
const currentHash = await sha256File(target);
|
|
6516
6540
|
if (recorded && currentHash === recorded.sha256) {
|
|
6517
6541
|
if (!fresh) {
|
|
@@ -6622,7 +6646,7 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
6622
6646
|
}
|
|
6623
6647
|
async function readCachedMedia(cacheDir, hash, mediaType) {
|
|
6624
6648
|
const file = path14.join(cacheDir, cacheFileName(hash, mediaType));
|
|
6625
|
-
if (!
|
|
6649
|
+
if (!existsSync10(file)) return null;
|
|
6626
6650
|
try {
|
|
6627
6651
|
const buf = await readFile7(file);
|
|
6628
6652
|
if (sha256(buf) !== hash) return null;
|
|
@@ -6643,7 +6667,7 @@ async function cacheMedia(cacheDir, buf, mediaType, knownHash) {
|
|
|
6643
6667
|
try {
|
|
6644
6668
|
await rename3(tmp, file);
|
|
6645
6669
|
} catch (err) {
|
|
6646
|
-
if (!
|
|
6670
|
+
if (!existsSync10(file)) throw err;
|
|
6647
6671
|
} finally {
|
|
6648
6672
|
await rm4(tmp, { force: true });
|
|
6649
6673
|
}
|
|
@@ -6676,7 +6700,7 @@ async function pushTags(provider, specs, lock, opts = {}) {
|
|
|
6676
6700
|
}
|
|
6677
6701
|
|
|
6678
6702
|
// src/pipeline/doctor.ts
|
|
6679
|
-
import { constants, existsSync as
|
|
6703
|
+
import { constants, existsSync as existsSync11 } from "fs";
|
|
6680
6704
|
import { access, stat as stat2 } from "fs/promises";
|
|
6681
6705
|
import path15 from "path";
|
|
6682
6706
|
async function doctor(loaded, specs, lock, lockPath, opts = {}) {
|
|
@@ -6748,7 +6772,7 @@ async function doctor(loaded, specs, lock, lockPath, opts = {}) {
|
|
|
6748
6772
|
cacheDir,
|
|
6749
6773
|
cacheFileName(output.sha256, output.mediaType ?? MediaType.PNG)
|
|
6750
6774
|
);
|
|
6751
|
-
if (
|
|
6775
|
+
if (existsSync11(cached) && await sha256File(cached) === output.sha256) {
|
|
6752
6776
|
hasCache = true;
|
|
6753
6777
|
break;
|
|
6754
6778
|
}
|
|
@@ -6851,13 +6875,13 @@ async function nearestExistingDirectory(start) {
|
|
|
6851
6875
|
}
|
|
6852
6876
|
|
|
6853
6877
|
// src/pipeline/adopt.ts
|
|
6854
|
-
import { existsSync as
|
|
6878
|
+
import { existsSync as existsSync13 } from "fs";
|
|
6855
6879
|
import { readFile as readFile9, writeFile as writeFile5 } from "fs/promises";
|
|
6856
6880
|
import path16 from "path";
|
|
6857
6881
|
|
|
6858
6882
|
// src/cache.ts
|
|
6859
6883
|
import { mkdir as mkdir4, readFile as readFile8, rename as rename4, rm as rm5, writeFile as writeFile4 } from "fs/promises";
|
|
6860
|
-
import { existsSync as
|
|
6884
|
+
import { existsSync as existsSync12 } from "fs";
|
|
6861
6885
|
import pathModule from "path";
|
|
6862
6886
|
import { z as z3 } from "zod";
|
|
6863
6887
|
var HashCacheSchema = z3.object({
|
|
@@ -6873,7 +6897,7 @@ function parseCache(value) {
|
|
|
6873
6897
|
return HashCacheSchema.parse(value);
|
|
6874
6898
|
}
|
|
6875
6899
|
async function loadCache(path31) {
|
|
6876
|
-
if (!
|
|
6900
|
+
if (!existsSync12(path31)) return { version: 1, hashes: {} };
|
|
6877
6901
|
try {
|
|
6878
6902
|
const parsed = HashCacheSchema.safeParse(JSON.parse(await readFile8(path31, "utf8")));
|
|
6879
6903
|
if (!parsed.success) return { version: 1, hashes: {} };
|
|
@@ -6929,7 +6953,7 @@ async function adopt(provider, specs, lock, lockPath, opts = {}) {
|
|
|
6929
6953
|
const localByHash = /* @__PURE__ */ new Map();
|
|
6930
6954
|
const unmatchedLocal = [];
|
|
6931
6955
|
for (const spec of specs) {
|
|
6932
|
-
if (!
|
|
6956
|
+
if (!existsSync13(spec.outFile)) {
|
|
6933
6957
|
unmatchedLocal.push(`${spec.styleId}/${spec.assetId} (no file at ${spec.outFile})`);
|
|
6934
6958
|
continue;
|
|
6935
6959
|
}
|
|
@@ -7086,12 +7110,12 @@ function formatUnmatchedRemote(objects, limit = 15) {
|
|
|
7086
7110
|
}
|
|
7087
7111
|
|
|
7088
7112
|
// src/pick/server.ts
|
|
7089
|
-
import { existsSync as
|
|
7113
|
+
import { existsSync as existsSync15 } from "fs";
|
|
7090
7114
|
import { readFile as readFile12 } from "fs/promises";
|
|
7091
7115
|
|
|
7092
7116
|
// src/pipeline/init.ts
|
|
7093
7117
|
import { readdir, readFile as readFile10, writeFile as writeFile6 } from "fs/promises";
|
|
7094
|
-
import { existsSync as
|
|
7118
|
+
import { existsSync as existsSync14 } from "fs";
|
|
7095
7119
|
import path17 from "path";
|
|
7096
7120
|
function pngSize(buf) {
|
|
7097
7121
|
if (buf.length < 24) return null;
|
|
@@ -7184,7 +7208,7 @@ function mode(values) {
|
|
|
7184
7208
|
return [...counts.entries()].sort((a, b) => b[1] - a[1])[0]?.[0] ?? 64;
|
|
7185
7209
|
}
|
|
7186
7210
|
async function writeManifestFile(target, manifest) {
|
|
7187
|
-
if (
|
|
7211
|
+
if (existsSync14(target)) {
|
|
7188
7212
|
throw new Error(`${target} already exists \u2014 refusing to overwrite an existing manifest.`);
|
|
7189
7213
|
}
|
|
7190
7214
|
await writeFile6(target, JSON.stringify(manifest, null, 2) + "\n");
|
|
@@ -7670,7 +7694,7 @@ async function currentArt(entry, spec) {
|
|
|
7670
7694
|
const previous = primaryOutput({ ...entry, outputs: entry.supersededOutputs });
|
|
7671
7695
|
if (!previous) return null;
|
|
7672
7696
|
const file = resolveOutputPath(previous.path, spec.root);
|
|
7673
|
-
if (!
|
|
7697
|
+
if (!existsSync15(file)) return null;
|
|
7674
7698
|
const contentType = previous.mediaType ?? mediaTypeFromExtension(file);
|
|
7675
7699
|
if (!contentType) return null;
|
|
7676
7700
|
let width = entry.width;
|
|
@@ -7841,13 +7865,13 @@ async function runPicker(provider, lock, lockPath, opts = {}) {
|
|
|
7841
7865
|
|
|
7842
7866
|
// src/gallery/snapshot.ts
|
|
7843
7867
|
import { createHash as createHash3 } from "crypto";
|
|
7844
|
-
import { existsSync as
|
|
7868
|
+
import { existsSync as existsSync18 } from "fs";
|
|
7845
7869
|
import { readFile as readFile16, stat as stat3 } from "fs/promises";
|
|
7846
7870
|
import path22 from "path";
|
|
7847
7871
|
|
|
7848
7872
|
// src/workspace.ts
|
|
7849
7873
|
import { mkdir as mkdir5, readFile as readFile13, rename as rename5, rm as rm6, writeFile as writeFile7 } from "fs/promises";
|
|
7850
|
-
import { existsSync as
|
|
7874
|
+
import { existsSync as existsSync16 } from "fs";
|
|
7851
7875
|
import path18 from "path";
|
|
7852
7876
|
import { z as z4 } from "zod";
|
|
7853
7877
|
var WorkspaceProjectSchema = z4.object({
|
|
@@ -7873,7 +7897,7 @@ ${parsed.error.issues.slice(0, 5).map((i) => ` ${i.path.join(".")}: ${i.message
|
|
|
7873
7897
|
);
|
|
7874
7898
|
}
|
|
7875
7899
|
async function loadWorkspace(workspacePath) {
|
|
7876
|
-
if (!
|
|
7900
|
+
if (!existsSync16(workspacePath)) return { version: 1, projects: [] };
|
|
7877
7901
|
let raw;
|
|
7878
7902
|
try {
|
|
7879
7903
|
raw = JSON.parse(await readFile13(workspacePath, "utf8"));
|
|
@@ -7925,14 +7949,14 @@ function validateWorkspace(ws, dir) {
|
|
|
7925
7949
|
message: `project "${project.id}" stores an absolute path \u2014 the catalog will not resolve correctly if this tree is cloned or moved elsewhere`
|
|
7926
7950
|
});
|
|
7927
7951
|
}
|
|
7928
|
-
if (!
|
|
7952
|
+
if (!existsSync16(manifestPath)) {
|
|
7929
7953
|
diagnostics.push({
|
|
7930
7954
|
id: "missing-manifest",
|
|
7931
7955
|
level: "error",
|
|
7932
7956
|
message: `project "${project.id}" manifest not found: ${manifestPath}`
|
|
7933
7957
|
});
|
|
7934
7958
|
}
|
|
7935
|
-
if (!
|
|
7959
|
+
if (!existsSync16(lockPath)) {
|
|
7936
7960
|
diagnostics.push({
|
|
7937
7961
|
id: "missing-lock",
|
|
7938
7962
|
level: "error",
|
|
@@ -8233,7 +8257,7 @@ async function applyManifestEdit(manifestPath, edit) {
|
|
|
8233
8257
|
|
|
8234
8258
|
// src/pipeline/hand-edit.ts
|
|
8235
8259
|
import { spawn as spawn2 } from "child_process";
|
|
8236
|
-
import { existsSync as
|
|
8260
|
+
import { existsSync as existsSync17 } from "fs";
|
|
8237
8261
|
import { copyFile, mkdir as mkdir6, readFile as readFile15, rename as rename7, rm as rm8, writeFile as writeFile9 } from "fs/promises";
|
|
8238
8262
|
import path20 from "path";
|
|
8239
8263
|
import { z as z6 } from "zod";
|
|
@@ -8246,36 +8270,59 @@ function handEditPath(loaded, spec) {
|
|
|
8246
8270
|
const file = inside ? relative : path20.basename(spec.outFile);
|
|
8247
8271
|
return path20.join(outDir, HAND_EDIT_DIR, file.replace(/\.gif$/i, ".png"));
|
|
8248
8272
|
}
|
|
8249
|
-
function
|
|
8273
|
+
function handEditBases(spec, lock) {
|
|
8250
8274
|
const entry = lock.entries[lockKey(spec.styleId, spec.assetId)];
|
|
8251
8275
|
if (entry) {
|
|
8252
|
-
if (entry.outputs.length !== 1) return null;
|
|
8253
|
-
const
|
|
8254
|
-
|
|
8255
|
-
|
|
8256
|
-
|
|
8276
|
+
if (entry.outputs.length !== 1 && !isMemberSetEntry(entry)) return null;
|
|
8277
|
+
const members = entry.outputs.map((output, index) => ({
|
|
8278
|
+
role: entry.outputs.length === 1 ? null : output.role ?? null,
|
|
8279
|
+
path: currentEntryOutputPath(entry, spec, index),
|
|
8280
|
+
mediaType: output.mediaType
|
|
8281
|
+
}));
|
|
8282
|
+
if (members.some((member) => member.mediaType && member.mediaType !== "image/png")) return null;
|
|
8283
|
+
if (entry.outputs.length === 1 && !primaryOutput(entry)) return null;
|
|
8284
|
+
if (!members.every((member) => existsSync17(member.path))) return null;
|
|
8285
|
+
return members.map(({ role, path: file }) => ({ role, path: file }));
|
|
8286
|
+
}
|
|
8287
|
+
return existsSync17(spec.outFile) ? [{ role: null, path: spec.outFile }] : null;
|
|
8288
|
+
}
|
|
8289
|
+
function handEditMembers(loaded, spec, lock, editPath) {
|
|
8290
|
+
const entry = lock.entries[lockKey(spec.styleId, spec.assetId)];
|
|
8291
|
+
const bases = handEditBases(spec, lock);
|
|
8292
|
+
if (entry && isMemberSetEntry(entry)) {
|
|
8293
|
+
return entry.outputs.map((output, index) => ({
|
|
8294
|
+
role: output.role ?? null,
|
|
8295
|
+
path: memberPath(editPath, output.role, index, entry.outputs.length, output.mediaType),
|
|
8296
|
+
base: bases?.[index]?.path ?? null
|
|
8297
|
+
}));
|
|
8257
8298
|
}
|
|
8258
|
-
return
|
|
8299
|
+
return [{ role: null, path: editPath, base: bases?.[0]?.path ?? null }];
|
|
8259
8300
|
}
|
|
8260
8301
|
async function startHandEdit(loaded, lock, spec, opts = {}) {
|
|
8261
8302
|
const entry = lock.entries[lockKey(spec.styleId, spec.assetId)];
|
|
8262
|
-
if (entry && entry.outputs.length > 1) {
|
|
8303
|
+
if (entry && entry.outputs.length > 1 && !isMemberSetEntry(entry)) {
|
|
8304
|
+
throw new ManifestEditError(
|
|
8305
|
+
`${spec.styleId}/${spec.assetId} is a set of ${entry.outputs.length} outputs that are not all PNG; hand edits cover PNG images and sets of them`
|
|
8306
|
+
);
|
|
8307
|
+
}
|
|
8308
|
+
if (entry && spec.source && isMemberSetEntry(entry) && !sourceIsStem(spec.source, entry, loaded.root)) {
|
|
8263
8309
|
throw new ManifestEditError(
|
|
8264
|
-
`${spec.styleId}/${spec.assetId}
|
|
8310
|
+
`${spec.styleId}/${spec.assetId} declares ${spec.source}, one committed file placed for the whole set; clear that source (pixelkiln edit detach) to hand-edit the members one by one`
|
|
8265
8311
|
);
|
|
8266
8312
|
}
|
|
8267
8313
|
const existing = spec.source ? path20.resolve(loaded.root, spec.source) : null;
|
|
8268
8314
|
const editPath = existing ?? handEditPath(loaded, spec);
|
|
8315
|
+
const members = handEditMembers(loaded, spec, lock, editPath);
|
|
8269
8316
|
let created = false;
|
|
8270
|
-
|
|
8271
|
-
|
|
8272
|
-
if (!base) {
|
|
8317
|
+
for (const member of members) {
|
|
8318
|
+
if (existsSync17(member.path)) continue;
|
|
8319
|
+
if (!member.base) {
|
|
8273
8320
|
throw new ManifestEditError(
|
|
8274
8321
|
`${spec.styleId}/${spec.assetId} has no generated PNG on disk to start from; generate or restore it first`
|
|
8275
8322
|
);
|
|
8276
8323
|
}
|
|
8277
|
-
await mkdir6(path20.dirname(
|
|
8278
|
-
await copyFile(base,
|
|
8324
|
+
await mkdir6(path20.dirname(member.path), { recursive: true });
|
|
8325
|
+
await copyFile(member.base, member.path);
|
|
8279
8326
|
created = true;
|
|
8280
8327
|
}
|
|
8281
8328
|
const source = portableOutputPath(editPath, loaded.root);
|
|
@@ -8292,7 +8339,7 @@ async function startHandEdit(loaded, lock, spec, opts = {}) {
|
|
|
8292
8339
|
declared = result.changed;
|
|
8293
8340
|
manifestSha256 = result.sha256;
|
|
8294
8341
|
}
|
|
8295
|
-
return { editPath, source, created, declared, manifestSha256 };
|
|
8342
|
+
return { editPath, members, source, created, declared, manifestSha256 };
|
|
8296
8343
|
}
|
|
8297
8344
|
async function detachHandEdit(loaded, spec, opts = {}) {
|
|
8298
8345
|
if (!spec.source) {
|
|
@@ -8306,19 +8353,44 @@ async function detachHandEdit(loaded, spec, opts = {}) {
|
|
|
8306
8353
|
});
|
|
8307
8354
|
return { manifestSha256: result.sha256, changed: result.changed };
|
|
8308
8355
|
}
|
|
8309
|
-
var
|
|
8310
|
-
|
|
8311
|
-
|
|
8356
|
+
var CompanionOutputSchema = z6.object({
|
|
8357
|
+
/** Output role for a set member; null for a single image. */
|
|
8358
|
+
role: z6.string().nullable(),
|
|
8359
|
+
/** sha256 of the generated file this member started from; null when it started from untracked art. */
|
|
8312
8360
|
basedOn: z6.string().regex(/^[0-9a-f]{64}$/).nullable(),
|
|
8313
|
-
/** sha256 of the
|
|
8314
|
-
sha256: z6.string().regex(/^[0-9a-f]{64}$/)
|
|
8361
|
+
/** sha256 of the member as saved, so a later change by another tool is visible. */
|
|
8362
|
+
sha256: z6.string().regex(/^[0-9a-f]{64}$/)
|
|
8363
|
+
}).strict();
|
|
8364
|
+
var CompanionV2Schema = z6.object({
|
|
8365
|
+
version: z6.literal(2),
|
|
8315
8366
|
/** Editor identity the bridge reported, e.g. `pixelorama@v1.2.2-stable`. */
|
|
8316
8367
|
editor: z6.string().min(1),
|
|
8317
8368
|
protocol: z6.number().int().positive(),
|
|
8318
8369
|
savedAt: z6.string().datetime(),
|
|
8319
8370
|
/** Basename of the layered project file beside the edit, when one was kept. */
|
|
8371
|
+
project: z6.string().min(1).nullable(),
|
|
8372
|
+
/** One entry per file, in output order. */
|
|
8373
|
+
outputs: z6.array(CompanionOutputSchema).min(1)
|
|
8374
|
+
}).strict();
|
|
8375
|
+
var CompanionV1Schema = z6.object({
|
|
8376
|
+
version: z6.literal(1),
|
|
8377
|
+
basedOn: z6.string().regex(/^[0-9a-f]{64}$/).nullable(),
|
|
8378
|
+
sha256: z6.string().regex(/^[0-9a-f]{64}$/),
|
|
8379
|
+
editor: z6.string().min(1),
|
|
8380
|
+
protocol: z6.number().int().positive(),
|
|
8381
|
+
savedAt: z6.string().datetime(),
|
|
8320
8382
|
project: z6.string().min(1).nullable()
|
|
8321
8383
|
}).strict();
|
|
8384
|
+
var HandEditCompanionSchema = z6.union([CompanionV2Schema, CompanionV1Schema]).transform(
|
|
8385
|
+
(value) => value.version === 2 ? value : {
|
|
8386
|
+
version: 2,
|
|
8387
|
+
editor: value.editor,
|
|
8388
|
+
protocol: value.protocol,
|
|
8389
|
+
savedAt: value.savedAt,
|
|
8390
|
+
project: value.project,
|
|
8391
|
+
outputs: [{ role: null, basedOn: value.basedOn, sha256: value.sha256 }]
|
|
8392
|
+
}
|
|
8393
|
+
);
|
|
8322
8394
|
function handEditCompanionPath(editPath) {
|
|
8323
8395
|
return editPath.replace(/\.png$/i, "") + ".edit.json";
|
|
8324
8396
|
}
|
|
@@ -8340,45 +8412,79 @@ async function readHandEditCompanion(editPath) {
|
|
|
8340
8412
|
}
|
|
8341
8413
|
var MAX_HAND_EDIT_BYTES = 16 * 1024 * 1024;
|
|
8342
8414
|
async function saveHandEdit(loaded, lock, spec, input) {
|
|
8343
|
-
|
|
8344
|
-
|
|
8345
|
-
}
|
|
8415
|
+
const entry = lock.entries[lockKey(spec.styleId, spec.assetId)];
|
|
8416
|
+
const memberSet = entry ? isMemberSetEntry(entry) : false;
|
|
8417
|
+
const incoming = input.frames ?? (input.png ? [{ role: null, png: input.png }] : []);
|
|
8418
|
+
if (!incoming.length) throw new ManifestEditError("the save carried no image");
|
|
8346
8419
|
if (input.project && input.project.length > MAX_HAND_EDIT_BYTES) {
|
|
8347
8420
|
throw new ManifestEditError(`the project file is ${input.project.length} bytes; the limit is ${MAX_HAND_EDIT_BYTES}`);
|
|
8348
8421
|
}
|
|
8349
|
-
|
|
8350
|
-
|
|
8351
|
-
|
|
8352
|
-
|
|
8353
|
-
|
|
8354
|
-
|
|
8422
|
+
if (memberSet) {
|
|
8423
|
+
const expected = entry.outputs.map((output) => output.role ?? null);
|
|
8424
|
+
const got = incoming.map((frame) => frame.role);
|
|
8425
|
+
if (got.length !== expected.length || expected.some((role, index) => got[index] !== role)) {
|
|
8426
|
+
throw new ManifestEditError(
|
|
8427
|
+
`${spec.styleId}/${spec.assetId} is a set of ${expected.length} members (${expected.join(", ")}); the editor returned ${got.length} (${got.map((role) => role ?? "new").join(", ")}) \u2014 keep the frames it opened with`
|
|
8428
|
+
);
|
|
8429
|
+
}
|
|
8430
|
+
} else if (incoming.length !== 1) {
|
|
8431
|
+
throw new ManifestEditError(`${spec.styleId}/${spec.assetId} is one image; the editor returned ${incoming.length} frames \u2014 keep a single frame`);
|
|
8355
8432
|
}
|
|
8356
|
-
const
|
|
8357
|
-
|
|
8358
|
-
|
|
8359
|
-
|
|
8360
|
-
|
|
8361
|
-
|
|
8433
|
+
const decoded = incoming.map((frame, index) => {
|
|
8434
|
+
if (frame.png.length > MAX_HAND_EDIT_BYTES) {
|
|
8435
|
+
throw new ManifestEditError(`the edit is ${frame.png.length} bytes; the limit is ${MAX_HAND_EDIT_BYTES}`);
|
|
8436
|
+
}
|
|
8437
|
+
try {
|
|
8438
|
+
validateMedia(frame.png, MediaType.PNG);
|
|
8439
|
+
return decodePng(frame.png);
|
|
8440
|
+
} catch (error) {
|
|
8441
|
+
const which = memberSet ? `member ${frame.role ?? index}` : "the edit";
|
|
8442
|
+
throw new ManifestEditError(`${which} is not a valid PNG: ${error instanceof Error ? error.message : String(error)}`);
|
|
8443
|
+
}
|
|
8444
|
+
});
|
|
8445
|
+
const bases = handEditBases(spec, lock);
|
|
8446
|
+
const expectedSize = bases?.[0] ? decodePng(await readFile15(bases[0].path)) : { width: spec.width, height: spec.height };
|
|
8447
|
+
for (const [index, image] of decoded.entries()) {
|
|
8448
|
+
if (image.width !== expectedSize.width || image.height !== expectedSize.height) {
|
|
8449
|
+
const which = memberSet ? `member ${incoming[index].role ?? index}` : "the edit";
|
|
8450
|
+
throw new ManifestEditError(
|
|
8451
|
+
`${which} is ${image.width}\xD7${image.height}; ${spec.styleId}/${spec.assetId} is ${expectedSize.width}\xD7${expectedSize.height}`
|
|
8452
|
+
);
|
|
8453
|
+
}
|
|
8362
8454
|
}
|
|
8363
8455
|
const started = await startHandEdit(loaded, lock, spec, { expectedSha256: input.expectedSha256 });
|
|
8364
|
-
const entry = lock.entries[lockKey(spec.styleId, spec.assetId)];
|
|
8365
|
-
const basedOn = entry ? primaryOutput(entry)?.sha256 ?? null : null;
|
|
8366
|
-
const digest2 = sha256(input.png);
|
|
8367
8456
|
const projectPath = input.project ? handEditProjectPath(started.editPath) : null;
|
|
8368
8457
|
const companion = {
|
|
8369
|
-
version:
|
|
8370
|
-
basedOn,
|
|
8371
|
-
sha256: digest2,
|
|
8458
|
+
version: 2,
|
|
8372
8459
|
editor: input.editor,
|
|
8373
8460
|
protocol: input.protocol,
|
|
8374
8461
|
savedAt: (input.now ?? /* @__PURE__ */ new Date()).toISOString(),
|
|
8375
|
-
project: projectPath ? path20.basename(projectPath) : null
|
|
8462
|
+
project: projectPath ? path20.basename(projectPath) : null,
|
|
8463
|
+
outputs: incoming.map((frame, index) => ({
|
|
8464
|
+
role: frame.role,
|
|
8465
|
+
basedOn: basedOnHash(entry, index),
|
|
8466
|
+
sha256: sha256(frame.png)
|
|
8467
|
+
}))
|
|
8376
8468
|
};
|
|
8377
|
-
|
|
8469
|
+
for (const [index, member] of started.members.entries()) {
|
|
8470
|
+
await replaceFile(member.path, incoming[index].png);
|
|
8471
|
+
}
|
|
8378
8472
|
if (projectPath && input.project) await replaceFile(projectPath, input.project);
|
|
8379
8473
|
const companionPath = handEditCompanionPath(started.editPath);
|
|
8380
8474
|
await replaceFile(companionPath, JSON.stringify(companion, null, 2) + "\n");
|
|
8381
|
-
return {
|
|
8475
|
+
return {
|
|
8476
|
+
...started,
|
|
8477
|
+
sha256: companion.outputs[0].sha256,
|
|
8478
|
+
basedOn: companion.outputs[0].basedOn,
|
|
8479
|
+
companion,
|
|
8480
|
+
companionPath,
|
|
8481
|
+
projectPath
|
|
8482
|
+
};
|
|
8483
|
+
}
|
|
8484
|
+
function basedOnHash(entry, index) {
|
|
8485
|
+
if (!entry) return null;
|
|
8486
|
+
if (entry.outputs.length === 1) return primaryOutput(entry)?.sha256 ?? null;
|
|
8487
|
+
return entry.outputs[index]?.sha256 ?? null;
|
|
8382
8488
|
}
|
|
8383
8489
|
async function replaceFile(file, bytes) {
|
|
8384
8490
|
const tmp = `${file}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
@@ -8425,7 +8531,10 @@ var HandEditRequestSchema = z7.discriminatedUnion("action", [
|
|
|
8425
8531
|
assetId: z7.string().min(1),
|
|
8426
8532
|
styleId: z7.string().min(1),
|
|
8427
8533
|
expectedSha256: z7.string().regex(/^[0-9a-f]{64}$/),
|
|
8428
|
-
|
|
8534
|
+
/** The flattened image; for a frame set, `frames` instead. */
|
|
8535
|
+
png: Base64.min(1).optional(),
|
|
8536
|
+
/** Every frame of a set, tagged with the role it was opened under (null for one added in the editor). */
|
|
8537
|
+
frames: z7.array(z7.object({ role: z7.string().min(1).nullable(), png: Base64.min(1) }).strict()).min(1).max(256).optional(),
|
|
8429
8538
|
/** The editor's layered project file, kept beside the edit. */
|
|
8430
8539
|
pxo: Base64.optional(),
|
|
8431
8540
|
/** Editor identity from the bridge's ready message. */
|
|
@@ -8448,18 +8557,23 @@ function createGalleryEditHandler(opts) {
|
|
|
8448
8557
|
const started = await startHandEdit(ctx.loaded, ctx.lock, spec, { expectedSha256: request.expectedSha256 });
|
|
8449
8558
|
log2(` hand edit ${started.created ? "created" : "found"}: ${started.source}${started.declared ? " (declared in the manifest)" : ""}`);
|
|
8450
8559
|
if (request.open) {
|
|
8451
|
-
const command = (opts.openEditor ?? openInEditor)(started.
|
|
8560
|
+
const command = (opts.openEditor ?? openInEditor)(started.members[0].path);
|
|
8452
8561
|
log2(` opened with: ${command}`);
|
|
8453
8562
|
}
|
|
8454
8563
|
} else if (request.action === "save-edit") {
|
|
8564
|
+
if (request.png !== void 0 === (request.frames !== void 0)) {
|
|
8565
|
+
throw new ManifestEditError("invalid edit: send png for one image or frames for a set, not both");
|
|
8566
|
+
}
|
|
8455
8567
|
const saved = await saveHandEdit(ctx.loaded, ctx.lock, spec, {
|
|
8456
|
-
png: Buffer.from(request.png, "base64"),
|
|
8568
|
+
...request.png !== void 0 ? { png: Buffer.from(request.png, "base64") } : {},
|
|
8569
|
+
...request.frames ? { frames: request.frames.map((frame) => ({ role: frame.role, png: Buffer.from(frame.png, "base64") })) } : {},
|
|
8457
8570
|
...request.pxo ? { project: Buffer.from(request.pxo, "base64") } : {},
|
|
8458
8571
|
editor: request.editor,
|
|
8459
8572
|
protocol: request.protocol,
|
|
8460
8573
|
expectedSha256: request.expectedSha256
|
|
8461
8574
|
});
|
|
8462
|
-
|
|
8575
|
+
const what = saved.members.length > 1 ? `${saved.source} (${saved.members.length} frames)` : saved.source;
|
|
8576
|
+
log2(` hand edit saved from ${request.editor}: ${what}${saved.declared ? " (declared in the manifest)" : ""}${saved.projectPath ? " + project file" : ""}`);
|
|
8463
8577
|
} else {
|
|
8464
8578
|
const result2 = await detachHandEdit(ctx.loaded, spec, { expectedSha256: request.expectedSha256 });
|
|
8465
8579
|
if (result2.changed) log2(` hand edit detached: ${request.styleId}/${request.assetId} (file kept)`);
|
|
@@ -8507,6 +8621,18 @@ function matchesFilter(filter, styleId, assetId) {
|
|
|
8507
8621
|
if (filter?.assets?.length && !filter.assets.includes(assetId)) return false;
|
|
8508
8622
|
return true;
|
|
8509
8623
|
}
|
|
8624
|
+
var MAX_COMPARED_BYTES = 8 * 1024 * 1024;
|
|
8625
|
+
async function samePixels(a, b) {
|
|
8626
|
+
try {
|
|
8627
|
+
const [left, right] = await Promise.all([readFile16(a), readFile16(b)]);
|
|
8628
|
+
if (left.length > MAX_COMPARED_BYTES || right.length > MAX_COMPARED_BYTES) return false;
|
|
8629
|
+
const x = decodePng(left);
|
|
8630
|
+
const y = decodePng(right);
|
|
8631
|
+
return x.width === y.width && x.height === y.height && x.pixels.equals(y.pixels);
|
|
8632
|
+
} catch {
|
|
8633
|
+
return false;
|
|
8634
|
+
}
|
|
8635
|
+
}
|
|
8510
8636
|
function metadataFps(entry) {
|
|
8511
8637
|
if (!entry) return null;
|
|
8512
8638
|
const namespace = entry.providerMetadata?.[entry.provider];
|
|
@@ -8554,7 +8680,7 @@ async function describeQuality(media, root, inspection) {
|
|
|
8554
8680
|
outputPaths.map((file) => describeOutput(media, root, path22.resolve(root, file)))
|
|
8555
8681
|
);
|
|
8556
8682
|
const recordPath = path22.resolve(root, inspection.record);
|
|
8557
|
-
const recordExists =
|
|
8683
|
+
const recordExists = existsSync18(recordPath);
|
|
8558
8684
|
let options = null;
|
|
8559
8685
|
let check = null;
|
|
8560
8686
|
if (recordExists) {
|
|
@@ -8624,6 +8750,8 @@ async function buildGallerySnapshot(opts) {
|
|
|
8624
8750
|
const asset = loaded.manifest.assets[spec.assetId] ?? null;
|
|
8625
8751
|
let outputs;
|
|
8626
8752
|
let edit = null;
|
|
8753
|
+
let edits = [];
|
|
8754
|
+
let editChanged = [];
|
|
8627
8755
|
let editStatus = null;
|
|
8628
8756
|
let editMeta = null;
|
|
8629
8757
|
if (entry && entry.outputs.length) {
|
|
@@ -8634,13 +8762,19 @@ async function buildGallerySnapshot(opts) {
|
|
|
8634
8762
|
);
|
|
8635
8763
|
if (spec.source) {
|
|
8636
8764
|
const editPath = path22.resolve(root, spec.source);
|
|
8637
|
-
const
|
|
8638
|
-
|
|
8639
|
-
|
|
8765
|
+
const memberCount = sourceIsStem(spec.source, entry, root) ? entry.outputs.length : 1;
|
|
8766
|
+
const editShas = await Promise.all(
|
|
8767
|
+
Array.from({ length: memberCount }, async (_, index) => {
|
|
8768
|
+
const file = sourceOutputPath(spec.source, entry, index, root);
|
|
8769
|
+
return { file, sha256: existsSync18(file) ? await sha256File(file) : null };
|
|
8770
|
+
})
|
|
8771
|
+
);
|
|
8772
|
+
edits = await Promise.all(editShas.map(({ file, sha256: editSha }) => describeOutput(media, root, file, { sha256: editSha })));
|
|
8773
|
+
edit = edits[0] ?? null;
|
|
8640
8774
|
const companion = await readHandEditCompanion(editPath);
|
|
8641
8775
|
if (companion) {
|
|
8642
8776
|
const projectPath = handEditProjectPath(editPath);
|
|
8643
|
-
const project = companion.project &&
|
|
8777
|
+
const project = companion.project && existsSync18(projectPath) ? await fileInfo(projectPath) : null;
|
|
8644
8778
|
let projectUrl = null;
|
|
8645
8779
|
if (project) {
|
|
8646
8780
|
const id = galleryMediaId(projectPath);
|
|
@@ -8650,18 +8784,34 @@ async function buildGallerySnapshot(opts) {
|
|
|
8650
8784
|
editMeta = {
|
|
8651
8785
|
editor: companion.editor,
|
|
8652
8786
|
savedAt: companion.savedAt,
|
|
8653
|
-
basedOn: companion.basedOn,
|
|
8654
|
-
changedSince:
|
|
8787
|
+
basedOn: companion.outputs[0]?.basedOn ?? null,
|
|
8788
|
+
changedSince: editShas.some(({ sha256: editSha }, index) => {
|
|
8789
|
+
const recorded = companion.outputs[index]?.sha256;
|
|
8790
|
+
return editSha !== null && recorded !== void 0 && editSha !== recorded;
|
|
8791
|
+
}),
|
|
8655
8792
|
project: project ? portableOutputPath(projectPath, root) : null,
|
|
8656
8793
|
projectUrl
|
|
8657
8794
|
};
|
|
8658
8795
|
}
|
|
8659
|
-
const regenerated =
|
|
8660
|
-
|
|
8796
|
+
const regenerated = edits.some((member, index) => {
|
|
8797
|
+
const generated = outputs[index];
|
|
8798
|
+
if (companion) {
|
|
8799
|
+
const recorded = companion.outputs[index];
|
|
8800
|
+
return recorded !== void 0 && generated.sha256 !== null && recorded.basedOn !== generated.sha256;
|
|
8801
|
+
}
|
|
8802
|
+
return Boolean(generated.modifiedAt && member.modifiedAt && generated.modifiedAt > member.modifiedAt && generated.exists);
|
|
8803
|
+
});
|
|
8804
|
+
editChanged = await Promise.all(edits.map(async (member, index) => {
|
|
8805
|
+
const generated = outputs[index];
|
|
8806
|
+
if (!member.exists || !generated.exists) return true;
|
|
8807
|
+
if (member.sha256 === generated.sha256) return false;
|
|
8808
|
+
return !await samePixels(member.absolutePath, generated.absolutePath);
|
|
8809
|
+
}));
|
|
8810
|
+
editStatus = edits.some((member) => !member.exists) ? "missing" : editChanged.every((changed) => !changed) ? "same" : regenerated ? "regenerated-since" : "edited";
|
|
8661
8811
|
}
|
|
8662
8812
|
} else if (spec.source) {
|
|
8663
8813
|
outputs = [await describeOutput(media, root, path22.resolve(root, spec.source))];
|
|
8664
|
-
} else if (!entry &&
|
|
8814
|
+
} else if (!entry && existsSync18(spec.outFile)) {
|
|
8665
8815
|
outputs = [await describeOutput(media, root, spec.outFile)];
|
|
8666
8816
|
} else {
|
|
8667
8817
|
outputs = [];
|
|
@@ -8706,6 +8856,8 @@ async function buildGallerySnapshot(opts) {
|
|
|
8706
8856
|
asset,
|
|
8707
8857
|
source: spec.source ?? null,
|
|
8708
8858
|
edit,
|
|
8859
|
+
edits,
|
|
8860
|
+
editChanged,
|
|
8709
8861
|
editStatus,
|
|
8710
8862
|
editMeta,
|
|
8711
8863
|
upstreamUrl: entry?.provider === "pixellab" ? pixelLabObjectUrl(entry.generator, entry.objectId) : null,
|
|
@@ -8763,6 +8915,8 @@ async function buildGallerySnapshot(opts) {
|
|
|
8763
8915
|
asset: null,
|
|
8764
8916
|
source: null,
|
|
8765
8917
|
edit: null,
|
|
8918
|
+
edits: [],
|
|
8919
|
+
editChanged: [],
|
|
8766
8920
|
editStatus: null,
|
|
8767
8921
|
editMeta: null,
|
|
8768
8922
|
upstreamUrl: entry.provider === "pixellab" ? pixelLabObjectUrl(entry.generator, entry.objectId) : null,
|
|
@@ -9697,10 +9851,15 @@ window.addEventListener('message', (e) => {
|
|
|
9697
9851
|
// declares the file the same way pixelkiln edit does.
|
|
9698
9852
|
|
|
9699
9853
|
let SHEET = null;
|
|
9700
|
-
|
|
9701
|
-
|
|
9702
|
-
const
|
|
9703
|
-
|
|
9854
|
+
// What the editor gets: the edit files when they are all there, else the
|
|
9855
|
+
// generated outputs (one PNG, or every frame of a set).
|
|
9856
|
+
const editorSources = (item) => {
|
|
9857
|
+
if (item.edits.length && item.edits.every((e) => e.exists)) return { files: item.edits, fromEdit: true };
|
|
9858
|
+
if (editableOutputs(item)) return { files: item.outputs, fromEdit: false };
|
|
9859
|
+
return null;
|
|
9860
|
+
};
|
|
9861
|
+
const editorSource = (item) => { const s = editorSources(item); return s ? s.files[0] : null; };
|
|
9862
|
+
const canEditInBrowser = (item) => EDITOR && EDITABLE && item.declared && item.asset && projectOf(item)?.manifestSha256 && editorSources(item);
|
|
9704
9863
|
const toBase64 = (buf) => new Promise((resolve, reject) => {
|
|
9705
9864
|
const r = new FileReader();
|
|
9706
9865
|
r.onload = () => resolve(String(r.result).slice(String(r.result).indexOf(',') + 1));
|
|
@@ -9729,7 +9888,7 @@ function openEditorSheet(item) {
|
|
|
9729
9888
|
const bar = el('div', 'rbar');
|
|
9730
9889
|
const close = el('button', null, 'Close'); close.type = 'button';
|
|
9731
9890
|
close.onclick = () => closeEditorSheet();
|
|
9732
|
-
const title = el('b', null, (item.project ? item.project + ':' : '') + item.styleId + '/' + item.assetId + ' \xB7 ' + item.width + '\xD7' + item.height);
|
|
9891
|
+
const title = el('b', null, (item.project ? item.project + ':' : '') + item.styleId + '/' + item.assetId + ' \xB7 ' + item.width + '\xD7' + item.height + (item.outputs.length > 1 ? ' \xB7 ' + item.outputs.length + ' ' + memberNoun(item, item.outputs.length) : ''));
|
|
9733
9892
|
const status = el('span', 'st');
|
|
9734
9893
|
const acts = el('div', 'acts');
|
|
9735
9894
|
const msg = el('span', 'msg');
|
|
@@ -9750,23 +9909,34 @@ function openEditorSheet(item) {
|
|
|
9750
9909
|
stage.append(frame, loading);
|
|
9751
9910
|
panel.append(bar, stage);
|
|
9752
9911
|
host.append(scrim, panel);
|
|
9753
|
-
SHEET = { item, frame, loading, status, msg, save, saveClose, dirty: false, opened: false, ready: null, pending: null, requests: 0, source: src, sentProject: false };
|
|
9912
|
+
SHEET = { item, frame, loading, status, msg, save, saveClose, dirty: false, opened: false, ready: null, pending: null, requests: 0, source: src, sentProject: false, roles: null };
|
|
9754
9913
|
sheetStatus('cool', 'loading');
|
|
9755
9914
|
sheetButtons();
|
|
9756
9915
|
close.focus({ preventScroll: true });
|
|
9757
9916
|
}
|
|
9758
|
-
// The edit
|
|
9759
|
-
// project beside
|
|
9760
|
-
// falling back to the flattened
|
|
9917
|
+
// The edit files are what the editor gets; when a browser save kept the
|
|
9918
|
+
// layered project beside them, that goes along too and the editor restores
|
|
9919
|
+
// the layers, falling back to the flattened PNGs if the file cannot be read.
|
|
9920
|
+
// A frame set is sent as frames, one per member, and comes back the same way.
|
|
9761
9921
|
async function sendOpen() {
|
|
9762
|
-
const { item
|
|
9922
|
+
const { item } = SHEET;
|
|
9923
|
+
const sources = editorSources(item);
|
|
9763
9924
|
const style = snap.styles.find((s) => s.id === item.styleId && s.project === item.project);
|
|
9764
|
-
const projectUrl =
|
|
9765
|
-
|
|
9925
|
+
const projectUrl = sources.fromEdit && item.editMeta && item.editMeta.projectUrl && SHEET.ready.version >= 2 ? item.editMeta.projectUrl : null;
|
|
9926
|
+
const set = sources.files.length > 1;
|
|
9927
|
+
if (set && SHEET.ready.version < 3) {
|
|
9928
|
+
SHEET.msg.textContent = 'This editor build cannot open sets; reinstall it with pixelkiln tools install editor.';
|
|
9929
|
+
sheetStatus('bad', 'unsupported');
|
|
9930
|
+
return;
|
|
9931
|
+
}
|
|
9932
|
+
const pngs = [];
|
|
9933
|
+
let pxo = null;
|
|
9766
9934
|
try {
|
|
9767
|
-
const
|
|
9768
|
-
|
|
9769
|
-
|
|
9935
|
+
for (const file of sources.files) {
|
|
9936
|
+
const res = await fetch(file.url, { cache: 'no-store' });
|
|
9937
|
+
if (!res.ok) throw new Error(await res.text());
|
|
9938
|
+
pngs.push(await res.arrayBuffer());
|
|
9939
|
+
}
|
|
9770
9940
|
if (projectUrl) {
|
|
9771
9941
|
const p = await fetch(projectUrl, { cache: 'no-store' });
|
|
9772
9942
|
if (p.ok) pxo = await p.arrayBuffer();
|
|
@@ -9778,12 +9948,22 @@ async function sendOpen() {
|
|
|
9778
9948
|
if (!SHEET) return;
|
|
9779
9949
|
const request = 'open-' + (++SHEET.requests);
|
|
9780
9950
|
SHEET.sentProject = !!pxo;
|
|
9951
|
+
SHEET.roles = set ? item.outputs.map((o) => o.role || null) : null;
|
|
9781
9952
|
const message = {
|
|
9782
9953
|
type: 'pixelkiln:open', request,
|
|
9783
9954
|
asset: { key: item.styleId + '/' + item.assetId, id: item.id, name: item.assetId, width: item.width, height: item.height },
|
|
9784
|
-
png, palette: style ? style.palette : [],
|
|
9955
|
+
png: pngs[0], palette: style ? style.palette : [],
|
|
9785
9956
|
};
|
|
9786
|
-
const transfer = [
|
|
9957
|
+
const transfer = [pngs[0]];
|
|
9958
|
+
if (set) {
|
|
9959
|
+
message.frames = pngs.map((png, i) => ({ role: SHEET.roles[i], png }));
|
|
9960
|
+
// A tile set is not an animation; a slow rate keeps its timeline still-ish.
|
|
9961
|
+
message.fps = isFrameSet(item) ? (item.fps || 12) : 1;
|
|
9962
|
+
for (const png of pngs.slice(1)) transfer.push(png);
|
|
9963
|
+
// The first frame is both png (older editors) and frames[0]; copy it so both transfer.
|
|
9964
|
+
message.png = pngs[0].slice(0);
|
|
9965
|
+
transfer.push(message.png);
|
|
9966
|
+
}
|
|
9787
9967
|
if (pxo) { message.pxo = pxo; transfer.push(pxo); }
|
|
9788
9968
|
SHEET.frame.contentWindow.postMessage(message, location.origin, transfer);
|
|
9789
9969
|
sheetStatus('cool', 'opening');
|
|
@@ -9811,8 +9991,9 @@ async function onEditorMessage(m) {
|
|
|
9811
9991
|
case 'pixelkiln:opened':
|
|
9812
9992
|
SHEET.opened = true;
|
|
9813
9993
|
SHEET.loading.remove();
|
|
9814
|
-
if (m.source === 'pxo') sheetStatus('ok', 'editing the edit file
|
|
9815
|
-
else if (SHEET.sentProject) sheetStatus('warn', 'editing the flattened edit file;
|
|
9994
|
+
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');
|
|
9995
|
+
else if (SHEET.sentProject) sheetStatus('warn', 'editing the flattened edit file' + (m.frames > 1 ? 's' : '') + '; the layer file could not be opened');
|
|
9996
|
+
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)));
|
|
9816
9997
|
else sheetStatus('ok', SHEET.item.edit && SHEET.item.edit.exists ? 'editing the edit file' : 'editing a copy of the generated art');
|
|
9817
9998
|
sheetButtons();
|
|
9818
9999
|
break;
|
|
@@ -9825,14 +10006,21 @@ async function onEditorMessage(m) {
|
|
|
9825
10006
|
const pending = SHEET.pending;
|
|
9826
10007
|
if (!pending || pending.request !== m.request) return;
|
|
9827
10008
|
try {
|
|
9828
|
-
const body = {
|
|
10009
|
+
const body = { editor: SHEET.ready.editor, protocol: SHEET.ready.version };
|
|
10010
|
+
if (SHEET.roles) {
|
|
10011
|
+
if (!Array.isArray(m.frames)) throw new Error('the editor returned no frames');
|
|
10012
|
+
body.frames = [];
|
|
10013
|
+
for (const f of m.frames) body.frames.push({ role: f.role === undefined ? null : f.role, png: await toBase64(f.png) });
|
|
10014
|
+
} else {
|
|
10015
|
+
body.png = await toBase64(m.png);
|
|
10016
|
+
}
|
|
9829
10017
|
if (m.pxo && m.pxo.byteLength) body.pxo = await toBase64(m.pxo);
|
|
9830
10018
|
await postHandEdit(SHEET.item, 'save-edit', body);
|
|
9831
10019
|
const saved = snap.items.find((i) => i.id === SHEET.item.id);
|
|
9832
10020
|
if (saved) SHEET.item = saved;
|
|
9833
10021
|
SHEET.dirty = false;
|
|
9834
10022
|
SHEET.pending = null;
|
|
9835
|
-
ui.notice = { id: SHEET.item.id, text: 'Saved ' + (saved && saved.edit ? saved.edit.path : 'the edit') + '. mount and pack place it in place of the generated art.' };
|
|
10023
|
+
ui.notice = { id: SHEET.item.id, text: 'Saved ' + (saved && saved.edits.length > 1 ? saved.edits.length + ' ' + memberNoun(saved, saved.edits.length) + ' to ' + saved.source : saved && saved.edit ? saved.edit.path : 'the edit') + '. mount and pack place ' + (saved && saved.edits.length > 1 ? 'them' : 'it') + ' in place of the generated art.' };
|
|
9836
10024
|
render();
|
|
9837
10025
|
if (pending.close) { closeEditorSheet(true); return; }
|
|
9838
10026
|
sheetStatus('ok', 'saved');
|
|
@@ -9980,7 +10168,7 @@ function clearFilters() {
|
|
|
9980
10168
|
function thumb(item) {
|
|
9981
10169
|
const cell = el('div', 'cell');
|
|
9982
10170
|
// A hand edit is what ships, so it is what the card shows.
|
|
9983
|
-
const shown = item.edit && item.
|
|
10171
|
+
const shown = item.edit && item.editStatus === 'edited' && item.edits.every((e) => e.url) ? item.edits : item.outputs.filter((o) => o.url);
|
|
9984
10172
|
if (!shown.length) {
|
|
9985
10173
|
cell.classList.add('none');
|
|
9986
10174
|
cell.append(el('span', null, item.state === 'missing' ? 'not generated yet'
|
|
@@ -10394,7 +10582,7 @@ function openCompare() {
|
|
|
10394
10582
|
// ---- hand edits -------------------------------------------------------------
|
|
10395
10583
|
|
|
10396
10584
|
const EDIT_STATUS_TEXT = {
|
|
10397
|
-
same: '
|
|
10585
|
+
same: 'The generated art\u2019s pixels, not changed yet. Open it in your editor and save.',
|
|
10398
10586
|
edited: 'Differs from the generated art. mount and pack place this file; the generated file stays as the record.',
|
|
10399
10587
|
'regenerated-since': 'The generated art changed after this edit was saved \u2014 the edit is based on an older generation.',
|
|
10400
10588
|
missing: 'The declared file is not on disk.',
|
|
@@ -10405,12 +10593,18 @@ async function postHandEdit(item, action, extra) {
|
|
|
10405
10593
|
if (item.project) body.project = item.project;
|
|
10406
10594
|
snap = await postEdit(body);
|
|
10407
10595
|
}
|
|
10596
|
+
// A single PNG or a set of them \u2014 frames, tiles \u2014 is hand-editable, one file
|
|
10597
|
+
// per member; a set with a GIF in it is not.
|
|
10598
|
+
const editableOutputs = (item) => item.outputs.length && item.outputs.every((o) => o.exists && o.mediaType === 'image/png');
|
|
10599
|
+
// What the members of a set are called: frames for an animation, tiles for a tile set.
|
|
10600
|
+
const memberNoun = (item, n) => isFrameSet(item) ? (n === 1 ? 'frame' : 'frames')
|
|
10601
|
+
: item.outputs.every((o) => o.role && /^tile/i.test(o.role)) ? (n === 1 ? 'tile' : 'tiles') : (n === 1 ? 'member' : 'members');
|
|
10408
10602
|
function handEditSection(item) {
|
|
10409
|
-
const canStart = EDITABLE && item.declared && item.asset && projectOf(item)?.manifestSha256 &&
|
|
10410
|
-
item.outputs.length === 1 && item.outputs[0].exists && item.outputs[0].mediaType === 'image/png';
|
|
10603
|
+
const canStart = EDITABLE && item.declared && item.asset && projectOf(item)?.manifestSha256 && editableOutputs(item);
|
|
10411
10604
|
if (!item.edit && !canStart) return null;
|
|
10605
|
+
const set = item.edits.length > 1;
|
|
10412
10606
|
const s = el('section', 'meta');
|
|
10413
|
-
s.append(el('h3', null, 'Hand edit'));
|
|
10607
|
+
s.append(el('h3', null, set ? 'Hand edit (' + item.edits.length + ' ' + memberNoun(item, item.edits.length) + ')' : 'Hand edit'));
|
|
10414
10608
|
if (item.edit) {
|
|
10415
10609
|
const pair = el('div', 'pair');
|
|
10416
10610
|
const shared = displayScale(item.width, item.height, 200, 160);
|
|
@@ -10424,24 +10618,36 @@ function handEditSection(item) {
|
|
|
10424
10618
|
f.append(el('figcaption', null, label));
|
|
10425
10619
|
return f;
|
|
10426
10620
|
};
|
|
10427
|
-
|
|
10621
|
+
// For a set, the member the preview is on; the strip there steps through them.
|
|
10622
|
+
const at = set ? Math.min(ui.member, item.edits.length - 1) : 0;
|
|
10623
|
+
const which = set ? ' \xB7 ' + (item.outputs[at].role || memberNoun(item, 1) + ' ' + (at + 1)) : '';
|
|
10624
|
+
pair.append(fig(item.outputs[at], 'generated' + which), fig(item.edits[at], 'edit' + which));
|
|
10428
10625
|
s.append(pair);
|
|
10429
10626
|
const dl = el('dl');
|
|
10430
10627
|
const tone = item.editStatus === 'edited' ? 'ok' : item.editStatus === 'same' ? 'dim' : 'warn';
|
|
10431
10628
|
const st = el('span', 'state-' + tone); st.append(el('i', 'dot ' + tone), document.createTextNode(' ' + item.editStatus));
|
|
10432
10629
|
row(dl, 'status', st);
|
|
10433
10630
|
row(dl, 'why', EDIT_STATUS_TEXT[item.editStatus] || '');
|
|
10434
|
-
|
|
10435
|
-
|
|
10436
|
-
|
|
10631
|
+
if (set) {
|
|
10632
|
+
row(dl, 'files', item.source + ' \u2014 one per ' + memberNoun(item, 1) + ': ' + item.edits.map((e) => e.path.split('/').pop()).join(', '), { mono: true, copy: item.edits.map((e) => e.absolutePath).join('\\n') });
|
|
10633
|
+
const differing = item.editChanged.filter(Boolean).length;
|
|
10634
|
+
row(dl, 'changed', differing + ' of ' + item.edits.length + ' ' + memberNoun(item, item.edits.length) + ' differ from the generated art' +
|
|
10635
|
+
(differing ? ': ' + item.edits.filter((e, i) => item.editChanged[i]).map((e) => item.outputs[item.edits.indexOf(e)].role || e.path.split('/').pop()).join(', ') : ''));
|
|
10636
|
+
} else {
|
|
10637
|
+
row(dl, 'file', item.edit.path, { mono: true, copy: item.edit.absolutePath });
|
|
10638
|
+
if (item.edit.sha256) row(dl, 'sha256', item.edit.sha256.slice(0, 16) + '\u2026', { mono: true, copy: item.edit.sha256 });
|
|
10639
|
+
}
|
|
10640
|
+
row(dl, 'saved', fmtWhen(item.edits.reduce((latest, e) => e.modifiedAt && (!latest || e.modifiedAt > latest) ? e.modifiedAt : latest, null)));
|
|
10437
10641
|
if (item.editMeta) {
|
|
10438
|
-
row(dl, 'editor', item.editMeta.editor + ', ' + fmtWhen(item.editMeta.savedAt) + (item.editMeta.changedSince ? ' \u2014
|
|
10439
|
-
if (item.editMeta.basedOn) row(dl, 'based on', item.editMeta.basedOn.slice(0, 16) + '\u2026', { mono: true, copy: item.editMeta.basedOn });
|
|
10642
|
+
row(dl, 'editor', item.editMeta.editor + ', ' + fmtWhen(item.editMeta.savedAt) + (item.editMeta.changedSince ? ' \u2014 a file changed since' : ''));
|
|
10643
|
+
if (item.editMeta.basedOn && !set) row(dl, 'based on', item.editMeta.basedOn.slice(0, 16) + '\u2026', { mono: true, copy: item.editMeta.basedOn });
|
|
10440
10644
|
if (item.editMeta.project) row(dl, 'layers', item.editMeta.project, { mono: true });
|
|
10441
10645
|
}
|
|
10442
10646
|
s.append(dl);
|
|
10443
10647
|
} else {
|
|
10444
|
-
s.append(el('div', 'state-dim',
|
|
10648
|
+
s.append(el('div', 'state-dim', item.outputs.length > 1
|
|
10649
|
+
? 'Touch the ' + memberNoun(item, 2) + ' up in your own editor or in the browser, where they open as ' + (isFrameSet(item) ? 'one animation' : 'the frames of one project') + '. The generated files stay untouched; the edit is one sibling file per ' + memberNoun(item, 1) + ' that the manifest points at, and mount, pack, and export place those instead.'
|
|
10650
|
+
: '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.'));
|
|
10445
10651
|
}
|
|
10446
10652
|
if (EDITABLE && item.declared && projectOf(item)?.manifestSha256) {
|
|
10447
10653
|
const acts = el('div', 'hand-actions');
|
|
@@ -10469,10 +10675,11 @@ function handEditSection(item) {
|
|
|
10469
10675
|
}
|
|
10470
10676
|
if (item.edit) {
|
|
10471
10677
|
go('Open in desktop editor', 'start-edit', { open: true }, 'Opened ' + item.edit.path + ' in your editor. Save there, then Refresh.');
|
|
10472
|
-
go('Detach edit', 'detach-edit', {}, 'Detached. The file is still at ' + item.
|
|
10678
|
+
go('Detach edit', 'detach-edit', {}, 'Detached. The file' + (set ? 's are' : ' is') + ' still at ' + item.source + '; the generated art is placed again.');
|
|
10473
10679
|
} else if (canStart) {
|
|
10474
|
-
|
|
10475
|
-
go('
|
|
10680
|
+
const many = item.outputs.length > 1;
|
|
10681
|
+
go('Edit by hand', 'start-edit', { open: true }, 'Created the edit file' + (many ? 's and opened the first ' + memberNoun(item, 1) : ' and opened it') + ' in your editor. Save there, then Refresh to see it here.');
|
|
10682
|
+
go('Create edit file' + (many ? 's' : '') + ' only', 'start-edit', { open: false }, 'Created the edit file' + (many ? 's' : '') + ' and declared ' + (many ? 'them' : 'it') + ' in the manifest.');
|
|
10476
10683
|
}
|
|
10477
10684
|
acts.append(msg);
|
|
10478
10685
|
s.append(acts);
|
|
@@ -11775,7 +11982,7 @@ import path24 from "path";
|
|
|
11775
11982
|
|
|
11776
11983
|
// src/editor/install.ts
|
|
11777
11984
|
import { createHash as createHash4 } from "crypto";
|
|
11778
|
-
import { existsSync as
|
|
11985
|
+
import { existsSync as existsSync19 } from "fs";
|
|
11779
11986
|
import { mkdir as mkdir7, readFile as readFile18, rename as rename8, rm as rm9, stat as stat4, writeFile as writeFile10 } from "fs/promises";
|
|
11780
11987
|
import os2 from "os";
|
|
11781
11988
|
import path23 from "path";
|
|
@@ -11785,8 +11992,8 @@ var pin_default = {
|
|
|
11785
11992
|
pixelorama: "v1.2.2",
|
|
11786
11993
|
godot: "4.7.2",
|
|
11787
11994
|
extensionsApi: 9,
|
|
11788
|
-
protocol:
|
|
11789
|
-
release: "editor-pixelorama-v1.2.2-pk.
|
|
11995
|
+
protocol: 3,
|
|
11996
|
+
release: "editor-pixelorama-v1.2.2-pk.14",
|
|
11790
11997
|
files: {
|
|
11791
11998
|
"index.apple-touch-icon.png": {
|
|
11792
11999
|
sha256: "ee5434873e0fed91e9a888c3ea5a8b79beac25999308be7a6eb41bf4ce640d27",
|
|
@@ -11801,7 +12008,7 @@ var pin_default = {
|
|
|
11801
12008
|
bytes: 7298
|
|
11802
12009
|
},
|
|
11803
12010
|
"index.html": {
|
|
11804
|
-
sha256: "
|
|
12011
|
+
sha256: "2974146c89f4df9f796e840947c334aaf3e4fa3dd18bcd7e08db2eabfd0f086e",
|
|
11805
12012
|
bytes: 5442
|
|
11806
12013
|
},
|
|
11807
12014
|
"index.icon.png": {
|
|
@@ -11813,8 +12020,8 @@ var pin_default = {
|
|
|
11813
12020
|
bytes: 279815
|
|
11814
12021
|
},
|
|
11815
12022
|
"index.pck": {
|
|
11816
|
-
sha256: "
|
|
11817
|
-
bytes:
|
|
12023
|
+
sha256: "736bd383c82fdadf31cc894caf08490b99c5df572aa20db683972c35c7a0465b",
|
|
12024
|
+
bytes: 6429628
|
|
11818
12025
|
},
|
|
11819
12026
|
"index.png": {
|
|
11820
12027
|
sha256: "6e3ca3eb4a99e4c07bdd89ebc873b440ec0ffd66ce1955160da9e7ed80d538d7",
|
|
@@ -11874,7 +12081,7 @@ async function editorStatus(opts = {}) {
|
|
|
11874
12081
|
for (const [name, expected] of Object.entries(pin.files)) {
|
|
11875
12082
|
totalBytes += expected.bytes;
|
|
11876
12083
|
const file = path23.join(dir, name);
|
|
11877
|
-
const info =
|
|
12084
|
+
const info = existsSync19(file) ? await stat4(file).catch(() => null) : null;
|
|
11878
12085
|
if (info && info.size === expected.bytes && await sha256Of(file) === expected.sha256) {
|
|
11879
12086
|
installedBytes += expected.bytes;
|
|
11880
12087
|
} else {
|
|
@@ -12069,7 +12276,7 @@ function createGalleryEditorHandlers(opts = {}) {
|
|
|
12069
12276
|
|
|
12070
12277
|
// src/pipeline/salvage.ts
|
|
12071
12278
|
import { readFile as readFile20 } from "fs/promises";
|
|
12072
|
-
import { existsSync as
|
|
12279
|
+
import { existsSync as existsSync20 } from "fs";
|
|
12073
12280
|
import path25 from "path";
|
|
12074
12281
|
function providerClaimId(provider, objectId) {
|
|
12075
12282
|
return `${encodeURIComponent(provider)}:${encodeURIComponent(objectId)}`;
|
|
@@ -12077,7 +12284,7 @@ function providerClaimId(provider, objectId) {
|
|
|
12077
12284
|
async function loadClaims(lockPaths, opts = {}) {
|
|
12078
12285
|
const claimed = /* @__PURE__ */ new Set();
|
|
12079
12286
|
for (const p of lockPaths) {
|
|
12080
|
-
if (!
|
|
12287
|
+
if (!existsSync20(p)) throw new Error(`Claim lockfile not found: ${p}`);
|
|
12081
12288
|
let parsed;
|
|
12082
12289
|
try {
|
|
12083
12290
|
parsed = parseLock(JSON.parse(await readFile20(p, "utf8")));
|
|
@@ -12138,7 +12345,7 @@ async function loadSiblingManifests(ownManifestPath, workspaceManifestPaths, cla
|
|
|
12138
12345
|
const siblings = [];
|
|
12139
12346
|
for (const siblingManifestPath of siblingManifestPaths) {
|
|
12140
12347
|
if (path25.resolve(siblingManifestPath) === own) continue;
|
|
12141
|
-
if (!
|
|
12348
|
+
if (!existsSync20(siblingManifestPath)) continue;
|
|
12142
12349
|
try {
|
|
12143
12350
|
const { manifest } = await loadManifest(siblingManifestPath);
|
|
12144
12351
|
siblings.push({ label: path25.basename(path25.dirname(siblingManifestPath)), manifest });
|
|
@@ -12360,11 +12567,11 @@ async function workspaceStatus(ws, dir) {
|
|
|
12360
12567
|
}
|
|
12361
12568
|
|
|
12362
12569
|
// src/pipeline/cache-health.ts
|
|
12363
|
-
import { existsSync as
|
|
12570
|
+
import { existsSync as existsSync21 } from "fs";
|
|
12364
12571
|
import { readFile as readFile21, readdir as readdir2, rm as rm10 } from "fs/promises";
|
|
12365
12572
|
import path26 from "path";
|
|
12366
12573
|
async function inspectCaches(lock, lockPath, options = {}) {
|
|
12367
|
-
if (options.prune && !
|
|
12574
|
+
if (options.prune && !existsSync21(lockPath)) {
|
|
12368
12575
|
throw new Error(`Refusing to prune without an existing lockfile at ${path26.resolve(lockPath)}`);
|
|
12369
12576
|
}
|
|
12370
12577
|
const contentDir = path26.resolve(path26.dirname(lockPath), ".pixelkiln", "cache");
|
|
@@ -12409,7 +12616,7 @@ async function inspectCaches(lock, lockPath, options = {}) {
|
|
|
12409
12616
|
async function inspectContentCache(contentDir, referenced) {
|
|
12410
12617
|
const report = {
|
|
12411
12618
|
path: contentDir,
|
|
12412
|
-
exists:
|
|
12619
|
+
exists: existsSync21(contentDir),
|
|
12413
12620
|
files: 0,
|
|
12414
12621
|
bytes: 0,
|
|
12415
12622
|
valid: 0,
|
|
@@ -12473,7 +12680,7 @@ async function inspectContentCache(contentDir, referenced) {
|
|
|
12473
12680
|
async function inspectRemoteHashCache(remotePath) {
|
|
12474
12681
|
const report = {
|
|
12475
12682
|
path: remotePath,
|
|
12476
|
-
exists:
|
|
12683
|
+
exists: existsSync21(remotePath),
|
|
12477
12684
|
entries: 0,
|
|
12478
12685
|
valid: 0,
|
|
12479
12686
|
invalidIds: [],
|
|
@@ -12523,8 +12730,11 @@ function normalizeTileRules(raw) {
|
|
|
12523
12730
|
};
|
|
12524
12731
|
}
|
|
12525
12732
|
function exportTileset(entry, spec, options) {
|
|
12526
|
-
|
|
12733
|
+
let outputs = resolveSpecEntryOutputs(entry, spec);
|
|
12527
12734
|
if (!outputs.length) throw new Error(`${spec.styleId}/${spec.assetId} has no downloaded outputs`);
|
|
12735
|
+
if (spec.source && sourceIsStem(spec.source, entry, options.manifestDir)) {
|
|
12736
|
+
outputs = outputs.map((output) => ({ ...output, absolutePath: sourceOutputPath(spec.source, entry, output.index, options.manifestDir) }));
|
|
12737
|
+
}
|
|
12528
12738
|
const packed = packSprites(
|
|
12529
12739
|
outputs.map((output) => ({
|
|
12530
12740
|
id: output.role ?? fallbackOutputRole(output.index),
|
|
@@ -13018,7 +13228,7 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
13018
13228
|
|
|
13019
13229
|
// src/recipes.ts
|
|
13020
13230
|
import path28 from "path";
|
|
13021
|
-
import { existsSync as
|
|
13231
|
+
import { existsSync as existsSync22 } from "fs";
|
|
13022
13232
|
import { readdir as readdir3, readFile as readFile23 } from "fs/promises";
|
|
13023
13233
|
import { fileURLToPath } from "url";
|
|
13024
13234
|
import { z as z9 } from "zod";
|
|
@@ -13221,7 +13431,7 @@ async function readRecipeFile(recipePath, bundled) {
|
|
|
13221
13431
|
return { recipe: parsed.data, path: absolute, dir: path28.dirname(absolute), bundled };
|
|
13222
13432
|
}
|
|
13223
13433
|
async function walkRecipeFiles(root) {
|
|
13224
|
-
if (!
|
|
13434
|
+
if (!existsSync22(root)) return [];
|
|
13225
13435
|
const out = [];
|
|
13226
13436
|
for (const entry of await readdir3(root, { withFileTypes: true })) {
|
|
13227
13437
|
const resolved = path28.join(root, entry.name);
|
|
@@ -13259,7 +13469,7 @@ function parseSelector(selector) {
|
|
|
13259
13469
|
}
|
|
13260
13470
|
async function resolveRecipe(target, bundledRoot = bundledRecipeRoot()) {
|
|
13261
13471
|
const local = path28.resolve(target);
|
|
13262
|
-
if (
|
|
13472
|
+
if (existsSync22(local)) {
|
|
13263
13473
|
const recipePath = path28.basename(local) === RECIPE_FILE ? local : path28.join(local, RECIPE_FILE);
|
|
13264
13474
|
return readRecipeFile(recipePath, false);
|
|
13265
13475
|
}
|
|
@@ -13275,7 +13485,7 @@ async function resolveRecipe(target, bundledRoot = bundledRecipeRoot()) {
|
|
|
13275
13485
|
}
|
|
13276
13486
|
async function verifyFile(root, file) {
|
|
13277
13487
|
const absolute = path28.resolve(root, ...file.path.split("/"));
|
|
13278
|
-
if (!
|
|
13488
|
+
if (!existsSync22(absolute)) {
|
|
13279
13489
|
return { path: file.path, expectedSha256: file.sha256, actualSha256: null, status: "missing" };
|
|
13280
13490
|
}
|
|
13281
13491
|
const actualSha256 = await sha256File(absolute);
|
|
@@ -13353,7 +13563,7 @@ async function installRecipe(target, options = {}) {
|
|
|
13353
13563
|
})));
|
|
13354
13564
|
if (!options.force) {
|
|
13355
13565
|
for (const file of files) {
|
|
13356
|
-
if (!
|
|
13566
|
+
if (!existsSync22(file.path)) continue;
|
|
13357
13567
|
const current = await readFile23(file.path);
|
|
13358
13568
|
if (!current.equals(file.data)) {
|
|
13359
13569
|
throw new Error(
|
|
@@ -13378,7 +13588,7 @@ async function installRecipe(target, options = {}) {
|
|
|
13378
13588
|
|
|
13379
13589
|
// src/pipeline/quality-regression.ts
|
|
13380
13590
|
import path29 from "path";
|
|
13381
|
-
import { existsSync as
|
|
13591
|
+
import { existsSync as existsSync23 } from "fs";
|
|
13382
13592
|
import { readFile as readFile24 } from "fs/promises";
|
|
13383
13593
|
import { z as z10 } from "zod";
|
|
13384
13594
|
var SHA256_RE2 = /^[0-9a-f]{64}$/;
|
|
@@ -13621,7 +13831,7 @@ async function snapshotQualityBaseline(inputs, baselinePath, options = {}) {
|
|
|
13621
13831
|
cases
|
|
13622
13832
|
});
|
|
13623
13833
|
const data = Buffer.from(JSON.stringify(baseline, null, 2) + "\n");
|
|
13624
|
-
if (
|
|
13834
|
+
if (existsSync23(absolute) && !options.force) {
|
|
13625
13835
|
const current = await readFile24(absolute);
|
|
13626
13836
|
if (!current.equals(data)) {
|
|
13627
13837
|
throw new Error(`Quality baseline already exists with different content: ${absolute}. Pass --force to replace it.`);
|
|
@@ -13862,7 +14072,7 @@ async function provenanceFile(id, file) {
|
|
|
13862
14072
|
return {
|
|
13863
14073
|
id,
|
|
13864
14074
|
path: absolute,
|
|
13865
|
-
sha256:
|
|
14075
|
+
sha256: existsSync24(absolute) ? await sha256File(absolute) : null,
|
|
13866
14076
|
included: true
|
|
13867
14077
|
};
|
|
13868
14078
|
}
|
|
@@ -14502,7 +14712,7 @@ async function confirm(question, auto) {
|
|
|
14502
14712
|
return answer === "y" || answer === "yes";
|
|
14503
14713
|
}
|
|
14504
14714
|
async function requireCompleteWorkspaceClaims(workspacePath) {
|
|
14505
|
-
if (!
|
|
14715
|
+
if (!existsSync24(workspacePath)) {
|
|
14506
14716
|
throw new Error(`Workspace catalog not found: ${workspacePath}`);
|
|
14507
14717
|
}
|
|
14508
14718
|
const dir = path30.dirname(path30.resolve(workspacePath));
|
|
@@ -14761,7 +14971,7 @@ async function main() {
|
|
|
14761
14971
|
if (args.command === "init") {
|
|
14762
14972
|
if (!args.from) throw new Error("init needs --from <dir> pointing at your existing PNGs.");
|
|
14763
14973
|
const root = path30.resolve(args.from);
|
|
14764
|
-
if (!
|
|
14974
|
+
if (!existsSync24(root)) throw new Error(`No directory at ${root}`);
|
|
14765
14975
|
const generator = args.generator ?? "map";
|
|
14766
14976
|
if (generator !== "1dir" && generator !== "map") {
|
|
14767
14977
|
throw new Error(`--generator must be "1dir" or "map", got "${args.generator}".`);
|
|
@@ -14881,7 +15091,7 @@ async function main() {
|
|
|
14881
15091
|
log(` registered "${id}" in ${path30.relative(process.cwd(), workspacePath)}`);
|
|
14882
15092
|
log(` manifest: ${project.manifest}`);
|
|
14883
15093
|
log(` lock: ${project.lock}`);
|
|
14884
|
-
if (!
|
|
15094
|
+
if (!existsSync24(lockPath)) {
|
|
14885
15095
|
log(
|
|
14886
15096
|
` warning: no lockfile there yet \u2014 this project contributes no claims until one is generated`
|
|
14887
15097
|
);
|
|
@@ -14905,7 +15115,7 @@ async function main() {
|
|
|
14905
15115
|
return;
|
|
14906
15116
|
}
|
|
14907
15117
|
if (args.subcommand === "list") {
|
|
14908
|
-
if (!
|
|
15118
|
+
if (!existsSync24(workspacePath)) {
|
|
14909
15119
|
throw new Error(`Workspace catalog not found: ${workspacePath}`);
|
|
14910
15120
|
}
|
|
14911
15121
|
const ws = await loadWorkspace(workspacePath);
|
|
@@ -14925,7 +15135,7 @@ async function main() {
|
|
|
14925
15135
|
return;
|
|
14926
15136
|
}
|
|
14927
15137
|
if (args.subcommand === "status") {
|
|
14928
|
-
if (!
|
|
15138
|
+
if (!existsSync24(workspacePath)) {
|
|
14929
15139
|
throw new Error(`Workspace catalog not found: ${workspacePath}`);
|
|
14930
15140
|
}
|
|
14931
15141
|
const ws = await loadWorkspace(workspacePath);
|
|
@@ -14981,7 +15191,7 @@ async function main() {
|
|
|
14981
15191
|
}
|
|
14982
15192
|
if (args.command === "gallery" && args.workspace) {
|
|
14983
15193
|
const workspacePath = path30.resolve(args.workspace);
|
|
14984
|
-
if (!
|
|
15194
|
+
if (!existsSync24(workspacePath)) {
|
|
14985
15195
|
throw new Error(`Workspace catalog not found: ${workspacePath}`);
|
|
14986
15196
|
}
|
|
14987
15197
|
const filter = { styles: args.styles, assets: args.assets };
|
|
@@ -15016,7 +15226,7 @@ async function main() {
|
|
|
15016
15226
|
});
|
|
15017
15227
|
return;
|
|
15018
15228
|
}
|
|
15019
|
-
if (!
|
|
15229
|
+
if (!existsSync24(path30.resolve(args.manifest))) {
|
|
15020
15230
|
throw new Error(
|
|
15021
15231
|
`No manifest at ${path30.resolve(args.manifest)}. Pass --manifest, or run \`pixelkiln init --from <dir>\`.`
|
|
15022
15232
|
);
|
|
@@ -15121,12 +15331,15 @@ async function main() {
|
|
|
15121
15331
|
return;
|
|
15122
15332
|
}
|
|
15123
15333
|
const started = await startHandEdit(loaded, lock, spec);
|
|
15124
|
-
|
|
15334
|
+
const set = started.members.length > 1;
|
|
15335
|
+
const kind = spec.generator === "frames" ? "frames" : "members";
|
|
15336
|
+
log(` ${started.created ? "created" : "found"} ${started.source}${set ? ` (${started.members.length} ${kind})` : ""}${started.declared ? " and declared it as the asset's source" : ""}`);
|
|
15337
|
+
if (set) for (const member of started.members) log(` ${member.role ?? ""} ${path30.relative(process.cwd(), member.path)}`);
|
|
15125
15338
|
if (args.noOpen) {
|
|
15126
|
-
log(` edit it with your own tool, then run pixelkiln plan; mount and pack place it in place of the generated art`);
|
|
15339
|
+
log(` edit ${set ? "them" : "it"} with your own tool, then run pixelkiln plan; mount and pack place ${set ? "them" : "it"} in place of the generated art`);
|
|
15127
15340
|
} else {
|
|
15128
|
-
const command = openInEditor(started.
|
|
15129
|
-
log(` opened with: ${command}${process.env.PIXELKILN_EDITOR ? "" : " (set PIXELKILN_EDITOR to choose the program)"}`);
|
|
15341
|
+
const command = openInEditor(started.members[0].path);
|
|
15342
|
+
log(` opened ${set ? `the first ${kind === "frames" ? "frame" : "member"} ` : ""}with: ${command}${process.env.PIXELKILN_EDITOR ? "" : " (set PIXELKILN_EDITOR to choose the program)"}`);
|
|
15130
15343
|
}
|
|
15131
15344
|
return;
|
|
15132
15345
|
}
|
|
@@ -15262,7 +15475,7 @@ async function main() {
|
|
|
15262
15475
|
let intact = true;
|
|
15263
15476
|
for (const output of entry.outputs) {
|
|
15264
15477
|
const file = resolveOutputPath(output.path, item.spec.root);
|
|
15265
|
-
if (!
|
|
15478
|
+
if (!existsSync24(file) || await sha256File(file) !== output.sha256) {
|
|
15266
15479
|
intact = false;
|
|
15267
15480
|
break;
|
|
15268
15481
|
}
|
|
@@ -15644,7 +15857,7 @@ async function main() {
|
|
|
15644
15857
|
const lockPaths = [
|
|
15645
15858
|
.../* @__PURE__ */ new Set([
|
|
15646
15859
|
...workspaceLockPaths,
|
|
15647
|
-
...
|
|
15860
|
+
...existsSync24(ownLock) ? [ownLock] : [],
|
|
15648
15861
|
...args.claims.map((c) => path30.resolve(c))
|
|
15649
15862
|
])
|
|
15650
15863
|
];
|