pixelkiln 0.29.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 +129 -112
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +509 -140
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +416 -78
- package/dist/index.d.ts +416 -78
- package/dist/index.js +474 -131
- package/dist/index.js.map +1 -1
- package/docs/AGENTS.md +5 -0
- package/docs/ARCHITECTURE.md +14 -0
- package/docs/CLI.md +11 -10
- 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
|
|
@@ -3478,10 +3478,11 @@ registerProvider({
|
|
|
3478
3478
|
|
|
3479
3479
|
// src/manifest.ts
|
|
3480
3480
|
import { readFile as readFile3 } from "fs/promises";
|
|
3481
|
-
import { existsSync as
|
|
3481
|
+
import { existsSync as existsSync5 } from "fs";
|
|
3482
3482
|
import path6 from "path";
|
|
3483
3483
|
|
|
3484
3484
|
// src/outputs.ts
|
|
3485
|
+
import { existsSync as existsSync4 } from "fs";
|
|
3485
3486
|
import path5 from "path";
|
|
3486
3487
|
|
|
3487
3488
|
// src/lock.ts
|
|
@@ -3678,12 +3679,15 @@ function memberPath(file, role, index, total, mediaType) {
|
|
|
3678
3679
|
const safeRole = (role ?? fallbackOutputRole(index)).replace(/[^a-zA-Z0-9_-]+/g, "-");
|
|
3679
3680
|
return `${stem}-${safeRole}${ext}`;
|
|
3680
3681
|
}
|
|
3681
|
-
function
|
|
3682
|
-
return entry.
|
|
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));
|
|
3683
3687
|
}
|
|
3684
3688
|
function sourceOutputPath(source, entry, index, manifestDir) {
|
|
3685
3689
|
const absolute = path5.resolve(manifestDir, source);
|
|
3686
|
-
if (!
|
|
3690
|
+
if (!sourceIsStem(source, entry, manifestDir)) return absolute;
|
|
3687
3691
|
const output = entry.outputs[index];
|
|
3688
3692
|
return memberPath(absolute, output.role, index, entry.outputs.length, output.mediaType);
|
|
3689
3693
|
}
|
|
@@ -3771,7 +3775,7 @@ function resolveSpecOutputs(spec, lock, manifestDir) {
|
|
|
3771
3775
|
// src/manifest.ts
|
|
3772
3776
|
async function loadManifest(manifestPath) {
|
|
3773
3777
|
const abs = path6.resolve(manifestPath);
|
|
3774
|
-
if (!
|
|
3778
|
+
if (!existsSync5(abs)) throw new Error(`No manifest at ${abs}`);
|
|
3775
3779
|
const input = ManifestInputSchema.safeParse(JSON.parse(await readFile3(abs, "utf8")));
|
|
3776
3780
|
if (!input.success) {
|
|
3777
3781
|
const issues = formatManifestIssues(input.error.issues);
|
|
@@ -3943,7 +3947,7 @@ async function resolveSpecs(loaded, filter) {
|
|
|
3943
3947
|
const abs = path6.resolve(root, rel);
|
|
3944
3948
|
let hit = styleImageCache.get(abs);
|
|
3945
3949
|
if (!hit) {
|
|
3946
|
-
if (!
|
|
3950
|
+
if (!existsSync5(abs)) throw new Error(`Style image not found: ${abs}`);
|
|
3947
3951
|
const buf = await readFile3(abs);
|
|
3948
3952
|
const metadata = imageMetadata(buf);
|
|
3949
3953
|
if (!metadata) throw new Error(`Style image is not a readable PNG or JPEG: ${abs}`);
|
|
@@ -4184,7 +4188,7 @@ function pngPath(file) {
|
|
|
4184
4188
|
return extension ? `${file.slice(0, -extension.length)}.png` : `${file}.png`;
|
|
4185
4189
|
}
|
|
4186
4190
|
async function optionalRevisionImage(file, label, requiredFormat) {
|
|
4187
|
-
if (!
|
|
4191
|
+
if (!existsSync5(file)) return null;
|
|
4188
4192
|
const bytes = await readFile3(file);
|
|
4189
4193
|
const metadata = imageMetadata(bytes);
|
|
4190
4194
|
if (!metadata) throw new Error(`${label} is not a readable PNG or JPEG: ${file}`);
|
|
@@ -4349,7 +4353,7 @@ function packStyle(lock, styleId, manifestDir, options = {}) {
|
|
|
4349
4353
|
inputs.push({ id, path: path7.resolve(manifestDir, source) });
|
|
4350
4354
|
continue;
|
|
4351
4355
|
}
|
|
4352
|
-
if (source &&
|
|
4356
|
+
if (source && sourceIsStem(source, entry, manifestDir)) {
|
|
4353
4357
|
outputs = outputs.map((output) => ({ ...output, absolutePath: sourceOutputPath(source, entry, output.index, manifestDir) }));
|
|
4354
4358
|
}
|
|
4355
4359
|
let selected = outputs;
|
|
@@ -4474,7 +4478,7 @@ function mountStyle(lock, styleId, manifestDir, mount, cells, sources = {}, outp
|
|
|
4474
4478
|
const cell = cells[id];
|
|
4475
4479
|
const source = sources[id];
|
|
4476
4480
|
const entry = lock.entries[`${styleId}/${id}`];
|
|
4477
|
-
if (source && !(entry &&
|
|
4481
|
+
if (source && !(entry && sourceIsStem(source, entry, manifestDir))) {
|
|
4478
4482
|
placements.push({ id, path: path7.resolve(manifestDir, source), cell });
|
|
4479
4483
|
continue;
|
|
4480
4484
|
}
|
|
@@ -4528,11 +4532,11 @@ function mountStyle(lock, styleId, manifestDir, mount, cells, sources = {}, outp
|
|
|
4528
4532
|
}
|
|
4529
4533
|
|
|
4530
4534
|
// src/pipeline/plan.ts
|
|
4531
|
-
import { existsSync as
|
|
4535
|
+
import { existsSync as existsSync9 } from "fs";
|
|
4532
4536
|
import path13 from "path";
|
|
4533
4537
|
|
|
4534
4538
|
// src/pipeline/quality-profile.ts
|
|
4535
|
-
import { existsSync as
|
|
4539
|
+
import { existsSync as existsSync7 } from "fs";
|
|
4536
4540
|
import path11 from "path";
|
|
4537
4541
|
|
|
4538
4542
|
// src/pipeline/refine.ts
|
|
@@ -5000,7 +5004,7 @@ async function writeArtifactBundle(files, options = {}) {
|
|
|
5000
5004
|
|
|
5001
5005
|
// src/pipeline/audit.ts
|
|
5002
5006
|
import { readFile as readFile5 } from "fs/promises";
|
|
5003
|
-
import { existsSync as
|
|
5007
|
+
import { existsSync as existsSync6 } from "fs";
|
|
5004
5008
|
import path9 from "path";
|
|
5005
5009
|
function colorDistance(a, b) {
|
|
5006
5010
|
const rmean = (a.r + b.r) / 2;
|
|
@@ -5046,7 +5050,7 @@ async function auditStyle(loaded, specs, styleId, lock) {
|
|
|
5046
5050
|
const unreadable = [];
|
|
5047
5051
|
for (const spec of mine) {
|
|
5048
5052
|
for (const output of resolveSpecOutputs(spec, lock, loaded.root)) {
|
|
5049
|
-
if (!
|
|
5053
|
+
if (!existsSync6(output.absolutePath)) {
|
|
5050
5054
|
missing.push(output.id);
|
|
5051
5055
|
continue;
|
|
5052
5056
|
}
|
|
@@ -5076,7 +5080,7 @@ async function auditStyle(loaded, specs, styleId, lock) {
|
|
|
5076
5080
|
const refPalettes = [];
|
|
5077
5081
|
for (const img of style.styleImages) {
|
|
5078
5082
|
const abs = path9.resolve(loaded.root, img.path);
|
|
5079
|
-
if (!
|
|
5083
|
+
if (!existsSync6(abs)) continue;
|
|
5080
5084
|
try {
|
|
5081
5085
|
refPalettes.push(extractPalette(decodePng(await readFile5(abs)), 12));
|
|
5082
5086
|
} catch {
|
|
@@ -5705,7 +5709,7 @@ async function qualitySource(spec, lock) {
|
|
|
5705
5709
|
return { items: null, reason: "frame-set quality requires ordered downloaded provider outputs" };
|
|
5706
5710
|
}
|
|
5707
5711
|
const source = path11.resolve(spec.root, spec.source);
|
|
5708
|
-
return
|
|
5712
|
+
return existsSync7(source) ? { items: [{ role: "source", path: source }] } : { items: null, reason: `declared source is missing: ${spec.source}` };
|
|
5709
5713
|
}
|
|
5710
5714
|
const entry = lock.entries[lockKey(spec.styleId, spec.assetId)];
|
|
5711
5715
|
if (!entry) return { items: null, reason: "raw provider output is not in the lockfile" };
|
|
@@ -5734,7 +5738,7 @@ async function qualitySource(spec, lock) {
|
|
|
5734
5738
|
}
|
|
5735
5739
|
if (role) roles.add(role);
|
|
5736
5740
|
const source = currentEntryOutputPath(entry, spec, index);
|
|
5737
|
-
if (!
|
|
5741
|
+
if (!existsSync7(source)) return { items: null, reason: `raw provider output is missing: ${source}` };
|
|
5738
5742
|
if (await sha256File(source) !== output.sha256) {
|
|
5739
5743
|
return { items: null, reason: "raw provider output was modified after download" };
|
|
5740
5744
|
}
|
|
@@ -5753,7 +5757,7 @@ async function inspectQualityProfile(spec, lock) {
|
|
|
5753
5757
|
}
|
|
5754
5758
|
const sourcePaths = source.items.map((item) => item.path);
|
|
5755
5759
|
const expectedOutputs = spec.generator === "frames" ? source.items.map((item, index) => qualityFrameOutputPath(output, item.role, index)) : [output];
|
|
5756
|
-
if (!
|
|
5760
|
+
if (!existsSync7(record)) {
|
|
5757
5761
|
return {
|
|
5758
5762
|
key,
|
|
5759
5763
|
state: "needs-refinement",
|
|
@@ -5934,7 +5938,7 @@ async function requireApprovedQualitySources(specs, lock, assetIds, outputRoles)
|
|
|
5934
5938
|
}
|
|
5935
5939
|
|
|
5936
5940
|
// src/pipeline/revision.ts
|
|
5937
|
-
import { existsSync as
|
|
5941
|
+
import { existsSync as existsSync8 } from "fs";
|
|
5938
5942
|
import path12 from "path";
|
|
5939
5943
|
async function inspectRevisionReadiness(spec, lock) {
|
|
5940
5944
|
if (!spec.revision) return null;
|
|
@@ -5950,14 +5954,14 @@ async function inspectRevision(spec, lock, seen) {
|
|
|
5950
5954
|
reason: `revision source ${spec.styleId}/${revision.sourceAssetId} is not ready: ${dependency.reason}`
|
|
5951
5955
|
};
|
|
5952
5956
|
}
|
|
5953
|
-
if (!revision.sourceSha256 || !
|
|
5957
|
+
if (!revision.sourceSha256 || !existsSync8(revision.sourceFile)) {
|
|
5954
5958
|
return { ready: false, reason: `revision source is missing: ${revision.sourceFile}` };
|
|
5955
5959
|
}
|
|
5956
5960
|
if (await sha256File(revision.sourceFile) !== revision.sourceSha256) {
|
|
5957
5961
|
return { ready: false, reason: "revision source changed after the manifest was resolved" };
|
|
5958
5962
|
}
|
|
5959
5963
|
if (revision.maskFile) {
|
|
5960
|
-
if (!revision.maskSha256 || !
|
|
5964
|
+
if (!revision.maskSha256 || !existsSync8(revision.maskFile)) {
|
|
5961
5965
|
return { ready: false, reason: `revision mask is missing: ${revision.maskFile}` };
|
|
5962
5966
|
}
|
|
5963
5967
|
if (await sha256File(revision.maskFile) !== revision.maskSha256) {
|
|
@@ -5988,7 +5992,7 @@ async function inspectParent(spec, lock, seen) {
|
|
|
5988
5992
|
}
|
|
5989
5993
|
if (spec.source) {
|
|
5990
5994
|
const source = path12.resolve(spec.root, spec.source);
|
|
5991
|
-
return
|
|
5995
|
+
return existsSync8(source) ? { ready: true, reason: "declared source is present" } : { ready: false, reason: `declared source is missing: ${spec.source}` };
|
|
5992
5996
|
}
|
|
5993
5997
|
const entry = lock.entries[key];
|
|
5994
5998
|
if (!entry) return { ready: false, reason: "parent is not in the lockfile" };
|
|
@@ -6002,7 +6006,7 @@ async function inspectParent(spec, lock, seen) {
|
|
|
6002
6006
|
for (let index = 0; index < entry.outputs.length; index++) {
|
|
6003
6007
|
const output = entry.outputs[index];
|
|
6004
6008
|
const file = currentEntryOutputPath(entry, spec, index);
|
|
6005
|
-
if (!
|
|
6009
|
+
if (!existsSync8(file)) return { ready: false, reason: `parent output is missing: ${file}` };
|
|
6006
6010
|
if (await sha256File(file) !== output.sha256) {
|
|
6007
6011
|
return { ready: false, reason: `parent output was modified: ${file}` };
|
|
6008
6012
|
}
|
|
@@ -6057,7 +6061,7 @@ async function buildPlan(specs, lock, opts = {}) {
|
|
|
6057
6061
|
state = "missing";
|
|
6058
6062
|
reason = "--force";
|
|
6059
6063
|
} else if (!entry && spec.source && spec.generator !== "frames") {
|
|
6060
|
-
if (
|
|
6064
|
+
if (existsSync9(path13.resolve(spec.root, spec.source))) {
|
|
6061
6065
|
state = "ok";
|
|
6062
6066
|
reason = `placed from ${spec.source}; not generated`;
|
|
6063
6067
|
} else {
|
|
@@ -6065,7 +6069,7 @@ async function buildPlan(specs, lock, opts = {}) {
|
|
|
6065
6069
|
reason = `declared source is missing: ${spec.source}`;
|
|
6066
6070
|
}
|
|
6067
6071
|
} else if (!entry) {
|
|
6068
|
-
if (
|
|
6072
|
+
if (existsSync9(spec.outFile)) {
|
|
6069
6073
|
state = "untracked";
|
|
6070
6074
|
reason = "file on disk, no provenance recorded";
|
|
6071
6075
|
} else {
|
|
@@ -6097,9 +6101,9 @@ async function buildPlan(specs, lock, opts = {}) {
|
|
|
6097
6101
|
} else if (entry.status !== "downloaded") {
|
|
6098
6102
|
state = "in-flight";
|
|
6099
6103
|
reason = `awaiting ${entry.status}`;
|
|
6100
|
-
} 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)))) {
|
|
6101
6105
|
state = "orphaned";
|
|
6102
|
-
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(", ")}`;
|
|
6103
6107
|
} else if (await anyOutputModified(entry, spec)) {
|
|
6104
6108
|
state = "orphaned";
|
|
6105
6109
|
reason = "output modified since download";
|
|
@@ -6427,7 +6431,7 @@ async function poll(provider, lock, lockPath, opts = {}) {
|
|
|
6427
6431
|
}
|
|
6428
6432
|
|
|
6429
6433
|
// src/pipeline/fetch.ts
|
|
6430
|
-
import { existsSync as
|
|
6434
|
+
import { existsSync as existsSync10 } from "fs";
|
|
6431
6435
|
import { mkdir as mkdir3, readFile as readFile7, rename as rename3, rm as rm4, writeFile as writeFile3 } from "fs/promises";
|
|
6432
6436
|
import path14 from "path";
|
|
6433
6437
|
|
|
@@ -6483,7 +6487,7 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
6483
6487
|
if (opts.refresh) return e.outputs.length > 0 && Boolean(e.sourceUrls?.length || e.sourceUrl);
|
|
6484
6488
|
if (!opts.repair) return false;
|
|
6485
6489
|
const spec = specByKey.get(key);
|
|
6486
|
-
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)));
|
|
6487
6491
|
});
|
|
6488
6492
|
const concurrency = Math.min(Math.max(1, opts.concurrency ?? 8), Math.max(1, pending.length));
|
|
6489
6493
|
let cursor = 0;
|
|
@@ -6531,7 +6535,7 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
6531
6535
|
continue;
|
|
6532
6536
|
}
|
|
6533
6537
|
}
|
|
6534
|
-
if (
|
|
6538
|
+
if (existsSync10(target)) {
|
|
6535
6539
|
const currentHash = await sha256File(target);
|
|
6536
6540
|
if (recorded && currentHash === recorded.sha256) {
|
|
6537
6541
|
if (!fresh) {
|
|
@@ -6642,7 +6646,7 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
6642
6646
|
}
|
|
6643
6647
|
async function readCachedMedia(cacheDir, hash, mediaType) {
|
|
6644
6648
|
const file = path14.join(cacheDir, cacheFileName(hash, mediaType));
|
|
6645
|
-
if (!
|
|
6649
|
+
if (!existsSync10(file)) return null;
|
|
6646
6650
|
try {
|
|
6647
6651
|
const buf = await readFile7(file);
|
|
6648
6652
|
if (sha256(buf) !== hash) return null;
|
|
@@ -6663,7 +6667,7 @@ async function cacheMedia(cacheDir, buf, mediaType, knownHash) {
|
|
|
6663
6667
|
try {
|
|
6664
6668
|
await rename3(tmp, file);
|
|
6665
6669
|
} catch (err) {
|
|
6666
|
-
if (!
|
|
6670
|
+
if (!existsSync10(file)) throw err;
|
|
6667
6671
|
} finally {
|
|
6668
6672
|
await rm4(tmp, { force: true });
|
|
6669
6673
|
}
|
|
@@ -6696,7 +6700,7 @@ async function pushTags(provider, specs, lock, opts = {}) {
|
|
|
6696
6700
|
}
|
|
6697
6701
|
|
|
6698
6702
|
// src/pipeline/doctor.ts
|
|
6699
|
-
import { constants, existsSync as
|
|
6703
|
+
import { constants, existsSync as existsSync11 } from "fs";
|
|
6700
6704
|
import { access, stat as stat2 } from "fs/promises";
|
|
6701
6705
|
import path15 from "path";
|
|
6702
6706
|
async function doctor(loaded, specs, lock, lockPath, opts = {}) {
|
|
@@ -6768,7 +6772,7 @@ async function doctor(loaded, specs, lock, lockPath, opts = {}) {
|
|
|
6768
6772
|
cacheDir,
|
|
6769
6773
|
cacheFileName(output.sha256, output.mediaType ?? MediaType.PNG)
|
|
6770
6774
|
);
|
|
6771
|
-
if (
|
|
6775
|
+
if (existsSync11(cached) && await sha256File(cached) === output.sha256) {
|
|
6772
6776
|
hasCache = true;
|
|
6773
6777
|
break;
|
|
6774
6778
|
}
|
|
@@ -6871,13 +6875,13 @@ async function nearestExistingDirectory(start) {
|
|
|
6871
6875
|
}
|
|
6872
6876
|
|
|
6873
6877
|
// src/pipeline/adopt.ts
|
|
6874
|
-
import { existsSync as
|
|
6878
|
+
import { existsSync as existsSync13 } from "fs";
|
|
6875
6879
|
import { readFile as readFile9, writeFile as writeFile5 } from "fs/promises";
|
|
6876
6880
|
import path16 from "path";
|
|
6877
6881
|
|
|
6878
6882
|
// src/cache.ts
|
|
6879
6883
|
import { mkdir as mkdir4, readFile as readFile8, rename as rename4, rm as rm5, writeFile as writeFile4 } from "fs/promises";
|
|
6880
|
-
import { existsSync as
|
|
6884
|
+
import { existsSync as existsSync12 } from "fs";
|
|
6881
6885
|
import pathModule from "path";
|
|
6882
6886
|
import { z as z3 } from "zod";
|
|
6883
6887
|
var HashCacheSchema = z3.object({
|
|
@@ -6893,7 +6897,7 @@ function parseCache(value) {
|
|
|
6893
6897
|
return HashCacheSchema.parse(value);
|
|
6894
6898
|
}
|
|
6895
6899
|
async function loadCache(path31) {
|
|
6896
|
-
if (!
|
|
6900
|
+
if (!existsSync12(path31)) return { version: 1, hashes: {} };
|
|
6897
6901
|
try {
|
|
6898
6902
|
const parsed = HashCacheSchema.safeParse(JSON.parse(await readFile8(path31, "utf8")));
|
|
6899
6903
|
if (!parsed.success) return { version: 1, hashes: {} };
|
|
@@ -6949,7 +6953,7 @@ async function adopt(provider, specs, lock, lockPath, opts = {}) {
|
|
|
6949
6953
|
const localByHash = /* @__PURE__ */ new Map();
|
|
6950
6954
|
const unmatchedLocal = [];
|
|
6951
6955
|
for (const spec of specs) {
|
|
6952
|
-
if (!
|
|
6956
|
+
if (!existsSync13(spec.outFile)) {
|
|
6953
6957
|
unmatchedLocal.push(`${spec.styleId}/${spec.assetId} (no file at ${spec.outFile})`);
|
|
6954
6958
|
continue;
|
|
6955
6959
|
}
|
|
@@ -7106,12 +7110,12 @@ function formatUnmatchedRemote(objects, limit = 15) {
|
|
|
7106
7110
|
}
|
|
7107
7111
|
|
|
7108
7112
|
// src/pick/server.ts
|
|
7109
|
-
import { existsSync as
|
|
7113
|
+
import { existsSync as existsSync15 } from "fs";
|
|
7110
7114
|
import { readFile as readFile12 } from "fs/promises";
|
|
7111
7115
|
|
|
7112
7116
|
// src/pipeline/init.ts
|
|
7113
7117
|
import { readdir, readFile as readFile10, writeFile as writeFile6 } from "fs/promises";
|
|
7114
|
-
import { existsSync as
|
|
7118
|
+
import { existsSync as existsSync14 } from "fs";
|
|
7115
7119
|
import path17 from "path";
|
|
7116
7120
|
function pngSize(buf) {
|
|
7117
7121
|
if (buf.length < 24) return null;
|
|
@@ -7204,7 +7208,7 @@ function mode(values) {
|
|
|
7204
7208
|
return [...counts.entries()].sort((a, b) => b[1] - a[1])[0]?.[0] ?? 64;
|
|
7205
7209
|
}
|
|
7206
7210
|
async function writeManifestFile(target, manifest) {
|
|
7207
|
-
if (
|
|
7211
|
+
if (existsSync14(target)) {
|
|
7208
7212
|
throw new Error(`${target} already exists \u2014 refusing to overwrite an existing manifest.`);
|
|
7209
7213
|
}
|
|
7210
7214
|
await writeFile6(target, JSON.stringify(manifest, null, 2) + "\n");
|
|
@@ -7690,7 +7694,7 @@ async function currentArt(entry, spec) {
|
|
|
7690
7694
|
const previous = primaryOutput({ ...entry, outputs: entry.supersededOutputs });
|
|
7691
7695
|
if (!previous) return null;
|
|
7692
7696
|
const file = resolveOutputPath(previous.path, spec.root);
|
|
7693
|
-
if (!
|
|
7697
|
+
if (!existsSync15(file)) return null;
|
|
7694
7698
|
const contentType = previous.mediaType ?? mediaTypeFromExtension(file);
|
|
7695
7699
|
if (!contentType) return null;
|
|
7696
7700
|
let width = entry.width;
|
|
@@ -7861,13 +7865,13 @@ async function runPicker(provider, lock, lockPath, opts = {}) {
|
|
|
7861
7865
|
|
|
7862
7866
|
// src/gallery/snapshot.ts
|
|
7863
7867
|
import { createHash as createHash3 } from "crypto";
|
|
7864
|
-
import { existsSync as
|
|
7868
|
+
import { existsSync as existsSync18 } from "fs";
|
|
7865
7869
|
import { readFile as readFile16, stat as stat3 } from "fs/promises";
|
|
7866
7870
|
import path22 from "path";
|
|
7867
7871
|
|
|
7868
7872
|
// src/workspace.ts
|
|
7869
7873
|
import { mkdir as mkdir5, readFile as readFile13, rename as rename5, rm as rm6, writeFile as writeFile7 } from "fs/promises";
|
|
7870
|
-
import { existsSync as
|
|
7874
|
+
import { existsSync as existsSync16 } from "fs";
|
|
7871
7875
|
import path18 from "path";
|
|
7872
7876
|
import { z as z4 } from "zod";
|
|
7873
7877
|
var WorkspaceProjectSchema = z4.object({
|
|
@@ -7893,7 +7897,7 @@ ${parsed.error.issues.slice(0, 5).map((i) => ` ${i.path.join(".")}: ${i.message
|
|
|
7893
7897
|
);
|
|
7894
7898
|
}
|
|
7895
7899
|
async function loadWorkspace(workspacePath) {
|
|
7896
|
-
if (!
|
|
7900
|
+
if (!existsSync16(workspacePath)) return { version: 1, projects: [] };
|
|
7897
7901
|
let raw;
|
|
7898
7902
|
try {
|
|
7899
7903
|
raw = JSON.parse(await readFile13(workspacePath, "utf8"));
|
|
@@ -7945,14 +7949,14 @@ function validateWorkspace(ws, dir) {
|
|
|
7945
7949
|
message: `project "${project.id}" stores an absolute path \u2014 the catalog will not resolve correctly if this tree is cloned or moved elsewhere`
|
|
7946
7950
|
});
|
|
7947
7951
|
}
|
|
7948
|
-
if (!
|
|
7952
|
+
if (!existsSync16(manifestPath)) {
|
|
7949
7953
|
diagnostics.push({
|
|
7950
7954
|
id: "missing-manifest",
|
|
7951
7955
|
level: "error",
|
|
7952
7956
|
message: `project "${project.id}" manifest not found: ${manifestPath}`
|
|
7953
7957
|
});
|
|
7954
7958
|
}
|
|
7955
|
-
if (!
|
|
7959
|
+
if (!existsSync16(lockPath)) {
|
|
7956
7960
|
diagnostics.push({
|
|
7957
7961
|
id: "missing-lock",
|
|
7958
7962
|
level: "error",
|
|
@@ -8253,7 +8257,7 @@ async function applyManifestEdit(manifestPath, edit) {
|
|
|
8253
8257
|
|
|
8254
8258
|
// src/pipeline/hand-edit.ts
|
|
8255
8259
|
import { spawn as spawn2 } from "child_process";
|
|
8256
|
-
import { existsSync as
|
|
8260
|
+
import { existsSync as existsSync17 } from "fs";
|
|
8257
8261
|
import { copyFile, mkdir as mkdir6, readFile as readFile15, rename as rename7, rm as rm8, writeFile as writeFile9 } from "fs/promises";
|
|
8258
8262
|
import path20 from "path";
|
|
8259
8263
|
import { z as z6 } from "zod";
|
|
@@ -8269,7 +8273,7 @@ function handEditPath(loaded, spec) {
|
|
|
8269
8273
|
function handEditBases(spec, lock) {
|
|
8270
8274
|
const entry = lock.entries[lockKey(spec.styleId, spec.assetId)];
|
|
8271
8275
|
if (entry) {
|
|
8272
|
-
if (entry.outputs.length !== 1 && !
|
|
8276
|
+
if (entry.outputs.length !== 1 && !isMemberSetEntry(entry)) return null;
|
|
8273
8277
|
const members = entry.outputs.map((output, index) => ({
|
|
8274
8278
|
role: entry.outputs.length === 1 ? null : output.role ?? null,
|
|
8275
8279
|
path: currentEntryOutputPath(entry, spec, index),
|
|
@@ -8277,15 +8281,15 @@ function handEditBases(spec, lock) {
|
|
|
8277
8281
|
}));
|
|
8278
8282
|
if (members.some((member) => member.mediaType && member.mediaType !== "image/png")) return null;
|
|
8279
8283
|
if (entry.outputs.length === 1 && !primaryOutput(entry)) return null;
|
|
8280
|
-
if (!members.every((member) =>
|
|
8284
|
+
if (!members.every((member) => existsSync17(member.path))) return null;
|
|
8281
8285
|
return members.map(({ role, path: file }) => ({ role, path: file }));
|
|
8282
8286
|
}
|
|
8283
|
-
return
|
|
8287
|
+
return existsSync17(spec.outFile) ? [{ role: null, path: spec.outFile }] : null;
|
|
8284
8288
|
}
|
|
8285
8289
|
function handEditMembers(loaded, spec, lock, editPath) {
|
|
8286
8290
|
const entry = lock.entries[lockKey(spec.styleId, spec.assetId)];
|
|
8287
8291
|
const bases = handEditBases(spec, lock);
|
|
8288
|
-
if (entry &&
|
|
8292
|
+
if (entry && isMemberSetEntry(entry)) {
|
|
8289
8293
|
return entry.outputs.map((output, index) => ({
|
|
8290
8294
|
role: output.role ?? null,
|
|
8291
8295
|
path: memberPath(editPath, output.role, index, entry.outputs.length, output.mediaType),
|
|
@@ -8296,9 +8300,14 @@ function handEditMembers(loaded, spec, lock, editPath) {
|
|
|
8296
8300
|
}
|
|
8297
8301
|
async function startHandEdit(loaded, lock, spec, opts = {}) {
|
|
8298
8302
|
const entry = lock.entries[lockKey(spec.styleId, spec.assetId)];
|
|
8299
|
-
if (entry && entry.outputs.length > 1 && !
|
|
8303
|
+
if (entry && entry.outputs.length > 1 && !isMemberSetEntry(entry)) {
|
|
8300
8304
|
throw new ManifestEditError(
|
|
8301
|
-
`${spec.styleId}/${spec.assetId} is a
|
|
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)) {
|
|
8309
|
+
throw new ManifestEditError(
|
|
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`
|
|
8302
8311
|
);
|
|
8303
8312
|
}
|
|
8304
8313
|
const existing = spec.source ? path20.resolve(loaded.root, spec.source) : null;
|
|
@@ -8306,7 +8315,7 @@ async function startHandEdit(loaded, lock, spec, opts = {}) {
|
|
|
8306
8315
|
const members = handEditMembers(loaded, spec, lock, editPath);
|
|
8307
8316
|
let created = false;
|
|
8308
8317
|
for (const member of members) {
|
|
8309
|
-
if (
|
|
8318
|
+
if (existsSync17(member.path)) continue;
|
|
8310
8319
|
if (!member.base) {
|
|
8311
8320
|
throw new ManifestEditError(
|
|
8312
8321
|
`${spec.styleId}/${spec.assetId} has no generated PNG on disk to start from; generate or restore it first`
|
|
@@ -8404,18 +8413,18 @@ async function readHandEditCompanion(editPath) {
|
|
|
8404
8413
|
var MAX_HAND_EDIT_BYTES = 16 * 1024 * 1024;
|
|
8405
8414
|
async function saveHandEdit(loaded, lock, spec, input) {
|
|
8406
8415
|
const entry = lock.entries[lockKey(spec.styleId, spec.assetId)];
|
|
8407
|
-
const
|
|
8416
|
+
const memberSet = entry ? isMemberSetEntry(entry) : false;
|
|
8408
8417
|
const incoming = input.frames ?? (input.png ? [{ role: null, png: input.png }] : []);
|
|
8409
8418
|
if (!incoming.length) throw new ManifestEditError("the save carried no image");
|
|
8410
8419
|
if (input.project && input.project.length > MAX_HAND_EDIT_BYTES) {
|
|
8411
8420
|
throw new ManifestEditError(`the project file is ${input.project.length} bytes; the limit is ${MAX_HAND_EDIT_BYTES}`);
|
|
8412
8421
|
}
|
|
8413
|
-
if (
|
|
8422
|
+
if (memberSet) {
|
|
8414
8423
|
const expected = entry.outputs.map((output) => output.role ?? null);
|
|
8415
8424
|
const got = incoming.map((frame) => frame.role);
|
|
8416
8425
|
if (got.length !== expected.length || expected.some((role, index) => got[index] !== role)) {
|
|
8417
8426
|
throw new ManifestEditError(
|
|
8418
|
-
`${spec.styleId}/${spec.assetId} is a set of ${expected.length}
|
|
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`
|
|
8419
8428
|
);
|
|
8420
8429
|
}
|
|
8421
8430
|
} else if (incoming.length !== 1) {
|
|
@@ -8429,7 +8438,7 @@ async function saveHandEdit(loaded, lock, spec, input) {
|
|
|
8429
8438
|
validateMedia(frame.png, MediaType.PNG);
|
|
8430
8439
|
return decodePng(frame.png);
|
|
8431
8440
|
} catch (error) {
|
|
8432
|
-
const which =
|
|
8441
|
+
const which = memberSet ? `member ${frame.role ?? index}` : "the edit";
|
|
8433
8442
|
throw new ManifestEditError(`${which} is not a valid PNG: ${error instanceof Error ? error.message : String(error)}`);
|
|
8434
8443
|
}
|
|
8435
8444
|
});
|
|
@@ -8437,7 +8446,7 @@ async function saveHandEdit(loaded, lock, spec, input) {
|
|
|
8437
8446
|
const expectedSize = bases?.[0] ? decodePng(await readFile15(bases[0].path)) : { width: spec.width, height: spec.height };
|
|
8438
8447
|
for (const [index, image] of decoded.entries()) {
|
|
8439
8448
|
if (image.width !== expectedSize.width || image.height !== expectedSize.height) {
|
|
8440
|
-
const which =
|
|
8449
|
+
const which = memberSet ? `member ${incoming[index].role ?? index}` : "the edit";
|
|
8441
8450
|
throw new ManifestEditError(
|
|
8442
8451
|
`${which} is ${image.width}\xD7${image.height}; ${spec.styleId}/${spec.assetId} is ${expectedSize.width}\xD7${expectedSize.height}`
|
|
8443
8452
|
);
|
|
@@ -8671,7 +8680,7 @@ async function describeQuality(media, root, inspection) {
|
|
|
8671
8680
|
outputPaths.map((file) => describeOutput(media, root, path22.resolve(root, file)))
|
|
8672
8681
|
);
|
|
8673
8682
|
const recordPath = path22.resolve(root, inspection.record);
|
|
8674
|
-
const recordExists =
|
|
8683
|
+
const recordExists = existsSync18(recordPath);
|
|
8675
8684
|
let options = null;
|
|
8676
8685
|
let check = null;
|
|
8677
8686
|
if (recordExists) {
|
|
@@ -8753,11 +8762,11 @@ async function buildGallerySnapshot(opts) {
|
|
|
8753
8762
|
);
|
|
8754
8763
|
if (spec.source) {
|
|
8755
8764
|
const editPath = path22.resolve(root, spec.source);
|
|
8756
|
-
const memberCount =
|
|
8765
|
+
const memberCount = sourceIsStem(spec.source, entry, root) ? entry.outputs.length : 1;
|
|
8757
8766
|
const editShas = await Promise.all(
|
|
8758
8767
|
Array.from({ length: memberCount }, async (_, index) => {
|
|
8759
8768
|
const file = sourceOutputPath(spec.source, entry, index, root);
|
|
8760
|
-
return { file, sha256:
|
|
8769
|
+
return { file, sha256: existsSync18(file) ? await sha256File(file) : null };
|
|
8761
8770
|
})
|
|
8762
8771
|
);
|
|
8763
8772
|
edits = await Promise.all(editShas.map(({ file, sha256: editSha }) => describeOutput(media, root, file, { sha256: editSha })));
|
|
@@ -8765,7 +8774,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
8765
8774
|
const companion = await readHandEditCompanion(editPath);
|
|
8766
8775
|
if (companion) {
|
|
8767
8776
|
const projectPath = handEditProjectPath(editPath);
|
|
8768
|
-
const project = companion.project &&
|
|
8777
|
+
const project = companion.project && existsSync18(projectPath) ? await fileInfo(projectPath) : null;
|
|
8769
8778
|
let projectUrl = null;
|
|
8770
8779
|
if (project) {
|
|
8771
8780
|
const id = galleryMediaId(projectPath);
|
|
@@ -8802,7 +8811,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
8802
8811
|
}
|
|
8803
8812
|
} else if (spec.source) {
|
|
8804
8813
|
outputs = [await describeOutput(media, root, path22.resolve(root, spec.source))];
|
|
8805
|
-
} else if (!entry &&
|
|
8814
|
+
} else if (!entry && existsSync18(spec.outFile)) {
|
|
8806
8815
|
outputs = [await describeOutput(media, root, spec.outFile)];
|
|
8807
8816
|
} else {
|
|
8808
8817
|
outputs = [];
|
|
@@ -9879,7 +9888,7 @@ function openEditorSheet(item) {
|
|
|
9879
9888
|
const bar = el('div', 'rbar');
|
|
9880
9889
|
const close = el('button', null, 'Close'); close.type = 'button';
|
|
9881
9890
|
close.onclick = () => closeEditorSheet();
|
|
9882
|
-
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 + '
|
|
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) : ''));
|
|
9883
9892
|
const status = el('span', 'st');
|
|
9884
9893
|
const acts = el('div', 'acts');
|
|
9885
9894
|
const msg = el('span', 'msg');
|
|
@@ -9916,7 +9925,7 @@ async function sendOpen() {
|
|
|
9916
9925
|
const projectUrl = sources.fromEdit && item.editMeta && item.editMeta.projectUrl && SHEET.ready.version >= 2 ? item.editMeta.projectUrl : null;
|
|
9917
9926
|
const set = sources.files.length > 1;
|
|
9918
9927
|
if (set && SHEET.ready.version < 3) {
|
|
9919
|
-
SHEET.msg.textContent = 'This editor build cannot open
|
|
9928
|
+
SHEET.msg.textContent = 'This editor build cannot open sets; reinstall it with pixelkiln tools install editor.';
|
|
9920
9929
|
sheetStatus('bad', 'unsupported');
|
|
9921
9930
|
return;
|
|
9922
9931
|
}
|
|
@@ -9948,7 +9957,8 @@ async function sendOpen() {
|
|
|
9948
9957
|
const transfer = [pngs[0]];
|
|
9949
9958
|
if (set) {
|
|
9950
9959
|
message.frames = pngs.map((png, i) => ({ role: SHEET.roles[i], png }));
|
|
9951
|
-
|
|
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;
|
|
9952
9962
|
for (const png of pngs.slice(1)) transfer.push(png);
|
|
9953
9963
|
// The first frame is both png (older editors) and frames[0]; copy it so both transfer.
|
|
9954
9964
|
message.png = pngs[0].slice(0);
|
|
@@ -9981,9 +9991,9 @@ async function onEditorMessage(m) {
|
|
|
9981
9991
|
case 'pixelkiln:opened':
|
|
9982
9992
|
SHEET.opened = true;
|
|
9983
9993
|
SHEET.loading.remove();
|
|
9984
|
-
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 + '
|
|
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');
|
|
9985
9995
|
else if (SHEET.sentProject) sheetStatus('warn', 'editing the flattened edit file' + (m.frames > 1 ? 's' : '') + '; the layer file could not be opened');
|
|
9986
|
-
else if (m.source === 'frames') sheetStatus('ok', (SHEET.item.edits.length ? 'editing the edit files' : 'editing a copy of the generated frames
|
|
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)));
|
|
9987
9997
|
else sheetStatus('ok', SHEET.item.edit && SHEET.item.edit.exists ? 'editing the edit file' : 'editing a copy of the generated art');
|
|
9988
9998
|
sheetButtons();
|
|
9989
9999
|
break;
|
|
@@ -10010,7 +10020,7 @@ async function onEditorMessage(m) {
|
|
|
10010
10020
|
if (saved) SHEET.item = saved;
|
|
10011
10021
|
SHEET.dirty = false;
|
|
10012
10022
|
SHEET.pending = null;
|
|
10013
|
-
ui.notice = { id: SHEET.item.id, text: 'Saved ' + (saved && saved.edits.length > 1 ? saved.edits.length + '
|
|
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.' };
|
|
10014
10024
|
render();
|
|
10015
10025
|
if (pending.close) { closeEditorSheet(true); return; }
|
|
10016
10026
|
sheetStatus('ok', 'saved');
|
|
@@ -10583,16 +10593,18 @@ async function postHandEdit(item, action, extra) {
|
|
|
10583
10593
|
if (item.project) body.project = item.project;
|
|
10584
10594
|
snap = await postEdit(body);
|
|
10585
10595
|
}
|
|
10586
|
-
// A single PNG
|
|
10587
|
-
// member
|
|
10588
|
-
const editableOutputs = (item) => item.outputs.length &&
|
|
10589
|
-
|
|
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');
|
|
10590
10602
|
function handEditSection(item) {
|
|
10591
10603
|
const canStart = EDITABLE && item.declared && item.asset && projectOf(item)?.manifestSha256 && editableOutputs(item);
|
|
10592
10604
|
if (!item.edit && !canStart) return null;
|
|
10593
10605
|
const set = item.edits.length > 1;
|
|
10594
10606
|
const s = el('section', 'meta');
|
|
10595
|
-
s.append(el('h3', null, set ? 'Hand edit (' + item.edits.length + '
|
|
10607
|
+
s.append(el('h3', null, set ? 'Hand edit (' + item.edits.length + ' ' + memberNoun(item, item.edits.length) + ')' : 'Hand edit'));
|
|
10596
10608
|
if (item.edit) {
|
|
10597
10609
|
const pair = el('div', 'pair');
|
|
10598
10610
|
const shared = displayScale(item.width, item.height, 200, 160);
|
|
@@ -10608,7 +10620,7 @@ function handEditSection(item) {
|
|
|
10608
10620
|
};
|
|
10609
10621
|
// For a set, the member the preview is on; the strip there steps through them.
|
|
10610
10622
|
const at = set ? Math.min(ui.member, item.edits.length - 1) : 0;
|
|
10611
|
-
const which = set ? ' \xB7 ' + (item.outputs[at].role || '
|
|
10623
|
+
const which = set ? ' \xB7 ' + (item.outputs[at].role || memberNoun(item, 1) + ' ' + (at + 1)) : '';
|
|
10612
10624
|
pair.append(fig(item.outputs[at], 'generated' + which), fig(item.edits[at], 'edit' + which));
|
|
10613
10625
|
s.append(pair);
|
|
10614
10626
|
const dl = el('dl');
|
|
@@ -10617,9 +10629,9 @@ function handEditSection(item) {
|
|
|
10617
10629
|
row(dl, 'status', st);
|
|
10618
10630
|
row(dl, 'why', EDIT_STATUS_TEXT[item.editStatus] || '');
|
|
10619
10631
|
if (set) {
|
|
10620
|
-
row(dl, 'files', item.source + ' \u2014 one per
|
|
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') });
|
|
10621
10633
|
const differing = item.editChanged.filter(Boolean).length;
|
|
10622
|
-
row(dl, 'changed', differing + ' of ' + item.edits.length + '
|
|
10634
|
+
row(dl, 'changed', differing + ' of ' + item.edits.length + ' ' + memberNoun(item, item.edits.length) + ' differ from the generated art' +
|
|
10623
10635
|
(differing ? ': ' + item.edits.filter((e, i) => item.editChanged[i]).map((e) => item.outputs[item.edits.indexOf(e)].role || e.path.split('/').pop()).join(', ') : ''));
|
|
10624
10636
|
} else {
|
|
10625
10637
|
row(dl, 'file', item.edit.path, { mono: true, copy: item.edit.absolutePath });
|
|
@@ -10633,8 +10645,8 @@ function handEditSection(item) {
|
|
|
10633
10645
|
}
|
|
10634
10646
|
s.append(dl);
|
|
10635
10647
|
} else {
|
|
10636
|
-
s.append(el('div', 'state-dim',
|
|
10637
|
-
? 'Touch the
|
|
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.'
|
|
10638
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.'));
|
|
10639
10651
|
}
|
|
10640
10652
|
if (EDITABLE && item.declared && projectOf(item)?.manifestSha256) {
|
|
@@ -10665,8 +10677,9 @@ function handEditSection(item) {
|
|
|
10665
10677
|
go('Open in desktop editor', 'start-edit', { open: true }, 'Opened ' + item.edit.path + ' in your editor. Save there, then Refresh.');
|
|
10666
10678
|
go('Detach edit', 'detach-edit', {}, 'Detached. The file' + (set ? 's are' : ' is') + ' still at ' + item.source + '; the generated art is placed again.');
|
|
10667
10679
|
} else if (canStart) {
|
|
10668
|
-
|
|
10669
|
-
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.');
|
|
10670
10683
|
}
|
|
10671
10684
|
acts.append(msg);
|
|
10672
10685
|
s.append(acts);
|
|
@@ -11969,7 +11982,7 @@ import path24 from "path";
|
|
|
11969
11982
|
|
|
11970
11983
|
// src/editor/install.ts
|
|
11971
11984
|
import { createHash as createHash4 } from "crypto";
|
|
11972
|
-
import { existsSync as
|
|
11985
|
+
import { existsSync as existsSync19 } from "fs";
|
|
11973
11986
|
import { mkdir as mkdir7, readFile as readFile18, rename as rename8, rm as rm9, stat as stat4, writeFile as writeFile10 } from "fs/promises";
|
|
11974
11987
|
import os2 from "os";
|
|
11975
11988
|
import path23 from "path";
|
|
@@ -12068,7 +12081,7 @@ async function editorStatus(opts = {}) {
|
|
|
12068
12081
|
for (const [name, expected] of Object.entries(pin.files)) {
|
|
12069
12082
|
totalBytes += expected.bytes;
|
|
12070
12083
|
const file = path23.join(dir, name);
|
|
12071
|
-
const info =
|
|
12084
|
+
const info = existsSync19(file) ? await stat4(file).catch(() => null) : null;
|
|
12072
12085
|
if (info && info.size === expected.bytes && await sha256Of(file) === expected.sha256) {
|
|
12073
12086
|
installedBytes += expected.bytes;
|
|
12074
12087
|
} else {
|
|
@@ -12263,7 +12276,7 @@ function createGalleryEditorHandlers(opts = {}) {
|
|
|
12263
12276
|
|
|
12264
12277
|
// src/pipeline/salvage.ts
|
|
12265
12278
|
import { readFile as readFile20 } from "fs/promises";
|
|
12266
|
-
import { existsSync as
|
|
12279
|
+
import { existsSync as existsSync20 } from "fs";
|
|
12267
12280
|
import path25 from "path";
|
|
12268
12281
|
function providerClaimId(provider, objectId) {
|
|
12269
12282
|
return `${encodeURIComponent(provider)}:${encodeURIComponent(objectId)}`;
|
|
@@ -12271,7 +12284,7 @@ function providerClaimId(provider, objectId) {
|
|
|
12271
12284
|
async function loadClaims(lockPaths, opts = {}) {
|
|
12272
12285
|
const claimed = /* @__PURE__ */ new Set();
|
|
12273
12286
|
for (const p of lockPaths) {
|
|
12274
|
-
if (!
|
|
12287
|
+
if (!existsSync20(p)) throw new Error(`Claim lockfile not found: ${p}`);
|
|
12275
12288
|
let parsed;
|
|
12276
12289
|
try {
|
|
12277
12290
|
parsed = parseLock(JSON.parse(await readFile20(p, "utf8")));
|
|
@@ -12332,7 +12345,7 @@ async function loadSiblingManifests(ownManifestPath, workspaceManifestPaths, cla
|
|
|
12332
12345
|
const siblings = [];
|
|
12333
12346
|
for (const siblingManifestPath of siblingManifestPaths) {
|
|
12334
12347
|
if (path25.resolve(siblingManifestPath) === own) continue;
|
|
12335
|
-
if (!
|
|
12348
|
+
if (!existsSync20(siblingManifestPath)) continue;
|
|
12336
12349
|
try {
|
|
12337
12350
|
const { manifest } = await loadManifest(siblingManifestPath);
|
|
12338
12351
|
siblings.push({ label: path25.basename(path25.dirname(siblingManifestPath)), manifest });
|
|
@@ -12554,11 +12567,11 @@ async function workspaceStatus(ws, dir) {
|
|
|
12554
12567
|
}
|
|
12555
12568
|
|
|
12556
12569
|
// src/pipeline/cache-health.ts
|
|
12557
|
-
import { existsSync as
|
|
12570
|
+
import { existsSync as existsSync21 } from "fs";
|
|
12558
12571
|
import { readFile as readFile21, readdir as readdir2, rm as rm10 } from "fs/promises";
|
|
12559
12572
|
import path26 from "path";
|
|
12560
12573
|
async function inspectCaches(lock, lockPath, options = {}) {
|
|
12561
|
-
if (options.prune && !
|
|
12574
|
+
if (options.prune && !existsSync21(lockPath)) {
|
|
12562
12575
|
throw new Error(`Refusing to prune without an existing lockfile at ${path26.resolve(lockPath)}`);
|
|
12563
12576
|
}
|
|
12564
12577
|
const contentDir = path26.resolve(path26.dirname(lockPath), ".pixelkiln", "cache");
|
|
@@ -12603,7 +12616,7 @@ async function inspectCaches(lock, lockPath, options = {}) {
|
|
|
12603
12616
|
async function inspectContentCache(contentDir, referenced) {
|
|
12604
12617
|
const report = {
|
|
12605
12618
|
path: contentDir,
|
|
12606
|
-
exists:
|
|
12619
|
+
exists: existsSync21(contentDir),
|
|
12607
12620
|
files: 0,
|
|
12608
12621
|
bytes: 0,
|
|
12609
12622
|
valid: 0,
|
|
@@ -12667,7 +12680,7 @@ async function inspectContentCache(contentDir, referenced) {
|
|
|
12667
12680
|
async function inspectRemoteHashCache(remotePath) {
|
|
12668
12681
|
const report = {
|
|
12669
12682
|
path: remotePath,
|
|
12670
|
-
exists:
|
|
12683
|
+
exists: existsSync21(remotePath),
|
|
12671
12684
|
entries: 0,
|
|
12672
12685
|
valid: 0,
|
|
12673
12686
|
invalidIds: [],
|
|
@@ -12717,8 +12730,11 @@ function normalizeTileRules(raw) {
|
|
|
12717
12730
|
};
|
|
12718
12731
|
}
|
|
12719
12732
|
function exportTileset(entry, spec, options) {
|
|
12720
|
-
|
|
12733
|
+
let outputs = resolveSpecEntryOutputs(entry, spec);
|
|
12721
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
|
+
}
|
|
12722
12738
|
const packed = packSprites(
|
|
12723
12739
|
outputs.map((output) => ({
|
|
12724
12740
|
id: output.role ?? fallbackOutputRole(output.index),
|
|
@@ -13212,7 +13228,7 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
13212
13228
|
|
|
13213
13229
|
// src/recipes.ts
|
|
13214
13230
|
import path28 from "path";
|
|
13215
|
-
import { existsSync as
|
|
13231
|
+
import { existsSync as existsSync22 } from "fs";
|
|
13216
13232
|
import { readdir as readdir3, readFile as readFile23 } from "fs/promises";
|
|
13217
13233
|
import { fileURLToPath } from "url";
|
|
13218
13234
|
import { z as z9 } from "zod";
|
|
@@ -13415,7 +13431,7 @@ async function readRecipeFile(recipePath, bundled) {
|
|
|
13415
13431
|
return { recipe: parsed.data, path: absolute, dir: path28.dirname(absolute), bundled };
|
|
13416
13432
|
}
|
|
13417
13433
|
async function walkRecipeFiles(root) {
|
|
13418
|
-
if (!
|
|
13434
|
+
if (!existsSync22(root)) return [];
|
|
13419
13435
|
const out = [];
|
|
13420
13436
|
for (const entry of await readdir3(root, { withFileTypes: true })) {
|
|
13421
13437
|
const resolved = path28.join(root, entry.name);
|
|
@@ -13453,7 +13469,7 @@ function parseSelector(selector) {
|
|
|
13453
13469
|
}
|
|
13454
13470
|
async function resolveRecipe(target, bundledRoot = bundledRecipeRoot()) {
|
|
13455
13471
|
const local = path28.resolve(target);
|
|
13456
|
-
if (
|
|
13472
|
+
if (existsSync22(local)) {
|
|
13457
13473
|
const recipePath = path28.basename(local) === RECIPE_FILE ? local : path28.join(local, RECIPE_FILE);
|
|
13458
13474
|
return readRecipeFile(recipePath, false);
|
|
13459
13475
|
}
|
|
@@ -13469,7 +13485,7 @@ async function resolveRecipe(target, bundledRoot = bundledRecipeRoot()) {
|
|
|
13469
13485
|
}
|
|
13470
13486
|
async function verifyFile(root, file) {
|
|
13471
13487
|
const absolute = path28.resolve(root, ...file.path.split("/"));
|
|
13472
|
-
if (!
|
|
13488
|
+
if (!existsSync22(absolute)) {
|
|
13473
13489
|
return { path: file.path, expectedSha256: file.sha256, actualSha256: null, status: "missing" };
|
|
13474
13490
|
}
|
|
13475
13491
|
const actualSha256 = await sha256File(absolute);
|
|
@@ -13547,7 +13563,7 @@ async function installRecipe(target, options = {}) {
|
|
|
13547
13563
|
})));
|
|
13548
13564
|
if (!options.force) {
|
|
13549
13565
|
for (const file of files) {
|
|
13550
|
-
if (!
|
|
13566
|
+
if (!existsSync22(file.path)) continue;
|
|
13551
13567
|
const current = await readFile23(file.path);
|
|
13552
13568
|
if (!current.equals(file.data)) {
|
|
13553
13569
|
throw new Error(
|
|
@@ -13572,7 +13588,7 @@ async function installRecipe(target, options = {}) {
|
|
|
13572
13588
|
|
|
13573
13589
|
// src/pipeline/quality-regression.ts
|
|
13574
13590
|
import path29 from "path";
|
|
13575
|
-
import { existsSync as
|
|
13591
|
+
import { existsSync as existsSync23 } from "fs";
|
|
13576
13592
|
import { readFile as readFile24 } from "fs/promises";
|
|
13577
13593
|
import { z as z10 } from "zod";
|
|
13578
13594
|
var SHA256_RE2 = /^[0-9a-f]{64}$/;
|
|
@@ -13815,7 +13831,7 @@ async function snapshotQualityBaseline(inputs, baselinePath, options = {}) {
|
|
|
13815
13831
|
cases
|
|
13816
13832
|
});
|
|
13817
13833
|
const data = Buffer.from(JSON.stringify(baseline, null, 2) + "\n");
|
|
13818
|
-
if (
|
|
13834
|
+
if (existsSync23(absolute) && !options.force) {
|
|
13819
13835
|
const current = await readFile24(absolute);
|
|
13820
13836
|
if (!current.equals(data)) {
|
|
13821
13837
|
throw new Error(`Quality baseline already exists with different content: ${absolute}. Pass --force to replace it.`);
|
|
@@ -14056,7 +14072,7 @@ async function provenanceFile(id, file) {
|
|
|
14056
14072
|
return {
|
|
14057
14073
|
id,
|
|
14058
14074
|
path: absolute,
|
|
14059
|
-
sha256:
|
|
14075
|
+
sha256: existsSync24(absolute) ? await sha256File(absolute) : null,
|
|
14060
14076
|
included: true
|
|
14061
14077
|
};
|
|
14062
14078
|
}
|
|
@@ -14696,7 +14712,7 @@ async function confirm(question, auto) {
|
|
|
14696
14712
|
return answer === "y" || answer === "yes";
|
|
14697
14713
|
}
|
|
14698
14714
|
async function requireCompleteWorkspaceClaims(workspacePath) {
|
|
14699
|
-
if (!
|
|
14715
|
+
if (!existsSync24(workspacePath)) {
|
|
14700
14716
|
throw new Error(`Workspace catalog not found: ${workspacePath}`);
|
|
14701
14717
|
}
|
|
14702
14718
|
const dir = path30.dirname(path30.resolve(workspacePath));
|
|
@@ -14955,7 +14971,7 @@ async function main() {
|
|
|
14955
14971
|
if (args.command === "init") {
|
|
14956
14972
|
if (!args.from) throw new Error("init needs --from <dir> pointing at your existing PNGs.");
|
|
14957
14973
|
const root = path30.resolve(args.from);
|
|
14958
|
-
if (!
|
|
14974
|
+
if (!existsSync24(root)) throw new Error(`No directory at ${root}`);
|
|
14959
14975
|
const generator = args.generator ?? "map";
|
|
14960
14976
|
if (generator !== "1dir" && generator !== "map") {
|
|
14961
14977
|
throw new Error(`--generator must be "1dir" or "map", got "${args.generator}".`);
|
|
@@ -15075,7 +15091,7 @@ async function main() {
|
|
|
15075
15091
|
log(` registered "${id}" in ${path30.relative(process.cwd(), workspacePath)}`);
|
|
15076
15092
|
log(` manifest: ${project.manifest}`);
|
|
15077
15093
|
log(` lock: ${project.lock}`);
|
|
15078
|
-
if (!
|
|
15094
|
+
if (!existsSync24(lockPath)) {
|
|
15079
15095
|
log(
|
|
15080
15096
|
` warning: no lockfile there yet \u2014 this project contributes no claims until one is generated`
|
|
15081
15097
|
);
|
|
@@ -15099,7 +15115,7 @@ async function main() {
|
|
|
15099
15115
|
return;
|
|
15100
15116
|
}
|
|
15101
15117
|
if (args.subcommand === "list") {
|
|
15102
|
-
if (!
|
|
15118
|
+
if (!existsSync24(workspacePath)) {
|
|
15103
15119
|
throw new Error(`Workspace catalog not found: ${workspacePath}`);
|
|
15104
15120
|
}
|
|
15105
15121
|
const ws = await loadWorkspace(workspacePath);
|
|
@@ -15119,7 +15135,7 @@ async function main() {
|
|
|
15119
15135
|
return;
|
|
15120
15136
|
}
|
|
15121
15137
|
if (args.subcommand === "status") {
|
|
15122
|
-
if (!
|
|
15138
|
+
if (!existsSync24(workspacePath)) {
|
|
15123
15139
|
throw new Error(`Workspace catalog not found: ${workspacePath}`);
|
|
15124
15140
|
}
|
|
15125
15141
|
const ws = await loadWorkspace(workspacePath);
|
|
@@ -15175,7 +15191,7 @@ async function main() {
|
|
|
15175
15191
|
}
|
|
15176
15192
|
if (args.command === "gallery" && args.workspace) {
|
|
15177
15193
|
const workspacePath = path30.resolve(args.workspace);
|
|
15178
|
-
if (!
|
|
15194
|
+
if (!existsSync24(workspacePath)) {
|
|
15179
15195
|
throw new Error(`Workspace catalog not found: ${workspacePath}`);
|
|
15180
15196
|
}
|
|
15181
15197
|
const filter = { styles: args.styles, assets: args.assets };
|
|
@@ -15210,7 +15226,7 @@ async function main() {
|
|
|
15210
15226
|
});
|
|
15211
15227
|
return;
|
|
15212
15228
|
}
|
|
15213
|
-
if (!
|
|
15229
|
+
if (!existsSync24(path30.resolve(args.manifest))) {
|
|
15214
15230
|
throw new Error(
|
|
15215
15231
|
`No manifest at ${path30.resolve(args.manifest)}. Pass --manifest, or run \`pixelkiln init --from <dir>\`.`
|
|
15216
15232
|
);
|
|
@@ -15316,13 +15332,14 @@ async function main() {
|
|
|
15316
15332
|
}
|
|
15317
15333
|
const started = await startHandEdit(loaded, lock, spec);
|
|
15318
15334
|
const set = started.members.length > 1;
|
|
15319
|
-
|
|
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" : ""}`);
|
|
15320
15337
|
if (set) for (const member of started.members) log(` ${member.role ?? ""} ${path30.relative(process.cwd(), member.path)}`);
|
|
15321
15338
|
if (args.noOpen) {
|
|
15322
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`);
|
|
15323
15340
|
} else {
|
|
15324
15341
|
const command = openInEditor(started.members[0].path);
|
|
15325
|
-
log(` opened ${set ?
|
|
15342
|
+
log(` opened ${set ? `the first ${kind === "frames" ? "frame" : "member"} ` : ""}with: ${command}${process.env.PIXELKILN_EDITOR ? "" : " (set PIXELKILN_EDITOR to choose the program)"}`);
|
|
15326
15343
|
}
|
|
15327
15344
|
return;
|
|
15328
15345
|
}
|
|
@@ -15458,7 +15475,7 @@ async function main() {
|
|
|
15458
15475
|
let intact = true;
|
|
15459
15476
|
for (const output of entry.outputs) {
|
|
15460
15477
|
const file = resolveOutputPath(output.path, item.spec.root);
|
|
15461
|
-
if (!
|
|
15478
|
+
if (!existsSync24(file) || await sha256File(file) !== output.sha256) {
|
|
15462
15479
|
intact = false;
|
|
15463
15480
|
break;
|
|
15464
15481
|
}
|
|
@@ -15840,7 +15857,7 @@ async function main() {
|
|
|
15840
15857
|
const lockPaths = [
|
|
15841
15858
|
.../* @__PURE__ */ new Set([
|
|
15842
15859
|
...workspaceLockPaths,
|
|
15843
|
-
...
|
|
15860
|
+
...existsSync24(ownLock) ? [ownLock] : [],
|
|
15844
15861
|
...args.claims.map((c) => path30.resolve(c))
|
|
15845
15862
|
])
|
|
15846
15863
|
];
|