pixelkiln 0.36.0 → 0.37.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 +1 -1
- package/dist/cli.js +637 -510
- package/dist/cli.js.map +1 -1
- package/dist/client/gallery.js +42 -5
- package/dist/index.cjs +414 -285
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +501 -20
- package/dist/index.d.ts +501 -20
- package/dist/index.js +410 -282
- package/dist/index.js.map +1 -1
- package/docs/ARCHITECTURE.md +8 -1
- package/docs/CLI.md +13 -2
- package/docs/MANIFEST.md +50 -2
- package/docs/QUALITY.md +16 -0
- package/docs/RECIPES.md +6 -2
- package/package.json +1 -1
- package/recipes/comfyui/pixel-art-xl-environment/1.0.0/pixelkiln.recipe.json +1 -1
- package/recipes/comfyui/pixel-art-xl-img2img/1.0.0/pixelkiln.recipe.json +1 -1
- package/schema/manifest.schema.json +8 -0
- package/schema/recipe.schema.json +4 -0
package/dist/cli.js
CHANGED
|
@@ -606,8 +606,8 @@ function formatCost(unit, amount) {
|
|
|
606
606
|
}
|
|
607
607
|
|
|
608
608
|
// src/pipeline/plan.ts
|
|
609
|
-
import { existsSync as
|
|
610
|
-
import
|
|
609
|
+
import { existsSync as existsSync5 } from "fs";
|
|
610
|
+
import path8 from "path";
|
|
611
611
|
|
|
612
612
|
// src/hash.ts
|
|
613
613
|
import { createHash } from "crypto";
|
|
@@ -843,6 +843,18 @@ var StyleObjectSchema = z.object({
|
|
|
843
843
|
* chocolate bar.
|
|
844
844
|
*/
|
|
845
845
|
palette: z.array(HexColorSchema).default([]),
|
|
846
|
+
/**
|
|
847
|
+
* Snap every visible pixel of the downloaded art to the nearest `palette`
|
|
848
|
+
* colour, without dithering. The palette is sent to providers that take
|
|
849
|
+
* one, and most honour it most of the time; this makes it a guarantee
|
|
850
|
+
* for every generator, including the ones with no palette parameter.
|
|
851
|
+
* Applied by `fetch` as the file is written and recorded on the lock
|
|
852
|
+
* entry, so a restore reproduces the same bytes. Turning it on or off
|
|
853
|
+
* does not change the spec hash: `plan` reports the entry as recoverable
|
|
854
|
+
* and the next `fetch` or `restore` re-applies it from the art already
|
|
855
|
+
* on disk or in the cache, spending nothing.
|
|
856
|
+
*/
|
|
857
|
+
enforcePalette: z.boolean().default(false),
|
|
846
858
|
/** Fail-closed native-grid, palette, and human-approval policy for derived art. */
|
|
847
859
|
quality: QualityProfileSchema.optional(),
|
|
848
860
|
/**
|
|
@@ -884,6 +896,13 @@ var StyleSchema = StyleObjectSchema.refine((s) => !(s.tileFeature && s.styleImag
|
|
|
884
896
|
path: ["quality"]
|
|
885
897
|
});
|
|
886
898
|
}
|
|
899
|
+
if (style.enforcePalette && style.palette.length < 2) {
|
|
900
|
+
ctx.addIssue({
|
|
901
|
+
code: z.ZodIssueCode.custom,
|
|
902
|
+
message: "enforcePalette needs a palette of at least two colours to snap to",
|
|
903
|
+
path: ["enforcePalette"]
|
|
904
|
+
});
|
|
905
|
+
}
|
|
887
906
|
});
|
|
888
907
|
var InheritedStyleSchema = StyleObjectSchema.partial().extend({
|
|
889
908
|
extends: z.string().min(1),
|
|
@@ -1006,7 +1025,21 @@ var LockOutputSchema = z.object({
|
|
|
1006
1025
|
path: z.string(),
|
|
1007
1026
|
sha256: z.string(),
|
|
1008
1027
|
role: z.string().optional(),
|
|
1009
|
-
mediaType: MediaTypeSchema.optional()
|
|
1028
|
+
mediaType: MediaTypeSchema.optional(),
|
|
1029
|
+
/**
|
|
1030
|
+
* Hash of the provider's bytes before post-processing changed them, when
|
|
1031
|
+
* it did. The content cache keeps both, so the post-processing can be
|
|
1032
|
+
* undone or redone without contacting the provider.
|
|
1033
|
+
*/
|
|
1034
|
+
raw: z.string().optional()
|
|
1035
|
+
});
|
|
1036
|
+
var PostprocessSchema = z.object({
|
|
1037
|
+
palette: z.object({
|
|
1038
|
+
/** Canonical lowercase `#rrggbb` values, in manifest order. */
|
|
1039
|
+
colors: z.array(z.string()).min(2).max(256),
|
|
1040
|
+
dither: z.literal("none"),
|
|
1041
|
+
distance: z.literal("redmean")
|
|
1042
|
+
}).optional()
|
|
1010
1043
|
});
|
|
1011
1044
|
var LockSourceSchema = z.object({
|
|
1012
1045
|
url: z.string(),
|
|
@@ -1032,6 +1065,7 @@ var LockHistoryEntrySchema = z.object({
|
|
|
1032
1065
|
cost: z.number().finite().nonnegative().default(0),
|
|
1033
1066
|
costUnit: z.string().min(1).default("generations"),
|
|
1034
1067
|
provider: z.string().default("pixellab"),
|
|
1068
|
+
postprocess: PostprocessSchema.optional(),
|
|
1035
1069
|
/** When the replacement was submitted. */
|
|
1036
1070
|
retiredAt: z.string()
|
|
1037
1071
|
});
|
|
@@ -1103,14 +1137,7 @@ var LockEntrySchema = z.object({
|
|
|
1103
1137
|
* `role` labels non-primary artifacts (e.g. "portrait", "spriteframes") so a
|
|
1104
1138
|
* consumer can find the one it wants without pattern-matching on paths.
|
|
1105
1139
|
*/
|
|
1106
|
-
outputs: z.array(
|
|
1107
|
-
z.object({
|
|
1108
|
-
path: z.string(),
|
|
1109
|
-
sha256: z.string(),
|
|
1110
|
-
role: z.string().optional(),
|
|
1111
|
-
mediaType: MediaTypeSchema.optional()
|
|
1112
|
-
})
|
|
1113
|
-
).default([]),
|
|
1140
|
+
outputs: z.array(LockOutputSchema).default([]),
|
|
1114
1141
|
/**
|
|
1115
1142
|
* Provider-owned data retained for downstream consumers, namespaced by
|
|
1116
1143
|
* provider id. PixelLab connectable sets keep their exact `tileRules` here
|
|
@@ -1126,7 +1153,9 @@ var LockEntrySchema = z.object({
|
|
|
1126
1153
|
/** Which provider produced this. Absent on entries written before providers. */
|
|
1127
1154
|
provider: z.string().default("pixellab"),
|
|
1128
1155
|
/** Generations this entry replaced, newest first; see LockHistoryEntrySchema. */
|
|
1129
|
-
history: z.array(LockHistoryEntrySchema).optional()
|
|
1156
|
+
history: z.array(LockHistoryEntrySchema).optional(),
|
|
1157
|
+
/** Post-processing applied to the recorded outputs; absent when the bytes are the provider's. */
|
|
1158
|
+
postprocess: PostprocessSchema.optional()
|
|
1130
1159
|
});
|
|
1131
1160
|
var LockSchema = z.object({
|
|
1132
1161
|
version: z.literal(2),
|
|
@@ -1876,13 +1905,13 @@ function resolveSpecOutputs(spec, lock, manifestDir) {
|
|
|
1876
1905
|
}
|
|
1877
1906
|
|
|
1878
1907
|
// src/pipeline/quality-profile.ts
|
|
1879
|
-
import { existsSync as
|
|
1880
|
-
import
|
|
1908
|
+
import { existsSync as existsSync3 } from "fs";
|
|
1909
|
+
import path6 from "path";
|
|
1881
1910
|
|
|
1882
1911
|
// src/pipeline/refine.ts
|
|
1883
|
-
import
|
|
1912
|
+
import path5 from "path";
|
|
1884
1913
|
import { execFile } from "child_process";
|
|
1885
|
-
import { mkdtemp, readFile as
|
|
1914
|
+
import { mkdtemp, readFile as readFile3, rm as rm3 } from "fs/promises";
|
|
1886
1915
|
import { tmpdir } from "os";
|
|
1887
1916
|
import { promisify } from "util";
|
|
1888
1917
|
|
|
@@ -2343,10 +2372,19 @@ async function writeArtifactBundle(files, options = {}) {
|
|
|
2343
2372
|
};
|
|
2344
2373
|
}
|
|
2345
2374
|
|
|
2346
|
-
// src/
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2375
|
+
// src/palette.ts
|
|
2376
|
+
function normalizePalette(values) {
|
|
2377
|
+
const colors = /* @__PURE__ */ new Map();
|
|
2378
|
+
for (const value of values) {
|
|
2379
|
+
const rgb = parseHex(value);
|
|
2380
|
+
const hex2 = `#${rgb.r.toString(16).padStart(2, "0")}${rgb.g.toString(16).padStart(2, "0")}${rgb.b.toString(16).padStart(2, "0")}`;
|
|
2381
|
+
if (!colors.has(hex2)) colors.set(hex2, { hex: hex2, ...rgb });
|
|
2382
|
+
}
|
|
2383
|
+
if (colors.size < 2 || colors.size > 256) {
|
|
2384
|
+
throw new Error(`A palette must contain 2\u2013256 unique colors; got ${colors.size}.`);
|
|
2385
|
+
}
|
|
2386
|
+
return [...colors.values()];
|
|
2387
|
+
}
|
|
2350
2388
|
function colorDistance(a, b) {
|
|
2351
2389
|
const rmean = (a.r + b.r) / 2;
|
|
2352
2390
|
const dr = a.r - b.r;
|
|
@@ -2356,146 +2394,45 @@ function colorDistance(a, b) {
|
|
|
2356
2394
|
(512 + rmean) * dr * dr / 256 + 4 * dg * dg + (767 - rmean) * db * db / 256
|
|
2357
2395
|
);
|
|
2358
2396
|
}
|
|
2359
|
-
function
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
const totals = /* @__PURE__ */ new Map();
|
|
2373
|
-
for (const palette of palettes) {
|
|
2374
|
-
for (const color of palette) {
|
|
2375
|
-
const key = `${color.r},${color.g},${color.b}`;
|
|
2376
|
-
const existing = totals.get(key);
|
|
2377
|
-
if (existing) existing.weight += color.weight;
|
|
2378
|
-
else totals.set(key, { ...color });
|
|
2397
|
+
function quantize(png, palette) {
|
|
2398
|
+
const colors = normalizePalette(palette);
|
|
2399
|
+
const pixels = Buffer.from(png.pixels);
|
|
2400
|
+
const nearestOf = /* @__PURE__ */ new Map();
|
|
2401
|
+
let changed = 0;
|
|
2402
|
+
for (let i = 0; i < pixels.length; i += 4) {
|
|
2403
|
+
const alpha = pixels[i + 3];
|
|
2404
|
+
if (alpha === 0) {
|
|
2405
|
+
if (pixels[i] || pixels[i + 1] || pixels[i + 2]) changed++;
|
|
2406
|
+
pixels[i] = 0;
|
|
2407
|
+
pixels[i + 1] = 0;
|
|
2408
|
+
pixels[i + 2] = 0;
|
|
2409
|
+
continue;
|
|
2379
2410
|
}
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
for (const output of resolveSpecOutputs(spec, lock, loaded.root)) {
|
|
2394
|
-
if (!existsSync3(output.absolutePath)) {
|
|
2395
|
-
missing.push(output.id);
|
|
2396
|
-
continue;
|
|
2397
|
-
}
|
|
2398
|
-
try {
|
|
2399
|
-
const png = decodePng(await readFile3(output.absolutePath));
|
|
2400
|
-
const palette = extractPalette(png, 12);
|
|
2401
|
-
assets.push({
|
|
2402
|
-
assetId: spec.assetId,
|
|
2403
|
-
...output.role ? { outputRole: output.role } : {},
|
|
2404
|
-
id: output.id,
|
|
2405
|
-
file: output.absolutePath,
|
|
2406
|
-
width: png.width,
|
|
2407
|
-
height: png.height,
|
|
2408
|
-
paletteDistance: 0,
|
|
2409
|
-
// filled once the reference is known
|
|
2410
|
-
transparency: transparencyRatio(png),
|
|
2411
|
-
colorCount: countColors(png),
|
|
2412
|
-
palette
|
|
2413
|
-
});
|
|
2414
|
-
} catch (err) {
|
|
2415
|
-
unreadable.push(`${output.id}: ${err instanceof Error ? err.message : String(err)}`);
|
|
2411
|
+
const key = pixels[i] << 16 | pixels[i + 1] << 8 | pixels[i + 2];
|
|
2412
|
+
let nearest = nearestOf.get(key);
|
|
2413
|
+
if (!nearest) {
|
|
2414
|
+
const pixel = { r: pixels[i], g: pixels[i + 1], b: pixels[i + 2] };
|
|
2415
|
+
nearest = colors[0];
|
|
2416
|
+
let distance = colorDistance(pixel, nearest);
|
|
2417
|
+
for (let j = 1; j < colors.length; j++) {
|
|
2418
|
+
const candidate = colors[j];
|
|
2419
|
+
const candidateDistance = colorDistance(pixel, candidate);
|
|
2420
|
+
if (candidateDistance < distance) {
|
|
2421
|
+
nearest = candidate;
|
|
2422
|
+
distance = candidateDistance;
|
|
2423
|
+
}
|
|
2416
2424
|
}
|
|
2425
|
+
nearestOf.set(key, nearest);
|
|
2417
2426
|
}
|
|
2427
|
+
if (pixels[i] !== nearest.r || pixels[i + 1] !== nearest.g || pixels[i + 2] !== nearest.b) changed++;
|
|
2428
|
+
pixels[i] = nearest.r;
|
|
2429
|
+
pixels[i + 1] = nearest.g;
|
|
2430
|
+
pixels[i + 2] = nearest.b;
|
|
2418
2431
|
}
|
|
2419
|
-
|
|
2420
|
-
let referenceFromStyleImages = false;
|
|
2421
|
-
const refPalettes = [];
|
|
2422
|
-
for (const img of style.styleImages) {
|
|
2423
|
-
const abs = path5.resolve(loaded.root, img.path);
|
|
2424
|
-
if (!existsSync3(abs)) continue;
|
|
2425
|
-
try {
|
|
2426
|
-
refPalettes.push(extractPalette(decodePng(await readFile3(abs)), 12));
|
|
2427
|
-
} catch {
|
|
2428
|
-
}
|
|
2429
|
-
}
|
|
2430
|
-
if (refPalettes.length) {
|
|
2431
|
-
reference = mergePalettes(refPalettes);
|
|
2432
|
-
referenceFromStyleImages = true;
|
|
2433
|
-
for (const asset of assets) {
|
|
2434
|
-
asset.paletteDistance = paletteDistance(asset.palette, reference);
|
|
2435
|
-
}
|
|
2436
|
-
} else {
|
|
2437
|
-
reference = mergePalettes(assets.map((a) => a.palette));
|
|
2438
|
-
for (const asset of assets) {
|
|
2439
|
-
const others = assets.filter((a) => a !== asset).map((a) => a.palette);
|
|
2440
|
-
asset.paletteDistance = others.length ? paletteDistance(asset.palette, mergePalettes(others)) : 0;
|
|
2441
|
-
}
|
|
2442
|
-
}
|
|
2443
|
-
assets.sort((a, b) => b.paletteDistance - a.paletteDistance);
|
|
2444
|
-
return { styleId, referenceFromStyleImages, reference, assets, missing, unreadable };
|
|
2445
|
-
}
|
|
2446
|
-
function countColors(png) {
|
|
2447
|
-
const seen = /* @__PURE__ */ new Set();
|
|
2448
|
-
for (let i = 0; i < png.pixels.length; i += 4) {
|
|
2449
|
-
if (png.pixels[i + 3] < 128) continue;
|
|
2450
|
-
seen.add(png.pixels[i] << 16 | png.pixels[i + 1] << 8 | png.pixels[i + 2]);
|
|
2451
|
-
}
|
|
2452
|
-
return seen.size;
|
|
2432
|
+
return { png: { width: png.width, height: png.height, pixels }, changed };
|
|
2453
2433
|
}
|
|
2454
|
-
function
|
|
2455
|
-
|
|
2456
|
-
if (n < 3) return [];
|
|
2457
|
-
const mean = audit.assets.reduce((s, a) => s + a.paletteDistance, 0) / n;
|
|
2458
|
-
const variance = audit.assets.reduce((s, a) => s + (a.paletteDistance - mean) ** 2, 0) / n;
|
|
2459
|
-
const sd = Math.sqrt(variance);
|
|
2460
|
-
if (sd === 0) return [];
|
|
2461
|
-
return audit.assets.filter((a) => a.paletteDistance > mean + sigma * sd);
|
|
2462
|
-
}
|
|
2463
|
-
function evaluateAudit(audit, thresholds = {}) {
|
|
2464
|
-
const resolved = { ...thresholds, sigma: thresholds.sigma ?? 1.5 };
|
|
2465
|
-
const relative = outliers(audit, resolved.sigma);
|
|
2466
|
-
const relativeIds = new Set(relative.map((asset) => asset.id));
|
|
2467
|
-
const violations = [];
|
|
2468
|
-
for (const asset of audit.assets) {
|
|
2469
|
-
const reasons = [];
|
|
2470
|
-
if (relativeIds.has(asset.id)) {
|
|
2471
|
-
reasons.push(`palette outlier beyond ${resolved.sigma} standard deviations`);
|
|
2472
|
-
}
|
|
2473
|
-
if (resolved.maxDistance !== void 0 && asset.paletteDistance > resolved.maxDistance) {
|
|
2474
|
-
reasons.push(
|
|
2475
|
-
`palette distance ${asset.paletteDistance.toFixed(1)} exceeds ${resolved.maxDistance}`
|
|
2476
|
-
);
|
|
2477
|
-
}
|
|
2478
|
-
if (resolved.minTransparency !== void 0 && asset.transparency < resolved.minTransparency) {
|
|
2479
|
-
reasons.push(
|
|
2480
|
-
`transparency ${(asset.transparency * 100).toFixed(1)}% is below ${(resolved.minTransparency * 100).toFixed(1)}%`
|
|
2481
|
-
);
|
|
2482
|
-
}
|
|
2483
|
-
if (resolved.maxColors !== void 0 && asset.colorCount > resolved.maxColors) {
|
|
2484
|
-
reasons.push(`color count ${asset.colorCount} exceeds ${resolved.maxColors}`);
|
|
2485
|
-
}
|
|
2486
|
-
if (reasons.length) violations.push({ id: asset.id, reasons });
|
|
2487
|
-
}
|
|
2488
|
-
return {
|
|
2489
|
-
safe: violations.length === 0 && audit.missing.length === 0 && audit.unreadable.length === 0,
|
|
2490
|
-
thresholds: resolved,
|
|
2491
|
-
outliers: [...relativeIds],
|
|
2492
|
-
violations,
|
|
2493
|
-
missing: [...audit.missing],
|
|
2494
|
-
unreadable: [...audit.unreadable]
|
|
2495
|
-
};
|
|
2496
|
-
}
|
|
2497
|
-
function hex(c) {
|
|
2498
|
-
return `#${[c.r, c.g, c.b].map((v) => v.toString(16).padStart(2, "0")).join("")}`;
|
|
2434
|
+
function quantizeToPalette(png, palette) {
|
|
2435
|
+
return quantize(png, palette).png;
|
|
2499
2436
|
}
|
|
2500
2437
|
|
|
2501
2438
|
// src/pipeline/refine.ts
|
|
@@ -2582,48 +2519,12 @@ function parseDetection(raw) {
|
|
|
2582
2519
|
confidence: confidenceFor(consensus)
|
|
2583
2520
|
};
|
|
2584
2521
|
}
|
|
2585
|
-
function normalizePalette(values) {
|
|
2586
|
-
const colors = /* @__PURE__ */ new Map();
|
|
2587
|
-
for (const value of values) {
|
|
2588
|
-
const rgb = parseHex(value);
|
|
2589
|
-
const hex2 = `#${rgb.r.toString(16).padStart(2, "0")}${rgb.g.toString(16).padStart(2, "0")}${rgb.b.toString(16).padStart(2, "0")}`;
|
|
2590
|
-
colors.set(hex2, { hex: hex2, ...rgb });
|
|
2591
|
-
}
|
|
2592
|
-
if (colors.size < 2 || colors.size > 256) {
|
|
2593
|
-
throw new Error(`A refinement palette must contain 2\u2013256 unique colors; got ${colors.size}.`);
|
|
2594
|
-
}
|
|
2595
|
-
return [...colors.values()];
|
|
2596
|
-
}
|
|
2597
2522
|
function normalizeRefinementPalette(values) {
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
const pixels = Buffer.from(png.pixels);
|
|
2603
|
-
for (let i = 0; i < pixels.length; i += 4) {
|
|
2604
|
-
const alpha = pixels[i + 3];
|
|
2605
|
-
if (alpha === 0) {
|
|
2606
|
-
pixels[i] = 0;
|
|
2607
|
-
pixels[i + 1] = 0;
|
|
2608
|
-
pixels[i + 2] = 0;
|
|
2609
|
-
continue;
|
|
2610
|
-
}
|
|
2611
|
-
const pixel = { r: pixels[i], g: pixels[i + 1], b: pixels[i + 2] };
|
|
2612
|
-
let nearest = colors[0];
|
|
2613
|
-
let distance = colorDistance(pixel, nearest);
|
|
2614
|
-
for (let j = 1; j < colors.length; j++) {
|
|
2615
|
-
const candidate = colors[j];
|
|
2616
|
-
const candidateDistance = colorDistance(pixel, candidate);
|
|
2617
|
-
if (candidateDistance < distance) {
|
|
2618
|
-
nearest = candidate;
|
|
2619
|
-
distance = candidateDistance;
|
|
2620
|
-
}
|
|
2621
|
-
}
|
|
2622
|
-
pixels[i] = nearest.r;
|
|
2623
|
-
pixels[i + 1] = nearest.g;
|
|
2624
|
-
pixels[i + 2] = nearest.b;
|
|
2523
|
+
try {
|
|
2524
|
+
return normalizePalette(values).map((color) => color.hex);
|
|
2525
|
+
} catch (err) {
|
|
2526
|
+
throw new Error(`A refinement palette must contain 2\u2013256 unique colors; got ${new Set(values.map((v) => v.toLowerCase())).size}.`, { cause: err });
|
|
2625
2527
|
}
|
|
2626
|
-
return { width: png.width, height: png.height, pixels };
|
|
2627
2528
|
}
|
|
2628
2529
|
function colorCount(png) {
|
|
2629
2530
|
const colors = /* @__PURE__ */ new Set();
|
|
@@ -2733,16 +2634,16 @@ function sameFrameGrid(first, next) {
|
|
|
2733
2634
|
return next.detection.stepX === first.detection.stepX && next.detection.stepY === first.detection.stepY && next.detection.columns === first.detection.columns && next.detection.rows === first.detection.rows && phaseMatches(next.detection.phaseX, first.detection.phaseX, first.detection.stepX) && phaseMatches(next.detection.phaseY, first.detection.phaseY, first.detection.stepY) && next.audit.width === first.audit.width && next.audit.height === first.audit.height && next.sourceWidth === first.sourceWidth && next.sourceHeight === first.sourceHeight;
|
|
2734
2635
|
}
|
|
2735
2636
|
async function refineAsset(options) {
|
|
2736
|
-
const source =
|
|
2737
|
-
const output =
|
|
2637
|
+
const source = path5.resolve(options.source);
|
|
2638
|
+
const output = path5.resolve(options.output);
|
|
2738
2639
|
if (source === output) throw new Error("Refinement source and output must be different files.");
|
|
2739
|
-
const record =
|
|
2640
|
+
const record = path5.resolve(qualityRecordPath(output));
|
|
2740
2641
|
const palette = normalizePalette(options.palette).map((color) => color.hex);
|
|
2741
2642
|
const minGridConfidence = requireConfidence(options.minGridConfidence ?? "high");
|
|
2742
2643
|
if (options.minTransparency !== void 0 && (!Number.isFinite(options.minTransparency) || options.minTransparency < 0 || options.minTransparency > 1)) throw new Error("minTransparency must be between 0 and 1.");
|
|
2743
2644
|
let sourcePng;
|
|
2744
2645
|
try {
|
|
2745
|
-
sourcePng = decodePng(await
|
|
2646
|
+
sourcePng = decodePng(await readFile3(source));
|
|
2746
2647
|
} catch (error) {
|
|
2747
2648
|
throw new Error(`Cannot read refinement source ${source}: ${message2(error)}`, { cause: error });
|
|
2748
2649
|
}
|
|
@@ -2759,8 +2660,8 @@ async function refineAsset(options) {
|
|
|
2759
2660
|
);
|
|
2760
2661
|
}
|
|
2761
2662
|
}
|
|
2762
|
-
const temp = await mkdtemp(
|
|
2763
|
-
const recovered =
|
|
2663
|
+
const temp = await mkdtemp(path5.join(tmpdir(), "pixelkiln-refine-"));
|
|
2664
|
+
const recovered = path5.join(temp, "native.png");
|
|
2764
2665
|
const fixerCommand = compatibilityCommand ?? options.fixerPython ?? process.env.PIXELKILN_PIXEL_FIXER_PYTHON ?? "python3";
|
|
2765
2666
|
const args = compatibilityCommand ? [...options.fixerArgsPrefix ?? [], source, "--extract", recovered] : ["-c", PYTHON_FIXER_BRIDGE, source, recovered];
|
|
2766
2667
|
try {
|
|
@@ -2781,7 +2682,7 @@ async function refineAsset(options) {
|
|
|
2781
2682
|
);
|
|
2782
2683
|
}
|
|
2783
2684
|
const detection = parseDetection(firstJsonObject(stdout));
|
|
2784
|
-
const native = decodePng(await
|
|
2685
|
+
const native = decodePng(await readFile3(recovered));
|
|
2785
2686
|
const quantized = quantizeToPalette(native, palette);
|
|
2786
2687
|
const png = encodeRgbaPng(quantized.width, quantized.height, quantized.pixels);
|
|
2787
2688
|
const audit = auditRefinement(
|
|
@@ -2845,18 +2746,18 @@ async function refineFrameSet(options) {
|
|
|
2845
2746
|
if (roles.some((role) => !role.trim()) || new Set(roles).size !== roles.length) {
|
|
2846
2747
|
throw new Error("Frame-set roles must be non-empty and unique.");
|
|
2847
2748
|
}
|
|
2848
|
-
const output =
|
|
2849
|
-
const record =
|
|
2749
|
+
const output = path5.resolve(options.output);
|
|
2750
|
+
const record = path5.resolve(qualityRecordPath(output));
|
|
2850
2751
|
const palette = normalizeRefinementPalette(options.palette);
|
|
2851
2752
|
const finalOutputs = options.sources.map((source, index) => qualityFrameOutputPath(output, source.role, index));
|
|
2852
2753
|
if (new Set(finalOutputs).size !== finalOutputs.length) {
|
|
2853
2754
|
throw new Error("Frame-set roles resolve to duplicate output filenames.");
|
|
2854
2755
|
}
|
|
2855
|
-
const sourceSet = new Set(options.sources.map((source) =>
|
|
2756
|
+
const sourceSet = new Set(options.sources.map((source) => path5.resolve(source.path)));
|
|
2856
2757
|
if (finalOutputs.some((file) => sourceSet.has(file))) {
|
|
2857
2758
|
throw new Error("Frame-set refinement sources and outputs must be different files.");
|
|
2858
2759
|
}
|
|
2859
|
-
const temp = await mkdtemp(
|
|
2760
|
+
const temp = await mkdtemp(path5.join(tmpdir(), "pixelkiln-refine-frames-"));
|
|
2860
2761
|
try {
|
|
2861
2762
|
const frames = [];
|
|
2862
2763
|
const outputs = [];
|
|
@@ -2864,8 +2765,8 @@ async function refineFrameSet(options) {
|
|
|
2864
2765
|
const sourcePaths = [];
|
|
2865
2766
|
let firstOptions = null;
|
|
2866
2767
|
for (const [index, sourceEntry] of options.sources.entries()) {
|
|
2867
|
-
const source =
|
|
2868
|
-
const temporaryOutput =
|
|
2768
|
+
const source = path5.resolve(sourceEntry.path);
|
|
2769
|
+
const temporaryOutput = path5.join(temp, `${String(index).padStart(2, "0")}.png`);
|
|
2869
2770
|
const refined = await refineAsset({
|
|
2870
2771
|
source,
|
|
2871
2772
|
output: temporaryOutput,
|
|
@@ -2897,7 +2798,7 @@ async function refineFrameSet(options) {
|
|
|
2897
2798
|
});
|
|
2898
2799
|
outputs.push({
|
|
2899
2800
|
path: finalOutputs[index],
|
|
2900
|
-
data: await
|
|
2801
|
+
data: await readFile3(temporaryOutput)
|
|
2901
2802
|
});
|
|
2902
2803
|
}
|
|
2903
2804
|
const first = frames[0];
|
|
@@ -2940,7 +2841,7 @@ async function refineFrameSet(options) {
|
|
|
2940
2841
|
);
|
|
2941
2842
|
return {
|
|
2942
2843
|
sources: sourcePaths,
|
|
2943
|
-
outputs: outputs.map((item) =>
|
|
2844
|
+
outputs: outputs.map((item) => path5.resolve(item.path)),
|
|
2944
2845
|
record,
|
|
2945
2846
|
frames,
|
|
2946
2847
|
palette,
|
|
@@ -2951,12 +2852,12 @@ async function refineFrameSet(options) {
|
|
|
2951
2852
|
}
|
|
2952
2853
|
}
|
|
2953
2854
|
async function checkQualityRecord(recordPath) {
|
|
2954
|
-
const record =
|
|
2855
|
+
const record = path5.resolve(recordPath);
|
|
2955
2856
|
const manifest = await readArtifactBundleManifest(record);
|
|
2956
2857
|
const options = parseRecordOptions(manifest, record);
|
|
2957
2858
|
const verification = await verifyArtifactBundle(record);
|
|
2958
|
-
const sources = manifest.sources.map((source2) =>
|
|
2959
|
-
const outputs = manifest.outputs.map((output2) =>
|
|
2859
|
+
const sources = manifest.sources.map((source2) => path5.resolve(path5.dirname(record), source2.path));
|
|
2860
|
+
const outputs = manifest.outputs.map((output2) => path5.resolve(path5.dirname(record), output2.path));
|
|
2960
2861
|
const source = sources[0] ?? "";
|
|
2961
2862
|
const output = outputs[0] ?? "";
|
|
2962
2863
|
const expectedCount = options.frameSet?.count ?? 1;
|
|
@@ -2989,7 +2890,7 @@ async function checkQualityRecord(recordPath) {
|
|
|
2989
2890
|
async function approveQualityRecord(recordPath, approval) {
|
|
2990
2891
|
const reviewer = approval.reviewer.trim();
|
|
2991
2892
|
if (!reviewer) throw new Error("A human reviewer name is required.");
|
|
2992
|
-
const record =
|
|
2893
|
+
const record = path5.resolve(recordPath);
|
|
2993
2894
|
const manifest = await readArtifactBundleManifest(record);
|
|
2994
2895
|
const options = parseRecordOptions(manifest, record);
|
|
2995
2896
|
const verification = await verifyArtifactBundle(record);
|
|
@@ -3010,14 +2911,14 @@ async function approveQualityRecord(recordPath, approval) {
|
|
|
3010
2911
|
`A refinement record must contain exactly ${expectedCount} source(s) and output(s).`
|
|
3011
2912
|
);
|
|
3012
2913
|
}
|
|
3013
|
-
const root =
|
|
2914
|
+
const root = path5.dirname(record);
|
|
3014
2915
|
const outputs = await Promise.all(manifest.outputs.map(async (entry) => {
|
|
3015
|
-
const outputPath =
|
|
3016
|
-
return { path: outputPath, data: await
|
|
2916
|
+
const outputPath = path5.resolve(root, entry.path);
|
|
2917
|
+
return { path: outputPath, data: await readFile3(outputPath) };
|
|
3017
2918
|
}));
|
|
3018
2919
|
const sources = manifest.sources.map((source) => ({
|
|
3019
2920
|
...source,
|
|
3020
|
-
path:
|
|
2921
|
+
path: path5.resolve(root, source.path)
|
|
3021
2922
|
}));
|
|
3022
2923
|
const approved = {
|
|
3023
2924
|
...options,
|
|
@@ -3049,8 +2950,8 @@ async function qualitySource(spec, lock) {
|
|
|
3049
2950
|
if (spec.generator === "frames") {
|
|
3050
2951
|
return { items: null, reason: "frame-set quality requires ordered downloaded provider outputs" };
|
|
3051
2952
|
}
|
|
3052
|
-
const source =
|
|
3053
|
-
return
|
|
2953
|
+
const source = path6.resolve(spec.root, spec.source);
|
|
2954
|
+
return existsSync3(source) ? { items: [{ role: "source", path: source }] } : { items: null, reason: `declared source is missing: ${spec.source}` };
|
|
3054
2955
|
}
|
|
3055
2956
|
const entry = lock.entries[lockKey(spec.styleId, spec.assetId)];
|
|
3056
2957
|
if (!entry) return { items: null, reason: "raw provider output is not in the lockfile" };
|
|
@@ -3079,7 +2980,7 @@ async function qualitySource(spec, lock) {
|
|
|
3079
2980
|
}
|
|
3080
2981
|
if (role) roles.add(role);
|
|
3081
2982
|
const source = currentEntryOutputPath(entry, spec, index);
|
|
3082
|
-
if (!
|
|
2983
|
+
if (!existsSync3(source)) return { items: null, reason: `raw provider output is missing: ${source}` };
|
|
3083
2984
|
if (await sha256File(source) !== output.sha256) {
|
|
3084
2985
|
return { items: null, reason: "raw provider output was modified after download" };
|
|
3085
2986
|
}
|
|
@@ -3098,7 +2999,7 @@ async function inspectQualityProfile(spec, lock) {
|
|
|
3098
2999
|
}
|
|
3099
3000
|
const sourcePaths = source.items.map((item) => item.path);
|
|
3100
3001
|
const expectedOutputs = spec.generator === "frames" ? source.items.map((item, index) => qualityFrameOutputPath(output, item.role, index)) : [output];
|
|
3101
|
-
if (!
|
|
3002
|
+
if (!existsSync3(record)) {
|
|
3102
3003
|
return {
|
|
3103
3004
|
key,
|
|
3104
3005
|
state: "needs-refinement",
|
|
@@ -3116,7 +3017,7 @@ async function inspectQualityProfile(spec, lock) {
|
|
|
3116
3017
|
const expectedRevision = spec.quality.fixerRevision ?? PIXEL_ART_FIXER_REVISION;
|
|
3117
3018
|
const expectedTransparency = spec.quality.minTransparency ?? null;
|
|
3118
3019
|
const expectedFrameSet = spec.generator === "frames" ? { fps: spec.quality.fps ?? 12, count: source.items.length, roles: source.items.map((item) => item.role) } : null;
|
|
3119
|
-
const contractChanged = JSON.stringify(check.sources.map((item) =>
|
|
3020
|
+
const contractChanged = JSON.stringify(check.sources.map((item) => path6.resolve(item))) !== JSON.stringify(sourcePaths.map((item) => path6.resolve(item))) || JSON.stringify(check.outputs.map((item) => path6.resolve(item))) !== JSON.stringify(expectedOutputs.map((item) => path6.resolve(item))) || JSON.stringify(check.options.palette.colors) !== JSON.stringify(expectedPalette) || check.options.nativeGrid.revision !== expectedRevision || check.options.audit.thresholds.minGridConfidence !== spec.quality.minGridConfidence || check.options.audit.thresholds.minTransparency !== expectedTransparency || (expectedFrameSet ? !check.options.frameSet || check.options.frameSet.fps !== expectedFrameSet.fps || check.options.frameSet.count !== expectedFrameSet.count || JSON.stringify(check.options.frameSet.roles) !== JSON.stringify(expectedFrameSet.roles) : check.options.frameSet !== void 0);
|
|
3120
3021
|
if (contractChanged) {
|
|
3121
3022
|
return {
|
|
3122
3023
|
key,
|
|
@@ -3278,9 +3179,38 @@ async function requireApprovedQualitySources(specs, lock, assetIds, outputRoles)
|
|
|
3278
3179
|
return sources;
|
|
3279
3180
|
}
|
|
3280
3181
|
|
|
3182
|
+
// src/pipeline/postprocess.ts
|
|
3183
|
+
function postprocessFor(spec) {
|
|
3184
|
+
if (!spec.enforcePalette || spec.palette.length < 2) return void 0;
|
|
3185
|
+
return {
|
|
3186
|
+
palette: {
|
|
3187
|
+
colors: normalizePalette(spec.palette).map((color) => color.hex),
|
|
3188
|
+
dither: "none",
|
|
3189
|
+
distance: "redmean"
|
|
3190
|
+
}
|
|
3191
|
+
};
|
|
3192
|
+
}
|
|
3193
|
+
function samePostprocess(a, b) {
|
|
3194
|
+
const pa = a?.palette;
|
|
3195
|
+
const pb = b?.palette;
|
|
3196
|
+
if (!pa && !pb) return true;
|
|
3197
|
+
if (!pa || !pb) return false;
|
|
3198
|
+
return pa.dither === pb.dither && pa.distance === pb.distance && pa.colors.length === pb.colors.length && pa.colors.every((color, i) => color === pb.colors[i]);
|
|
3199
|
+
}
|
|
3200
|
+
function postprocessCurrent(entry, spec) {
|
|
3201
|
+
return samePostprocess(entry.postprocess, postprocessFor(spec));
|
|
3202
|
+
}
|
|
3203
|
+
function applyPostprocess(bytes, mediaType, record) {
|
|
3204
|
+
if (!record?.palette || mediaType !== MediaType.PNG) return { bytes, changed: false };
|
|
3205
|
+
const png = decodePng(bytes);
|
|
3206
|
+
const { png: snapped, changed } = quantize(png, record.palette.colors);
|
|
3207
|
+
if (!changed) return { bytes, changed: false };
|
|
3208
|
+
return { bytes: encodeRgbaPng(snapped.width, snapped.height, snapped.pixels), changed: true };
|
|
3209
|
+
}
|
|
3210
|
+
|
|
3281
3211
|
// src/pipeline/revision.ts
|
|
3282
|
-
import { existsSync as
|
|
3283
|
-
import
|
|
3212
|
+
import { existsSync as existsSync4 } from "fs";
|
|
3213
|
+
import path7 from "path";
|
|
3284
3214
|
async function inspectRevisionReadiness(spec, lock) {
|
|
3285
3215
|
if (!spec.revision) return null;
|
|
3286
3216
|
return inspectRevision(spec, lock, /* @__PURE__ */ new Set());
|
|
@@ -3295,14 +3225,14 @@ async function inspectRevision(spec, lock, seen) {
|
|
|
3295
3225
|
reason: `revision source ${spec.styleId}/${revision.sourceAssetId} is not ready: ${dependency.reason}`
|
|
3296
3226
|
};
|
|
3297
3227
|
}
|
|
3298
|
-
if (!revision.sourceSha256 || !
|
|
3228
|
+
if (!revision.sourceSha256 || !existsSync4(revision.sourceFile)) {
|
|
3299
3229
|
return { ready: false, reason: `revision source is missing: ${revision.sourceFile}` };
|
|
3300
3230
|
}
|
|
3301
3231
|
if (await sha256File(revision.sourceFile) !== revision.sourceSha256) {
|
|
3302
3232
|
return { ready: false, reason: "revision source changed after the manifest was resolved" };
|
|
3303
3233
|
}
|
|
3304
3234
|
if (revision.maskFile) {
|
|
3305
|
-
if (!revision.maskSha256 || !
|
|
3235
|
+
if (!revision.maskSha256 || !existsSync4(revision.maskFile)) {
|
|
3306
3236
|
return { ready: false, reason: `revision mask is missing: ${revision.maskFile}` };
|
|
3307
3237
|
}
|
|
3308
3238
|
if (await sha256File(revision.maskFile) !== revision.maskSha256) {
|
|
@@ -3332,8 +3262,8 @@ async function inspectParent(spec, lock, seen) {
|
|
|
3332
3262
|
};
|
|
3333
3263
|
}
|
|
3334
3264
|
if (spec.source) {
|
|
3335
|
-
const source =
|
|
3336
|
-
return
|
|
3265
|
+
const source = path7.resolve(spec.root, spec.source);
|
|
3266
|
+
return existsSync4(source) ? { ready: true, reason: "declared source is present" } : { ready: false, reason: `declared source is missing: ${spec.source}` };
|
|
3337
3267
|
}
|
|
3338
3268
|
const entry = lock.entries[key];
|
|
3339
3269
|
if (!entry) return { ready: false, reason: "parent is not in the lockfile" };
|
|
@@ -3347,7 +3277,7 @@ async function inspectParent(spec, lock, seen) {
|
|
|
3347
3277
|
for (let index = 0; index < entry.outputs.length; index++) {
|
|
3348
3278
|
const output = entry.outputs[index];
|
|
3349
3279
|
const file = currentEntryOutputPath(entry, spec, index);
|
|
3350
|
-
if (!
|
|
3280
|
+
if (!existsSync4(file)) return { ready: false, reason: `parent output is missing: ${file}` };
|
|
3351
3281
|
if (await sha256File(file) !== output.sha256) {
|
|
3352
3282
|
return { ready: false, reason: `parent output was modified: ${file}` };
|
|
3353
3283
|
}
|
|
@@ -3376,7 +3306,7 @@ function resumeActions(specs, lock) {
|
|
|
3376
3306
|
const entry = lock.entries[key];
|
|
3377
3307
|
if (!entry || entry.specHash !== spec.specHash) continue;
|
|
3378
3308
|
if (entry.submissionComplete === false) continue;
|
|
3379
|
-
const command = resumeCommandForStatus(entry.status);
|
|
3309
|
+
const command = resumeCommandForStatus(entry.status) ?? (entry.status === "downloaded" && entry.outputs.length && !postprocessCurrent(entry, spec) ? "fetch" : null);
|
|
3380
3310
|
if (!command) continue;
|
|
3381
3311
|
if (command === "poll" && !entry.jobId) continue;
|
|
3382
3312
|
if (command === "pick" && !entry.reviewObjectId) continue;
|
|
@@ -3402,7 +3332,7 @@ async function buildPlan(specs, lock, opts = {}) {
|
|
|
3402
3332
|
state = "missing";
|
|
3403
3333
|
reason = "--force";
|
|
3404
3334
|
} else if (!entry && spec.source && spec.generator !== "frames") {
|
|
3405
|
-
if (
|
|
3335
|
+
if (existsSync5(path8.resolve(spec.root, spec.source))) {
|
|
3406
3336
|
state = "ok";
|
|
3407
3337
|
reason = `placed from ${spec.source}; not generated`;
|
|
3408
3338
|
} else {
|
|
@@ -3410,7 +3340,7 @@ async function buildPlan(specs, lock, opts = {}) {
|
|
|
3410
3340
|
reason = `declared source is missing: ${spec.source}`;
|
|
3411
3341
|
}
|
|
3412
3342
|
} else if (!entry) {
|
|
3413
|
-
if (
|
|
3343
|
+
if (existsSync5(spec.outFile)) {
|
|
3414
3344
|
state = "untracked";
|
|
3415
3345
|
reason = "file on disk, no provenance recorded";
|
|
3416
3346
|
} else {
|
|
@@ -3442,12 +3372,15 @@ async function buildPlan(specs, lock, opts = {}) {
|
|
|
3442
3372
|
} else if (entry.status !== "downloaded") {
|
|
3443
3373
|
state = "in-flight";
|
|
3444
3374
|
reason = `awaiting ${entry.status}`;
|
|
3445
|
-
} else if (entry.outputs.length === 0 || entry.outputs.some((_output, index) => !
|
|
3375
|
+
} else if (entry.outputs.length === 0 || entry.outputs.some((_output, index) => !existsSync5(currentEntryOutputPath(entry, spec, index)))) {
|
|
3446
3376
|
state = "orphaned";
|
|
3447
|
-
reason = entry.outputs.length === 0 ? "no recorded output files" : `missing on disk: ${entry.outputs.filter((_output, index) => !
|
|
3377
|
+
reason = entry.outputs.length === 0 ? "no recorded output files" : `missing on disk: ${entry.outputs.filter((_output, index) => !existsSync5(currentEntryOutputPath(entry, spec, index))).map((o) => o.role ?? "asset").join(", ")}`;
|
|
3448
3378
|
} else if (await anyOutputModified(entry, spec)) {
|
|
3449
3379
|
state = "orphaned";
|
|
3450
3380
|
reason = "output modified since download";
|
|
3381
|
+
} else if (!postprocessCurrent(entry, spec)) {
|
|
3382
|
+
state = "recoverable";
|
|
3383
|
+
reason = spec.enforcePalette ? "palette enforcement turned on; run pixelkiln fetch to snap the file (no generation cost)" : "palette enforcement turned off; run pixelkiln fetch to restore the provider's bytes (no generation cost)";
|
|
3451
3384
|
} else {
|
|
3452
3385
|
state = "ok";
|
|
3453
3386
|
reason = "up to date";
|
|
@@ -3607,12 +3540,12 @@ async function confirm(question, auto) {
|
|
|
3607
3540
|
}
|
|
3608
3541
|
|
|
3609
3542
|
// src/cli/commands/account.ts
|
|
3610
|
-
import
|
|
3611
|
-
import { existsSync as
|
|
3543
|
+
import path20 from "path";
|
|
3544
|
+
import { existsSync as existsSync15 } from "fs";
|
|
3612
3545
|
|
|
3613
3546
|
// src/env.ts
|
|
3614
|
-
import { readFileSync, existsSync as
|
|
3615
|
-
import
|
|
3547
|
+
import { readFileSync, existsSync as existsSync6 } from "fs";
|
|
3548
|
+
import path9 from "path";
|
|
3616
3549
|
function loadEnvFiles(dir) {
|
|
3617
3550
|
const loaded = [];
|
|
3618
3551
|
for (const [file, vars] of envFiles(dir)) {
|
|
@@ -3635,8 +3568,8 @@ function readEnvFiles(dir) {
|
|
|
3635
3568
|
function envFiles(dir) {
|
|
3636
3569
|
const out = [];
|
|
3637
3570
|
for (const name of [".env.local", ".env"]) {
|
|
3638
|
-
const file =
|
|
3639
|
-
if (!
|
|
3571
|
+
const file = path9.join(dir, name);
|
|
3572
|
+
if (!existsSync6(file)) continue;
|
|
3640
3573
|
try {
|
|
3641
3574
|
out.push([file, parseEnv(readFileSync(file, "utf8"))]);
|
|
3642
3575
|
} catch {
|
|
@@ -3665,8 +3598,8 @@ function parseEnv(contents) {
|
|
|
3665
3598
|
|
|
3666
3599
|
// src/providers/comfyui.ts
|
|
3667
3600
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
3668
|
-
import { readFile as
|
|
3669
|
-
import
|
|
3601
|
+
import { readFile as readFile4 } from "fs/promises";
|
|
3602
|
+
import path10 from "path";
|
|
3670
3603
|
|
|
3671
3604
|
// src/http.ts
|
|
3672
3605
|
var MAX_RETRIES = 4;
|
|
@@ -3853,10 +3786,10 @@ var ComfyUIProvider = class _ComfyUIProvider {
|
|
|
3853
3786
|
}
|
|
3854
3787
|
async resolveOptions(value, context) {
|
|
3855
3788
|
const options = parseOptions(value);
|
|
3856
|
-
const workflowPath =
|
|
3789
|
+
const workflowPath = path10.resolve(context.root, options.workflowFile);
|
|
3857
3790
|
let raw;
|
|
3858
3791
|
try {
|
|
3859
|
-
raw = await
|
|
3792
|
+
raw = await readFile4(workflowPath, "utf8");
|
|
3860
3793
|
} catch (err) {
|
|
3861
3794
|
throw new Error(
|
|
3862
3795
|
`ComfyUI workflow for style "${context.styleId}" could not be read at ${workflowPath}: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -3908,10 +3841,10 @@ var ComfyUIProvider = class _ComfyUIProvider {
|
|
|
3908
3841
|
if (typeof item !== "string") {
|
|
3909
3842
|
throw new Error(`ComfyUI ${label} must be a manifest-relative PNG or JPEG path`);
|
|
3910
3843
|
}
|
|
3911
|
-
const file =
|
|
3844
|
+
const file = path10.resolve(context.root, item);
|
|
3912
3845
|
let bytes;
|
|
3913
3846
|
try {
|
|
3914
|
-
bytes = await
|
|
3847
|
+
bytes = await readFile4(file);
|
|
3915
3848
|
} catch (error) {
|
|
3916
3849
|
throw new Error(
|
|
3917
3850
|
`ComfyUI ${label} could not be read at ${file}: ${error instanceof Error ? error.message : String(error)}`
|
|
@@ -4043,7 +3976,7 @@ var ComfyUIProvider = class _ComfyUIProvider {
|
|
|
4043
3976
|
setBinding(workflow2, binding, input);
|
|
4044
3977
|
return;
|
|
4045
3978
|
}
|
|
4046
|
-
const bytes = await
|
|
3979
|
+
const bytes = await readFile4(input.path);
|
|
4047
3980
|
if (sha256(bytes) !== input.sha256) {
|
|
4048
3981
|
throw new Error(`ComfyUI provider input "${name}" changed before upload`);
|
|
4049
3982
|
}
|
|
@@ -4110,7 +4043,7 @@ var ComfyUIProvider = class _ComfyUIProvider {
|
|
|
4110
4043
|
if (!spec.revision.sourceSha256 || !spec.revision.sourceFormat) {
|
|
4111
4044
|
throw new Error("ComfyUI revision source is not ready");
|
|
4112
4045
|
}
|
|
4113
|
-
const sourceBytes = await
|
|
4046
|
+
const sourceBytes = await readFile4(spec.revision.sourceFile);
|
|
4114
4047
|
if (sha256(sourceBytes) !== spec.revision.sourceSha256) {
|
|
4115
4048
|
throw new Error("ComfyUI revision source changed before upload");
|
|
4116
4049
|
}
|
|
@@ -4124,7 +4057,7 @@ var ComfyUIProvider = class _ComfyUIProvider {
|
|
|
4124
4057
|
if (!spec.revision.maskSha256 || !spec.revision.maskFormat) {
|
|
4125
4058
|
throw new Error("ComfyUI revision mask is not ready");
|
|
4126
4059
|
}
|
|
4127
|
-
const maskBytes = await
|
|
4060
|
+
const maskBytes = await readFile4(spec.revision.maskFile);
|
|
4128
4061
|
if (sha256(maskBytes) !== spec.revision.maskSha256) {
|
|
4129
4062
|
throw new Error("ComfyUI revision mask changed before upload");
|
|
4130
4063
|
}
|
|
@@ -4595,10 +4528,10 @@ function canonical2(value) {
|
|
|
4595
4528
|
}
|
|
4596
4529
|
|
|
4597
4530
|
// src/providers/pixellab.ts
|
|
4598
|
-
import { mkdirSync, existsSync as
|
|
4531
|
+
import { mkdirSync, existsSync as existsSync7, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
4599
4532
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
4600
4533
|
import os from "os";
|
|
4601
|
-
import
|
|
4534
|
+
import path11 from "path";
|
|
4602
4535
|
|
|
4603
4536
|
// src/client.ts
|
|
4604
4537
|
import { z as z2 } from "zod";
|
|
@@ -4950,7 +4883,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
4950
4883
|
* filename, polling is an existence check, and downloading is a file read.
|
|
4951
4884
|
*/
|
|
4952
4885
|
static cacheDir() {
|
|
4953
|
-
const dir =
|
|
4886
|
+
const dir = path11.join(os.tmpdir(), "pixelkiln-pixflux");
|
|
4954
4887
|
mkdirSync(dir, { recursive: true });
|
|
4955
4888
|
return dir;
|
|
4956
4889
|
}
|
|
@@ -5019,7 +4952,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5019
4952
|
seed: spec.seed
|
|
5020
4953
|
});
|
|
5021
4954
|
const jobId = randomUUID3();
|
|
5022
|
-
writeFileSync(
|
|
4955
|
+
writeFileSync(path11.join(_PixelLabProvider.cacheDir(), `${jobId}.png`), png);
|
|
5023
4956
|
return { jobId };
|
|
5024
4957
|
}
|
|
5025
4958
|
if (spec.generator === "tiles") {
|
|
@@ -5060,8 +4993,8 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5060
4993
|
}
|
|
5061
4994
|
async poll(jobId, generator, context) {
|
|
5062
4995
|
if (generator === "pixflux") {
|
|
5063
|
-
const file =
|
|
5064
|
-
if (
|
|
4996
|
+
const file = path11.join(_PixelLabProvider.cacheDir(), `${jobId}.png`);
|
|
4997
|
+
if (existsSync7(file)) {
|
|
5065
4998
|
const sourceUrl = `file://${file}`;
|
|
5066
4999
|
return { status: "ready", objectId: jobId, sourceUrl, sources: [{ url: sourceUrl }] };
|
|
5067
5000
|
}
|
|
@@ -6114,13 +6047,13 @@ registerProvider({
|
|
|
6114
6047
|
});
|
|
6115
6048
|
|
|
6116
6049
|
// src/manifest.ts
|
|
6117
|
-
import { readFile as
|
|
6118
|
-
import { existsSync as
|
|
6119
|
-
import
|
|
6050
|
+
import { readFile as readFile5 } from "fs/promises";
|
|
6051
|
+
import { existsSync as existsSync8 } from "fs";
|
|
6052
|
+
import path12 from "path";
|
|
6120
6053
|
async function loadManifest(manifestPath) {
|
|
6121
|
-
const abs =
|
|
6122
|
-
if (!
|
|
6123
|
-
const input = ManifestInputSchema.safeParse(JSON.parse(await
|
|
6054
|
+
const abs = path12.resolve(manifestPath);
|
|
6055
|
+
if (!existsSync8(abs)) throw new ProjectError(`No manifest at ${abs}`);
|
|
6056
|
+
const input = ManifestInputSchema.safeParse(JSON.parse(await readFile5(abs, "utf8")));
|
|
6124
6057
|
if (!input.success) {
|
|
6125
6058
|
const issues = formatManifestIssues(input.error.issues);
|
|
6126
6059
|
throw new ProjectError(`Manifest at ${abs} is invalid:
|
|
@@ -6191,7 +6124,7 @@ ${issues}`);
|
|
|
6191
6124
|
throw new ProjectError(`Manifest at ${abs} is invalid:
|
|
6192
6125
|
${unknownReferences.map((i) => ` ${i}`).join("\n")}`);
|
|
6193
6126
|
}
|
|
6194
|
-
return { manifest: parsed.data, root:
|
|
6127
|
+
return { manifest: parsed.data, root: path12.dirname(abs), path: abs };
|
|
6195
6128
|
}
|
|
6196
6129
|
function formatManifestIssues(issues) {
|
|
6197
6130
|
return issues.flatMap((issue) => {
|
|
@@ -6288,11 +6221,11 @@ async function resolveSpecs(loaded, filter) {
|
|
|
6288
6221
|
}
|
|
6289
6222
|
const styleImageCache = /* @__PURE__ */ new Map();
|
|
6290
6223
|
async function loadStyleImage(rel) {
|
|
6291
|
-
const abs =
|
|
6224
|
+
const abs = path12.resolve(root, rel);
|
|
6292
6225
|
let hit = styleImageCache.get(abs);
|
|
6293
6226
|
if (!hit) {
|
|
6294
|
-
if (!
|
|
6295
|
-
const buf = await
|
|
6227
|
+
if (!existsSync8(abs)) throw new Error(`Style image not found: ${abs}`);
|
|
6228
|
+
const buf = await readFile5(abs);
|
|
6296
6229
|
const metadata = imageMetadata(buf);
|
|
6297
6230
|
if (!metadata) throw new Error(`Style image is not a readable PNG or JPEG: ${abs}`);
|
|
6298
6231
|
if (metadata.width < 1 || metadata.height < 1) {
|
|
@@ -6318,7 +6251,7 @@ async function resolveSpecs(loaded, filter) {
|
|
|
6318
6251
|
styleImageDimensions.push(loadedImage);
|
|
6319
6252
|
}
|
|
6320
6253
|
const resolvedImages = style.styleImages.map((image) => {
|
|
6321
|
-
const hit = styleImageCache.get(
|
|
6254
|
+
const hit = styleImageCache.get(path12.resolve(root, image.path));
|
|
6322
6255
|
return { base64: hit.base64, width: hit.width, height: hit.height, format: hit.format };
|
|
6323
6256
|
});
|
|
6324
6257
|
const styleSpecs = /* @__PURE__ */ new Map();
|
|
@@ -6363,9 +6296,9 @@ async function resolveSpecs(loaded, filter) {
|
|
|
6363
6296
|
}
|
|
6364
6297
|
const subject = asset.promptByStyle[styleId] ?? asset.prompt;
|
|
6365
6298
|
const prompt = [style.promptPrefix, subject, style.promptSuffix].map((p) => p.trim()).filter(Boolean).join(", ");
|
|
6366
|
-
const relFile = asset.file ??
|
|
6367
|
-
const outFile =
|
|
6368
|
-
const qualityOutFile = style.quality ?
|
|
6299
|
+
const relFile = asset.file ?? path12.join(asset.category ?? "", `${assetId}.png`);
|
|
6300
|
+
const outFile = path12.resolve(root, style.outDir, relFile);
|
|
6301
|
+
const qualityOutFile = style.quality ? path12.resolve(root, style.quality.outDir, pngPath(relFile)) : void 0;
|
|
6369
6302
|
const tileSize = generator === "tiles" ? size : style.tileSize ?? 32;
|
|
6370
6303
|
const tileVariations = tileFeatureOutputCount(generator === "tiles" ? style.tileFeature : void 0) ?? tileVariationCount(countNumberedDescriptions(prompt));
|
|
6371
6304
|
const base = {
|
|
@@ -6386,6 +6319,7 @@ async function resolveSpecs(loaded, filter) {
|
|
|
6386
6319
|
detail: style.detail,
|
|
6387
6320
|
seed: style.seed,
|
|
6388
6321
|
palette: style.palette,
|
|
6322
|
+
enforcePalette: style.enforcePalette,
|
|
6389
6323
|
noBackground: style.noBackground,
|
|
6390
6324
|
tileSize: generator === "tiles" ? tileSize : void 0,
|
|
6391
6325
|
tileType: generator === "tiles" ? style.tileType : void 0,
|
|
@@ -6416,7 +6350,7 @@ async function resolveSpecs(loaded, filter) {
|
|
|
6416
6350
|
minGridConfidence: style.quality.minGridConfidence,
|
|
6417
6351
|
...style.quality.minTransparency == null ? {} : { minTransparency: style.quality.minTransparency },
|
|
6418
6352
|
...style.quality.fixerRevision ? { fixerRevision: style.quality.fixerRevision } : {},
|
|
6419
|
-
...style.quality.fixerPython ? { fixerPython:
|
|
6353
|
+
...style.quality.fixerPython ? { fixerPython: path12.resolve(root, style.quality.fixerPython) } : {},
|
|
6420
6354
|
...style.quality.fps == null ? {} : { fps: style.quality.fps }
|
|
6421
6355
|
}
|
|
6422
6356
|
} : {},
|
|
@@ -6445,9 +6379,9 @@ async function resolveSpecs(loaded, filter) {
|
|
|
6445
6379
|
);
|
|
6446
6380
|
}
|
|
6447
6381
|
const sourceSpec = await finalize(asset.revision.from);
|
|
6448
|
-
const sourceFile = sourceSpec.quality?.outFile ?? (sourceSpec.source ?
|
|
6382
|
+
const sourceFile = sourceSpec.quality?.outFile ?? (sourceSpec.source ? path12.resolve(root, sourceSpec.source) : sourceSpec.outFile);
|
|
6449
6383
|
const sourceImage = await optionalRevisionImage(sourceFile, "revision source");
|
|
6450
|
-
const maskFile = asset.revision.mask ?
|
|
6384
|
+
const maskFile = asset.revision.mask ? path12.resolve(root, asset.revision.mask) : void 0;
|
|
6451
6385
|
const maskImage = maskFile ? await optionalRevisionImage(maskFile, "revision mask", "png") : null;
|
|
6452
6386
|
if (sourceImage && maskImage && (sourceImage.width !== maskImage.width || sourceImage.height !== maskImage.height)) {
|
|
6453
6387
|
throw new Error(
|
|
@@ -6528,12 +6462,12 @@ async function resolveSpecs(loaded, filter) {
|
|
|
6528
6462
|
return specs;
|
|
6529
6463
|
}
|
|
6530
6464
|
function pngPath(file) {
|
|
6531
|
-
const extension =
|
|
6465
|
+
const extension = path12.extname(file);
|
|
6532
6466
|
return extension ? `${file.slice(0, -extension.length)}.png` : `${file}.png`;
|
|
6533
6467
|
}
|
|
6534
6468
|
async function optionalRevisionImage(file, label, requiredFormat) {
|
|
6535
|
-
if (!
|
|
6536
|
-
const bytes = await
|
|
6469
|
+
if (!existsSync8(file)) return null;
|
|
6470
|
+
const bytes = await readFile5(file);
|
|
6537
6471
|
const metadata = imageMetadata(bytes);
|
|
6538
6472
|
if (!metadata) throw new Error(`${label} is not a readable PNG or JPEG: ${file}`);
|
|
6539
6473
|
if (requiredFormat && metadata.format !== requiredFormat) {
|
|
@@ -6546,8 +6480,8 @@ async function resolveStyleImages(loaded, styleId) {
|
|
|
6546
6480
|
if (!style) throw new Error(`Unknown style "${styleId}"`);
|
|
6547
6481
|
const out = [];
|
|
6548
6482
|
for (const img of style.styleImages) {
|
|
6549
|
-
const file =
|
|
6550
|
-
const buf = await
|
|
6483
|
+
const file = path12.resolve(loaded.root, img.path);
|
|
6484
|
+
const buf = await readFile5(file);
|
|
6551
6485
|
const metadata = imageMetadata(buf);
|
|
6552
6486
|
if (!metadata) throw new Error(`Style image is not a readable PNG or JPEG: ${file}`);
|
|
6553
6487
|
if (metadata.width < 1 || metadata.height < 1) {
|
|
@@ -6587,9 +6521,9 @@ function imageMetadata(buf) {
|
|
|
6587
6521
|
}
|
|
6588
6522
|
|
|
6589
6523
|
// src/pipeline/fetch.ts
|
|
6590
|
-
import { existsSync as
|
|
6591
|
-
import { mkdir as mkdir3, readFile as
|
|
6592
|
-
import
|
|
6524
|
+
import { existsSync as existsSync9, readFileSync as readFileSync3 } from "fs";
|
|
6525
|
+
import { mkdir as mkdir3, readFile as readFile6, rename as rename3, rm as rm4, writeFile as writeFile3 } from "fs/promises";
|
|
6526
|
+
import path13 from "path";
|
|
6593
6527
|
|
|
6594
6528
|
// src/source-url.ts
|
|
6595
6529
|
var SENSITIVE_QUERY_KEYS = /* @__PURE__ */ new Set([
|
|
@@ -6635,18 +6569,24 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
6635
6569
|
const result = { downloaded: 0, skipped: 0, failed: 0, ...opts.refresh ? { unchanged: 0 } : {} };
|
|
6636
6570
|
const specByKey = new Map(specs.map((s) => [lockKey(s.styleId, s.assetId), s]));
|
|
6637
6571
|
normalizeLockOutputPaths(lock, specs);
|
|
6638
|
-
const cacheDir = opts.cacheDir === false ? null :
|
|
6572
|
+
const cacheDir = opts.cacheDir === false ? null : path13.resolve(opts.cacheDir ?? path13.join(path13.dirname(lockPath), ".pixelkiln", "cache"));
|
|
6639
6573
|
const pending = Object.entries(lock.entries).filter(([key, e]) => {
|
|
6640
6574
|
if (e.provider !== provider.id || !specByKey.has(key)) return false;
|
|
6641
6575
|
if (e.status === "selected" || e.status === "download-failed") return true;
|
|
6642
6576
|
if (e.status !== "downloaded") return false;
|
|
6643
6577
|
if (opts.refresh) return e.outputs.length > 0 && Boolean(e.sourceUrls?.length || e.sourceUrl);
|
|
6644
|
-
if (!opts.repair) return false;
|
|
6645
6578
|
const spec = specByKey.get(key);
|
|
6579
|
+
if (spec && e.specHash === spec.specHash && e.outputs.length && !postprocessCurrent(e, spec)) {
|
|
6580
|
+
return Boolean(opts.force) || e.outputs.every((output) => {
|
|
6581
|
+
const file = resolveOutputPath(output.path, spec.root);
|
|
6582
|
+
return existsSync9(file) && sha256(readFileSync3(file)) === output.sha256;
|
|
6583
|
+
});
|
|
6584
|
+
}
|
|
6585
|
+
if (!opts.repair) return false;
|
|
6646
6586
|
if (e.outputs.length === 0 || !spec) return true;
|
|
6647
6587
|
return e.outputs.some((output) => {
|
|
6648
6588
|
const file = resolveOutputPath(output.path, spec.root);
|
|
6649
|
-
if (!
|
|
6589
|
+
if (!existsSync9(file)) return true;
|
|
6650
6590
|
const current = sha256(readFileSync3(file));
|
|
6651
6591
|
if (current === output.sha256) return false;
|
|
6652
6592
|
return Boolean(opts.force) || (e.supersededOutputs ?? []).some((old) => old.sha256 === current);
|
|
@@ -6681,6 +6621,8 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
6681
6621
|
}
|
|
6682
6622
|
const outputs = [];
|
|
6683
6623
|
let wrote = false;
|
|
6624
|
+
const wanted = postprocessFor(spec);
|
|
6625
|
+
const reapply = entry.status === "downloaded" && !postprocessCurrent(entry, spec);
|
|
6684
6626
|
try {
|
|
6685
6627
|
for (let index = 0; index < sources.length; index++) {
|
|
6686
6628
|
const source = sources[index];
|
|
@@ -6688,24 +6630,28 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
6688
6630
|
(o) => source.role ? o.role === source.role : !o.role && sources.length === 1
|
|
6689
6631
|
);
|
|
6690
6632
|
const target = recorded ? resolveOutputPath(recorded.path, spec.root) : expectedOutputPath(spec, source.role, index, sources.length, source.mediaType);
|
|
6633
|
+
const expectedMediaType = source.mediaType ?? recorded?.mediaType ?? MediaType.PNG;
|
|
6634
|
+
const recordedRaw = recorded?.raw ?? recorded?.sha256;
|
|
6691
6635
|
let fresh = null;
|
|
6692
6636
|
if (opts.refresh) {
|
|
6693
6637
|
if (!source.url) throw new Error(`no durable source URL to refresh ${source.role ?? "asset"} from`);
|
|
6694
6638
|
fresh = await provider.download(source.url);
|
|
6695
|
-
if (recorded && sha256(fresh) ===
|
|
6696
|
-
log2(` current ${
|
|
6639
|
+
if (recorded && sha256(fresh) === recordedRaw && !reapply) {
|
|
6640
|
+
log2(` current ${path13.relative(process.cwd(), target)}`);
|
|
6697
6641
|
outputs.push({ ...recorded, path: portableOutputPath(target, spec.root) });
|
|
6698
6642
|
continue;
|
|
6699
6643
|
}
|
|
6700
6644
|
}
|
|
6701
|
-
|
|
6645
|
+
let raw = null;
|
|
6646
|
+
let final = null;
|
|
6647
|
+
if (existsSync9(target)) {
|
|
6702
6648
|
const currentHash = await sha256File(target);
|
|
6703
6649
|
if (recorded && currentHash === recorded.sha256) {
|
|
6704
|
-
if (!fresh) {
|
|
6650
|
+
if (!fresh && !reapply) {
|
|
6705
6651
|
if (cacheDir) {
|
|
6706
6652
|
await cacheMedia(
|
|
6707
6653
|
cacheDir,
|
|
6708
|
-
await
|
|
6654
|
+
await readFile6(target),
|
|
6709
6655
|
recorded.mediaType ?? MediaType.PNG,
|
|
6710
6656
|
recorded.sha256
|
|
6711
6657
|
);
|
|
@@ -6713,7 +6659,12 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
6713
6659
|
outputs.push({ ...recorded, path: portableOutputPath(target, spec.root) });
|
|
6714
6660
|
continue;
|
|
6715
6661
|
}
|
|
6716
|
-
|
|
6662
|
+
if (reapply && !fresh) {
|
|
6663
|
+
if (!recorded.raw) raw = await readFile6(target);
|
|
6664
|
+
log2(` reapply ${path13.relative(process.cwd(), target)}`);
|
|
6665
|
+
} else {
|
|
6666
|
+
log2(` changed ${path13.relative(process.cwd(), target)} (upstream)`);
|
|
6667
|
+
}
|
|
6717
6668
|
} else {
|
|
6718
6669
|
const superseded = (entry.supersededOutputs ?? []).find(
|
|
6719
6670
|
(output, oldIndex, all) => currentOutputPath(output, spec, oldIndex, all.length) === target
|
|
@@ -6731,45 +6682,61 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
6731
6682
|
}
|
|
6732
6683
|
}
|
|
6733
6684
|
log2(
|
|
6734
|
-
` replace ${
|
|
6685
|
+
` replace ${path13.relative(process.cwd(), target)}` + (opts.force ? " (--force)" : " (previous tracked generation)")
|
|
6735
6686
|
);
|
|
6736
6687
|
}
|
|
6737
6688
|
}
|
|
6738
|
-
|
|
6739
|
-
|
|
6740
|
-
if (
|
|
6741
|
-
|
|
6742
|
-
|
|
6689
|
+
if (fresh) {
|
|
6690
|
+
raw = fresh;
|
|
6691
|
+
} else if (!raw && recorded && cacheDir) {
|
|
6692
|
+
if (!reapply) final = await readCachedMedia(cacheDir, recorded.sha256, expectedMediaType);
|
|
6693
|
+
if (!final && recordedRaw) raw = await readCachedMedia(cacheDir, recordedRaw, expectedMediaType);
|
|
6694
|
+
if (final || raw) log2(` cached ${path13.relative(process.cwd(), target)}`);
|
|
6695
|
+
}
|
|
6696
|
+
if (!final && !raw) {
|
|
6743
6697
|
if (!source.url) {
|
|
6744
6698
|
throw new Error(`no source URL or cached bytes remain for ${source.role ?? "asset"}`);
|
|
6745
6699
|
}
|
|
6746
|
-
|
|
6700
|
+
raw = await provider.download(source.url);
|
|
6747
6701
|
}
|
|
6748
6702
|
let mediaType;
|
|
6703
|
+
const bytes = final ?? raw;
|
|
6749
6704
|
try {
|
|
6750
|
-
mediaType = validateMedia(
|
|
6705
|
+
mediaType = validateMedia(bytes, expectedMediaType);
|
|
6751
6706
|
} catch (err) {
|
|
6752
6707
|
const label = expectedMediaType === MediaType.GIF ? "GIF" : "PNG";
|
|
6753
|
-
const mismatch = detectMediaType(
|
|
6708
|
+
const mismatch = detectMediaType(bytes) !== expectedMediaType;
|
|
6754
6709
|
throw new Error(
|
|
6755
|
-
`response for ${source.role ?? "asset"} was not ${mismatch ? "a" : "a valid"} ${label}` + (mismatch ? ` (${
|
|
6710
|
+
`response for ${source.role ?? "asset"} was not ${mismatch ? "a" : "a valid"} ${label}` + (mismatch ? ` (${bytes.length} bytes)` : `: ${err instanceof Error ? err.message : String(err)}`)
|
|
6756
6711
|
);
|
|
6757
6712
|
}
|
|
6713
|
+
let rawHash = final ? recorded?.raw : void 0;
|
|
6714
|
+
let buf = bytes;
|
|
6715
|
+
if (!final) {
|
|
6716
|
+
if (cacheDir) await cacheMedia(cacheDir, raw, mediaType);
|
|
6717
|
+
const processed = applyPostprocess(raw, mediaType, wanted);
|
|
6718
|
+
buf = processed.bytes;
|
|
6719
|
+
if (processed.changed) {
|
|
6720
|
+
rawHash = sha256(raw);
|
|
6721
|
+
log2(` snapped ${path13.relative(process.cwd(), target)} to ${wanted.palette.colors.length} colours`);
|
|
6722
|
+
}
|
|
6723
|
+
}
|
|
6758
6724
|
if (cacheDir) await cacheMedia(cacheDir, buf, mediaType);
|
|
6759
|
-
await mkdir3(
|
|
6725
|
+
await mkdir3(path13.dirname(target), { recursive: true });
|
|
6760
6726
|
const tmp = `${target}.pixelkiln.tmp`;
|
|
6761
6727
|
await writeFile3(tmp, buf);
|
|
6762
6728
|
outputs.push({
|
|
6763
6729
|
path: portableOutputPath(target, spec.root),
|
|
6764
6730
|
sha256: sha256(buf),
|
|
6765
6731
|
...source.role ? { role: source.role } : {},
|
|
6766
|
-
mediaType
|
|
6732
|
+
mediaType,
|
|
6733
|
+
...rawHash ? { raw: rawHash } : {}
|
|
6767
6734
|
});
|
|
6768
6735
|
upsert(lock, key, { outputs: mergeOutputs(entry.outputs, outputs) });
|
|
6769
6736
|
await saveLock(lockPath, lock);
|
|
6770
6737
|
await rename3(tmp, target);
|
|
6771
6738
|
wrote = true;
|
|
6772
|
-
log2(` wrote ${
|
|
6739
|
+
log2(` wrote ${path13.relative(process.cwd(), target)}`);
|
|
6773
6740
|
}
|
|
6774
6741
|
if (opts.refresh && !wrote) {
|
|
6775
6742
|
result.unchanged = (result.unchanged ?? 0) + 1;
|
|
@@ -6787,7 +6754,8 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
6787
6754
|
sourceUrls: persistentSources,
|
|
6788
6755
|
downloadedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6789
6756
|
error: null,
|
|
6790
|
-
supersededOutputs: []
|
|
6757
|
+
supersededOutputs: [],
|
|
6758
|
+
postprocess: wanted
|
|
6791
6759
|
});
|
|
6792
6760
|
result.downloaded++;
|
|
6793
6761
|
} catch (err) {
|
|
@@ -6808,10 +6776,10 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
6808
6776
|
return result;
|
|
6809
6777
|
}
|
|
6810
6778
|
async function readCachedMedia(cacheDir, hash, mediaType) {
|
|
6811
|
-
const file =
|
|
6812
|
-
if (!
|
|
6779
|
+
const file = path13.join(cacheDir, cacheFileName(hash, mediaType));
|
|
6780
|
+
if (!existsSync9(file)) return null;
|
|
6813
6781
|
try {
|
|
6814
|
-
const buf = await
|
|
6782
|
+
const buf = await readFile6(file);
|
|
6815
6783
|
if (sha256(buf) !== hash) return null;
|
|
6816
6784
|
validateMedia(buf, mediaType);
|
|
6817
6785
|
return buf;
|
|
@@ -6822,7 +6790,7 @@ async function readCachedMedia(cacheDir, hash, mediaType) {
|
|
|
6822
6790
|
async function cacheMedia(cacheDir, buf, mediaType, knownHash) {
|
|
6823
6791
|
const hash = knownHash ?? sha256(buf);
|
|
6824
6792
|
validateMedia(buf, mediaType);
|
|
6825
|
-
const file =
|
|
6793
|
+
const file = path13.join(cacheDir, cacheFileName(hash, mediaType));
|
|
6826
6794
|
if (await readCachedMedia(cacheDir, hash, mediaType)) return;
|
|
6827
6795
|
await mkdir3(cacheDir, { recursive: true });
|
|
6828
6796
|
const tmp = `${file}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
@@ -6830,7 +6798,7 @@ async function cacheMedia(cacheDir, buf, mediaType, knownHash) {
|
|
|
6830
6798
|
try {
|
|
6831
6799
|
await rename3(tmp, file);
|
|
6832
6800
|
} catch (err) {
|
|
6833
|
-
if (!
|
|
6801
|
+
if (!existsSync9(file)) throw err;
|
|
6834
6802
|
} finally {
|
|
6835
6803
|
await rm4(tmp, { force: true });
|
|
6836
6804
|
}
|
|
@@ -6863,13 +6831,13 @@ async function pushTags(provider, specs, lock, opts = {}) {
|
|
|
6863
6831
|
}
|
|
6864
6832
|
|
|
6865
6833
|
// src/pipeline/adopt.ts
|
|
6866
|
-
import { existsSync as
|
|
6867
|
-
import { readFile as
|
|
6868
|
-
import
|
|
6834
|
+
import { existsSync as existsSync11 } from "fs";
|
|
6835
|
+
import { readFile as readFile8, writeFile as writeFile5 } from "fs/promises";
|
|
6836
|
+
import path14 from "path";
|
|
6869
6837
|
|
|
6870
6838
|
// src/cache.ts
|
|
6871
|
-
import { mkdir as mkdir4, readFile as
|
|
6872
|
-
import { existsSync as
|
|
6839
|
+
import { mkdir as mkdir4, readFile as readFile7, rename as rename4, rm as rm5, writeFile as writeFile4 } from "fs/promises";
|
|
6840
|
+
import { existsSync as existsSync10 } from "fs";
|
|
6873
6841
|
import pathModule from "path";
|
|
6874
6842
|
import { z as z3 } from "zod";
|
|
6875
6843
|
var HashCacheSchema = z3.object({
|
|
@@ -6885,9 +6853,9 @@ function parseCache(value) {
|
|
|
6885
6853
|
return HashCacheSchema.parse(value);
|
|
6886
6854
|
}
|
|
6887
6855
|
async function loadCache(path43) {
|
|
6888
|
-
if (!
|
|
6856
|
+
if (!existsSync10(path43)) return { version: 1, hashes: {} };
|
|
6889
6857
|
try {
|
|
6890
|
-
const parsed = HashCacheSchema.safeParse(JSON.parse(await
|
|
6858
|
+
const parsed = HashCacheSchema.safeParse(JSON.parse(await readFile7(path43, "utf8")));
|
|
6891
6859
|
if (!parsed.success) return { version: 1, hashes: {} };
|
|
6892
6860
|
return {
|
|
6893
6861
|
version: 1,
|
|
@@ -6941,11 +6909,11 @@ async function adopt(provider, specs, lock, lockPath, opts = {}) {
|
|
|
6941
6909
|
const localByHash = /* @__PURE__ */ new Map();
|
|
6942
6910
|
const unmatchedLocal = [];
|
|
6943
6911
|
for (const spec of specs) {
|
|
6944
|
-
if (!
|
|
6912
|
+
if (!existsSync11(spec.outFile)) {
|
|
6945
6913
|
unmatchedLocal.push(`${spec.styleId}/${spec.assetId} (no file at ${spec.outFile})`);
|
|
6946
6914
|
continue;
|
|
6947
6915
|
}
|
|
6948
|
-
const bytes = await
|
|
6916
|
+
const bytes = await readFile8(spec.outFile);
|
|
6949
6917
|
try {
|
|
6950
6918
|
decodePng(bytes);
|
|
6951
6919
|
} catch (err) {
|
|
@@ -7065,7 +7033,7 @@ async function writePromptsBack(manifestPath, lock, opts = {}) {
|
|
|
7065
7033
|
const log2 = opts.onProgress ?? (() => {
|
|
7066
7034
|
});
|
|
7067
7035
|
const selectedAssets = opts.assetIds ? new Set(opts.assetIds) : null;
|
|
7068
|
-
const raw = JSON.parse(await
|
|
7036
|
+
const raw = JSON.parse(await readFile8(manifestPath, "utf8"));
|
|
7069
7037
|
const best = /* @__PURE__ */ new Map();
|
|
7070
7038
|
for (const entry of Object.values(lock.entries)) {
|
|
7071
7039
|
if (opts.provider && entry.provider !== opts.provider) continue;
|
|
@@ -7098,19 +7066,19 @@ function formatUnmatchedRemote(objects, limit = 15) {
|
|
|
7098
7066
|
}
|
|
7099
7067
|
|
|
7100
7068
|
// src/pipeline/salvage.ts
|
|
7101
|
-
import { readFile as
|
|
7102
|
-
import { existsSync as
|
|
7103
|
-
import
|
|
7069
|
+
import { readFile as readFile9 } from "fs/promises";
|
|
7070
|
+
import { existsSync as existsSync12 } from "fs";
|
|
7071
|
+
import path15 from "path";
|
|
7104
7072
|
function providerClaimId(provider, objectId) {
|
|
7105
7073
|
return `${encodeURIComponent(provider)}:${encodeURIComponent(objectId)}`;
|
|
7106
7074
|
}
|
|
7107
7075
|
async function loadClaims(lockPaths, opts = {}) {
|
|
7108
7076
|
const claimed = /* @__PURE__ */ new Set();
|
|
7109
7077
|
for (const p of lockPaths) {
|
|
7110
|
-
if (!
|
|
7078
|
+
if (!existsSync12(p)) throw new Error(`Claim lockfile not found: ${p}`);
|
|
7111
7079
|
let parsed;
|
|
7112
7080
|
try {
|
|
7113
|
-
parsed = parseLock(JSON.parse(await
|
|
7081
|
+
parsed = parseLock(JSON.parse(await readFile9(p, "utf8")));
|
|
7114
7082
|
} catch {
|
|
7115
7083
|
throw new Error(`Claim lockfile is malformed: ${p}`);
|
|
7116
7084
|
}
|
|
@@ -7158,20 +7126,20 @@ function matchStyleByPattern(prompt, manifest) {
|
|
|
7158
7126
|
return null;
|
|
7159
7127
|
}
|
|
7160
7128
|
async function loadSiblingManifests(ownManifestPath, workspaceManifestPaths, claimPaths) {
|
|
7161
|
-
const own =
|
|
7129
|
+
const own = path15.resolve(ownManifestPath);
|
|
7162
7130
|
const siblingManifestPaths = [
|
|
7163
7131
|
.../* @__PURE__ */ new Set([
|
|
7164
7132
|
...workspaceManifestPaths,
|
|
7165
|
-
...claimPaths.map((c) =>
|
|
7133
|
+
...claimPaths.map((c) => path15.join(path15.dirname(path15.resolve(c)), "pixelkiln.manifest.json"))
|
|
7166
7134
|
])
|
|
7167
7135
|
];
|
|
7168
7136
|
const siblings = [];
|
|
7169
7137
|
for (const siblingManifestPath of siblingManifestPaths) {
|
|
7170
|
-
if (
|
|
7171
|
-
if (!
|
|
7138
|
+
if (path15.resolve(siblingManifestPath) === own) continue;
|
|
7139
|
+
if (!existsSync12(siblingManifestPath)) continue;
|
|
7172
7140
|
try {
|
|
7173
7141
|
const { manifest } = await loadManifest(siblingManifestPath);
|
|
7174
|
-
siblings.push({ label:
|
|
7142
|
+
siblings.push({ label: path15.basename(path15.dirname(siblingManifestPath)), manifest });
|
|
7175
7143
|
} catch {
|
|
7176
7144
|
}
|
|
7177
7145
|
}
|
|
@@ -7287,9 +7255,9 @@ async function applyTags(provider, decisions, existing, opts = {}) {
|
|
|
7287
7255
|
}
|
|
7288
7256
|
|
|
7289
7257
|
// src/workspace.ts
|
|
7290
|
-
import { mkdir as mkdir5, readFile as
|
|
7291
|
-
import { existsSync as
|
|
7292
|
-
import
|
|
7258
|
+
import { mkdir as mkdir5, readFile as readFile10, rename as rename5, rm as rm6, writeFile as writeFile6 } from "fs/promises";
|
|
7259
|
+
import { existsSync as existsSync13 } from "fs";
|
|
7260
|
+
import path16 from "path";
|
|
7293
7261
|
import { z as z4 } from "zod";
|
|
7294
7262
|
var WorkspaceProjectSchema = z4.object({
|
|
7295
7263
|
id: z4.string().min(1),
|
|
@@ -7314,10 +7282,10 @@ ${parsed.error.issues.slice(0, 5).map((i) => ` ${i.path.join(".")}: ${i.message
|
|
|
7314
7282
|
);
|
|
7315
7283
|
}
|
|
7316
7284
|
async function loadWorkspace(workspacePath) {
|
|
7317
|
-
if (!
|
|
7285
|
+
if (!existsSync13(workspacePath)) return { version: 1, projects: [] };
|
|
7318
7286
|
let raw;
|
|
7319
7287
|
try {
|
|
7320
|
-
raw = JSON.parse(await
|
|
7288
|
+
raw = JSON.parse(await readFile10(workspacePath, "utf8"));
|
|
7321
7289
|
} catch (err) {
|
|
7322
7290
|
throw new ProjectError(
|
|
7323
7291
|
`Workspace catalog at ${workspacePath} is malformed:
|
|
@@ -7331,7 +7299,7 @@ async function saveWorkspace(workspacePath, ws) {
|
|
|
7331
7299
|
version: 1,
|
|
7332
7300
|
projects: [...ws.projects].sort((a, b) => a.id.localeCompare(b.id))
|
|
7333
7301
|
};
|
|
7334
|
-
await mkdir5(
|
|
7302
|
+
await mkdir5(path16.dirname(path16.resolve(workspacePath)), { recursive: true });
|
|
7335
7303
|
const tmp = `${workspacePath}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
7336
7304
|
try {
|
|
7337
7305
|
await writeFile6(tmp, JSON.stringify(sorted, null, 2) + "\n");
|
|
@@ -7341,12 +7309,12 @@ async function saveWorkspace(workspacePath, ws) {
|
|
|
7341
7309
|
}
|
|
7342
7310
|
}
|
|
7343
7311
|
function toPortablePath(dir, absolute) {
|
|
7344
|
-
return
|
|
7312
|
+
return path16.relative(dir, absolute).split(path16.sep).join("/");
|
|
7345
7313
|
}
|
|
7346
7314
|
function resolveProject(dir, project) {
|
|
7347
7315
|
return {
|
|
7348
|
-
manifestPath:
|
|
7349
|
-
lockPath:
|
|
7316
|
+
manifestPath: path16.resolve(dir, project.manifest.split("/").join(path16.sep)),
|
|
7317
|
+
lockPath: path16.resolve(dir, project.lock.split("/").join(path16.sep))
|
|
7350
7318
|
};
|
|
7351
7319
|
}
|
|
7352
7320
|
function validateWorkspace(ws, dir) {
|
|
@@ -7359,21 +7327,21 @@ function validateWorkspace(ws, dir) {
|
|
|
7359
7327
|
const { manifestPath, lockPath } = resolveProject(dir, project);
|
|
7360
7328
|
lockOwners.set(lockPath, [...lockOwners.get(lockPath) ?? [], project.id]);
|
|
7361
7329
|
manifestOwners.set(manifestPath, [...manifestOwners.get(manifestPath) ?? [], project.id]);
|
|
7362
|
-
if (
|
|
7330
|
+
if (path16.isAbsolute(project.manifest) || path16.isAbsolute(project.lock)) {
|
|
7363
7331
|
diagnostics.push({
|
|
7364
7332
|
id: "absolute-path",
|
|
7365
7333
|
level: "warning",
|
|
7366
7334
|
message: `project "${project.id}" stores an absolute path \u2014 the catalog will not resolve correctly if this tree is cloned or moved elsewhere`
|
|
7367
7335
|
});
|
|
7368
7336
|
}
|
|
7369
|
-
if (!
|
|
7337
|
+
if (!existsSync13(manifestPath)) {
|
|
7370
7338
|
diagnostics.push({
|
|
7371
7339
|
id: "missing-manifest",
|
|
7372
7340
|
level: "error",
|
|
7373
7341
|
message: `project "${project.id}" manifest not found: ${manifestPath}`
|
|
7374
7342
|
});
|
|
7375
7343
|
}
|
|
7376
|
-
if (!
|
|
7344
|
+
if (!existsSync13(lockPath)) {
|
|
7377
7345
|
diagnostics.push({
|
|
7378
7346
|
id: "missing-lock",
|
|
7379
7347
|
level: "error",
|
|
@@ -7420,8 +7388,8 @@ function validateWorkspace(ws, dir) {
|
|
|
7420
7388
|
}
|
|
7421
7389
|
|
|
7422
7390
|
// src/pick/salvage-server.ts
|
|
7423
|
-
import { mkdir as mkdir6, writeFile as writeFile7, readFile as
|
|
7424
|
-
import
|
|
7391
|
+
import { mkdir as mkdir6, writeFile as writeFile7, readFile as readFile12 } from "fs/promises";
|
|
7392
|
+
import path17 from "path";
|
|
7425
7393
|
|
|
7426
7394
|
// src/pick/salvage-sheet.ts
|
|
7427
7395
|
var escapeHtml = (s) => s.replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
@@ -7581,7 +7549,7 @@ refresh();
|
|
|
7581
7549
|
|
|
7582
7550
|
// src/pick/review-server.ts
|
|
7583
7551
|
import { createServer } from "http";
|
|
7584
|
-
import { readFile as
|
|
7552
|
+
import { readFile as readFile11 } from "fs/promises";
|
|
7585
7553
|
|
|
7586
7554
|
// src/open.ts
|
|
7587
7555
|
import { spawn } from "child_process";
|
|
@@ -7608,7 +7576,7 @@ function serveReviewPage(opts) {
|
|
|
7608
7576
|
if (req.method === "GET" && req.url && opts.assets?.has(req.url)) {
|
|
7609
7577
|
try {
|
|
7610
7578
|
const asset = opts.assets.get(req.url);
|
|
7611
|
-
const bytes = await
|
|
7579
|
+
const bytes = await readFile11(asset.path);
|
|
7612
7580
|
res.writeHead(200, {
|
|
7613
7581
|
"Content-Type": asset.contentType,
|
|
7614
7582
|
"Cache-Control": "no-store",
|
|
@@ -7685,7 +7653,7 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
7685
7653
|
});
|
|
7686
7654
|
const html = renderSalvageSheet(orphans, {
|
|
7687
7655
|
styleId: ctx.styleId,
|
|
7688
|
-
importDir:
|
|
7656
|
+
importDir: path17.relative(process.cwd(), ctx.importDir) || "."
|
|
7689
7657
|
});
|
|
7690
7658
|
const byId = new Map(orphans.map((o) => [o.id, o]));
|
|
7691
7659
|
const existingTags = new Map(orphans.map((o) => [o.id, o.tags]));
|
|
@@ -7714,9 +7682,9 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
7714
7682
|
if (!buf.subarray(0, 8).equals(PNG_SIGNATURE2)) throw new Error("not a PNG");
|
|
7715
7683
|
decodePng(buf);
|
|
7716
7684
|
const assetId = idFromPrompt(orphan.prompt, taken);
|
|
7717
|
-
const rel =
|
|
7718
|
-
const outFile =
|
|
7719
|
-
await mkdir6(
|
|
7685
|
+
const rel = path17.join("_salvaged", `${assetId}.png`);
|
|
7686
|
+
const outFile = path17.resolve(ctx.importDir, rel);
|
|
7687
|
+
await mkdir6(path17.dirname(outFile), { recursive: true });
|
|
7720
7688
|
await writeFile7(outFile, buf);
|
|
7721
7689
|
ctx.manifest.assets[assetId] = {
|
|
7722
7690
|
prompt: orphan.prompt,
|
|
@@ -7749,7 +7717,7 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
7749
7717
|
sourceUrl: durableSource,
|
|
7750
7718
|
sourceUrls: durableSource ? [{ url: durableSource }] : [],
|
|
7751
7719
|
outputs: [{
|
|
7752
|
-
path: portableOutputPath(outFile,
|
|
7720
|
+
path: portableOutputPath(outFile, path17.dirname(ctx.manifestPath)),
|
|
7753
7721
|
sha256: sha256(buf)
|
|
7754
7722
|
}],
|
|
7755
7723
|
submittedAt: orphan.createdAt,
|
|
@@ -7773,7 +7741,7 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
7773
7741
|
}
|
|
7774
7742
|
}
|
|
7775
7743
|
await applyTags(provider, decisions, existingTags, { onProgress: log2 });
|
|
7776
|
-
const raw = JSON.parse(await
|
|
7744
|
+
const raw = JSON.parse(await readFile12(ctx.manifestPath, "utf8"));
|
|
7777
7745
|
for (const id of importedAssetIds) raw.assets[id] = ctx.manifest.assets[id];
|
|
7778
7746
|
await writeFile7(ctx.manifestPath, JSON.stringify(raw, null, 2) + "\n");
|
|
7779
7747
|
await saveLock(ctx.lockPath, ctx.lock);
|
|
@@ -7783,19 +7751,19 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
7783
7751
|
}
|
|
7784
7752
|
|
|
7785
7753
|
// src/cli/project.ts
|
|
7786
|
-
import
|
|
7787
|
-
import { existsSync as
|
|
7754
|
+
import path19 from "path";
|
|
7755
|
+
import { existsSync as existsSync14 } from "fs";
|
|
7788
7756
|
|
|
7789
7757
|
// src/project.ts
|
|
7790
|
-
import
|
|
7758
|
+
import path18 from "path";
|
|
7791
7759
|
async function openProject(manifestPath = "pixelkiln.manifest.json", opts = {}) {
|
|
7792
|
-
const absolute =
|
|
7793
|
-
const root =
|
|
7760
|
+
const absolute = path18.resolve(manifestPath);
|
|
7761
|
+
const root = path18.dirname(absolute);
|
|
7794
7762
|
if (opts.env !== false) {
|
|
7795
7763
|
loadEnvFiles(root);
|
|
7796
|
-
if (
|
|
7764
|
+
if (path18.resolve(process.cwd()) !== root) loadEnvFiles(process.cwd());
|
|
7797
7765
|
}
|
|
7798
|
-
const lockPath =
|
|
7766
|
+
const lockPath = path18.resolve(opts.lockPath ?? path18.join(root, "pixelkiln.lock.json"));
|
|
7799
7767
|
const loaded = await loadManifest(absolute);
|
|
7800
7768
|
const specs = await resolveSpecs(loaded, { styles: opts.styles ?? [], assets: opts.assets ?? [] });
|
|
7801
7769
|
const lock = await loadLock(lockPath);
|
|
@@ -7926,11 +7894,11 @@ async function workspaceStatus(ws, dir) {
|
|
|
7926
7894
|
|
|
7927
7895
|
// src/cli/project.ts
|
|
7928
7896
|
async function provenanceFile(id, file) {
|
|
7929
|
-
const absolute =
|
|
7897
|
+
const absolute = path19.resolve(file);
|
|
7930
7898
|
return {
|
|
7931
7899
|
id,
|
|
7932
7900
|
path: absolute,
|
|
7933
|
-
sha256:
|
|
7901
|
+
sha256: existsSync14(absolute) ? await sha256File(absolute) : null,
|
|
7934
7902
|
included: true
|
|
7935
7903
|
};
|
|
7936
7904
|
}
|
|
@@ -8002,10 +7970,10 @@ function budgetsForPlan(plan, args) {
|
|
|
8002
7970
|
return out;
|
|
8003
7971
|
}
|
|
8004
7972
|
async function requireCompleteWorkspaceClaims(workspacePath) {
|
|
8005
|
-
if (!
|
|
7973
|
+
if (!existsSync14(workspacePath)) {
|
|
8006
7974
|
throw new ProjectError(`Workspace catalog not found: ${workspacePath}`);
|
|
8007
7975
|
}
|
|
8008
|
-
const dir =
|
|
7976
|
+
const dir = path19.dirname(path19.resolve(workspacePath));
|
|
8009
7977
|
const ws = await loadWorkspace(workspacePath);
|
|
8010
7978
|
const diagnostics = validateWorkspace(ws, dir);
|
|
8011
7979
|
const errors = diagnostics.filter((d) => d.level === "error");
|
|
@@ -8028,9 +7996,9 @@ async function openAccountProject(args) {
|
|
|
8028
7996
|
return project;
|
|
8029
7997
|
}
|
|
8030
7998
|
async function open(args) {
|
|
8031
|
-
if (!
|
|
7999
|
+
if (!existsSync14(path19.resolve(args.manifest))) {
|
|
8032
8000
|
throw new ProjectError(
|
|
8033
|
-
`No manifest at ${
|
|
8001
|
+
`No manifest at ${path19.resolve(args.manifest)}. Pass --manifest, or run \`pixelkiln init --from <dir>\`.`
|
|
8034
8002
|
);
|
|
8035
8003
|
}
|
|
8036
8004
|
const project = await openProject(args.manifest, { lockPath: args.lock, styles: args.styles, assets: args.assets });
|
|
@@ -8072,7 +8040,7 @@ function providerCache(mode2) {
|
|
|
8072
8040
|
|
|
8073
8041
|
// src/cli/commands/account.ts
|
|
8074
8042
|
async function runBalance(args) {
|
|
8075
|
-
loadEnvFiles(
|
|
8043
|
+
loadEnvFiles(path20.dirname(path20.resolve(args.manifest)));
|
|
8076
8044
|
loadEnvFiles(process.cwd());
|
|
8077
8045
|
const loaded = await loadManifest(args.manifest);
|
|
8078
8046
|
const selectedProvider = accountProviderId(loaded.manifest, args.provider, "balance");
|
|
@@ -8119,12 +8087,12 @@ async function runAdopt(args) {
|
|
|
8119
8087
|
tagged ${n} object(s) upstream`);
|
|
8120
8088
|
}
|
|
8121
8089
|
if (args.writePrompts) {
|
|
8122
|
-
const { filled, stillEmpty } = await writePromptsBack(
|
|
8090
|
+
const { filled, stillEmpty } = await writePromptsBack(path20.resolve(args.manifest), lock, {
|
|
8123
8091
|
onProgress: log,
|
|
8124
8092
|
provider: provider.id,
|
|
8125
8093
|
assetIds: specs.map((spec) => spec.assetId)
|
|
8126
8094
|
});
|
|
8127
|
-
log(` recovered ${filled} prompt(s) into ${
|
|
8095
|
+
log(` recovered ${filled} prompt(s) into ${path20.relative(process.cwd(), args.manifest)}`);
|
|
8128
8096
|
const reloaded = await loadManifest(args.manifest);
|
|
8129
8097
|
const rebased = (await resolveSpecs(reloaded, {
|
|
8130
8098
|
styles: args.styles,
|
|
@@ -8154,12 +8122,12 @@ async function runSalvage2(args) {
|
|
|
8154
8122
|
const provider = providerFor(accountProvider);
|
|
8155
8123
|
const jsonMode = args.dryRun && args.json;
|
|
8156
8124
|
const diag = jsonMode ? (msg = "") => console.error(msg) : log;
|
|
8157
|
-
const ownLock =
|
|
8125
|
+
const ownLock = path20.resolve(args.lock);
|
|
8158
8126
|
let workspaceProjects = [];
|
|
8159
8127
|
let workspaceDir = "";
|
|
8160
8128
|
if (args.workspace) {
|
|
8161
|
-
const workspacePath =
|
|
8162
|
-
workspaceDir =
|
|
8129
|
+
const workspacePath = path20.resolve(args.workspace);
|
|
8130
|
+
workspaceDir = path20.dirname(workspacePath);
|
|
8163
8131
|
const complete = await requireCompleteWorkspaceClaims(workspacePath);
|
|
8164
8132
|
workspaceProjects = complete.ws.projects;
|
|
8165
8133
|
for (const d of complete.diagnostics) diag(` WARN ${d.id}: ${d.message}`);
|
|
@@ -8168,12 +8136,12 @@ async function runSalvage2(args) {
|
|
|
8168
8136
|
const lockPaths = [
|
|
8169
8137
|
.../* @__PURE__ */ new Set([
|
|
8170
8138
|
...workspaceLockPaths,
|
|
8171
|
-
...
|
|
8172
|
-
...args.claims.map((c) =>
|
|
8139
|
+
...existsSync15(ownLock) ? [ownLock] : [],
|
|
8140
|
+
...args.claims.map((c) => path20.resolve(c))
|
|
8173
8141
|
])
|
|
8174
8142
|
];
|
|
8175
8143
|
diag(` claim set (${lockPaths.length} lockfile(s)):`);
|
|
8176
|
-
for (const p of lockPaths) diag(` ${
|
|
8144
|
+
for (const p of lockPaths) diag(` ${path20.relative(process.cwd(), p)}`);
|
|
8177
8145
|
if (!args.claims.length && !args.workspace) {
|
|
8178
8146
|
diag(
|
|
8179
8147
|
`
|
|
@@ -8181,7 +8149,7 @@ async function runSalvage2(args) {
|
|
|
8181
8149
|
pass every other project's lockfile via --claims a.json,b.json, or
|
|
8182
8150
|
register every project in a workspace catalog and pass --workspace.`
|
|
8183
8151
|
);
|
|
8184
|
-
} else if (args.workspace && !workspaceProjects.some((p) => resolveProject(workspaceDir, p).manifestPath ===
|
|
8152
|
+
} else if (args.workspace && !workspaceProjects.some((p) => resolveProject(workspaceDir, p).manifestPath === path20.resolve(args.manifest))) {
|
|
8185
8153
|
diag(
|
|
8186
8154
|
`
|
|
8187
8155
|
This project's manifest is not registered in the workspace catalog. Its own
|
|
@@ -8265,7 +8233,7 @@ async function runSalvage2(args) {
|
|
|
8265
8233
|
manifestPath: loaded.path,
|
|
8266
8234
|
manifest: accountManifest,
|
|
8267
8235
|
styleId,
|
|
8268
|
-
importDir:
|
|
8236
|
+
importDir: path20.resolve(loaded.root, style.outDir),
|
|
8269
8237
|
lock,
|
|
8270
8238
|
lockPath: args.lock
|
|
8271
8239
|
},
|
|
@@ -8366,17 +8334,17 @@ async function runTag(args) {
|
|
|
8366
8334
|
}
|
|
8367
8335
|
|
|
8368
8336
|
// src/cli/commands/edit.ts
|
|
8369
|
-
import
|
|
8337
|
+
import path23 from "path";
|
|
8370
8338
|
|
|
8371
8339
|
// src/pipeline/hand-edit.ts
|
|
8372
|
-
import { existsSync as
|
|
8373
|
-
import { copyFile, mkdir as mkdir7, readFile as
|
|
8374
|
-
import
|
|
8340
|
+
import { existsSync as existsSync16 } from "fs";
|
|
8341
|
+
import { copyFile, mkdir as mkdir7, readFile as readFile14, rename as rename7, rm as rm8, writeFile as writeFile9 } from "fs/promises";
|
|
8342
|
+
import path22 from "path";
|
|
8375
8343
|
import { z as z6 } from "zod";
|
|
8376
8344
|
|
|
8377
8345
|
// src/manifest-edit.ts
|
|
8378
|
-
import { readFile as
|
|
8379
|
-
import
|
|
8346
|
+
import { readFile as readFile13, rename as rename6, rm as rm7, writeFile as writeFile8 } from "fs/promises";
|
|
8347
|
+
import path21 from "path";
|
|
8380
8348
|
import { z as z5 } from "zod";
|
|
8381
8349
|
var HexSha = z5.string().regex(/^[0-9a-f]{64}$/);
|
|
8382
8350
|
var AssetPatchSchema = z5.object({
|
|
@@ -8450,6 +8418,8 @@ var ManifestEditSchema = z5.discriminatedUnion("action", [
|
|
|
8450
8418
|
promptSuffix: z5.string().optional(),
|
|
8451
8419
|
/** `#rrggbb` values; null or empty clears the style's own palette. */
|
|
8452
8420
|
palette: z5.array(z5.string().regex(/^#?[0-9a-f]{6}$/i, "expected a six-digit hex colour")).max(256).nullable().optional(),
|
|
8421
|
+
/** Snap downloaded art to the palette; false clears the style's own value. */
|
|
8422
|
+
enforcePalette: z5.boolean().optional(),
|
|
8453
8423
|
/** Provider view name (PixelLab: low top-down, high top-down, side); empty clears. */
|
|
8454
8424
|
view: z5.string().max(64).nullable().optional(),
|
|
8455
8425
|
/** pixflux only; null clears the style's own value. */
|
|
@@ -8541,6 +8511,7 @@ function applyEdit(raw, edit) {
|
|
|
8541
8511
|
const colors = (patch2.palette ?? []).map((color) => "#" + color.replace(/^#/, "").toLowerCase());
|
|
8542
8512
|
setOrDelete(style, "palette", colors.length ? colors : null);
|
|
8543
8513
|
}
|
|
8514
|
+
if (patch2.enforcePalette !== void 0) setOrDelete(style, "enforcePalette", patch2.enforcePalette || null);
|
|
8544
8515
|
if (patch2.view !== void 0) setOrDelete(style, "view", patch2.view?.trim() || null);
|
|
8545
8516
|
if (patch2.noBackground !== void 0) setOrDelete(style, "noBackground", patch2.noBackground);
|
|
8546
8517
|
return;
|
|
@@ -8588,8 +8559,8 @@ function applyEdit(raw, edit) {
|
|
|
8588
8559
|
}
|
|
8589
8560
|
}
|
|
8590
8561
|
async function applyManifestEdit(manifestPath, edit) {
|
|
8591
|
-
const absolute =
|
|
8592
|
-
const original = await
|
|
8562
|
+
const absolute = path21.resolve(manifestPath);
|
|
8563
|
+
const original = await readFile13(absolute, "utf8");
|
|
8593
8564
|
const before = sha256(original);
|
|
8594
8565
|
if (before !== edit.expectedSha256) throw new ManifestDriftError(absolute);
|
|
8595
8566
|
let raw;
|
|
@@ -8603,9 +8574,9 @@ async function applyManifestEdit(manifestPath, edit) {
|
|
|
8603
8574
|
applyEdit(raw, edit);
|
|
8604
8575
|
const next = serializeLike(original, raw);
|
|
8605
8576
|
if (next === original) return { manifestPath: absolute, sha256: before, changed: false };
|
|
8606
|
-
const tmp =
|
|
8607
|
-
|
|
8608
|
-
`.${
|
|
8577
|
+
const tmp = path21.join(
|
|
8578
|
+
path21.dirname(absolute),
|
|
8579
|
+
`.${path21.basename(absolute)}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`
|
|
8609
8580
|
);
|
|
8610
8581
|
try {
|
|
8611
8582
|
await writeFile8(tmp, next);
|
|
@@ -8627,11 +8598,11 @@ async function applyManifestEdit(manifestPath, edit) {
|
|
|
8627
8598
|
var HAND_EDIT_DIR = "edits";
|
|
8628
8599
|
function handEditPath(loaded, spec) {
|
|
8629
8600
|
const style = loaded.manifest.styles[spec.styleId];
|
|
8630
|
-
const outDir =
|
|
8631
|
-
const relative =
|
|
8632
|
-
const inside = relative && !relative.startsWith("..") && !
|
|
8633
|
-
const file = inside ? relative :
|
|
8634
|
-
return
|
|
8601
|
+
const outDir = path22.resolve(loaded.root, style?.outDir ?? ".");
|
|
8602
|
+
const relative = path22.relative(outDir, spec.outFile);
|
|
8603
|
+
const inside = relative && !relative.startsWith("..") && !path22.isAbsolute(relative);
|
|
8604
|
+
const file = inside ? relative : path22.basename(spec.outFile);
|
|
8605
|
+
return path22.join(outDir, HAND_EDIT_DIR, file.replace(/\.gif$/i, ".png"));
|
|
8635
8606
|
}
|
|
8636
8607
|
function handEditBases(spec, lock) {
|
|
8637
8608
|
const entry = lock.entries[lockKey(spec.styleId, spec.assetId)];
|
|
@@ -8644,10 +8615,10 @@ function handEditBases(spec, lock) {
|
|
|
8644
8615
|
}));
|
|
8645
8616
|
if (members.some((member) => member.mediaType && member.mediaType !== "image/png")) return null;
|
|
8646
8617
|
if (entry.outputs.length === 1 && !primaryOutput(entry)) return null;
|
|
8647
|
-
if (!members.every((member) =>
|
|
8618
|
+
if (!members.every((member) => existsSync16(member.path))) return null;
|
|
8648
8619
|
return members.map(({ role, path: file }) => ({ role, path: file }));
|
|
8649
8620
|
}
|
|
8650
|
-
return
|
|
8621
|
+
return existsSync16(spec.outFile) ? [{ role: null, path: spec.outFile }] : null;
|
|
8651
8622
|
}
|
|
8652
8623
|
function handEditMembers(loaded, spec, lock, editPath) {
|
|
8653
8624
|
const entry = lock.entries[lockKey(spec.styleId, spec.assetId)];
|
|
@@ -8673,18 +8644,18 @@ async function startHandEdit(loaded, lock, spec, opts = {}) {
|
|
|
8673
8644
|
`${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`
|
|
8674
8645
|
);
|
|
8675
8646
|
}
|
|
8676
|
-
const existing = spec.source ?
|
|
8647
|
+
const existing = spec.source ? path22.resolve(loaded.root, spec.source) : null;
|
|
8677
8648
|
const editPath = existing ?? handEditPath(loaded, spec);
|
|
8678
8649
|
const members = handEditMembers(loaded, spec, lock, editPath);
|
|
8679
8650
|
let created = false;
|
|
8680
8651
|
for (const member of members) {
|
|
8681
|
-
if (
|
|
8652
|
+
if (existsSync16(member.path)) continue;
|
|
8682
8653
|
if (!member.base) {
|
|
8683
8654
|
throw new ManifestEditError(
|
|
8684
8655
|
`${spec.styleId}/${spec.assetId} has no generated PNG on disk to start from; generate or restore it first`
|
|
8685
8656
|
);
|
|
8686
8657
|
}
|
|
8687
|
-
await mkdir7(
|
|
8658
|
+
await mkdir7(path22.dirname(member.path), { recursive: true });
|
|
8688
8659
|
await copyFile(member.base, member.path);
|
|
8689
8660
|
created = true;
|
|
8690
8661
|
}
|
|
@@ -8763,7 +8734,7 @@ function handEditProjectPath(editPath) {
|
|
|
8763
8734
|
async function readHandEditCompanion(editPath) {
|
|
8764
8735
|
let text;
|
|
8765
8736
|
try {
|
|
8766
|
-
text = await
|
|
8737
|
+
text = await readFile14(handEditCompanionPath(editPath), "utf8");
|
|
8767
8738
|
} catch {
|
|
8768
8739
|
return null;
|
|
8769
8740
|
}
|
|
@@ -8806,7 +8777,7 @@ async function saveHandEdit(loaded, lock, spec, input) {
|
|
|
8806
8777
|
}
|
|
8807
8778
|
});
|
|
8808
8779
|
const bases = handEditBases(spec, lock);
|
|
8809
|
-
const expectedSize = bases?.[0] ? decodePng(await
|
|
8780
|
+
const expectedSize = bases?.[0] ? decodePng(await readFile14(bases[0].path)) : { width: spec.width, height: spec.height };
|
|
8810
8781
|
for (const [index, image] of decoded.entries()) {
|
|
8811
8782
|
if (image.width !== expectedSize.width || image.height !== expectedSize.height) {
|
|
8812
8783
|
const which = memberSet ? `member ${incoming[index].role ?? index}` : "the edit";
|
|
@@ -8822,7 +8793,7 @@ async function saveHandEdit(loaded, lock, spec, input) {
|
|
|
8822
8793
|
editor: input.editor,
|
|
8823
8794
|
protocol: input.protocol,
|
|
8824
8795
|
savedAt: (input.now ?? /* @__PURE__ */ new Date()).toISOString(),
|
|
8825
|
-
project: projectPath ?
|
|
8796
|
+
project: projectPath ? path22.basename(projectPath) : null,
|
|
8826
8797
|
outputs: incoming.map((frame, index) => ({
|
|
8827
8798
|
role: frame.role,
|
|
8828
8799
|
basedOn: basedOnHash(entry, index),
|
|
@@ -8882,7 +8853,7 @@ async function runEdit(args) {
|
|
|
8882
8853
|
const set = started.members.length > 1;
|
|
8883
8854
|
const kind = spec.generator === "frames" ? "frames" : "members";
|
|
8884
8855
|
log(` ${started.created ? "created" : "found"} ${started.source}${set ? ` (${started.members.length} ${kind})` : ""}${started.declared ? " and declared it as the asset's source" : ""}`);
|
|
8885
|
-
if (set) for (const member of started.members) log(` ${member.role ?? ""} ${
|
|
8856
|
+
if (set) for (const member of started.members) log(` ${member.role ?? ""} ${path23.relative(process.cwd(), member.path)}`);
|
|
8886
8857
|
if (args.noOpen) {
|
|
8887
8858
|
log(` edit ${set ? "them" : "it"} with your own tool, then run pixelkiln plan; mount and pack place ${set ? "them" : "it"} in place of the generated art`);
|
|
8888
8859
|
} else {
|
|
@@ -8892,17 +8863,17 @@ async function runEdit(args) {
|
|
|
8892
8863
|
}
|
|
8893
8864
|
|
|
8894
8865
|
// src/cli/commands/gallery.ts
|
|
8895
|
-
import
|
|
8896
|
-
import { existsSync as
|
|
8866
|
+
import path30 from "path";
|
|
8867
|
+
import { existsSync as existsSync22 } from "fs";
|
|
8897
8868
|
|
|
8898
8869
|
// src/gallery/snapshot.ts
|
|
8899
8870
|
import { createHash as createHash3 } from "crypto";
|
|
8900
|
-
import { existsSync as
|
|
8901
|
-
import { readFile as
|
|
8902
|
-
import
|
|
8871
|
+
import { existsSync as existsSync18 } from "fs";
|
|
8872
|
+
import { readFile as readFile15, stat as stat2 } from "fs/promises";
|
|
8873
|
+
import path26 from "path";
|
|
8903
8874
|
|
|
8904
8875
|
// src/gallery/edit.ts
|
|
8905
|
-
import
|
|
8876
|
+
import path24 from "path";
|
|
8906
8877
|
import { z as z7 } from "zod";
|
|
8907
8878
|
var Base64 = z7.string().regex(/^[A-Za-z0-9+/]*={0,2}$/).max(Math.ceil(MAX_HAND_EDIT_BYTES / 3) * 4);
|
|
8908
8879
|
var HandEditRequestSchema = z7.discriminatedUnion("action", [
|
|
@@ -8992,7 +8963,7 @@ function createGalleryEditHandler(opts) {
|
|
|
8992
8963
|
}
|
|
8993
8964
|
const result = await applyManifestEdit(manifestPath, parsed.data);
|
|
8994
8965
|
if (result.changed) {
|
|
8995
|
-
const relative =
|
|
8966
|
+
const relative = path24.relative(process.cwd(), result.manifestPath);
|
|
8996
8967
|
const shown = !relative || relative.startsWith("..") ? result.manifestPath : relative;
|
|
8997
8968
|
const subject = parsed.data.action === "patch-style" ? `style ${parsed.data.styleId}` : parsed.data.action === "set-source" || parsed.data.action === "clear-source" ? `${parsed.data.styleId}/${parsed.data.assetId} source` : parsed.data.assetId;
|
|
8998
8969
|
log2(` manifest edited: ${parsed.data.action === "add-asset" ? "added" : "changed"} ${subject} in ${shown}`);
|
|
@@ -9002,8 +8973,8 @@ function createGalleryEditHandler(opts) {
|
|
|
9002
8973
|
}
|
|
9003
8974
|
|
|
9004
8975
|
// src/pipeline/history.ts
|
|
9005
|
-
import { existsSync as
|
|
9006
|
-
import
|
|
8976
|
+
import { existsSync as existsSync17 } from "fs";
|
|
8977
|
+
import path25 from "path";
|
|
9007
8978
|
var HISTORY_ENV = "PIXELKILN_HISTORY";
|
|
9008
8979
|
function historyLimit(manifest, env = process.env) {
|
|
9009
8980
|
if (manifest?.history !== void 0) return manifest.history;
|
|
@@ -9035,6 +9006,7 @@ function retireGeneration(entry, retiredAt) {
|
|
|
9035
9006
|
cost: entry.cost,
|
|
9036
9007
|
costUnit: entry.costUnit,
|
|
9037
9008
|
provider: entry.provider,
|
|
9009
|
+
postprocess: entry.postprocess,
|
|
9038
9010
|
retiredAt
|
|
9039
9011
|
};
|
|
9040
9012
|
}
|
|
@@ -9047,9 +9019,13 @@ function historyAfterReplacing(previous, limit, retiredAt) {
|
|
|
9047
9019
|
}
|
|
9048
9020
|
function referencedHashes(lock) {
|
|
9049
9021
|
const hashes = /* @__PURE__ */ new Set();
|
|
9022
|
+
const add = (output) => {
|
|
9023
|
+
hashes.add(output.sha256);
|
|
9024
|
+
if (output.raw) hashes.add(output.raw);
|
|
9025
|
+
};
|
|
9050
9026
|
for (const entry of Object.values(lock.entries)) {
|
|
9051
|
-
for (const output of entry.outputs)
|
|
9052
|
-
for (const generation of entry.history ?? []) for (const output of generation.outputs)
|
|
9027
|
+
for (const output of entry.outputs) add(output);
|
|
9028
|
+
for (const generation of entry.history ?? []) for (const output of generation.outputs) add(output);
|
|
9053
9029
|
}
|
|
9054
9030
|
return hashes;
|
|
9055
9031
|
}
|
|
@@ -9084,10 +9060,10 @@ async function revertGeneration(provider, spec, lock, lockPath, opts) {
|
|
|
9084
9060
|
}
|
|
9085
9061
|
for (let i = 0; i < entry.outputs.length; i++) {
|
|
9086
9062
|
const file = currentEntryOutputPath(entry, spec, i);
|
|
9087
|
-
if (!
|
|
9063
|
+
if (!existsSync17(file)) continue;
|
|
9088
9064
|
if (await sha256File(file) !== entry.outputs[i].sha256 && !opts.force) {
|
|
9089
9065
|
throw new OverwriteRefusedError(
|
|
9090
|
-
`${
|
|
9066
|
+
`${path25.relative(process.cwd(), file)} was changed after download; keep it with pixelkiln edit, or pass --force to replace it`,
|
|
9091
9067
|
[file]
|
|
9092
9068
|
);
|
|
9093
9069
|
}
|
|
@@ -9120,6 +9096,7 @@ async function revertGeneration(provider, spec, lock, lockPath, opts) {
|
|
|
9120
9096
|
cost: generation.cost,
|
|
9121
9097
|
costUnit: generation.costUnit,
|
|
9122
9098
|
provider: generation.provider,
|
|
9099
|
+
postprocess: generation.postprocess,
|
|
9123
9100
|
history
|
|
9124
9101
|
});
|
|
9125
9102
|
await saveLock(lockPath, lock);
|
|
@@ -9138,7 +9115,7 @@ async function revertGeneration(provider, spec, lock, lockPath, opts) {
|
|
|
9138
9115
|
// src/gallery/snapshot.ts
|
|
9139
9116
|
var PROJECT_FILE_TYPE = "application/octet-stream";
|
|
9140
9117
|
function galleryMediaId(absolutePath) {
|
|
9141
|
-
return createHash3("sha256").update(
|
|
9118
|
+
return createHash3("sha256").update(path26.resolve(absolutePath)).digest("hex").slice(0, 24);
|
|
9142
9119
|
}
|
|
9143
9120
|
function galleryMediaRoute(id) {
|
|
9144
9121
|
return `/media/${id}`;
|
|
@@ -9157,9 +9134,9 @@ function describeHistory(media, entry, cacheDir) {
|
|
|
9157
9134
|
return (entry.history ?? []).map((generation, i) => {
|
|
9158
9135
|
const outputs = generation.outputs.map((output) => {
|
|
9159
9136
|
const mediaType = output.mediaType ?? MediaType.PNG;
|
|
9160
|
-
const file =
|
|
9137
|
+
const file = path26.join(cacheDir, cacheFileName(output.sha256, mediaType));
|
|
9161
9138
|
let url = null;
|
|
9162
|
-
if (
|
|
9139
|
+
if (existsSync18(file)) {
|
|
9163
9140
|
const id = galleryMediaId(file);
|
|
9164
9141
|
media.set(id, { path: file, contentType: mediaType });
|
|
9165
9142
|
url = `${galleryMediaRoute(id)}?v=${output.sha256.slice(0, 24)}`;
|
|
@@ -9188,7 +9165,7 @@ function describeHistory(media, entry, cacheDir) {
|
|
|
9188
9165
|
var MAX_COMPARED_BYTES = 8 * 1024 * 1024;
|
|
9189
9166
|
async function samePixels(a, b) {
|
|
9190
9167
|
try {
|
|
9191
|
-
const [left, right] = await Promise.all([
|
|
9168
|
+
const [left, right] = await Promise.all([readFile15(a), readFile15(b)]);
|
|
9192
9169
|
if (left.length > MAX_COMPARED_BYTES || right.length > MAX_COMPARED_BYTES) return false;
|
|
9193
9170
|
const x = decodePng(left);
|
|
9194
9171
|
const y = decodePng(right);
|
|
@@ -9241,10 +9218,10 @@ async function describeOutput(media, root, absolutePath, recorded = {}) {
|
|
|
9241
9218
|
async function describeQuality(media, root, inspection) {
|
|
9242
9219
|
const outputPaths = inspection.outputs ?? [inspection.output];
|
|
9243
9220
|
const outputs = await Promise.all(
|
|
9244
|
-
outputPaths.map((file) => describeOutput(media, root,
|
|
9221
|
+
outputPaths.map((file) => describeOutput(media, root, path26.resolve(root, file)))
|
|
9245
9222
|
);
|
|
9246
|
-
const recordPath =
|
|
9247
|
-
const recordExists =
|
|
9223
|
+
const recordPath = path26.resolve(root, inspection.record);
|
|
9224
|
+
const recordExists = existsSync18(recordPath);
|
|
9248
9225
|
let options = null;
|
|
9249
9226
|
let check = null;
|
|
9250
9227
|
if (recordExists) {
|
|
@@ -9263,7 +9240,7 @@ async function describeQuality(media, root, inspection) {
|
|
|
9263
9240
|
return {
|
|
9264
9241
|
state: inspection.state,
|
|
9265
9242
|
reason: inspection.reason,
|
|
9266
|
-
output: portableOutputPath(
|
|
9243
|
+
output: portableOutputPath(path26.resolve(root, inspection.output), root),
|
|
9267
9244
|
record: portableOutputPath(recordPath, root),
|
|
9268
9245
|
recordExists,
|
|
9269
9246
|
outputs,
|
|
@@ -9300,9 +9277,9 @@ async function buildGallerySnapshot(opts) {
|
|
|
9300
9277
|
const media = /* @__PURE__ */ new Map();
|
|
9301
9278
|
const plan = await buildPlan(specs, lock);
|
|
9302
9279
|
const items = [];
|
|
9303
|
-
const manifestText = await
|
|
9280
|
+
const manifestText = await readFile15(loaded.path, "utf8");
|
|
9304
9281
|
const manifestSha256 = sha256(manifestText);
|
|
9305
|
-
const cacheDir =
|
|
9282
|
+
const cacheDir = path26.resolve(path26.dirname(opts.lockPath), ".pixelkiln", "cache");
|
|
9306
9283
|
let rawStyles = {};
|
|
9307
9284
|
try {
|
|
9308
9285
|
const raw = JSON.parse(manifestText);
|
|
@@ -9326,12 +9303,12 @@ async function buildGallerySnapshot(opts) {
|
|
|
9326
9303
|
)
|
|
9327
9304
|
);
|
|
9328
9305
|
if (spec.source) {
|
|
9329
|
-
const editPath =
|
|
9306
|
+
const editPath = path26.resolve(root, spec.source);
|
|
9330
9307
|
const memberCount = sourceIsStem(spec.source, entry, root) ? entry.outputs.length : 1;
|
|
9331
9308
|
const editShas = await Promise.all(
|
|
9332
9309
|
Array.from({ length: memberCount }, async (_, index) => {
|
|
9333
9310
|
const file = sourceOutputPath(spec.source, entry, index, root);
|
|
9334
|
-
return { file, sha256:
|
|
9311
|
+
return { file, sha256: existsSync18(file) ? await sha256File(file) : null };
|
|
9335
9312
|
})
|
|
9336
9313
|
);
|
|
9337
9314
|
edits = await Promise.all(editShas.map(({ file, sha256: editSha }) => describeOutput(media, root, file, { sha256: editSha })));
|
|
@@ -9339,7 +9316,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
9339
9316
|
const companion = await readHandEditCompanion(editPath);
|
|
9340
9317
|
if (companion) {
|
|
9341
9318
|
const projectPath = handEditProjectPath(editPath);
|
|
9342
|
-
const project = companion.project &&
|
|
9319
|
+
const project = companion.project && existsSync18(projectPath) ? await fileInfo(projectPath) : null;
|
|
9343
9320
|
let projectUrl = null;
|
|
9344
9321
|
if (project) {
|
|
9345
9322
|
const id = galleryMediaId(projectPath);
|
|
@@ -9375,8 +9352,8 @@ async function buildGallerySnapshot(opts) {
|
|
|
9375
9352
|
editStatus = edits.some((member) => !member.exists) ? "missing" : editChanged.every((changed) => !changed) ? "same" : regenerated ? "regenerated-since" : "edited";
|
|
9376
9353
|
}
|
|
9377
9354
|
} else if (spec.source) {
|
|
9378
|
-
outputs = [await describeOutput(media, root,
|
|
9379
|
-
} else if (!entry &&
|
|
9355
|
+
outputs = [await describeOutput(media, root, path26.resolve(root, spec.source))];
|
|
9356
|
+
} else if (!entry && existsSync18(spec.outFile)) {
|
|
9380
9357
|
outputs = [await describeOutput(media, root, spec.outFile)];
|
|
9381
9358
|
} else {
|
|
9382
9359
|
outputs = [];
|
|
@@ -9418,6 +9395,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
9418
9395
|
outputs,
|
|
9419
9396
|
quality,
|
|
9420
9397
|
providerMetadata: entry?.providerMetadata ?? {},
|
|
9398
|
+
postprocess: entry?.postprocess ?? null,
|
|
9421
9399
|
asset,
|
|
9422
9400
|
source: spec.source ?? null,
|
|
9423
9401
|
edit,
|
|
@@ -9478,6 +9456,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
9478
9456
|
outputs,
|
|
9479
9457
|
quality: null,
|
|
9480
9458
|
providerMetadata: entry.providerMetadata ?? {},
|
|
9459
|
+
postprocess: entry.postprocess ?? null,
|
|
9481
9460
|
asset: null,
|
|
9482
9461
|
source: null,
|
|
9483
9462
|
edit: null,
|
|
@@ -9526,6 +9505,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
9526
9505
|
promptPrefix: style?.promptPrefix ?? "",
|
|
9527
9506
|
promptSuffix: style?.promptSuffix ?? "",
|
|
9528
9507
|
palette: style?.palette ?? [],
|
|
9508
|
+
enforcePalette: style?.enforcePalette ?? false,
|
|
9529
9509
|
view: style?.view ?? null,
|
|
9530
9510
|
noBackground: style?.noBackground ?? true,
|
|
9531
9511
|
quality: Boolean(style?.quality),
|
|
@@ -9555,7 +9535,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
9555
9535
|
id: loaded.manifest.name,
|
|
9556
9536
|
name: loaded.manifest.name,
|
|
9557
9537
|
manifest: loaded.path,
|
|
9558
|
-
lock:
|
|
9538
|
+
lock: path26.resolve(opts.lockPath),
|
|
9559
9539
|
root,
|
|
9560
9540
|
provider: loaded.manifest.provider,
|
|
9561
9541
|
account: null,
|
|
@@ -9581,7 +9561,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
9581
9561
|
return { snapshot, media };
|
|
9582
9562
|
}
|
|
9583
9563
|
async function buildWorkspaceGallerySnapshot(opts) {
|
|
9584
|
-
const dir =
|
|
9564
|
+
const dir = path26.dirname(path26.resolve(opts.workspacePath));
|
|
9585
9565
|
const media = /* @__PURE__ */ new Map();
|
|
9586
9566
|
const projects = [];
|
|
9587
9567
|
const items = [];
|
|
@@ -9624,11 +9604,11 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
9624
9604
|
id: project.id,
|
|
9625
9605
|
name: loaded.manifest.name,
|
|
9626
9606
|
manifest: manifestPath,
|
|
9627
|
-
lock:
|
|
9607
|
+
lock: path26.resolve(lockPath),
|
|
9628
9608
|
root: loaded.root,
|
|
9629
9609
|
provider: loaded.manifest.provider,
|
|
9630
9610
|
account: project.account ?? null,
|
|
9631
|
-
manifestSha256: manifestSha256 ?? sha256(await
|
|
9611
|
+
manifestSha256: manifestSha256 ?? sha256(await readFile15(manifestPath, "utf8")),
|
|
9632
9612
|
historyLimit: historyLimit(loaded.manifest),
|
|
9633
9613
|
entries: Object.keys(lock.entries).length,
|
|
9634
9614
|
items: projectItems.length,
|
|
@@ -9640,8 +9620,8 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
9640
9620
|
id: project.id,
|
|
9641
9621
|
name: project.id,
|
|
9642
9622
|
manifest: manifestPath,
|
|
9643
|
-
lock:
|
|
9644
|
-
root:
|
|
9623
|
+
lock: path26.resolve(lockPath),
|
|
9624
|
+
root: path26.dirname(manifestPath),
|
|
9645
9625
|
provider: project.provider,
|
|
9646
9626
|
account: project.account ?? null,
|
|
9647
9627
|
manifestSha256: null,
|
|
@@ -9668,7 +9648,7 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
9668
9648
|
version: 1,
|
|
9669
9649
|
generatedAt: (opts.now ?? (() => /* @__PURE__ */ new Date()))().toISOString(),
|
|
9670
9650
|
project: null,
|
|
9671
|
-
workspace: { path:
|
|
9651
|
+
workspace: { path: path26.resolve(opts.workspacePath), dir, projects },
|
|
9672
9652
|
filter,
|
|
9673
9653
|
totals: {
|
|
9674
9654
|
entries: projects.reduce((sum, project) => sum + project.entries, 0),
|
|
@@ -9687,7 +9667,7 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
9687
9667
|
// src/gallery/server.ts
|
|
9688
9668
|
import { randomBytes } from "crypto";
|
|
9689
9669
|
import { createServer as createServer2 } from "http";
|
|
9690
|
-
import { readFile as
|
|
9670
|
+
import { readFile as readFile16 } from "fs/promises";
|
|
9691
9671
|
|
|
9692
9672
|
// src/gallery/page.ts
|
|
9693
9673
|
import { readFileSync as readFileSync4 } from "fs";
|
|
@@ -9928,7 +9908,7 @@ async function serveGallery(opts) {
|
|
|
9928
9908
|
const asset = reviewAssets.get(jobId)?.get(url2.pathname);
|
|
9929
9909
|
if (!asset) return fail(404, "no such review asset");
|
|
9930
9910
|
try {
|
|
9931
|
-
const bytes = await
|
|
9911
|
+
const bytes = await readFile16(asset.path);
|
|
9932
9912
|
res.writeHead(200, {
|
|
9933
9913
|
"Content-Type": asset.contentType,
|
|
9934
9914
|
"Cache-Control": "no-store",
|
|
@@ -10004,7 +9984,7 @@ async function serveGallery(opts) {
|
|
|
10004
9984
|
if (!asset) return fail(404, "no such gallery media");
|
|
10005
9985
|
let bytes;
|
|
10006
9986
|
try {
|
|
10007
|
-
bytes = await
|
|
9987
|
+
bytes = await readFile16(asset.path);
|
|
10008
9988
|
} catch {
|
|
10009
9989
|
return fail(404, "gallery media is no longer on disk; refresh the page");
|
|
10010
9990
|
}
|
|
@@ -10317,13 +10297,13 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
|
|
|
10317
10297
|
}
|
|
10318
10298
|
|
|
10319
10299
|
// src/pick/server.ts
|
|
10320
|
-
import { existsSync as
|
|
10321
|
-
import { readFile as
|
|
10300
|
+
import { existsSync as existsSync20 } from "fs";
|
|
10301
|
+
import { readFile as readFile18 } from "fs/promises";
|
|
10322
10302
|
|
|
10323
10303
|
// src/pipeline/init.ts
|
|
10324
|
-
import { readdir, readFile as
|
|
10325
|
-
import { existsSync as
|
|
10326
|
-
import
|
|
10304
|
+
import { readdir, readFile as readFile17, writeFile as writeFile10 } from "fs/promises";
|
|
10305
|
+
import { existsSync as existsSync19 } from "fs";
|
|
10306
|
+
import path27 from "path";
|
|
10327
10307
|
function pngSize(buf) {
|
|
10328
10308
|
if (buf.length < 24) return null;
|
|
10329
10309
|
const isPng = buf.readUInt32BE(0) === 2303741511 && buf.readUInt32BE(4) === 218765834;
|
|
@@ -10337,7 +10317,7 @@ function slugify(value) {
|
|
|
10337
10317
|
async function walk(dir, exclude) {
|
|
10338
10318
|
const out = [];
|
|
10339
10319
|
for (const entry of await readdir(dir, { withFileTypes: true })) {
|
|
10340
|
-
const full =
|
|
10320
|
+
const full = path27.join(dir, entry.name);
|
|
10341
10321
|
if (exclude.some((x) => full.includes(x))) continue;
|
|
10342
10322
|
if (entry.isDirectory()) out.push(...await walk(full, exclude));
|
|
10343
10323
|
else if (entry.name.toLowerCase().endsWith(".png")) out.push(full);
|
|
@@ -10351,14 +10331,14 @@ async function scanAssets(root, opts = {}) {
|
|
|
10351
10331
|
const skipped = [];
|
|
10352
10332
|
const seen = /* @__PURE__ */ new Map();
|
|
10353
10333
|
for (const file of files.sort()) {
|
|
10354
|
-
const size = pngSize(await
|
|
10334
|
+
const size = pngSize(await readFile17(file));
|
|
10355
10335
|
if (!size) {
|
|
10356
10336
|
skipped.push(`${file} (not a readable PNG)`);
|
|
10357
10337
|
continue;
|
|
10358
10338
|
}
|
|
10359
|
-
const rel =
|
|
10360
|
-
const category =
|
|
10361
|
-
let id = slugify(
|
|
10339
|
+
const rel = path27.relative(root, file);
|
|
10340
|
+
const category = path27.dirname(rel) === "." ? "" : path27.dirname(rel);
|
|
10341
|
+
let id = slugify(path27.basename(rel));
|
|
10362
10342
|
if (seen.has(id)) {
|
|
10363
10343
|
const n = seen.get(id) + 1;
|
|
10364
10344
|
seen.set(id, n);
|
|
@@ -10401,6 +10381,7 @@ function buildManifest(name, styleId, generator, outDir, scanned) {
|
|
|
10401
10381
|
promptSuffix: "",
|
|
10402
10382
|
styleImages: [],
|
|
10403
10383
|
palette: [],
|
|
10384
|
+
enforcePalette: false,
|
|
10404
10385
|
outDir,
|
|
10405
10386
|
tags: [name],
|
|
10406
10387
|
providerOptions: {}
|
|
@@ -10415,7 +10396,7 @@ function mode(values) {
|
|
|
10415
10396
|
return [...counts.entries()].sort((a, b) => b[1] - a[1])[0]?.[0] ?? 64;
|
|
10416
10397
|
}
|
|
10417
10398
|
async function writeManifestFile(target, manifest) {
|
|
10418
|
-
if (
|
|
10399
|
+
if (existsSync19(target)) {
|
|
10419
10400
|
throw new Error(`${target} already exists \u2014 refusing to overwrite an existing manifest.`);
|
|
10420
10401
|
}
|
|
10421
10402
|
await writeFile10(target, JSON.stringify(manifest, null, 2) + "\n");
|
|
@@ -10818,13 +10799,13 @@ async function currentArt(entry, spec) {
|
|
|
10818
10799
|
const previous = primaryOutput({ ...entry, outputs: entry.supersededOutputs });
|
|
10819
10800
|
if (!previous) return null;
|
|
10820
10801
|
const file = resolveOutputPath(previous.path, spec.root);
|
|
10821
|
-
if (!
|
|
10802
|
+
if (!existsSync20(file)) return null;
|
|
10822
10803
|
const contentType = previous.mediaType ?? mediaTypeFromExtension(file);
|
|
10823
10804
|
if (!contentType) return null;
|
|
10824
10805
|
let width = entry.width;
|
|
10825
10806
|
let height = entry.height;
|
|
10826
10807
|
if (contentType === "image/png") {
|
|
10827
|
-
const size = pngSize(await
|
|
10808
|
+
const size = pngSize(await readFile18(file));
|
|
10828
10809
|
if (size) ({ width, height } = size);
|
|
10829
10810
|
}
|
|
10830
10811
|
return { path: file, contentType, width, height };
|
|
@@ -11317,15 +11298,15 @@ function createGenerateHandlers(opts) {
|
|
|
11317
11298
|
}
|
|
11318
11299
|
|
|
11319
11300
|
// src/gallery/editor.ts
|
|
11320
|
-
import { readFile as
|
|
11321
|
-
import
|
|
11301
|
+
import { readFile as readFile20 } from "fs/promises";
|
|
11302
|
+
import path29 from "path";
|
|
11322
11303
|
|
|
11323
11304
|
// src/editor/install.ts
|
|
11324
11305
|
import { createHash as createHash4 } from "crypto";
|
|
11325
|
-
import { existsSync as
|
|
11326
|
-
import { mkdir as mkdir8, readFile as
|
|
11306
|
+
import { existsSync as existsSync21 } from "fs";
|
|
11307
|
+
import { mkdir as mkdir8, readFile as readFile19, rename as rename8, rm as rm9, stat as stat3, writeFile as writeFile11 } from "fs/promises";
|
|
11327
11308
|
import os2 from "os";
|
|
11328
|
-
import
|
|
11309
|
+
import path28 from "path";
|
|
11329
11310
|
|
|
11330
11311
|
// tools/pixelorama-bridge/pin.json
|
|
11331
11312
|
var pin_default = {
|
|
@@ -11389,15 +11370,15 @@ var EditorInstallError = class extends Error {
|
|
|
11389
11370
|
};
|
|
11390
11371
|
function editorToolsRoot() {
|
|
11391
11372
|
const override = process.env.PIXELKILN_TOOLS_DIR?.trim();
|
|
11392
|
-
if (override) return
|
|
11393
|
-
if (process.platform === "darwin") return
|
|
11373
|
+
if (override) return path28.resolve(override);
|
|
11374
|
+
if (process.platform === "darwin") return path28.join(os2.homedir(), "Library", "Caches", "pixelkiln", "tools");
|
|
11394
11375
|
if (process.platform === "win32") {
|
|
11395
|
-
return
|
|
11376
|
+
return path28.join(process.env.LOCALAPPDATA ?? path28.join(os2.homedir(), "AppData", "Local"), "pixelkiln", "tools");
|
|
11396
11377
|
}
|
|
11397
|
-
return
|
|
11378
|
+
return path28.join(process.env.XDG_CACHE_HOME ?? path28.join(os2.homedir(), ".cache"), "pixelkiln", "tools");
|
|
11398
11379
|
}
|
|
11399
11380
|
function editorDir(pin = EDITOR_PIN, root = editorToolsRoot()) {
|
|
11400
|
-
return
|
|
11381
|
+
return path28.join(root, "pixelorama", pin.release ?? "unpublished");
|
|
11401
11382
|
}
|
|
11402
11383
|
function editorBaseUrl(pin = EDITOR_PIN) {
|
|
11403
11384
|
const override = process.env.PIXELKILN_EDITOR_URL?.trim();
|
|
@@ -11407,7 +11388,7 @@ function editorBaseUrl(pin = EDITOR_PIN) {
|
|
|
11407
11388
|
}
|
|
11408
11389
|
async function sha256Of(file) {
|
|
11409
11390
|
try {
|
|
11410
|
-
return createHash4("sha256").update(await
|
|
11391
|
+
return createHash4("sha256").update(await readFile19(file)).digest("hex");
|
|
11411
11392
|
} catch {
|
|
11412
11393
|
return null;
|
|
11413
11394
|
}
|
|
@@ -11420,8 +11401,8 @@ async function editorStatus(opts = {}) {
|
|
|
11420
11401
|
let totalBytes = 0;
|
|
11421
11402
|
for (const [name, expected] of Object.entries(pin.files)) {
|
|
11422
11403
|
totalBytes += expected.bytes;
|
|
11423
|
-
const file =
|
|
11424
|
-
const info =
|
|
11404
|
+
const file = path28.join(dir, name);
|
|
11405
|
+
const info = existsSync21(file) ? await stat3(file).catch(() => null) : null;
|
|
11425
11406
|
if (info && info.size === expected.bytes && await sha256Of(file) === expected.sha256) {
|
|
11426
11407
|
installedBytes += expected.bytes;
|
|
11427
11408
|
} else {
|
|
@@ -11478,7 +11459,7 @@ async function installEditor(opts = {}) {
|
|
|
11478
11459
|
if (digest2 !== expected.sha256) {
|
|
11479
11460
|
throw new EditorInstallError(`${name}: sha256 ${digest2.slice(0, 12)}\u2026 does not match the pinned ${expected.sha256.slice(0, 12)}\u2026`, name);
|
|
11480
11461
|
}
|
|
11481
|
-
const final =
|
|
11462
|
+
const final = path28.join(dir, name);
|
|
11482
11463
|
const tmp = `${final}.${process.pid}.part`;
|
|
11483
11464
|
try {
|
|
11484
11465
|
await writeFile11(tmp, bytes);
|
|
@@ -11524,7 +11505,7 @@ var EDITOR_CONTENT_TYPES = {
|
|
|
11524
11505
|
".json": "application/json; charset=utf-8"
|
|
11525
11506
|
};
|
|
11526
11507
|
function editorContentType(name) {
|
|
11527
|
-
return EDITOR_CONTENT_TYPES[
|
|
11508
|
+
return EDITOR_CONTENT_TYPES[path28.extname(name).toLowerCase()] ?? null;
|
|
11528
11509
|
}
|
|
11529
11510
|
|
|
11530
11511
|
// src/gallery/editor.ts
|
|
@@ -11600,7 +11581,7 @@ function createGalleryEditorHandlers(opts = {}) {
|
|
|
11600
11581
|
if (!status.installed) return null;
|
|
11601
11582
|
let bytes;
|
|
11602
11583
|
try {
|
|
11603
|
-
bytes = await
|
|
11584
|
+
bytes = await readFile20(path29.join(dir, name));
|
|
11604
11585
|
} catch {
|
|
11605
11586
|
verified = null;
|
|
11606
11587
|
return null;
|
|
@@ -11640,13 +11621,13 @@ async function serveUntilStopped(initial, reload, args, access2) {
|
|
|
11640
11621
|
};
|
|
11641
11622
|
const loadProject = async (project) => {
|
|
11642
11623
|
const ctx = await access2.loadProject(project);
|
|
11643
|
-
const dir =
|
|
11624
|
+
const dir = path30.dirname(ctx.loaded.path);
|
|
11644
11625
|
const declared = readEnvFiles(dir);
|
|
11645
11626
|
const credentialNames = new Set(availableProviders().flatMap((id) => providerCredentialEnvs(providerFactory(id))));
|
|
11646
11627
|
for (const [name, value] of Object.entries(declared)) {
|
|
11647
11628
|
if (credentialNames.has(name) && name in process.env && process.env[name] !== value) {
|
|
11648
11629
|
throw new Error(
|
|
11649
|
-
`${
|
|
11630
|
+
`${path30.relative(process.cwd(), dir) || "."} sets ${name} to a different value than the one this gallery already loaded; run a separate gallery for that project so its work uses its own account.`
|
|
11650
11631
|
);
|
|
11651
11632
|
}
|
|
11652
11633
|
}
|
|
@@ -11691,8 +11672,8 @@ async function serveUntilStopped(initial, reload, args, access2) {
|
|
|
11691
11672
|
}
|
|
11692
11673
|
async function runGallery(args) {
|
|
11693
11674
|
if (args.workspace) {
|
|
11694
|
-
const workspacePath =
|
|
11695
|
-
if (!
|
|
11675
|
+
const workspacePath = path30.resolve(args.workspace);
|
|
11676
|
+
if (!existsSync22(workspacePath)) {
|
|
11696
11677
|
throw new Error(`Workspace catalog not found: ${workspacePath}`);
|
|
11697
11678
|
}
|
|
11698
11679
|
const filter2 = { styles: args.styles, assets: args.assets };
|
|
@@ -11712,7 +11693,7 @@ async function runGallery(args) {
|
|
|
11712
11693
|
const registered = async (projectId) => {
|
|
11713
11694
|
const project = (await loadWorkspace(workspacePath)).projects.find((candidate) => candidate.id === projectId);
|
|
11714
11695
|
if (!projectId || !project) throw new Error(`unknown workspace project "${projectId ?? ""}"`);
|
|
11715
|
-
return resolveProject(
|
|
11696
|
+
return resolveProject(path30.dirname(workspacePath), project);
|
|
11716
11697
|
};
|
|
11717
11698
|
await serveUntilStopped(initial, build2, args, {
|
|
11718
11699
|
manifestFor: async (projectId) => (await registered(projectId)).manifestPath,
|
|
@@ -11737,7 +11718,7 @@ async function runGallery(args) {
|
|
|
11737
11718
|
return buildGallerySnapshot({ ...fresh, filter });
|
|
11738
11719
|
};
|
|
11739
11720
|
await serveUntilStopped(await build(), reload, args, {
|
|
11740
|
-
manifestFor: () =>
|
|
11721
|
+
manifestFor: () => path30.resolve(args.manifest),
|
|
11741
11722
|
loadProject: () => openProject(args.manifest, { lockPath: args.lock, env: false })
|
|
11742
11723
|
});
|
|
11743
11724
|
}
|
|
@@ -11989,18 +11970,18 @@ async function runRestoreGeneration(args) {
|
|
|
11989
11970
|
}
|
|
11990
11971
|
|
|
11991
11972
|
// src/cli/commands/housekeeping.ts
|
|
11992
|
-
import { existsSync as
|
|
11973
|
+
import { existsSync as existsSync24 } from "fs";
|
|
11993
11974
|
|
|
11994
11975
|
// src/pipeline/cache-health.ts
|
|
11995
|
-
import { existsSync as
|
|
11996
|
-
import { readFile as
|
|
11997
|
-
import
|
|
11976
|
+
import { existsSync as existsSync23 } from "fs";
|
|
11977
|
+
import { readFile as readFile21, readdir as readdir2, rm as rm10 } from "fs/promises";
|
|
11978
|
+
import path31 from "path";
|
|
11998
11979
|
async function inspectCaches(lock, lockPath, options = {}) {
|
|
11999
|
-
if (options.prune && !
|
|
12000
|
-
throw new Error(`Refusing to prune without an existing lockfile at ${
|
|
11980
|
+
if (options.prune && !existsSync23(lockPath)) {
|
|
11981
|
+
throw new Error(`Refusing to prune without an existing lockfile at ${path31.resolve(lockPath)}`);
|
|
12001
11982
|
}
|
|
12002
|
-
const contentDir =
|
|
12003
|
-
const remotePath =
|
|
11983
|
+
const contentDir = path31.resolve(path31.dirname(lockPath), ".pixelkiln", "cache");
|
|
11984
|
+
const remotePath = path31.resolve(cachePathFor(lockPath));
|
|
12004
11985
|
const referenced = referencedHashes(lock);
|
|
12005
11986
|
let content = await inspectContentCache(contentDir, referenced);
|
|
12006
11987
|
let remoteHashes = await inspectRemoteHashCache(remotePath);
|
|
@@ -12012,7 +11993,7 @@ async function inspectCaches(lock, lockPath, options = {}) {
|
|
|
12012
11993
|
]);
|
|
12013
11994
|
for (const name of names) {
|
|
12014
11995
|
try {
|
|
12015
|
-
await rm10(
|
|
11996
|
+
await rm10(path31.join(contentDir, name), { force: true });
|
|
12016
11997
|
removed.contentFiles++;
|
|
12017
11998
|
} catch {
|
|
12018
11999
|
}
|
|
@@ -12021,7 +12002,7 @@ async function inspectCaches(lock, lockPath, options = {}) {
|
|
|
12021
12002
|
await saveCache(remotePath, { version: 1, hashes: {} });
|
|
12022
12003
|
removed.resetRemoteHashCache = true;
|
|
12023
12004
|
} else if (remoteHashes.invalidIds.length) {
|
|
12024
|
-
const cache = parseCache(JSON.parse(await
|
|
12005
|
+
const cache = parseCache(JSON.parse(await readFile21(remotePath, "utf8")));
|
|
12025
12006
|
for (const id of remoteHashes.invalidIds) delete cache.hashes[id];
|
|
12026
12007
|
removed.remoteHashEntries = remoteHashes.invalidIds.length;
|
|
12027
12008
|
await saveCache(remotePath, cache);
|
|
@@ -12039,7 +12020,7 @@ async function inspectCaches(lock, lockPath, options = {}) {
|
|
|
12039
12020
|
async function inspectContentCache(contentDir, referenced) {
|
|
12040
12021
|
const report = {
|
|
12041
12022
|
path: contentDir,
|
|
12042
|
-
exists:
|
|
12023
|
+
exists: existsSync23(contentDir),
|
|
12043
12024
|
files: 0,
|
|
12044
12025
|
bytes: 0,
|
|
12045
12026
|
valid: 0,
|
|
@@ -12060,12 +12041,12 @@ async function inspectContentCache(contentDir, referenced) {
|
|
|
12060
12041
|
continue;
|
|
12061
12042
|
}
|
|
12062
12043
|
report.files++;
|
|
12063
|
-
const file =
|
|
12044
|
+
const file = path31.join(contentDir, entry.name);
|
|
12064
12045
|
const mediaType = mediaTypeFromExtension(entry.name);
|
|
12065
12046
|
const expected = mediaType ? entry.name.slice(0, -4) : "";
|
|
12066
12047
|
let bytes;
|
|
12067
12048
|
try {
|
|
12068
|
-
bytes = await
|
|
12049
|
+
bytes = await readFile21(file);
|
|
12069
12050
|
report.bytes += bytes.length;
|
|
12070
12051
|
} catch (err) {
|
|
12071
12052
|
report.invalid.push({
|
|
@@ -12103,7 +12084,7 @@ async function inspectContentCache(contentDir, referenced) {
|
|
|
12103
12084
|
async function inspectRemoteHashCache(remotePath) {
|
|
12104
12085
|
const report = {
|
|
12105
12086
|
path: remotePath,
|
|
12106
|
-
exists:
|
|
12087
|
+
exists: existsSync23(remotePath),
|
|
12107
12088
|
entries: 0,
|
|
12108
12089
|
valid: 0,
|
|
12109
12090
|
invalidIds: [],
|
|
@@ -12112,7 +12093,7 @@ async function inspectRemoteHashCache(remotePath) {
|
|
|
12112
12093
|
if (!report.exists) return report;
|
|
12113
12094
|
let cache;
|
|
12114
12095
|
try {
|
|
12115
|
-
cache = parseCache(JSON.parse(await
|
|
12096
|
+
cache = parseCache(JSON.parse(await readFile21(remotePath, "utf8")));
|
|
12116
12097
|
} catch (err) {
|
|
12117
12098
|
report.error = err instanceof Error ? err.message : String(err);
|
|
12118
12099
|
return report;
|
|
@@ -12176,7 +12157,7 @@ async function runAccept(args) {
|
|
|
12176
12157
|
let intact = true;
|
|
12177
12158
|
for (const output of entry.outputs) {
|
|
12178
12159
|
const file = resolveOutputPath(output.path, item.spec.root);
|
|
12179
|
-
if (!
|
|
12160
|
+
if (!existsSync24(file) || await sha256File(file) !== output.sha256) {
|
|
12180
12161
|
intact = false;
|
|
12181
12162
|
break;
|
|
12182
12163
|
}
|
|
@@ -12231,42 +12212,42 @@ This drops their provenance from the lockfile. The art on disk is`);
|
|
|
12231
12212
|
}
|
|
12232
12213
|
|
|
12233
12214
|
// src/cli/commands/init.ts
|
|
12234
|
-
import
|
|
12235
|
-
import { existsSync as
|
|
12215
|
+
import path32 from "path";
|
|
12216
|
+
import { existsSync as existsSync25 } from "fs";
|
|
12236
12217
|
async function runInit(args) {
|
|
12237
12218
|
if (!args.from) throw new Error("init needs --from <dir> pointing at your existing PNGs.");
|
|
12238
|
-
const root =
|
|
12239
|
-
if (!
|
|
12219
|
+
const root = path32.resolve(args.from);
|
|
12220
|
+
if (!existsSync25(root)) throw new Error(`No directory at ${root}`);
|
|
12240
12221
|
const generator = args.generator ?? "map";
|
|
12241
12222
|
if (generator !== "1dir" && generator !== "map") {
|
|
12242
12223
|
throw new Error(`--generator must be "1dir" or "map", got "${args.generator}".`);
|
|
12243
12224
|
}
|
|
12244
|
-
const target =
|
|
12225
|
+
const target = path32.resolve(args.out ?? "pixelkiln.manifest.json");
|
|
12245
12226
|
const { assets, skipped } = await scanAssets(root, { exclude: args.exclude });
|
|
12246
12227
|
if (!assets.length) throw new Error(`No PNGs found under ${root}`);
|
|
12247
12228
|
const manifest = buildManifest(
|
|
12248
|
-
args.name ??
|
|
12229
|
+
args.name ?? path32.basename(path32.dirname(target)),
|
|
12249
12230
|
args.styles[0] ?? "base",
|
|
12250
12231
|
generator,
|
|
12251
|
-
|
|
12232
|
+
path32.relative(path32.dirname(target), root) || ".",
|
|
12252
12233
|
assets
|
|
12253
12234
|
);
|
|
12254
12235
|
await writeManifestFile(target, manifest);
|
|
12255
|
-
log(` scanned ${assets.length} PNG(s) under ${
|
|
12236
|
+
log(` scanned ${assets.length} PNG(s) under ${path32.relative(process.cwd(), root)}`);
|
|
12256
12237
|
if (skipped.length) log(` skipped ${skipped.length} unreadable file(s)`);
|
|
12257
|
-
log(` wrote ${
|
|
12238
|
+
log(` wrote ${path32.relative(process.cwd(), target)}`);
|
|
12258
12239
|
log(`
|
|
12259
12240
|
Prompts are intentionally empty. To recover the real ones from your`);
|
|
12260
12241
|
log(` PixelLab account instead of inventing them:`);
|
|
12261
12242
|
log(`
|
|
12262
|
-
pixelkiln adopt --manifest ${
|
|
12243
|
+
pixelkiln adopt --manifest ${path32.relative(process.cwd(), target)} --write-prompts
|
|
12263
12244
|
`);
|
|
12264
12245
|
}
|
|
12265
12246
|
|
|
12266
12247
|
// src/pipeline/doctor.ts
|
|
12267
|
-
import { constants, existsSync as
|
|
12248
|
+
import { constants, existsSync as existsSync26 } from "fs";
|
|
12268
12249
|
import { access, stat as stat4 } from "fs/promises";
|
|
12269
|
-
import
|
|
12250
|
+
import path33 from "path";
|
|
12270
12251
|
async function doctor(loaded, specs, lock, lockPath, opts = {}) {
|
|
12271
12252
|
const checks = [];
|
|
12272
12253
|
const add = (id, level, message6) => checks.push({ id, level, message: message6 });
|
|
@@ -12277,8 +12258,8 @@ async function doctor(loaded, specs, lock, lockPath, opts = {}) {
|
|
|
12277
12258
|
);
|
|
12278
12259
|
add("lock", "ok", `${Object.keys(lock.entries).length} valid v2 lock entr(ies)`);
|
|
12279
12260
|
const dirs = [...new Set(specs.flatMap((spec) => [
|
|
12280
|
-
|
|
12281
|
-
...spec.quality ? [
|
|
12261
|
+
path33.dirname(spec.outFile),
|
|
12262
|
+
...spec.quality ? [path33.dirname(spec.quality.outFile)] : []
|
|
12282
12263
|
]))];
|
|
12283
12264
|
const unwritable = [];
|
|
12284
12265
|
for (const dir of dirs) {
|
|
@@ -12301,7 +12282,7 @@ async function doctor(loaded, specs, lock, lockPath, opts = {}) {
|
|
|
12301
12282
|
for (let index = 0; index < entry.outputs.length; index++) {
|
|
12302
12283
|
const output = entry.outputs[index];
|
|
12303
12284
|
const spec = specByKey.get(key);
|
|
12304
|
-
const absolute = spec ? currentEntryOutputPath(entry, spec, index) : resolveOutputPath(output.path,
|
|
12285
|
+
const absolute = spec ? currentEntryOutputPath(entry, spec, index) : resolveOutputPath(output.path, path33.dirname(lockPath));
|
|
12305
12286
|
const owner = outputOwners.get(absolute);
|
|
12306
12287
|
if (owner && owner !== key) duplicateOutputs.push(`${output.path} (${owner}, ${key})`);
|
|
12307
12288
|
outputOwners.set(absolute, key);
|
|
@@ -12316,7 +12297,7 @@ async function doctor(loaded, specs, lock, lockPath, opts = {}) {
|
|
|
12316
12297
|
} else {
|
|
12317
12298
|
add("lock-outputs", "ok", `${outputOwners.size} recorded output path(s) are uniquely owned`);
|
|
12318
12299
|
}
|
|
12319
|
-
const cacheDir =
|
|
12300
|
+
const cacheDir = path33.join(path33.dirname(lockPath), ".pixelkiln", "cache");
|
|
12320
12301
|
const stranded = [];
|
|
12321
12302
|
const invalidState = [];
|
|
12322
12303
|
const staleMapUrls = [];
|
|
@@ -12332,11 +12313,11 @@ async function doctor(loaded, specs, lock, lockPath, opts = {}) {
|
|
|
12332
12313
|
const hasSource = Boolean(entry.sourceUrl || entry.sourceUrls?.length);
|
|
12333
12314
|
let hasCache = false;
|
|
12334
12315
|
for (const output of entry.outputs) {
|
|
12335
|
-
const cached =
|
|
12316
|
+
const cached = path33.join(
|
|
12336
12317
|
cacheDir,
|
|
12337
12318
|
cacheFileName(output.sha256, output.mediaType ?? MediaType.PNG)
|
|
12338
12319
|
);
|
|
12339
|
-
if (
|
|
12320
|
+
if (existsSync26(cached) && await sha256File(cached) === output.sha256) {
|
|
12340
12321
|
hasCache = true;
|
|
12341
12322
|
break;
|
|
12342
12323
|
}
|
|
@@ -12432,12 +12413,158 @@ async function nearestExistingDirectory(start) {
|
|
|
12432
12413
|
if ((await stat4(current)).isDirectory()) return current;
|
|
12433
12414
|
} catch {
|
|
12434
12415
|
}
|
|
12435
|
-
const parent =
|
|
12416
|
+
const parent = path33.dirname(current);
|
|
12436
12417
|
if (parent === current) return current;
|
|
12437
12418
|
current = parent;
|
|
12438
12419
|
}
|
|
12439
12420
|
}
|
|
12440
12421
|
|
|
12422
|
+
// src/pipeline/audit.ts
|
|
12423
|
+
import { readFile as readFile22 } from "fs/promises";
|
|
12424
|
+
import { existsSync as existsSync27 } from "fs";
|
|
12425
|
+
import path34 from "path";
|
|
12426
|
+
function paletteDistance(asset, reference) {
|
|
12427
|
+
if (!asset.length || !reference.length) return 0;
|
|
12428
|
+
let total = 0;
|
|
12429
|
+
let weight = 0;
|
|
12430
|
+
for (const color of asset) {
|
|
12431
|
+
let nearest = Infinity;
|
|
12432
|
+
for (const ref of reference) nearest = Math.min(nearest, colorDistance(color, ref));
|
|
12433
|
+
total += nearest * color.weight;
|
|
12434
|
+
weight += color.weight;
|
|
12435
|
+
}
|
|
12436
|
+
return weight === 0 ? 0 : total / weight;
|
|
12437
|
+
}
|
|
12438
|
+
function mergePalettes(palettes, topN = 24) {
|
|
12439
|
+
const totals = /* @__PURE__ */ new Map();
|
|
12440
|
+
for (const palette of palettes) {
|
|
12441
|
+
for (const color of palette) {
|
|
12442
|
+
const key = `${color.r},${color.g},${color.b}`;
|
|
12443
|
+
const existing = totals.get(key);
|
|
12444
|
+
if (existing) existing.weight += color.weight;
|
|
12445
|
+
else totals.set(key, { ...color });
|
|
12446
|
+
}
|
|
12447
|
+
}
|
|
12448
|
+
const merged = [...totals.values()].sort((a, b) => b.weight - a.weight).slice(0, topN);
|
|
12449
|
+
const sum = merged.reduce((s, c) => s + c.weight, 0) || 1;
|
|
12450
|
+
return merged.map((c) => ({ ...c, weight: c.weight / sum }));
|
|
12451
|
+
}
|
|
12452
|
+
async function auditStyle(loaded, specs, styleId, lock) {
|
|
12453
|
+
const style = loaded.manifest.styles[styleId];
|
|
12454
|
+
if (!style) throw new Error(`Unknown style "${styleId}"`);
|
|
12455
|
+
const mine = specs.filter((s) => s.styleId === styleId);
|
|
12456
|
+
const assets = [];
|
|
12457
|
+
const missing = [];
|
|
12458
|
+
const unreadable = [];
|
|
12459
|
+
for (const spec of mine) {
|
|
12460
|
+
for (const output of resolveSpecOutputs(spec, lock, loaded.root)) {
|
|
12461
|
+
if (!existsSync27(output.absolutePath)) {
|
|
12462
|
+
missing.push(output.id);
|
|
12463
|
+
continue;
|
|
12464
|
+
}
|
|
12465
|
+
try {
|
|
12466
|
+
const png = decodePng(await readFile22(output.absolutePath));
|
|
12467
|
+
const palette = extractPalette(png, 12);
|
|
12468
|
+
assets.push({
|
|
12469
|
+
assetId: spec.assetId,
|
|
12470
|
+
...output.role ? { outputRole: output.role } : {},
|
|
12471
|
+
id: output.id,
|
|
12472
|
+
file: output.absolutePath,
|
|
12473
|
+
width: png.width,
|
|
12474
|
+
height: png.height,
|
|
12475
|
+
paletteDistance: 0,
|
|
12476
|
+
// filled once the reference is known
|
|
12477
|
+
transparency: transparencyRatio(png),
|
|
12478
|
+
colorCount: countColors(png),
|
|
12479
|
+
palette
|
|
12480
|
+
});
|
|
12481
|
+
} catch (err) {
|
|
12482
|
+
unreadable.push(`${output.id}: ${err instanceof Error ? err.message : String(err)}`);
|
|
12483
|
+
}
|
|
12484
|
+
}
|
|
12485
|
+
}
|
|
12486
|
+
let reference = [];
|
|
12487
|
+
let referenceFromStyleImages = false;
|
|
12488
|
+
const refPalettes = [];
|
|
12489
|
+
for (const img of style.styleImages) {
|
|
12490
|
+
const abs = path34.resolve(loaded.root, img.path);
|
|
12491
|
+
if (!existsSync27(abs)) continue;
|
|
12492
|
+
try {
|
|
12493
|
+
refPalettes.push(extractPalette(decodePng(await readFile22(abs)), 12));
|
|
12494
|
+
} catch {
|
|
12495
|
+
}
|
|
12496
|
+
}
|
|
12497
|
+
if (refPalettes.length) {
|
|
12498
|
+
reference = mergePalettes(refPalettes);
|
|
12499
|
+
referenceFromStyleImages = true;
|
|
12500
|
+
for (const asset of assets) {
|
|
12501
|
+
asset.paletteDistance = paletteDistance(asset.palette, reference);
|
|
12502
|
+
}
|
|
12503
|
+
} else {
|
|
12504
|
+
reference = mergePalettes(assets.map((a) => a.palette));
|
|
12505
|
+
for (const asset of assets) {
|
|
12506
|
+
const others = assets.filter((a) => a !== asset).map((a) => a.palette);
|
|
12507
|
+
asset.paletteDistance = others.length ? paletteDistance(asset.palette, mergePalettes(others)) : 0;
|
|
12508
|
+
}
|
|
12509
|
+
}
|
|
12510
|
+
assets.sort((a, b) => b.paletteDistance - a.paletteDistance);
|
|
12511
|
+
return { styleId, referenceFromStyleImages, reference, assets, missing, unreadable };
|
|
12512
|
+
}
|
|
12513
|
+
function countColors(png) {
|
|
12514
|
+
const seen = /* @__PURE__ */ new Set();
|
|
12515
|
+
for (let i = 0; i < png.pixels.length; i += 4) {
|
|
12516
|
+
if (png.pixels[i + 3] < 128) continue;
|
|
12517
|
+
seen.add(png.pixels[i] << 16 | png.pixels[i + 1] << 8 | png.pixels[i + 2]);
|
|
12518
|
+
}
|
|
12519
|
+
return seen.size;
|
|
12520
|
+
}
|
|
12521
|
+
function outliers(audit, sigma = 1.5) {
|
|
12522
|
+
const n = audit.assets.length;
|
|
12523
|
+
if (n < 3) return [];
|
|
12524
|
+
const mean = audit.assets.reduce((s, a) => s + a.paletteDistance, 0) / n;
|
|
12525
|
+
const variance = audit.assets.reduce((s, a) => s + (a.paletteDistance - mean) ** 2, 0) / n;
|
|
12526
|
+
const sd = Math.sqrt(variance);
|
|
12527
|
+
if (sd === 0) return [];
|
|
12528
|
+
return audit.assets.filter((a) => a.paletteDistance > mean + sigma * sd);
|
|
12529
|
+
}
|
|
12530
|
+
function evaluateAudit(audit, thresholds = {}) {
|
|
12531
|
+
const resolved = { ...thresholds, sigma: thresholds.sigma ?? 1.5 };
|
|
12532
|
+
const relative = outliers(audit, resolved.sigma);
|
|
12533
|
+
const relativeIds = new Set(relative.map((asset) => asset.id));
|
|
12534
|
+
const violations = [];
|
|
12535
|
+
for (const asset of audit.assets) {
|
|
12536
|
+
const reasons = [];
|
|
12537
|
+
if (relativeIds.has(asset.id)) {
|
|
12538
|
+
reasons.push(`palette outlier beyond ${resolved.sigma} standard deviations`);
|
|
12539
|
+
}
|
|
12540
|
+
if (resolved.maxDistance !== void 0 && asset.paletteDistance > resolved.maxDistance) {
|
|
12541
|
+
reasons.push(
|
|
12542
|
+
`palette distance ${asset.paletteDistance.toFixed(1)} exceeds ${resolved.maxDistance}`
|
|
12543
|
+
);
|
|
12544
|
+
}
|
|
12545
|
+
if (resolved.minTransparency !== void 0 && asset.transparency < resolved.minTransparency) {
|
|
12546
|
+
reasons.push(
|
|
12547
|
+
`transparency ${(asset.transparency * 100).toFixed(1)}% is below ${(resolved.minTransparency * 100).toFixed(1)}%`
|
|
12548
|
+
);
|
|
12549
|
+
}
|
|
12550
|
+
if (resolved.maxColors !== void 0 && asset.colorCount > resolved.maxColors) {
|
|
12551
|
+
reasons.push(`color count ${asset.colorCount} exceeds ${resolved.maxColors}`);
|
|
12552
|
+
}
|
|
12553
|
+
if (reasons.length) violations.push({ id: asset.id, reasons });
|
|
12554
|
+
}
|
|
12555
|
+
return {
|
|
12556
|
+
safe: violations.length === 0 && audit.missing.length === 0 && audit.unreadable.length === 0,
|
|
12557
|
+
thresholds: resolved,
|
|
12558
|
+
outliers: [...relativeIds],
|
|
12559
|
+
violations,
|
|
12560
|
+
missing: [...audit.missing],
|
|
12561
|
+
unreadable: [...audit.unreadable]
|
|
12562
|
+
};
|
|
12563
|
+
}
|
|
12564
|
+
function hex(c) {
|
|
12565
|
+
return `#${[c.r, c.g, c.b].map((v) => v.toString(16).padStart(2, "0")).join("")}`;
|
|
12566
|
+
}
|
|
12567
|
+
|
|
12441
12568
|
// src/cli/commands/inspect.ts
|
|
12442
12569
|
async function runStatus(args) {
|
|
12443
12570
|
const { lock } = await openProject2(args);
|
|
@@ -13993,7 +14120,7 @@ async function readRecipeFile(recipePath, bundled) {
|
|
|
13993
14120
|
` + parsed.error.issues.map((issue) => ` ${issue.path.join(".") || "$"}: ${issue.message}`).join("\n")
|
|
13994
14121
|
);
|
|
13995
14122
|
}
|
|
13996
|
-
return { recipe: parsed.data, path: absolute, dir: path39.dirname(absolute), bundled };
|
|
14123
|
+
return { recipe: parsed.data, authored: raw, path: absolute, dir: path39.dirname(absolute), bundled };
|
|
13997
14124
|
}
|
|
13998
14125
|
async function walkRecipeFiles(root) {
|
|
13999
14126
|
if (!existsSync29(root)) return [];
|
|
@@ -14063,7 +14190,7 @@ async function verifyFile(root, file) {
|
|
|
14063
14190
|
}
|
|
14064
14191
|
async function verifyRecipe(target, options = {}) {
|
|
14065
14192
|
const loaded = await resolveRecipe(target, options.bundledRoot);
|
|
14066
|
-
const actualIntegrity = recipeDigest(loaded.
|
|
14193
|
+
const actualIntegrity = recipeDigest(loaded.authored);
|
|
14067
14194
|
const files = await Promise.all(loaded.recipe.files.map((file) => verifyFile(loaded.dir, file)));
|
|
14068
14195
|
const modelRoot = options.modelRoot ? path39.resolve(options.modelRoot) : null;
|
|
14069
14196
|
const models = modelRoot ? await Promise.all(loaded.recipe.models.map(async (model) => ({
|