pixelkiln 0.41.0 → 0.42.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 +772 -533
- package/dist/cli.js.map +1 -1
- package/dist/client/gallery.js +14 -0
- package/dist/index.cjs +675 -420
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +138 -23
- package/dist/index.d.ts +138 -23
- package/dist/index.js +652 -398
- package/dist/index.js.map +1 -1
- package/docs/GENERATORS.md +6 -2
- package/docs/MANIFEST.md +48 -3
- package/package.json +1 -1
- package/schema/manifest.schema.json +4 -3
- package/skills/pixelkiln/references/pixellab.md +7 -1
package/dist/cli.js
CHANGED
|
@@ -68,7 +68,7 @@ function exitCodeFor(err) {
|
|
|
68
68
|
|
|
69
69
|
// src/cli/main.ts
|
|
70
70
|
import { existsSync as existsSync32 } from "fs";
|
|
71
|
-
import { readFile as
|
|
71
|
+
import { readFile as readFile29 } from "fs/promises";
|
|
72
72
|
|
|
73
73
|
// src/cli/args.ts
|
|
74
74
|
import path from "path";
|
|
@@ -610,7 +610,7 @@ function formatCost(unit, amount) {
|
|
|
610
610
|
|
|
611
611
|
// src/pipeline/plan.ts
|
|
612
612
|
import { existsSync as existsSync5 } from "fs";
|
|
613
|
-
import
|
|
613
|
+
import path9 from "path";
|
|
614
614
|
|
|
615
615
|
// src/hash.ts
|
|
616
616
|
import { createHash } from "crypto";
|
|
@@ -618,10 +618,10 @@ import { createReadStream } from "fs";
|
|
|
618
618
|
function sha256(data) {
|
|
619
619
|
return createHash("sha256").update(data).digest("hex");
|
|
620
620
|
}
|
|
621
|
-
async function sha256File(
|
|
621
|
+
async function sha256File(path45) {
|
|
622
622
|
return new Promise((resolve, reject) => {
|
|
623
623
|
const hash = createHash("sha256");
|
|
624
|
-
const stream = createReadStream(
|
|
624
|
+
const stream = createReadStream(path45);
|
|
625
625
|
stream.on("data", (chunk2) => hash.update(chunk2));
|
|
626
626
|
stream.on("error", reject);
|
|
627
627
|
stream.on("end", () => resolve(hash.digest("hex")));
|
|
@@ -667,6 +667,9 @@ function specHash(spec, styleImageHashes, providerOptionIdentity = spec.provider
|
|
|
667
667
|
state: spec.character.state,
|
|
668
668
|
animation: spec.character.animation
|
|
669
669
|
} : void 0,
|
|
670
|
+
// A mirror's bytes come from its source's recorded outputs; the plan
|
|
671
|
+
// compares those directly, so only the choice of source is identity.
|
|
672
|
+
mirror: spec.mirror ? { of: spec.mirror.sourceAssetId } : void 0,
|
|
670
673
|
revision: spec.revision ? {
|
|
671
674
|
mode: spec.revision.mode,
|
|
672
675
|
from: spec.revision.sourceAssetId,
|
|
@@ -678,9 +681,9 @@ function specHash(spec, styleImageHashes, providerOptionIdentity = spec.provider
|
|
|
678
681
|
);
|
|
679
682
|
}
|
|
680
683
|
|
|
681
|
-
// src/
|
|
682
|
-
import {
|
|
683
|
-
import
|
|
684
|
+
// src/pipeline/mirror.ts
|
|
685
|
+
import { mkdir as mkdir2, readFile as readFile2, rename as rename2, writeFile as writeFile2 } from "fs/promises";
|
|
686
|
+
import path4 from "path";
|
|
684
687
|
|
|
685
688
|
// src/lock.ts
|
|
686
689
|
import { readFile, writeFile, rename, mkdir, rmdir, rm, stat } from "fs/promises";
|
|
@@ -769,6 +772,16 @@ var CHARACTER_DIRECTIONS_8 = [
|
|
|
769
772
|
"south-west"
|
|
770
773
|
];
|
|
771
774
|
var CHARACTER_DIRECTIONS_4 = ["south", "west", "east", "north"];
|
|
775
|
+
var MIRRORED_DIRECTION = {
|
|
776
|
+
south: "south",
|
|
777
|
+
"south-east": "south-west",
|
|
778
|
+
east: "west",
|
|
779
|
+
"north-east": "north-west",
|
|
780
|
+
north: "north",
|
|
781
|
+
"north-west": "north-east",
|
|
782
|
+
west: "east",
|
|
783
|
+
"south-west": "south-east"
|
|
784
|
+
};
|
|
772
785
|
var CharacterModeSchema = z.enum(["standard", "v3", "pro"]);
|
|
773
786
|
var CharacterAnimationModeSchema = z.enum(["template", "v3", "pro"]);
|
|
774
787
|
var CharacterStateSchema = z.object({
|
|
@@ -1028,8 +1041,8 @@ var InheritedStyleSchema = StyleObjectSchema.partial().extend({
|
|
|
1028
1041
|
}).strict();
|
|
1029
1042
|
var StyleInputSchema = z.union([StyleSchema, InheritedStyleSchema]);
|
|
1030
1043
|
var AssetSchema = z.object({
|
|
1031
|
-
/** The subject. Style wrapping comes from the style's prefix/suffix. */
|
|
1032
|
-
prompt: z.string(),
|
|
1044
|
+
/** The subject. Style wrapping comes from the style's prefix/suffix. Only a `mirror` may leave it out. */
|
|
1045
|
+
prompt: z.string().optional(),
|
|
1033
1046
|
/** Subdirectory under the style's outDir. Optional. */
|
|
1034
1047
|
category: z.string().optional(),
|
|
1035
1048
|
/** Overrides the style default. `map` generator only. */
|
|
@@ -1087,6 +1100,19 @@ var AssetSchema = z.object({
|
|
|
1087
1100
|
state: CharacterStateSchema.optional(),
|
|
1088
1101
|
/** `character` styles: this asset is a loop of another character asset in one direction. */
|
|
1089
1102
|
animation: CharacterAnimationSchema.optional(),
|
|
1103
|
+
/**
|
|
1104
|
+
* Another asset of the same style flipped left to right, made locally
|
|
1105
|
+
* from that asset's downloaded files at no generation cost.
|
|
1106
|
+
*
|
|
1107
|
+
* The saving is in character loops, where each direction is its own
|
|
1108
|
+
* generation: a walk facing west mirrored is the walk facing east, and a
|
|
1109
|
+
* diagonal mirrors to the other diagonal. South and north mirror onto
|
|
1110
|
+
* themselves and are refused. A single image or a frame set from any
|
|
1111
|
+
* generator can be mirrored too; a tile set cannot, since its edges
|
|
1112
|
+
* carry meaning. Mirroring swaps handedness, so an asymmetric character
|
|
1113
|
+
* (a sword hand, a parted fringe) comes out the other way round.
|
|
1114
|
+
*/
|
|
1115
|
+
mirror: z.string().min(1).optional(),
|
|
1090
1116
|
/**
|
|
1091
1117
|
* Generated output role to place in `cell` when this asset expands to
|
|
1092
1118
|
* several files. Omit for ordinary single-output assets. A structural set
|
|
@@ -1126,7 +1152,7 @@ var AssetSchema = z.object({
|
|
|
1126
1152
|
path: ["revision"]
|
|
1127
1153
|
});
|
|
1128
1154
|
}
|
|
1129
|
-
const shapes = [asset.revision && "revision", asset.state && "state", asset.animation && "animation"].filter(Boolean);
|
|
1155
|
+
const shapes = [asset.revision && "revision", asset.state && "state", asset.animation && "animation", asset.mirror && "mirror"].filter(Boolean);
|
|
1130
1156
|
if (shapes.length > 1) {
|
|
1131
1157
|
context.addIssue({
|
|
1132
1158
|
code: z.ZodIssueCode.custom,
|
|
@@ -1134,6 +1160,20 @@ var AssetSchema = z.object({
|
|
|
1134
1160
|
path: [shapes[1]]
|
|
1135
1161
|
});
|
|
1136
1162
|
}
|
|
1163
|
+
if (asset.mirror && (asset.source || Object.keys(asset.sourceByStyle).length)) {
|
|
1164
|
+
context.addIssue({
|
|
1165
|
+
code: z.ZodIssueCode.custom,
|
|
1166
|
+
message: "a mirror is made from another asset's files; it cannot also declare a source",
|
|
1167
|
+
path: ["mirror"]
|
|
1168
|
+
});
|
|
1169
|
+
}
|
|
1170
|
+
if (asset.prompt === void 0 && !asset.mirror) {
|
|
1171
|
+
context.addIssue({
|
|
1172
|
+
code: z.ZodIssueCode.custom,
|
|
1173
|
+
message: "Required",
|
|
1174
|
+
path: ["prompt"]
|
|
1175
|
+
});
|
|
1176
|
+
}
|
|
1137
1177
|
});
|
|
1138
1178
|
var DEFAULT_HISTORY_LIMIT = 5;
|
|
1139
1179
|
var HistoryLimitSchema = z.number().int().min(0).max(100);
|
|
@@ -1223,6 +1263,11 @@ var LockEntrySchema = z.object({
|
|
|
1223
1263
|
maskSha256: z.string().regex(/^[0-9a-f]{64}$/).optional(),
|
|
1224
1264
|
strength: z.number().min(0).max(1).optional()
|
|
1225
1265
|
}).strict().nullable().default(null),
|
|
1266
|
+
/** For a mirror: the asset it flips and a hash over that asset's output hashes when this was made. */
|
|
1267
|
+
mirror: z.object({
|
|
1268
|
+
sourceAssetId: z.string().min(1),
|
|
1269
|
+
sourceSha256: z.string().regex(/^[0-9a-f]{64}$/)
|
|
1270
|
+
}).strict().nullable().default(null),
|
|
1226
1271
|
/**
|
|
1227
1272
|
* Output hashes owned by the previous generation while its replacement is
|
|
1228
1273
|
* pending. They authorize replacing only unchanged PixelKiln-owned files.
|
|
@@ -1922,6 +1967,8 @@ function cacheFileName(hash, mediaType = MediaType.PNG) {
|
|
|
1922
1967
|
}
|
|
1923
1968
|
|
|
1924
1969
|
// src/outputs.ts
|
|
1970
|
+
import { existsSync as existsSync2 } from "fs";
|
|
1971
|
+
import path3 from "path";
|
|
1925
1972
|
function portableOutputPath(file, manifestDir) {
|
|
1926
1973
|
const root = path3.resolve(manifestDir);
|
|
1927
1974
|
const absolute = path3.isAbsolute(file) ? path3.normalize(file) : path3.resolve(root, file);
|
|
@@ -2047,21 +2094,219 @@ function frameSetFps(entry) {
|
|
|
2047
2094
|
return null;
|
|
2048
2095
|
}
|
|
2049
2096
|
|
|
2097
|
+
// src/palette.ts
|
|
2098
|
+
function normalizePalette(values) {
|
|
2099
|
+
const colors = /* @__PURE__ */ new Map();
|
|
2100
|
+
for (const value of values) {
|
|
2101
|
+
const rgb = parseHex(value);
|
|
2102
|
+
const hex2 = `#${rgb.r.toString(16).padStart(2, "0")}${rgb.g.toString(16).padStart(2, "0")}${rgb.b.toString(16).padStart(2, "0")}`;
|
|
2103
|
+
if (!colors.has(hex2)) colors.set(hex2, { hex: hex2, ...rgb });
|
|
2104
|
+
}
|
|
2105
|
+
if (colors.size < 2 || colors.size > 256) {
|
|
2106
|
+
throw new Error(`A palette must contain 2\u2013256 unique colors; got ${colors.size}.`);
|
|
2107
|
+
}
|
|
2108
|
+
return [...colors.values()];
|
|
2109
|
+
}
|
|
2110
|
+
function colorDistance(a, b) {
|
|
2111
|
+
const rmean = (a.r + b.r) / 2;
|
|
2112
|
+
const dr = a.r - b.r;
|
|
2113
|
+
const dg = a.g - b.g;
|
|
2114
|
+
const db = a.b - b.b;
|
|
2115
|
+
return Math.sqrt(
|
|
2116
|
+
(512 + rmean) * dr * dr / 256 + 4 * dg * dg + (767 - rmean) * db * db / 256
|
|
2117
|
+
);
|
|
2118
|
+
}
|
|
2119
|
+
function quantize(png, palette) {
|
|
2120
|
+
const colors = normalizePalette(palette);
|
|
2121
|
+
const pixels = Buffer.from(png.pixels);
|
|
2122
|
+
const nearestOf = /* @__PURE__ */ new Map();
|
|
2123
|
+
let changed = 0;
|
|
2124
|
+
for (let i = 0; i < pixels.length; i += 4) {
|
|
2125
|
+
const alpha = pixels[i + 3];
|
|
2126
|
+
if (alpha === 0) {
|
|
2127
|
+
if (pixels[i] || pixels[i + 1] || pixels[i + 2]) changed++;
|
|
2128
|
+
pixels[i] = 0;
|
|
2129
|
+
pixels[i + 1] = 0;
|
|
2130
|
+
pixels[i + 2] = 0;
|
|
2131
|
+
continue;
|
|
2132
|
+
}
|
|
2133
|
+
const key = pixels[i] << 16 | pixels[i + 1] << 8 | pixels[i + 2];
|
|
2134
|
+
let nearest = nearestOf.get(key);
|
|
2135
|
+
if (!nearest) {
|
|
2136
|
+
const pixel = { r: pixels[i], g: pixels[i + 1], b: pixels[i + 2] };
|
|
2137
|
+
nearest = colors[0];
|
|
2138
|
+
let distance = colorDistance(pixel, nearest);
|
|
2139
|
+
for (let j = 1; j < colors.length; j++) {
|
|
2140
|
+
const candidate = colors[j];
|
|
2141
|
+
const candidateDistance = colorDistance(pixel, candidate);
|
|
2142
|
+
if (candidateDistance < distance) {
|
|
2143
|
+
nearest = candidate;
|
|
2144
|
+
distance = candidateDistance;
|
|
2145
|
+
}
|
|
2146
|
+
}
|
|
2147
|
+
nearestOf.set(key, nearest);
|
|
2148
|
+
}
|
|
2149
|
+
if (pixels[i] !== nearest.r || pixels[i + 1] !== nearest.g || pixels[i + 2] !== nearest.b) changed++;
|
|
2150
|
+
pixels[i] = nearest.r;
|
|
2151
|
+
pixels[i + 1] = nearest.g;
|
|
2152
|
+
pixels[i + 2] = nearest.b;
|
|
2153
|
+
}
|
|
2154
|
+
return { png: { width: png.width, height: png.height, pixels }, changed };
|
|
2155
|
+
}
|
|
2156
|
+
function quantizeToPalette(png, palette) {
|
|
2157
|
+
return quantize(png, palette).png;
|
|
2158
|
+
}
|
|
2159
|
+
|
|
2160
|
+
// src/pipeline/postprocess.ts
|
|
2161
|
+
function postprocessFor(spec) {
|
|
2162
|
+
if (!spec.enforcePalette || spec.palette.length < 2) return void 0;
|
|
2163
|
+
return {
|
|
2164
|
+
palette: {
|
|
2165
|
+
colors: normalizePalette(spec.palette).map((color) => color.hex),
|
|
2166
|
+
dither: "none",
|
|
2167
|
+
distance: "redmean"
|
|
2168
|
+
}
|
|
2169
|
+
};
|
|
2170
|
+
}
|
|
2171
|
+
function samePostprocess(a, b) {
|
|
2172
|
+
const pa = a?.palette;
|
|
2173
|
+
const pb = b?.palette;
|
|
2174
|
+
if (!pa && !pb) return true;
|
|
2175
|
+
if (!pa || !pb) return false;
|
|
2176
|
+
return pa.dither === pb.dither && pa.distance === pb.distance && pa.colors.length === pb.colors.length && pa.colors.every((color, i) => color === pb.colors[i]);
|
|
2177
|
+
}
|
|
2178
|
+
function postprocessCurrent(entry, spec) {
|
|
2179
|
+
return samePostprocess(entry.postprocess, postprocessFor(spec));
|
|
2180
|
+
}
|
|
2181
|
+
function applyPostprocess(bytes, mediaType, record) {
|
|
2182
|
+
if (!record?.palette || mediaType !== MediaType.PNG) return { bytes, changed: false };
|
|
2183
|
+
const png = decodePng(bytes);
|
|
2184
|
+
const { png: snapped, changed } = quantize(png, record.palette.colors);
|
|
2185
|
+
if (!changed) return { bytes, changed: false };
|
|
2186
|
+
return { bytes: encodeRgbaPng(snapped.width, snapped.height, snapped.pixels), changed: true };
|
|
2187
|
+
}
|
|
2188
|
+
|
|
2189
|
+
// src/pipeline/mirror.ts
|
|
2190
|
+
function mirrorSourceHash(source) {
|
|
2191
|
+
return sha256(JSON.stringify(source.outputs.map((output) => output.sha256)));
|
|
2192
|
+
}
|
|
2193
|
+
function mirrorCurrent(entry, source) {
|
|
2194
|
+
return Boolean(entry.mirror && source && entry.mirror.sourceSha256 === mirrorSourceHash(source));
|
|
2195
|
+
}
|
|
2196
|
+
function mirroredRole(role) {
|
|
2197
|
+
if (role && role in MIRRORED_DIRECTION) return MIRRORED_DIRECTION[role];
|
|
2198
|
+
return role;
|
|
2199
|
+
}
|
|
2200
|
+
function flipHorizontal(png) {
|
|
2201
|
+
const { width, height, pixels } = decodePng(png);
|
|
2202
|
+
const flipped = Buffer.alloc(pixels.length);
|
|
2203
|
+
const stride = width * 4;
|
|
2204
|
+
for (let y = 0; y < height; y++) {
|
|
2205
|
+
const row = y * stride;
|
|
2206
|
+
for (let x = 0; x < width; x++) {
|
|
2207
|
+
pixels.copy(flipped, row + (width - 1 - x) * 4, row + x * 4, row + x * 4 + 4);
|
|
2208
|
+
}
|
|
2209
|
+
}
|
|
2210
|
+
return encodeRgbaPng(width, height, flipped);
|
|
2211
|
+
}
|
|
2212
|
+
async function deriveMirror(spec, lock, lockPath, opts = {}) {
|
|
2213
|
+
const log2 = opts.onProgress ?? (() => {
|
|
2214
|
+
});
|
|
2215
|
+
const mirror = spec.mirror;
|
|
2216
|
+
const key = lockKey(spec.styleId, spec.assetId);
|
|
2217
|
+
const sourceKey = lockKey(spec.styleId, mirror.sourceAssetId);
|
|
2218
|
+
const source = lock.entries[sourceKey];
|
|
2219
|
+
if (!source || source.status !== "downloaded" || !source.outputs.length) {
|
|
2220
|
+
throw new Error(`mirror source ${sourceKey} has no downloaded outputs`);
|
|
2221
|
+
}
|
|
2222
|
+
const members = source.outputs.map((output, index) => ({
|
|
2223
|
+
file: currentEntryOutputPath(source, mirror.sourceSpec, index),
|
|
2224
|
+
role: mirroredRole(output.role)
|
|
2225
|
+
}));
|
|
2226
|
+
if (members.every((member) => member.role && CHARACTER_DIRECTIONS_8.includes(member.role))) {
|
|
2227
|
+
members.sort((a, b) => CHARACTER_DIRECTIONS_8.indexOf(a.role) - CHARACTER_DIRECTIONS_8.indexOf(b.role));
|
|
2228
|
+
}
|
|
2229
|
+
const outputs = [];
|
|
2230
|
+
const written = [];
|
|
2231
|
+
for (const [index, member] of members.entries()) {
|
|
2232
|
+
const bytes = flipHorizontal(await readFile2(member.file));
|
|
2233
|
+
const target = expectedOutputPath(spec, member.role, index, members.length, MediaType.PNG);
|
|
2234
|
+
await mkdir2(path4.dirname(target), { recursive: true });
|
|
2235
|
+
const tmp = `${target}.pixelkiln.tmp`;
|
|
2236
|
+
await writeFile2(tmp, bytes);
|
|
2237
|
+
await rename2(tmp, target);
|
|
2238
|
+
written.push(target);
|
|
2239
|
+
outputs.push({
|
|
2240
|
+
path: portableOutputPath(target, spec.root),
|
|
2241
|
+
sha256: sha256(bytes),
|
|
2242
|
+
...member.role ? { role: member.role } : {},
|
|
2243
|
+
mediaType: MediaType.PNG
|
|
2244
|
+
});
|
|
2245
|
+
}
|
|
2246
|
+
const sourceMetadata = source.providerMetadata?.[source.provider] ?? {};
|
|
2247
|
+
const character = spec.character;
|
|
2248
|
+
const entry = upsert(lock, key, {
|
|
2249
|
+
styleId: spec.styleId,
|
|
2250
|
+
assetId: spec.assetId,
|
|
2251
|
+
specHash: spec.specHash,
|
|
2252
|
+
generator: spec.generator,
|
|
2253
|
+
tileFeature: null,
|
|
2254
|
+
prompt: "",
|
|
2255
|
+
width: spec.width,
|
|
2256
|
+
height: spec.height,
|
|
2257
|
+
revision: null,
|
|
2258
|
+
mirror: { sourceAssetId: mirror.sourceAssetId, sourceSha256: mirrorSourceHash(source) },
|
|
2259
|
+
supersededOutputs: [],
|
|
2260
|
+
jobId: null,
|
|
2261
|
+
submissionComplete: void 0,
|
|
2262
|
+
reviewObjectId: null,
|
|
2263
|
+
objectId: null,
|
|
2264
|
+
candidateIndex: null,
|
|
2265
|
+
status: "downloaded",
|
|
2266
|
+
error: null,
|
|
2267
|
+
outputs,
|
|
2268
|
+
providerMetadata: {
|
|
2269
|
+
[source.provider]: {
|
|
2270
|
+
...sourceMetadata.frameSet ? { frameSet: sourceMetadata.frameSet } : {},
|
|
2271
|
+
...character ? {
|
|
2272
|
+
character: {
|
|
2273
|
+
kind: character.kind,
|
|
2274
|
+
mirrorOf: mirror.sourceAssetId,
|
|
2275
|
+
...character.animation ? { direction: character.animation.direction } : {}
|
|
2276
|
+
}
|
|
2277
|
+
} : {}
|
|
2278
|
+
}
|
|
2279
|
+
},
|
|
2280
|
+
sourceUrl: null,
|
|
2281
|
+
sourceUrls: [],
|
|
2282
|
+
submittedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2283
|
+
downloadedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2284
|
+
history: [],
|
|
2285
|
+
cost: 0,
|
|
2286
|
+
costUnit: spec.costUnit,
|
|
2287
|
+
provider: source.provider,
|
|
2288
|
+
postprocess: postprocessFor(spec)
|
|
2289
|
+
});
|
|
2290
|
+
await saveLock(lockPath, lock);
|
|
2291
|
+
log2(` mirror ${key} \u2190 ${sourceKey} (${written.length} file${written.length === 1 ? "" : "s"} flipped, no generation cost)`);
|
|
2292
|
+
return entry;
|
|
2293
|
+
}
|
|
2294
|
+
|
|
2050
2295
|
// src/pipeline/quality-profile.ts
|
|
2051
2296
|
import { existsSync as existsSync3 } from "fs";
|
|
2052
|
-
import
|
|
2297
|
+
import path7 from "path";
|
|
2053
2298
|
|
|
2054
2299
|
// src/pipeline/refine.ts
|
|
2055
|
-
import
|
|
2300
|
+
import path6 from "path";
|
|
2056
2301
|
import { execFile } from "child_process";
|
|
2057
|
-
import { mkdtemp, readFile as
|
|
2302
|
+
import { mkdtemp, readFile as readFile4, rm as rm3 } from "fs/promises";
|
|
2058
2303
|
import { tmpdir } from "os";
|
|
2059
2304
|
import { promisify } from "util";
|
|
2060
2305
|
|
|
2061
2306
|
// src/artifacts.ts
|
|
2062
|
-
import
|
|
2307
|
+
import path5 from "path";
|
|
2063
2308
|
import { createHash as createHash2, randomUUID } from "crypto";
|
|
2064
|
-
import { mkdir as
|
|
2309
|
+
import { mkdir as mkdir3, readFile as readFile3, rename as rename3, rm as rm2, writeFile as writeFile3 } from "fs/promises";
|
|
2065
2310
|
var activeTransactions = /* @__PURE__ */ new Set();
|
|
2066
2311
|
function message(error) {
|
|
2067
2312
|
return error instanceof Error ? error.message : String(error);
|
|
@@ -2070,7 +2315,7 @@ function digest(data) {
|
|
|
2070
2315
|
return createHash2("sha256").update(data).digest("hex");
|
|
2071
2316
|
}
|
|
2072
2317
|
function portableRelative(from, to) {
|
|
2073
|
-
return
|
|
2318
|
+
return path5.relative(from, path5.resolve(to)).split(path5.sep).join("/") || ".";
|
|
2074
2319
|
}
|
|
2075
2320
|
function canonical(value) {
|
|
2076
2321
|
if (value === null || typeof value === "string" || typeof value === "boolean") return value;
|
|
@@ -2120,12 +2365,12 @@ function parseArtifactManifest(absolute, data) {
|
|
|
2120
2365
|
return raw;
|
|
2121
2366
|
}
|
|
2122
2367
|
async function readArtifactBundleManifest(manifestPath) {
|
|
2123
|
-
const absolute =
|
|
2124
|
-
return parseArtifactManifest(absolute, await
|
|
2368
|
+
const absolute = path5.resolve(manifestPath);
|
|
2369
|
+
return parseArtifactManifest(absolute, await readFile3(absolute, "utf8"));
|
|
2125
2370
|
}
|
|
2126
2371
|
async function readOptional(file) {
|
|
2127
2372
|
try {
|
|
2128
|
-
return await
|
|
2373
|
+
return await readFile3(file);
|
|
2129
2374
|
} catch (error) {
|
|
2130
2375
|
if (isCode(error, "ENOENT")) return null;
|
|
2131
2376
|
throw error;
|
|
@@ -2144,8 +2389,8 @@ function processIsAlive(pid) {
|
|
|
2144
2389
|
}
|
|
2145
2390
|
}
|
|
2146
2391
|
function validTemporaryPath(candidate, destination, type) {
|
|
2147
|
-
return
|
|
2148
|
-
`.${
|
|
2392
|
+
return path5.dirname(candidate) === path5.dirname(destination) && path5.basename(candidate).startsWith(
|
|
2393
|
+
`.${path5.basename(destination)}.pixelkiln-${type}-`
|
|
2149
2394
|
);
|
|
2150
2395
|
}
|
|
2151
2396
|
function parseTransaction(journal, data) {
|
|
@@ -2165,7 +2410,7 @@ async function removeTransactionFiles(journal) {
|
|
|
2165
2410
|
await rm2(transactionMarker(journal), { force: true });
|
|
2166
2411
|
}
|
|
2167
2412
|
async function recoverArtifactTransaction(recoveryFile, allowedDestinations) {
|
|
2168
|
-
const journal =
|
|
2413
|
+
const journal = path5.resolve(recoveryFile);
|
|
2169
2414
|
const bytes = await readOptional(journal);
|
|
2170
2415
|
if (!bytes) {
|
|
2171
2416
|
await rm2(transactionMarker(journal), { force: true });
|
|
@@ -2181,7 +2426,7 @@ async function recoverArtifactTransaction(recoveryFile, allowedDestinations) {
|
|
|
2181
2426
|
);
|
|
2182
2427
|
}
|
|
2183
2428
|
for (const entry of transaction.entries) {
|
|
2184
|
-
if (typeof entry.destination !== "string" || typeof entry.stage !== "string" || typeof entry.sha256 !== "string" || entry.backup !== void 0 && typeof entry.backup !== "string" || !allowedDestinations.has(
|
|
2429
|
+
if (typeof entry.destination !== "string" || typeof entry.stage !== "string" || typeof entry.sha256 !== "string" || entry.backup !== void 0 && typeof entry.backup !== "string" || !allowedDestinations.has(path5.resolve(entry.destination)) || !validTemporaryPath(entry.stage, entry.destination, "stage") || entry.backup !== void 0 && !validTemporaryPath(entry.backup, entry.destination, "backup")) {
|
|
2185
2430
|
throw new Error(
|
|
2186
2431
|
`Refusing unsafe artifact recovery from ${journal}; its destinations or temporary paths do not match the current bundle.`
|
|
2187
2432
|
);
|
|
@@ -2207,7 +2452,7 @@ async function recoverArtifactTransaction(recoveryFile, allowedDestinations) {
|
|
|
2207
2452
|
}
|
|
2208
2453
|
await remove2(entry.destination, errors);
|
|
2209
2454
|
try {
|
|
2210
|
-
await
|
|
2455
|
+
await rename3(entry.backup, entry.destination);
|
|
2211
2456
|
} catch (error) {
|
|
2212
2457
|
errors.push(
|
|
2213
2458
|
`could not restore ${entry.destination}; previous file remains at ${entry.backup}: ${message(error)}`
|
|
@@ -2227,7 +2472,7 @@ async function recoverArtifactTransaction(recoveryFile, allowedDestinations) {
|
|
|
2227
2472
|
await removeTransactionFiles(journal);
|
|
2228
2473
|
}
|
|
2229
2474
|
function createArtifactBundleManifest(manifestPath, outputs, provenance) {
|
|
2230
|
-
const root =
|
|
2475
|
+
const root = path5.dirname(path5.resolve(manifestPath));
|
|
2231
2476
|
const sources = provenance.sources.map((source) => ({
|
|
2232
2477
|
...source,
|
|
2233
2478
|
path: portableRelative(root, source.path)
|
|
@@ -2257,11 +2502,11 @@ function withArtifactManifest(manifestPath, outputs, provenance) {
|
|
|
2257
2502
|
];
|
|
2258
2503
|
}
|
|
2259
2504
|
async function writeManagedArtifactBundle(manifestPath, outputs, provenance, options = {}) {
|
|
2260
|
-
const absoluteManifest =
|
|
2505
|
+
const absoluteManifest = path5.resolve(manifestPath);
|
|
2261
2506
|
const recoveryFile = `${absoluteManifest}.transaction`;
|
|
2262
2507
|
const allowedDestinations = /* @__PURE__ */ new Set([
|
|
2263
2508
|
absoluteManifest,
|
|
2264
|
-
...outputs.map((output) =>
|
|
2509
|
+
...outputs.map((output) => path5.resolve(output.path))
|
|
2265
2510
|
]);
|
|
2266
2511
|
await recoverArtifactTransaction(recoveryFile, allowedDestinations);
|
|
2267
2512
|
const existingManifestBytes = await readOptional(absoluteManifest);
|
|
@@ -2279,13 +2524,13 @@ async function writeManagedArtifactBundle(manifestPath, outputs, provenance, opt
|
|
|
2279
2524
|
}
|
|
2280
2525
|
}
|
|
2281
2526
|
if (!options.force) {
|
|
2282
|
-
const root =
|
|
2527
|
+
const root = path5.dirname(absoluteManifest);
|
|
2283
2528
|
const recorded = new Map(
|
|
2284
|
-
previous?.outputs.map((output) => [
|
|
2529
|
+
previous?.outputs.map((output) => [path5.resolve(root, output.path), output.sha256]) ?? []
|
|
2285
2530
|
);
|
|
2286
2531
|
const conflicts = [];
|
|
2287
2532
|
for (const output of outputs) {
|
|
2288
|
-
const destination =
|
|
2533
|
+
const destination = path5.resolve(output.path);
|
|
2289
2534
|
const current = await readOptional(destination);
|
|
2290
2535
|
if (!current || digest(current) === digest(output.data)) continue;
|
|
2291
2536
|
const expected = recorded.get(destination);
|
|
@@ -2307,15 +2552,15 @@ Pass { force: true } (CLI: --force) to take ownership and replace the complete b
|
|
|
2307
2552
|
);
|
|
2308
2553
|
}
|
|
2309
2554
|
async function verifyArtifactBundle(manifestPath) {
|
|
2310
|
-
const absolute =
|
|
2555
|
+
const absolute = path5.resolve(manifestPath);
|
|
2311
2556
|
const raw = await readArtifactBundleManifest(absolute);
|
|
2312
|
-
const root =
|
|
2557
|
+
const root = path5.dirname(absolute);
|
|
2313
2558
|
const changedSources = [];
|
|
2314
2559
|
const changedOutputs = [];
|
|
2315
2560
|
for (const source of raw.sources) {
|
|
2316
2561
|
let actual = null;
|
|
2317
2562
|
try {
|
|
2318
|
-
actual = digest(await
|
|
2563
|
+
actual = digest(await readFile3(path5.resolve(root, source.path)));
|
|
2319
2564
|
} catch (error) {
|
|
2320
2565
|
if (!isCode(error, "ENOENT")) throw error;
|
|
2321
2566
|
}
|
|
@@ -2323,7 +2568,7 @@ async function verifyArtifactBundle(manifestPath) {
|
|
|
2323
2568
|
}
|
|
2324
2569
|
for (const output of raw.outputs) {
|
|
2325
2570
|
try {
|
|
2326
|
-
if (digest(await
|
|
2571
|
+
if (digest(await readFile3(path5.resolve(root, output.path))) !== output.sha256) {
|
|
2327
2572
|
changedOutputs.push(output.path);
|
|
2328
2573
|
}
|
|
2329
2574
|
} catch (error) {
|
|
@@ -2362,7 +2607,7 @@ async function rollback(prepared) {
|
|
|
2362
2607
|
for (const artifact of [...prepared].reverse()) {
|
|
2363
2608
|
if (!artifact.backup || !artifact.backedUp) continue;
|
|
2364
2609
|
try {
|
|
2365
|
-
await
|
|
2610
|
+
await rename3(artifact.backup, artifact.destination);
|
|
2366
2611
|
artifact.backedUp = false;
|
|
2367
2612
|
} catch (error) {
|
|
2368
2613
|
errors.push(
|
|
@@ -2379,7 +2624,7 @@ async function writeArtifactBundle(files, options = {}) {
|
|
|
2379
2624
|
if (!files.length) throw new Error("An artifact bundle must contain at least one file.");
|
|
2380
2625
|
const normalized = files.map((file) => {
|
|
2381
2626
|
if (!file.path.trim()) throw new Error("Artifact paths cannot be empty.");
|
|
2382
|
-
return { destination:
|
|
2627
|
+
return { destination: path5.resolve(file.path), data: Buffer.from(file.data) };
|
|
2383
2628
|
});
|
|
2384
2629
|
const destinations = /* @__PURE__ */ new Set();
|
|
2385
2630
|
for (const artifact of normalized) {
|
|
@@ -2395,7 +2640,7 @@ async function writeArtifactBundle(files, options = {}) {
|
|
|
2395
2640
|
const unchanged = [];
|
|
2396
2641
|
for (const artifact of normalized) {
|
|
2397
2642
|
try {
|
|
2398
|
-
const current = await
|
|
2643
|
+
const current = await readFile3(artifact.destination);
|
|
2399
2644
|
if (current.equals(artifact.data)) {
|
|
2400
2645
|
unchanged.push(artifact.destination);
|
|
2401
2646
|
} else {
|
|
@@ -2409,21 +2654,21 @@ async function writeArtifactBundle(files, options = {}) {
|
|
|
2409
2654
|
if (!changed.length) return { changed: [], unchanged };
|
|
2410
2655
|
const token = randomUUID();
|
|
2411
2656
|
for (const [index, artifact] of changed.entries()) {
|
|
2412
|
-
const basename =
|
|
2413
|
-
artifact.stage =
|
|
2414
|
-
|
|
2657
|
+
const basename = path5.basename(artifact.destination);
|
|
2658
|
+
artifact.stage = path5.join(
|
|
2659
|
+
path5.dirname(artifact.destination),
|
|
2415
2660
|
`.${basename}.pixelkiln-stage-${token}-${index}`
|
|
2416
2661
|
);
|
|
2417
2662
|
if (artifact.existed) {
|
|
2418
|
-
artifact.backup =
|
|
2419
|
-
|
|
2663
|
+
artifact.backup = path5.join(
|
|
2664
|
+
path5.dirname(artifact.destination),
|
|
2420
2665
|
`.${basename}.pixelkiln-backup-${token}-${index}`
|
|
2421
2666
|
);
|
|
2422
2667
|
}
|
|
2423
2668
|
}
|
|
2424
|
-
const journal = options.recoveryFile ?
|
|
2669
|
+
const journal = options.recoveryFile ? path5.resolve(options.recoveryFile) : null;
|
|
2425
2670
|
if (journal) {
|
|
2426
|
-
await
|
|
2671
|
+
await mkdir3(path5.dirname(journal), { recursive: true });
|
|
2427
2672
|
const transaction = {
|
|
2428
2673
|
format: "pixelkiln-artifact-transaction",
|
|
2429
2674
|
version: 1,
|
|
@@ -2437,7 +2682,7 @@ async function writeArtifactBundle(files, options = {}) {
|
|
|
2437
2682
|
}))
|
|
2438
2683
|
};
|
|
2439
2684
|
try {
|
|
2440
|
-
await
|
|
2685
|
+
await writeFile3(journal, JSON.stringify(transaction, null, 2) + "\n", { flag: "wx" });
|
|
2441
2686
|
activeTransactions.add(journal);
|
|
2442
2687
|
} catch (error) {
|
|
2443
2688
|
throw new Error(
|
|
@@ -2448,9 +2693,9 @@ async function writeArtifactBundle(files, options = {}) {
|
|
|
2448
2693
|
}
|
|
2449
2694
|
try {
|
|
2450
2695
|
for (const [index, artifact] of changed.entries()) {
|
|
2451
|
-
await
|
|
2696
|
+
await mkdir3(path5.dirname(artifact.destination), { recursive: true });
|
|
2452
2697
|
await options.beforeStage?.(artifact.destination, index);
|
|
2453
|
-
await
|
|
2698
|
+
await writeFile3(artifact.stage, artifact.data, { flag: "wx" });
|
|
2454
2699
|
}
|
|
2455
2700
|
} catch (error) {
|
|
2456
2701
|
const cleanupErrors2 = await rollback(changed);
|
|
@@ -2463,17 +2708,17 @@ async function writeArtifactBundle(files, options = {}) {
|
|
|
2463
2708
|
try {
|
|
2464
2709
|
for (const artifact of changed) {
|
|
2465
2710
|
if (!artifact.existed) continue;
|
|
2466
|
-
await
|
|
2711
|
+
await rename3(artifact.destination, artifact.backup);
|
|
2467
2712
|
artifact.backedUp = true;
|
|
2468
2713
|
}
|
|
2469
2714
|
for (const [index, artifact] of changed.entries()) {
|
|
2470
2715
|
await options.beforePromote?.(artifact.destination, index);
|
|
2471
|
-
await
|
|
2716
|
+
await rename3(artifact.stage, artifact.destination);
|
|
2472
2717
|
artifact.stage = void 0;
|
|
2473
2718
|
artifact.promoted = true;
|
|
2474
2719
|
}
|
|
2475
2720
|
if (journal) {
|
|
2476
|
-
await
|
|
2721
|
+
await writeFile3(transactionMarker(journal), `${token}
|
|
2477
2722
|
`, { flag: "wx" });
|
|
2478
2723
|
durableCommit = true;
|
|
2479
2724
|
await options.afterCommit?.();
|
|
@@ -2515,69 +2760,6 @@ async function writeArtifactBundle(files, options = {}) {
|
|
|
2515
2760
|
};
|
|
2516
2761
|
}
|
|
2517
2762
|
|
|
2518
|
-
// src/palette.ts
|
|
2519
|
-
function normalizePalette(values) {
|
|
2520
|
-
const colors = /* @__PURE__ */ new Map();
|
|
2521
|
-
for (const value of values) {
|
|
2522
|
-
const rgb = parseHex(value);
|
|
2523
|
-
const hex2 = `#${rgb.r.toString(16).padStart(2, "0")}${rgb.g.toString(16).padStart(2, "0")}${rgb.b.toString(16).padStart(2, "0")}`;
|
|
2524
|
-
if (!colors.has(hex2)) colors.set(hex2, { hex: hex2, ...rgb });
|
|
2525
|
-
}
|
|
2526
|
-
if (colors.size < 2 || colors.size > 256) {
|
|
2527
|
-
throw new Error(`A palette must contain 2\u2013256 unique colors; got ${colors.size}.`);
|
|
2528
|
-
}
|
|
2529
|
-
return [...colors.values()];
|
|
2530
|
-
}
|
|
2531
|
-
function colorDistance(a, b) {
|
|
2532
|
-
const rmean = (a.r + b.r) / 2;
|
|
2533
|
-
const dr = a.r - b.r;
|
|
2534
|
-
const dg = a.g - b.g;
|
|
2535
|
-
const db = a.b - b.b;
|
|
2536
|
-
return Math.sqrt(
|
|
2537
|
-
(512 + rmean) * dr * dr / 256 + 4 * dg * dg + (767 - rmean) * db * db / 256
|
|
2538
|
-
);
|
|
2539
|
-
}
|
|
2540
|
-
function quantize(png, palette) {
|
|
2541
|
-
const colors = normalizePalette(palette);
|
|
2542
|
-
const pixels = Buffer.from(png.pixels);
|
|
2543
|
-
const nearestOf = /* @__PURE__ */ new Map();
|
|
2544
|
-
let changed = 0;
|
|
2545
|
-
for (let i = 0; i < pixels.length; i += 4) {
|
|
2546
|
-
const alpha = pixels[i + 3];
|
|
2547
|
-
if (alpha === 0) {
|
|
2548
|
-
if (pixels[i] || pixels[i + 1] || pixels[i + 2]) changed++;
|
|
2549
|
-
pixels[i] = 0;
|
|
2550
|
-
pixels[i + 1] = 0;
|
|
2551
|
-
pixels[i + 2] = 0;
|
|
2552
|
-
continue;
|
|
2553
|
-
}
|
|
2554
|
-
const key = pixels[i] << 16 | pixels[i + 1] << 8 | pixels[i + 2];
|
|
2555
|
-
let nearest = nearestOf.get(key);
|
|
2556
|
-
if (!nearest) {
|
|
2557
|
-
const pixel = { r: pixels[i], g: pixels[i + 1], b: pixels[i + 2] };
|
|
2558
|
-
nearest = colors[0];
|
|
2559
|
-
let distance = colorDistance(pixel, nearest);
|
|
2560
|
-
for (let j = 1; j < colors.length; j++) {
|
|
2561
|
-
const candidate = colors[j];
|
|
2562
|
-
const candidateDistance = colorDistance(pixel, candidate);
|
|
2563
|
-
if (candidateDistance < distance) {
|
|
2564
|
-
nearest = candidate;
|
|
2565
|
-
distance = candidateDistance;
|
|
2566
|
-
}
|
|
2567
|
-
}
|
|
2568
|
-
nearestOf.set(key, nearest);
|
|
2569
|
-
}
|
|
2570
|
-
if (pixels[i] !== nearest.r || pixels[i + 1] !== nearest.g || pixels[i + 2] !== nearest.b) changed++;
|
|
2571
|
-
pixels[i] = nearest.r;
|
|
2572
|
-
pixels[i + 1] = nearest.g;
|
|
2573
|
-
pixels[i + 2] = nearest.b;
|
|
2574
|
-
}
|
|
2575
|
-
return { png: { width: png.width, height: png.height, pixels }, changed };
|
|
2576
|
-
}
|
|
2577
|
-
function quantizeToPalette(png, palette) {
|
|
2578
|
-
return quantize(png, palette).png;
|
|
2579
|
-
}
|
|
2580
|
-
|
|
2581
2763
|
// src/pipeline/refine.ts
|
|
2582
2764
|
var execFileAsync = promisify(execFile);
|
|
2583
2765
|
var PIXEL_ART_FIXER_MIN_SIDE = 16;
|
|
@@ -2777,16 +2959,16 @@ function sameFrameGrid(first, next) {
|
|
|
2777
2959
|
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;
|
|
2778
2960
|
}
|
|
2779
2961
|
async function refineAsset(options) {
|
|
2780
|
-
const source =
|
|
2781
|
-
const output =
|
|
2962
|
+
const source = path6.resolve(options.source);
|
|
2963
|
+
const output = path6.resolve(options.output);
|
|
2782
2964
|
if (source === output) throw new Error("Refinement source and output must be different files.");
|
|
2783
|
-
const record =
|
|
2965
|
+
const record = path6.resolve(qualityRecordPath(output));
|
|
2784
2966
|
const palette = normalizePalette(options.palette).map((color) => color.hex);
|
|
2785
2967
|
const minGridConfidence = requireConfidence(options.minGridConfidence ?? "high");
|
|
2786
2968
|
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.");
|
|
2787
2969
|
let sourcePng;
|
|
2788
2970
|
try {
|
|
2789
|
-
sourcePng = decodePng(await
|
|
2971
|
+
sourcePng = decodePng(await readFile4(source));
|
|
2790
2972
|
} catch (error) {
|
|
2791
2973
|
throw new Error(`Cannot read refinement source ${source}: ${message2(error)}`, { cause: error });
|
|
2792
2974
|
}
|
|
@@ -2803,8 +2985,8 @@ async function refineAsset(options) {
|
|
|
2803
2985
|
);
|
|
2804
2986
|
}
|
|
2805
2987
|
}
|
|
2806
|
-
const temp = await mkdtemp(
|
|
2807
|
-
const recovered =
|
|
2988
|
+
const temp = await mkdtemp(path6.join(tmpdir(), "pixelkiln-refine-"));
|
|
2989
|
+
const recovered = path6.join(temp, "native.png");
|
|
2808
2990
|
const fixerCommand = compatibilityCommand ?? options.fixerPython ?? process.env.PIXELKILN_PIXEL_FIXER_PYTHON ?? "python3";
|
|
2809
2991
|
const args = compatibilityCommand ? [...options.fixerArgsPrefix ?? [], source, "--extract", recovered] : ["-c", PYTHON_FIXER_BRIDGE, source, recovered];
|
|
2810
2992
|
try {
|
|
@@ -2825,7 +3007,7 @@ async function refineAsset(options) {
|
|
|
2825
3007
|
);
|
|
2826
3008
|
}
|
|
2827
3009
|
const detection = parseDetection(firstJsonObject(stdout));
|
|
2828
|
-
const native = decodePng(await
|
|
3010
|
+
const native = decodePng(await readFile4(recovered));
|
|
2829
3011
|
const quantized = quantizeToPalette(native, palette);
|
|
2830
3012
|
const png = encodeRgbaPng(quantized.width, quantized.height, quantized.pixels);
|
|
2831
3013
|
const audit = auditRefinement(
|
|
@@ -2889,18 +3071,18 @@ async function refineFrameSet(options) {
|
|
|
2889
3071
|
if (roles.some((role) => !role.trim()) || new Set(roles).size !== roles.length) {
|
|
2890
3072
|
throw new Error("Frame-set roles must be non-empty and unique.");
|
|
2891
3073
|
}
|
|
2892
|
-
const output =
|
|
2893
|
-
const record =
|
|
3074
|
+
const output = path6.resolve(options.output);
|
|
3075
|
+
const record = path6.resolve(qualityRecordPath(output));
|
|
2894
3076
|
const palette = normalizeRefinementPalette(options.palette);
|
|
2895
3077
|
const finalOutputs = options.sources.map((source, index) => qualityFrameOutputPath(output, source.role, index));
|
|
2896
3078
|
if (new Set(finalOutputs).size !== finalOutputs.length) {
|
|
2897
3079
|
throw new Error("Frame-set roles resolve to duplicate output filenames.");
|
|
2898
3080
|
}
|
|
2899
|
-
const sourceSet = new Set(options.sources.map((source) =>
|
|
3081
|
+
const sourceSet = new Set(options.sources.map((source) => path6.resolve(source.path)));
|
|
2900
3082
|
if (finalOutputs.some((file) => sourceSet.has(file))) {
|
|
2901
3083
|
throw new Error("Frame-set refinement sources and outputs must be different files.");
|
|
2902
3084
|
}
|
|
2903
|
-
const temp = await mkdtemp(
|
|
3085
|
+
const temp = await mkdtemp(path6.join(tmpdir(), "pixelkiln-refine-frames-"));
|
|
2904
3086
|
try {
|
|
2905
3087
|
const frames = [];
|
|
2906
3088
|
const outputs = [];
|
|
@@ -2908,8 +3090,8 @@ async function refineFrameSet(options) {
|
|
|
2908
3090
|
const sourcePaths = [];
|
|
2909
3091
|
let firstOptions = null;
|
|
2910
3092
|
for (const [index, sourceEntry] of options.sources.entries()) {
|
|
2911
|
-
const source =
|
|
2912
|
-
const temporaryOutput =
|
|
3093
|
+
const source = path6.resolve(sourceEntry.path);
|
|
3094
|
+
const temporaryOutput = path6.join(temp, `${String(index).padStart(2, "0")}.png`);
|
|
2913
3095
|
const refined = await refineAsset({
|
|
2914
3096
|
source,
|
|
2915
3097
|
output: temporaryOutput,
|
|
@@ -2941,7 +3123,7 @@ async function refineFrameSet(options) {
|
|
|
2941
3123
|
});
|
|
2942
3124
|
outputs.push({
|
|
2943
3125
|
path: finalOutputs[index],
|
|
2944
|
-
data: await
|
|
3126
|
+
data: await readFile4(temporaryOutput)
|
|
2945
3127
|
});
|
|
2946
3128
|
}
|
|
2947
3129
|
const first = frames[0];
|
|
@@ -2984,7 +3166,7 @@ async function refineFrameSet(options) {
|
|
|
2984
3166
|
);
|
|
2985
3167
|
return {
|
|
2986
3168
|
sources: sourcePaths,
|
|
2987
|
-
outputs: outputs.map((item) =>
|
|
3169
|
+
outputs: outputs.map((item) => path6.resolve(item.path)),
|
|
2988
3170
|
record,
|
|
2989
3171
|
frames,
|
|
2990
3172
|
palette,
|
|
@@ -2995,12 +3177,12 @@ async function refineFrameSet(options) {
|
|
|
2995
3177
|
}
|
|
2996
3178
|
}
|
|
2997
3179
|
async function checkQualityRecord(recordPath) {
|
|
2998
|
-
const record =
|
|
3180
|
+
const record = path6.resolve(recordPath);
|
|
2999
3181
|
const manifest = await readArtifactBundleManifest(record);
|
|
3000
3182
|
const options = parseRecordOptions(manifest, record);
|
|
3001
3183
|
const verification = await verifyArtifactBundle(record);
|
|
3002
|
-
const sources = manifest.sources.map((source2) =>
|
|
3003
|
-
const outputs = manifest.outputs.map((output2) =>
|
|
3184
|
+
const sources = manifest.sources.map((source2) => path6.resolve(path6.dirname(record), source2.path));
|
|
3185
|
+
const outputs = manifest.outputs.map((output2) => path6.resolve(path6.dirname(record), output2.path));
|
|
3004
3186
|
const source = sources[0] ?? "";
|
|
3005
3187
|
const output = outputs[0] ?? "";
|
|
3006
3188
|
const expectedCount = options.frameSet?.count ?? 1;
|
|
@@ -3033,7 +3215,7 @@ async function checkQualityRecord(recordPath) {
|
|
|
3033
3215
|
async function approveQualityRecord(recordPath, approval) {
|
|
3034
3216
|
const reviewer = approval.reviewer.trim();
|
|
3035
3217
|
if (!reviewer) throw new Error("A human reviewer name is required.");
|
|
3036
|
-
const record =
|
|
3218
|
+
const record = path6.resolve(recordPath);
|
|
3037
3219
|
const manifest = await readArtifactBundleManifest(record);
|
|
3038
3220
|
const options = parseRecordOptions(manifest, record);
|
|
3039
3221
|
const verification = await verifyArtifactBundle(record);
|
|
@@ -3054,14 +3236,14 @@ async function approveQualityRecord(recordPath, approval) {
|
|
|
3054
3236
|
`A refinement record must contain exactly ${expectedCount} source(s) and output(s).`
|
|
3055
3237
|
);
|
|
3056
3238
|
}
|
|
3057
|
-
const root =
|
|
3239
|
+
const root = path6.dirname(record);
|
|
3058
3240
|
const outputs = await Promise.all(manifest.outputs.map(async (entry) => {
|
|
3059
|
-
const outputPath =
|
|
3060
|
-
return { path: outputPath, data: await
|
|
3241
|
+
const outputPath = path6.resolve(root, entry.path);
|
|
3242
|
+
return { path: outputPath, data: await readFile4(outputPath) };
|
|
3061
3243
|
}));
|
|
3062
3244
|
const sources = manifest.sources.map((source) => ({
|
|
3063
3245
|
...source,
|
|
3064
|
-
path:
|
|
3246
|
+
path: path6.resolve(root, source.path)
|
|
3065
3247
|
}));
|
|
3066
3248
|
const approved = {
|
|
3067
3249
|
...options,
|
|
@@ -3093,7 +3275,7 @@ async function qualitySource(spec, lock) {
|
|
|
3093
3275
|
if (spec.generator === "frames") {
|
|
3094
3276
|
return { items: null, reason: "frame-set quality requires ordered downloaded provider outputs" };
|
|
3095
3277
|
}
|
|
3096
|
-
const source =
|
|
3278
|
+
const source = path7.resolve(spec.root, spec.source);
|
|
3097
3279
|
return existsSync3(source) ? { items: [{ role: "source", path: source }] } : { items: null, reason: `declared source is missing: ${spec.source}` };
|
|
3098
3280
|
}
|
|
3099
3281
|
const entry = lock.entries[lockKey(spec.styleId, spec.assetId)];
|
|
@@ -3160,7 +3342,7 @@ async function inspectQualityProfile(spec, lock) {
|
|
|
3160
3342
|
const expectedRevision = spec.quality.fixerRevision ?? PIXEL_ART_FIXER_REVISION;
|
|
3161
3343
|
const expectedTransparency = spec.quality.minTransparency ?? null;
|
|
3162
3344
|
const expectedFrameSet = spec.generator === "frames" ? { fps: spec.quality.fps ?? 12, count: source.items.length, roles: source.items.map((item) => item.role) } : null;
|
|
3163
|
-
const contractChanged = JSON.stringify(check.sources.map((item) =>
|
|
3345
|
+
const contractChanged = JSON.stringify(check.sources.map((item) => path7.resolve(item))) !== JSON.stringify(sourcePaths.map((item) => path7.resolve(item))) || JSON.stringify(check.outputs.map((item) => path7.resolve(item))) !== JSON.stringify(expectedOutputs.map((item) => path7.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);
|
|
3164
3346
|
if (contractChanged) {
|
|
3165
3347
|
return {
|
|
3166
3348
|
key,
|
|
@@ -3322,43 +3504,21 @@ async function requireApprovedQualitySources(specs, lock, assetIds, outputRoles)
|
|
|
3322
3504
|
return sources;
|
|
3323
3505
|
}
|
|
3324
3506
|
|
|
3325
|
-
// src/pipeline/postprocess.ts
|
|
3326
|
-
function postprocessFor(spec) {
|
|
3327
|
-
if (!spec.enforcePalette || spec.palette.length < 2) return void 0;
|
|
3328
|
-
return {
|
|
3329
|
-
palette: {
|
|
3330
|
-
colors: normalizePalette(spec.palette).map((color) => color.hex),
|
|
3331
|
-
dither: "none",
|
|
3332
|
-
distance: "redmean"
|
|
3333
|
-
}
|
|
3334
|
-
};
|
|
3335
|
-
}
|
|
3336
|
-
function samePostprocess(a, b) {
|
|
3337
|
-
const pa = a?.palette;
|
|
3338
|
-
const pb = b?.palette;
|
|
3339
|
-
if (!pa && !pb) return true;
|
|
3340
|
-
if (!pa || !pb) return false;
|
|
3341
|
-
return pa.dither === pb.dither && pa.distance === pb.distance && pa.colors.length === pb.colors.length && pa.colors.every((color, i) => color === pb.colors[i]);
|
|
3342
|
-
}
|
|
3343
|
-
function postprocessCurrent(entry, spec) {
|
|
3344
|
-
return samePostprocess(entry.postprocess, postprocessFor(spec));
|
|
3345
|
-
}
|
|
3346
|
-
function applyPostprocess(bytes, mediaType, record) {
|
|
3347
|
-
if (!record?.palette || mediaType !== MediaType.PNG) return { bytes, changed: false };
|
|
3348
|
-
const png = decodePng(bytes);
|
|
3349
|
-
const { png: snapped, changed } = quantize(png, record.palette.colors);
|
|
3350
|
-
if (!changed) return { bytes, changed: false };
|
|
3351
|
-
return { bytes: encodeRgbaPng(snapped.width, snapped.height, snapped.pixels), changed: true };
|
|
3352
|
-
}
|
|
3353
|
-
|
|
3354
3507
|
// src/pipeline/revision.ts
|
|
3355
3508
|
import { existsSync as existsSync4 } from "fs";
|
|
3356
|
-
import
|
|
3509
|
+
import path8 from "path";
|
|
3357
3510
|
async function inspectRevisionReadiness(spec, lock) {
|
|
3511
|
+
if (spec.mirror) return inspectMirror(spec, lock, /* @__PURE__ */ new Set());
|
|
3358
3512
|
if (spec.character?.parentSpec) return inspectCharacterParent(spec, lock, /* @__PURE__ */ new Set());
|
|
3359
3513
|
if (!spec.revision) return null;
|
|
3360
3514
|
return inspectRevision(spec, lock, /* @__PURE__ */ new Set());
|
|
3361
3515
|
}
|
|
3516
|
+
async function inspectMirror(spec, lock, seen) {
|
|
3517
|
+
const source = spec.mirror.sourceSpec;
|
|
3518
|
+
const label = `mirror source ${spec.styleId}/${spec.mirror.sourceAssetId}`;
|
|
3519
|
+
const dependency = await inspectParent(source, lock, seen);
|
|
3520
|
+
return dependency.ready ? { ready: true, reason: `${label} is current` } : { ready: false, reason: `${label} is not ready: ${dependency.reason}` };
|
|
3521
|
+
}
|
|
3362
3522
|
async function inspectCharacterParent(spec, lock, seen) {
|
|
3363
3523
|
const character = spec.character;
|
|
3364
3524
|
const parentSpec = character.parentSpec;
|
|
@@ -3412,6 +3572,10 @@ async function inspectParent(spec, lock, seen) {
|
|
|
3412
3572
|
if (seen.has(key)) return { ready: false, reason: `revision dependency cycle at ${key}` };
|
|
3413
3573
|
const nextSeen = new Set(seen);
|
|
3414
3574
|
nextSeen.add(key);
|
|
3575
|
+
if (spec.mirror) {
|
|
3576
|
+
const inputs = await inspectMirror(spec, lock, nextSeen);
|
|
3577
|
+
if (!inputs.ready) return inputs;
|
|
3578
|
+
}
|
|
3415
3579
|
if (spec.revision) {
|
|
3416
3580
|
const inputs = await inspectRevision(spec, lock, nextSeen);
|
|
3417
3581
|
if (!inputs.ready) return inputs;
|
|
@@ -3428,7 +3592,7 @@ async function inspectParent(spec, lock, seen) {
|
|
|
3428
3592
|
};
|
|
3429
3593
|
}
|
|
3430
3594
|
if (spec.source) {
|
|
3431
|
-
const source =
|
|
3595
|
+
const source = path8.resolve(spec.root, spec.source);
|
|
3432
3596
|
return existsSync4(source) ? { ready: true, reason: "declared source is present" } : { ready: false, reason: `declared source is missing: ${spec.source}` };
|
|
3433
3597
|
}
|
|
3434
3598
|
const entry = lock.entries[key];
|
|
@@ -3498,7 +3662,7 @@ async function buildPlan(specs, lock, opts = {}) {
|
|
|
3498
3662
|
state = "missing";
|
|
3499
3663
|
reason = "--force";
|
|
3500
3664
|
} else if (!entry && spec.source && spec.generator !== "frames") {
|
|
3501
|
-
if (existsSync5(
|
|
3665
|
+
if (existsSync5(path9.resolve(spec.root, spec.source))) {
|
|
3502
3666
|
state = "ok";
|
|
3503
3667
|
reason = `placed from ${spec.source}; not generated`;
|
|
3504
3668
|
} else {
|
|
@@ -3511,11 +3675,17 @@ async function buildPlan(specs, lock, opts = {}) {
|
|
|
3511
3675
|
reason = "file on disk, no provenance recorded";
|
|
3512
3676
|
} else {
|
|
3513
3677
|
state = "missing";
|
|
3514
|
-
reason = "not in lockfile";
|
|
3678
|
+
reason = spec.mirror ? `not made yet; flipped from ${spec.mirror.sourceAssetId} (no generation cost)` : "not in lockfile";
|
|
3515
3679
|
}
|
|
3516
3680
|
} else if (entry.specHash !== spec.specHash) {
|
|
3517
3681
|
state = "stale";
|
|
3518
3682
|
reason = "prompt, size, or style changed";
|
|
3683
|
+
} else if (spec.mirror && !mirrorCurrent(entry, lock.entries[lockKey(spec.styleId, spec.mirror.sourceAssetId)])) {
|
|
3684
|
+
state = "stale";
|
|
3685
|
+
reason = `mirror source ${spec.mirror.sourceAssetId} changed; flip it again (no generation cost)`;
|
|
3686
|
+
} else if (spec.mirror && entry.status === "downloaded" && entry.outputs.some((_output, index) => !existsSync5(currentEntryOutputPath(entry, spec, index)))) {
|
|
3687
|
+
state = "stale";
|
|
3688
|
+
reason = "mirror output missing on disk; flip it again (no generation cost)";
|
|
3519
3689
|
} else if (entry.status === "download-failed") {
|
|
3520
3690
|
state = "recoverable";
|
|
3521
3691
|
const force = entry.error?.includes("pass --force") ? " --force" : "";
|
|
@@ -3544,6 +3714,9 @@ async function buildPlan(specs, lock, opts = {}) {
|
|
|
3544
3714
|
} else if (await anyOutputModified(entry, spec)) {
|
|
3545
3715
|
state = "orphaned";
|
|
3546
3716
|
reason = "output modified since download";
|
|
3717
|
+
} else if (spec.mirror && !postprocessCurrent(entry, spec)) {
|
|
3718
|
+
state = "stale";
|
|
3719
|
+
reason = "palette enforcement changed; flip the source again once it is refreshed (no generation cost)";
|
|
3547
3720
|
} else if (!postprocessCurrent(entry, spec)) {
|
|
3548
3721
|
state = "recoverable";
|
|
3549
3722
|
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)";
|
|
@@ -3555,15 +3728,19 @@ async function buildPlan(specs, lock, opts = {}) {
|
|
|
3555
3728
|
items.push({ spec, key, state, reason, ...quality ? { quality } : {} });
|
|
3556
3729
|
}
|
|
3557
3730
|
const isActionable = (i) => i.state === "missing" || i.state === "stale" || i.state === "failed";
|
|
3558
|
-
const
|
|
3731
|
+
const stateOf = new Map(items.map((i) => [i.key, i.state]));
|
|
3559
3732
|
for (const item of items) {
|
|
3560
3733
|
if (!isActionable(item)) continue;
|
|
3561
|
-
const parent = item.spec.revision?.sourceSpec ?? item.spec.character?.parentSpec;
|
|
3734
|
+
const parent = item.spec.mirror?.sourceSpec ?? item.spec.revision?.sourceSpec ?? item.spec.character?.parentSpec;
|
|
3562
3735
|
if (!parent) continue;
|
|
3563
3736
|
const parentKey = lockKey(parent.styleId, parent.assetId);
|
|
3564
|
-
|
|
3737
|
+
const parentState = stateOf.get(parentKey);
|
|
3738
|
+
if (parentState && isActionable({ state: parentState })) {
|
|
3565
3739
|
item.state = "blocked";
|
|
3566
3740
|
item.reason = `parent ${parentKey} is being generated in this run; this follows in the next wave`;
|
|
3741
|
+
} else if (item.spec.mirror && (parentState === "recoverable" || parentState === "in-flight")) {
|
|
3742
|
+
item.state = "blocked";
|
|
3743
|
+
item.reason = `mirror source ${parentKey} is ${parentState}; this follows in the next wave`;
|
|
3567
3744
|
}
|
|
3568
3745
|
}
|
|
3569
3746
|
const actionable = items.filter(isActionable);
|
|
@@ -3718,12 +3895,12 @@ async function confirm(question, auto) {
|
|
|
3718
3895
|
}
|
|
3719
3896
|
|
|
3720
3897
|
// src/cli/commands/account.ts
|
|
3721
|
-
import
|
|
3898
|
+
import path22 from "path";
|
|
3722
3899
|
import { existsSync as existsSync16 } from "fs";
|
|
3723
3900
|
|
|
3724
3901
|
// src/env.ts
|
|
3725
3902
|
import { readFileSync, existsSync as existsSync6 } from "fs";
|
|
3726
|
-
import
|
|
3903
|
+
import path10 from "path";
|
|
3727
3904
|
function loadEnvFiles(dir) {
|
|
3728
3905
|
const loaded = [];
|
|
3729
3906
|
for (const [file, vars] of envFiles(dir)) {
|
|
@@ -3746,7 +3923,7 @@ function readEnvFiles(dir) {
|
|
|
3746
3923
|
function envFiles(dir) {
|
|
3747
3924
|
const out = [];
|
|
3748
3925
|
for (const name of [".env.local", ".env"]) {
|
|
3749
|
-
const file =
|
|
3926
|
+
const file = path10.join(dir, name);
|
|
3750
3927
|
if (!existsSync6(file)) continue;
|
|
3751
3928
|
try {
|
|
3752
3929
|
out.push([file, parseEnv(readFileSync(file, "utf8"))]);
|
|
@@ -3776,8 +3953,8 @@ function parseEnv(contents) {
|
|
|
3776
3953
|
|
|
3777
3954
|
// src/providers/comfyui.ts
|
|
3778
3955
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
3779
|
-
import { readFile as
|
|
3780
|
-
import
|
|
3956
|
+
import { readFile as readFile5 } from "fs/promises";
|
|
3957
|
+
import path11 from "path";
|
|
3781
3958
|
|
|
3782
3959
|
// src/http.ts
|
|
3783
3960
|
var MAX_RETRIES = 4;
|
|
@@ -3964,10 +4141,10 @@ var ComfyUIProvider = class _ComfyUIProvider {
|
|
|
3964
4141
|
}
|
|
3965
4142
|
async resolveOptions(value, context) {
|
|
3966
4143
|
const options = parseOptions(value);
|
|
3967
|
-
const workflowPath =
|
|
4144
|
+
const workflowPath = path11.resolve(context.root, options.workflowFile);
|
|
3968
4145
|
let raw;
|
|
3969
4146
|
try {
|
|
3970
|
-
raw = await
|
|
4147
|
+
raw = await readFile5(workflowPath, "utf8");
|
|
3971
4148
|
} catch (err) {
|
|
3972
4149
|
throw new Error(
|
|
3973
4150
|
`ComfyUI workflow for style "${context.styleId}" could not be read at ${workflowPath}: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -4019,10 +4196,10 @@ var ComfyUIProvider = class _ComfyUIProvider {
|
|
|
4019
4196
|
if (typeof item !== "string") {
|
|
4020
4197
|
throw new Error(`ComfyUI ${label} must be a manifest-relative PNG or JPEG path`);
|
|
4021
4198
|
}
|
|
4022
|
-
const file =
|
|
4199
|
+
const file = path11.resolve(context.root, item);
|
|
4023
4200
|
let bytes;
|
|
4024
4201
|
try {
|
|
4025
|
-
bytes = await
|
|
4202
|
+
bytes = await readFile5(file);
|
|
4026
4203
|
} catch (error) {
|
|
4027
4204
|
throw new Error(
|
|
4028
4205
|
`ComfyUI ${label} could not be read at ${file}: ${error instanceof Error ? error.message : String(error)}`
|
|
@@ -4154,7 +4331,7 @@ var ComfyUIProvider = class _ComfyUIProvider {
|
|
|
4154
4331
|
setBinding(workflow2, binding, input);
|
|
4155
4332
|
return;
|
|
4156
4333
|
}
|
|
4157
|
-
const bytes = await
|
|
4334
|
+
const bytes = await readFile5(input.path);
|
|
4158
4335
|
if (sha256(bytes) !== input.sha256) {
|
|
4159
4336
|
throw new Error(`ComfyUI provider input "${name}" changed before upload`);
|
|
4160
4337
|
}
|
|
@@ -4221,7 +4398,7 @@ var ComfyUIProvider = class _ComfyUIProvider {
|
|
|
4221
4398
|
if (!spec.revision.sourceSha256 || !spec.revision.sourceFormat) {
|
|
4222
4399
|
throw new Error("ComfyUI revision source is not ready");
|
|
4223
4400
|
}
|
|
4224
|
-
const sourceBytes = await
|
|
4401
|
+
const sourceBytes = await readFile5(spec.revision.sourceFile);
|
|
4225
4402
|
if (sha256(sourceBytes) !== spec.revision.sourceSha256) {
|
|
4226
4403
|
throw new Error("ComfyUI revision source changed before upload");
|
|
4227
4404
|
}
|
|
@@ -4235,7 +4412,7 @@ var ComfyUIProvider = class _ComfyUIProvider {
|
|
|
4235
4412
|
if (!spec.revision.maskSha256 || !spec.revision.maskFormat) {
|
|
4236
4413
|
throw new Error("ComfyUI revision mask is not ready");
|
|
4237
4414
|
}
|
|
4238
|
-
const maskBytes = await
|
|
4415
|
+
const maskBytes = await readFile5(spec.revision.maskFile);
|
|
4239
4416
|
if (sha256(maskBytes) !== spec.revision.maskSha256) {
|
|
4240
4417
|
throw new Error("ComfyUI revision mask changed before upload");
|
|
4241
4418
|
}
|
|
@@ -4709,7 +4886,7 @@ function canonical2(value) {
|
|
|
4709
4886
|
import { mkdirSync, existsSync as existsSync7, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
4710
4887
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
4711
4888
|
import os from "os";
|
|
4712
|
-
import
|
|
4889
|
+
import path12 from "path";
|
|
4713
4890
|
|
|
4714
4891
|
// src/client.ts
|
|
4715
4892
|
import { z as z2 } from "zod";
|
|
@@ -4848,9 +5025,9 @@ var PixelLabClient = class {
|
|
|
4848
5025
|
apiKey;
|
|
4849
5026
|
http;
|
|
4850
5027
|
/** Retries transport failures, 429, and 5xx; see http.ts for the policy. */
|
|
4851
|
-
async request(
|
|
5028
|
+
async request(path45, init) {
|
|
4852
5029
|
const auth = this.apiKey.startsWith("Bearer ") ? this.apiKey : `Bearer ${this.apiKey}`;
|
|
4853
|
-
const res = await this.http(`${BASE}${
|
|
5030
|
+
const res = await this.http(`${BASE}${path45}`, {
|
|
4854
5031
|
...init,
|
|
4855
5032
|
headers: {
|
|
4856
5033
|
Authorization: auth,
|
|
@@ -4860,13 +5037,13 @@ var PixelLabClient = class {
|
|
|
4860
5037
|
});
|
|
4861
5038
|
const text = await res.text();
|
|
4862
5039
|
if (!res.ok) {
|
|
4863
|
-
throw new PixelLabError(`${init?.method ?? "GET"} ${
|
|
5040
|
+
throw new PixelLabError(`${init?.method ?? "GET"} ${path45} \u2192 ${res.status}`, res.status, text);
|
|
4864
5041
|
}
|
|
4865
5042
|
if (!text) return {};
|
|
4866
5043
|
try {
|
|
4867
5044
|
return JSON.parse(text);
|
|
4868
5045
|
} catch {
|
|
4869
|
-
throw new Error(`${init?.method ?? "GET"} ${
|
|
5046
|
+
throw new Error(`${init?.method ?? "GET"} ${path45} returned invalid JSON`);
|
|
4870
5047
|
}
|
|
4871
5048
|
}
|
|
4872
5049
|
async balance() {
|
|
@@ -5051,10 +5228,10 @@ var PixelLabClient = class {
|
|
|
5051
5228
|
async createCharacter(args) {
|
|
5052
5229
|
const imageSize = { width: args.size, height: args.size };
|
|
5053
5230
|
const palette = args.paletteSwatchBase64 ? { color_image: { type: "base64", base64: args.paletteSwatchBase64, format: "png" }, force_colors: true } : {};
|
|
5054
|
-
let
|
|
5231
|
+
let path45;
|
|
5055
5232
|
let body;
|
|
5056
5233
|
if (args.mode === "v3") {
|
|
5057
|
-
|
|
5234
|
+
path45 = "/create-character-v3";
|
|
5058
5235
|
body = {
|
|
5059
5236
|
description: args.description,
|
|
5060
5237
|
image_size: imageSize,
|
|
@@ -5066,7 +5243,7 @@ var PixelLabClient = class {
|
|
|
5066
5243
|
...args.seed != null ? { seed: args.seed } : {}
|
|
5067
5244
|
};
|
|
5068
5245
|
} else if (args.mode === "pro") {
|
|
5069
|
-
|
|
5246
|
+
path45 = "/create-character-pro";
|
|
5070
5247
|
body = {
|
|
5071
5248
|
description: args.description,
|
|
5072
5249
|
image_size: imageSize,
|
|
@@ -5076,7 +5253,7 @@ var PixelLabClient = class {
|
|
|
5076
5253
|
...args.seed != null ? { seed: args.seed } : {}
|
|
5077
5254
|
};
|
|
5078
5255
|
} else {
|
|
5079
|
-
|
|
5256
|
+
path45 = args.directions === 4 ? "/create-character-with-4-directions" : "/create-character-with-8-directions";
|
|
5080
5257
|
body = {
|
|
5081
5258
|
description: args.description,
|
|
5082
5259
|
image_size: imageSize,
|
|
@@ -5089,8 +5266,8 @@ var PixelLabClient = class {
|
|
|
5089
5266
|
...palette
|
|
5090
5267
|
};
|
|
5091
5268
|
}
|
|
5092
|
-
const raw = await this.request(
|
|
5093
|
-
return validateResponse(CharacterSubmitSchema, raw,
|
|
5269
|
+
const raw = await this.request(path45, { method: "POST", body: JSON.stringify(body) });
|
|
5270
|
+
return validateResponse(CharacterSubmitSchema, raw, path45.slice(1));
|
|
5094
5271
|
}
|
|
5095
5272
|
/** A text edit of an existing character, applied to every direction and stored as a sibling. */
|
|
5096
5273
|
async createCharacterState(args) {
|
|
@@ -5278,7 +5455,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5278
5455
|
* filename, polling is an existence check, and downloading is a file read.
|
|
5279
5456
|
*/
|
|
5280
5457
|
static cacheDir() {
|
|
5281
|
-
const dir =
|
|
5458
|
+
const dir = path12.join(os.tmpdir(), "pixelkiln-pixflux");
|
|
5282
5459
|
mkdirSync(dir, { recursive: true });
|
|
5283
5460
|
return dir;
|
|
5284
5461
|
}
|
|
@@ -5536,7 +5713,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5536
5713
|
seed: spec.seed
|
|
5537
5714
|
});
|
|
5538
5715
|
const jobId = randomUUID3();
|
|
5539
|
-
writeFileSync(
|
|
5716
|
+
writeFileSync(path12.join(_PixelLabProvider.cacheDir(), `${jobId}.png`), png);
|
|
5540
5717
|
return { jobId };
|
|
5541
5718
|
}
|
|
5542
5719
|
if (spec.generator === "tiles") {
|
|
@@ -5577,7 +5754,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5577
5754
|
}
|
|
5578
5755
|
async poll(jobId, generator, context) {
|
|
5579
5756
|
if (generator === "pixflux") {
|
|
5580
|
-
const file =
|
|
5757
|
+
const file = path12.join(_PixelLabProvider.cacheDir(), `${jobId}.png`);
|
|
5581
5758
|
if (existsSync7(file)) {
|
|
5582
5759
|
const sourceUrl = `file://${file}`;
|
|
5583
5760
|
return { status: "ready", objectId: jobId, sourceUrl, sources: [{ url: sourceUrl }] };
|
|
@@ -5861,9 +6038,9 @@ var RetroDiffusionClient = class {
|
|
|
5861
6038
|
if (!response.ok) throw new ProviderError("retrodiffusion", `Retro Diffusion download failed (${response.status})`, { status: response.status });
|
|
5862
6039
|
return Buffer.from(await response.arrayBuffer());
|
|
5863
6040
|
}
|
|
5864
|
-
async call(
|
|
6041
|
+
async call(path45, init = {}) {
|
|
5865
6042
|
if (!this.token) throw new Error("RD_API_KEY is not set");
|
|
5866
|
-
const response = await this.request(`${this.baseUrl}${
|
|
6043
|
+
const response = await this.request(`${this.baseUrl}${path45}`, {
|
|
5867
6044
|
...init,
|
|
5868
6045
|
headers: {
|
|
5869
6046
|
"Content-Type": "application/json",
|
|
@@ -6720,13 +6897,13 @@ registerProvider({
|
|
|
6720
6897
|
});
|
|
6721
6898
|
|
|
6722
6899
|
// src/manifest.ts
|
|
6723
|
-
import { readFile as
|
|
6900
|
+
import { readFile as readFile6 } from "fs/promises";
|
|
6724
6901
|
import { existsSync as existsSync8 } from "fs";
|
|
6725
|
-
import
|
|
6902
|
+
import path13 from "path";
|
|
6726
6903
|
async function loadManifest(manifestPath) {
|
|
6727
|
-
const abs =
|
|
6904
|
+
const abs = path13.resolve(manifestPath);
|
|
6728
6905
|
if (!existsSync8(abs)) throw new ProjectError(`No manifest at ${abs}`);
|
|
6729
|
-
const input = ManifestInputSchema.safeParse(JSON.parse(await
|
|
6906
|
+
const input = ManifestInputSchema.safeParse(JSON.parse(await readFile6(abs, "utf8")));
|
|
6730
6907
|
if (!input.success) {
|
|
6731
6908
|
const issues = formatManifestIssues(input.error.issues);
|
|
6732
6909
|
throw new ProjectError(`Manifest at ${abs} is invalid:
|
|
@@ -6811,7 +6988,7 @@ ${issues}`);
|
|
|
6811
6988
|
throw new ProjectError(`Manifest at ${abs} is invalid:
|
|
6812
6989
|
${unknownReferences.map((i) => ` ${i}`).join("\n")}`);
|
|
6813
6990
|
}
|
|
6814
|
-
return { manifest: parsed.data, root:
|
|
6991
|
+
return { manifest: parsed.data, root: path13.dirname(abs), path: abs };
|
|
6815
6992
|
}
|
|
6816
6993
|
function parentAssetId(asset) {
|
|
6817
6994
|
return asset?.revision?.from ?? asset?.state?.of ?? asset?.animation?.of;
|
|
@@ -6911,11 +7088,11 @@ async function resolveSpecs(loaded, filter) {
|
|
|
6911
7088
|
}
|
|
6912
7089
|
const styleImageCache = /* @__PURE__ */ new Map();
|
|
6913
7090
|
async function loadStyleImage(rel) {
|
|
6914
|
-
const abs =
|
|
7091
|
+
const abs = path13.resolve(root, rel);
|
|
6915
7092
|
let hit = styleImageCache.get(abs);
|
|
6916
7093
|
if (!hit) {
|
|
6917
7094
|
if (!existsSync8(abs)) throw new Error(`Style image not found: ${abs}`);
|
|
6918
|
-
const buf = await
|
|
7095
|
+
const buf = await readFile6(abs);
|
|
6919
7096
|
const metadata = imageMetadata(buf);
|
|
6920
7097
|
if (!metadata) throw new Error(`Style image is not a readable PNG or JPEG: ${abs}`);
|
|
6921
7098
|
if (metadata.width < 1 || metadata.height < 1) {
|
|
@@ -6941,7 +7118,7 @@ async function resolveSpecs(loaded, filter) {
|
|
|
6941
7118
|
styleImageDimensions.push(loadedImage);
|
|
6942
7119
|
}
|
|
6943
7120
|
const resolvedImages = style.styleImages.map((image) => {
|
|
6944
|
-
const hit = styleImageCache.get(
|
|
7121
|
+
const hit = styleImageCache.get(path13.resolve(root, image.path));
|
|
6945
7122
|
return { base64: hit.base64, width: hit.width, height: hit.height, format: hit.format };
|
|
6946
7123
|
});
|
|
6947
7124
|
const styleSpecs = /* @__PURE__ */ new Map();
|
|
@@ -6994,11 +7171,11 @@ async function resolveSpecs(loaded, filter) {
|
|
|
6994
7171
|
);
|
|
6995
7172
|
}
|
|
6996
7173
|
const characterKind = asset.animation ? "animation" : asset.state ? "state" : "base";
|
|
6997
|
-
const subject = asset.promptByStyle[styleId] ?? asset.prompt;
|
|
7174
|
+
const subject = asset.promptByStyle[styleId] ?? asset.prompt ?? "";
|
|
6998
7175
|
const prompt = generator === "character" && characterKind !== "base" ? subject.trim() : [style.promptPrefix, subject, style.promptSuffix].map((p) => p.trim()).filter(Boolean).join(", ");
|
|
6999
|
-
const relFile = asset.file ??
|
|
7000
|
-
const outFile =
|
|
7001
|
-
const qualityOutFile = style.quality ?
|
|
7176
|
+
const relFile = asset.file ?? path13.join(asset.category ?? "", `${assetId}.png`);
|
|
7177
|
+
const outFile = path13.resolve(root, style.outDir, relFile);
|
|
7178
|
+
const qualityOutFile = style.quality ? path13.resolve(root, style.quality.outDir, pngPath(relFile)) : void 0;
|
|
7002
7179
|
const tileSize = generator === "tiles" ? size : style.tileSize ?? 32;
|
|
7003
7180
|
const tileVariations = tileFeatureOutputCount(generator === "tiles" ? style.tileFeature : void 0) ?? tileVariationCount(countNumberedDescriptions(prompt));
|
|
7004
7181
|
const base = {
|
|
@@ -7051,7 +7228,7 @@ async function resolveSpecs(loaded, filter) {
|
|
|
7051
7228
|
minGridConfidence: style.quality.minGridConfidence,
|
|
7052
7229
|
...style.quality.minTransparency == null ? {} : { minTransparency: style.quality.minTransparency },
|
|
7053
7230
|
...style.quality.fixerRevision ? { fixerRevision: style.quality.fixerRevision } : {},
|
|
7054
|
-
...style.quality.fixerPython ? { fixerPython:
|
|
7231
|
+
...style.quality.fixerPython ? { fixerPython: path13.resolve(root, style.quality.fixerPython) } : {},
|
|
7055
7232
|
...style.quality.fps == null ? {} : { fps: style.quality.fps }
|
|
7056
7233
|
}
|
|
7057
7234
|
} : {},
|
|
@@ -7069,11 +7246,42 @@ async function resolveSpecs(loaded, filter) {
|
|
|
7069
7246
|
const resolved = styleSpecs.get(assetId);
|
|
7070
7247
|
if (!resolved) {
|
|
7071
7248
|
throw new Error(
|
|
7072
|
-
`Asset "${assetId}" is not available in style "${styleId}"; revision parents must
|
|
7249
|
+
`Asset "${assetId}" is not available in style "${styleId}"; revision parents, character parents, and mirror sources must be in the same style.`
|
|
7073
7250
|
);
|
|
7074
7251
|
}
|
|
7075
7252
|
if (finalized.has(assetId)) return resolved;
|
|
7076
7253
|
const asset = manifest.assets[assetId];
|
|
7254
|
+
if (asset.mirror) {
|
|
7255
|
+
if (asset.mirror === assetId) throw new Error(`assets.${assetId}: an asset cannot mirror itself`);
|
|
7256
|
+
const sourceSpec = await finalize(asset.mirror);
|
|
7257
|
+
if (sourceSpec.generator === "tiles") {
|
|
7258
|
+
throw new Error(`assets.${assetId}: a tile set cannot be mirrored; its edges carry meaning`);
|
|
7259
|
+
}
|
|
7260
|
+
if (sourceSpec.character?.kind === "animation") {
|
|
7261
|
+
const facing = sourceSpec.character.animation.direction;
|
|
7262
|
+
const mirrored = MIRRORED_DIRECTION[facing];
|
|
7263
|
+
if (mirrored === facing) {
|
|
7264
|
+
throw new Error(
|
|
7265
|
+
`assets.${assetId}: mirroring ${asset.mirror} gives another ${facing}-facing loop; mirror a loop facing east, west, or a diagonal`
|
|
7266
|
+
);
|
|
7267
|
+
}
|
|
7268
|
+
resolved.character = { ...sourceSpec.character, animation: { ...sourceSpec.character.animation, direction: mirrored } };
|
|
7269
|
+
} else if (sourceSpec.character) {
|
|
7270
|
+
resolved.character = { ...sourceSpec.character };
|
|
7271
|
+
}
|
|
7272
|
+
resolved.mirror = { sourceAssetId: asset.mirror, sourceSpec };
|
|
7273
|
+
resolved.generator = sourceSpec.generator;
|
|
7274
|
+
resolved.width = sourceSpec.width;
|
|
7275
|
+
resolved.height = sourceSpec.height;
|
|
7276
|
+
resolved.size = sourceSpec.size;
|
|
7277
|
+
resolved.prompt = "";
|
|
7278
|
+
resolved.specHash = specHash(resolved, styleImageHashes, providerOptionIdentity, providerInputIdentities.get(assetId));
|
|
7279
|
+
resolved.cost = 0;
|
|
7280
|
+
resolved.costUnit = sourceSpec.costUnit;
|
|
7281
|
+
resolved.candidates = 1;
|
|
7282
|
+
finalized.add(assetId);
|
|
7283
|
+
return resolved;
|
|
7284
|
+
}
|
|
7077
7285
|
const characterParent = asset.state?.of ?? asset.animation?.of;
|
|
7078
7286
|
if (resolved.character && characterParent) {
|
|
7079
7287
|
const parentSpec = await finalize(characterParent);
|
|
@@ -7097,9 +7305,9 @@ async function resolveSpecs(loaded, filter) {
|
|
|
7097
7305
|
);
|
|
7098
7306
|
}
|
|
7099
7307
|
const sourceSpec = await finalize(asset.revision.from);
|
|
7100
|
-
const sourceFile = sourceSpec.quality?.outFile ?? (sourceSpec.source ?
|
|
7308
|
+
const sourceFile = sourceSpec.quality?.outFile ?? (sourceSpec.source ? path13.resolve(root, sourceSpec.source) : sourceSpec.outFile);
|
|
7101
7309
|
const sourceImage = await optionalRevisionImage(sourceFile, "revision source");
|
|
7102
|
-
const maskFile = asset.revision.mask ?
|
|
7310
|
+
const maskFile = asset.revision.mask ? path13.resolve(root, asset.revision.mask) : void 0;
|
|
7103
7311
|
const maskImage = maskFile ? await optionalRevisionImage(maskFile, "revision mask", "png") : null;
|
|
7104
7312
|
if (sourceImage && maskImage && (sourceImage.width !== maskImage.width || sourceImage.height !== maskImage.height)) {
|
|
7105
7313
|
throw new Error(
|
|
@@ -7180,12 +7388,12 @@ async function resolveSpecs(loaded, filter) {
|
|
|
7180
7388
|
return specs;
|
|
7181
7389
|
}
|
|
7182
7390
|
function pngPath(file) {
|
|
7183
|
-
const extension =
|
|
7391
|
+
const extension = path13.extname(file);
|
|
7184
7392
|
return extension ? `${file.slice(0, -extension.length)}.png` : `${file}.png`;
|
|
7185
7393
|
}
|
|
7186
7394
|
async function optionalRevisionImage(file, label, requiredFormat) {
|
|
7187
7395
|
if (!existsSync8(file)) return null;
|
|
7188
|
-
const bytes = await
|
|
7396
|
+
const bytes = await readFile6(file);
|
|
7189
7397
|
const metadata = imageMetadata(bytes);
|
|
7190
7398
|
if (!metadata) throw new Error(`${label} is not a readable PNG or JPEG: ${file}`);
|
|
7191
7399
|
if (requiredFormat && metadata.format !== requiredFormat) {
|
|
@@ -7198,8 +7406,8 @@ async function resolveStyleImages(loaded, styleId) {
|
|
|
7198
7406
|
if (!style) throw new Error(`Unknown style "${styleId}"`);
|
|
7199
7407
|
const out = [];
|
|
7200
7408
|
for (const img of style.styleImages) {
|
|
7201
|
-
const file =
|
|
7202
|
-
const buf = await
|
|
7409
|
+
const file = path13.resolve(loaded.root, img.path);
|
|
7410
|
+
const buf = await readFile6(file);
|
|
7203
7411
|
const metadata = imageMetadata(buf);
|
|
7204
7412
|
if (!metadata) throw new Error(`Style image is not a readable PNG or JPEG: ${file}`);
|
|
7205
7413
|
if (metadata.width < 1 || metadata.height < 1) {
|
|
@@ -7268,8 +7476,8 @@ function resolveCharacterShape(asset, style, kind) {
|
|
|
7268
7476
|
|
|
7269
7477
|
// src/pipeline/fetch.ts
|
|
7270
7478
|
import { existsSync as existsSync9, readFileSync as readFileSync3 } from "fs";
|
|
7271
|
-
import { mkdir as
|
|
7272
|
-
import
|
|
7479
|
+
import { mkdir as mkdir4, readFile as readFile7, rename as rename4, rm as rm4, writeFile as writeFile4 } from "fs/promises";
|
|
7480
|
+
import path14 from "path";
|
|
7273
7481
|
|
|
7274
7482
|
// src/source-url.ts
|
|
7275
7483
|
var SENSITIVE_QUERY_KEYS = /* @__PURE__ */ new Set([
|
|
@@ -7315,9 +7523,10 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
7315
7523
|
const result = { downloaded: 0, skipped: 0, failed: 0, ...opts.refresh ? { unchanged: 0 } : {} };
|
|
7316
7524
|
const specByKey = new Map(specs.map((s) => [lockKey(s.styleId, s.assetId), s]));
|
|
7317
7525
|
normalizeLockOutputPaths(lock, specs);
|
|
7318
|
-
const cacheDir = opts.cacheDir === false ? null :
|
|
7526
|
+
const cacheDir = opts.cacheDir === false ? null : path14.resolve(opts.cacheDir ?? path14.join(path14.dirname(lockPath), ".pixelkiln", "cache"));
|
|
7319
7527
|
const pending = Object.entries(lock.entries).filter(([key, e]) => {
|
|
7320
7528
|
if (e.provider !== provider.id || !specByKey.has(key)) return false;
|
|
7529
|
+
if (specByKey.get(key)?.mirror) return false;
|
|
7321
7530
|
if (e.status === "selected" || e.status === "download-failed") return true;
|
|
7322
7531
|
if (e.status !== "downloaded") return false;
|
|
7323
7532
|
if (opts.refresh) return e.outputs.length > 0 && Boolean(e.sourceUrls?.length || e.sourceUrl);
|
|
@@ -7416,7 +7625,7 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
7416
7625
|
if (!source.url) throw new Error(`no durable source URL to refresh ${source.role ?? "asset"} from`);
|
|
7417
7626
|
fresh = await provider.download(source.url);
|
|
7418
7627
|
if (recorded && sha256(fresh) === recordedRaw && !reapply) {
|
|
7419
|
-
log2(` current ${
|
|
7628
|
+
log2(` current ${path14.relative(process.cwd(), target)}`);
|
|
7420
7629
|
outputs.push({ ...recorded, path: portableOutputPath(target, spec.root) });
|
|
7421
7630
|
continue;
|
|
7422
7631
|
}
|
|
@@ -7430,7 +7639,7 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
7430
7639
|
if (cacheDir) {
|
|
7431
7640
|
await cacheMedia(
|
|
7432
7641
|
cacheDir,
|
|
7433
|
-
await
|
|
7642
|
+
await readFile7(target),
|
|
7434
7643
|
recorded.mediaType ?? MediaType.PNG,
|
|
7435
7644
|
recorded.sha256
|
|
7436
7645
|
);
|
|
@@ -7439,10 +7648,10 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
7439
7648
|
continue;
|
|
7440
7649
|
}
|
|
7441
7650
|
if (reapply && !fresh) {
|
|
7442
|
-
if (!recorded.raw) raw = await
|
|
7443
|
-
log2(` reapply ${
|
|
7651
|
+
if (!recorded.raw) raw = await readFile7(target);
|
|
7652
|
+
log2(` reapply ${path14.relative(process.cwd(), target)}`);
|
|
7444
7653
|
} else {
|
|
7445
|
-
log2(` changed ${
|
|
7654
|
+
log2(` changed ${path14.relative(process.cwd(), target)} (upstream)`);
|
|
7446
7655
|
}
|
|
7447
7656
|
} else {
|
|
7448
7657
|
const superseded = (entry.supersededOutputs ?? []).find(
|
|
@@ -7461,7 +7670,7 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
7461
7670
|
}
|
|
7462
7671
|
}
|
|
7463
7672
|
log2(
|
|
7464
|
-
` replace ${
|
|
7673
|
+
` replace ${path14.relative(process.cwd(), target)}` + (opts.force ? " (--force)" : " (previous tracked generation)")
|
|
7465
7674
|
);
|
|
7466
7675
|
}
|
|
7467
7676
|
}
|
|
@@ -7470,7 +7679,7 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
7470
7679
|
} else if (!raw && recorded && cacheDir) {
|
|
7471
7680
|
if (!reapply) final = await readCachedMedia(cacheDir, recorded.sha256, expectedMediaType);
|
|
7472
7681
|
if (!final && recordedRaw) raw = await readCachedMedia(cacheDir, recordedRaw, expectedMediaType);
|
|
7473
|
-
if (final || raw) log2(` cached ${
|
|
7682
|
+
if (final || raw) log2(` cached ${path14.relative(process.cwd(), target)}`);
|
|
7474
7683
|
}
|
|
7475
7684
|
if (!final && !raw) {
|
|
7476
7685
|
if (!source.url) {
|
|
@@ -7497,13 +7706,13 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
7497
7706
|
buf = processed.bytes;
|
|
7498
7707
|
if (processed.changed) {
|
|
7499
7708
|
rawHash = sha256(raw);
|
|
7500
|
-
log2(` snapped ${
|
|
7709
|
+
log2(` snapped ${path14.relative(process.cwd(), target)} to ${wanted.palette.colors.length} colours`);
|
|
7501
7710
|
}
|
|
7502
7711
|
}
|
|
7503
7712
|
if (cacheDir) await cacheMedia(cacheDir, buf, mediaType);
|
|
7504
|
-
await
|
|
7713
|
+
await mkdir4(path14.dirname(target), { recursive: true });
|
|
7505
7714
|
const tmp = `${target}.pixelkiln.tmp`;
|
|
7506
|
-
await
|
|
7715
|
+
await writeFile4(tmp, buf);
|
|
7507
7716
|
outputs.push({
|
|
7508
7717
|
path: portableOutputPath(target, spec.root),
|
|
7509
7718
|
sha256: sha256(buf),
|
|
@@ -7513,9 +7722,9 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
7513
7722
|
});
|
|
7514
7723
|
upsert(lock, key, { outputs: mergeOutputs(entry.outputs, outputs) });
|
|
7515
7724
|
await saveLock(lockPath, lock);
|
|
7516
|
-
await
|
|
7725
|
+
await rename4(tmp, target);
|
|
7517
7726
|
wrote = true;
|
|
7518
|
-
log2(` wrote ${
|
|
7727
|
+
log2(` wrote ${path14.relative(process.cwd(), target)}`);
|
|
7519
7728
|
}
|
|
7520
7729
|
if (opts.refresh && !wrote) {
|
|
7521
7730
|
if (rotated) {
|
|
@@ -7559,10 +7768,10 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
7559
7768
|
return result;
|
|
7560
7769
|
}
|
|
7561
7770
|
async function readCachedMedia(cacheDir, hash, mediaType) {
|
|
7562
|
-
const file =
|
|
7771
|
+
const file = path14.join(cacheDir, cacheFileName(hash, mediaType));
|
|
7563
7772
|
if (!existsSync9(file)) return null;
|
|
7564
7773
|
try {
|
|
7565
|
-
const buf = await
|
|
7774
|
+
const buf = await readFile7(file);
|
|
7566
7775
|
if (sha256(buf) !== hash) return null;
|
|
7567
7776
|
validateMedia(buf, mediaType);
|
|
7568
7777
|
return buf;
|
|
@@ -7573,13 +7782,13 @@ async function readCachedMedia(cacheDir, hash, mediaType) {
|
|
|
7573
7782
|
async function cacheMedia(cacheDir, buf, mediaType, knownHash) {
|
|
7574
7783
|
const hash = knownHash ?? sha256(buf);
|
|
7575
7784
|
validateMedia(buf, mediaType);
|
|
7576
|
-
const file =
|
|
7785
|
+
const file = path14.join(cacheDir, cacheFileName(hash, mediaType));
|
|
7577
7786
|
if (await readCachedMedia(cacheDir, hash, mediaType)) return;
|
|
7578
|
-
await
|
|
7787
|
+
await mkdir4(cacheDir, { recursive: true });
|
|
7579
7788
|
const tmp = `${file}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
7580
|
-
await
|
|
7789
|
+
await writeFile4(tmp, buf);
|
|
7581
7790
|
try {
|
|
7582
|
-
await
|
|
7791
|
+
await rename4(tmp, file);
|
|
7583
7792
|
} catch (err) {
|
|
7584
7793
|
if (!existsSync9(file)) throw err;
|
|
7585
7794
|
} finally {
|
|
@@ -7615,11 +7824,11 @@ async function pushTags(provider, specs, lock, opts = {}) {
|
|
|
7615
7824
|
|
|
7616
7825
|
// src/pipeline/adopt.ts
|
|
7617
7826
|
import { existsSync as existsSync11 } from "fs";
|
|
7618
|
-
import { readFile as
|
|
7619
|
-
import
|
|
7827
|
+
import { readFile as readFile9, writeFile as writeFile6 } from "fs/promises";
|
|
7828
|
+
import path15 from "path";
|
|
7620
7829
|
|
|
7621
7830
|
// src/cache.ts
|
|
7622
|
-
import { mkdir as
|
|
7831
|
+
import { mkdir as mkdir5, readFile as readFile8, rename as rename5, rm as rm5, writeFile as writeFile5 } from "fs/promises";
|
|
7623
7832
|
import { existsSync as existsSync10 } from "fs";
|
|
7624
7833
|
import pathModule from "path";
|
|
7625
7834
|
import { z as z3 } from "zod";
|
|
@@ -7635,10 +7844,10 @@ function isSha256Hash(value) {
|
|
|
7635
7844
|
function parseCache(value) {
|
|
7636
7845
|
return HashCacheSchema.parse(value);
|
|
7637
7846
|
}
|
|
7638
|
-
async function loadCache(
|
|
7639
|
-
if (!existsSync10(
|
|
7847
|
+
async function loadCache(path45) {
|
|
7848
|
+
if (!existsSync10(path45)) return { version: 1, hashes: {} };
|
|
7640
7849
|
try {
|
|
7641
|
-
const parsed = HashCacheSchema.safeParse(JSON.parse(await
|
|
7850
|
+
const parsed = HashCacheSchema.safeParse(JSON.parse(await readFile8(path45, "utf8")));
|
|
7642
7851
|
if (!parsed.success) return { version: 1, hashes: {} };
|
|
7643
7852
|
return {
|
|
7644
7853
|
version: 1,
|
|
@@ -7650,18 +7859,18 @@ async function loadCache(path44) {
|
|
|
7650
7859
|
return { version: 1, hashes: {} };
|
|
7651
7860
|
}
|
|
7652
7861
|
}
|
|
7653
|
-
async function saveCache(
|
|
7862
|
+
async function saveCache(path45, cache) {
|
|
7654
7863
|
const sorted = {};
|
|
7655
7864
|
for (const key of Object.keys(cache.hashes).sort()) {
|
|
7656
7865
|
const hash = cache.hashes[key];
|
|
7657
7866
|
if (!isSha256Hash(hash)) throw new Error(`Refusing to cache invalid SHA-256 for ${key}`);
|
|
7658
7867
|
sorted[key] = hash;
|
|
7659
7868
|
}
|
|
7660
|
-
await
|
|
7661
|
-
const tmp = `${
|
|
7869
|
+
await mkdir5(pathModule.dirname(pathModule.resolve(path45)), { recursive: true });
|
|
7870
|
+
const tmp = `${path45}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
7662
7871
|
try {
|
|
7663
|
-
await
|
|
7664
|
-
await
|
|
7872
|
+
await writeFile5(tmp, JSON.stringify({ version: 1, hashes: sorted }, null, 2) + "\n");
|
|
7873
|
+
await rename5(tmp, path45);
|
|
7665
7874
|
} finally {
|
|
7666
7875
|
await rm5(tmp, { force: true });
|
|
7667
7876
|
}
|
|
@@ -7699,7 +7908,7 @@ async function adopt(provider, specs, lock, lockPath, opts = {}) {
|
|
|
7699
7908
|
unmatchedLocal.push(`${spec.styleId}/${spec.assetId} (no file at ${spec.outFile})`);
|
|
7700
7909
|
continue;
|
|
7701
7910
|
}
|
|
7702
|
-
const bytes = await
|
|
7911
|
+
const bytes = await readFile9(spec.outFile);
|
|
7703
7912
|
try {
|
|
7704
7913
|
decodePng(bytes);
|
|
7705
7914
|
} catch (err) {
|
|
@@ -7819,7 +8028,7 @@ async function writePromptsBack(manifestPath, lock, opts = {}) {
|
|
|
7819
8028
|
const log2 = opts.onProgress ?? (() => {
|
|
7820
8029
|
});
|
|
7821
8030
|
const selectedAssets = opts.assetIds ? new Set(opts.assetIds) : null;
|
|
7822
|
-
const raw = JSON.parse(await
|
|
8031
|
+
const raw = JSON.parse(await readFile9(manifestPath, "utf8"));
|
|
7823
8032
|
const best = /* @__PURE__ */ new Map();
|
|
7824
8033
|
for (const entry of Object.values(lock.entries)) {
|
|
7825
8034
|
if (opts.provider && entry.provider !== opts.provider) continue;
|
|
@@ -7841,7 +8050,7 @@ async function writePromptsBack(manifestPath, lock, opts = {}) {
|
|
|
7841
8050
|
stillEmpty.push(id);
|
|
7842
8051
|
}
|
|
7843
8052
|
}
|
|
7844
|
-
await
|
|
8053
|
+
await writeFile6(manifestPath, JSON.stringify(raw, null, 2) + "\n");
|
|
7845
8054
|
log2(` filled ${filled} prompt(s) from adopted objects`);
|
|
7846
8055
|
return { filled, stillEmpty };
|
|
7847
8056
|
}
|
|
@@ -7853,14 +8062,14 @@ function formatUnmatchedRemote(objects, limit = 15) {
|
|
|
7853
8062
|
|
|
7854
8063
|
// src/pipeline/adopt-characters.ts
|
|
7855
8064
|
import { existsSync as existsSync12 } from "fs";
|
|
7856
|
-
import { mkdir as
|
|
7857
|
-
import
|
|
8065
|
+
import { mkdir as mkdir6, readFile as readFile10, writeFile as writeFile7 } from "fs/promises";
|
|
8066
|
+
import path16 from "path";
|
|
7858
8067
|
async function adoptCharacters(provider, specs, lock, lockPath, opts) {
|
|
7859
8068
|
const log2 = opts.onProgress ?? (() => {
|
|
7860
8069
|
});
|
|
7861
8070
|
const result = { scanned: 0, matched: 0, unmatched: [], remaining: [], written: [], differing: [] };
|
|
7862
8071
|
if (!provider.listCharacters || !provider.getCharacter) return result;
|
|
7863
|
-
const wanted = specs.filter((spec) => spec.character);
|
|
8072
|
+
const wanted = specs.filter((spec) => spec.character && !spec.mirror);
|
|
7864
8073
|
if (!wanted.length) return result;
|
|
7865
8074
|
const remote = [];
|
|
7866
8075
|
for await (const character of provider.listCharacters()) remote.push(character);
|
|
@@ -7906,11 +8115,11 @@ async function adoptCharacters(provider, specs, lock, lockPath, opts) {
|
|
|
7906
8115
|
const hash = sha256(bytes);
|
|
7907
8116
|
const file = expectedOutputPath(spec, source.role, index, sources.length);
|
|
7908
8117
|
if (existsSync12(file)) {
|
|
7909
|
-
if (sha256(await
|
|
8118
|
+
if (sha256(await readFile10(file)) !== hash) result.differing.push(path16.relative(process.cwd(), file));
|
|
7910
8119
|
} else {
|
|
7911
|
-
await
|
|
7912
|
-
await
|
|
7913
|
-
result.written.push(
|
|
8120
|
+
await mkdir6(path16.dirname(file), { recursive: true });
|
|
8121
|
+
await writeFile7(file, bytes);
|
|
8122
|
+
result.written.push(path16.relative(process.cwd(), file));
|
|
7914
8123
|
}
|
|
7915
8124
|
outputs.push({ path: portableOutputPath(file, spec.root), sha256: hash, ...source.role ? { role: source.role } : {} });
|
|
7916
8125
|
}
|
|
@@ -7949,10 +8158,10 @@ async function adoptCharacters(provider, specs, lock, lockPath, opts) {
|
|
|
7949
8158
|
} else {
|
|
7950
8159
|
const south = expectedOutputPath(spec, "south", 0, spec.character.directions);
|
|
7951
8160
|
if (!existsSync12(south)) {
|
|
7952
|
-
result.unmatched.push(`${key}: no remoteId and no ${
|
|
8161
|
+
result.unmatched.push(`${key}: no remoteId and no ${path16.relative(process.cwd(), south)} to match by`);
|
|
7953
8162
|
return false;
|
|
7954
8163
|
}
|
|
7955
|
-
const localHash = sha256(await
|
|
8164
|
+
const localHash = sha256(await readFile10(south));
|
|
7956
8165
|
for (const candidate of remote) {
|
|
7957
8166
|
if (claimed.has(candidate.id)) continue;
|
|
7958
8167
|
if (await southHash(candidate) === localHash) {
|
|
@@ -7961,7 +8170,7 @@ async function adoptCharacters(provider, specs, lock, lockPath, opts) {
|
|
|
7961
8170
|
}
|
|
7962
8171
|
}
|
|
7963
8172
|
if (!match) {
|
|
7964
|
-
result.unmatched.push(`${key}: no character's south rotation has the bytes of ${
|
|
8173
|
+
result.unmatched.push(`${key}: no character's south rotation has the bytes of ${path16.relative(process.cwd(), south)}`);
|
|
7965
8174
|
return false;
|
|
7966
8175
|
}
|
|
7967
8176
|
}
|
|
@@ -8031,12 +8240,12 @@ async function adoptCharacters(provider, specs, lock, lockPath, opts) {
|
|
|
8031
8240
|
if (!existsSync12(first)) {
|
|
8032
8241
|
const byName = candidates.find((a) => a.name === characterAnimationName(spec));
|
|
8033
8242
|
if (!byName) {
|
|
8034
|
-
result.unmatched.push(`${key}: no remoteId and no ${
|
|
8243
|
+
result.unmatched.push(`${key}: no remoteId and no ${path16.relative(process.cwd(), first)} to match by`);
|
|
8035
8244
|
return false;
|
|
8036
8245
|
}
|
|
8037
8246
|
match = byName;
|
|
8038
8247
|
} else {
|
|
8039
|
-
const localHash = sha256(await
|
|
8248
|
+
const localHash = sha256(await readFile10(first));
|
|
8040
8249
|
for (const candidate of candidates) {
|
|
8041
8250
|
if (sha256(await download(candidate.frames[0])) === localHash) {
|
|
8042
8251
|
match = candidate;
|
|
@@ -8044,7 +8253,7 @@ async function adoptCharacters(provider, specs, lock, lockPath, opts) {
|
|
|
8044
8253
|
}
|
|
8045
8254
|
}
|
|
8046
8255
|
if (!match) {
|
|
8047
|
-
result.unmatched.push(`${key}: no ${animation.direction} animation of character ${parentId} starts with the bytes of ${
|
|
8256
|
+
result.unmatched.push(`${key}: no ${animation.direction} animation of character ${parentId} starts with the bytes of ${path16.relative(process.cwd(), first)}`);
|
|
8048
8257
|
return false;
|
|
8049
8258
|
}
|
|
8050
8259
|
}
|
|
@@ -8104,9 +8313,9 @@ async function adoptCharacters(provider, specs, lock, lockPath, opts) {
|
|
|
8104
8313
|
}
|
|
8105
8314
|
|
|
8106
8315
|
// src/pipeline/salvage.ts
|
|
8107
|
-
import { readFile as
|
|
8316
|
+
import { readFile as readFile11 } from "fs/promises";
|
|
8108
8317
|
import { existsSync as existsSync13 } from "fs";
|
|
8109
|
-
import
|
|
8318
|
+
import path17 from "path";
|
|
8110
8319
|
function providerClaimId(provider, objectId) {
|
|
8111
8320
|
return `${encodeURIComponent(provider)}:${encodeURIComponent(objectId)}`;
|
|
8112
8321
|
}
|
|
@@ -8116,7 +8325,7 @@ async function loadClaims(lockPaths, opts = {}) {
|
|
|
8116
8325
|
if (!existsSync13(p)) throw new Error(`Claim lockfile not found: ${p}`);
|
|
8117
8326
|
let parsed;
|
|
8118
8327
|
try {
|
|
8119
|
-
parsed = parseLock(JSON.parse(await
|
|
8328
|
+
parsed = parseLock(JSON.parse(await readFile11(p, "utf8")));
|
|
8120
8329
|
} catch {
|
|
8121
8330
|
throw new Error(`Claim lockfile is malformed: ${p}`);
|
|
8122
8331
|
}
|
|
@@ -8164,20 +8373,20 @@ function matchStyleByPattern(prompt, manifest) {
|
|
|
8164
8373
|
return null;
|
|
8165
8374
|
}
|
|
8166
8375
|
async function loadSiblingManifests(ownManifestPath, workspaceManifestPaths, claimPaths) {
|
|
8167
|
-
const own =
|
|
8376
|
+
const own = path17.resolve(ownManifestPath);
|
|
8168
8377
|
const siblingManifestPaths = [
|
|
8169
8378
|
.../* @__PURE__ */ new Set([
|
|
8170
8379
|
...workspaceManifestPaths,
|
|
8171
|
-
...claimPaths.map((c) =>
|
|
8380
|
+
...claimPaths.map((c) => path17.join(path17.dirname(path17.resolve(c)), "pixelkiln.manifest.json"))
|
|
8172
8381
|
])
|
|
8173
8382
|
];
|
|
8174
8383
|
const siblings = [];
|
|
8175
8384
|
for (const siblingManifestPath of siblingManifestPaths) {
|
|
8176
|
-
if (
|
|
8385
|
+
if (path17.resolve(siblingManifestPath) === own) continue;
|
|
8177
8386
|
if (!existsSync13(siblingManifestPath)) continue;
|
|
8178
8387
|
try {
|
|
8179
8388
|
const { manifest } = await loadManifest(siblingManifestPath);
|
|
8180
|
-
siblings.push({ label:
|
|
8389
|
+
siblings.push({ label: path17.basename(path17.dirname(siblingManifestPath)), manifest });
|
|
8181
8390
|
} catch {
|
|
8182
8391
|
}
|
|
8183
8392
|
}
|
|
@@ -8293,9 +8502,9 @@ async function applyTags(provider, decisions, existing, opts = {}) {
|
|
|
8293
8502
|
}
|
|
8294
8503
|
|
|
8295
8504
|
// src/workspace.ts
|
|
8296
|
-
import { mkdir as
|
|
8505
|
+
import { mkdir as mkdir7, readFile as readFile12, rename as rename6, rm as rm6, writeFile as writeFile8 } from "fs/promises";
|
|
8297
8506
|
import { existsSync as existsSync14 } from "fs";
|
|
8298
|
-
import
|
|
8507
|
+
import path18 from "path";
|
|
8299
8508
|
import { z as z4 } from "zod";
|
|
8300
8509
|
var WorkspaceProjectSchema = z4.object({
|
|
8301
8510
|
id: z4.string().min(1),
|
|
@@ -8323,7 +8532,7 @@ async function loadWorkspace(workspacePath) {
|
|
|
8323
8532
|
if (!existsSync14(workspacePath)) return { version: 1, projects: [] };
|
|
8324
8533
|
let raw;
|
|
8325
8534
|
try {
|
|
8326
|
-
raw = JSON.parse(await
|
|
8535
|
+
raw = JSON.parse(await readFile12(workspacePath, "utf8"));
|
|
8327
8536
|
} catch (err) {
|
|
8328
8537
|
throw new ProjectError(
|
|
8329
8538
|
`Workspace catalog at ${workspacePath} is malformed:
|
|
@@ -8337,22 +8546,22 @@ async function saveWorkspace(workspacePath, ws) {
|
|
|
8337
8546
|
version: 1,
|
|
8338
8547
|
projects: [...ws.projects].sort((a, b) => a.id.localeCompare(b.id))
|
|
8339
8548
|
};
|
|
8340
|
-
await
|
|
8549
|
+
await mkdir7(path18.dirname(path18.resolve(workspacePath)), { recursive: true });
|
|
8341
8550
|
const tmp = `${workspacePath}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
8342
8551
|
try {
|
|
8343
|
-
await
|
|
8344
|
-
await
|
|
8552
|
+
await writeFile8(tmp, JSON.stringify(sorted, null, 2) + "\n");
|
|
8553
|
+
await rename6(tmp, workspacePath);
|
|
8345
8554
|
} finally {
|
|
8346
8555
|
await rm6(tmp, { force: true });
|
|
8347
8556
|
}
|
|
8348
8557
|
}
|
|
8349
8558
|
function toPortablePath(dir, absolute) {
|
|
8350
|
-
return
|
|
8559
|
+
return path18.relative(dir, absolute).split(path18.sep).join("/");
|
|
8351
8560
|
}
|
|
8352
8561
|
function resolveProject(dir, project) {
|
|
8353
8562
|
return {
|
|
8354
|
-
manifestPath:
|
|
8355
|
-
lockPath:
|
|
8563
|
+
manifestPath: path18.resolve(dir, project.manifest.split("/").join(path18.sep)),
|
|
8564
|
+
lockPath: path18.resolve(dir, project.lock.split("/").join(path18.sep))
|
|
8356
8565
|
};
|
|
8357
8566
|
}
|
|
8358
8567
|
function validateWorkspace(ws, dir) {
|
|
@@ -8365,7 +8574,7 @@ function validateWorkspace(ws, dir) {
|
|
|
8365
8574
|
const { manifestPath, lockPath } = resolveProject(dir, project);
|
|
8366
8575
|
lockOwners.set(lockPath, [...lockOwners.get(lockPath) ?? [], project.id]);
|
|
8367
8576
|
manifestOwners.set(manifestPath, [...manifestOwners.get(manifestPath) ?? [], project.id]);
|
|
8368
|
-
if (
|
|
8577
|
+
if (path18.isAbsolute(project.manifest) || path18.isAbsolute(project.lock)) {
|
|
8369
8578
|
diagnostics.push({
|
|
8370
8579
|
id: "absolute-path",
|
|
8371
8580
|
level: "warning",
|
|
@@ -8426,8 +8635,8 @@ function validateWorkspace(ws, dir) {
|
|
|
8426
8635
|
}
|
|
8427
8636
|
|
|
8428
8637
|
// src/pick/salvage-server.ts
|
|
8429
|
-
import { mkdir as
|
|
8430
|
-
import
|
|
8638
|
+
import { mkdir as mkdir8, writeFile as writeFile9, readFile as readFile14 } from "fs/promises";
|
|
8639
|
+
import path19 from "path";
|
|
8431
8640
|
|
|
8432
8641
|
// src/pick/salvage-sheet.ts
|
|
8433
8642
|
var escapeHtml = (s) => s.replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
@@ -8587,7 +8796,7 @@ refresh();
|
|
|
8587
8796
|
|
|
8588
8797
|
// src/pick/review-server.ts
|
|
8589
8798
|
import { createServer } from "http";
|
|
8590
|
-
import { readFile as
|
|
8799
|
+
import { readFile as readFile13 } from "fs/promises";
|
|
8591
8800
|
|
|
8592
8801
|
// src/open.ts
|
|
8593
8802
|
import { spawn } from "child_process";
|
|
@@ -8614,7 +8823,7 @@ function serveReviewPage(opts) {
|
|
|
8614
8823
|
if (req.method === "GET" && req.url && opts.assets?.has(req.url)) {
|
|
8615
8824
|
try {
|
|
8616
8825
|
const asset = opts.assets.get(req.url);
|
|
8617
|
-
const bytes = await
|
|
8826
|
+
const bytes = await readFile13(asset.path);
|
|
8618
8827
|
res.writeHead(200, {
|
|
8619
8828
|
"Content-Type": asset.contentType,
|
|
8620
8829
|
"Cache-Control": "no-store",
|
|
@@ -8691,7 +8900,7 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
8691
8900
|
});
|
|
8692
8901
|
const html = renderSalvageSheet(orphans, {
|
|
8693
8902
|
styleId: ctx.styleId,
|
|
8694
|
-
importDir:
|
|
8903
|
+
importDir: path19.relative(process.cwd(), ctx.importDir) || "."
|
|
8695
8904
|
});
|
|
8696
8905
|
const byId = new Map(orphans.map((o) => [o.id, o]));
|
|
8697
8906
|
const existingTags = new Map(orphans.map((o) => [o.id, o.tags]));
|
|
@@ -8720,10 +8929,10 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
8720
8929
|
if (!buf.subarray(0, 8).equals(PNG_SIGNATURE2)) throw new Error("not a PNG");
|
|
8721
8930
|
decodePng(buf);
|
|
8722
8931
|
const assetId = idFromPrompt(orphan.prompt, taken);
|
|
8723
|
-
const rel =
|
|
8724
|
-
const outFile =
|
|
8725
|
-
await
|
|
8726
|
-
await
|
|
8932
|
+
const rel = path19.join("_salvaged", `${assetId}.png`);
|
|
8933
|
+
const outFile = path19.resolve(ctx.importDir, rel);
|
|
8934
|
+
await mkdir8(path19.dirname(outFile), { recursive: true });
|
|
8935
|
+
await writeFile9(outFile, buf);
|
|
8727
8936
|
ctx.manifest.assets[assetId] = {
|
|
8728
8937
|
prompt: orphan.prompt,
|
|
8729
8938
|
promptByStyle: {},
|
|
@@ -8755,7 +8964,7 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
8755
8964
|
sourceUrl: durableSource,
|
|
8756
8965
|
sourceUrls: durableSource ? [{ url: durableSource }] : [],
|
|
8757
8966
|
outputs: [{
|
|
8758
|
-
path: portableOutputPath(outFile,
|
|
8967
|
+
path: portableOutputPath(outFile, path19.dirname(ctx.manifestPath)),
|
|
8759
8968
|
sha256: sha256(buf)
|
|
8760
8969
|
}],
|
|
8761
8970
|
submittedAt: orphan.createdAt,
|
|
@@ -8779,9 +8988,9 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
8779
8988
|
}
|
|
8780
8989
|
}
|
|
8781
8990
|
await applyTags(provider, decisions, existingTags, { onProgress: log2 });
|
|
8782
|
-
const raw = JSON.parse(await
|
|
8991
|
+
const raw = JSON.parse(await readFile14(ctx.manifestPath, "utf8"));
|
|
8783
8992
|
for (const id of importedAssetIds) raw.assets[id] = ctx.manifest.assets[id];
|
|
8784
|
-
await
|
|
8993
|
+
await writeFile9(ctx.manifestPath, JSON.stringify(raw, null, 2) + "\n");
|
|
8785
8994
|
await saveLock(ctx.lockPath, ctx.lock);
|
|
8786
8995
|
return result;
|
|
8787
8996
|
}
|
|
@@ -8789,19 +8998,19 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
8789
8998
|
}
|
|
8790
8999
|
|
|
8791
9000
|
// src/cli/project.ts
|
|
8792
|
-
import
|
|
9001
|
+
import path21 from "path";
|
|
8793
9002
|
import { existsSync as existsSync15 } from "fs";
|
|
8794
9003
|
|
|
8795
9004
|
// src/project.ts
|
|
8796
|
-
import
|
|
9005
|
+
import path20 from "path";
|
|
8797
9006
|
async function openProject(manifestPath = "pixelkiln.manifest.json", opts = {}) {
|
|
8798
|
-
const absolute =
|
|
8799
|
-
const root =
|
|
9007
|
+
const absolute = path20.resolve(manifestPath);
|
|
9008
|
+
const root = path20.dirname(absolute);
|
|
8800
9009
|
if (opts.env !== false) {
|
|
8801
9010
|
loadEnvFiles(root);
|
|
8802
|
-
if (
|
|
9011
|
+
if (path20.resolve(process.cwd()) !== root) loadEnvFiles(process.cwd());
|
|
8803
9012
|
}
|
|
8804
|
-
const lockPath =
|
|
9013
|
+
const lockPath = path20.resolve(opts.lockPath ?? path20.join(root, "pixelkiln.lock.json"));
|
|
8805
9014
|
const loaded = await loadManifest(absolute);
|
|
8806
9015
|
const specs = await resolveSpecs(loaded, { styles: opts.styles ?? [], assets: opts.assets ?? [] });
|
|
8807
9016
|
const lock = await loadLock(lockPath);
|
|
@@ -8932,7 +9141,7 @@ async function workspaceStatus(ws, dir) {
|
|
|
8932
9141
|
|
|
8933
9142
|
// src/cli/project.ts
|
|
8934
9143
|
async function provenanceFile(id, file) {
|
|
8935
|
-
const absolute =
|
|
9144
|
+
const absolute = path21.resolve(file);
|
|
8936
9145
|
return {
|
|
8937
9146
|
id,
|
|
8938
9147
|
path: absolute,
|
|
@@ -9011,7 +9220,7 @@ async function requireCompleteWorkspaceClaims(workspacePath) {
|
|
|
9011
9220
|
if (!existsSync15(workspacePath)) {
|
|
9012
9221
|
throw new ProjectError(`Workspace catalog not found: ${workspacePath}`);
|
|
9013
9222
|
}
|
|
9014
|
-
const dir =
|
|
9223
|
+
const dir = path21.dirname(path21.resolve(workspacePath));
|
|
9015
9224
|
const ws = await loadWorkspace(workspacePath);
|
|
9016
9225
|
const diagnostics = validateWorkspace(ws, dir);
|
|
9017
9226
|
const errors = diagnostics.filter((d) => d.level === "error");
|
|
@@ -9034,9 +9243,9 @@ async function openAccountProject(args) {
|
|
|
9034
9243
|
return project;
|
|
9035
9244
|
}
|
|
9036
9245
|
async function open(args) {
|
|
9037
|
-
if (!existsSync15(
|
|
9246
|
+
if (!existsSync15(path21.resolve(args.manifest))) {
|
|
9038
9247
|
throw new ProjectError(
|
|
9039
|
-
`No manifest at ${
|
|
9248
|
+
`No manifest at ${path21.resolve(args.manifest)}. Pass --manifest, or run \`pixelkiln init --from <dir>\`.`
|
|
9040
9249
|
);
|
|
9041
9250
|
}
|
|
9042
9251
|
const project = await openProject(args.manifest, { lockPath: args.lock, styles: args.styles, assets: args.assets });
|
|
@@ -9078,7 +9287,7 @@ function providerCache(mode2) {
|
|
|
9078
9287
|
|
|
9079
9288
|
// src/cli/commands/account.ts
|
|
9080
9289
|
async function runBalance(args) {
|
|
9081
|
-
loadEnvFiles(
|
|
9290
|
+
loadEnvFiles(path22.dirname(path22.resolve(args.manifest)));
|
|
9082
9291
|
loadEnvFiles(process.cwd());
|
|
9083
9292
|
const loaded = await loadManifest(args.manifest);
|
|
9084
9293
|
const selectedProvider = accountProviderId(loaded.manifest, args.provider, "balance");
|
|
@@ -9151,12 +9360,12 @@ async function runAdopt(args) {
|
|
|
9151
9360
|
tagged ${n} object(s) upstream`);
|
|
9152
9361
|
}
|
|
9153
9362
|
if (args.writePrompts) {
|
|
9154
|
-
const { filled, stillEmpty } = await writePromptsBack(
|
|
9363
|
+
const { filled, stillEmpty } = await writePromptsBack(path22.resolve(args.manifest), lock, {
|
|
9155
9364
|
onProgress: log,
|
|
9156
9365
|
provider: provider.id,
|
|
9157
9366
|
assetIds: specs.map((spec) => spec.assetId)
|
|
9158
9367
|
});
|
|
9159
|
-
log(` recovered ${filled} prompt(s) into ${
|
|
9368
|
+
log(` recovered ${filled} prompt(s) into ${path22.relative(process.cwd(), args.manifest)}`);
|
|
9160
9369
|
const reloaded = await loadManifest(args.manifest);
|
|
9161
9370
|
const rebased = (await resolveSpecs(reloaded, {
|
|
9162
9371
|
styles: args.styles,
|
|
@@ -9186,12 +9395,12 @@ async function runSalvage2(args) {
|
|
|
9186
9395
|
const provider = providerFor(accountProvider);
|
|
9187
9396
|
const jsonMode = args.dryRun && args.json;
|
|
9188
9397
|
const diag = jsonMode ? (msg = "") => console.error(msg) : log;
|
|
9189
|
-
const ownLock =
|
|
9398
|
+
const ownLock = path22.resolve(args.lock);
|
|
9190
9399
|
let workspaceProjects = [];
|
|
9191
9400
|
let workspaceDir = "";
|
|
9192
9401
|
if (args.workspace) {
|
|
9193
|
-
const workspacePath =
|
|
9194
|
-
workspaceDir =
|
|
9402
|
+
const workspacePath = path22.resolve(args.workspace);
|
|
9403
|
+
workspaceDir = path22.dirname(workspacePath);
|
|
9195
9404
|
const complete = await requireCompleteWorkspaceClaims(workspacePath);
|
|
9196
9405
|
workspaceProjects = complete.ws.projects;
|
|
9197
9406
|
for (const d of complete.diagnostics) diag(` WARN ${d.id}: ${d.message}`);
|
|
@@ -9201,11 +9410,11 @@ async function runSalvage2(args) {
|
|
|
9201
9410
|
.../* @__PURE__ */ new Set([
|
|
9202
9411
|
...workspaceLockPaths,
|
|
9203
9412
|
...existsSync16(ownLock) ? [ownLock] : [],
|
|
9204
|
-
...args.claims.map((c) =>
|
|
9413
|
+
...args.claims.map((c) => path22.resolve(c))
|
|
9205
9414
|
])
|
|
9206
9415
|
];
|
|
9207
9416
|
diag(` claim set (${lockPaths.length} lockfile(s)):`);
|
|
9208
|
-
for (const p of lockPaths) diag(` ${
|
|
9417
|
+
for (const p of lockPaths) diag(` ${path22.relative(process.cwd(), p)}`);
|
|
9209
9418
|
if (!args.claims.length && !args.workspace) {
|
|
9210
9419
|
diag(
|
|
9211
9420
|
`
|
|
@@ -9213,7 +9422,7 @@ async function runSalvage2(args) {
|
|
|
9213
9422
|
pass every other project's lockfile via --claims a.json,b.json, or
|
|
9214
9423
|
register every project in a workspace catalog and pass --workspace.`
|
|
9215
9424
|
);
|
|
9216
|
-
} else if (args.workspace && !workspaceProjects.some((p) => resolveProject(workspaceDir, p).manifestPath ===
|
|
9425
|
+
} else if (args.workspace && !workspaceProjects.some((p) => resolveProject(workspaceDir, p).manifestPath === path22.resolve(args.manifest))) {
|
|
9217
9426
|
diag(
|
|
9218
9427
|
`
|
|
9219
9428
|
This project's manifest is not registered in the workspace catalog. Its own
|
|
@@ -9297,7 +9506,7 @@ async function runSalvage2(args) {
|
|
|
9297
9506
|
manifestPath: loaded.path,
|
|
9298
9507
|
manifest: accountManifest,
|
|
9299
9508
|
styleId,
|
|
9300
|
-
importDir:
|
|
9509
|
+
importDir: path22.resolve(loaded.root, style.outDir),
|
|
9301
9510
|
lock,
|
|
9302
9511
|
lockPath: args.lock
|
|
9303
9512
|
},
|
|
@@ -9398,17 +9607,17 @@ async function runTag(args) {
|
|
|
9398
9607
|
}
|
|
9399
9608
|
|
|
9400
9609
|
// src/cli/commands/edit.ts
|
|
9401
|
-
import
|
|
9610
|
+
import path25 from "path";
|
|
9402
9611
|
|
|
9403
9612
|
// src/pipeline/hand-edit.ts
|
|
9404
9613
|
import { existsSync as existsSync17 } from "fs";
|
|
9405
|
-
import { copyFile, mkdir as
|
|
9406
|
-
import
|
|
9614
|
+
import { copyFile, mkdir as mkdir9, readFile as readFile16, rename as rename8, rm as rm8, writeFile as writeFile11 } from "fs/promises";
|
|
9615
|
+
import path24 from "path";
|
|
9407
9616
|
import { z as z6 } from "zod";
|
|
9408
9617
|
|
|
9409
9618
|
// src/manifest-edit.ts
|
|
9410
|
-
import { readFile as
|
|
9411
|
-
import
|
|
9619
|
+
import { readFile as readFile15, rename as rename7, rm as rm7, writeFile as writeFile10 } from "fs/promises";
|
|
9620
|
+
import path23 from "path";
|
|
9412
9621
|
import { z as z5 } from "zod";
|
|
9413
9622
|
var HexSha = z5.string().regex(/^[0-9a-f]{64}$/);
|
|
9414
9623
|
var AssetPatchSchema = z5.object({
|
|
@@ -9623,8 +9832,8 @@ function applyEdit(raw, edit) {
|
|
|
9623
9832
|
}
|
|
9624
9833
|
}
|
|
9625
9834
|
async function applyManifestEdit(manifestPath, edit) {
|
|
9626
|
-
const absolute =
|
|
9627
|
-
const original = await
|
|
9835
|
+
const absolute = path23.resolve(manifestPath);
|
|
9836
|
+
const original = await readFile15(absolute, "utf8");
|
|
9628
9837
|
const before = sha256(original);
|
|
9629
9838
|
if (before !== edit.expectedSha256) throw new ManifestDriftError(absolute);
|
|
9630
9839
|
let raw;
|
|
@@ -9638,12 +9847,12 @@ async function applyManifestEdit(manifestPath, edit) {
|
|
|
9638
9847
|
applyEdit(raw, edit);
|
|
9639
9848
|
const next = serializeLike(original, raw);
|
|
9640
9849
|
if (next === original) return { manifestPath: absolute, sha256: before, changed: false };
|
|
9641
|
-
const tmp =
|
|
9642
|
-
|
|
9643
|
-
`.${
|
|
9850
|
+
const tmp = path23.join(
|
|
9851
|
+
path23.dirname(absolute),
|
|
9852
|
+
`.${path23.basename(absolute)}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`
|
|
9644
9853
|
);
|
|
9645
9854
|
try {
|
|
9646
|
-
await
|
|
9855
|
+
await writeFile10(tmp, next);
|
|
9647
9856
|
try {
|
|
9648
9857
|
const loaded = await loadManifest(tmp);
|
|
9649
9858
|
await resolveSpecs(loaded);
|
|
@@ -9651,7 +9860,7 @@ async function applyManifestEdit(manifestPath, edit) {
|
|
|
9651
9860
|
const message6 = error instanceof Error ? error.message : String(error);
|
|
9652
9861
|
throw new ManifestEditError(message6.replace(tmp, absolute));
|
|
9653
9862
|
}
|
|
9654
|
-
await
|
|
9863
|
+
await rename7(tmp, absolute);
|
|
9655
9864
|
} finally {
|
|
9656
9865
|
await rm7(tmp, { force: true });
|
|
9657
9866
|
}
|
|
@@ -9662,11 +9871,11 @@ async function applyManifestEdit(manifestPath, edit) {
|
|
|
9662
9871
|
var HAND_EDIT_DIR = "edits";
|
|
9663
9872
|
function handEditPath(loaded, spec) {
|
|
9664
9873
|
const style = loaded.manifest.styles[spec.styleId];
|
|
9665
|
-
const outDir =
|
|
9666
|
-
const relative =
|
|
9667
|
-
const inside = relative && !relative.startsWith("..") && !
|
|
9668
|
-
const file = inside ? relative :
|
|
9669
|
-
return
|
|
9874
|
+
const outDir = path24.resolve(loaded.root, style?.outDir ?? ".");
|
|
9875
|
+
const relative = path24.relative(outDir, spec.outFile);
|
|
9876
|
+
const inside = relative && !relative.startsWith("..") && !path24.isAbsolute(relative);
|
|
9877
|
+
const file = inside ? relative : path24.basename(spec.outFile);
|
|
9878
|
+
return path24.join(outDir, HAND_EDIT_DIR, file.replace(/\.gif$/i, ".png"));
|
|
9670
9879
|
}
|
|
9671
9880
|
function handEditBases(spec, lock) {
|
|
9672
9881
|
const entry = lock.entries[lockKey(spec.styleId, spec.assetId)];
|
|
@@ -9708,7 +9917,7 @@ async function startHandEdit(loaded, lock, spec, opts = {}) {
|
|
|
9708
9917
|
`${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`
|
|
9709
9918
|
);
|
|
9710
9919
|
}
|
|
9711
|
-
const existing = spec.source ?
|
|
9920
|
+
const existing = spec.source ? path24.resolve(loaded.root, spec.source) : null;
|
|
9712
9921
|
const editPath = existing ?? handEditPath(loaded, spec);
|
|
9713
9922
|
const members = handEditMembers(loaded, spec, lock, editPath);
|
|
9714
9923
|
let created = false;
|
|
@@ -9719,7 +9928,7 @@ async function startHandEdit(loaded, lock, spec, opts = {}) {
|
|
|
9719
9928
|
`${spec.styleId}/${spec.assetId} has no generated PNG on disk to start from; generate or restore it first`
|
|
9720
9929
|
);
|
|
9721
9930
|
}
|
|
9722
|
-
await
|
|
9931
|
+
await mkdir9(path24.dirname(member.path), { recursive: true });
|
|
9723
9932
|
await copyFile(member.base, member.path);
|
|
9724
9933
|
created = true;
|
|
9725
9934
|
}
|
|
@@ -9798,7 +10007,7 @@ function handEditProjectPath(editPath) {
|
|
|
9798
10007
|
async function readHandEditCompanion(editPath) {
|
|
9799
10008
|
let text;
|
|
9800
10009
|
try {
|
|
9801
|
-
text = await
|
|
10010
|
+
text = await readFile16(handEditCompanionPath(editPath), "utf8");
|
|
9802
10011
|
} catch {
|
|
9803
10012
|
return null;
|
|
9804
10013
|
}
|
|
@@ -9841,7 +10050,7 @@ async function saveHandEdit(loaded, lock, spec, input) {
|
|
|
9841
10050
|
}
|
|
9842
10051
|
});
|
|
9843
10052
|
const bases = handEditBases(spec, lock);
|
|
9844
|
-
const expectedSize = bases?.[0] ? decodePng(await
|
|
10053
|
+
const expectedSize = bases?.[0] ? decodePng(await readFile16(bases[0].path)) : { width: spec.width, height: spec.height };
|
|
9845
10054
|
for (const [index, image] of decoded.entries()) {
|
|
9846
10055
|
if (image.width !== expectedSize.width || image.height !== expectedSize.height) {
|
|
9847
10056
|
const which = memberSet ? `member ${incoming[index].role ?? index}` : "the edit";
|
|
@@ -9857,7 +10066,7 @@ async function saveHandEdit(loaded, lock, spec, input) {
|
|
|
9857
10066
|
editor: input.editor,
|
|
9858
10067
|
protocol: input.protocol,
|
|
9859
10068
|
savedAt: (input.now ?? /* @__PURE__ */ new Date()).toISOString(),
|
|
9860
|
-
project: projectPath ?
|
|
10069
|
+
project: projectPath ? path24.basename(projectPath) : null,
|
|
9861
10070
|
outputs: incoming.map((frame, index) => ({
|
|
9862
10071
|
role: frame.role,
|
|
9863
10072
|
basedOn: basedOnHash(entry, index),
|
|
@@ -9887,8 +10096,8 @@ function basedOnHash(entry, index) {
|
|
|
9887
10096
|
async function replaceFile(file, bytes) {
|
|
9888
10097
|
const tmp = `${file}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
9889
10098
|
try {
|
|
9890
|
-
await
|
|
9891
|
-
await
|
|
10099
|
+
await writeFile11(tmp, bytes);
|
|
10100
|
+
await rename8(tmp, file);
|
|
9892
10101
|
} finally {
|
|
9893
10102
|
await rm8(tmp, { force: true });
|
|
9894
10103
|
}
|
|
@@ -9917,7 +10126,7 @@ async function runEdit(args) {
|
|
|
9917
10126
|
const set = started.members.length > 1;
|
|
9918
10127
|
const kind = spec.generator === "frames" ? "frames" : "members";
|
|
9919
10128
|
log(` ${started.created ? "created" : "found"} ${started.source}${set ? ` (${started.members.length} ${kind})` : ""}${started.declared ? " and declared it as the asset's source" : ""}`);
|
|
9920
|
-
if (set) for (const member of started.members) log(` ${member.role ?? ""} ${
|
|
10129
|
+
if (set) for (const member of started.members) log(` ${member.role ?? ""} ${path25.relative(process.cwd(), member.path)}`);
|
|
9921
10130
|
if (args.noOpen) {
|
|
9922
10131
|
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`);
|
|
9923
10132
|
} else {
|
|
@@ -9927,17 +10136,17 @@ async function runEdit(args) {
|
|
|
9927
10136
|
}
|
|
9928
10137
|
|
|
9929
10138
|
// src/cli/commands/gallery.ts
|
|
9930
|
-
import
|
|
10139
|
+
import path32 from "path";
|
|
9931
10140
|
import { existsSync as existsSync23 } from "fs";
|
|
9932
10141
|
|
|
9933
10142
|
// src/gallery/snapshot.ts
|
|
9934
10143
|
import { createHash as createHash3 } from "crypto";
|
|
9935
10144
|
import { existsSync as existsSync19 } from "fs";
|
|
9936
|
-
import { readFile as
|
|
9937
|
-
import
|
|
10145
|
+
import { readFile as readFile17, stat as stat2 } from "fs/promises";
|
|
10146
|
+
import path28 from "path";
|
|
9938
10147
|
|
|
9939
10148
|
// src/gallery/edit.ts
|
|
9940
|
-
import
|
|
10149
|
+
import path26 from "path";
|
|
9941
10150
|
import { z as z7 } from "zod";
|
|
9942
10151
|
var Base64 = z7.string().regex(/^[A-Za-z0-9+/]*={0,2}$/).max(Math.ceil(MAX_HAND_EDIT_BYTES / 3) * 4);
|
|
9943
10152
|
var HandEditRequestSchema = z7.discriminatedUnion("action", [
|
|
@@ -10027,7 +10236,7 @@ function createGalleryEditHandler(opts) {
|
|
|
10027
10236
|
}
|
|
10028
10237
|
const result = await applyManifestEdit(manifestPath, parsed.data);
|
|
10029
10238
|
if (result.changed) {
|
|
10030
|
-
const relative =
|
|
10239
|
+
const relative = path26.relative(process.cwd(), result.manifestPath);
|
|
10031
10240
|
const shown = !relative || relative.startsWith("..") ? result.manifestPath : relative;
|
|
10032
10241
|
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;
|
|
10033
10242
|
log2(` manifest edited: ${parsed.data.action === "add-asset" ? "added" : "changed"} ${subject} in ${shown}`);
|
|
@@ -10038,7 +10247,7 @@ function createGalleryEditHandler(opts) {
|
|
|
10038
10247
|
|
|
10039
10248
|
// src/pipeline/history.ts
|
|
10040
10249
|
import { existsSync as existsSync18 } from "fs";
|
|
10041
|
-
import
|
|
10250
|
+
import path27 from "path";
|
|
10042
10251
|
var HISTORY_ENV = "PIXELKILN_HISTORY";
|
|
10043
10252
|
function historyLimit(manifest, env = process.env) {
|
|
10044
10253
|
if (manifest?.history !== void 0) return manifest.history;
|
|
@@ -10127,7 +10336,7 @@ async function revertGeneration(provider, spec, lock, lockPath, opts) {
|
|
|
10127
10336
|
if (!existsSync18(file)) continue;
|
|
10128
10337
|
if (await sha256File(file) !== entry.outputs[i].sha256 && !opts.force) {
|
|
10129
10338
|
throw new OverwriteRefusedError(
|
|
10130
|
-
`${
|
|
10339
|
+
`${path27.relative(process.cwd(), file)} was changed after download; keep it with pixelkiln edit, or pass --force to replace it`,
|
|
10131
10340
|
[file]
|
|
10132
10341
|
);
|
|
10133
10342
|
}
|
|
@@ -10179,7 +10388,7 @@ async function revertGeneration(provider, spec, lock, lockPath, opts) {
|
|
|
10179
10388
|
// src/gallery/snapshot.ts
|
|
10180
10389
|
var PROJECT_FILE_TYPE = "application/octet-stream";
|
|
10181
10390
|
function galleryMediaId(absolutePath) {
|
|
10182
|
-
return createHash3("sha256").update(
|
|
10391
|
+
return createHash3("sha256").update(path28.resolve(absolutePath)).digest("hex").slice(0, 24);
|
|
10183
10392
|
}
|
|
10184
10393
|
function galleryMediaRoute(id) {
|
|
10185
10394
|
return `/media/${id}`;
|
|
@@ -10198,7 +10407,7 @@ function describeHistory(media, entry, cacheDir) {
|
|
|
10198
10407
|
return (entry.history ?? []).map((generation, i) => {
|
|
10199
10408
|
const outputs = generation.outputs.map((output) => {
|
|
10200
10409
|
const mediaType = output.mediaType ?? MediaType.PNG;
|
|
10201
|
-
const file =
|
|
10410
|
+
const file = path28.join(cacheDir, cacheFileName(output.sha256, mediaType));
|
|
10202
10411
|
let url = null;
|
|
10203
10412
|
if (existsSync19(file)) {
|
|
10204
10413
|
const id = galleryMediaId(file);
|
|
@@ -10229,7 +10438,7 @@ function describeHistory(media, entry, cacheDir) {
|
|
|
10229
10438
|
var MAX_COMPARED_BYTES = 8 * 1024 * 1024;
|
|
10230
10439
|
async function samePixels(a, b) {
|
|
10231
10440
|
try {
|
|
10232
|
-
const [left, right] = await Promise.all([
|
|
10441
|
+
const [left, right] = await Promise.all([readFile17(a), readFile17(b)]);
|
|
10233
10442
|
if (left.length > MAX_COMPARED_BYTES || right.length > MAX_COMPARED_BYTES) return false;
|
|
10234
10443
|
const x = decodePng(left);
|
|
10235
10444
|
const y = decodePng(right);
|
|
@@ -10299,9 +10508,9 @@ async function describeOutput(media, root, absolutePath, recorded = {}) {
|
|
|
10299
10508
|
async function describeQuality(media, root, inspection) {
|
|
10300
10509
|
const outputPaths = inspection.outputs ?? [inspection.output];
|
|
10301
10510
|
const outputs = await Promise.all(
|
|
10302
|
-
outputPaths.map((file) => describeOutput(media, root,
|
|
10511
|
+
outputPaths.map((file) => describeOutput(media, root, path28.resolve(root, file)))
|
|
10303
10512
|
);
|
|
10304
|
-
const recordPath =
|
|
10513
|
+
const recordPath = path28.resolve(root, inspection.record);
|
|
10305
10514
|
const recordExists = existsSync19(recordPath);
|
|
10306
10515
|
let options = null;
|
|
10307
10516
|
let check = null;
|
|
@@ -10321,7 +10530,7 @@ async function describeQuality(media, root, inspection) {
|
|
|
10321
10530
|
return {
|
|
10322
10531
|
state: inspection.state,
|
|
10323
10532
|
reason: inspection.reason,
|
|
10324
|
-
output: portableOutputPath(
|
|
10533
|
+
output: portableOutputPath(path28.resolve(root, inspection.output), root),
|
|
10325
10534
|
record: portableOutputPath(recordPath, root),
|
|
10326
10535
|
recordExists,
|
|
10327
10536
|
outputs,
|
|
@@ -10358,9 +10567,9 @@ async function buildGallerySnapshot(opts) {
|
|
|
10358
10567
|
const media = /* @__PURE__ */ new Map();
|
|
10359
10568
|
const plan = await buildPlan(specs, lock);
|
|
10360
10569
|
const items = [];
|
|
10361
|
-
const manifestText = await
|
|
10570
|
+
const manifestText = await readFile17(loaded.path, "utf8");
|
|
10362
10571
|
const manifestSha256 = sha256(manifestText);
|
|
10363
|
-
const cacheDir =
|
|
10572
|
+
const cacheDir = path28.resolve(path28.dirname(opts.lockPath), ".pixelkiln", "cache");
|
|
10364
10573
|
let rawStyles = {};
|
|
10365
10574
|
try {
|
|
10366
10575
|
const raw = JSON.parse(manifestText);
|
|
@@ -10384,7 +10593,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
10384
10593
|
)
|
|
10385
10594
|
);
|
|
10386
10595
|
if (spec.source) {
|
|
10387
|
-
const editPath =
|
|
10596
|
+
const editPath = path28.resolve(root, spec.source);
|
|
10388
10597
|
const memberCount = sourceIsStem(spec.source, entry, root) ? entry.outputs.length : 1;
|
|
10389
10598
|
const editShas = await Promise.all(
|
|
10390
10599
|
Array.from({ length: memberCount }, async (_, index) => {
|
|
@@ -10433,7 +10642,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
10433
10642
|
editStatus = edits.some((member) => !member.exists) ? "missing" : editChanged.every((changed) => !changed) ? "same" : regenerated ? "regenerated-since" : "edited";
|
|
10434
10643
|
}
|
|
10435
10644
|
} else if (spec.source) {
|
|
10436
|
-
outputs = [await describeOutput(media, root,
|
|
10645
|
+
outputs = [await describeOutput(media, root, path28.resolve(root, spec.source))];
|
|
10437
10646
|
} else if (!entry && existsSync19(spec.outFile)) {
|
|
10438
10647
|
outputs = [await describeOutput(media, root, spec.outFile)];
|
|
10439
10648
|
} else {
|
|
@@ -10473,6 +10682,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
10473
10682
|
currentSpecHash: spec.specHash,
|
|
10474
10683
|
revision: entry?.revision ?? null,
|
|
10475
10684
|
revisionParentKey: spec.revision ? lockKey(spec.styleId, spec.revision.sourceAssetId) : entry?.revision ? lockKey(spec.styleId, entry.revision.sourceAssetId) : null,
|
|
10685
|
+
mirrorOfKey: spec.mirror ? lockKey(spec.styleId, spec.mirror.sourceAssetId) : entry?.mirror ? lockKey(spec.styleId, entry.mirror.sourceAssetId) : null,
|
|
10476
10686
|
character: describeCharacter(spec, entry),
|
|
10477
10687
|
outputs,
|
|
10478
10688
|
quality,
|
|
@@ -10535,6 +10745,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
10535
10745
|
currentSpecHash: null,
|
|
10536
10746
|
revision: entry.revision,
|
|
10537
10747
|
revisionParentKey: entry.revision ? lockKey(entry.styleId, entry.revision.sourceAssetId) : null,
|
|
10748
|
+
mirrorOfKey: entry.mirror ? lockKey(entry.styleId, entry.mirror.sourceAssetId) : null,
|
|
10538
10749
|
character: describeCharacter(void 0, entry),
|
|
10539
10750
|
outputs,
|
|
10540
10751
|
quality: null,
|
|
@@ -10623,7 +10834,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
10623
10834
|
id: loaded.manifest.name,
|
|
10624
10835
|
name: loaded.manifest.name,
|
|
10625
10836
|
manifest: loaded.path,
|
|
10626
|
-
lock:
|
|
10837
|
+
lock: path28.resolve(opts.lockPath),
|
|
10627
10838
|
root,
|
|
10628
10839
|
provider: loaded.manifest.provider,
|
|
10629
10840
|
account: null,
|
|
@@ -10649,7 +10860,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
10649
10860
|
return { snapshot, media };
|
|
10650
10861
|
}
|
|
10651
10862
|
async function buildWorkspaceGallerySnapshot(opts) {
|
|
10652
|
-
const dir =
|
|
10863
|
+
const dir = path28.dirname(path28.resolve(opts.workspacePath));
|
|
10653
10864
|
const media = /* @__PURE__ */ new Map();
|
|
10654
10865
|
const projects = [];
|
|
10655
10866
|
const items = [];
|
|
@@ -10692,11 +10903,11 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
10692
10903
|
id: project.id,
|
|
10693
10904
|
name: loaded.manifest.name,
|
|
10694
10905
|
manifest: manifestPath,
|
|
10695
|
-
lock:
|
|
10906
|
+
lock: path28.resolve(lockPath),
|
|
10696
10907
|
root: loaded.root,
|
|
10697
10908
|
provider: loaded.manifest.provider,
|
|
10698
10909
|
account: project.account ?? null,
|
|
10699
|
-
manifestSha256: manifestSha256 ?? sha256(await
|
|
10910
|
+
manifestSha256: manifestSha256 ?? sha256(await readFile17(manifestPath, "utf8")),
|
|
10700
10911
|
historyLimit: historyLimit(loaded.manifest),
|
|
10701
10912
|
entries: Object.keys(lock.entries).length,
|
|
10702
10913
|
items: projectItems.length,
|
|
@@ -10708,8 +10919,8 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
10708
10919
|
id: project.id,
|
|
10709
10920
|
name: project.id,
|
|
10710
10921
|
manifest: manifestPath,
|
|
10711
|
-
lock:
|
|
10712
|
-
root:
|
|
10922
|
+
lock: path28.resolve(lockPath),
|
|
10923
|
+
root: path28.dirname(manifestPath),
|
|
10713
10924
|
provider: project.provider,
|
|
10714
10925
|
account: project.account ?? null,
|
|
10715
10926
|
manifestSha256: null,
|
|
@@ -10736,7 +10947,7 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
10736
10947
|
version: 1,
|
|
10737
10948
|
generatedAt: (opts.now ?? (() => /* @__PURE__ */ new Date()))().toISOString(),
|
|
10738
10949
|
project: null,
|
|
10739
|
-
workspace: { path:
|
|
10950
|
+
workspace: { path: path28.resolve(opts.workspacePath), dir, projects },
|
|
10740
10951
|
filter,
|
|
10741
10952
|
totals: {
|
|
10742
10953
|
entries: projects.reduce((sum, project) => sum + project.entries, 0),
|
|
@@ -10755,7 +10966,7 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
10755
10966
|
// src/gallery/server.ts
|
|
10756
10967
|
import { randomBytes } from "crypto";
|
|
10757
10968
|
import { createServer as createServer2 } from "http";
|
|
10758
|
-
import { readFile as
|
|
10969
|
+
import { readFile as readFile18 } from "fs/promises";
|
|
10759
10970
|
|
|
10760
10971
|
// src/gallery/page.ts
|
|
10761
10972
|
import { readFileSync as readFileSync4 } from "fs";
|
|
@@ -10996,7 +11207,7 @@ async function serveGallery(opts) {
|
|
|
10996
11207
|
const asset = reviewAssets.get(jobId)?.get(url2.pathname);
|
|
10997
11208
|
if (!asset) return fail(404, "no such review asset");
|
|
10998
11209
|
try {
|
|
10999
|
-
const bytes = await
|
|
11210
|
+
const bytes = await readFile18(asset.path);
|
|
11000
11211
|
res.writeHead(200, {
|
|
11001
11212
|
"Content-Type": asset.contentType,
|
|
11002
11213
|
"Cache-Control": "no-store",
|
|
@@ -11072,7 +11283,7 @@ async function serveGallery(opts) {
|
|
|
11072
11283
|
if (!asset) return fail(404, "no such gallery media");
|
|
11073
11284
|
let bytes;
|
|
11074
11285
|
try {
|
|
11075
|
-
bytes = await
|
|
11286
|
+
bytes = await readFile18(asset.path);
|
|
11076
11287
|
} catch {
|
|
11077
11288
|
return fail(404, "gallery media is no longer on disk; refresh the page");
|
|
11078
11289
|
}
|
|
@@ -11216,7 +11427,7 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
|
|
|
11216
11427
|
});
|
|
11217
11428
|
const estimates = new Map(items.map((item) => [
|
|
11218
11429
|
item.key,
|
|
11219
|
-
validateCostEstimate(provider.id, provider.estimate(item.spec))
|
|
11430
|
+
item.spec.mirror ? { amount: 0, unit: item.spec.costUnit, candidates: 1 } : validateCostEstimate(provider.id, provider.estimate(item.spec))
|
|
11220
11431
|
]));
|
|
11221
11432
|
const units = new Set([...estimates.values()].map((estimate) => estimate.unit));
|
|
11222
11433
|
if (units.size > 1) {
|
|
@@ -11266,6 +11477,34 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
|
|
|
11266
11477
|
}
|
|
11267
11478
|
for (const { spec, key } of items) {
|
|
11268
11479
|
const estimate = estimates.get(key);
|
|
11480
|
+
if (spec.mirror) {
|
|
11481
|
+
try {
|
|
11482
|
+
await requireRevisionReady(spec, lock);
|
|
11483
|
+
await deriveMirror(spec, lock, lockPath, { onProgress: log2 });
|
|
11484
|
+
submitted++;
|
|
11485
|
+
} catch (err) {
|
|
11486
|
+
const message6 = err instanceof Error ? err.message : String(err);
|
|
11487
|
+
failed++;
|
|
11488
|
+
upsert(lock, key, {
|
|
11489
|
+
styleId: spec.styleId,
|
|
11490
|
+
assetId: spec.assetId,
|
|
11491
|
+
specHash: spec.specHash,
|
|
11492
|
+
generator: spec.generator,
|
|
11493
|
+
prompt: "",
|
|
11494
|
+
width: spec.width,
|
|
11495
|
+
height: spec.height,
|
|
11496
|
+
status: "failed",
|
|
11497
|
+
error: message6,
|
|
11498
|
+
outputs: lock.entries[key]?.outputs ?? [],
|
|
11499
|
+
cost: 0,
|
|
11500
|
+
costUnit: spec.costUnit,
|
|
11501
|
+
provider: provider.id
|
|
11502
|
+
});
|
|
11503
|
+
await saveLock(lockPath, lock);
|
|
11504
|
+
log2(` FAILED ${key}: ${message6}`);
|
|
11505
|
+
}
|
|
11506
|
+
continue;
|
|
11507
|
+
}
|
|
11269
11508
|
await waitForSlot();
|
|
11270
11509
|
const since = Date.now() - lastSubmitAt;
|
|
11271
11510
|
if (since < spacing) await sleep2(spacing - since);
|
|
@@ -11391,12 +11630,12 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
|
|
|
11391
11630
|
|
|
11392
11631
|
// src/pick/server.ts
|
|
11393
11632
|
import { existsSync as existsSync21 } from "fs";
|
|
11394
|
-
import { readFile as
|
|
11633
|
+
import { readFile as readFile20 } from "fs/promises";
|
|
11395
11634
|
|
|
11396
11635
|
// src/pipeline/init.ts
|
|
11397
|
-
import { readdir, readFile as
|
|
11636
|
+
import { readdir, readFile as readFile19, writeFile as writeFile12 } from "fs/promises";
|
|
11398
11637
|
import { existsSync as existsSync20 } from "fs";
|
|
11399
|
-
import
|
|
11638
|
+
import path29 from "path";
|
|
11400
11639
|
function pngSize(buf) {
|
|
11401
11640
|
if (buf.length < 24) return null;
|
|
11402
11641
|
const isPng = buf.readUInt32BE(0) === 2303741511 && buf.readUInt32BE(4) === 218765834;
|
|
@@ -11410,7 +11649,7 @@ function slugify(value) {
|
|
|
11410
11649
|
async function walk(dir, exclude) {
|
|
11411
11650
|
const out = [];
|
|
11412
11651
|
for (const entry of await readdir(dir, { withFileTypes: true })) {
|
|
11413
|
-
const full =
|
|
11652
|
+
const full = path29.join(dir, entry.name);
|
|
11414
11653
|
if (exclude.some((x) => full.includes(x))) continue;
|
|
11415
11654
|
if (entry.isDirectory()) out.push(...await walk(full, exclude));
|
|
11416
11655
|
else if (entry.name.toLowerCase().endsWith(".png")) out.push(full);
|
|
@@ -11424,14 +11663,14 @@ async function scanAssets(root, opts = {}) {
|
|
|
11424
11663
|
const skipped = [];
|
|
11425
11664
|
const seen = /* @__PURE__ */ new Map();
|
|
11426
11665
|
for (const file of files.sort()) {
|
|
11427
|
-
const size = pngSize(await
|
|
11666
|
+
const size = pngSize(await readFile19(file));
|
|
11428
11667
|
if (!size) {
|
|
11429
11668
|
skipped.push(`${file} (not a readable PNG)`);
|
|
11430
11669
|
continue;
|
|
11431
11670
|
}
|
|
11432
|
-
const rel =
|
|
11433
|
-
const category =
|
|
11434
|
-
let id = slugify(
|
|
11671
|
+
const rel = path29.relative(root, file);
|
|
11672
|
+
const category = path29.dirname(rel) === "." ? "" : path29.dirname(rel);
|
|
11673
|
+
let id = slugify(path29.basename(rel));
|
|
11435
11674
|
if (seen.has(id)) {
|
|
11436
11675
|
const n = seen.get(id) + 1;
|
|
11437
11676
|
seen.set(id, n);
|
|
@@ -11492,7 +11731,7 @@ async function writeManifestFile(target, manifest) {
|
|
|
11492
11731
|
if (existsSync20(target)) {
|
|
11493
11732
|
throw new Error(`${target} already exists; refusing to overwrite an existing manifest.`);
|
|
11494
11733
|
}
|
|
11495
|
-
await
|
|
11734
|
+
await writeFile12(target, JSON.stringify(manifest, null, 2) + "\n");
|
|
11496
11735
|
}
|
|
11497
11736
|
|
|
11498
11737
|
// src/pick/sheet.ts
|
|
@@ -11898,7 +12137,7 @@ async function currentArt(entry, spec) {
|
|
|
11898
12137
|
let width = entry.width;
|
|
11899
12138
|
let height = entry.height;
|
|
11900
12139
|
if (contentType === "image/png") {
|
|
11901
|
-
const size = pngSize(await
|
|
12140
|
+
const size = pngSize(await readFile20(file));
|
|
11902
12141
|
if (size) ({ width, height } = size);
|
|
11903
12142
|
}
|
|
11904
12143
|
return { path: file, contentType, width, height };
|
|
@@ -12391,15 +12630,15 @@ function createGenerateHandlers(opts) {
|
|
|
12391
12630
|
}
|
|
12392
12631
|
|
|
12393
12632
|
// src/gallery/editor.ts
|
|
12394
|
-
import { readFile as
|
|
12395
|
-
import
|
|
12633
|
+
import { readFile as readFile22 } from "fs/promises";
|
|
12634
|
+
import path31 from "path";
|
|
12396
12635
|
|
|
12397
12636
|
// src/editor/install.ts
|
|
12398
12637
|
import { createHash as createHash4 } from "crypto";
|
|
12399
12638
|
import { existsSync as existsSync22 } from "fs";
|
|
12400
|
-
import { mkdir as
|
|
12639
|
+
import { mkdir as mkdir10, readFile as readFile21, rename as rename9, rm as rm9, stat as stat3, writeFile as writeFile13 } from "fs/promises";
|
|
12401
12640
|
import os2 from "os";
|
|
12402
|
-
import
|
|
12641
|
+
import path30 from "path";
|
|
12403
12642
|
|
|
12404
12643
|
// tools/pixelorama-bridge/pin.json
|
|
12405
12644
|
var pin_default = {
|
|
@@ -12463,15 +12702,15 @@ var EditorInstallError = class extends Error {
|
|
|
12463
12702
|
};
|
|
12464
12703
|
function editorToolsRoot() {
|
|
12465
12704
|
const override = process.env.PIXELKILN_TOOLS_DIR?.trim();
|
|
12466
|
-
if (override) return
|
|
12467
|
-
if (process.platform === "darwin") return
|
|
12705
|
+
if (override) return path30.resolve(override);
|
|
12706
|
+
if (process.platform === "darwin") return path30.join(os2.homedir(), "Library", "Caches", "pixelkiln", "tools");
|
|
12468
12707
|
if (process.platform === "win32") {
|
|
12469
|
-
return
|
|
12708
|
+
return path30.join(process.env.LOCALAPPDATA ?? path30.join(os2.homedir(), "AppData", "Local"), "pixelkiln", "tools");
|
|
12470
12709
|
}
|
|
12471
|
-
return
|
|
12710
|
+
return path30.join(process.env.XDG_CACHE_HOME ?? path30.join(os2.homedir(), ".cache"), "pixelkiln", "tools");
|
|
12472
12711
|
}
|
|
12473
12712
|
function editorDir(pin = EDITOR_PIN, root = editorToolsRoot()) {
|
|
12474
|
-
return
|
|
12713
|
+
return path30.join(root, "pixelorama", pin.release ?? "unpublished");
|
|
12475
12714
|
}
|
|
12476
12715
|
function editorBaseUrl(pin = EDITOR_PIN) {
|
|
12477
12716
|
const override = process.env.PIXELKILN_EDITOR_URL?.trim();
|
|
@@ -12481,7 +12720,7 @@ function editorBaseUrl(pin = EDITOR_PIN) {
|
|
|
12481
12720
|
}
|
|
12482
12721
|
async function sha256Of(file) {
|
|
12483
12722
|
try {
|
|
12484
|
-
return createHash4("sha256").update(await
|
|
12723
|
+
return createHash4("sha256").update(await readFile21(file)).digest("hex");
|
|
12485
12724
|
} catch {
|
|
12486
12725
|
return null;
|
|
12487
12726
|
}
|
|
@@ -12494,7 +12733,7 @@ async function editorStatus(opts = {}) {
|
|
|
12494
12733
|
let totalBytes = 0;
|
|
12495
12734
|
for (const [name, expected] of Object.entries(pin.files)) {
|
|
12496
12735
|
totalBytes += expected.bytes;
|
|
12497
|
-
const file =
|
|
12736
|
+
const file = path30.join(dir, name);
|
|
12498
12737
|
const info = existsSync22(file) ? await stat3(file).catch(() => null) : null;
|
|
12499
12738
|
if (info && info.size === expected.bytes && await sha256Of(file) === expected.sha256) {
|
|
12500
12739
|
installedBytes += expected.bytes;
|
|
@@ -12524,7 +12763,7 @@ async function installEditor(opts = {}) {
|
|
|
12524
12763
|
);
|
|
12525
12764
|
}
|
|
12526
12765
|
if (!baseUrl) throw new EditorInstallError("no download location for the editor build");
|
|
12527
|
-
await
|
|
12766
|
+
await mkdir10(dir, { recursive: true });
|
|
12528
12767
|
const downloaded = [];
|
|
12529
12768
|
const skipped = Object.keys(pin.files).filter((name) => !before.missing.includes(name));
|
|
12530
12769
|
let fetchedBytes = before.installedBytes;
|
|
@@ -12552,11 +12791,11 @@ async function installEditor(opts = {}) {
|
|
|
12552
12791
|
if (digest2 !== expected.sha256) {
|
|
12553
12792
|
throw new EditorInstallError(`${name}: sha256 ${digest2.slice(0, 12)}\u2026 does not match the pinned ${expected.sha256.slice(0, 12)}\u2026`, name);
|
|
12554
12793
|
}
|
|
12555
|
-
const final =
|
|
12794
|
+
const final = path30.join(dir, name);
|
|
12556
12795
|
const tmp = `${final}.${process.pid}.part`;
|
|
12557
12796
|
try {
|
|
12558
|
-
await
|
|
12559
|
-
await
|
|
12797
|
+
await writeFile13(tmp, bytes);
|
|
12798
|
+
await rename9(tmp, final);
|
|
12560
12799
|
} finally {
|
|
12561
12800
|
await rm9(tmp, { force: true });
|
|
12562
12801
|
}
|
|
@@ -12598,7 +12837,7 @@ var EDITOR_CONTENT_TYPES = {
|
|
|
12598
12837
|
".json": "application/json; charset=utf-8"
|
|
12599
12838
|
};
|
|
12600
12839
|
function editorContentType(name) {
|
|
12601
|
-
return EDITOR_CONTENT_TYPES[
|
|
12840
|
+
return EDITOR_CONTENT_TYPES[path30.extname(name).toLowerCase()] ?? null;
|
|
12602
12841
|
}
|
|
12603
12842
|
|
|
12604
12843
|
// src/gallery/editor.ts
|
|
@@ -12674,7 +12913,7 @@ function createGalleryEditorHandlers(opts = {}) {
|
|
|
12674
12913
|
if (!status.installed) return null;
|
|
12675
12914
|
let bytes;
|
|
12676
12915
|
try {
|
|
12677
|
-
bytes = await
|
|
12916
|
+
bytes = await readFile22(path31.join(dir, name));
|
|
12678
12917
|
} catch {
|
|
12679
12918
|
verified = null;
|
|
12680
12919
|
return null;
|
|
@@ -12714,13 +12953,13 @@ async function serveUntilStopped(initial, reload, args, access2) {
|
|
|
12714
12953
|
};
|
|
12715
12954
|
const loadProject = async (project) => {
|
|
12716
12955
|
const ctx = await access2.loadProject(project);
|
|
12717
|
-
const dir =
|
|
12956
|
+
const dir = path32.dirname(ctx.loaded.path);
|
|
12718
12957
|
const declared = readEnvFiles(dir);
|
|
12719
12958
|
const credentialNames = new Set(availableProviders().flatMap((id) => providerCredentialEnvs(providerFactory(id))));
|
|
12720
12959
|
for (const [name, value] of Object.entries(declared)) {
|
|
12721
12960
|
if (credentialNames.has(name) && name in process.env && process.env[name] !== value) {
|
|
12722
12961
|
throw new Error(
|
|
12723
|
-
`${
|
|
12962
|
+
`${path32.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.`
|
|
12724
12963
|
);
|
|
12725
12964
|
}
|
|
12726
12965
|
}
|
|
@@ -12765,7 +13004,7 @@ async function serveUntilStopped(initial, reload, args, access2) {
|
|
|
12765
13004
|
}
|
|
12766
13005
|
async function runGallery(args) {
|
|
12767
13006
|
if (args.workspace) {
|
|
12768
|
-
const workspacePath =
|
|
13007
|
+
const workspacePath = path32.resolve(args.workspace);
|
|
12769
13008
|
if (!existsSync23(workspacePath)) {
|
|
12770
13009
|
throw new Error(`Workspace catalog not found: ${workspacePath}`);
|
|
12771
13010
|
}
|
|
@@ -12786,7 +13025,7 @@ async function runGallery(args) {
|
|
|
12786
13025
|
const registered = async (projectId) => {
|
|
12787
13026
|
const project = (await loadWorkspace(workspacePath)).projects.find((candidate) => candidate.id === projectId);
|
|
12788
13027
|
if (!projectId || !project) throw new Error(`unknown workspace project "${projectId ?? ""}"`);
|
|
12789
|
-
return resolveProject(
|
|
13028
|
+
return resolveProject(path32.dirname(workspacePath), project);
|
|
12790
13029
|
};
|
|
12791
13030
|
await serveUntilStopped(initial, build2, args, {
|
|
12792
13031
|
manifestFor: async (projectId) => (await registered(projectId)).manifestPath,
|
|
@@ -12811,7 +13050,7 @@ async function runGallery(args) {
|
|
|
12811
13050
|
return buildGallerySnapshot({ ...fresh, filter });
|
|
12812
13051
|
};
|
|
12813
13052
|
await serveUntilStopped(await build(), reload, args, {
|
|
12814
|
-
manifestFor: () =>
|
|
13053
|
+
manifestFor: () => path32.resolve(args.manifest),
|
|
12815
13054
|
loadProject: () => openProject(args.manifest, { lockPath: args.lock, env: false })
|
|
12816
13055
|
});
|
|
12817
13056
|
}
|
|
@@ -13054,7 +13293,7 @@ async function runLifecycle(args, wave, spent) {
|
|
|
13054
13293
|
log(` Packaging stays blocked until each refined PNG has a current human approval.`);
|
|
13055
13294
|
}
|
|
13056
13295
|
}
|
|
13057
|
-
return args.command === "gen" && submitted > 0 && total.downloaded > 0 && !total.failed && specs.some((spec) => spec.character?.parentSpec || spec.revision);
|
|
13296
|
+
return args.command === "gen" && submitted > 0 && total.downloaded > 0 && !total.failed && specs.some((spec) => spec.character?.parentSpec || spec.revision || spec.mirror);
|
|
13058
13297
|
}
|
|
13059
13298
|
return false;
|
|
13060
13299
|
}
|
|
@@ -13120,14 +13359,14 @@ import { existsSync as existsSync25 } from "fs";
|
|
|
13120
13359
|
|
|
13121
13360
|
// src/pipeline/cache-health.ts
|
|
13122
13361
|
import { existsSync as existsSync24 } from "fs";
|
|
13123
|
-
import { readFile as
|
|
13124
|
-
import
|
|
13362
|
+
import { readFile as readFile23, readdir as readdir2, rm as rm10 } from "fs/promises";
|
|
13363
|
+
import path33 from "path";
|
|
13125
13364
|
async function inspectCaches(lock, lockPath, options = {}) {
|
|
13126
13365
|
if (options.prune && !existsSync24(lockPath)) {
|
|
13127
|
-
throw new Error(`Refusing to prune without an existing lockfile at ${
|
|
13366
|
+
throw new Error(`Refusing to prune without an existing lockfile at ${path33.resolve(lockPath)}`);
|
|
13128
13367
|
}
|
|
13129
|
-
const contentDir =
|
|
13130
|
-
const remotePath =
|
|
13368
|
+
const contentDir = path33.resolve(path33.dirname(lockPath), ".pixelkiln", "cache");
|
|
13369
|
+
const remotePath = path33.resolve(cachePathFor(lockPath));
|
|
13131
13370
|
const referenced = referencedHashes(lock);
|
|
13132
13371
|
let content = await inspectContentCache(contentDir, referenced);
|
|
13133
13372
|
let remoteHashes = await inspectRemoteHashCache(remotePath);
|
|
@@ -13139,7 +13378,7 @@ async function inspectCaches(lock, lockPath, options = {}) {
|
|
|
13139
13378
|
]);
|
|
13140
13379
|
for (const name of names) {
|
|
13141
13380
|
try {
|
|
13142
|
-
await rm10(
|
|
13381
|
+
await rm10(path33.join(contentDir, name), { force: true });
|
|
13143
13382
|
removed.contentFiles++;
|
|
13144
13383
|
} catch {
|
|
13145
13384
|
}
|
|
@@ -13148,7 +13387,7 @@ async function inspectCaches(lock, lockPath, options = {}) {
|
|
|
13148
13387
|
await saveCache(remotePath, { version: 1, hashes: {} });
|
|
13149
13388
|
removed.resetRemoteHashCache = true;
|
|
13150
13389
|
} else if (remoteHashes.invalidIds.length) {
|
|
13151
|
-
const cache = parseCache(JSON.parse(await
|
|
13390
|
+
const cache = parseCache(JSON.parse(await readFile23(remotePath, "utf8")));
|
|
13152
13391
|
for (const id of remoteHashes.invalidIds) delete cache.hashes[id];
|
|
13153
13392
|
removed.remoteHashEntries = remoteHashes.invalidIds.length;
|
|
13154
13393
|
await saveCache(remotePath, cache);
|
|
@@ -13187,12 +13426,12 @@ async function inspectContentCache(contentDir, referenced) {
|
|
|
13187
13426
|
continue;
|
|
13188
13427
|
}
|
|
13189
13428
|
report.files++;
|
|
13190
|
-
const file =
|
|
13429
|
+
const file = path33.join(contentDir, entry.name);
|
|
13191
13430
|
const mediaType = mediaTypeFromExtension(entry.name);
|
|
13192
13431
|
const expected = mediaType ? entry.name.slice(0, -4) : "";
|
|
13193
13432
|
let bytes;
|
|
13194
13433
|
try {
|
|
13195
|
-
bytes = await
|
|
13434
|
+
bytes = await readFile23(file);
|
|
13196
13435
|
report.bytes += bytes.length;
|
|
13197
13436
|
} catch (err) {
|
|
13198
13437
|
report.invalid.push({
|
|
@@ -13239,7 +13478,7 @@ async function inspectRemoteHashCache(remotePath) {
|
|
|
13239
13478
|
if (!report.exists) return report;
|
|
13240
13479
|
let cache;
|
|
13241
13480
|
try {
|
|
13242
|
-
cache = parseCache(JSON.parse(await
|
|
13481
|
+
cache = parseCache(JSON.parse(await readFile23(remotePath, "utf8")));
|
|
13243
13482
|
} catch (err) {
|
|
13244
13483
|
report.error = err instanceof Error ? err.message : String(err);
|
|
13245
13484
|
return report;
|
|
@@ -13358,42 +13597,42 @@ This drops their provenance from the lockfile. The art on disk is`);
|
|
|
13358
13597
|
}
|
|
13359
13598
|
|
|
13360
13599
|
// src/cli/commands/init.ts
|
|
13361
|
-
import
|
|
13600
|
+
import path34 from "path";
|
|
13362
13601
|
import { existsSync as existsSync26 } from "fs";
|
|
13363
13602
|
async function runInit(args) {
|
|
13364
13603
|
if (!args.from) throw new Error("init needs --from <dir> pointing at your existing PNGs.");
|
|
13365
|
-
const root =
|
|
13604
|
+
const root = path34.resolve(args.from);
|
|
13366
13605
|
if (!existsSync26(root)) throw new Error(`No directory at ${root}`);
|
|
13367
13606
|
const generator = args.generator ?? "map";
|
|
13368
13607
|
if (generator !== "1dir" && generator !== "map") {
|
|
13369
13608
|
throw new Error(`--generator must be "1dir" or "map", got "${args.generator}".`);
|
|
13370
13609
|
}
|
|
13371
|
-
const target =
|
|
13610
|
+
const target = path34.resolve(args.out ?? "pixelkiln.manifest.json");
|
|
13372
13611
|
const { assets, skipped } = await scanAssets(root, { exclude: args.exclude });
|
|
13373
13612
|
if (!assets.length) throw new Error(`No PNGs found under ${root}`);
|
|
13374
13613
|
const manifest = buildManifest(
|
|
13375
|
-
args.name ??
|
|
13614
|
+
args.name ?? path34.basename(path34.dirname(target)),
|
|
13376
13615
|
args.styles[0] ?? "base",
|
|
13377
13616
|
generator,
|
|
13378
|
-
|
|
13617
|
+
path34.relative(path34.dirname(target), root) || ".",
|
|
13379
13618
|
assets
|
|
13380
13619
|
);
|
|
13381
13620
|
await writeManifestFile(target, manifest);
|
|
13382
|
-
log(` scanned ${assets.length} PNG(s) under ${
|
|
13621
|
+
log(` scanned ${assets.length} PNG(s) under ${path34.relative(process.cwd(), root)}`);
|
|
13383
13622
|
if (skipped.length) log(` skipped ${skipped.length} unreadable file(s)`);
|
|
13384
|
-
log(` wrote ${
|
|
13623
|
+
log(` wrote ${path34.relative(process.cwd(), target)}`);
|
|
13385
13624
|
log(`
|
|
13386
13625
|
Prompts are intentionally empty. To recover the real ones from your`);
|
|
13387
13626
|
log(` PixelLab account instead of inventing them:`);
|
|
13388
13627
|
log(`
|
|
13389
|
-
pixelkiln adopt --manifest ${
|
|
13628
|
+
pixelkiln adopt --manifest ${path34.relative(process.cwd(), target)} --write-prompts
|
|
13390
13629
|
`);
|
|
13391
13630
|
}
|
|
13392
13631
|
|
|
13393
13632
|
// src/pipeline/doctor.ts
|
|
13394
13633
|
import { constants, existsSync as existsSync27 } from "fs";
|
|
13395
13634
|
import { access, stat as stat4 } from "fs/promises";
|
|
13396
|
-
import
|
|
13635
|
+
import path35 from "path";
|
|
13397
13636
|
async function doctor(loaded, specs, lock, lockPath, opts = {}) {
|
|
13398
13637
|
const checks = [];
|
|
13399
13638
|
const add = (id, level, message6) => checks.push({ id, level, message: message6 });
|
|
@@ -13404,8 +13643,8 @@ async function doctor(loaded, specs, lock, lockPath, opts = {}) {
|
|
|
13404
13643
|
);
|
|
13405
13644
|
add("lock", "ok", `${Object.keys(lock.entries).length} valid v2 lock entr(ies)`);
|
|
13406
13645
|
const dirs = [...new Set(specs.flatMap((spec) => [
|
|
13407
|
-
|
|
13408
|
-
...spec.quality ? [
|
|
13646
|
+
path35.dirname(spec.outFile),
|
|
13647
|
+
...spec.quality ? [path35.dirname(spec.quality.outFile)] : []
|
|
13409
13648
|
]))];
|
|
13410
13649
|
const unwritable = [];
|
|
13411
13650
|
for (const dir of dirs) {
|
|
@@ -13428,7 +13667,7 @@ async function doctor(loaded, specs, lock, lockPath, opts = {}) {
|
|
|
13428
13667
|
for (let index = 0; index < entry.outputs.length; index++) {
|
|
13429
13668
|
const output = entry.outputs[index];
|
|
13430
13669
|
const spec = specByKey.get(key);
|
|
13431
|
-
const absolute = spec ? currentEntryOutputPath(entry, spec, index) : resolveOutputPath(output.path,
|
|
13670
|
+
const absolute = spec ? currentEntryOutputPath(entry, spec, index) : resolveOutputPath(output.path, path35.dirname(lockPath));
|
|
13432
13671
|
const owner = outputOwners.get(absolute);
|
|
13433
13672
|
if (owner && owner !== key) duplicateOutputs.push(`${output.path} (${owner}, ${key})`);
|
|
13434
13673
|
outputOwners.set(absolute, key);
|
|
@@ -13443,7 +13682,7 @@ async function doctor(loaded, specs, lock, lockPath, opts = {}) {
|
|
|
13443
13682
|
} else {
|
|
13444
13683
|
add("lock-outputs", "ok", `${outputOwners.size} recorded output path(s) are uniquely owned`);
|
|
13445
13684
|
}
|
|
13446
|
-
const cacheDir =
|
|
13685
|
+
const cacheDir = path35.join(path35.dirname(lockPath), ".pixelkiln", "cache");
|
|
13447
13686
|
const stranded = [];
|
|
13448
13687
|
const invalidState = [];
|
|
13449
13688
|
const staleMapUrls = [];
|
|
@@ -13459,7 +13698,7 @@ async function doctor(loaded, specs, lock, lockPath, opts = {}) {
|
|
|
13459
13698
|
const hasSource = Boolean(entry.sourceUrl || entry.sourceUrls?.length);
|
|
13460
13699
|
let hasCache = false;
|
|
13461
13700
|
for (const output of entry.outputs) {
|
|
13462
|
-
const cached =
|
|
13701
|
+
const cached = path35.join(
|
|
13463
13702
|
cacheDir,
|
|
13464
13703
|
cacheFileName(output.sha256, output.mediaType ?? MediaType.PNG)
|
|
13465
13704
|
);
|
|
@@ -13559,16 +13798,16 @@ async function nearestExistingDirectory(start) {
|
|
|
13559
13798
|
if ((await stat4(current)).isDirectory()) return current;
|
|
13560
13799
|
} catch {
|
|
13561
13800
|
}
|
|
13562
|
-
const parent =
|
|
13801
|
+
const parent = path35.dirname(current);
|
|
13563
13802
|
if (parent === current) return current;
|
|
13564
13803
|
current = parent;
|
|
13565
13804
|
}
|
|
13566
13805
|
}
|
|
13567
13806
|
|
|
13568
13807
|
// src/pipeline/audit.ts
|
|
13569
|
-
import { readFile as
|
|
13808
|
+
import { readFile as readFile24 } from "fs/promises";
|
|
13570
13809
|
import { existsSync as existsSync28 } from "fs";
|
|
13571
|
-
import
|
|
13810
|
+
import path36 from "path";
|
|
13572
13811
|
function paletteDistance(asset, reference) {
|
|
13573
13812
|
if (!asset.length || !reference.length) return 0;
|
|
13574
13813
|
let total = 0;
|
|
@@ -13609,7 +13848,7 @@ async function auditStyle(loaded, specs, styleId, lock) {
|
|
|
13609
13848
|
continue;
|
|
13610
13849
|
}
|
|
13611
13850
|
try {
|
|
13612
|
-
const png = decodePng(await
|
|
13851
|
+
const png = decodePng(await readFile24(output.absolutePath));
|
|
13613
13852
|
const palette = extractPalette(png, 12);
|
|
13614
13853
|
assets.push({
|
|
13615
13854
|
assetId: spec.assetId,
|
|
@@ -13633,10 +13872,10 @@ async function auditStyle(loaded, specs, styleId, lock) {
|
|
|
13633
13872
|
let referenceFromStyleImages = false;
|
|
13634
13873
|
const refPalettes = [];
|
|
13635
13874
|
for (const img of style.styleImages) {
|
|
13636
|
-
const abs =
|
|
13875
|
+
const abs = path36.resolve(loaded.root, img.path);
|
|
13637
13876
|
if (!existsSync28(abs)) continue;
|
|
13638
13877
|
try {
|
|
13639
|
-
refPalettes.push(extractPalette(decodePng(await
|
|
13878
|
+
refPalettes.push(extractPalette(decodePng(await readFile24(abs)), 12));
|
|
13640
13879
|
} catch {
|
|
13641
13880
|
}
|
|
13642
13881
|
}
|
|
@@ -13856,24 +14095,24 @@ async function runAudit(args) {
|
|
|
13856
14095
|
}
|
|
13857
14096
|
|
|
13858
14097
|
// src/cli/commands/pack.ts
|
|
13859
|
-
import
|
|
13860
|
-
import { readFile as
|
|
14098
|
+
import path38 from "path";
|
|
14099
|
+
import { readFile as readFile25 } from "fs/promises";
|
|
13861
14100
|
|
|
13862
14101
|
// src/pipeline/pack.ts
|
|
13863
14102
|
import { readFileSync as readFileSync5 } from "fs";
|
|
13864
|
-
import
|
|
14103
|
+
import path37 from "path";
|
|
13865
14104
|
var DEFAULT_FRAME_SET_FPS = 12;
|
|
13866
14105
|
function resolvePackInputs(raw, inputsFilePath) {
|
|
13867
14106
|
if (!Array.isArray(raw) || !raw.length) {
|
|
13868
14107
|
throw new Error("--inputs must be a non-empty JSON array of { id, path }");
|
|
13869
14108
|
}
|
|
13870
|
-
const baseDir =
|
|
14109
|
+
const baseDir = path37.dirname(path37.resolve(inputsFilePath));
|
|
13871
14110
|
return raw.map((entry, i) => {
|
|
13872
14111
|
const e = entry;
|
|
13873
14112
|
if (typeof e.id !== "string" || typeof e.path !== "string") {
|
|
13874
14113
|
throw new Error(`--inputs[${i}] needs string "id" and "path"`);
|
|
13875
14114
|
}
|
|
13876
|
-
return { id: e.id, path:
|
|
14115
|
+
return { id: e.id, path: path37.resolve(baseDir, e.path) };
|
|
13877
14116
|
});
|
|
13878
14117
|
}
|
|
13879
14118
|
function packSprites(inputs, options = {}) {
|
|
@@ -13945,7 +14184,7 @@ function packStyle(lock, styleId, manifestDir, options = {}) {
|
|
|
13945
14184
|
}
|
|
13946
14185
|
const inputs2 = Object.entries(options.sourceOverrides).map(([id, source]) => ({
|
|
13947
14186
|
id,
|
|
13948
|
-
path:
|
|
14187
|
+
path: path37.resolve(source)
|
|
13949
14188
|
}));
|
|
13950
14189
|
if (!inputs2.length) throw new Error(`No approved quality outputs for style "${styleId}".`);
|
|
13951
14190
|
const packed2 = packSprites(inputs2, options);
|
|
@@ -13969,7 +14208,7 @@ function packStyle(lock, styleId, manifestDir, options = {}) {
|
|
|
13969
14208
|
let outputs = resolveEntryOutputs(entry, id, manifestDir);
|
|
13970
14209
|
const source = sources[id];
|
|
13971
14210
|
if (source && outputs.length <= 1) {
|
|
13972
|
-
inputs.push({ id, path:
|
|
14211
|
+
inputs.push({ id, path: path37.resolve(manifestDir, source) });
|
|
13973
14212
|
continue;
|
|
13974
14213
|
}
|
|
13975
14214
|
if (source && sourceIsStem(source, entry, manifestDir)) {
|
|
@@ -14003,7 +14242,7 @@ function packStyle(lock, styleId, manifestDir, options = {}) {
|
|
|
14003
14242
|
}
|
|
14004
14243
|
}
|
|
14005
14244
|
for (const [id, source] of Object.entries(sources)) {
|
|
14006
|
-
if (!locked.has(id)) inputs.push({ id, path:
|
|
14245
|
+
if (!locked.has(id)) inputs.push({ id, path: path37.resolve(manifestDir, source) });
|
|
14007
14246
|
}
|
|
14008
14247
|
if (!inputs.length) {
|
|
14009
14248
|
throw new Error(`No readable sprites for style "${styleId}"; ${noOutput.length} skipped.`);
|
|
@@ -14111,7 +14350,7 @@ function mountStyle(lock, styleId, manifestDir, mount, cells, sources = {}, outp
|
|
|
14111
14350
|
const source = sources[id];
|
|
14112
14351
|
const entry = lock.entries[`${styleId}/${id}`];
|
|
14113
14352
|
if (source && !(entry && sourceIsStem(source, entry, manifestDir))) {
|
|
14114
|
-
placements.push({ id, path:
|
|
14353
|
+
placements.push({ id, path: path37.resolve(manifestDir, source), cell });
|
|
14115
14354
|
continue;
|
|
14116
14355
|
}
|
|
14117
14356
|
if (source && entry) {
|
|
@@ -14144,7 +14383,7 @@ function mountStyle(lock, styleId, manifestDir, mount, cells, sources = {}, outp
|
|
|
14144
14383
|
`Nothing to mount for style "${styleId}": ${ids.length} asset(s) declare a cell, but none has generated output or a \`source\`. Run \`pixelkiln gen --style ${styleId}\`, or point each at a file.${detail}`
|
|
14145
14384
|
);
|
|
14146
14385
|
}
|
|
14147
|
-
const basePng = mount.base ? readFileSync5(
|
|
14386
|
+
const basePng = mount.base ? readFileSync5(path37.resolve(manifestDir, mount.base)) : void 0;
|
|
14148
14387
|
const mounted = mountSprites(placements, {
|
|
14149
14388
|
cellWidth: mount.cellWidth,
|
|
14150
14389
|
cellHeight: mount.cellHeight,
|
|
@@ -14154,7 +14393,7 @@ function mountStyle(lock, styleId, manifestDir, mount, cells, sources = {}, outp
|
|
|
14154
14393
|
...mounted,
|
|
14155
14394
|
sources: mount.base ? [{
|
|
14156
14395
|
id: "$base",
|
|
14157
|
-
path:
|
|
14396
|
+
path: path37.resolve(manifestDir, mount.base),
|
|
14158
14397
|
sha256: sha256(basePng),
|
|
14159
14398
|
included: true
|
|
14160
14399
|
}, ...mounted.sources] : mounted.sources,
|
|
@@ -14521,12 +14760,12 @@ async function runPack(args) {
|
|
|
14521
14760
|
if (args.primaryOnly || args.outputRoles.length) {
|
|
14522
14761
|
throw new Error("--primary-only and --output-role require manifest-driven pack");
|
|
14523
14762
|
}
|
|
14524
|
-
const raw = JSON.parse(await
|
|
14763
|
+
const raw = JSON.parse(await readFile25(path38.resolve(args.inputs), "utf8"));
|
|
14525
14764
|
const inputs = resolvePackInputs(raw, args.inputs);
|
|
14526
14765
|
const { png, atlas, skipped, sources } = packSprites(inputs, { columns: args.columns });
|
|
14527
14766
|
const format = sheetFormat(args);
|
|
14528
|
-
const base =
|
|
14529
|
-
const { extension, document } = renderSheetDocument(format, atlas, { imageName:
|
|
14767
|
+
const base = path38.resolve(args.out.replace(/\.(?:png|json|tres)$/i, ""));
|
|
14768
|
+
const { extension, document } = renderSheetDocument(format, atlas, { imageName: path38.basename(`${base}.png`) });
|
|
14530
14769
|
const outputs = [
|
|
14531
14770
|
{ path: `${base}.png`, data: png },
|
|
14532
14771
|
{ path: `${base}${extension}`, data: document }
|
|
@@ -14539,7 +14778,7 @@ async function runPack(args) {
|
|
|
14539
14778
|
log(
|
|
14540
14779
|
` ${atlas.frames.length} sprite(s), ${atlas.sheet.width}x${atlas.sheet.height} in ${atlas.columns} column(s), ${(png.length / 1024).toFixed(1)} KB (${format})`
|
|
14541
14780
|
);
|
|
14542
|
-
log(` ${
|
|
14781
|
+
log(` ${path38.relative(process.cwd(), base)}.png + ${extension} + .pixelkiln.json`);
|
|
14543
14782
|
for (const s of skipped) log(` skipped ${s.id}: ${s.reason}`);
|
|
14544
14783
|
return;
|
|
14545
14784
|
}
|
|
@@ -14547,7 +14786,7 @@ async function runPack(args) {
|
|
|
14547
14786
|
if (args.primaryOnly && args.outputRoles.length) {
|
|
14548
14787
|
throw new Error("pack accepts either --primary-only or --output-role, not both");
|
|
14549
14788
|
}
|
|
14550
|
-
const manifestDir =
|
|
14789
|
+
const manifestDir = path38.dirname(path38.resolve(args.manifest));
|
|
14551
14790
|
const styleIds = args.styles.length ? args.styles : Object.keys(loaded.manifest.styles);
|
|
14552
14791
|
for (const styleId of styleIds) {
|
|
14553
14792
|
const packagingSpecs = await resolveSpecs(loaded, { styles: [styleId] });
|
|
@@ -14560,9 +14799,9 @@ async function runPack(args) {
|
|
|
14560
14799
|
sources: manifestSources(loaded.manifest, styleId)
|
|
14561
14800
|
});
|
|
14562
14801
|
const style = loaded.manifest.styles[styleId];
|
|
14563
|
-
const base = args.out ?
|
|
14802
|
+
const base = args.out ? path38.resolve(args.out.replace(/\.(?:png|json|tres)$/i, "")) : path38.resolve(manifestDir, style.outDir, `${styleId}-sheet`);
|
|
14564
14803
|
const format = sheetFormat(args);
|
|
14565
|
-
const { extension, document } = renderSheetDocument(format, atlas, { imageName:
|
|
14804
|
+
const { extension, document } = renderSheetDocument(format, atlas, { imageName: path38.basename(`${base}.png`) });
|
|
14566
14805
|
const outputs = [
|
|
14567
14806
|
{ path: `${base}.png`, data: png },
|
|
14568
14807
|
{ path: `${base}${extension}`, data: document }
|
|
@@ -14593,13 +14832,13 @@ async function runPack(args) {
|
|
|
14593
14832
|
log(
|
|
14594
14833
|
` ${styleId}: ${atlas.frames.length} sprite(s), ${atlas.sheet.width}x${atlas.sheet.height} in ${atlas.columns} column(s)` + (atlas.sets?.length ? `, ${atlas.sets.length} set(s)` : "") + ` (${format})`
|
|
14595
14834
|
);
|
|
14596
|
-
log(` ${
|
|
14835
|
+
log(` ${path38.relative(process.cwd(), base)}.png + ${extension} + .pixelkiln.json`);
|
|
14597
14836
|
for (const s of skipped) log(` skipped ${s.id}: ${s.reason}`);
|
|
14598
14837
|
}
|
|
14599
14838
|
}
|
|
14600
14839
|
async function runMount(args) {
|
|
14601
14840
|
const { loaded, lock } = await openProject2(args);
|
|
14602
|
-
const manifestDir =
|
|
14841
|
+
const manifestDir = path38.dirname(path38.resolve(args.manifest));
|
|
14603
14842
|
const styleIds = args.styles.length ? args.styles : Object.keys(loaded.manifest.styles);
|
|
14604
14843
|
for (const styleId of styleIds) {
|
|
14605
14844
|
const style = loaded.manifest.styles[styleId];
|
|
@@ -14638,9 +14877,9 @@ async function runMount(args) {
|
|
|
14638
14877
|
sources,
|
|
14639
14878
|
outputRoles
|
|
14640
14879
|
);
|
|
14641
|
-
const out =
|
|
14880
|
+
const out = path38.resolve(manifestDir, style.mount.out);
|
|
14642
14881
|
const format = sheetFormat(args);
|
|
14643
|
-
const { extension, document } = renderSheetDocument(format, atlas, { imageName:
|
|
14882
|
+
const { extension, document } = renderSheetDocument(format, atlas, { imageName: path38.basename(out) });
|
|
14644
14883
|
const metadata = out.replace(/\.png$/, "") + extension;
|
|
14645
14884
|
const companion = out.replace(/\.png$/, "") + ".pixelkiln.json";
|
|
14646
14885
|
const outputs = [
|
|
@@ -14660,7 +14899,7 @@ async function runMount(args) {
|
|
|
14660
14899
|
await provenanceFile("$lock", args.lock),
|
|
14661
14900
|
...qualityRecords,
|
|
14662
14901
|
...artifactSources.filter(
|
|
14663
|
-
(source) => source.id !== "$base" ||
|
|
14902
|
+
(source) => source.id !== "$base" || path38.resolve(source.path) !== out
|
|
14664
14903
|
)
|
|
14665
14904
|
],
|
|
14666
14905
|
options: {
|
|
@@ -14674,14 +14913,14 @@ async function runMount(args) {
|
|
|
14674
14913
|
log(
|
|
14675
14914
|
` ${styleId}: ${atlas.frames.length} cell(s) into ${atlas.sheet.width}x${atlas.sheet.height}` + (overBase ? ` over ${style.mount.base}` : " (new sheet)")
|
|
14676
14915
|
);
|
|
14677
|
-
log(` ${
|
|
14916
|
+
log(` ${path38.relative(process.cwd(), out)} + atlas/provenance JSON`);
|
|
14678
14917
|
for (const s of skipped) log(` skipped ${s.id}: ${s.reason}`);
|
|
14679
14918
|
}
|
|
14680
14919
|
}
|
|
14681
14920
|
async function runExport(args) {
|
|
14682
14921
|
const { loaded, specs, lock } = await openProject2(args);
|
|
14683
14922
|
const format = args.format ?? "generic";
|
|
14684
|
-
const manifestDir =
|
|
14923
|
+
const manifestDir = path38.dirname(path38.resolve(args.manifest));
|
|
14685
14924
|
const selected = specs.filter((spec) => {
|
|
14686
14925
|
if (spec.generator !== "tiles") return false;
|
|
14687
14926
|
if (args.styles.length && !args.styles.includes(spec.styleId)) return false;
|
|
@@ -14697,12 +14936,12 @@ async function runExport(args) {
|
|
|
14697
14936
|
for (const spec of selected) {
|
|
14698
14937
|
const entry = lock.entries[lockKey(spec.styleId, spec.assetId)];
|
|
14699
14938
|
const style = loaded.manifest.styles[spec.styleId];
|
|
14700
|
-
const defaultBase =
|
|
14701
|
-
const base = args.out ?
|
|
14939
|
+
const defaultBase = path38.resolve(manifestDir, style.outDir, `${spec.assetId}-tileset`);
|
|
14940
|
+
const base = args.out ? path38.resolve(args.out.replace(/\.(?:png|json|tsj|tres)$/i, "")) : defaultBase;
|
|
14702
14941
|
const result = exportTileset(entry, spec, {
|
|
14703
14942
|
format,
|
|
14704
14943
|
manifestDir,
|
|
14705
|
-
imageName:
|
|
14944
|
+
imageName: path38.basename(`${base}.png`),
|
|
14706
14945
|
columns: args.columns
|
|
14707
14946
|
});
|
|
14708
14947
|
const outputs = [
|
|
@@ -14720,7 +14959,7 @@ async function runExport(args) {
|
|
|
14720
14959
|
asset: spec.assetId,
|
|
14721
14960
|
columns: args.columns ?? null,
|
|
14722
14961
|
format,
|
|
14723
|
-
image:
|
|
14962
|
+
image: path38.basename(`${base}.png`),
|
|
14724
14963
|
providerRules: result.generic.providerRules,
|
|
14725
14964
|
style: spec.styleId,
|
|
14726
14965
|
tileType: spec.tileType ?? null
|
|
@@ -14730,7 +14969,7 @@ async function runExport(args) {
|
|
|
14730
14969
|
` ${spec.styleId}/${spec.assetId}: ${result.generic.tiles.length} tile(s), ${result.generic.sheet.width}x${result.generic.sheet.height} (${format})`
|
|
14731
14970
|
);
|
|
14732
14971
|
log(
|
|
14733
|
-
` ${
|
|
14972
|
+
` ${path38.relative(process.cwd(), base)}.png + ${path38.basename(base)}${result.extension} + .pixelkiln.json`
|
|
14734
14973
|
);
|
|
14735
14974
|
}
|
|
14736
14975
|
}
|
|
@@ -14743,19 +14982,19 @@ function sheetFormat(args) {
|
|
|
14743
14982
|
}
|
|
14744
14983
|
|
|
14745
14984
|
// src/cli/commands/quality.ts
|
|
14746
|
-
import
|
|
14747
|
-
import { readFile as
|
|
14985
|
+
import path40 from "path";
|
|
14986
|
+
import { readFile as readFile27 } from "fs/promises";
|
|
14748
14987
|
|
|
14749
14988
|
// src/pipeline/quality-regression.ts
|
|
14750
|
-
import
|
|
14989
|
+
import path39 from "path";
|
|
14751
14990
|
import { existsSync as existsSync29 } from "fs";
|
|
14752
|
-
import { readFile as
|
|
14991
|
+
import { readFile as readFile26 } from "fs/promises";
|
|
14753
14992
|
import { z as z9 } from "zod";
|
|
14754
14993
|
var SHA256_RE = /^[0-9a-f]{64}$/;
|
|
14755
14994
|
var HEX_RE = /^#[0-9a-f]{6}$/;
|
|
14756
14995
|
var MAX_REDMEAN_DISTANCE = 765;
|
|
14757
14996
|
var QualityPathSchema = z9.string().min(1).refine(
|
|
14758
|
-
(value) => !
|
|
14997
|
+
(value) => !path39.posix.isAbsolute(value) && !value.includes("\\"),
|
|
14759
14998
|
"expected a portable relative path"
|
|
14760
14999
|
);
|
|
14761
15000
|
var DEFAULT_QUALITY_TOLERANCES = {
|
|
@@ -14902,10 +15141,10 @@ function measureDecodedImageQuality(png, digest2) {
|
|
|
14902
15141
|
};
|
|
14903
15142
|
}
|
|
14904
15143
|
async function measureImageQuality(file) {
|
|
14905
|
-
const absolute =
|
|
15144
|
+
const absolute = path39.resolve(file);
|
|
14906
15145
|
let bytes;
|
|
14907
15146
|
try {
|
|
14908
|
-
bytes = await
|
|
15147
|
+
bytes = await readFile26(absolute);
|
|
14909
15148
|
} catch (error) {
|
|
14910
15149
|
throw new Error(`Cannot read quality image ${absolute}: ${message4(error)}`, { cause: error });
|
|
14911
15150
|
}
|
|
@@ -14924,11 +15163,11 @@ function resolveQualityInputs(raw, inputsFilePath) {
|
|
|
14924
15163
|
}
|
|
14925
15164
|
const ids = parsed.data.map((entry) => entry.id);
|
|
14926
15165
|
if (new Set(ids).size !== ids.length) throw new Error("--inputs contains a duplicate quality case id.");
|
|
14927
|
-
const root =
|
|
15166
|
+
const root = path39.dirname(path39.resolve(inputsFilePath));
|
|
14928
15167
|
return parsed.data.map((entry) => ({
|
|
14929
15168
|
id: entry.id,
|
|
14930
|
-
file:
|
|
14931
|
-
...entry.record ? { record:
|
|
15169
|
+
file: path39.resolve(root, entry.path),
|
|
15170
|
+
...entry.record ? { record: path39.resolve(root, entry.record) } : {},
|
|
14932
15171
|
tolerances: QualityToleranceSchema.parse({
|
|
14933
15172
|
...DEFAULT_QUALITY_TOLERANCES,
|
|
14934
15173
|
...entry.tolerances
|
|
@@ -14936,7 +15175,7 @@ function resolveQualityInputs(raw, inputsFilePath) {
|
|
|
14936
15175
|
}));
|
|
14937
15176
|
}
|
|
14938
15177
|
function portableRelative2(from, to) {
|
|
14939
|
-
return
|
|
15178
|
+
return path39.relative(from, path39.resolve(to)).split(path39.sep).join("/") || ".";
|
|
14940
15179
|
}
|
|
14941
15180
|
async function verifyRecordForImage(recordPath, imagePath) {
|
|
14942
15181
|
const verification = await verifyArtifactBundle(recordPath);
|
|
@@ -14951,20 +15190,20 @@ async function verifyRecordForImage(recordPath, imagePath) {
|
|
|
14951
15190
|
if (record.kind !== "refine" || options === null || typeof options !== "object" || options.schema !== "pixelkiln-quality") {
|
|
14952
15191
|
throw new Error(`Quality record ${recordPath} is not a PixelKiln refinement record.`);
|
|
14953
15192
|
}
|
|
14954
|
-
const outputs = record.outputs.map((output) =>
|
|
14955
|
-
if (!outputs.includes(
|
|
15193
|
+
const outputs = record.outputs.map((output) => path39.resolve(path39.dirname(recordPath), output.path));
|
|
15194
|
+
if (!outputs.includes(path39.resolve(imagePath))) {
|
|
14956
15195
|
throw new Error(`Quality record ${recordPath} does not own ${imagePath}.`);
|
|
14957
15196
|
}
|
|
14958
15197
|
}
|
|
14959
15198
|
async function snapshotQualityBaseline(inputs, baselinePath, options = {}) {
|
|
14960
15199
|
if (!inputs.length) throw new Error("Cannot snapshot an empty quality baseline.");
|
|
14961
|
-
const absolute =
|
|
15200
|
+
const absolute = path39.resolve(baselinePath);
|
|
14962
15201
|
for (const input of inputs) {
|
|
14963
|
-
if (
|
|
15202
|
+
if (path39.resolve(input.file) === absolute || input.record && path39.resolve(input.record) === absolute) {
|
|
14964
15203
|
throw new Error(`Quality baseline output would overwrite an input: ${absolute}.`);
|
|
14965
15204
|
}
|
|
14966
15205
|
}
|
|
14967
|
-
const root =
|
|
15206
|
+
const root = path39.dirname(absolute);
|
|
14968
15207
|
const cases = [];
|
|
14969
15208
|
for (const input of [...inputs].sort((a, b) => a.id.localeCompare(b.id))) {
|
|
14970
15209
|
const expected = await measureImageQuality(input.file);
|
|
@@ -14992,7 +15231,7 @@ async function snapshotQualityBaseline(inputs, baselinePath, options = {}) {
|
|
|
14992
15231
|
});
|
|
14993
15232
|
const data = Buffer.from(JSON.stringify(baseline, null, 2) + "\n");
|
|
14994
15233
|
if (existsSync29(absolute) && !options.force) {
|
|
14995
|
-
const current = await
|
|
15234
|
+
const current = await readFile26(absolute);
|
|
14996
15235
|
if (!current.equals(data)) {
|
|
14997
15236
|
throw new OverwriteRefusedError(
|
|
14998
15237
|
`Quality baseline already exists with different content: ${absolute}. Pass --force to replace it.`,
|
|
@@ -15004,10 +15243,10 @@ async function snapshotQualityBaseline(inputs, baselinePath, options = {}) {
|
|
|
15004
15243
|
return { baseline, path: absolute, changed: result.changed.length > 0 };
|
|
15005
15244
|
}
|
|
15006
15245
|
async function readQualityBaseline(baselinePath) {
|
|
15007
|
-
const absolute =
|
|
15246
|
+
const absolute = path39.resolve(baselinePath);
|
|
15008
15247
|
let raw;
|
|
15009
15248
|
try {
|
|
15010
|
-
raw = JSON.parse(await
|
|
15249
|
+
raw = JSON.parse(await readFile26(absolute, "utf8"));
|
|
15011
15250
|
} catch (error) {
|
|
15012
15251
|
throw new Error(`Could not read quality baseline ${absolute}: ${message4(error)}`, { cause: error });
|
|
15013
15252
|
}
|
|
@@ -15077,12 +15316,12 @@ function regressionViolations(expected, actual, tolerances) {
|
|
|
15077
15316
|
return { violations, warnings };
|
|
15078
15317
|
}
|
|
15079
15318
|
async function checkQualityBaseline(baselinePath) {
|
|
15080
|
-
const absolute =
|
|
15319
|
+
const absolute = path39.resolve(baselinePath);
|
|
15081
15320
|
const baseline = await readQualityBaseline(absolute);
|
|
15082
|
-
const root =
|
|
15321
|
+
const root = path39.dirname(absolute);
|
|
15083
15322
|
const cases = [];
|
|
15084
15323
|
for (const entry of baseline.cases) {
|
|
15085
|
-
const file =
|
|
15324
|
+
const file = path39.resolve(root, entry.file);
|
|
15086
15325
|
let actual = null;
|
|
15087
15326
|
const violations = [];
|
|
15088
15327
|
const warnings = [];
|
|
@@ -15095,7 +15334,7 @@ async function checkQualityBaseline(baselinePath) {
|
|
|
15095
15334
|
violations.push(message4(error));
|
|
15096
15335
|
}
|
|
15097
15336
|
if (entry.record) {
|
|
15098
|
-
const recordPath =
|
|
15337
|
+
const recordPath = path39.resolve(root, entry.record.path);
|
|
15099
15338
|
try {
|
|
15100
15339
|
const recordHash = await sha256File(recordPath);
|
|
15101
15340
|
if (recordHash !== entry.record.sha256) violations.push("quality record hash changed");
|
|
@@ -15137,15 +15376,15 @@ async function runQuality(args) {
|
|
|
15137
15376
|
if (!args.out) throw new Error("quality snapshot needs --out <pixelkiln.quality.json>.");
|
|
15138
15377
|
let raw;
|
|
15139
15378
|
try {
|
|
15140
|
-
raw = JSON.parse(await
|
|
15379
|
+
raw = JSON.parse(await readFile27(path40.resolve(args.inputs), "utf8"));
|
|
15141
15380
|
} catch (error) {
|
|
15142
15381
|
throw new Error(
|
|
15143
|
-
`Could not read quality inputs ${
|
|
15382
|
+
`Could not read quality inputs ${path40.resolve(args.inputs)}: ${error instanceof Error ? error.message : String(error)}`,
|
|
15144
15383
|
{ cause: error }
|
|
15145
15384
|
);
|
|
15146
15385
|
}
|
|
15147
15386
|
const inputs = resolveQualityInputs(raw, args.inputs);
|
|
15148
|
-
if (
|
|
15387
|
+
if (path40.resolve(args.inputs) === path40.resolve(args.out)) {
|
|
15149
15388
|
throw new Error("quality snapshot --out must not overwrite its --inputs file.");
|
|
15150
15389
|
}
|
|
15151
15390
|
const result = await snapshotQualityBaseline(inputs, args.out, { force: args.force });
|
|
@@ -15158,7 +15397,7 @@ async function runQuality(args) {
|
|
|
15158
15397
|
}, null, 2));
|
|
15159
15398
|
} else {
|
|
15160
15399
|
log(
|
|
15161
|
-
` ${result.changed ? "wrote" : "unchanged"} ${
|
|
15400
|
+
` ${result.changed ? "wrote" : "unchanged"} ${path40.relative(process.cwd(), result.path)} (${result.baseline.cases.length} case(s))`
|
|
15162
15401
|
);
|
|
15163
15402
|
log(` Review the tolerances, commit the baseline, then run quality check in CI.`);
|
|
15164
15403
|
}
|
|
@@ -15184,12 +15423,12 @@ async function runQuality(args) {
|
|
|
15184
15423
|
}
|
|
15185
15424
|
|
|
15186
15425
|
// src/cli/commands/recipe.ts
|
|
15187
|
-
import
|
|
15426
|
+
import path42 from "path";
|
|
15188
15427
|
|
|
15189
15428
|
// src/recipes.ts
|
|
15190
|
-
import
|
|
15429
|
+
import path41 from "path";
|
|
15191
15430
|
import { existsSync as existsSync30 } from "fs";
|
|
15192
|
-
import { readdir as readdir3, readFile as
|
|
15431
|
+
import { readdir as readdir3, readFile as readFile28 } from "fs/promises";
|
|
15193
15432
|
import { fileURLToPath } from "url";
|
|
15194
15433
|
import { z as z10 } from "zod";
|
|
15195
15434
|
var SHA256_RE2 = /^[0-9a-f]{64}$/;
|
|
@@ -15198,7 +15437,7 @@ var VERSION_RE = /^\d+\.\d+\.\d+$/;
|
|
|
15198
15437
|
var RECIPE_FILE = "pixelkiln.recipe.json";
|
|
15199
15438
|
var RECIPE_DIR_TOKEN = "{{recipeDir}}";
|
|
15200
15439
|
var PortablePathSchema = z10.string().min(1).refine((value) => {
|
|
15201
|
-
if (value.includes("\\") ||
|
|
15440
|
+
if (value.includes("\\") || path41.posix.isAbsolute(value)) return false;
|
|
15202
15441
|
const parts = value.split("/");
|
|
15203
15442
|
return !parts.includes("") && !parts.includes(".") && !parts.includes("..");
|
|
15204
15443
|
}, "expected a portable relative path without '.', '..', or backslashes");
|
|
@@ -15374,10 +15613,10 @@ function message5(error) {
|
|
|
15374
15613
|
return error instanceof Error ? error.message : String(error);
|
|
15375
15614
|
}
|
|
15376
15615
|
async function readRecipeFile(recipePath, bundled) {
|
|
15377
|
-
const absolute =
|
|
15616
|
+
const absolute = path41.resolve(recipePath);
|
|
15378
15617
|
let raw;
|
|
15379
15618
|
try {
|
|
15380
|
-
raw = JSON.parse(await
|
|
15619
|
+
raw = JSON.parse(await readFile28(absolute, "utf8"));
|
|
15381
15620
|
} catch (error) {
|
|
15382
15621
|
throw new Error(`Could not read recipe ${absolute}: ${message5(error)}`, { cause: error });
|
|
15383
15622
|
}
|
|
@@ -15388,13 +15627,13 @@ async function readRecipeFile(recipePath, bundled) {
|
|
|
15388
15627
|
` + parsed.error.issues.map((issue) => ` ${issue.path.join(".") || "$"}: ${issue.message}`).join("\n")
|
|
15389
15628
|
);
|
|
15390
15629
|
}
|
|
15391
|
-
return { recipe: parsed.data, authored: raw, path: absolute, dir:
|
|
15630
|
+
return { recipe: parsed.data, authored: raw, path: absolute, dir: path41.dirname(absolute), bundled };
|
|
15392
15631
|
}
|
|
15393
15632
|
async function walkRecipeFiles(root) {
|
|
15394
15633
|
if (!existsSync30(root)) return [];
|
|
15395
15634
|
const out = [];
|
|
15396
15635
|
for (const entry of await readdir3(root, { withFileTypes: true })) {
|
|
15397
|
-
const resolved =
|
|
15636
|
+
const resolved = path41.join(root, entry.name);
|
|
15398
15637
|
if (entry.isDirectory()) out.push(...await walkRecipeFiles(resolved));
|
|
15399
15638
|
else if (entry.isFile() && entry.name === RECIPE_FILE) out.push(resolved);
|
|
15400
15639
|
}
|
|
@@ -15405,7 +15644,7 @@ function bundledRecipeRoot() {
|
|
|
15405
15644
|
}
|
|
15406
15645
|
async function listBundledRecipes(root = bundledRecipeRoot()) {
|
|
15407
15646
|
const recipes = await Promise.all(
|
|
15408
|
-
(await walkRecipeFiles(
|
|
15647
|
+
(await walkRecipeFiles(path41.resolve(root))).map((file) => readRecipeFile(file, true))
|
|
15409
15648
|
);
|
|
15410
15649
|
return recipes.sort(
|
|
15411
15650
|
(a, b) => a.recipe.id.localeCompare(b.recipe.id) || compareVersions(b.recipe.version, a.recipe.version)
|
|
@@ -15428,9 +15667,9 @@ function parseSelector(selector) {
|
|
|
15428
15667
|
return { id: match[1], version: match[2] };
|
|
15429
15668
|
}
|
|
15430
15669
|
async function resolveRecipe(target, bundledRoot = bundledRecipeRoot()) {
|
|
15431
|
-
const local =
|
|
15670
|
+
const local = path41.resolve(target);
|
|
15432
15671
|
if (existsSync30(local)) {
|
|
15433
|
-
const recipePath =
|
|
15672
|
+
const recipePath = path41.basename(local) === RECIPE_FILE ? local : path41.join(local, RECIPE_FILE);
|
|
15434
15673
|
return readRecipeFile(recipePath, false);
|
|
15435
15674
|
}
|
|
15436
15675
|
if (target.startsWith(".") || target.startsWith("/") || target.includes("\\")) {
|
|
@@ -15444,7 +15683,7 @@ async function resolveRecipe(target, bundledRoot = bundledRecipeRoot()) {
|
|
|
15444
15683
|
return matches[0];
|
|
15445
15684
|
}
|
|
15446
15685
|
async function verifyFile(root, file) {
|
|
15447
|
-
const absolute =
|
|
15686
|
+
const absolute = path41.resolve(root, ...file.path.split("/"));
|
|
15448
15687
|
if (!existsSync30(absolute)) {
|
|
15449
15688
|
return { path: file.path, expectedSha256: file.sha256, actualSha256: null, status: "missing" };
|
|
15450
15689
|
}
|
|
@@ -15460,7 +15699,7 @@ async function verifyRecipe(target, options = {}) {
|
|
|
15460
15699
|
const loaded = await resolveRecipe(target, options.bundledRoot);
|
|
15461
15700
|
const actualIntegrity = recipeDigest(loaded.authored);
|
|
15462
15701
|
const files = await Promise.all(loaded.recipe.files.map((file) => verifyFile(loaded.dir, file)));
|
|
15463
|
-
const modelRoot = options.modelRoot ?
|
|
15702
|
+
const modelRoot = options.modelRoot ? path41.resolve(options.modelRoot) : null;
|
|
15464
15703
|
const models = modelRoot ? await Promise.all(loaded.recipe.models.map(async (model) => ({
|
|
15465
15704
|
...await verifyFile(modelRoot, model),
|
|
15466
15705
|
source: model.source,
|
|
@@ -15494,7 +15733,7 @@ async function verifyRecipe(target, options = {}) {
|
|
|
15494
15733
|
};
|
|
15495
15734
|
}
|
|
15496
15735
|
function renderStyle(style, destination, cwd) {
|
|
15497
|
-
const portableDir =
|
|
15736
|
+
const portableDir = path41.relative(cwd, destination).split(path41.sep).join("/") || ".";
|
|
15498
15737
|
const replace = (value) => {
|
|
15499
15738
|
if (typeof value === "string") return value.replaceAll(RECIPE_DIR_TOKEN, portableDir);
|
|
15500
15739
|
if (Array.isArray(value)) return value.map(replace);
|
|
@@ -15506,25 +15745,25 @@ function renderStyle(style, destination, cwd) {
|
|
|
15506
15745
|
return replace(style);
|
|
15507
15746
|
}
|
|
15508
15747
|
async function installRecipe(target, options = {}) {
|
|
15509
|
-
const cwd =
|
|
15748
|
+
const cwd = path41.resolve(options.cwd ?? process.cwd());
|
|
15510
15749
|
const loaded = await resolveRecipe(target, options.bundledRoot);
|
|
15511
15750
|
const verification = await verifyRecipe(loaded.path);
|
|
15512
15751
|
if (!verification.ok) {
|
|
15513
15752
|
throw new Error(`Recipe ${loaded.recipe.id}@${loaded.recipe.version} failed integrity verification.`);
|
|
15514
15753
|
}
|
|
15515
|
-
const destination = options.out ?
|
|
15754
|
+
const destination = options.out ? path41.resolve(cwd, options.out) : path41.join(cwd, "pixelkiln-recipes", ...loaded.recipe.id.split("/"), loaded.recipe.version);
|
|
15516
15755
|
const sources = [
|
|
15517
15756
|
{ path: RECIPE_FILE, source: loaded.path },
|
|
15518
|
-
...loaded.recipe.files.map((file) => ({ path: file.path, source:
|
|
15757
|
+
...loaded.recipe.files.map((file) => ({ path: file.path, source: path41.join(loaded.dir, ...file.path.split("/")) }))
|
|
15519
15758
|
];
|
|
15520
15759
|
const files = await Promise.all(sources.map(async (file) => ({
|
|
15521
|
-
path:
|
|
15522
|
-
data: await
|
|
15760
|
+
path: path41.join(destination, ...file.path.split("/")),
|
|
15761
|
+
data: await readFile28(file.source)
|
|
15523
15762
|
})));
|
|
15524
15763
|
if (!options.force) {
|
|
15525
15764
|
for (const file of files) {
|
|
15526
15765
|
if (!existsSync30(file.path)) continue;
|
|
15527
|
-
const current = await
|
|
15766
|
+
const current = await readFile28(file.path);
|
|
15528
15767
|
if (!current.equals(file.data)) {
|
|
15529
15768
|
throw new OverwriteRefusedError(
|
|
15530
15769
|
`Recipe destination has local changes: ${file.path}. Choose another --out or pass --force to replace declared recipe files.`,
|
|
@@ -15618,14 +15857,14 @@ async function runRecipe(args) {
|
|
|
15618
15857
|
}, null, 2));
|
|
15619
15858
|
} else {
|
|
15620
15859
|
log(` installed ${result.recipe.id}@${result.recipe.version}`);
|
|
15621
|
-
log(` ${
|
|
15860
|
+
log(` ${path42.relative(process.cwd(), result.destination) || "."}`);
|
|
15622
15861
|
log(`
|
|
15623
15862
|
Add this entry under your manifest's styles object:`);
|
|
15624
15863
|
log(JSON.stringify({ [result.styleId]: result.style }, null, 2));
|
|
15625
15864
|
if (result.recipe.models.length) {
|
|
15626
15865
|
log(`
|
|
15627
15866
|
Models are not downloaded automatically. Verify them with:`);
|
|
15628
|
-
log(` pixelkiln recipe verify ${
|
|
15867
|
+
log(` pixelkiln recipe verify ${path42.relative(process.cwd(), result.destination)} --model-root <ComfyUI/models>`);
|
|
15629
15868
|
}
|
|
15630
15869
|
}
|
|
15631
15870
|
return;
|
|
@@ -15633,7 +15872,7 @@ async function runRecipe(args) {
|
|
|
15633
15872
|
}
|
|
15634
15873
|
|
|
15635
15874
|
// src/cli/commands/refine.ts
|
|
15636
|
-
import
|
|
15875
|
+
import path43 from "path";
|
|
15637
15876
|
async function runRefine(args) {
|
|
15638
15877
|
if (args.from || args.subcommand === "approve") {
|
|
15639
15878
|
if (!args.from) {
|
|
@@ -15663,12 +15902,12 @@ async function runRefine(args) {
|
|
|
15663
15902
|
` recovered ${result3.detection.columns}x${result3.detection.rows} native grid (${result3.detection.confidence}: ${result3.detection.consensus})`
|
|
15664
15903
|
);
|
|
15665
15904
|
log(` enforced ${result3.palette.length}-color palette without dithering`);
|
|
15666
|
-
log(` wrote ${
|
|
15667
|
-
log(` quality record: ${
|
|
15905
|
+
log(` wrote ${path43.relative(process.cwd(), result3.output)}`);
|
|
15906
|
+
log(` quality record: ${path43.relative(process.cwd(), result3.record)}`);
|
|
15668
15907
|
log(`
|
|
15669
15908
|
Automated checks passed. Human 1\xD7 review is still required:`);
|
|
15670
15909
|
log(
|
|
15671
|
-
` pixelkiln refine approve --from ${
|
|
15910
|
+
` pixelkiln refine approve --from ${path43.relative(process.cwd(), result3.record)} --reviewer "Your Name"`
|
|
15672
15911
|
);
|
|
15673
15912
|
}
|
|
15674
15913
|
return;
|
|
@@ -15696,7 +15935,7 @@ async function runRefine(args) {
|
|
|
15696
15935
|
if (args.json) log(JSON.stringify(result3, null, 2));
|
|
15697
15936
|
else {
|
|
15698
15937
|
log(
|
|
15699
|
-
result3.options.frameSet ? ` approved ${result3.outputs.length} frames in ${
|
|
15938
|
+
result3.options.frameSet ? ` approved ${result3.outputs.length} frames in ${path43.relative(process.cwd(), result3.record)} by ${args.reviewer.trim()}` : ` approved ${path43.relative(process.cwd(), result3.output)} by ${args.reviewer.trim()}`
|
|
15700
15939
|
);
|
|
15701
15940
|
}
|
|
15702
15941
|
return;
|
|
@@ -15705,7 +15944,7 @@ async function runRefine(args) {
|
|
|
15705
15944
|
if (args.json) {
|
|
15706
15945
|
log(JSON.stringify(result2, null, 2));
|
|
15707
15946
|
} else {
|
|
15708
|
-
log(` ${result2.safe ? "release-ready" : "not release-ready"}: ${
|
|
15947
|
+
log(` ${result2.safe ? "release-ready" : "not release-ready"}: ${path43.relative(process.cwd(), result2.output || result2.record)}`);
|
|
15709
15948
|
for (const reason of result2.reasons) log(` ${reason}`);
|
|
15710
15949
|
}
|
|
15711
15950
|
if (!result2.safe) process.exitCode = 1;
|
|
@@ -15751,7 +15990,7 @@ async function runRefine(args) {
|
|
|
15751
15990
|
log(` ${item.state.padEnd(16)} ${item.key} ${item.reason}`);
|
|
15752
15991
|
if (item.state === "needs-approval") {
|
|
15753
15992
|
log(
|
|
15754
|
-
` pixelkiln refine approve --from ${
|
|
15993
|
+
` pixelkiln refine approve --from ${path43.relative(process.cwd(), item.record)} --reviewer "Your Name"`
|
|
15755
15994
|
);
|
|
15756
15995
|
}
|
|
15757
15996
|
}
|
|
@@ -15803,15 +16042,15 @@ async function runTools(args) {
|
|
|
15803
16042
|
}
|
|
15804
16043
|
|
|
15805
16044
|
// src/cli/commands/workspace.ts
|
|
15806
|
-
import
|
|
16045
|
+
import path44 from "path";
|
|
15807
16046
|
import { existsSync as existsSync31 } from "fs";
|
|
15808
16047
|
async function runWorkspace(args) {
|
|
15809
|
-
const workspacePath =
|
|
15810
|
-
const dir =
|
|
16048
|
+
const workspacePath = path44.resolve(args.workspace ?? "pixelkiln.workspace.json");
|
|
16049
|
+
const dir = path44.dirname(workspacePath);
|
|
15811
16050
|
if (args.subcommand === "add") {
|
|
15812
|
-
const manifestPath =
|
|
16051
|
+
const manifestPath = path44.resolve(args.target);
|
|
15813
16052
|
const loadedTarget = await loadManifest(manifestPath);
|
|
15814
|
-
const lockPath = args.explicitLock ?
|
|
16053
|
+
const lockPath = args.explicitLock ? path44.resolve(args.explicitLock) : path44.join(path44.dirname(manifestPath), "pixelkiln.lock.json");
|
|
15815
16054
|
const ws = await loadWorkspace(workspacePath);
|
|
15816
16055
|
const id = args.name ?? loadedTarget.manifest.name;
|
|
15817
16056
|
if (ws.projects.some((p) => p.id === id)) {
|
|
@@ -15831,7 +16070,7 @@ async function runWorkspace(args) {
|
|
|
15831
16070
|
...args.account ? { account: args.account } : {}
|
|
15832
16071
|
};
|
|
15833
16072
|
await saveWorkspace(workspacePath, { version: 1, projects: [...ws.projects, project] });
|
|
15834
|
-
log(` registered "${id}" in ${
|
|
16073
|
+
log(` registered "${id}" in ${path44.relative(process.cwd(), workspacePath)}`);
|
|
15835
16074
|
log(` manifest: ${project.manifest}`);
|
|
15836
16075
|
log(` lock: ${project.lock}`);
|
|
15837
16076
|
if (!existsSync31(lockPath)) {
|
|
@@ -15843,7 +16082,7 @@ async function runWorkspace(args) {
|
|
|
15843
16082
|
}
|
|
15844
16083
|
if (args.subcommand === "remove") {
|
|
15845
16084
|
const ws = await loadWorkspace(workspacePath);
|
|
15846
|
-
const resolvedTarget =
|
|
16085
|
+
const resolvedTarget = path44.resolve(args.target);
|
|
15847
16086
|
const match = ws.projects.find(
|
|
15848
16087
|
(p) => p.id === args.target || resolveProject(dir, p).manifestPath === resolvedTarget
|
|
15849
16088
|
);
|
|
@@ -15854,7 +16093,7 @@ async function runWorkspace(args) {
|
|
|
15854
16093
|
version: 1,
|
|
15855
16094
|
projects: ws.projects.filter((p) => p !== match)
|
|
15856
16095
|
});
|
|
15857
|
-
log(` removed "${match.id}" from ${
|
|
16096
|
+
log(` removed "${match.id}" from ${path44.relative(process.cwd(), workspacePath)}`);
|
|
15858
16097
|
return;
|
|
15859
16098
|
}
|
|
15860
16099
|
if (args.subcommand === "list") {
|
|
@@ -15866,9 +16105,9 @@ async function runWorkspace(args) {
|
|
|
15866
16105
|
if (args.json) {
|
|
15867
16106
|
log(JSON.stringify({ version: 1, workspace: workspacePath, projects: ws.projects, diagnostics }, null, 2));
|
|
15868
16107
|
} else if (!ws.projects.length) {
|
|
15869
|
-
log(` no projects registered in ${
|
|
16108
|
+
log(` no projects registered in ${path44.relative(process.cwd(), workspacePath)}`);
|
|
15870
16109
|
} else {
|
|
15871
|
-
log(` ${ws.projects.length} project(s) in ${
|
|
16110
|
+
log(` ${ws.projects.length} project(s) in ${path44.relative(process.cwd(), workspacePath)}:`);
|
|
15872
16111
|
for (const p of ws.projects) {
|
|
15873
16112
|
log(` ${p.id.padEnd(24)} ${p.manifest.padEnd(40)} (${p.provider}${p.account ? `, ${p.account}` : ""})`);
|
|
15874
16113
|
}
|
|
@@ -15886,7 +16125,7 @@ async function runWorkspace(args) {
|
|
|
15886
16125
|
if (args.json) {
|
|
15887
16126
|
log(JSON.stringify({ ...report, workspace: workspacePath }, null, 2));
|
|
15888
16127
|
} else {
|
|
15889
|
-
log(` workspace: ${
|
|
16128
|
+
log(` workspace: ${path44.relative(process.cwd(), workspacePath)}`);
|
|
15890
16129
|
for (const p of report.projects) {
|
|
15891
16130
|
if (p.error) {
|
|
15892
16131
|
log(`
|
|
@@ -15941,7 +16180,7 @@ async function runVersion() {
|
|
|
15941
16180
|
for (const up of ["../package.json", "../../package.json"]) {
|
|
15942
16181
|
const url = new URL(up, import.meta.url);
|
|
15943
16182
|
if (!existsSync32(url)) continue;
|
|
15944
|
-
const pkg = JSON.parse(await
|
|
16183
|
+
const pkg = JSON.parse(await readFile29(url, "utf8"));
|
|
15945
16184
|
if (pkg.name === "pixelkiln") {
|
|
15946
16185
|
log(`${pkg.name} ${pkg.version}`);
|
|
15947
16186
|
return;
|