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/index.js
CHANGED
|
@@ -544,11 +544,11 @@ var PixelLabClient = class {
|
|
|
544
544
|
* common than the failure mode of retrying (a duplicate object), and a
|
|
545
545
|
* duplicate is visible and free to delete whereas a silent gap is neither.
|
|
546
546
|
*/
|
|
547
|
-
async request(
|
|
547
|
+
async request(path29, init, attempt = 0) {
|
|
548
548
|
const auth = this.apiKey.startsWith("Bearer ") ? this.apiKey : `Bearer ${this.apiKey}`;
|
|
549
549
|
let res;
|
|
550
550
|
try {
|
|
551
|
-
res = await fetch(`${BASE}${
|
|
551
|
+
res = await fetch(`${BASE}${path29}`, {
|
|
552
552
|
...init,
|
|
553
553
|
signal: init?.signal ?? AbortSignal.timeout(this.timeoutMs),
|
|
554
554
|
headers: {
|
|
@@ -560,24 +560,24 @@ var PixelLabClient = class {
|
|
|
560
560
|
} catch (err) {
|
|
561
561
|
if (attempt < MAX_RETRIES) {
|
|
562
562
|
await sleep(backoffMs(attempt));
|
|
563
|
-
return this.request(
|
|
563
|
+
return this.request(path29, init, attempt + 1);
|
|
564
564
|
}
|
|
565
565
|
throw err;
|
|
566
566
|
}
|
|
567
567
|
if (!res.ok && shouldRetry(res.status) && attempt < MAX_RETRIES) {
|
|
568
568
|
const waitMs = retryAfterMs(res.headers.get("retry-after")) ?? backoffMs(attempt);
|
|
569
569
|
await sleep(waitMs);
|
|
570
|
-
return this.request(
|
|
570
|
+
return this.request(path29, init, attempt + 1);
|
|
571
571
|
}
|
|
572
572
|
const text = await res.text();
|
|
573
573
|
if (!res.ok) {
|
|
574
|
-
throw new PixelLabError(`${init?.method ?? "GET"} ${
|
|
574
|
+
throw new PixelLabError(`${init?.method ?? "GET"} ${path29} \u2192 ${res.status}`, res.status, text);
|
|
575
575
|
}
|
|
576
576
|
if (!text) return {};
|
|
577
577
|
try {
|
|
578
578
|
return JSON.parse(text);
|
|
579
579
|
} catch {
|
|
580
|
-
throw new Error(`${init?.method ?? "GET"} ${
|
|
580
|
+
throw new Error(`${init?.method ?? "GET"} ${path29} returned invalid JSON`);
|
|
581
581
|
}
|
|
582
582
|
}
|
|
583
583
|
async balance() {
|
|
@@ -1628,9 +1628,9 @@ var RetroDiffusionClient = class {
|
|
|
1628
1628
|
}
|
|
1629
1629
|
return balance;
|
|
1630
1630
|
}
|
|
1631
|
-
async call(
|
|
1631
|
+
async call(path29, init = {}) {
|
|
1632
1632
|
if (!this.token) throw new Error("RD_API_KEY is not set");
|
|
1633
|
-
const response = await this.request(`${this.baseUrl}${
|
|
1633
|
+
const response = await this.request(`${this.baseUrl}${path29}`, {
|
|
1634
1634
|
...init,
|
|
1635
1635
|
headers: {
|
|
1636
1636
|
"Content-Type": "application/json",
|
|
@@ -2433,10 +2433,10 @@ import { createReadStream } from "fs";
|
|
|
2433
2433
|
function sha256(data) {
|
|
2434
2434
|
return createHash("sha256").update(data).digest("hex");
|
|
2435
2435
|
}
|
|
2436
|
-
async function sha256File(
|
|
2436
|
+
async function sha256File(path29) {
|
|
2437
2437
|
return new Promise((resolve, reject) => {
|
|
2438
2438
|
const hash = createHash("sha256");
|
|
2439
|
-
const stream = createReadStream(
|
|
2439
|
+
const stream = createReadStream(path29);
|
|
2440
2440
|
stream.on("data", (chunk2) => hash.update(chunk2));
|
|
2441
2441
|
stream.on("error", reject);
|
|
2442
2442
|
stream.on("end", () => resolve(hash.digest("hex")));
|
|
@@ -2729,12 +2729,7 @@ var ComfyUIProvider = class _ComfyUIProvider {
|
|
|
2729
2729
|
}
|
|
2730
2730
|
validate(spec, styleImages) {
|
|
2731
2731
|
const options = resolvedOptions(spec);
|
|
2732
|
-
if (spec.source && !spec.revision)
|
|
2733
|
-
if (spec.generator === "frames") {
|
|
2734
|
-
throw new Error("ComfyUI frame sets require generated provider outputs, not asset.source");
|
|
2735
|
-
}
|
|
2736
|
-
return;
|
|
2737
|
-
}
|
|
2732
|
+
if (spec.source && !spec.revision && spec.generator !== "frames") return;
|
|
2738
2733
|
if (spec.width < 16 || spec.height < 16 || spec.width > 4096 || spec.height > 4096) {
|
|
2739
2734
|
throw new Error("ComfyUI output dimensions must be between 16 and 4096 pixels");
|
|
2740
2735
|
}
|
|
@@ -3568,10 +3563,11 @@ registerProvider({
|
|
|
3568
3563
|
|
|
3569
3564
|
// src/manifest.ts
|
|
3570
3565
|
import { readFile as readFile3 } from "fs/promises";
|
|
3571
|
-
import { existsSync as
|
|
3566
|
+
import { existsSync as existsSync4 } from "fs";
|
|
3572
3567
|
import path5 from "path";
|
|
3573
3568
|
|
|
3574
3569
|
// src/outputs.ts
|
|
3570
|
+
import { existsSync as existsSync3 } from "fs";
|
|
3575
3571
|
import path4 from "path";
|
|
3576
3572
|
|
|
3577
3573
|
// src/lock.ts
|
|
@@ -3761,13 +3757,31 @@ function resolveOutputPath(recordedPath, manifestDir) {
|
|
|
3761
3757
|
return path4.resolve(manifestDir, recordedPath.split(/[\\/]/).join(path4.sep));
|
|
3762
3758
|
}
|
|
3763
3759
|
function expectedOutputPath(spec, role, index, total, mediaType) {
|
|
3764
|
-
|
|
3760
|
+
return memberPath(spec.outFile, role, index, total, mediaType);
|
|
3761
|
+
}
|
|
3762
|
+
function memberPath(file, role, index, total, mediaType) {
|
|
3763
|
+
const originalExt = path4.extname(file);
|
|
3765
3764
|
const ext = mediaType ? mediaExtension(mediaType) : originalExt || ".png";
|
|
3766
|
-
const stem = originalExt ?
|
|
3765
|
+
const stem = originalExt ? file.slice(0, -originalExt.length) : file;
|
|
3767
3766
|
if (total === 1) return `${stem}${ext}`;
|
|
3768
3767
|
const safeRole = (role ?? fallbackOutputRole(index)).replace(/[^a-zA-Z0-9_-]+/g, "-");
|
|
3769
3768
|
return `${stem}-${safeRole}${ext}`;
|
|
3770
3769
|
}
|
|
3770
|
+
function isMemberSetEntry(entry) {
|
|
3771
|
+
return entry.outputs.length > 1 && entry.outputs.every((output) => !output.mediaType || output.mediaType === "image/png");
|
|
3772
|
+
}
|
|
3773
|
+
function isFrameSetEntry(entry) {
|
|
3774
|
+
return entry.generator === "frames" && isMemberSetEntry(entry);
|
|
3775
|
+
}
|
|
3776
|
+
function sourceIsStem(source, entry, manifestDir) {
|
|
3777
|
+
return isMemberSetEntry(entry) && !existsSync3(path4.resolve(manifestDir, source));
|
|
3778
|
+
}
|
|
3779
|
+
function sourceOutputPath(source, entry, index, manifestDir) {
|
|
3780
|
+
const absolute = path4.resolve(manifestDir, source);
|
|
3781
|
+
if (!sourceIsStem(source, entry, manifestDir)) return absolute;
|
|
3782
|
+
const output = entry.outputs[index];
|
|
3783
|
+
return memberPath(absolute, output.role, index, entry.outputs.length, output.mediaType);
|
|
3784
|
+
}
|
|
3771
3785
|
function currentOutputPath(output, spec, index, total) {
|
|
3772
3786
|
return expectedOutputPath(spec, output.role, index, total, output.mediaType);
|
|
3773
3787
|
}
|
|
@@ -3858,7 +3872,7 @@ function resolveSpecOutputs(spec, lock, manifestDir) {
|
|
|
3858
3872
|
// src/manifest.ts
|
|
3859
3873
|
async function loadManifest(manifestPath) {
|
|
3860
3874
|
const abs = path5.resolve(manifestPath);
|
|
3861
|
-
if (!
|
|
3875
|
+
if (!existsSync4(abs)) throw new Error(`No manifest at ${abs}`);
|
|
3862
3876
|
const input = ManifestInputSchema.safeParse(JSON.parse(await readFile3(abs, "utf8")));
|
|
3863
3877
|
if (!input.success) {
|
|
3864
3878
|
const issues = formatManifestIssues(input.error.issues);
|
|
@@ -4030,7 +4044,7 @@ async function resolveSpecs(loaded, filter) {
|
|
|
4030
4044
|
const abs = path5.resolve(root, rel);
|
|
4031
4045
|
let hit = styleImageCache.get(abs);
|
|
4032
4046
|
if (!hit) {
|
|
4033
|
-
if (!
|
|
4047
|
+
if (!existsSync4(abs)) throw new Error(`Style image not found: ${abs}`);
|
|
4034
4048
|
const buf = await readFile3(abs);
|
|
4035
4049
|
const metadata = imageMetadata(buf);
|
|
4036
4050
|
if (!metadata) throw new Error(`Style image is not a readable PNG or JPEG: ${abs}`);
|
|
@@ -4271,7 +4285,7 @@ function pngPath(file) {
|
|
|
4271
4285
|
return extension ? `${file.slice(0, -extension.length)}.png` : `${file}.png`;
|
|
4272
4286
|
}
|
|
4273
4287
|
async function optionalRevisionImage(file, label, requiredFormat) {
|
|
4274
|
-
if (!
|
|
4288
|
+
if (!existsSync4(file)) return null;
|
|
4275
4289
|
const bytes = await readFile3(file);
|
|
4276
4290
|
const metadata = imageMetadata(bytes);
|
|
4277
4291
|
if (!metadata) throw new Error(`${label} is not a readable PNG or JPEG: ${file}`);
|
|
@@ -4823,7 +4837,7 @@ async function writeArtifactBundle(files, options = {}) {
|
|
|
4823
4837
|
|
|
4824
4838
|
// src/recipes.ts
|
|
4825
4839
|
import path7 from "path";
|
|
4826
|
-
import { existsSync as
|
|
4840
|
+
import { existsSync as existsSync5 } from "fs";
|
|
4827
4841
|
import { readdir, readFile as readFile5 } from "fs/promises";
|
|
4828
4842
|
import { fileURLToPath } from "url";
|
|
4829
4843
|
import { z as z3 } from "zod";
|
|
@@ -5026,7 +5040,7 @@ async function readRecipeFile(recipePath, bundled) {
|
|
|
5026
5040
|
return { recipe: parsed.data, path: absolute, dir: path7.dirname(absolute), bundled };
|
|
5027
5041
|
}
|
|
5028
5042
|
async function walkRecipeFiles(root) {
|
|
5029
|
-
if (!
|
|
5043
|
+
if (!existsSync5(root)) return [];
|
|
5030
5044
|
const out = [];
|
|
5031
5045
|
for (const entry of await readdir(root, { withFileTypes: true })) {
|
|
5032
5046
|
const resolved = path7.join(root, entry.name);
|
|
@@ -5064,7 +5078,7 @@ function parseSelector(selector) {
|
|
|
5064
5078
|
}
|
|
5065
5079
|
async function resolveRecipe(target, bundledRoot = bundledRecipeRoot()) {
|
|
5066
5080
|
const local = path7.resolve(target);
|
|
5067
|
-
if (
|
|
5081
|
+
if (existsSync5(local)) {
|
|
5068
5082
|
const recipePath = path7.basename(local) === RECIPE_FILE ? local : path7.join(local, RECIPE_FILE);
|
|
5069
5083
|
return readRecipeFile(recipePath, false);
|
|
5070
5084
|
}
|
|
@@ -5080,7 +5094,7 @@ async function resolveRecipe(target, bundledRoot = bundledRecipeRoot()) {
|
|
|
5080
5094
|
}
|
|
5081
5095
|
async function verifyFile(root, file) {
|
|
5082
5096
|
const absolute = path7.resolve(root, ...file.path.split("/"));
|
|
5083
|
-
if (!
|
|
5097
|
+
if (!existsSync5(absolute)) {
|
|
5084
5098
|
return { path: file.path, expectedSha256: file.sha256, actualSha256: null, status: "missing" };
|
|
5085
5099
|
}
|
|
5086
5100
|
const actualSha256 = await sha256File(absolute);
|
|
@@ -5158,7 +5172,7 @@ async function installRecipe(target, options = {}) {
|
|
|
5158
5172
|
})));
|
|
5159
5173
|
if (!options.force) {
|
|
5160
5174
|
for (const file of files) {
|
|
5161
|
-
if (!
|
|
5175
|
+
if (!existsSync5(file.path)) continue;
|
|
5162
5176
|
const current = await readFile5(file.path);
|
|
5163
5177
|
if (!current.equals(file.data)) {
|
|
5164
5178
|
throw new Error(
|
|
@@ -5182,11 +5196,11 @@ async function installRecipe(target, options = {}) {
|
|
|
5182
5196
|
}
|
|
5183
5197
|
|
|
5184
5198
|
// src/pipeline/plan.ts
|
|
5185
|
-
import { existsSync as
|
|
5199
|
+
import { existsSync as existsSync9 } from "fs";
|
|
5186
5200
|
import path12 from "path";
|
|
5187
5201
|
|
|
5188
5202
|
// src/pipeline/quality-profile.ts
|
|
5189
|
-
import { existsSync as
|
|
5203
|
+
import { existsSync as existsSync7 } from "fs";
|
|
5190
5204
|
import path10 from "path";
|
|
5191
5205
|
|
|
5192
5206
|
// src/pipeline/refine.ts
|
|
@@ -5198,7 +5212,7 @@ import { promisify } from "util";
|
|
|
5198
5212
|
|
|
5199
5213
|
// src/pipeline/audit.ts
|
|
5200
5214
|
import { readFile as readFile6 } from "fs/promises";
|
|
5201
|
-
import { existsSync as
|
|
5215
|
+
import { existsSync as existsSync6 } from "fs";
|
|
5202
5216
|
import path8 from "path";
|
|
5203
5217
|
function colorDistance(a, b) {
|
|
5204
5218
|
const rmean = (a.r + b.r) / 2;
|
|
@@ -5244,7 +5258,7 @@ async function auditStyle(loaded, specs, styleId, lock) {
|
|
|
5244
5258
|
const unreadable = [];
|
|
5245
5259
|
for (const spec of mine) {
|
|
5246
5260
|
for (const output of resolveSpecOutputs(spec, lock, loaded.root)) {
|
|
5247
|
-
if (!
|
|
5261
|
+
if (!existsSync6(output.absolutePath)) {
|
|
5248
5262
|
missing.push(output.id);
|
|
5249
5263
|
continue;
|
|
5250
5264
|
}
|
|
@@ -5274,7 +5288,7 @@ async function auditStyle(loaded, specs, styleId, lock) {
|
|
|
5274
5288
|
const refPalettes = [];
|
|
5275
5289
|
for (const img of style.styleImages) {
|
|
5276
5290
|
const abs = path8.resolve(loaded.root, img.path);
|
|
5277
|
-
if (!
|
|
5291
|
+
if (!existsSync6(abs)) continue;
|
|
5278
5292
|
try {
|
|
5279
5293
|
refPalettes.push(extractPalette(decodePng(await readFile6(abs)), 12));
|
|
5280
5294
|
} catch {
|
|
@@ -5903,7 +5917,7 @@ async function qualitySource(spec, lock) {
|
|
|
5903
5917
|
return { items: null, reason: "frame-set quality requires ordered downloaded provider outputs" };
|
|
5904
5918
|
}
|
|
5905
5919
|
const source = path10.resolve(spec.root, spec.source);
|
|
5906
|
-
return
|
|
5920
|
+
return existsSync7(source) ? { items: [{ role: "source", path: source }] } : { items: null, reason: `declared source is missing: ${spec.source}` };
|
|
5907
5921
|
}
|
|
5908
5922
|
const entry = lock.entries[lockKey(spec.styleId, spec.assetId)];
|
|
5909
5923
|
if (!entry) return { items: null, reason: "raw provider output is not in the lockfile" };
|
|
@@ -5932,7 +5946,7 @@ async function qualitySource(spec, lock) {
|
|
|
5932
5946
|
}
|
|
5933
5947
|
if (role) roles.add(role);
|
|
5934
5948
|
const source = currentEntryOutputPath(entry, spec, index);
|
|
5935
|
-
if (!
|
|
5949
|
+
if (!existsSync7(source)) return { items: null, reason: `raw provider output is missing: ${source}` };
|
|
5936
5950
|
if (await sha256File(source) !== output.sha256) {
|
|
5937
5951
|
return { items: null, reason: "raw provider output was modified after download" };
|
|
5938
5952
|
}
|
|
@@ -5951,7 +5965,7 @@ async function inspectQualityProfile(spec, lock) {
|
|
|
5951
5965
|
}
|
|
5952
5966
|
const sourcePaths = source.items.map((item) => item.path);
|
|
5953
5967
|
const expectedOutputs = spec.generator === "frames" ? source.items.map((item, index) => qualityFrameOutputPath(output, item.role, index)) : [output];
|
|
5954
|
-
if (!
|
|
5968
|
+
if (!existsSync7(record)) {
|
|
5955
5969
|
return {
|
|
5956
5970
|
key,
|
|
5957
5971
|
state: "needs-refinement",
|
|
@@ -6132,7 +6146,7 @@ async function requireApprovedQualitySources(specs, lock, assetIds, outputRoles)
|
|
|
6132
6146
|
}
|
|
6133
6147
|
|
|
6134
6148
|
// src/pipeline/revision.ts
|
|
6135
|
-
import { existsSync as
|
|
6149
|
+
import { existsSync as existsSync8 } from "fs";
|
|
6136
6150
|
import path11 from "path";
|
|
6137
6151
|
async function inspectRevisionReadiness(spec, lock) {
|
|
6138
6152
|
if (!spec.revision) return null;
|
|
@@ -6148,14 +6162,14 @@ async function inspectRevision(spec, lock, seen) {
|
|
|
6148
6162
|
reason: `revision source ${spec.styleId}/${revision.sourceAssetId} is not ready: ${dependency.reason}`
|
|
6149
6163
|
};
|
|
6150
6164
|
}
|
|
6151
|
-
if (!revision.sourceSha256 || !
|
|
6165
|
+
if (!revision.sourceSha256 || !existsSync8(revision.sourceFile)) {
|
|
6152
6166
|
return { ready: false, reason: `revision source is missing: ${revision.sourceFile}` };
|
|
6153
6167
|
}
|
|
6154
6168
|
if (await sha256File(revision.sourceFile) !== revision.sourceSha256) {
|
|
6155
6169
|
return { ready: false, reason: "revision source changed after the manifest was resolved" };
|
|
6156
6170
|
}
|
|
6157
6171
|
if (revision.maskFile) {
|
|
6158
|
-
if (!revision.maskSha256 || !
|
|
6172
|
+
if (!revision.maskSha256 || !existsSync8(revision.maskFile)) {
|
|
6159
6173
|
return { ready: false, reason: `revision mask is missing: ${revision.maskFile}` };
|
|
6160
6174
|
}
|
|
6161
6175
|
if (await sha256File(revision.maskFile) !== revision.maskSha256) {
|
|
@@ -6186,7 +6200,7 @@ async function inspectParent(spec, lock, seen) {
|
|
|
6186
6200
|
}
|
|
6187
6201
|
if (spec.source) {
|
|
6188
6202
|
const source = path11.resolve(spec.root, spec.source);
|
|
6189
|
-
return
|
|
6203
|
+
return existsSync8(source) ? { ready: true, reason: "declared source is present" } : { ready: false, reason: `declared source is missing: ${spec.source}` };
|
|
6190
6204
|
}
|
|
6191
6205
|
const entry = lock.entries[key];
|
|
6192
6206
|
if (!entry) return { ready: false, reason: "parent is not in the lockfile" };
|
|
@@ -6200,7 +6214,7 @@ async function inspectParent(spec, lock, seen) {
|
|
|
6200
6214
|
for (let index = 0; index < entry.outputs.length; index++) {
|
|
6201
6215
|
const output = entry.outputs[index];
|
|
6202
6216
|
const file = currentEntryOutputPath(entry, spec, index);
|
|
6203
|
-
if (!
|
|
6217
|
+
if (!existsSync8(file)) return { ready: false, reason: `parent output is missing: ${file}` };
|
|
6204
6218
|
if (await sha256File(file) !== output.sha256) {
|
|
6205
6219
|
return { ready: false, reason: `parent output was modified: ${file}` };
|
|
6206
6220
|
}
|
|
@@ -6254,8 +6268,8 @@ async function buildPlan(specs, lock, opts = {}) {
|
|
|
6254
6268
|
} else if (opts.force) {
|
|
6255
6269
|
state = "missing";
|
|
6256
6270
|
reason = "--force";
|
|
6257
|
-
} else if (!entry && spec.source) {
|
|
6258
|
-
if (
|
|
6271
|
+
} else if (!entry && spec.source && spec.generator !== "frames") {
|
|
6272
|
+
if (existsSync9(path12.resolve(spec.root, spec.source))) {
|
|
6259
6273
|
state = "ok";
|
|
6260
6274
|
reason = `placed from ${spec.source}; not generated`;
|
|
6261
6275
|
} else {
|
|
@@ -6263,7 +6277,7 @@ async function buildPlan(specs, lock, opts = {}) {
|
|
|
6263
6277
|
reason = `declared source is missing: ${spec.source}`;
|
|
6264
6278
|
}
|
|
6265
6279
|
} else if (!entry) {
|
|
6266
|
-
if (
|
|
6280
|
+
if (existsSync9(spec.outFile)) {
|
|
6267
6281
|
state = "untracked";
|
|
6268
6282
|
reason = "file on disk, no provenance recorded";
|
|
6269
6283
|
} else {
|
|
@@ -6295,9 +6309,9 @@ async function buildPlan(specs, lock, opts = {}) {
|
|
|
6295
6309
|
} else if (entry.status !== "downloaded") {
|
|
6296
6310
|
state = "in-flight";
|
|
6297
6311
|
reason = `awaiting ${entry.status}`;
|
|
6298
|
-
} else if (entry.outputs.length === 0 || entry.outputs.some((_output, index) => !
|
|
6312
|
+
} else if (entry.outputs.length === 0 || entry.outputs.some((_output, index) => !existsSync9(currentEntryOutputPath(entry, spec, index)))) {
|
|
6299
6313
|
state = "orphaned";
|
|
6300
|
-
reason = entry.outputs.length === 0 ? "no recorded output files" : `missing on disk: ${entry.outputs.filter((_output, index) => !
|
|
6314
|
+
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(", ")}`;
|
|
6301
6315
|
} else if (await anyOutputModified(entry, spec)) {
|
|
6302
6316
|
state = "orphaned";
|
|
6303
6317
|
reason = "output modified since download";
|
|
@@ -6363,7 +6377,7 @@ function summarize(plan) {
|
|
|
6363
6377
|
|
|
6364
6378
|
// src/pipeline/quality-regression.ts
|
|
6365
6379
|
import path13 from "path";
|
|
6366
|
-
import { existsSync as
|
|
6380
|
+
import { existsSync as existsSync10 } from "fs";
|
|
6367
6381
|
import { readFile as readFile8 } from "fs/promises";
|
|
6368
6382
|
import { z as z4 } from "zod";
|
|
6369
6383
|
var SHA256_RE2 = /^[0-9a-f]{64}$/;
|
|
@@ -6606,7 +6620,7 @@ async function snapshotQualityBaseline(inputs, baselinePath, options = {}) {
|
|
|
6606
6620
|
cases
|
|
6607
6621
|
});
|
|
6608
6622
|
const data = Buffer.from(JSON.stringify(baseline, null, 2) + "\n");
|
|
6609
|
-
if (
|
|
6623
|
+
if (existsSync10(absolute) && !options.force) {
|
|
6610
6624
|
const current = await readFile8(absolute);
|
|
6611
6625
|
if (!current.equals(data)) {
|
|
6612
6626
|
throw new Error(`Quality baseline already exists with different content: ${absolute}. Pass --force to replace it.`);
|
|
@@ -6743,13 +6757,13 @@ async function checkQualityBaseline(baselinePath) {
|
|
|
6743
6757
|
}
|
|
6744
6758
|
|
|
6745
6759
|
// src/pipeline/cache-health.ts
|
|
6746
|
-
import { existsSync as
|
|
6760
|
+
import { existsSync as existsSync12 } from "fs";
|
|
6747
6761
|
import { readFile as readFile10, readdir as readdir2, rm as rm5 } from "fs/promises";
|
|
6748
6762
|
import path14 from "path";
|
|
6749
6763
|
|
|
6750
6764
|
// src/cache.ts
|
|
6751
6765
|
import { mkdir as mkdir3, readFile as readFile9, rename as rename3, rm as rm4, writeFile as writeFile3 } from "fs/promises";
|
|
6752
|
-
import { existsSync as
|
|
6766
|
+
import { existsSync as existsSync11 } from "fs";
|
|
6753
6767
|
import pathModule from "path";
|
|
6754
6768
|
import { z as z5 } from "zod";
|
|
6755
6769
|
var HashCacheSchema = z5.object({
|
|
@@ -6764,10 +6778,10 @@ function isSha256Hash(value) {
|
|
|
6764
6778
|
function parseCache(value) {
|
|
6765
6779
|
return HashCacheSchema.parse(value);
|
|
6766
6780
|
}
|
|
6767
|
-
async function loadCache(
|
|
6768
|
-
if (!
|
|
6781
|
+
async function loadCache(path29) {
|
|
6782
|
+
if (!existsSync11(path29)) return { version: 1, hashes: {} };
|
|
6769
6783
|
try {
|
|
6770
|
-
const parsed = HashCacheSchema.safeParse(JSON.parse(await readFile9(
|
|
6784
|
+
const parsed = HashCacheSchema.safeParse(JSON.parse(await readFile9(path29, "utf8")));
|
|
6771
6785
|
if (!parsed.success) return { version: 1, hashes: {} };
|
|
6772
6786
|
return {
|
|
6773
6787
|
version: 1,
|
|
@@ -6779,18 +6793,18 @@ async function loadCache(path27) {
|
|
|
6779
6793
|
return { version: 1, hashes: {} };
|
|
6780
6794
|
}
|
|
6781
6795
|
}
|
|
6782
|
-
async function saveCache(
|
|
6796
|
+
async function saveCache(path29, cache) {
|
|
6783
6797
|
const sorted = {};
|
|
6784
6798
|
for (const key of Object.keys(cache.hashes).sort()) {
|
|
6785
6799
|
const hash = cache.hashes[key];
|
|
6786
6800
|
if (!isSha256Hash(hash)) throw new Error(`Refusing to cache invalid SHA-256 for ${key}`);
|
|
6787
6801
|
sorted[key] = hash;
|
|
6788
6802
|
}
|
|
6789
|
-
await mkdir3(pathModule.dirname(pathModule.resolve(
|
|
6790
|
-
const tmp = `${
|
|
6803
|
+
await mkdir3(pathModule.dirname(pathModule.resolve(path29)), { recursive: true });
|
|
6804
|
+
const tmp = `${path29}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
6791
6805
|
try {
|
|
6792
6806
|
await writeFile3(tmp, JSON.stringify({ version: 1, hashes: sorted }, null, 2) + "\n");
|
|
6793
|
-
await rename3(tmp,
|
|
6807
|
+
await rename3(tmp, path29);
|
|
6794
6808
|
} finally {
|
|
6795
6809
|
await rm4(tmp, { force: true });
|
|
6796
6810
|
}
|
|
@@ -6812,7 +6826,7 @@ function cachePathFor(lockPath) {
|
|
|
6812
6826
|
|
|
6813
6827
|
// src/pipeline/cache-health.ts
|
|
6814
6828
|
async function inspectCaches(lock, lockPath, options = {}) {
|
|
6815
|
-
if (options.prune && !
|
|
6829
|
+
if (options.prune && !existsSync12(lockPath)) {
|
|
6816
6830
|
throw new Error(`Refusing to prune without an existing lockfile at ${path14.resolve(lockPath)}`);
|
|
6817
6831
|
}
|
|
6818
6832
|
const contentDir = path14.resolve(path14.dirname(lockPath), ".pixelkiln", "cache");
|
|
@@ -6857,7 +6871,7 @@ async function inspectCaches(lock, lockPath, options = {}) {
|
|
|
6857
6871
|
async function inspectContentCache(contentDir, referenced) {
|
|
6858
6872
|
const report = {
|
|
6859
6873
|
path: contentDir,
|
|
6860
|
-
exists:
|
|
6874
|
+
exists: existsSync12(contentDir),
|
|
6861
6875
|
files: 0,
|
|
6862
6876
|
bytes: 0,
|
|
6863
6877
|
valid: 0,
|
|
@@ -6921,7 +6935,7 @@ async function inspectContentCache(contentDir, referenced) {
|
|
|
6921
6935
|
async function inspectRemoteHashCache(remotePath) {
|
|
6922
6936
|
const report = {
|
|
6923
6937
|
path: remotePath,
|
|
6924
|
-
exists:
|
|
6938
|
+
exists: existsSync12(remotePath),
|
|
6925
6939
|
entries: 0,
|
|
6926
6940
|
valid: 0,
|
|
6927
6941
|
invalidIds: [],
|
|
@@ -7049,12 +7063,15 @@ function packStyle(lock, styleId, manifestDir, options = {}) {
|
|
|
7049
7063
|
for (const [key, entry] of entries) {
|
|
7050
7064
|
const id = key.slice(prefix.length);
|
|
7051
7065
|
locked.add(id);
|
|
7052
|
-
|
|
7066
|
+
let outputs = resolveEntryOutputs(entry, id, manifestDir);
|
|
7053
7067
|
const source = sources[id];
|
|
7054
7068
|
if (source && outputs.length <= 1) {
|
|
7055
7069
|
inputs.push({ id, path: path15.resolve(manifestDir, source) });
|
|
7056
7070
|
continue;
|
|
7057
7071
|
}
|
|
7072
|
+
if (source && sourceIsStem(source, entry, manifestDir)) {
|
|
7073
|
+
outputs = outputs.map((output) => ({ ...output, absolutePath: sourceOutputPath(source, entry, output.index, manifestDir) }));
|
|
7074
|
+
}
|
|
7058
7075
|
let selected = outputs;
|
|
7059
7076
|
if (options.outputRoles?.length) {
|
|
7060
7077
|
selected = outputs.filter(
|
|
@@ -7176,11 +7193,21 @@ function mountStyle(lock, styleId, manifestDir, mount, cells, sources = {}, outp
|
|
|
7176
7193
|
for (const id of ids) {
|
|
7177
7194
|
const cell = cells[id];
|
|
7178
7195
|
const source = sources[id];
|
|
7179
|
-
|
|
7196
|
+
const entry = lock.entries[`${styleId}/${id}`];
|
|
7197
|
+
if (source && !(entry && sourceIsStem(source, entry, manifestDir))) {
|
|
7180
7198
|
placements.push({ id, path: path15.resolve(manifestDir, source), cell });
|
|
7181
7199
|
continue;
|
|
7182
7200
|
}
|
|
7183
|
-
|
|
7201
|
+
if (source && entry) {
|
|
7202
|
+
const selection2 = selectEntryOutput(entry, outputRoles[id]);
|
|
7203
|
+
if (!selection2.ok) {
|
|
7204
|
+
skipped.push({ id, reason: selection2.reason });
|
|
7205
|
+
continue;
|
|
7206
|
+
}
|
|
7207
|
+
const index = entry.outputs.findIndex((output) => output.path === selection2.output.path);
|
|
7208
|
+
placements.push({ id, path: sourceOutputPath(source, entry, Math.max(index, 0), manifestDir), cell });
|
|
7209
|
+
continue;
|
|
7210
|
+
}
|
|
7184
7211
|
if (!entry) {
|
|
7185
7212
|
skipped.push({
|
|
7186
7213
|
id,
|
|
@@ -7247,8 +7274,11 @@ function normalizeTileRules(raw) {
|
|
|
7247
7274
|
};
|
|
7248
7275
|
}
|
|
7249
7276
|
function exportTileset(entry, spec, options) {
|
|
7250
|
-
|
|
7277
|
+
let outputs = resolveSpecEntryOutputs(entry, spec);
|
|
7251
7278
|
if (!outputs.length) throw new Error(`${spec.styleId}/${spec.assetId} has no downloaded outputs`);
|
|
7279
|
+
if (spec.source && sourceIsStem(spec.source, entry, options.manifestDir)) {
|
|
7280
|
+
outputs = outputs.map((output) => ({ ...output, absolutePath: sourceOutputPath(spec.source, entry, output.index, options.manifestDir) }));
|
|
7281
|
+
}
|
|
7252
7282
|
const packed = packSprites(
|
|
7253
7283
|
outputs.map((output) => ({
|
|
7254
7284
|
id: output.role ?? fallbackOutputRole(output.index),
|
|
@@ -7740,7 +7770,7 @@ async function poll(provider, lock, lockPath, opts = {}) {
|
|
|
7740
7770
|
}
|
|
7741
7771
|
|
|
7742
7772
|
// src/pipeline/fetch.ts
|
|
7743
|
-
import { existsSync as
|
|
7773
|
+
import { existsSync as existsSync13 } from "fs";
|
|
7744
7774
|
import { mkdir as mkdir4, readFile as readFile11, rename as rename4, rm as rm6, writeFile as writeFile4 } from "fs/promises";
|
|
7745
7775
|
import path16 from "path";
|
|
7746
7776
|
async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
@@ -7757,7 +7787,7 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
7757
7787
|
if (opts.refresh) return e.outputs.length > 0 && Boolean(e.sourceUrls?.length || e.sourceUrl);
|
|
7758
7788
|
if (!opts.repair) return false;
|
|
7759
7789
|
const spec = specByKey.get(key);
|
|
7760
|
-
return e.outputs.length === 0 || !spec || e.outputs.some((output) => !
|
|
7790
|
+
return e.outputs.length === 0 || !spec || e.outputs.some((output) => !existsSync13(resolveOutputPath(output.path, spec.root)));
|
|
7761
7791
|
});
|
|
7762
7792
|
const concurrency = Math.min(Math.max(1, opts.concurrency ?? 8), Math.max(1, pending.length));
|
|
7763
7793
|
let cursor = 0;
|
|
@@ -7805,7 +7835,7 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
7805
7835
|
continue;
|
|
7806
7836
|
}
|
|
7807
7837
|
}
|
|
7808
|
-
if (
|
|
7838
|
+
if (existsSync13(target)) {
|
|
7809
7839
|
const currentHash = await sha256File(target);
|
|
7810
7840
|
if (recorded && currentHash === recorded.sha256) {
|
|
7811
7841
|
if (!fresh) {
|
|
@@ -7916,7 +7946,7 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
7916
7946
|
}
|
|
7917
7947
|
async function readCachedMedia(cacheDir, hash, mediaType) {
|
|
7918
7948
|
const file = path16.join(cacheDir, cacheFileName(hash, mediaType));
|
|
7919
|
-
if (!
|
|
7949
|
+
if (!existsSync13(file)) return null;
|
|
7920
7950
|
try {
|
|
7921
7951
|
const buf = await readFile11(file);
|
|
7922
7952
|
if (sha256(buf) !== hash) return null;
|
|
@@ -7937,7 +7967,7 @@ async function cacheMedia(cacheDir, buf, mediaType, knownHash) {
|
|
|
7937
7967
|
try {
|
|
7938
7968
|
await rename4(tmp, file);
|
|
7939
7969
|
} catch (err) {
|
|
7940
|
-
if (!
|
|
7970
|
+
if (!existsSync13(file)) throw err;
|
|
7941
7971
|
} finally {
|
|
7942
7972
|
await rm6(tmp, { force: true });
|
|
7943
7973
|
}
|
|
@@ -7970,7 +8000,7 @@ async function pushTags(provider, specs, lock, opts = {}) {
|
|
|
7970
8000
|
}
|
|
7971
8001
|
|
|
7972
8002
|
// src/pipeline/doctor.ts
|
|
7973
|
-
import { constants, existsSync as
|
|
8003
|
+
import { constants, existsSync as existsSync14 } from "fs";
|
|
7974
8004
|
import { access, stat as stat2 } from "fs/promises";
|
|
7975
8005
|
import path17 from "path";
|
|
7976
8006
|
async function doctor(loaded, specs, lock, lockPath, opts = {}) {
|
|
@@ -8042,7 +8072,7 @@ async function doctor(loaded, specs, lock, lockPath, opts = {}) {
|
|
|
8042
8072
|
cacheDir,
|
|
8043
8073
|
cacheFileName(output.sha256, output.mediaType ?? MediaType.PNG)
|
|
8044
8074
|
);
|
|
8045
|
-
if (
|
|
8075
|
+
if (existsSync14(cached) && await sha256File(cached) === output.sha256) {
|
|
8046
8076
|
hasCache = true;
|
|
8047
8077
|
break;
|
|
8048
8078
|
}
|
|
@@ -8145,7 +8175,7 @@ async function nearestExistingDirectory(start) {
|
|
|
8145
8175
|
}
|
|
8146
8176
|
|
|
8147
8177
|
// src/pipeline/adopt.ts
|
|
8148
|
-
import { existsSync as
|
|
8178
|
+
import { existsSync as existsSync15 } from "fs";
|
|
8149
8179
|
import { readFile as readFile12, writeFile as writeFile5 } from "fs/promises";
|
|
8150
8180
|
import path18 from "path";
|
|
8151
8181
|
async function adopt(provider, specs, lock, lockPath, opts = {}) {
|
|
@@ -8158,7 +8188,7 @@ async function adopt(provider, specs, lock, lockPath, opts = {}) {
|
|
|
8158
8188
|
const localByHash = /* @__PURE__ */ new Map();
|
|
8159
8189
|
const unmatchedLocal = [];
|
|
8160
8190
|
for (const spec of specs) {
|
|
8161
|
-
if (!
|
|
8191
|
+
if (!existsSync15(spec.outFile)) {
|
|
8162
8192
|
unmatchedLocal.push(`${spec.styleId}/${spec.assetId} (no file at ${spec.outFile})`);
|
|
8163
8193
|
continue;
|
|
8164
8194
|
}
|
|
@@ -8280,12 +8310,12 @@ async function tagAdopted(provider, specs, lock, opts = {}) {
|
|
|
8280
8310
|
}
|
|
8281
8311
|
|
|
8282
8312
|
// src/pick/server.ts
|
|
8283
|
-
import { existsSync as
|
|
8313
|
+
import { existsSync as existsSync17 } from "fs";
|
|
8284
8314
|
import { readFile as readFile15 } from "fs/promises";
|
|
8285
8315
|
|
|
8286
8316
|
// src/pipeline/init.ts
|
|
8287
8317
|
import { readdir as readdir3, readFile as readFile13, writeFile as writeFile6 } from "fs/promises";
|
|
8288
|
-
import { existsSync as
|
|
8318
|
+
import { existsSync as existsSync16 } from "fs";
|
|
8289
8319
|
import path19 from "path";
|
|
8290
8320
|
function pngSize(buf) {
|
|
8291
8321
|
if (buf.length < 24) return null;
|
|
@@ -8858,7 +8888,7 @@ async function currentArt(entry, spec) {
|
|
|
8858
8888
|
const previous = primaryOutput({ ...entry, outputs: entry.supersededOutputs });
|
|
8859
8889
|
if (!previous) return null;
|
|
8860
8890
|
const file = resolveOutputPath(previous.path, spec.root);
|
|
8861
|
-
if (!
|
|
8891
|
+
if (!existsSync17(file)) return null;
|
|
8862
8892
|
const contentType = previous.mediaType ?? mediaTypeFromExtension(file);
|
|
8863
8893
|
if (!contentType) return null;
|
|
8864
8894
|
let width = entry.width;
|
|
@@ -9029,13 +9059,13 @@ async function runPicker(provider, lock, lockPath, opts = {}) {
|
|
|
9029
9059
|
|
|
9030
9060
|
// src/gallery/snapshot.ts
|
|
9031
9061
|
import { createHash as createHash4 } from "crypto";
|
|
9032
|
-
import { existsSync as
|
|
9062
|
+
import { existsSync as existsSync20 } from "fs";
|
|
9033
9063
|
import { readFile as readFile19, stat as stat3 } from "fs/promises";
|
|
9034
9064
|
import path24 from "path";
|
|
9035
9065
|
|
|
9036
9066
|
// src/workspace.ts
|
|
9037
9067
|
import { mkdir as mkdir5, readFile as readFile16, rename as rename5, rm as rm7, writeFile as writeFile7 } from "fs/promises";
|
|
9038
|
-
import { existsSync as
|
|
9068
|
+
import { existsSync as existsSync18 } from "fs";
|
|
9039
9069
|
import path20 from "path";
|
|
9040
9070
|
import { z as z6 } from "zod";
|
|
9041
9071
|
var WorkspaceProjectSchema = z6.object({
|
|
@@ -9061,7 +9091,7 @@ ${parsed.error.issues.slice(0, 5).map((i) => ` ${i.path.join(".")}: ${i.message
|
|
|
9061
9091
|
);
|
|
9062
9092
|
}
|
|
9063
9093
|
async function loadWorkspace(workspacePath) {
|
|
9064
|
-
if (!
|
|
9094
|
+
if (!existsSync18(workspacePath)) return { version: 1, projects: [] };
|
|
9065
9095
|
let raw;
|
|
9066
9096
|
try {
|
|
9067
9097
|
raw = JSON.parse(await readFile16(workspacePath, "utf8"));
|
|
@@ -9113,14 +9143,14 @@ function validateWorkspace(ws, dir) {
|
|
|
9113
9143
|
message: `project "${project.id}" stores an absolute path \u2014 the catalog will not resolve correctly if this tree is cloned or moved elsewhere`
|
|
9114
9144
|
});
|
|
9115
9145
|
}
|
|
9116
|
-
if (!
|
|
9146
|
+
if (!existsSync18(manifestPath)) {
|
|
9117
9147
|
diagnostics.push({
|
|
9118
9148
|
id: "missing-manifest",
|
|
9119
9149
|
level: "error",
|
|
9120
9150
|
message: `project "${project.id}" manifest not found: ${manifestPath}`
|
|
9121
9151
|
});
|
|
9122
9152
|
}
|
|
9123
|
-
if (!
|
|
9153
|
+
if (!existsSync18(lockPath)) {
|
|
9124
9154
|
diagnostics.push({
|
|
9125
9155
|
id: "missing-lock",
|
|
9126
9156
|
level: "error",
|
|
@@ -9421,7 +9451,7 @@ async function applyManifestEdit(manifestPath, edit) {
|
|
|
9421
9451
|
|
|
9422
9452
|
// src/pipeline/hand-edit.ts
|
|
9423
9453
|
import { spawn as spawn2 } from "child_process";
|
|
9424
|
-
import { existsSync as
|
|
9454
|
+
import { existsSync as existsSync19 } from "fs";
|
|
9425
9455
|
import { copyFile, mkdir as mkdir6, readFile as readFile18, rename as rename7, rm as rm9, writeFile as writeFile9 } from "fs/promises";
|
|
9426
9456
|
import path22 from "path";
|
|
9427
9457
|
import { z as z8 } from "zod";
|
|
@@ -9434,36 +9464,63 @@ function handEditPath(loaded, spec) {
|
|
|
9434
9464
|
const file = inside ? relative : path22.basename(spec.outFile);
|
|
9435
9465
|
return path22.join(outDir, HAND_EDIT_DIR, file.replace(/\.gif$/i, ".png"));
|
|
9436
9466
|
}
|
|
9437
|
-
function
|
|
9467
|
+
function handEditBases(spec, lock) {
|
|
9438
9468
|
const entry = lock.entries[lockKey(spec.styleId, spec.assetId)];
|
|
9439
9469
|
if (entry) {
|
|
9440
|
-
if (entry.outputs.length !== 1) return null;
|
|
9441
|
-
const
|
|
9442
|
-
|
|
9443
|
-
|
|
9444
|
-
|
|
9470
|
+
if (entry.outputs.length !== 1 && !isMemberSetEntry(entry)) return null;
|
|
9471
|
+
const members = entry.outputs.map((output, index) => ({
|
|
9472
|
+
role: entry.outputs.length === 1 ? null : output.role ?? null,
|
|
9473
|
+
path: currentEntryOutputPath(entry, spec, index),
|
|
9474
|
+
mediaType: output.mediaType
|
|
9475
|
+
}));
|
|
9476
|
+
if (members.some((member) => member.mediaType && member.mediaType !== "image/png")) return null;
|
|
9477
|
+
if (entry.outputs.length === 1 && !primaryOutput(entry)) return null;
|
|
9478
|
+
if (!members.every((member) => existsSync19(member.path))) return null;
|
|
9479
|
+
return members.map(({ role, path: file }) => ({ role, path: file }));
|
|
9480
|
+
}
|
|
9481
|
+
return existsSync19(spec.outFile) ? [{ role: null, path: spec.outFile }] : null;
|
|
9482
|
+
}
|
|
9483
|
+
function handEditBase(spec, lock) {
|
|
9484
|
+
const bases = handEditBases(spec, lock);
|
|
9485
|
+
return bases && bases.length === 1 ? bases[0].path : null;
|
|
9486
|
+
}
|
|
9487
|
+
function handEditMembers(loaded, spec, lock, editPath) {
|
|
9488
|
+
const entry = lock.entries[lockKey(spec.styleId, spec.assetId)];
|
|
9489
|
+
const bases = handEditBases(spec, lock);
|
|
9490
|
+
if (entry && isMemberSetEntry(entry)) {
|
|
9491
|
+
return entry.outputs.map((output, index) => ({
|
|
9492
|
+
role: output.role ?? null,
|
|
9493
|
+
path: memberPath(editPath, output.role, index, entry.outputs.length, output.mediaType),
|
|
9494
|
+
base: bases?.[index]?.path ?? null
|
|
9495
|
+
}));
|
|
9445
9496
|
}
|
|
9446
|
-
return
|
|
9497
|
+
return [{ role: null, path: editPath, base: bases?.[0]?.path ?? null }];
|
|
9447
9498
|
}
|
|
9448
9499
|
async function startHandEdit(loaded, lock, spec, opts = {}) {
|
|
9449
9500
|
const entry = lock.entries[lockKey(spec.styleId, spec.assetId)];
|
|
9450
|
-
if (entry && entry.outputs.length > 1) {
|
|
9501
|
+
if (entry && entry.outputs.length > 1 && !isMemberSetEntry(entry)) {
|
|
9451
9502
|
throw new ManifestEditError(
|
|
9452
|
-
`${spec.styleId}/${spec.assetId}
|
|
9503
|
+
`${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`
|
|
9504
|
+
);
|
|
9505
|
+
}
|
|
9506
|
+
if (entry && spec.source && isMemberSetEntry(entry) && !sourceIsStem(spec.source, entry, loaded.root)) {
|
|
9507
|
+
throw new ManifestEditError(
|
|
9508
|
+
`${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`
|
|
9453
9509
|
);
|
|
9454
9510
|
}
|
|
9455
9511
|
const existing = spec.source ? path22.resolve(loaded.root, spec.source) : null;
|
|
9456
9512
|
const editPath = existing ?? handEditPath(loaded, spec);
|
|
9513
|
+
const members = handEditMembers(loaded, spec, lock, editPath);
|
|
9457
9514
|
let created = false;
|
|
9458
|
-
|
|
9459
|
-
|
|
9460
|
-
if (!base) {
|
|
9515
|
+
for (const member of members) {
|
|
9516
|
+
if (existsSync19(member.path)) continue;
|
|
9517
|
+
if (!member.base) {
|
|
9461
9518
|
throw new ManifestEditError(
|
|
9462
9519
|
`${spec.styleId}/${spec.assetId} has no generated PNG on disk to start from; generate or restore it first`
|
|
9463
9520
|
);
|
|
9464
9521
|
}
|
|
9465
|
-
await mkdir6(path22.dirname(
|
|
9466
|
-
await copyFile(base,
|
|
9522
|
+
await mkdir6(path22.dirname(member.path), { recursive: true });
|
|
9523
|
+
await copyFile(member.base, member.path);
|
|
9467
9524
|
created = true;
|
|
9468
9525
|
}
|
|
9469
9526
|
const source = portableOutputPath(editPath, loaded.root);
|
|
@@ -9480,7 +9537,7 @@ async function startHandEdit(loaded, lock, spec, opts = {}) {
|
|
|
9480
9537
|
declared = result.changed;
|
|
9481
9538
|
manifestSha256 = result.sha256;
|
|
9482
9539
|
}
|
|
9483
|
-
return { editPath, source, created, declared, manifestSha256 };
|
|
9540
|
+
return { editPath, members, source, created, declared, manifestSha256 };
|
|
9484
9541
|
}
|
|
9485
9542
|
async function detachHandEdit(loaded, spec, opts = {}) {
|
|
9486
9543
|
if (!spec.source) {
|
|
@@ -9494,19 +9551,44 @@ async function detachHandEdit(loaded, spec, opts = {}) {
|
|
|
9494
9551
|
});
|
|
9495
9552
|
return { manifestSha256: result.sha256, changed: result.changed };
|
|
9496
9553
|
}
|
|
9497
|
-
var
|
|
9498
|
-
|
|
9499
|
-
|
|
9554
|
+
var CompanionOutputSchema = z8.object({
|
|
9555
|
+
/** Output role for a set member; null for a single image. */
|
|
9556
|
+
role: z8.string().nullable(),
|
|
9557
|
+
/** sha256 of the generated file this member started from; null when it started from untracked art. */
|
|
9500
9558
|
basedOn: z8.string().regex(/^[0-9a-f]{64}$/).nullable(),
|
|
9501
|
-
/** sha256 of the
|
|
9502
|
-
sha256: z8.string().regex(/^[0-9a-f]{64}$/)
|
|
9559
|
+
/** sha256 of the member as saved, so a later change by another tool is visible. */
|
|
9560
|
+
sha256: z8.string().regex(/^[0-9a-f]{64}$/)
|
|
9561
|
+
}).strict();
|
|
9562
|
+
var CompanionV2Schema = z8.object({
|
|
9563
|
+
version: z8.literal(2),
|
|
9503
9564
|
/** Editor identity the bridge reported, e.g. `pixelorama@v1.2.2-stable`. */
|
|
9504
9565
|
editor: z8.string().min(1),
|
|
9505
9566
|
protocol: z8.number().int().positive(),
|
|
9506
9567
|
savedAt: z8.string().datetime(),
|
|
9507
9568
|
/** Basename of the layered project file beside the edit, when one was kept. */
|
|
9569
|
+
project: z8.string().min(1).nullable(),
|
|
9570
|
+
/** One entry per file, in output order. */
|
|
9571
|
+
outputs: z8.array(CompanionOutputSchema).min(1)
|
|
9572
|
+
}).strict();
|
|
9573
|
+
var CompanionV1Schema = z8.object({
|
|
9574
|
+
version: z8.literal(1),
|
|
9575
|
+
basedOn: z8.string().regex(/^[0-9a-f]{64}$/).nullable(),
|
|
9576
|
+
sha256: z8.string().regex(/^[0-9a-f]{64}$/),
|
|
9577
|
+
editor: z8.string().min(1),
|
|
9578
|
+
protocol: z8.number().int().positive(),
|
|
9579
|
+
savedAt: z8.string().datetime(),
|
|
9508
9580
|
project: z8.string().min(1).nullable()
|
|
9509
9581
|
}).strict();
|
|
9582
|
+
var HandEditCompanionSchema = z8.union([CompanionV2Schema, CompanionV1Schema]).transform(
|
|
9583
|
+
(value) => value.version === 2 ? value : {
|
|
9584
|
+
version: 2,
|
|
9585
|
+
editor: value.editor,
|
|
9586
|
+
protocol: value.protocol,
|
|
9587
|
+
savedAt: value.savedAt,
|
|
9588
|
+
project: value.project,
|
|
9589
|
+
outputs: [{ role: null, basedOn: value.basedOn, sha256: value.sha256 }]
|
|
9590
|
+
}
|
|
9591
|
+
);
|
|
9510
9592
|
function handEditCompanionPath(editPath) {
|
|
9511
9593
|
return editPath.replace(/\.png$/i, "") + ".edit.json";
|
|
9512
9594
|
}
|
|
@@ -9528,45 +9610,79 @@ async function readHandEditCompanion(editPath) {
|
|
|
9528
9610
|
}
|
|
9529
9611
|
var MAX_HAND_EDIT_BYTES = 16 * 1024 * 1024;
|
|
9530
9612
|
async function saveHandEdit(loaded, lock, spec, input) {
|
|
9531
|
-
|
|
9532
|
-
|
|
9533
|
-
}
|
|
9613
|
+
const entry = lock.entries[lockKey(spec.styleId, spec.assetId)];
|
|
9614
|
+
const memberSet = entry ? isMemberSetEntry(entry) : false;
|
|
9615
|
+
const incoming = input.frames ?? (input.png ? [{ role: null, png: input.png }] : []);
|
|
9616
|
+
if (!incoming.length) throw new ManifestEditError("the save carried no image");
|
|
9534
9617
|
if (input.project && input.project.length > MAX_HAND_EDIT_BYTES) {
|
|
9535
9618
|
throw new ManifestEditError(`the project file is ${input.project.length} bytes; the limit is ${MAX_HAND_EDIT_BYTES}`);
|
|
9536
9619
|
}
|
|
9537
|
-
|
|
9538
|
-
|
|
9539
|
-
|
|
9540
|
-
|
|
9541
|
-
|
|
9542
|
-
|
|
9620
|
+
if (memberSet) {
|
|
9621
|
+
const expected = entry.outputs.map((output) => output.role ?? null);
|
|
9622
|
+
const got = incoming.map((frame) => frame.role);
|
|
9623
|
+
if (got.length !== expected.length || expected.some((role, index) => got[index] !== role)) {
|
|
9624
|
+
throw new ManifestEditError(
|
|
9625
|
+
`${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`
|
|
9626
|
+
);
|
|
9627
|
+
}
|
|
9628
|
+
} else if (incoming.length !== 1) {
|
|
9629
|
+
throw new ManifestEditError(`${spec.styleId}/${spec.assetId} is one image; the editor returned ${incoming.length} frames \u2014 keep a single frame`);
|
|
9543
9630
|
}
|
|
9544
|
-
const
|
|
9545
|
-
|
|
9546
|
-
|
|
9547
|
-
|
|
9548
|
-
|
|
9549
|
-
|
|
9631
|
+
const decoded = incoming.map((frame, index) => {
|
|
9632
|
+
if (frame.png.length > MAX_HAND_EDIT_BYTES) {
|
|
9633
|
+
throw new ManifestEditError(`the edit is ${frame.png.length} bytes; the limit is ${MAX_HAND_EDIT_BYTES}`);
|
|
9634
|
+
}
|
|
9635
|
+
try {
|
|
9636
|
+
validateMedia(frame.png, MediaType.PNG);
|
|
9637
|
+
return decodePng(frame.png);
|
|
9638
|
+
} catch (error) {
|
|
9639
|
+
const which = memberSet ? `member ${frame.role ?? index}` : "the edit";
|
|
9640
|
+
throw new ManifestEditError(`${which} is not a valid PNG: ${error instanceof Error ? error.message : String(error)}`);
|
|
9641
|
+
}
|
|
9642
|
+
});
|
|
9643
|
+
const bases = handEditBases(spec, lock);
|
|
9644
|
+
const expectedSize = bases?.[0] ? decodePng(await readFile18(bases[0].path)) : { width: spec.width, height: spec.height };
|
|
9645
|
+
for (const [index, image] of decoded.entries()) {
|
|
9646
|
+
if (image.width !== expectedSize.width || image.height !== expectedSize.height) {
|
|
9647
|
+
const which = memberSet ? `member ${incoming[index].role ?? index}` : "the edit";
|
|
9648
|
+
throw new ManifestEditError(
|
|
9649
|
+
`${which} is ${image.width}\xD7${image.height}; ${spec.styleId}/${spec.assetId} is ${expectedSize.width}\xD7${expectedSize.height}`
|
|
9650
|
+
);
|
|
9651
|
+
}
|
|
9550
9652
|
}
|
|
9551
9653
|
const started = await startHandEdit(loaded, lock, spec, { expectedSha256: input.expectedSha256 });
|
|
9552
|
-
const entry = lock.entries[lockKey(spec.styleId, spec.assetId)];
|
|
9553
|
-
const basedOn = entry ? primaryOutput(entry)?.sha256 ?? null : null;
|
|
9554
|
-
const digest2 = sha256(input.png);
|
|
9555
9654
|
const projectPath = input.project ? handEditProjectPath(started.editPath) : null;
|
|
9556
9655
|
const companion = {
|
|
9557
|
-
version:
|
|
9558
|
-
basedOn,
|
|
9559
|
-
sha256: digest2,
|
|
9656
|
+
version: 2,
|
|
9560
9657
|
editor: input.editor,
|
|
9561
9658
|
protocol: input.protocol,
|
|
9562
9659
|
savedAt: (input.now ?? /* @__PURE__ */ new Date()).toISOString(),
|
|
9563
|
-
project: projectPath ? path22.basename(projectPath) : null
|
|
9660
|
+
project: projectPath ? path22.basename(projectPath) : null,
|
|
9661
|
+
outputs: incoming.map((frame, index) => ({
|
|
9662
|
+
role: frame.role,
|
|
9663
|
+
basedOn: basedOnHash(entry, index),
|
|
9664
|
+
sha256: sha256(frame.png)
|
|
9665
|
+
}))
|
|
9564
9666
|
};
|
|
9565
|
-
|
|
9667
|
+
for (const [index, member] of started.members.entries()) {
|
|
9668
|
+
await replaceFile(member.path, incoming[index].png);
|
|
9669
|
+
}
|
|
9566
9670
|
if (projectPath && input.project) await replaceFile(projectPath, input.project);
|
|
9567
9671
|
const companionPath = handEditCompanionPath(started.editPath);
|
|
9568
9672
|
await replaceFile(companionPath, JSON.stringify(companion, null, 2) + "\n");
|
|
9569
|
-
return {
|
|
9673
|
+
return {
|
|
9674
|
+
...started,
|
|
9675
|
+
sha256: companion.outputs[0].sha256,
|
|
9676
|
+
basedOn: companion.outputs[0].basedOn,
|
|
9677
|
+
companion,
|
|
9678
|
+
companionPath,
|
|
9679
|
+
projectPath
|
|
9680
|
+
};
|
|
9681
|
+
}
|
|
9682
|
+
function basedOnHash(entry, index) {
|
|
9683
|
+
if (!entry) return null;
|
|
9684
|
+
if (entry.outputs.length === 1) return primaryOutput(entry)?.sha256 ?? null;
|
|
9685
|
+
return entry.outputs[index]?.sha256 ?? null;
|
|
9570
9686
|
}
|
|
9571
9687
|
async function replaceFile(file, bytes) {
|
|
9572
9688
|
const tmp = `${file}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
@@ -9613,7 +9729,10 @@ var HandEditRequestSchema = z9.discriminatedUnion("action", [
|
|
|
9613
9729
|
assetId: z9.string().min(1),
|
|
9614
9730
|
styleId: z9.string().min(1),
|
|
9615
9731
|
expectedSha256: z9.string().regex(/^[0-9a-f]{64}$/),
|
|
9616
|
-
|
|
9732
|
+
/** The flattened image; for a frame set, `frames` instead. */
|
|
9733
|
+
png: Base64.min(1).optional(),
|
|
9734
|
+
/** Every frame of a set, tagged with the role it was opened under (null for one added in the editor). */
|
|
9735
|
+
frames: z9.array(z9.object({ role: z9.string().min(1).nullable(), png: Base64.min(1) }).strict()).min(1).max(256).optional(),
|
|
9617
9736
|
/** The editor's layered project file, kept beside the edit. */
|
|
9618
9737
|
pxo: Base64.optional(),
|
|
9619
9738
|
/** Editor identity from the bridge's ready message. */
|
|
@@ -9636,18 +9755,23 @@ function createGalleryEditHandler(opts) {
|
|
|
9636
9755
|
const started = await startHandEdit(ctx.loaded, ctx.lock, spec, { expectedSha256: request.expectedSha256 });
|
|
9637
9756
|
log(` hand edit ${started.created ? "created" : "found"}: ${started.source}${started.declared ? " (declared in the manifest)" : ""}`);
|
|
9638
9757
|
if (request.open) {
|
|
9639
|
-
const command = (opts.openEditor ?? openInEditor)(started.
|
|
9758
|
+
const command = (opts.openEditor ?? openInEditor)(started.members[0].path);
|
|
9640
9759
|
log(` opened with: ${command}`);
|
|
9641
9760
|
}
|
|
9642
9761
|
} else if (request.action === "save-edit") {
|
|
9762
|
+
if (request.png !== void 0 === (request.frames !== void 0)) {
|
|
9763
|
+
throw new ManifestEditError("invalid edit: send png for one image or frames for a set, not both");
|
|
9764
|
+
}
|
|
9643
9765
|
const saved = await saveHandEdit(ctx.loaded, ctx.lock, spec, {
|
|
9644
|
-
png: Buffer.from(request.png, "base64"),
|
|
9766
|
+
...request.png !== void 0 ? { png: Buffer.from(request.png, "base64") } : {},
|
|
9767
|
+
...request.frames ? { frames: request.frames.map((frame) => ({ role: frame.role, png: Buffer.from(frame.png, "base64") })) } : {},
|
|
9645
9768
|
...request.pxo ? { project: Buffer.from(request.pxo, "base64") } : {},
|
|
9646
9769
|
editor: request.editor,
|
|
9647
9770
|
protocol: request.protocol,
|
|
9648
9771
|
expectedSha256: request.expectedSha256
|
|
9649
9772
|
});
|
|
9650
|
-
|
|
9773
|
+
const what = saved.members.length > 1 ? `${saved.source} (${saved.members.length} frames)` : saved.source;
|
|
9774
|
+
log(` hand edit saved from ${request.editor}: ${what}${saved.declared ? " (declared in the manifest)" : ""}${saved.projectPath ? " + project file" : ""}`);
|
|
9651
9775
|
} else {
|
|
9652
9776
|
const result2 = await detachHandEdit(ctx.loaded, spec, { expectedSha256: request.expectedSha256 });
|
|
9653
9777
|
if (result2.changed) log(` hand edit detached: ${request.styleId}/${request.assetId} (file kept)`);
|
|
@@ -9695,6 +9819,18 @@ function matchesFilter(filter, styleId, assetId) {
|
|
|
9695
9819
|
if (filter?.assets?.length && !filter.assets.includes(assetId)) return false;
|
|
9696
9820
|
return true;
|
|
9697
9821
|
}
|
|
9822
|
+
var MAX_COMPARED_BYTES = 8 * 1024 * 1024;
|
|
9823
|
+
async function samePixels(a, b) {
|
|
9824
|
+
try {
|
|
9825
|
+
const [left, right] = await Promise.all([readFile19(a), readFile19(b)]);
|
|
9826
|
+
if (left.length > MAX_COMPARED_BYTES || right.length > MAX_COMPARED_BYTES) return false;
|
|
9827
|
+
const x = decodePng(left);
|
|
9828
|
+
const y = decodePng(right);
|
|
9829
|
+
return x.width === y.width && x.height === y.height && x.pixels.equals(y.pixels);
|
|
9830
|
+
} catch {
|
|
9831
|
+
return false;
|
|
9832
|
+
}
|
|
9833
|
+
}
|
|
9698
9834
|
function metadataFps(entry) {
|
|
9699
9835
|
if (!entry) return null;
|
|
9700
9836
|
const namespace = entry.providerMetadata?.[entry.provider];
|
|
@@ -9742,7 +9878,7 @@ async function describeQuality(media, root, inspection) {
|
|
|
9742
9878
|
outputPaths.map((file) => describeOutput(media, root, path24.resolve(root, file)))
|
|
9743
9879
|
);
|
|
9744
9880
|
const recordPath = path24.resolve(root, inspection.record);
|
|
9745
|
-
const recordExists =
|
|
9881
|
+
const recordExists = existsSync20(recordPath);
|
|
9746
9882
|
let options = null;
|
|
9747
9883
|
let check = null;
|
|
9748
9884
|
if (recordExists) {
|
|
@@ -9812,6 +9948,8 @@ async function buildGallerySnapshot(opts) {
|
|
|
9812
9948
|
const asset = loaded.manifest.assets[spec.assetId] ?? null;
|
|
9813
9949
|
let outputs;
|
|
9814
9950
|
let edit = null;
|
|
9951
|
+
let edits = [];
|
|
9952
|
+
let editChanged = [];
|
|
9815
9953
|
let editStatus = null;
|
|
9816
9954
|
let editMeta = null;
|
|
9817
9955
|
if (entry && entry.outputs.length) {
|
|
@@ -9822,13 +9960,19 @@ async function buildGallerySnapshot(opts) {
|
|
|
9822
9960
|
);
|
|
9823
9961
|
if (spec.source) {
|
|
9824
9962
|
const editPath = path24.resolve(root, spec.source);
|
|
9825
|
-
const
|
|
9826
|
-
|
|
9827
|
-
|
|
9963
|
+
const memberCount = sourceIsStem(spec.source, entry, root) ? entry.outputs.length : 1;
|
|
9964
|
+
const editShas = await Promise.all(
|
|
9965
|
+
Array.from({ length: memberCount }, async (_, index) => {
|
|
9966
|
+
const file = sourceOutputPath(spec.source, entry, index, root);
|
|
9967
|
+
return { file, sha256: existsSync20(file) ? await sha256File(file) : null };
|
|
9968
|
+
})
|
|
9969
|
+
);
|
|
9970
|
+
edits = await Promise.all(editShas.map(({ file, sha256: editSha }) => describeOutput(media, root, file, { sha256: editSha })));
|
|
9971
|
+
edit = edits[0] ?? null;
|
|
9828
9972
|
const companion = await readHandEditCompanion(editPath);
|
|
9829
9973
|
if (companion) {
|
|
9830
9974
|
const projectPath = handEditProjectPath(editPath);
|
|
9831
|
-
const project = companion.project &&
|
|
9975
|
+
const project = companion.project && existsSync20(projectPath) ? await fileInfo(projectPath) : null;
|
|
9832
9976
|
let projectUrl = null;
|
|
9833
9977
|
if (project) {
|
|
9834
9978
|
const id = galleryMediaId(projectPath);
|
|
@@ -9838,18 +9982,34 @@ async function buildGallerySnapshot(opts) {
|
|
|
9838
9982
|
editMeta = {
|
|
9839
9983
|
editor: companion.editor,
|
|
9840
9984
|
savedAt: companion.savedAt,
|
|
9841
|
-
basedOn: companion.basedOn,
|
|
9842
|
-
changedSince:
|
|
9985
|
+
basedOn: companion.outputs[0]?.basedOn ?? null,
|
|
9986
|
+
changedSince: editShas.some(({ sha256: editSha }, index) => {
|
|
9987
|
+
const recorded = companion.outputs[index]?.sha256;
|
|
9988
|
+
return editSha !== null && recorded !== void 0 && editSha !== recorded;
|
|
9989
|
+
}),
|
|
9843
9990
|
project: project ? portableOutputPath(projectPath, root) : null,
|
|
9844
9991
|
projectUrl
|
|
9845
9992
|
};
|
|
9846
9993
|
}
|
|
9847
|
-
const regenerated =
|
|
9848
|
-
|
|
9994
|
+
const regenerated = edits.some((member, index) => {
|
|
9995
|
+
const generated = outputs[index];
|
|
9996
|
+
if (companion) {
|
|
9997
|
+
const recorded = companion.outputs[index];
|
|
9998
|
+
return recorded !== void 0 && generated.sha256 !== null && recorded.basedOn !== generated.sha256;
|
|
9999
|
+
}
|
|
10000
|
+
return Boolean(generated.modifiedAt && member.modifiedAt && generated.modifiedAt > member.modifiedAt && generated.exists);
|
|
10001
|
+
});
|
|
10002
|
+
editChanged = await Promise.all(edits.map(async (member, index) => {
|
|
10003
|
+
const generated = outputs[index];
|
|
10004
|
+
if (!member.exists || !generated.exists) return true;
|
|
10005
|
+
if (member.sha256 === generated.sha256) return false;
|
|
10006
|
+
return !await samePixels(member.absolutePath, generated.absolutePath);
|
|
10007
|
+
}));
|
|
10008
|
+
editStatus = edits.some((member) => !member.exists) ? "missing" : editChanged.every((changed) => !changed) ? "same" : regenerated ? "regenerated-since" : "edited";
|
|
9849
10009
|
}
|
|
9850
10010
|
} else if (spec.source) {
|
|
9851
10011
|
outputs = [await describeOutput(media, root, path24.resolve(root, spec.source))];
|
|
9852
|
-
} else if (!entry &&
|
|
10012
|
+
} else if (!entry && existsSync20(spec.outFile)) {
|
|
9853
10013
|
outputs = [await describeOutput(media, root, spec.outFile)];
|
|
9854
10014
|
} else {
|
|
9855
10015
|
outputs = [];
|
|
@@ -9894,6 +10054,8 @@ async function buildGallerySnapshot(opts) {
|
|
|
9894
10054
|
asset,
|
|
9895
10055
|
source: spec.source ?? null,
|
|
9896
10056
|
edit,
|
|
10057
|
+
edits,
|
|
10058
|
+
editChanged,
|
|
9897
10059
|
editStatus,
|
|
9898
10060
|
editMeta,
|
|
9899
10061
|
upstreamUrl: entry?.provider === "pixellab" ? pixelLabObjectUrl(entry.generator, entry.objectId) : null,
|
|
@@ -9951,6 +10113,8 @@ async function buildGallerySnapshot(opts) {
|
|
|
9951
10113
|
asset: null,
|
|
9952
10114
|
source: null,
|
|
9953
10115
|
edit: null,
|
|
10116
|
+
edits: [],
|
|
10117
|
+
editChanged: [],
|
|
9954
10118
|
editStatus: null,
|
|
9955
10119
|
editMeta: null,
|
|
9956
10120
|
upstreamUrl: entry.provider === "pixellab" ? pixelLabObjectUrl(entry.generator, entry.objectId) : null,
|
|
@@ -10879,10 +11043,15 @@ window.addEventListener('message', (e) => {
|
|
|
10879
11043
|
// declares the file the same way pixelkiln edit does.
|
|
10880
11044
|
|
|
10881
11045
|
let SHEET = null;
|
|
10882
|
-
|
|
10883
|
-
|
|
10884
|
-
const
|
|
10885
|
-
|
|
11046
|
+
// What the editor gets: the edit files when they are all there, else the
|
|
11047
|
+
// generated outputs (one PNG, or every frame of a set).
|
|
11048
|
+
const editorSources = (item) => {
|
|
11049
|
+
if (item.edits.length && item.edits.every((e) => e.exists)) return { files: item.edits, fromEdit: true };
|
|
11050
|
+
if (editableOutputs(item)) return { files: item.outputs, fromEdit: false };
|
|
11051
|
+
return null;
|
|
11052
|
+
};
|
|
11053
|
+
const editorSource = (item) => { const s = editorSources(item); return s ? s.files[0] : null; };
|
|
11054
|
+
const canEditInBrowser = (item) => EDITOR && EDITABLE && item.declared && item.asset && projectOf(item)?.manifestSha256 && editorSources(item);
|
|
10886
11055
|
const toBase64 = (buf) => new Promise((resolve, reject) => {
|
|
10887
11056
|
const r = new FileReader();
|
|
10888
11057
|
r.onload = () => resolve(String(r.result).slice(String(r.result).indexOf(',') + 1));
|
|
@@ -10911,7 +11080,7 @@ function openEditorSheet(item) {
|
|
|
10911
11080
|
const bar = el('div', 'rbar');
|
|
10912
11081
|
const close = el('button', null, 'Close'); close.type = 'button';
|
|
10913
11082
|
close.onclick = () => closeEditorSheet();
|
|
10914
|
-
const title = el('b', null, (item.project ? item.project + ':' : '') + item.styleId + '/' + item.assetId + ' \xB7 ' + item.width + '\xD7' + item.height);
|
|
11083
|
+
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) : ''));
|
|
10915
11084
|
const status = el('span', 'st');
|
|
10916
11085
|
const acts = el('div', 'acts');
|
|
10917
11086
|
const msg = el('span', 'msg');
|
|
@@ -10932,23 +11101,34 @@ function openEditorSheet(item) {
|
|
|
10932
11101
|
stage.append(frame, loading);
|
|
10933
11102
|
panel.append(bar, stage);
|
|
10934
11103
|
host.append(scrim, panel);
|
|
10935
|
-
SHEET = { item, frame, loading, status, msg, save, saveClose, dirty: false, opened: false, ready: null, pending: null, requests: 0, source: src, sentProject: false };
|
|
11104
|
+
SHEET = { item, frame, loading, status, msg, save, saveClose, dirty: false, opened: false, ready: null, pending: null, requests: 0, source: src, sentProject: false, roles: null };
|
|
10936
11105
|
sheetStatus('cool', 'loading');
|
|
10937
11106
|
sheetButtons();
|
|
10938
11107
|
close.focus({ preventScroll: true });
|
|
10939
11108
|
}
|
|
10940
|
-
// The edit
|
|
10941
|
-
// project beside
|
|
10942
|
-
// falling back to the flattened
|
|
11109
|
+
// The edit files are what the editor gets; when a browser save kept the
|
|
11110
|
+
// layered project beside them, that goes along too and the editor restores
|
|
11111
|
+
// the layers, falling back to the flattened PNGs if the file cannot be read.
|
|
11112
|
+
// A frame set is sent as frames, one per member, and comes back the same way.
|
|
10943
11113
|
async function sendOpen() {
|
|
10944
|
-
const { item
|
|
11114
|
+
const { item } = SHEET;
|
|
11115
|
+
const sources = editorSources(item);
|
|
10945
11116
|
const style = snap.styles.find((s) => s.id === item.styleId && s.project === item.project);
|
|
10946
|
-
const projectUrl =
|
|
10947
|
-
|
|
11117
|
+
const projectUrl = sources.fromEdit && item.editMeta && item.editMeta.projectUrl && SHEET.ready.version >= 2 ? item.editMeta.projectUrl : null;
|
|
11118
|
+
const set = sources.files.length > 1;
|
|
11119
|
+
if (set && SHEET.ready.version < 3) {
|
|
11120
|
+
SHEET.msg.textContent = 'This editor build cannot open sets; reinstall it with pixelkiln tools install editor.';
|
|
11121
|
+
sheetStatus('bad', 'unsupported');
|
|
11122
|
+
return;
|
|
11123
|
+
}
|
|
11124
|
+
const pngs = [];
|
|
11125
|
+
let pxo = null;
|
|
10948
11126
|
try {
|
|
10949
|
-
const
|
|
10950
|
-
|
|
10951
|
-
|
|
11127
|
+
for (const file of sources.files) {
|
|
11128
|
+
const res = await fetch(file.url, { cache: 'no-store' });
|
|
11129
|
+
if (!res.ok) throw new Error(await res.text());
|
|
11130
|
+
pngs.push(await res.arrayBuffer());
|
|
11131
|
+
}
|
|
10952
11132
|
if (projectUrl) {
|
|
10953
11133
|
const p = await fetch(projectUrl, { cache: 'no-store' });
|
|
10954
11134
|
if (p.ok) pxo = await p.arrayBuffer();
|
|
@@ -10960,12 +11140,22 @@ async function sendOpen() {
|
|
|
10960
11140
|
if (!SHEET) return;
|
|
10961
11141
|
const request = 'open-' + (++SHEET.requests);
|
|
10962
11142
|
SHEET.sentProject = !!pxo;
|
|
11143
|
+
SHEET.roles = set ? item.outputs.map((o) => o.role || null) : null;
|
|
10963
11144
|
const message = {
|
|
10964
11145
|
type: 'pixelkiln:open', request,
|
|
10965
11146
|
asset: { key: item.styleId + '/' + item.assetId, id: item.id, name: item.assetId, width: item.width, height: item.height },
|
|
10966
|
-
png, palette: style ? style.palette : [],
|
|
11147
|
+
png: pngs[0], palette: style ? style.palette : [],
|
|
10967
11148
|
};
|
|
10968
|
-
const transfer = [
|
|
11149
|
+
const transfer = [pngs[0]];
|
|
11150
|
+
if (set) {
|
|
11151
|
+
message.frames = pngs.map((png, i) => ({ role: SHEET.roles[i], png }));
|
|
11152
|
+
// A tile set is not an animation; a slow rate keeps its timeline still-ish.
|
|
11153
|
+
message.fps = isFrameSet(item) ? (item.fps || 12) : 1;
|
|
11154
|
+
for (const png of pngs.slice(1)) transfer.push(png);
|
|
11155
|
+
// The first frame is both png (older editors) and frames[0]; copy it so both transfer.
|
|
11156
|
+
message.png = pngs[0].slice(0);
|
|
11157
|
+
transfer.push(message.png);
|
|
11158
|
+
}
|
|
10969
11159
|
if (pxo) { message.pxo = pxo; transfer.push(pxo); }
|
|
10970
11160
|
SHEET.frame.contentWindow.postMessage(message, location.origin, transfer);
|
|
10971
11161
|
sheetStatus('cool', 'opening');
|
|
@@ -10993,8 +11183,9 @@ async function onEditorMessage(m) {
|
|
|
10993
11183
|
case 'pixelkiln:opened':
|
|
10994
11184
|
SHEET.opened = true;
|
|
10995
11185
|
SHEET.loading.remove();
|
|
10996
|
-
if (m.source === 'pxo') sheetStatus('ok', 'editing the edit file
|
|
10997
|
-
else if (SHEET.sentProject) sheetStatus('warn', 'editing the flattened edit file;
|
|
11186
|
+
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');
|
|
11187
|
+
else if (SHEET.sentProject) sheetStatus('warn', 'editing the flattened edit file' + (m.frames > 1 ? 's' : '') + '; the layer file could not be opened');
|
|
11188
|
+
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)));
|
|
10998
11189
|
else sheetStatus('ok', SHEET.item.edit && SHEET.item.edit.exists ? 'editing the edit file' : 'editing a copy of the generated art');
|
|
10999
11190
|
sheetButtons();
|
|
11000
11191
|
break;
|
|
@@ -11007,14 +11198,21 @@ async function onEditorMessage(m) {
|
|
|
11007
11198
|
const pending = SHEET.pending;
|
|
11008
11199
|
if (!pending || pending.request !== m.request) return;
|
|
11009
11200
|
try {
|
|
11010
|
-
const body = {
|
|
11201
|
+
const body = { editor: SHEET.ready.editor, protocol: SHEET.ready.version };
|
|
11202
|
+
if (SHEET.roles) {
|
|
11203
|
+
if (!Array.isArray(m.frames)) throw new Error('the editor returned no frames');
|
|
11204
|
+
body.frames = [];
|
|
11205
|
+
for (const f of m.frames) body.frames.push({ role: f.role === undefined ? null : f.role, png: await toBase64(f.png) });
|
|
11206
|
+
} else {
|
|
11207
|
+
body.png = await toBase64(m.png);
|
|
11208
|
+
}
|
|
11011
11209
|
if (m.pxo && m.pxo.byteLength) body.pxo = await toBase64(m.pxo);
|
|
11012
11210
|
await postHandEdit(SHEET.item, 'save-edit', body);
|
|
11013
11211
|
const saved = snap.items.find((i) => i.id === SHEET.item.id);
|
|
11014
11212
|
if (saved) SHEET.item = saved;
|
|
11015
11213
|
SHEET.dirty = false;
|
|
11016
11214
|
SHEET.pending = null;
|
|
11017
|
-
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.' };
|
|
11215
|
+
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.' };
|
|
11018
11216
|
render();
|
|
11019
11217
|
if (pending.close) { closeEditorSheet(true); return; }
|
|
11020
11218
|
sheetStatus('ok', 'saved');
|
|
@@ -11162,7 +11360,7 @@ function clearFilters() {
|
|
|
11162
11360
|
function thumb(item) {
|
|
11163
11361
|
const cell = el('div', 'cell');
|
|
11164
11362
|
// A hand edit is what ships, so it is what the card shows.
|
|
11165
|
-
const shown = item.edit && item.
|
|
11363
|
+
const shown = item.edit && item.editStatus === 'edited' && item.edits.every((e) => e.url) ? item.edits : item.outputs.filter((o) => o.url);
|
|
11166
11364
|
if (!shown.length) {
|
|
11167
11365
|
cell.classList.add('none');
|
|
11168
11366
|
cell.append(el('span', null, item.state === 'missing' ? 'not generated yet'
|
|
@@ -11576,7 +11774,7 @@ function openCompare() {
|
|
|
11576
11774
|
// ---- hand edits -------------------------------------------------------------
|
|
11577
11775
|
|
|
11578
11776
|
const EDIT_STATUS_TEXT = {
|
|
11579
|
-
same: '
|
|
11777
|
+
same: 'The generated art\u2019s pixels, not changed yet. Open it in your editor and save.',
|
|
11580
11778
|
edited: 'Differs from the generated art. mount and pack place this file; the generated file stays as the record.',
|
|
11581
11779
|
'regenerated-since': 'The generated art changed after this edit was saved \u2014 the edit is based on an older generation.',
|
|
11582
11780
|
missing: 'The declared file is not on disk.',
|
|
@@ -11587,12 +11785,18 @@ async function postHandEdit(item, action, extra) {
|
|
|
11587
11785
|
if (item.project) body.project = item.project;
|
|
11588
11786
|
snap = await postEdit(body);
|
|
11589
11787
|
}
|
|
11788
|
+
// A single PNG or a set of them \u2014 frames, tiles \u2014 is hand-editable, one file
|
|
11789
|
+
// per member; a set with a GIF in it is not.
|
|
11790
|
+
const editableOutputs = (item) => item.outputs.length && item.outputs.every((o) => o.exists && o.mediaType === 'image/png');
|
|
11791
|
+
// What the members of a set are called: frames for an animation, tiles for a tile set.
|
|
11792
|
+
const memberNoun = (item, n) => isFrameSet(item) ? (n === 1 ? 'frame' : 'frames')
|
|
11793
|
+
: item.outputs.every((o) => o.role && /^tile/i.test(o.role)) ? (n === 1 ? 'tile' : 'tiles') : (n === 1 ? 'member' : 'members');
|
|
11590
11794
|
function handEditSection(item) {
|
|
11591
|
-
const canStart = EDITABLE && item.declared && item.asset && projectOf(item)?.manifestSha256 &&
|
|
11592
|
-
item.outputs.length === 1 && item.outputs[0].exists && item.outputs[0].mediaType === 'image/png';
|
|
11795
|
+
const canStart = EDITABLE && item.declared && item.asset && projectOf(item)?.manifestSha256 && editableOutputs(item);
|
|
11593
11796
|
if (!item.edit && !canStart) return null;
|
|
11797
|
+
const set = item.edits.length > 1;
|
|
11594
11798
|
const s = el('section', 'meta');
|
|
11595
|
-
s.append(el('h3', null, 'Hand edit'));
|
|
11799
|
+
s.append(el('h3', null, set ? 'Hand edit (' + item.edits.length + ' ' + memberNoun(item, item.edits.length) + ')' : 'Hand edit'));
|
|
11596
11800
|
if (item.edit) {
|
|
11597
11801
|
const pair = el('div', 'pair');
|
|
11598
11802
|
const shared = displayScale(item.width, item.height, 200, 160);
|
|
@@ -11606,24 +11810,36 @@ function handEditSection(item) {
|
|
|
11606
11810
|
f.append(el('figcaption', null, label));
|
|
11607
11811
|
return f;
|
|
11608
11812
|
};
|
|
11609
|
-
|
|
11813
|
+
// For a set, the member the preview is on; the strip there steps through them.
|
|
11814
|
+
const at = set ? Math.min(ui.member, item.edits.length - 1) : 0;
|
|
11815
|
+
const which = set ? ' \xB7 ' + (item.outputs[at].role || memberNoun(item, 1) + ' ' + (at + 1)) : '';
|
|
11816
|
+
pair.append(fig(item.outputs[at], 'generated' + which), fig(item.edits[at], 'edit' + which));
|
|
11610
11817
|
s.append(pair);
|
|
11611
11818
|
const dl = el('dl');
|
|
11612
11819
|
const tone = item.editStatus === 'edited' ? 'ok' : item.editStatus === 'same' ? 'dim' : 'warn';
|
|
11613
11820
|
const st = el('span', 'state-' + tone); st.append(el('i', 'dot ' + tone), document.createTextNode(' ' + item.editStatus));
|
|
11614
11821
|
row(dl, 'status', st);
|
|
11615
11822
|
row(dl, 'why', EDIT_STATUS_TEXT[item.editStatus] || '');
|
|
11616
|
-
|
|
11617
|
-
|
|
11618
|
-
|
|
11823
|
+
if (set) {
|
|
11824
|
+
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') });
|
|
11825
|
+
const differing = item.editChanged.filter(Boolean).length;
|
|
11826
|
+
row(dl, 'changed', differing + ' of ' + item.edits.length + ' ' + memberNoun(item, item.edits.length) + ' differ from the generated art' +
|
|
11827
|
+
(differing ? ': ' + item.edits.filter((e, i) => item.editChanged[i]).map((e) => item.outputs[item.edits.indexOf(e)].role || e.path.split('/').pop()).join(', ') : ''));
|
|
11828
|
+
} else {
|
|
11829
|
+
row(dl, 'file', item.edit.path, { mono: true, copy: item.edit.absolutePath });
|
|
11830
|
+
if (item.edit.sha256) row(dl, 'sha256', item.edit.sha256.slice(0, 16) + '\u2026', { mono: true, copy: item.edit.sha256 });
|
|
11831
|
+
}
|
|
11832
|
+
row(dl, 'saved', fmtWhen(item.edits.reduce((latest, e) => e.modifiedAt && (!latest || e.modifiedAt > latest) ? e.modifiedAt : latest, null)));
|
|
11619
11833
|
if (item.editMeta) {
|
|
11620
|
-
row(dl, 'editor', item.editMeta.editor + ', ' + fmtWhen(item.editMeta.savedAt) + (item.editMeta.changedSince ? ' \u2014
|
|
11621
|
-
if (item.editMeta.basedOn) row(dl, 'based on', item.editMeta.basedOn.slice(0, 16) + '\u2026', { mono: true, copy: item.editMeta.basedOn });
|
|
11834
|
+
row(dl, 'editor', item.editMeta.editor + ', ' + fmtWhen(item.editMeta.savedAt) + (item.editMeta.changedSince ? ' \u2014 a file changed since' : ''));
|
|
11835
|
+
if (item.editMeta.basedOn && !set) row(dl, 'based on', item.editMeta.basedOn.slice(0, 16) + '\u2026', { mono: true, copy: item.editMeta.basedOn });
|
|
11622
11836
|
if (item.editMeta.project) row(dl, 'layers', item.editMeta.project, { mono: true });
|
|
11623
11837
|
}
|
|
11624
11838
|
s.append(dl);
|
|
11625
11839
|
} else {
|
|
11626
|
-
s.append(el('div', 'state-dim',
|
|
11840
|
+
s.append(el('div', 'state-dim', item.outputs.length > 1
|
|
11841
|
+
? '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.'
|
|
11842
|
+
: '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.'));
|
|
11627
11843
|
}
|
|
11628
11844
|
if (EDITABLE && item.declared && projectOf(item)?.manifestSha256) {
|
|
11629
11845
|
const acts = el('div', 'hand-actions');
|
|
@@ -11651,10 +11867,11 @@ function handEditSection(item) {
|
|
|
11651
11867
|
}
|
|
11652
11868
|
if (item.edit) {
|
|
11653
11869
|
go('Open in desktop editor', 'start-edit', { open: true }, 'Opened ' + item.edit.path + ' in your editor. Save there, then Refresh.');
|
|
11654
|
-
go('Detach edit', 'detach-edit', {}, 'Detached. The file is still at ' + item.
|
|
11870
|
+
go('Detach edit', 'detach-edit', {}, 'Detached. The file' + (set ? 's are' : ' is') + ' still at ' + item.source + '; the generated art is placed again.');
|
|
11655
11871
|
} else if (canStart) {
|
|
11656
|
-
|
|
11657
|
-
go('
|
|
11872
|
+
const many = item.outputs.length > 1;
|
|
11873
|
+
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.');
|
|
11874
|
+
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.');
|
|
11658
11875
|
}
|
|
11659
11876
|
acts.append(msg);
|
|
11660
11877
|
s.append(acts);
|
|
@@ -12407,11 +12624,305 @@ if (ui.compare.length >= 2 && !ui.open) openCompare();
|
|
|
12407
12624
|
}
|
|
12408
12625
|
var escapeHtml2 = (s) => s.replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
12409
12626
|
|
|
12627
|
+
// tools/pixelorama-bridge/pin.json
|
|
12628
|
+
var pin_default = {
|
|
12629
|
+
pixelorama: "v1.2.2",
|
|
12630
|
+
godot: "4.7.2",
|
|
12631
|
+
extensionsApi: 9,
|
|
12632
|
+
protocol: 3,
|
|
12633
|
+
release: "editor-pixelorama-v1.2.2-pk.14",
|
|
12634
|
+
files: {
|
|
12635
|
+
"index.apple-touch-icon.png": {
|
|
12636
|
+
sha256: "ee5434873e0fed91e9a888c3ea5a8b79beac25999308be7a6eb41bf4ce640d27",
|
|
12637
|
+
bytes: 1036
|
|
12638
|
+
},
|
|
12639
|
+
"index.audio.position.worklet.js": {
|
|
12640
|
+
sha256: "be33985bc7160d6bf9646f259cd86b259cd67b02ccb297ee5c44f8ac84327bc8",
|
|
12641
|
+
bytes: 2973
|
|
12642
|
+
},
|
|
12643
|
+
"index.audio.worklet.js": {
|
|
12644
|
+
sha256: "5b476a9c9ce642c0ee4256436d1bc31d9c38f868aca0f9a8e2a57c18d2dec2a3",
|
|
12645
|
+
bytes: 7298
|
|
12646
|
+
},
|
|
12647
|
+
"index.html": {
|
|
12648
|
+
sha256: "2974146c89f4df9f796e840947c334aaf3e4fa3dd18bcd7e08db2eabfd0f086e",
|
|
12649
|
+
bytes: 5442
|
|
12650
|
+
},
|
|
12651
|
+
"index.icon.png": {
|
|
12652
|
+
sha256: "42a327d23178ddc26cc97e3560ebca6bc7c575d2a1b12a4837f2462049ca5745",
|
|
12653
|
+
bytes: 1070
|
|
12654
|
+
},
|
|
12655
|
+
"index.js": {
|
|
12656
|
+
sha256: "33c94cb3175f3333b82e2a3be5e8e86f77986f0aa2042b1631f6367a4e5bb6ba",
|
|
12657
|
+
bytes: 279815
|
|
12658
|
+
},
|
|
12659
|
+
"index.pck": {
|
|
12660
|
+
sha256: "736bd383c82fdadf31cc894caf08490b99c5df572aa20db683972c35c7a0465b",
|
|
12661
|
+
bytes: 6429628
|
|
12662
|
+
},
|
|
12663
|
+
"index.png": {
|
|
12664
|
+
sha256: "6e3ca3eb4a99e4c07bdd89ebc873b440ec0ffd66ce1955160da9e7ed80d538d7",
|
|
12665
|
+
bytes: 13558
|
|
12666
|
+
},
|
|
12667
|
+
"index.wasm": {
|
|
12668
|
+
sha256: "fc74679e3b97f76878947fcd4fbe1268cbfa6188182a2e33bbc3f5dc9bfa57d0",
|
|
12669
|
+
bytes: 39514754
|
|
12670
|
+
}
|
|
12671
|
+
}
|
|
12672
|
+
};
|
|
12673
|
+
|
|
12674
|
+
// src/editor/pin.ts
|
|
12675
|
+
var EDITOR_PIN = pin_default;
|
|
12676
|
+
var EDITOR_RELEASE_BASE = "https://github.com/gfargo/pixelkiln/releases/download";
|
|
12677
|
+
|
|
12678
|
+
// src/editor/install.ts
|
|
12679
|
+
import { createHash as createHash5 } from "crypto";
|
|
12680
|
+
import { existsSync as existsSync21 } from "fs";
|
|
12681
|
+
import { mkdir as mkdir7, readFile as readFile20, rename as rename8, rm as rm10, stat as stat4, writeFile as writeFile10 } from "fs/promises";
|
|
12682
|
+
import os2 from "os";
|
|
12683
|
+
import path25 from "path";
|
|
12684
|
+
var EditorInstallError = class extends Error {
|
|
12685
|
+
constructor(message6, file) {
|
|
12686
|
+
super(message6);
|
|
12687
|
+
this.file = file;
|
|
12688
|
+
this.name = "EditorInstallError";
|
|
12689
|
+
}
|
|
12690
|
+
file;
|
|
12691
|
+
};
|
|
12692
|
+
function editorToolsRoot() {
|
|
12693
|
+
const override = process.env.PIXELKILN_TOOLS_DIR?.trim();
|
|
12694
|
+
if (override) return path25.resolve(override);
|
|
12695
|
+
if (process.platform === "darwin") return path25.join(os2.homedir(), "Library", "Caches", "pixelkiln", "tools");
|
|
12696
|
+
if (process.platform === "win32") {
|
|
12697
|
+
return path25.join(process.env.LOCALAPPDATA ?? path25.join(os2.homedir(), "AppData", "Local"), "pixelkiln", "tools");
|
|
12698
|
+
}
|
|
12699
|
+
return path25.join(process.env.XDG_CACHE_HOME ?? path25.join(os2.homedir(), ".cache"), "pixelkiln", "tools");
|
|
12700
|
+
}
|
|
12701
|
+
function editorDir(pin = EDITOR_PIN, root = editorToolsRoot()) {
|
|
12702
|
+
return path25.join(root, "pixelorama", pin.release ?? "unpublished");
|
|
12703
|
+
}
|
|
12704
|
+
function editorBaseUrl(pin = EDITOR_PIN) {
|
|
12705
|
+
const override = process.env.PIXELKILN_EDITOR_URL?.trim();
|
|
12706
|
+
if (override) return override.replace(/\/+$/, "");
|
|
12707
|
+
if (!pin.release) return null;
|
|
12708
|
+
return `${EDITOR_RELEASE_BASE}/${encodeURIComponent(pin.release)}`;
|
|
12709
|
+
}
|
|
12710
|
+
async function sha256Of(file) {
|
|
12711
|
+
try {
|
|
12712
|
+
return createHash5("sha256").update(await readFile20(file)).digest("hex");
|
|
12713
|
+
} catch {
|
|
12714
|
+
return null;
|
|
12715
|
+
}
|
|
12716
|
+
}
|
|
12717
|
+
async function editorStatus(opts = {}) {
|
|
12718
|
+
const pin = opts.pin ?? EDITOR_PIN;
|
|
12719
|
+
const dir = opts.dir ?? editorDir(pin);
|
|
12720
|
+
const missing = [];
|
|
12721
|
+
let installedBytes = 0;
|
|
12722
|
+
let totalBytes = 0;
|
|
12723
|
+
for (const [name, expected] of Object.entries(pin.files)) {
|
|
12724
|
+
totalBytes += expected.bytes;
|
|
12725
|
+
const file = path25.join(dir, name);
|
|
12726
|
+
const info = existsSync21(file) ? await stat4(file).catch(() => null) : null;
|
|
12727
|
+
if (info && info.size === expected.bytes && await sha256Of(file) === expected.sha256) {
|
|
12728
|
+
installedBytes += expected.bytes;
|
|
12729
|
+
} else {
|
|
12730
|
+
missing.push(name);
|
|
12731
|
+
}
|
|
12732
|
+
}
|
|
12733
|
+
const pinned = Object.keys(pin.files).length > 0 && pin.release !== null;
|
|
12734
|
+
return {
|
|
12735
|
+
release: pin.release,
|
|
12736
|
+
dir,
|
|
12737
|
+
installed: pinned && missing.length === 0,
|
|
12738
|
+
missing: pinned ? missing : Object.keys(pin.files),
|
|
12739
|
+
totalBytes,
|
|
12740
|
+
installedBytes
|
|
12741
|
+
};
|
|
12742
|
+
}
|
|
12743
|
+
async function installEditor(opts = {}) {
|
|
12744
|
+
const pin = opts.pin ?? EDITOR_PIN;
|
|
12745
|
+
const dir = opts.dir ?? editorDir(pin);
|
|
12746
|
+
const baseUrl = opts.baseUrl ?? editorBaseUrl(pin);
|
|
12747
|
+
const doFetch = opts.fetch ?? fetch;
|
|
12748
|
+
const before = await editorStatus({ pin, dir });
|
|
12749
|
+
if (!pin.release || !Object.keys(pin.files).length) {
|
|
12750
|
+
throw new EditorInstallError(
|
|
12751
|
+
"this PixelKiln version pins no published editor build; upgrade, or set PIXELKILN_EDITOR_URL to a build you trust"
|
|
12752
|
+
);
|
|
12753
|
+
}
|
|
12754
|
+
if (!baseUrl) throw new EditorInstallError("no download location for the editor build");
|
|
12755
|
+
await mkdir7(dir, { recursive: true });
|
|
12756
|
+
const downloaded = [];
|
|
12757
|
+
const skipped = Object.keys(pin.files).filter((name) => !before.missing.includes(name));
|
|
12758
|
+
let fetchedBytes = before.installedBytes;
|
|
12759
|
+
const names = before.missing;
|
|
12760
|
+
for (const [index, name] of names.entries()) {
|
|
12761
|
+
const expected = pin.files[name];
|
|
12762
|
+
const report = (phase, received) => opts.onProgress?.({
|
|
12763
|
+
file: name,
|
|
12764
|
+
index,
|
|
12765
|
+
count: names.length,
|
|
12766
|
+
bytes: expected.bytes,
|
|
12767
|
+
totalBytes: before.totalBytes,
|
|
12768
|
+
fetchedBytes: fetchedBytes + received,
|
|
12769
|
+
phase
|
|
12770
|
+
});
|
|
12771
|
+
report("start", 0);
|
|
12772
|
+
const url = `${baseUrl}/${encodeURIComponent(name)}`;
|
|
12773
|
+
const response = await doFetch(url, { signal: opts.signal, redirect: "follow" });
|
|
12774
|
+
if (!response.ok) throw new EditorInstallError(`${name}: ${response.status} ${response.statusText} from ${url}`, name);
|
|
12775
|
+
const bytes = await readBody(response, expected.bytes, (received) => report("progress", received));
|
|
12776
|
+
if (bytes.length !== expected.bytes) {
|
|
12777
|
+
throw new EditorInstallError(`${name}: expected ${expected.bytes} bytes, got ${bytes.length}`, name);
|
|
12778
|
+
}
|
|
12779
|
+
const digest2 = createHash5("sha256").update(bytes).digest("hex");
|
|
12780
|
+
if (digest2 !== expected.sha256) {
|
|
12781
|
+
throw new EditorInstallError(`${name}: sha256 ${digest2.slice(0, 12)}\u2026 does not match the pinned ${expected.sha256.slice(0, 12)}\u2026`, name);
|
|
12782
|
+
}
|
|
12783
|
+
const final = path25.join(dir, name);
|
|
12784
|
+
const tmp = `${final}.${process.pid}.part`;
|
|
12785
|
+
try {
|
|
12786
|
+
await writeFile10(tmp, bytes);
|
|
12787
|
+
await rename8(tmp, final);
|
|
12788
|
+
} finally {
|
|
12789
|
+
await rm10(tmp, { force: true });
|
|
12790
|
+
}
|
|
12791
|
+
fetchedBytes += bytes.length;
|
|
12792
|
+
downloaded.push(name);
|
|
12793
|
+
report("done", 0);
|
|
12794
|
+
}
|
|
12795
|
+
const after = await editorStatus({ pin, dir });
|
|
12796
|
+
if (!after.installed) {
|
|
12797
|
+
throw new EditorInstallError(`editor install is incomplete: ${after.missing.join(", ")}`);
|
|
12798
|
+
}
|
|
12799
|
+
return { ...after, downloaded, skipped };
|
|
12800
|
+
}
|
|
12801
|
+
async function readBody(response, expectedBytes, onBytes) {
|
|
12802
|
+
if (!response.body) return Buffer.from(await response.arrayBuffer());
|
|
12803
|
+
const chunks = [];
|
|
12804
|
+
let received = 0;
|
|
12805
|
+
const reader = response.body.getReader();
|
|
12806
|
+
for (; ; ) {
|
|
12807
|
+
const { done, value } = await reader.read();
|
|
12808
|
+
if (done) break;
|
|
12809
|
+
chunks.push(value);
|
|
12810
|
+
received += value.length;
|
|
12811
|
+
if (received > expectedBytes) {
|
|
12812
|
+
await reader.cancel().catch(() => {
|
|
12813
|
+
});
|
|
12814
|
+
break;
|
|
12815
|
+
}
|
|
12816
|
+
onBytes(received);
|
|
12817
|
+
}
|
|
12818
|
+
return Buffer.concat(chunks);
|
|
12819
|
+
}
|
|
12820
|
+
var EDITOR_CONTENT_TYPES = {
|
|
12821
|
+
".html": "text/html; charset=utf-8",
|
|
12822
|
+
".js": "text/javascript; charset=utf-8",
|
|
12823
|
+
".wasm": "application/wasm",
|
|
12824
|
+
".pck": "application/octet-stream",
|
|
12825
|
+
".png": "image/png",
|
|
12826
|
+
".json": "application/json; charset=utf-8"
|
|
12827
|
+
};
|
|
12828
|
+
function editorContentType(name) {
|
|
12829
|
+
return EDITOR_CONTENT_TYPES[path25.extname(name).toLowerCase()] ?? null;
|
|
12830
|
+
}
|
|
12831
|
+
|
|
12832
|
+
// src/gallery/editor.ts
|
|
12833
|
+
import { readFile as readFile21 } from "fs/promises";
|
|
12834
|
+
import path26 from "path";
|
|
12835
|
+
function editorRoute(release, name = "index.html") {
|
|
12836
|
+
return `/editor/${encodeURIComponent(release)}/${encodeURIComponent(name)}`;
|
|
12837
|
+
}
|
|
12838
|
+
function createGalleryEditorHandlers(opts = {}) {
|
|
12839
|
+
const pin = opts.pin ?? EDITOR_PIN;
|
|
12840
|
+
const dir = opts.dir ?? editorDir(pin);
|
|
12841
|
+
const log = opts.onProgress ?? (() => {
|
|
12842
|
+
});
|
|
12843
|
+
let verified = null;
|
|
12844
|
+
let checking = null;
|
|
12845
|
+
let installing = null;
|
|
12846
|
+
let running = null;
|
|
12847
|
+
let error = null;
|
|
12848
|
+
const check = () => {
|
|
12849
|
+
if (!checking) {
|
|
12850
|
+
checking = editorStatus({ pin, dir }).then((status) => {
|
|
12851
|
+
verified = status;
|
|
12852
|
+
return status;
|
|
12853
|
+
}).finally(() => {
|
|
12854
|
+
checking = null;
|
|
12855
|
+
});
|
|
12856
|
+
}
|
|
12857
|
+
return checking;
|
|
12858
|
+
};
|
|
12859
|
+
const describe = (status) => ({
|
|
12860
|
+
...status,
|
|
12861
|
+
pixelorama: pin.pixelorama,
|
|
12862
|
+
protocol: pin.protocol,
|
|
12863
|
+
url: status.installed && pin.release ? editorRoute(pin.release) : null,
|
|
12864
|
+
installing,
|
|
12865
|
+
error
|
|
12866
|
+
});
|
|
12867
|
+
return {
|
|
12868
|
+
release: pin.release,
|
|
12869
|
+
status: async () => describe(verified ?? await check()),
|
|
12870
|
+
install: async () => {
|
|
12871
|
+
if (!running) {
|
|
12872
|
+
error = null;
|
|
12873
|
+
installing = { file: "", index: 0, count: 0, bytes: 0, totalBytes: 0, fetchedBytes: 0, phase: "start" };
|
|
12874
|
+
log(` installing the editor (Pixelorama ${pin.pixelorama}) into ${dir}`);
|
|
12875
|
+
running = installEditor({
|
|
12876
|
+
pin,
|
|
12877
|
+
dir,
|
|
12878
|
+
baseUrl: opts.baseUrl ?? editorBaseUrl(pin) ?? void 0,
|
|
12879
|
+
...opts.fetch ? { fetch: opts.fetch } : {},
|
|
12880
|
+
onProgress: (progress) => {
|
|
12881
|
+
installing = progress;
|
|
12882
|
+
}
|
|
12883
|
+
}).then((result) => {
|
|
12884
|
+
verified = result;
|
|
12885
|
+
const mb = (result.totalBytes / 1e6).toFixed(1);
|
|
12886
|
+
log(` editor ready: ${result.downloaded.length} file(s) fetched, ${result.skipped.length} already present (${mb} MB)`);
|
|
12887
|
+
}).catch((err) => {
|
|
12888
|
+
error = err instanceof Error ? err.message : String(err);
|
|
12889
|
+
verified = null;
|
|
12890
|
+
log(` editor install failed: ${error}`);
|
|
12891
|
+
}).finally(() => {
|
|
12892
|
+
installing = null;
|
|
12893
|
+
running = null;
|
|
12894
|
+
});
|
|
12895
|
+
}
|
|
12896
|
+
return describe(verified ?? await check());
|
|
12897
|
+
},
|
|
12898
|
+
file: async (release, name) => {
|
|
12899
|
+
if (!pin.release || release !== pin.release) return null;
|
|
12900
|
+
const expected = pin.files[name];
|
|
12901
|
+
const contentType = editorContentType(name);
|
|
12902
|
+
if (!expected || !contentType) return null;
|
|
12903
|
+
const status = verified ?? await check();
|
|
12904
|
+
if (!status.installed) return null;
|
|
12905
|
+
let bytes;
|
|
12906
|
+
try {
|
|
12907
|
+
bytes = await readFile21(path26.join(dir, name));
|
|
12908
|
+
} catch {
|
|
12909
|
+
verified = null;
|
|
12910
|
+
return null;
|
|
12911
|
+
}
|
|
12912
|
+
if (bytes.length !== expected.bytes) {
|
|
12913
|
+
verified = null;
|
|
12914
|
+
return null;
|
|
12915
|
+
}
|
|
12916
|
+
return { bytes, contentType };
|
|
12917
|
+
}
|
|
12918
|
+
};
|
|
12919
|
+
}
|
|
12920
|
+
|
|
12410
12921
|
// src/gallery/server.ts
|
|
12411
12922
|
import { spawn as spawn3 } from "child_process";
|
|
12412
12923
|
import { randomBytes } from "crypto";
|
|
12413
12924
|
import { createServer as createServer2 } from "http";
|
|
12414
|
-
import { readFile as
|
|
12925
|
+
import { readFile as readFile22 } from "fs/promises";
|
|
12415
12926
|
var MAX_EDIT_BYTES = 48 * 1024 * 1024;
|
|
12416
12927
|
var EDITOR_CSP = [
|
|
12417
12928
|
"default-src 'self'",
|
|
@@ -12570,7 +13081,7 @@ async function serveGallery(opts) {
|
|
|
12570
13081
|
const asset = reviewAssets.get(jobId)?.get(url2.pathname);
|
|
12571
13082
|
if (!asset) return fail(404, "no such review asset");
|
|
12572
13083
|
try {
|
|
12573
|
-
const bytes = await
|
|
13084
|
+
const bytes = await readFile22(asset.path);
|
|
12574
13085
|
res.writeHead(200, {
|
|
12575
13086
|
"Content-Type": asset.contentType,
|
|
12576
13087
|
"Cache-Control": "no-store",
|
|
@@ -12646,7 +13157,7 @@ async function serveGallery(opts) {
|
|
|
12646
13157
|
if (!asset) return fail(404, "no such gallery media");
|
|
12647
13158
|
let bytes;
|
|
12648
13159
|
try {
|
|
12649
|
-
bytes = await
|
|
13160
|
+
bytes = await readFile22(asset.path);
|
|
12650
13161
|
} catch {
|
|
12651
13162
|
return fail(404, "gallery media is no longer on disk; refresh the page");
|
|
12652
13163
|
}
|
|
@@ -12956,19 +13467,19 @@ function createGenerateHandlers(opts) {
|
|
|
12956
13467
|
}
|
|
12957
13468
|
|
|
12958
13469
|
// src/pipeline/salvage.ts
|
|
12959
|
-
import { readFile as
|
|
12960
|
-
import { existsSync as
|
|
12961
|
-
import
|
|
13470
|
+
import { readFile as readFile23 } from "fs/promises";
|
|
13471
|
+
import { existsSync as existsSync22 } from "fs";
|
|
13472
|
+
import path27 from "path";
|
|
12962
13473
|
function providerClaimId(provider, objectId) {
|
|
12963
13474
|
return `${encodeURIComponent(provider)}:${encodeURIComponent(objectId)}`;
|
|
12964
13475
|
}
|
|
12965
13476
|
async function loadClaims(lockPaths, opts = {}) {
|
|
12966
13477
|
const claimed = /* @__PURE__ */ new Set();
|
|
12967
13478
|
for (const p of lockPaths) {
|
|
12968
|
-
if (!
|
|
13479
|
+
if (!existsSync22(p)) throw new Error(`Claim lockfile not found: ${p}`);
|
|
12969
13480
|
let parsed;
|
|
12970
13481
|
try {
|
|
12971
|
-
parsed = parseLock(JSON.parse(await
|
|
13482
|
+
parsed = parseLock(JSON.parse(await readFile23(p, "utf8")));
|
|
12972
13483
|
} catch {
|
|
12973
13484
|
throw new Error(`Claim lockfile is malformed: ${p}`);
|
|
12974
13485
|
}
|
|
@@ -13021,20 +13532,20 @@ function matchOrphanStyle(prompt, manifest) {
|
|
|
13021
13532
|
return matchStyleByPattern(prompt, manifest);
|
|
13022
13533
|
}
|
|
13023
13534
|
async function loadSiblingManifests(ownManifestPath, workspaceManifestPaths, claimPaths) {
|
|
13024
|
-
const own =
|
|
13535
|
+
const own = path27.resolve(ownManifestPath);
|
|
13025
13536
|
const siblingManifestPaths = [
|
|
13026
13537
|
.../* @__PURE__ */ new Set([
|
|
13027
13538
|
...workspaceManifestPaths,
|
|
13028
|
-
...claimPaths.map((c) =>
|
|
13539
|
+
...claimPaths.map((c) => path27.join(path27.dirname(path27.resolve(c)), "pixelkiln.manifest.json"))
|
|
13029
13540
|
])
|
|
13030
13541
|
];
|
|
13031
13542
|
const siblings = [];
|
|
13032
13543
|
for (const siblingManifestPath of siblingManifestPaths) {
|
|
13033
|
-
if (
|
|
13034
|
-
if (!
|
|
13544
|
+
if (path27.resolve(siblingManifestPath) === own) continue;
|
|
13545
|
+
if (!existsSync22(siblingManifestPath)) continue;
|
|
13035
13546
|
try {
|
|
13036
13547
|
const { manifest } = await loadManifest(siblingManifestPath);
|
|
13037
|
-
siblings.push({ label:
|
|
13548
|
+
siblings.push({ label: path27.basename(path27.dirname(siblingManifestPath)), manifest });
|
|
13038
13549
|
} catch {
|
|
13039
13550
|
}
|
|
13040
13551
|
}
|
|
@@ -13150,8 +13661,8 @@ async function applyTags(provider, decisions, existing, opts = {}) {
|
|
|
13150
13661
|
}
|
|
13151
13662
|
|
|
13152
13663
|
// src/pick/salvage-server.ts
|
|
13153
|
-
import { mkdir as
|
|
13154
|
-
import
|
|
13664
|
+
import { mkdir as mkdir8, writeFile as writeFile11, readFile as readFile24 } from "fs/promises";
|
|
13665
|
+
import path28 from "path";
|
|
13155
13666
|
|
|
13156
13667
|
// src/pick/salvage-sheet.ts
|
|
13157
13668
|
var escapeHtml3 = (s) => s.replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
@@ -13316,7 +13827,7 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
13316
13827
|
});
|
|
13317
13828
|
const html = renderSalvageSheet(orphans, {
|
|
13318
13829
|
styleId: ctx.styleId,
|
|
13319
|
-
importDir:
|
|
13830
|
+
importDir: path28.relative(process.cwd(), ctx.importDir) || "."
|
|
13320
13831
|
});
|
|
13321
13832
|
const byId = new Map(orphans.map((o) => [o.id, o]));
|
|
13322
13833
|
const existingTags = new Map(orphans.map((o) => [o.id, o.tags]));
|
|
@@ -13345,10 +13856,10 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
13345
13856
|
if (!buf.subarray(0, 8).equals(PNG_SIGNATURE2)) throw new Error("not a PNG");
|
|
13346
13857
|
decodePng(buf);
|
|
13347
13858
|
const assetId = idFromPrompt(orphan.prompt, taken);
|
|
13348
|
-
const rel =
|
|
13349
|
-
const outFile =
|
|
13350
|
-
await
|
|
13351
|
-
await
|
|
13859
|
+
const rel = path28.join("_salvaged", `${assetId}.png`);
|
|
13860
|
+
const outFile = path28.resolve(ctx.importDir, rel);
|
|
13861
|
+
await mkdir8(path28.dirname(outFile), { recursive: true });
|
|
13862
|
+
await writeFile11(outFile, buf);
|
|
13352
13863
|
ctx.manifest.assets[assetId] = {
|
|
13353
13864
|
prompt: orphan.prompt,
|
|
13354
13865
|
promptByStyle: {},
|
|
@@ -13380,7 +13891,7 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
13380
13891
|
sourceUrl: durableSource,
|
|
13381
13892
|
sourceUrls: durableSource ? [{ url: durableSource }] : [],
|
|
13382
13893
|
outputs: [{
|
|
13383
|
-
path: portableOutputPath(outFile,
|
|
13894
|
+
path: portableOutputPath(outFile, path28.dirname(ctx.manifestPath)),
|
|
13384
13895
|
sha256: sha256(buf)
|
|
13385
13896
|
}],
|
|
13386
13897
|
submittedAt: orphan.createdAt,
|
|
@@ -13404,9 +13915,9 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
13404
13915
|
}
|
|
13405
13916
|
}
|
|
13406
13917
|
await applyTags(provider, decisions, existingTags, { onProgress: log });
|
|
13407
|
-
const raw = JSON.parse(await
|
|
13918
|
+
const raw = JSON.parse(await readFile24(ctx.manifestPath, "utf8"));
|
|
13408
13919
|
for (const id of importedAssetIds) raw.assets[id] = ctx.manifest.assets[id];
|
|
13409
|
-
await
|
|
13920
|
+
await writeFile11(ctx.manifestPath, JSON.stringify(raw, null, 2) + "\n");
|
|
13410
13921
|
await saveLock(ctx.lockPath, ctx.lock);
|
|
13411
13922
|
return result;
|
|
13412
13923
|
}
|
|
@@ -13520,17 +14031,23 @@ export {
|
|
|
13520
14031
|
ComfyUIProvider,
|
|
13521
14032
|
DEFAULT_QUALITY_TOLERANCES,
|
|
13522
14033
|
DEFAULT_RATE_LIMIT,
|
|
14034
|
+
EDITOR_PIN,
|
|
14035
|
+
EDITOR_RELEASE_BASE,
|
|
14036
|
+
EditorInstallError,
|
|
13523
14037
|
FAKE_PNG,
|
|
13524
14038
|
FakeProvider,
|
|
13525
14039
|
GenerateRequestError,
|
|
13526
14040
|
GenerateRequestSchema,
|
|
13527
14041
|
GeneratorSchema,
|
|
13528
14042
|
GridConfidenceSchema,
|
|
14043
|
+
HAND_EDIT_DIR,
|
|
14044
|
+
HandEditCompanionSchema,
|
|
13529
14045
|
ImageQualityMetricsSchema,
|
|
13530
14046
|
InheritedStyleSchema,
|
|
13531
14047
|
LockEntrySchema,
|
|
13532
14048
|
LockSchema,
|
|
13533
14049
|
MAX_DOWNLOAD_BYTES,
|
|
14050
|
+
MAX_HAND_EDIT_BYTES,
|
|
13534
14051
|
MAX_RETRIES,
|
|
13535
14052
|
ManifestDriftError,
|
|
13536
14053
|
ManifestEditError,
|
|
@@ -13577,12 +14094,19 @@ export {
|
|
|
13577
14094
|
countNumberedDescriptions,
|
|
13578
14095
|
createArtifactBundleManifest,
|
|
13579
14096
|
createGalleryEditHandler,
|
|
14097
|
+
createGalleryEditorHandlers,
|
|
13580
14098
|
createGenerateHandlers,
|
|
13581
14099
|
createProvider,
|
|
13582
14100
|
currentEntryOutputPath,
|
|
13583
14101
|
currentOutputPath,
|
|
14102
|
+
detachHandEdit,
|
|
13584
14103
|
detectMediaType,
|
|
13585
14104
|
doctor,
|
|
14105
|
+
editorBaseUrl,
|
|
14106
|
+
editorDir,
|
|
14107
|
+
editorRoute,
|
|
14108
|
+
editorStatus,
|
|
14109
|
+
editorToolsRoot,
|
|
13586
14110
|
evaluateAudit,
|
|
13587
14111
|
expectedOutputPath,
|
|
13588
14112
|
exportTileset,
|
|
@@ -13594,13 +14118,22 @@ export {
|
|
|
13594
14118
|
galleryMediaRoute,
|
|
13595
14119
|
generationCost,
|
|
13596
14120
|
groupOrphansByStyle,
|
|
14121
|
+
handEditBase,
|
|
14122
|
+
handEditBases,
|
|
14123
|
+
handEditCompanionPath,
|
|
14124
|
+
handEditMembers,
|
|
14125
|
+
handEditPath,
|
|
14126
|
+
handEditProjectPath,
|
|
13597
14127
|
hex,
|
|
13598
14128
|
idFromPrompt,
|
|
13599
14129
|
imageMetadata,
|
|
13600
14130
|
inspectCaches,
|
|
13601
14131
|
inspectQualityProfile,
|
|
13602
14132
|
inspectRevisionReadiness,
|
|
14133
|
+
installEditor,
|
|
13603
14134
|
installRecipe,
|
|
14135
|
+
isFrameSetEntry,
|
|
14136
|
+
isMemberSetEntry,
|
|
13604
14137
|
isSensitiveSourceUrl,
|
|
13605
14138
|
listBundledRecipes,
|
|
13606
14139
|
loadClaims,
|
|
@@ -13615,12 +14148,14 @@ export {
|
|
|
13615
14148
|
measureImageQuality,
|
|
13616
14149
|
mediaExtension,
|
|
13617
14150
|
mediaTypeFromExtension,
|
|
14151
|
+
memberPath,
|
|
13618
14152
|
mergePalettes,
|
|
13619
14153
|
mountSprites,
|
|
13620
14154
|
mountStyle,
|
|
13621
14155
|
normalizeLockOutputPaths,
|
|
13622
14156
|
normalizeRefinementPalette,
|
|
13623
14157
|
normalizeTileRules,
|
|
14158
|
+
openInEditor,
|
|
13624
14159
|
outliers,
|
|
13625
14160
|
outputId,
|
|
13626
14161
|
packSprites,
|
|
@@ -13640,6 +14175,7 @@ export {
|
|
|
13640
14175
|
qualityRecordPath,
|
|
13641
14176
|
quantizeToPalette,
|
|
13642
14177
|
readArtifactBundleManifest,
|
|
14178
|
+
readHandEditCompanion,
|
|
13643
14179
|
readQualityBaseline,
|
|
13644
14180
|
recipeDigest,
|
|
13645
14181
|
refineAsset,
|
|
@@ -13672,6 +14208,7 @@ export {
|
|
|
13672
14208
|
retryAfterMs,
|
|
13673
14209
|
runPicker,
|
|
13674
14210
|
runSalvage,
|
|
14211
|
+
saveHandEdit,
|
|
13675
14212
|
saveLock,
|
|
13676
14213
|
saveWorkspace,
|
|
13677
14214
|
scanAssets,
|
|
@@ -13683,8 +14220,11 @@ export {
|
|
|
13683
14220
|
shouldRetry,
|
|
13684
14221
|
slugify,
|
|
13685
14222
|
snapshotQualityBaseline,
|
|
14223
|
+
sourceIsStem,
|
|
14224
|
+
sourceOutputPath,
|
|
13686
14225
|
specHash,
|
|
13687
14226
|
spendByUnit,
|
|
14227
|
+
startHandEdit,
|
|
13688
14228
|
styleImagesBase64,
|
|
13689
14229
|
submit,
|
|
13690
14230
|
summarize,
|