pixelkiln 0.41.0 → 0.43.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 +954 -550
- package/dist/cli.js.map +1 -1
- package/dist/client/gallery.js +14 -0
- package/dist/index.cjs +940 -514
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1038 -29
- package/dist/index.d.ts +1038 -29
- package/dist/index.js +902 -480
- package/dist/index.js.map +1 -1
- package/docs/GENERATORS.md +14 -6
- package/docs/MANIFEST.md +94 -6
- package/package.json +1 -1
- package/schema/manifest.schema.json +188 -3
- package/schema/recipe.schema.json +55 -0
- package/skills/pixelkiln/references/pixellab.md +12 -1
package/dist/index.cjs
CHANGED
|
@@ -34,10 +34,12 @@ __export(index_exports, {
|
|
|
34
34
|
BudgetError: () => BudgetError,
|
|
35
35
|
CHARACTER_DIRECTIONS_4: () => CHARACTER_DIRECTIONS_4,
|
|
36
36
|
CHARACTER_DIRECTIONS_8: () => CHARACTER_DIRECTIONS_8,
|
|
37
|
+
CHARACTER_PROPORTION_PRESETS: () => CHARACTER_PROPORTION_PRESETS,
|
|
37
38
|
CharacterAnimationModeSchema: () => CharacterAnimationModeSchema,
|
|
38
39
|
CharacterAnimationSchema: () => CharacterAnimationSchema,
|
|
39
40
|
CharacterDirectionSchema: () => CharacterDirectionSchema,
|
|
40
41
|
CharacterModeSchema: () => CharacterModeSchema,
|
|
42
|
+
CharacterProportionsSchema: () => CharacterProportionsSchema,
|
|
41
43
|
CharacterStateSchema: () => CharacterStateSchema,
|
|
42
44
|
ComfyUIProvider: () => ComfyUIProvider,
|
|
43
45
|
DEFAULT_HISTORY_LIMIT: () => DEFAULT_HISTORY_LIMIT,
|
|
@@ -65,6 +67,7 @@ __export(index_exports, {
|
|
|
65
67
|
MAX_DOWNLOAD_BYTES: () => MAX_DOWNLOAD_BYTES,
|
|
66
68
|
MAX_HAND_EDIT_BYTES: () => MAX_HAND_EDIT_BYTES,
|
|
67
69
|
MAX_RETRIES: () => MAX_RETRIES,
|
|
70
|
+
MIRRORED_DIRECTION: () => MIRRORED_DIRECTION,
|
|
68
71
|
ManifestDriftError: () => ManifestDriftError,
|
|
69
72
|
ManifestEditError: () => ManifestEditError,
|
|
70
73
|
ManifestEditSchema: () => ManifestEditSchema,
|
|
@@ -203,6 +206,7 @@ __export(index_exports, {
|
|
|
203
206
|
portableOutputPath: () => portableOutputPath,
|
|
204
207
|
prepareReview: () => prepareReview,
|
|
205
208
|
primaryOutput: () => primaryOutput,
|
|
209
|
+
proportionsPayload: () => proportionsPayload,
|
|
206
210
|
providerCredentialEnvs: () => providerCredentialEnvs,
|
|
207
211
|
providerFactory: () => providerFactory,
|
|
208
212
|
pushTags: () => pushTags,
|
|
@@ -375,7 +379,28 @@ var CHARACTER_DIRECTIONS_8 = [
|
|
|
375
379
|
"south-west"
|
|
376
380
|
];
|
|
377
381
|
var CHARACTER_DIRECTIONS_4 = ["south", "west", "east", "north"];
|
|
382
|
+
var MIRRORED_DIRECTION = {
|
|
383
|
+
south: "south",
|
|
384
|
+
"south-east": "south-west",
|
|
385
|
+
east: "west",
|
|
386
|
+
"north-east": "north-west",
|
|
387
|
+
north: "north",
|
|
388
|
+
"north-west": "north-east",
|
|
389
|
+
west: "east",
|
|
390
|
+
"south-west": "south-east"
|
|
391
|
+
};
|
|
378
392
|
var CharacterModeSchema = import_zod.z.enum(["standard", "v3", "pro"]);
|
|
393
|
+
var CHARACTER_PROPORTION_PRESETS = ["default", "chibi", "cartoon", "stylized", "realistic_male", "realistic_female", "heroic"];
|
|
394
|
+
var CharacterProportionsSchema = import_zod.z.union([
|
|
395
|
+
import_zod.z.enum(CHARACTER_PROPORTION_PRESETS),
|
|
396
|
+
import_zod.z.object({
|
|
397
|
+
headSize: import_zod.z.number().min(0.5).max(2).optional(),
|
|
398
|
+
armsLength: import_zod.z.number().min(0.5).max(2).optional(),
|
|
399
|
+
legsLength: import_zod.z.number().min(0.5).max(2).optional(),
|
|
400
|
+
shoulderWidth: import_zod.z.number().min(0.5).max(2).optional(),
|
|
401
|
+
hipWidth: import_zod.z.number().min(0.5).max(2).optional()
|
|
402
|
+
}).strict()
|
|
403
|
+
]);
|
|
379
404
|
var CharacterAnimationModeSchema = import_zod.z.enum(["template", "v3", "pro"]);
|
|
380
405
|
var CharacterStateSchema = import_zod.z.object({
|
|
381
406
|
/** The base character, or another state, in the same style. */
|
|
@@ -534,6 +559,16 @@ var StyleObjectSchema = import_zod.z.object({
|
|
|
534
559
|
directions: import_zod.z.union([import_zod.z.literal(4), import_zod.z.literal(8)]).optional(),
|
|
535
560
|
/** `character` only. Body template: `mannequin` (default) or a quadruped (`bear`, `cat`, `dog`, `horse`, `lion`). */
|
|
536
561
|
template: import_zod.z.string().min(1).optional(),
|
|
562
|
+
/** `character` only, `standard` humanoid bases. A preset or multipliers; an asset may override it. */
|
|
563
|
+
proportions: CharacterProportionsSchema.optional(),
|
|
564
|
+
/**
|
|
565
|
+
* `character` only. How closely a `standard` base and a template loop
|
|
566
|
+
* follow their text, 1 to 20; PixelLab's default is 8. The v3 and pro
|
|
567
|
+
* engines do not take it.
|
|
568
|
+
*/
|
|
569
|
+
textGuidanceScale: import_zod.z.number().min(1).max(20).optional(),
|
|
570
|
+
/** `character` only. Draw `standard` bases and every loop in isometric view. */
|
|
571
|
+
isometric: import_zod.z.boolean().optional(),
|
|
537
572
|
/** Fixed seed for reproducibility where the endpoint supports it. */
|
|
538
573
|
seed: import_zod.z.number().int().optional(),
|
|
539
574
|
/**
|
|
@@ -634,8 +669,8 @@ var InheritedStyleSchema = StyleObjectSchema.partial().extend({
|
|
|
634
669
|
}).strict();
|
|
635
670
|
var StyleInputSchema = import_zod.z.union([StyleSchema, InheritedStyleSchema]);
|
|
636
671
|
var AssetSchema = import_zod.z.object({
|
|
637
|
-
/** The subject. Style wrapping comes from the style's prefix/suffix. */
|
|
638
|
-
prompt: import_zod.z.string(),
|
|
672
|
+
/** The subject. Style wrapping comes from the style's prefix/suffix. Only a `mirror` may leave it out. */
|
|
673
|
+
prompt: import_zod.z.string().optional(),
|
|
639
674
|
/** Subdirectory under the style's outDir. Optional. */
|
|
640
675
|
category: import_zod.z.string().optional(),
|
|
641
676
|
/** Overrides the style default. `map` generator only. */
|
|
@@ -693,6 +728,32 @@ var AssetSchema = import_zod.z.object({
|
|
|
693
728
|
state: CharacterStateSchema.optional(),
|
|
694
729
|
/** `character` styles: this asset is a loop of another character asset in one direction. */
|
|
695
730
|
animation: CharacterAnimationSchema.optional(),
|
|
731
|
+
/** `character` styles, `standard` humanoid bases: this character's proportions, over the style's. */
|
|
732
|
+
proportions: CharacterProportionsSchema.optional(),
|
|
733
|
+
/**
|
|
734
|
+
* `character` styles, bases only: the character's own sprite, which
|
|
735
|
+
* PixelLab rotates into the other directions instead of drawing from
|
|
736
|
+
* the prompt. A manifest-relative PNG or JPEG of the south-facing
|
|
737
|
+
* sprite, or an object keyed by direction (`{ "south": ..., "east":
|
|
738
|
+
* ... }`); quadrupeds in `standard` mode need south and east, and only
|
|
739
|
+
* `standard` takes more than south. `standard` wants each image at the
|
|
740
|
+
* style's size; v3 accepts up to 256px, pro up to 168px. The prompt
|
|
741
|
+
* still guides the result.
|
|
742
|
+
*/
|
|
743
|
+
reference: import_zod.z.union([import_zod.z.string().min(1), import_zod.z.record(CharacterDirectionSchema, import_zod.z.string().min(1))]).optional(),
|
|
744
|
+
/**
|
|
745
|
+
* Another asset of the same style flipped left to right, made locally
|
|
746
|
+
* from that asset's downloaded files at no generation cost.
|
|
747
|
+
*
|
|
748
|
+
* The saving is in character loops, where each direction is its own
|
|
749
|
+
* generation: a walk facing west mirrored is the walk facing east, and a
|
|
750
|
+
* diagonal mirrors to the other diagonal. South and north mirror onto
|
|
751
|
+
* themselves and are refused. A single image or a frame set from any
|
|
752
|
+
* generator can be mirrored too; a tile set cannot, since its edges
|
|
753
|
+
* carry meaning. Mirroring swaps handedness, so an asymmetric character
|
|
754
|
+
* (a sword hand, a parted fringe) comes out the other way round.
|
|
755
|
+
*/
|
|
756
|
+
mirror: import_zod.z.string().min(1).optional(),
|
|
696
757
|
/**
|
|
697
758
|
* Generated output role to place in `cell` when this asset expands to
|
|
698
759
|
* several files. Omit for ordinary single-output assets. A structural set
|
|
@@ -732,7 +793,7 @@ var AssetSchema = import_zod.z.object({
|
|
|
732
793
|
path: ["revision"]
|
|
733
794
|
});
|
|
734
795
|
}
|
|
735
|
-
const shapes = [asset.revision && "revision", asset.state && "state", asset.animation && "animation"].filter(Boolean);
|
|
796
|
+
const shapes = [asset.revision && "revision", asset.state && "state", asset.animation && "animation", asset.mirror && "mirror"].filter(Boolean);
|
|
736
797
|
if (shapes.length > 1) {
|
|
737
798
|
context.addIssue({
|
|
738
799
|
code: import_zod.z.ZodIssueCode.custom,
|
|
@@ -740,6 +801,30 @@ var AssetSchema = import_zod.z.object({
|
|
|
740
801
|
path: [shapes[1]]
|
|
741
802
|
});
|
|
742
803
|
}
|
|
804
|
+
if (asset.mirror && (asset.source || Object.keys(asset.sourceByStyle).length)) {
|
|
805
|
+
context.addIssue({
|
|
806
|
+
code: import_zod.z.ZodIssueCode.custom,
|
|
807
|
+
message: "a mirror is made from another asset's files; it cannot also declare a source",
|
|
808
|
+
path: ["mirror"]
|
|
809
|
+
});
|
|
810
|
+
}
|
|
811
|
+
if (asset.prompt === void 0 && !asset.mirror) {
|
|
812
|
+
context.addIssue({
|
|
813
|
+
code: import_zod.z.ZodIssueCode.custom,
|
|
814
|
+
message: "Required",
|
|
815
|
+
path: ["prompt"]
|
|
816
|
+
});
|
|
817
|
+
}
|
|
818
|
+
if (asset.reference && (asset.state || asset.animation || asset.mirror)) {
|
|
819
|
+
context.addIssue({
|
|
820
|
+
code: import_zod.z.ZodIssueCode.custom,
|
|
821
|
+
message: "a reference sprite belongs on a base; a state, animation, or mirror takes its look from its parent",
|
|
822
|
+
path: ["reference"]
|
|
823
|
+
});
|
|
824
|
+
}
|
|
825
|
+
if (typeof asset.reference === "object" && !Object.keys(asset.reference).length) {
|
|
826
|
+
context.addIssue({ code: import_zod.z.ZodIssueCode.custom, message: "a reference needs at least a south image", path: ["reference"] });
|
|
827
|
+
}
|
|
743
828
|
});
|
|
744
829
|
var DEFAULT_HISTORY_LIMIT = 5;
|
|
745
830
|
var HistoryLimitSchema = import_zod.z.number().int().min(0).max(100);
|
|
@@ -829,6 +914,11 @@ var LockEntrySchema = import_zod.z.object({
|
|
|
829
914
|
maskSha256: import_zod.z.string().regex(/^[0-9a-f]{64}$/).optional(),
|
|
830
915
|
strength: import_zod.z.number().min(0).max(1).optional()
|
|
831
916
|
}).strict().nullable().default(null),
|
|
917
|
+
/** For a mirror: the asset it flips and a hash over that asset's output hashes when this was made. */
|
|
918
|
+
mirror: import_zod.z.object({
|
|
919
|
+
sourceAssetId: import_zod.z.string().min(1),
|
|
920
|
+
sourceSha256: import_zod.z.string().regex(/^[0-9a-f]{64}$/)
|
|
921
|
+
}).strict().nullable().default(null),
|
|
832
922
|
/**
|
|
833
923
|
* Output hashes owned by the previous generation while its replacement is
|
|
834
924
|
* pending. They authorize replacing only unchanged PixelKiln-owned files.
|
|
@@ -1160,6 +1250,17 @@ var PixelLabError = class extends ProviderError {
|
|
|
1160
1250
|
}
|
|
1161
1251
|
body;
|
|
1162
1252
|
};
|
|
1253
|
+
function proportionsPayload(proportions) {
|
|
1254
|
+
if (typeof proportions === "string") return { type: "preset", name: proportions };
|
|
1255
|
+
return {
|
|
1256
|
+
type: "custom",
|
|
1257
|
+
...proportions.headSize !== void 0 ? { head_size: proportions.headSize } : {},
|
|
1258
|
+
...proportions.armsLength !== void 0 ? { arms_length: proportions.armsLength } : {},
|
|
1259
|
+
...proportions.legsLength !== void 0 ? { legs_length: proportions.legsLength } : {},
|
|
1260
|
+
...proportions.shoulderWidth !== void 0 ? { shoulder_width: proportions.shoulderWidth } : {},
|
|
1261
|
+
...proportions.hipWidth !== void 0 ? { hip_width: proportions.hipWidth } : {}
|
|
1262
|
+
};
|
|
1263
|
+
}
|
|
1163
1264
|
var PixelLabClient = class {
|
|
1164
1265
|
constructor(apiKey, timeoutMs = 12e4) {
|
|
1165
1266
|
this.apiKey = apiKey;
|
|
@@ -1169,9 +1270,9 @@ var PixelLabClient = class {
|
|
|
1169
1270
|
apiKey;
|
|
1170
1271
|
http;
|
|
1171
1272
|
/** Retries transport failures, 429, and 5xx; see http.ts for the policy. */
|
|
1172
|
-
async request(
|
|
1273
|
+
async request(path33, init) {
|
|
1173
1274
|
const auth = this.apiKey.startsWith("Bearer ") ? this.apiKey : `Bearer ${this.apiKey}`;
|
|
1174
|
-
const res = await this.http(`${BASE}${
|
|
1275
|
+
const res = await this.http(`${BASE}${path33}`, {
|
|
1175
1276
|
...init,
|
|
1176
1277
|
headers: {
|
|
1177
1278
|
Authorization: auth,
|
|
@@ -1181,13 +1282,13 @@ var PixelLabClient = class {
|
|
|
1181
1282
|
});
|
|
1182
1283
|
const text = await res.text();
|
|
1183
1284
|
if (!res.ok) {
|
|
1184
|
-
throw new PixelLabError(`${init?.method ?? "GET"} ${
|
|
1285
|
+
throw new PixelLabError(`${init?.method ?? "GET"} ${path33} \u2192 ${res.status}`, res.status, text);
|
|
1185
1286
|
}
|
|
1186
1287
|
if (!text) return {};
|
|
1187
1288
|
try {
|
|
1188
1289
|
return JSON.parse(text);
|
|
1189
1290
|
} catch {
|
|
1190
|
-
throw new Error(`${init?.method ?? "GET"} ${
|
|
1291
|
+
throw new Error(`${init?.method ?? "GET"} ${path33} returned invalid JSON`);
|
|
1191
1292
|
}
|
|
1192
1293
|
}
|
|
1193
1294
|
async balance() {
|
|
@@ -1368,14 +1469,21 @@ var PixelLabClient = class {
|
|
|
1368
1469
|
* A base character. Standard mode picks the 4- or 8-direction template
|
|
1369
1470
|
* endpoint; v3 and pro have their own and always draw 8. Every one answers
|
|
1370
1471
|
* at once with the character id and a background job.
|
|
1472
|
+
*
|
|
1473
|
+
* A `reference` is the author's own sprite: standard takes one per
|
|
1474
|
+
* direction and draws the rest, v3 rotates the south one, and pro
|
|
1475
|
+
* switches to its rotate method for it. A `styleReference` is pro's style
|
|
1476
|
+
* anchor; the other engines have no such input.
|
|
1371
1477
|
*/
|
|
1372
1478
|
async createCharacter(args) {
|
|
1373
1479
|
const imageSize = { width: args.size, height: args.size };
|
|
1374
1480
|
const palette = args.paletteSwatchBase64 ? { color_image: { type: "base64", base64: args.paletteSwatchBase64, format: "png" }, force_colors: true } : {};
|
|
1375
|
-
|
|
1481
|
+
const encode = (image) => ({ type: "base64", base64: image.base64, format: image.format });
|
|
1482
|
+
const south = args.reference?.south;
|
|
1483
|
+
let path33;
|
|
1376
1484
|
let body;
|
|
1377
1485
|
if (args.mode === "v3") {
|
|
1378
|
-
|
|
1486
|
+
path33 = "/create-character-v3";
|
|
1379
1487
|
body = {
|
|
1380
1488
|
description: args.description,
|
|
1381
1489
|
image_size: imageSize,
|
|
@@ -1384,20 +1492,23 @@ var PixelLabClient = class {
|
|
|
1384
1492
|
...args.view ? { view: args.view } : {},
|
|
1385
1493
|
...args.outline ? { outline: args.outline } : {},
|
|
1386
1494
|
...args.detail ? { detail: args.detail } : {},
|
|
1387
|
-
...args.seed != null ? { seed: args.seed } : {}
|
|
1495
|
+
...args.seed != null ? { seed: args.seed } : {},
|
|
1496
|
+
...south ? { reference_image: encode(south) } : {}
|
|
1388
1497
|
};
|
|
1389
1498
|
} else if (args.mode === "pro") {
|
|
1390
|
-
|
|
1499
|
+
path33 = "/create-character-pro";
|
|
1391
1500
|
body = {
|
|
1392
1501
|
description: args.description,
|
|
1393
1502
|
image_size: imageSize,
|
|
1394
1503
|
template_id: args.template,
|
|
1395
1504
|
no_background: args.noBackground ?? true,
|
|
1505
|
+
method: south ? "rotate_character" : "create_with_style",
|
|
1396
1506
|
...args.view ? { view: args.view } : {},
|
|
1397
|
-
...args.seed != null ? { seed: args.seed } : {}
|
|
1507
|
+
...args.seed != null ? { seed: args.seed } : {},
|
|
1508
|
+
...south ? { reference_image: encode(south) } : args.styleReference ? { reference_image: encode(args.styleReference) } : {}
|
|
1398
1509
|
};
|
|
1399
1510
|
} else {
|
|
1400
|
-
|
|
1511
|
+
path33 = args.directions === 4 ? "/create-character-with-4-directions" : "/create-character-with-8-directions";
|
|
1401
1512
|
body = {
|
|
1402
1513
|
description: args.description,
|
|
1403
1514
|
image_size: imageSize,
|
|
@@ -1407,11 +1518,15 @@ var PixelLabClient = class {
|
|
|
1407
1518
|
...args.shading ? { shading: args.shading } : {},
|
|
1408
1519
|
...args.detail ? { detail: args.detail } : {},
|
|
1409
1520
|
...args.seed != null ? { seed: args.seed } : {},
|
|
1521
|
+
...args.proportions !== void 0 ? { proportions: proportionsPayload(args.proportions) } : {},
|
|
1522
|
+
...args.textGuidanceScale !== void 0 ? { text_guidance_scale: args.textGuidanceScale } : {},
|
|
1523
|
+
...args.isometric !== void 0 ? { isometric: args.isometric } : {},
|
|
1524
|
+
...args.reference && Object.keys(args.reference).length ? { directions: Object.fromEntries(Object.entries(args.reference).flatMap(([direction, image]) => image ? [[direction, encode(image)]] : [])) } : {},
|
|
1410
1525
|
...palette
|
|
1411
1526
|
};
|
|
1412
1527
|
}
|
|
1413
|
-
const raw = await this.request(
|
|
1414
|
-
return validateResponse(CharacterSubmitSchema, raw,
|
|
1528
|
+
const raw = await this.request(path33, { method: "POST", body: JSON.stringify(body) });
|
|
1529
|
+
return validateResponse(CharacterSubmitSchema, raw, path33.slice(1));
|
|
1415
1530
|
}
|
|
1416
1531
|
/** A text edit of an existing character, applied to every direction and stored as a sibling. */
|
|
1417
1532
|
async createCharacterState(args) {
|
|
@@ -1446,6 +1561,8 @@ var PixelLabClient = class {
|
|
|
1446
1561
|
...args.actionDescription ? { action_description: args.actionDescription } : {},
|
|
1447
1562
|
...args.mode === "v3" && args.frameCount ? { frame_count: args.frameCount } : {},
|
|
1448
1563
|
...args.mode === "v3" && args.keepFirstFrame === false ? { keep_first_frame: false } : {},
|
|
1564
|
+
...args.mode === "template" && args.textGuidanceScale !== void 0 ? { text_guidance_scale: args.textGuidanceScale } : {},
|
|
1565
|
+
...args.isometric !== void 0 ? { isometric: args.isometric } : {},
|
|
1449
1566
|
...args.seed != null ? { seed: args.seed } : {}
|
|
1450
1567
|
})
|
|
1451
1568
|
});
|
|
@@ -2044,10 +2161,86 @@ function cacheFileName(hash, mediaType = MediaType.PNG) {
|
|
|
2044
2161
|
}
|
|
2045
2162
|
|
|
2046
2163
|
// src/providers/pixellab.ts
|
|
2047
|
-
var
|
|
2048
|
-
var
|
|
2164
|
+
var import_node_fs2 = require("fs");
|
|
2165
|
+
var import_node_crypto2 = require("crypto");
|
|
2049
2166
|
var import_node_os = __toESM(require("os"), 1);
|
|
2050
2167
|
var import_node_path = __toESM(require("path"), 1);
|
|
2168
|
+
|
|
2169
|
+
// src/hash.ts
|
|
2170
|
+
var import_node_crypto = require("crypto");
|
|
2171
|
+
var import_node_fs = require("fs");
|
|
2172
|
+
function sha256(data) {
|
|
2173
|
+
return (0, import_node_crypto.createHash)("sha256").update(data).digest("hex");
|
|
2174
|
+
}
|
|
2175
|
+
async function sha256File(path33) {
|
|
2176
|
+
return new Promise((resolve, reject) => {
|
|
2177
|
+
const hash = (0, import_node_crypto.createHash)("sha256");
|
|
2178
|
+
const stream = (0, import_node_fs.createReadStream)(path33);
|
|
2179
|
+
stream.on("data", (chunk2) => hash.update(chunk2));
|
|
2180
|
+
stream.on("error", reject);
|
|
2181
|
+
stream.on("end", () => resolve(hash.digest("hex")));
|
|
2182
|
+
});
|
|
2183
|
+
}
|
|
2184
|
+
function specHash(spec, styleImageHashes, providerOptionIdentity = spec.providerOptions, providerInputIdentity = spec.providerInputs ?? {}) {
|
|
2185
|
+
return sha256(
|
|
2186
|
+
JSON.stringify({
|
|
2187
|
+
// Preserve every existing PixelLab hash while making a provider switch
|
|
2188
|
+
// invalidate the spec. Older manifests implicitly mean pixellab.
|
|
2189
|
+
provider: spec.provider === "pixellab" ? void 0 : spec.provider,
|
|
2190
|
+
providerOptions: providerOptionIdentity && (typeof providerOptionIdentity !== "object" || Object.keys(providerOptionIdentity).length > 0) ? providerOptionIdentity : void 0,
|
|
2191
|
+
providerInputs: providerInputIdentity && (typeof providerInputIdentity !== "object" || Object.keys(providerInputIdentity).length > 0) ? providerInputIdentity : void 0,
|
|
2192
|
+
generator: spec.generator,
|
|
2193
|
+
prompt: spec.prompt,
|
|
2194
|
+
width: spec.width,
|
|
2195
|
+
height: spec.height,
|
|
2196
|
+
view: spec.view,
|
|
2197
|
+
outline: spec.outline ?? null,
|
|
2198
|
+
shading: spec.shading ?? null,
|
|
2199
|
+
detail: spec.detail ?? null,
|
|
2200
|
+
seed: spec.seed ?? null,
|
|
2201
|
+
palette: spec.palette,
|
|
2202
|
+
// `noBackground` only reaches the wire for pixflux; the tile fields are
|
|
2203
|
+
// undefined for every other generator. `tileSize` is intentionally
|
|
2204
|
+
// absent; width/height are derived from it, so it is already covered.
|
|
2205
|
+
noBackground: spec.generator === "pixflux" || spec.provider !== "pixellab" ? spec.noBackground : void 0,
|
|
2206
|
+
tileType: spec.tileType,
|
|
2207
|
+
tileView: spec.tileView,
|
|
2208
|
+
tileFeature: spec.tileFeature,
|
|
2209
|
+
outlineMode: spec.outlineMode,
|
|
2210
|
+
styleImages: styleImageHashes,
|
|
2211
|
+
// A character family: the engine and rotations of a base, the parent's
|
|
2212
|
+
// generated bytes for a state or animation (so a regenerated parent
|
|
2213
|
+
// makes its children stale), and each shape's own settings.
|
|
2214
|
+
character: spec.character ? {
|
|
2215
|
+
kind: spec.character.kind,
|
|
2216
|
+
mode: spec.character.mode,
|
|
2217
|
+
directions: spec.character.directions,
|
|
2218
|
+
template: spec.character.template,
|
|
2219
|
+
parent: spec.character.parentAssetId,
|
|
2220
|
+
parentSha256: spec.character.parentSha256 ?? null,
|
|
2221
|
+
state: spec.character.state,
|
|
2222
|
+
animation: spec.character.animation,
|
|
2223
|
+
// Absent keys keep the hashes of manifests that never set these.
|
|
2224
|
+
proportions: spec.character.proportions,
|
|
2225
|
+
textGuidanceScale: spec.character.textGuidanceScale,
|
|
2226
|
+
isometric: spec.character.isometric,
|
|
2227
|
+
reference: spec.character.reference ? Object.fromEntries(Object.entries(spec.character.reference).map(([direction, image]) => [direction, image.sha256])) : void 0
|
|
2228
|
+
} : void 0,
|
|
2229
|
+
// A mirror's bytes come from its source's recorded outputs; the plan
|
|
2230
|
+
// compares those directly, so only the choice of source is identity.
|
|
2231
|
+
mirror: spec.mirror ? { of: spec.mirror.sourceAssetId } : void 0,
|
|
2232
|
+
revision: spec.revision ? {
|
|
2233
|
+
mode: spec.revision.mode,
|
|
2234
|
+
from: spec.revision.sourceAssetId,
|
|
2235
|
+
sourceSha256: spec.revision.sourceSha256,
|
|
2236
|
+
maskSha256: spec.revision.maskSha256,
|
|
2237
|
+
strength: spec.revision.strength
|
|
2238
|
+
} : void 0
|
|
2239
|
+
})
|
|
2240
|
+
);
|
|
2241
|
+
}
|
|
2242
|
+
|
|
2243
|
+
// src/providers/pixellab.ts
|
|
2051
2244
|
function pixelLabObjectUrl(generator, objectId) {
|
|
2052
2245
|
if (!objectId) return null;
|
|
2053
2246
|
if (generator === "map" || generator === "1dir") {
|
|
@@ -2128,7 +2321,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
2128
2321
|
*/
|
|
2129
2322
|
static cacheDir() {
|
|
2130
2323
|
const dir = import_node_path.default.join(import_node_os.default.tmpdir(), "pixelkiln-pixflux");
|
|
2131
|
-
(0,
|
|
2324
|
+
(0, import_node_fs2.mkdirSync)(dir, { recursive: true });
|
|
2132
2325
|
return dir;
|
|
2133
2326
|
}
|
|
2134
2327
|
estimate(spec) {
|
|
@@ -2170,8 +2363,8 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
2170
2363
|
"high detail"
|
|
2171
2364
|
]);
|
|
2172
2365
|
}
|
|
2173
|
-
if (spec.generator === "character") this.validateCharacter(spec);
|
|
2174
|
-
if ((spec.generator === "map" || spec.generator === "pixflux"
|
|
2366
|
+
if (spec.generator === "character") this.validateCharacter(spec, styleImages);
|
|
2367
|
+
if ((spec.generator === "map" || spec.generator === "pixflux") && styleImages.length) {
|
|
2175
2368
|
throw new Error(`PixelLab ${spec.generator} does not support style images`);
|
|
2176
2369
|
}
|
|
2177
2370
|
for (const image of styleImages) {
|
|
@@ -2187,9 +2380,61 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
2187
2380
|
);
|
|
2188
2381
|
}
|
|
2189
2382
|
}
|
|
2190
|
-
validateCharacter(spec) {
|
|
2383
|
+
validateCharacter(spec, styleImages = []) {
|
|
2191
2384
|
const character = spec.character;
|
|
2192
2385
|
if (!character) throw new Error(`${spec.styleId}/${spec.assetId} has no character shape`);
|
|
2386
|
+
const label = `${spec.styleId}/${spec.assetId}`;
|
|
2387
|
+
if (character.kind === "base" && character.proportions !== void 0) {
|
|
2388
|
+
if (character.mode !== "standard") {
|
|
2389
|
+
throw new Error(`${label}: proportions apply to standard bases; the ${character.mode} engine has none to set`);
|
|
2390
|
+
}
|
|
2391
|
+
if (character.template !== "mannequin") {
|
|
2392
|
+
throw new Error(`${label}: proportions apply to the mannequin template; ${character.template} is a quadruped`);
|
|
2393
|
+
}
|
|
2394
|
+
}
|
|
2395
|
+
if (character.reference) {
|
|
2396
|
+
const given = Object.keys(character.reference);
|
|
2397
|
+
if (!character.reference.south) throw new Error(`${label}: a reference needs a south-facing sprite`);
|
|
2398
|
+
if (character.mode !== "standard") {
|
|
2399
|
+
if (given.length > 1) throw new Error(`${label}: PixelLab ${character.mode} rotates one south-facing reference; drop the other directions`);
|
|
2400
|
+
const limit = character.mode === "v3" ? 256 : 168;
|
|
2401
|
+
const south = character.reference.south;
|
|
2402
|
+
if (south.width > limit || south.height > limit) {
|
|
2403
|
+
throw new Error(`${label}: reference is ${south.width}x${south.height}; PixelLab ${character.mode} takes up to ${limit}px`);
|
|
2404
|
+
}
|
|
2405
|
+
} else {
|
|
2406
|
+
const allowed = character.directions === 4 ? CHARACTER_DIRECTIONS_4 : CHARACTER_DIRECTIONS_8;
|
|
2407
|
+
for (const direction of given) {
|
|
2408
|
+
if (!allowed.includes(direction)) {
|
|
2409
|
+
throw new Error(`${label}: reference "${direction}" is not one of this character's ${character.directions} directions`);
|
|
2410
|
+
}
|
|
2411
|
+
const image = character.reference[direction];
|
|
2412
|
+
if (image.width !== spec.width || image.height !== spec.height) {
|
|
2413
|
+
throw new Error(
|
|
2414
|
+
`${label}: reference (${direction}) is ${image.width}x${image.height}; standard mode wants each image at the style's size, ${spec.width}x${spec.height}`
|
|
2415
|
+
);
|
|
2416
|
+
}
|
|
2417
|
+
}
|
|
2418
|
+
if (character.template !== "mannequin" && !character.reference.east) {
|
|
2419
|
+
throw new Error(`${label}: a ${character.template} reference needs south and east images`);
|
|
2420
|
+
}
|
|
2421
|
+
}
|
|
2422
|
+
}
|
|
2423
|
+
if (styleImages.length) {
|
|
2424
|
+
if (character.mode !== "pro") {
|
|
2425
|
+
throw new Error(
|
|
2426
|
+
`PixelLab ${character.mode} characters take no style images; give a base its own sprite with \`reference\`, or use mode pro for a style anchor`
|
|
2427
|
+
);
|
|
2428
|
+
}
|
|
2429
|
+
if (styleImages.length > 1) throw new Error("PixelLab pro characters take one style image");
|
|
2430
|
+
if (character.kind === "base" && character.reference) {
|
|
2431
|
+
throw new Error(`${label}: a pro base rotating its own reference takes no style image`);
|
|
2432
|
+
}
|
|
2433
|
+
const image = styleImages[0];
|
|
2434
|
+
if (image.width > 168 || image.height > 168) {
|
|
2435
|
+
throw new Error(`PixelLab pro character style image is ${image.width}x${image.height}; the limit is 168px`);
|
|
2436
|
+
}
|
|
2437
|
+
}
|
|
2193
2438
|
const maxSize = character.mode === "v3" ? 256 : 128;
|
|
2194
2439
|
if (spec.width < 16 || spec.height < 16 || spec.width > maxSize || spec.height > maxSize) {
|
|
2195
2440
|
throw new Error(`PixelLab ${character.mode} characters must be between 16 and ${maxSize} pixels`);
|
|
@@ -2225,10 +2470,11 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
2225
2470
|
* asset so a later run can find it, and clears its own earlier take for
|
|
2226
2471
|
* that direction first, since PixelLab skips a direction that exists.
|
|
2227
2472
|
*/
|
|
2228
|
-
async submitCharacter(spec, context) {
|
|
2473
|
+
async submitCharacter(spec, styleImages, context) {
|
|
2229
2474
|
const character = spec.character;
|
|
2230
2475
|
if (character.kind === "base") {
|
|
2231
2476
|
const swatch = spec.palette.length && character.mode === "standard" ? paletteSwatch(spec.palette).toString("base64") : void 0;
|
|
2477
|
+
const styleImage = styleImages[0];
|
|
2232
2478
|
const res2 = await this.client.createCharacter({
|
|
2233
2479
|
mode: character.mode,
|
|
2234
2480
|
description: spec.prompt,
|
|
@@ -2241,7 +2487,12 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
2241
2487
|
detail: spec.detail,
|
|
2242
2488
|
seed: spec.seed,
|
|
2243
2489
|
noBackground: spec.noBackground,
|
|
2244
|
-
paletteSwatchBase64: swatch
|
|
2490
|
+
paletteSwatchBase64: swatch,
|
|
2491
|
+
proportions: character.proportions,
|
|
2492
|
+
textGuidanceScale: character.textGuidanceScale,
|
|
2493
|
+
isometric: character.isometric,
|
|
2494
|
+
reference: character.reference ? readReference(spec, character.reference) : void 0,
|
|
2495
|
+
styleReference: styleImage ? { base64: styleImage.base64, format: styleImage.format } : void 0
|
|
2245
2496
|
});
|
|
2246
2497
|
return { jobId: res2.character_id, metadata: { character: { kind: "base", characterId: res2.character_id, mode: character.mode, directions: character.directions, backgroundJobId: res2.background_job_id } } };
|
|
2247
2498
|
}
|
|
@@ -2283,7 +2534,9 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
2283
2534
|
frameCount: animation.frames,
|
|
2284
2535
|
keepFirstFrame: animation.keepFirstFrame,
|
|
2285
2536
|
directions: [animation.direction],
|
|
2286
|
-
seed: spec.seed
|
|
2537
|
+
seed: spec.seed,
|
|
2538
|
+
textGuidanceScale: character.textGuidanceScale,
|
|
2539
|
+
isometric: character.isometric
|
|
2287
2540
|
});
|
|
2288
2541
|
const job = { characterId: parentId, name, direction: animation.direction, jobIds: res.background_job_ids };
|
|
2289
2542
|
return {
|
|
@@ -2373,7 +2626,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
2373
2626
|
}
|
|
2374
2627
|
async submit(spec, styleImages, context) {
|
|
2375
2628
|
this.validate(spec, styleImages);
|
|
2376
|
-
if (spec.generator === "character") return this.submitCharacter(spec, context);
|
|
2629
|
+
if (spec.generator === "character") return this.submitCharacter(spec, styleImages, context);
|
|
2377
2630
|
if (spec.generator === "pixflux") {
|
|
2378
2631
|
const swatch = spec.palette.length ? paletteSwatch(spec.palette).toString("base64") : void 0;
|
|
2379
2632
|
const { png } = await this.client.createImagePixflux({
|
|
@@ -2384,8 +2637,8 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
2384
2637
|
paletteSwatchBase64: swatch,
|
|
2385
2638
|
seed: spec.seed
|
|
2386
2639
|
});
|
|
2387
|
-
const jobId = (0,
|
|
2388
|
-
(0,
|
|
2640
|
+
const jobId = (0, import_node_crypto2.randomUUID)();
|
|
2641
|
+
(0, import_node_fs2.writeFileSync)(import_node_path.default.join(_PixelLabProvider.cacheDir(), `${jobId}.png`), png);
|
|
2389
2642
|
return { jobId };
|
|
2390
2643
|
}
|
|
2391
2644
|
if (spec.generator === "tiles") {
|
|
@@ -2427,7 +2680,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
2427
2680
|
async poll(jobId, generator, context) {
|
|
2428
2681
|
if (generator === "pixflux") {
|
|
2429
2682
|
const file = import_node_path.default.join(_PixelLabProvider.cacheDir(), `${jobId}.png`);
|
|
2430
|
-
if ((0,
|
|
2683
|
+
if ((0, import_node_fs2.existsSync)(file)) {
|
|
2431
2684
|
const sourceUrl = `file://${file}`;
|
|
2432
2685
|
return { status: "ready", objectId: jobId, sourceUrl, sources: [{ url: sourceUrl }] };
|
|
2433
2686
|
}
|
|
@@ -2533,7 +2786,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
2533
2786
|
return { objectId, sourceUrl: firstUrl(obj?.rotation_urls) ?? obj?.preview_url ?? null };
|
|
2534
2787
|
}
|
|
2535
2788
|
async download(url) {
|
|
2536
|
-
if (url.startsWith("file://")) return (0,
|
|
2789
|
+
if (url.startsWith("file://")) return (0, import_node_fs2.readFileSync)(url.slice("file://".length));
|
|
2537
2790
|
return this.client.download(url);
|
|
2538
2791
|
}
|
|
2539
2792
|
async balance() {
|
|
@@ -2640,6 +2893,17 @@ function firstUrl(urls) {
|
|
|
2640
2893
|
if (!urls) return null;
|
|
2641
2894
|
return Object.values(urls).find((u) => typeof u === "string") ?? null;
|
|
2642
2895
|
}
|
|
2896
|
+
function readReference(spec, reference) {
|
|
2897
|
+
const images = {};
|
|
2898
|
+
for (const [direction, image] of Object.entries(reference)) {
|
|
2899
|
+
const bytes = (0, import_node_fs2.readFileSync)(image.path);
|
|
2900
|
+
if (sha256(bytes) !== image.sha256) {
|
|
2901
|
+
throw new Error(`${spec.styleId}/${spec.assetId}: reference (${direction}) changed after the manifest was resolved: ${image.path}`);
|
|
2902
|
+
}
|
|
2903
|
+
images[direction] = { base64: bytes.toString("base64"), format: image.format };
|
|
2904
|
+
}
|
|
2905
|
+
return images;
|
|
2906
|
+
}
|
|
2643
2907
|
function rotationSources(character) {
|
|
2644
2908
|
const order = character.directions === 4 ? CHARACTER_DIRECTIONS_4 : CHARACTER_DIRECTIONS_8;
|
|
2645
2909
|
const sources = [];
|
|
@@ -2710,9 +2974,9 @@ var RetroDiffusionClient = class {
|
|
|
2710
2974
|
if (!response.ok) throw new ProviderError("retrodiffusion", `Retro Diffusion download failed (${response.status})`, { status: response.status });
|
|
2711
2975
|
return Buffer.from(await response.arrayBuffer());
|
|
2712
2976
|
}
|
|
2713
|
-
async call(
|
|
2977
|
+
async call(path33, init = {}) {
|
|
2714
2978
|
if (!this.token) throw new Error("RD_API_KEY is not set");
|
|
2715
|
-
const response = await this.request(`${this.baseUrl}${
|
|
2979
|
+
const response = await this.request(`${this.baseUrl}${path33}`, {
|
|
2716
2980
|
...init,
|
|
2717
2981
|
headers: {
|
|
2718
2982
|
"Content-Type": "application/json",
|
|
@@ -3511,74 +3775,6 @@ function redactScenarioError(value, secrets) {
|
|
|
3511
3775
|
var import_node_crypto3 = require("crypto");
|
|
3512
3776
|
var import_promises = require("fs/promises");
|
|
3513
3777
|
var import_node_path2 = __toESM(require("path"), 1);
|
|
3514
|
-
|
|
3515
|
-
// src/hash.ts
|
|
3516
|
-
var import_node_crypto2 = require("crypto");
|
|
3517
|
-
var import_node_fs2 = require("fs");
|
|
3518
|
-
function sha256(data) {
|
|
3519
|
-
return (0, import_node_crypto2.createHash)("sha256").update(data).digest("hex");
|
|
3520
|
-
}
|
|
3521
|
-
async function sha256File(path32) {
|
|
3522
|
-
return new Promise((resolve, reject) => {
|
|
3523
|
-
const hash = (0, import_node_crypto2.createHash)("sha256");
|
|
3524
|
-
const stream = (0, import_node_fs2.createReadStream)(path32);
|
|
3525
|
-
stream.on("data", (chunk2) => hash.update(chunk2));
|
|
3526
|
-
stream.on("error", reject);
|
|
3527
|
-
stream.on("end", () => resolve(hash.digest("hex")));
|
|
3528
|
-
});
|
|
3529
|
-
}
|
|
3530
|
-
function specHash(spec, styleImageHashes, providerOptionIdentity = spec.providerOptions, providerInputIdentity = spec.providerInputs ?? {}) {
|
|
3531
|
-
return sha256(
|
|
3532
|
-
JSON.stringify({
|
|
3533
|
-
// Preserve every existing PixelLab hash while making a provider switch
|
|
3534
|
-
// invalidate the spec. Older manifests implicitly mean pixellab.
|
|
3535
|
-
provider: spec.provider === "pixellab" ? void 0 : spec.provider,
|
|
3536
|
-
providerOptions: providerOptionIdentity && (typeof providerOptionIdentity !== "object" || Object.keys(providerOptionIdentity).length > 0) ? providerOptionIdentity : void 0,
|
|
3537
|
-
providerInputs: providerInputIdentity && (typeof providerInputIdentity !== "object" || Object.keys(providerInputIdentity).length > 0) ? providerInputIdentity : void 0,
|
|
3538
|
-
generator: spec.generator,
|
|
3539
|
-
prompt: spec.prompt,
|
|
3540
|
-
width: spec.width,
|
|
3541
|
-
height: spec.height,
|
|
3542
|
-
view: spec.view,
|
|
3543
|
-
outline: spec.outline ?? null,
|
|
3544
|
-
shading: spec.shading ?? null,
|
|
3545
|
-
detail: spec.detail ?? null,
|
|
3546
|
-
seed: spec.seed ?? null,
|
|
3547
|
-
palette: spec.palette,
|
|
3548
|
-
// `noBackground` only reaches the wire for pixflux; the tile fields are
|
|
3549
|
-
// undefined for every other generator. `tileSize` is intentionally
|
|
3550
|
-
// absent; width/height are derived from it, so it is already covered.
|
|
3551
|
-
noBackground: spec.generator === "pixflux" || spec.provider !== "pixellab" ? spec.noBackground : void 0,
|
|
3552
|
-
tileType: spec.tileType,
|
|
3553
|
-
tileView: spec.tileView,
|
|
3554
|
-
tileFeature: spec.tileFeature,
|
|
3555
|
-
outlineMode: spec.outlineMode,
|
|
3556
|
-
styleImages: styleImageHashes,
|
|
3557
|
-
// A character family: the engine and rotations of a base, the parent's
|
|
3558
|
-
// generated bytes for a state or animation (so a regenerated parent
|
|
3559
|
-
// makes its children stale), and each shape's own settings.
|
|
3560
|
-
character: spec.character ? {
|
|
3561
|
-
kind: spec.character.kind,
|
|
3562
|
-
mode: spec.character.mode,
|
|
3563
|
-
directions: spec.character.directions,
|
|
3564
|
-
template: spec.character.template,
|
|
3565
|
-
parent: spec.character.parentAssetId,
|
|
3566
|
-
parentSha256: spec.character.parentSha256 ?? null,
|
|
3567
|
-
state: spec.character.state,
|
|
3568
|
-
animation: spec.character.animation
|
|
3569
|
-
} : void 0,
|
|
3570
|
-
revision: spec.revision ? {
|
|
3571
|
-
mode: spec.revision.mode,
|
|
3572
|
-
from: spec.revision.sourceAssetId,
|
|
3573
|
-
sourceSha256: spec.revision.sourceSha256,
|
|
3574
|
-
maskSha256: spec.revision.maskSha256,
|
|
3575
|
-
strength: spec.revision.strength
|
|
3576
|
-
} : void 0
|
|
3577
|
-
})
|
|
3578
|
-
);
|
|
3579
|
-
}
|
|
3580
|
-
|
|
3581
|
-
// src/providers/comfyui.ts
|
|
3582
3778
|
var DEFAULT_BASE_URL3 = "http://127.0.0.1:8188";
|
|
3583
3779
|
var BUILTIN_BINDINGS = /* @__PURE__ */ new Set([
|
|
3584
3780
|
"prompt",
|
|
@@ -4529,6 +4725,18 @@ var FakeProvider = class {
|
|
|
4529
4725
|
const objectId = job.objectId ?? `obj-${jobId}`;
|
|
4530
4726
|
job.objectId = objectId;
|
|
4531
4727
|
this.register(objectId, job.spec);
|
|
4728
|
+
const character = job.spec.character;
|
|
4729
|
+
if (character) {
|
|
4730
|
+
const roles = character.kind === "animation" ? Array.from({ length: character.animation.frames }, (_, i) => `frame-${String(i).padStart(2, "0")}`) : character.directions === 4 ? CHARACTER_DIRECTIONS_4 : CHARACTER_DIRECTIONS_8;
|
|
4731
|
+
const sources = roles.map((role) => ({ url: `fake://${objectId}/${role}.png`, role }));
|
|
4732
|
+
return {
|
|
4733
|
+
status: "ready",
|
|
4734
|
+
objectId,
|
|
4735
|
+
sourceUrl: sources[0].url,
|
|
4736
|
+
sources,
|
|
4737
|
+
...character.kind === "animation" ? { metadata: { frameSet: { fps: character.animation.fps, count: roles.length } } } : {}
|
|
4738
|
+
};
|
|
4739
|
+
}
|
|
4532
4740
|
const sourceUrl = `fake://${objectId}.png`;
|
|
4533
4741
|
return { status: "ready", objectId, sourceUrl, sources: [{ url: sourceUrl }] };
|
|
4534
4742
|
}
|
|
@@ -5193,16 +5401,16 @@ async function resolveSpecs(loaded, filter) {
|
|
|
5193
5401
|
}
|
|
5194
5402
|
}
|
|
5195
5403
|
const styleImageCache = /* @__PURE__ */ new Map();
|
|
5196
|
-
async function loadStyleImage(rel) {
|
|
5404
|
+
async function loadStyleImage(rel, what = "Style image") {
|
|
5197
5405
|
const abs = import_node_path5.default.resolve(root, rel);
|
|
5198
5406
|
let hit = styleImageCache.get(abs);
|
|
5199
5407
|
if (!hit) {
|
|
5200
|
-
if (!(0, import_node_fs5.existsSync)(abs)) throw new Error(
|
|
5408
|
+
if (!(0, import_node_fs5.existsSync)(abs)) throw new Error(`${what} not found: ${abs}`);
|
|
5201
5409
|
const buf = await (0, import_promises3.readFile)(abs);
|
|
5202
5410
|
const metadata = imageMetadata(buf);
|
|
5203
|
-
if (!metadata) throw new Error(
|
|
5411
|
+
if (!metadata) throw new Error(`${what} is not a readable PNG or JPEG: ${abs}`);
|
|
5204
5412
|
if (metadata.width < 1 || metadata.height < 1) {
|
|
5205
|
-
throw new Error(
|
|
5413
|
+
throw new Error(`${what} has invalid dimensions: ${abs}`);
|
|
5206
5414
|
}
|
|
5207
5415
|
hit = { base64: buf.toString("base64"), hash: sha256(buf), ...metadata };
|
|
5208
5416
|
styleImageCache.set(abs, hit);
|
|
@@ -5277,7 +5485,7 @@ async function resolveSpecs(loaded, filter) {
|
|
|
5277
5485
|
);
|
|
5278
5486
|
}
|
|
5279
5487
|
const characterKind = asset.animation ? "animation" : asset.state ? "state" : "base";
|
|
5280
|
-
const subject = asset.promptByStyle[styleId] ?? asset.prompt;
|
|
5488
|
+
const subject = asset.promptByStyle[styleId] ?? asset.prompt ?? "";
|
|
5281
5489
|
const prompt = generator === "character" && characterKind !== "base" ? subject.trim() : [style.promptPrefix, subject, style.promptSuffix].map((p) => p.trim()).filter(Boolean).join(", ");
|
|
5282
5490
|
const relFile = asset.file ?? import_node_path5.default.join(asset.category ?? "", `${assetId}.png`);
|
|
5283
5491
|
const outFile = import_node_path5.default.resolve(root, style.outDir, relFile);
|
|
@@ -5309,7 +5517,7 @@ async function resolveSpecs(loaded, filter) {
|
|
|
5309
5517
|
tileView: generator === "tiles" ? style.tileView : void 0,
|
|
5310
5518
|
tileFeature: generator === "tiles" ? style.tileFeature : void 0,
|
|
5311
5519
|
outlineMode: generator === "tiles" ? style.outlineMode : void 0,
|
|
5312
|
-
...generator === "character" ? { character: resolveCharacterShape(asset, style, characterKind) } : {},
|
|
5520
|
+
...generator === "character" ? { character: await resolveCharacterShape(asset, style, characterKind, { root, load: loadStyleImage }) } : {},
|
|
5313
5521
|
cost: generator === "tiles" ? tilesCost(tileSize, tileVariations) : generationCost(width, height, generator),
|
|
5314
5522
|
costUnit: "generations",
|
|
5315
5523
|
candidates: generator === "tiles" ? tileVariations : generator === "1dir" ? candidateCount(size) : 1
|
|
@@ -5352,11 +5560,42 @@ async function resolveSpecs(loaded, filter) {
|
|
|
5352
5560
|
const resolved = styleSpecs.get(assetId);
|
|
5353
5561
|
if (!resolved) {
|
|
5354
5562
|
throw new Error(
|
|
5355
|
-
`Asset "${assetId}" is not available in style "${styleId}"; revision parents must
|
|
5563
|
+
`Asset "${assetId}" is not available in style "${styleId}"; revision parents, character parents, and mirror sources must be in the same style.`
|
|
5356
5564
|
);
|
|
5357
5565
|
}
|
|
5358
5566
|
if (finalized.has(assetId)) return resolved;
|
|
5359
5567
|
const asset = manifest.assets[assetId];
|
|
5568
|
+
if (asset.mirror) {
|
|
5569
|
+
if (asset.mirror === assetId) throw new Error(`assets.${assetId}: an asset cannot mirror itself`);
|
|
5570
|
+
const sourceSpec = await finalize(asset.mirror);
|
|
5571
|
+
if (sourceSpec.generator === "tiles") {
|
|
5572
|
+
throw new Error(`assets.${assetId}: a tile set cannot be mirrored; its edges carry meaning`);
|
|
5573
|
+
}
|
|
5574
|
+
if (sourceSpec.character?.kind === "animation") {
|
|
5575
|
+
const facing = sourceSpec.character.animation.direction;
|
|
5576
|
+
const mirrored = MIRRORED_DIRECTION[facing];
|
|
5577
|
+
if (mirrored === facing) {
|
|
5578
|
+
throw new Error(
|
|
5579
|
+
`assets.${assetId}: mirroring ${asset.mirror} gives another ${facing}-facing loop; mirror a loop facing east, west, or a diagonal`
|
|
5580
|
+
);
|
|
5581
|
+
}
|
|
5582
|
+
resolved.character = { ...sourceSpec.character, animation: { ...sourceSpec.character.animation, direction: mirrored } };
|
|
5583
|
+
} else if (sourceSpec.character) {
|
|
5584
|
+
resolved.character = { ...sourceSpec.character };
|
|
5585
|
+
}
|
|
5586
|
+
resolved.mirror = { sourceAssetId: asset.mirror, sourceSpec };
|
|
5587
|
+
resolved.generator = sourceSpec.generator;
|
|
5588
|
+
resolved.width = sourceSpec.width;
|
|
5589
|
+
resolved.height = sourceSpec.height;
|
|
5590
|
+
resolved.size = sourceSpec.size;
|
|
5591
|
+
resolved.prompt = "";
|
|
5592
|
+
resolved.specHash = specHash(resolved, styleImageHashes, providerOptionIdentity, providerInputIdentities.get(assetId));
|
|
5593
|
+
resolved.cost = 0;
|
|
5594
|
+
resolved.costUnit = sourceSpec.costUnit;
|
|
5595
|
+
resolved.candidates = 1;
|
|
5596
|
+
finalized.add(assetId);
|
|
5597
|
+
return resolved;
|
|
5598
|
+
}
|
|
5360
5599
|
const characterParent = asset.state?.of ?? asset.animation?.of;
|
|
5361
5600
|
if (resolved.character && characterParent) {
|
|
5362
5601
|
const parentSpec = await finalize(characterParent);
|
|
@@ -5523,15 +5762,33 @@ function imageMetadata(buf) {
|
|
|
5523
5762
|
}
|
|
5524
5763
|
return null;
|
|
5525
5764
|
}
|
|
5526
|
-
function resolveCharacterShape(asset, style, kind) {
|
|
5765
|
+
async function resolveCharacterShape(asset, style, kind, files) {
|
|
5527
5766
|
const mode2 = style.mode ?? "standard";
|
|
5528
5767
|
const directions = mode2 === "standard" ? style.directions ?? 8 : 8;
|
|
5768
|
+
const proportions = asset.proportions ?? style.proportions;
|
|
5529
5769
|
const shape = {
|
|
5530
5770
|
kind,
|
|
5531
5771
|
mode: mode2,
|
|
5532
5772
|
directions,
|
|
5533
|
-
template: style.template ?? "mannequin"
|
|
5773
|
+
template: style.template ?? "mannequin",
|
|
5774
|
+
...proportions !== void 0 ? { proportions } : {},
|
|
5775
|
+
...style.textGuidanceScale !== void 0 ? { textGuidanceScale: style.textGuidanceScale } : {},
|
|
5776
|
+
...style.isometric !== void 0 ? { isometric: style.isometric } : {}
|
|
5534
5777
|
};
|
|
5778
|
+
if (asset.reference) {
|
|
5779
|
+
const byDirection = typeof asset.reference === "string" ? { south: asset.reference } : asset.reference;
|
|
5780
|
+
shape.reference = {};
|
|
5781
|
+
for (const [direction, rel] of Object.entries(byDirection)) {
|
|
5782
|
+
const image = await files.load(rel, `Reference sprite (${direction})`);
|
|
5783
|
+
shape.reference[direction] = {
|
|
5784
|
+
path: import_node_path5.default.resolve(files.root, rel),
|
|
5785
|
+
sha256: image.hash,
|
|
5786
|
+
width: image.width,
|
|
5787
|
+
height: image.height,
|
|
5788
|
+
format: image.format
|
|
5789
|
+
};
|
|
5790
|
+
}
|
|
5791
|
+
}
|
|
5535
5792
|
if (asset.state) {
|
|
5536
5793
|
shape.state = {
|
|
5537
5794
|
paletteFromReference: asset.state.paletteFromReference,
|
|
@@ -5556,7 +5813,7 @@ function characterDirections(directions) {
|
|
|
5556
5813
|
}
|
|
5557
5814
|
|
|
5558
5815
|
// src/project.ts
|
|
5559
|
-
var
|
|
5816
|
+
var import_node_path13 = __toESM(require("path"), 1);
|
|
5560
5817
|
|
|
5561
5818
|
// src/env.ts
|
|
5562
5819
|
var import_node_fs6 = require("fs");
|
|
@@ -5597,30 +5854,232 @@ function parseEnv(contents) {
|
|
|
5597
5854
|
if ((quote === '"' || quote === "'") && value.endsWith(quote) && value.length >= 2) {
|
|
5598
5855
|
value = value.slice(1, -1);
|
|
5599
5856
|
}
|
|
5600
|
-
vars[key] = value;
|
|
5857
|
+
vars[key] = value;
|
|
5858
|
+
}
|
|
5859
|
+
return vars;
|
|
5860
|
+
}
|
|
5861
|
+
|
|
5862
|
+
// src/pipeline/plan.ts
|
|
5863
|
+
var import_node_fs9 = require("fs");
|
|
5864
|
+
var import_node_path12 = __toESM(require("path"), 1);
|
|
5865
|
+
|
|
5866
|
+
// src/pipeline/mirror.ts
|
|
5867
|
+
var import_promises4 = require("fs/promises");
|
|
5868
|
+
var import_node_path7 = __toESM(require("path"), 1);
|
|
5869
|
+
|
|
5870
|
+
// src/palette.ts
|
|
5871
|
+
function normalizePalette(values) {
|
|
5872
|
+
const colors = /* @__PURE__ */ new Map();
|
|
5873
|
+
for (const value of values) {
|
|
5874
|
+
const rgb = parseHex(value);
|
|
5875
|
+
const hex2 = `#${rgb.r.toString(16).padStart(2, "0")}${rgb.g.toString(16).padStart(2, "0")}${rgb.b.toString(16).padStart(2, "0")}`;
|
|
5876
|
+
if (!colors.has(hex2)) colors.set(hex2, { hex: hex2, ...rgb });
|
|
5877
|
+
}
|
|
5878
|
+
if (colors.size < 2 || colors.size > 256) {
|
|
5879
|
+
throw new Error(`A palette must contain 2\u2013256 unique colors; got ${colors.size}.`);
|
|
5880
|
+
}
|
|
5881
|
+
return [...colors.values()];
|
|
5882
|
+
}
|
|
5883
|
+
function colorDistance(a, b) {
|
|
5884
|
+
const rmean = (a.r + b.r) / 2;
|
|
5885
|
+
const dr = a.r - b.r;
|
|
5886
|
+
const dg = a.g - b.g;
|
|
5887
|
+
const db = a.b - b.b;
|
|
5888
|
+
return Math.sqrt(
|
|
5889
|
+
(512 + rmean) * dr * dr / 256 + 4 * dg * dg + (767 - rmean) * db * db / 256
|
|
5890
|
+
);
|
|
5891
|
+
}
|
|
5892
|
+
function quantize(png, palette) {
|
|
5893
|
+
const colors = normalizePalette(palette);
|
|
5894
|
+
const pixels = Buffer.from(png.pixels);
|
|
5895
|
+
const nearestOf = /* @__PURE__ */ new Map();
|
|
5896
|
+
let changed = 0;
|
|
5897
|
+
for (let i = 0; i < pixels.length; i += 4) {
|
|
5898
|
+
const alpha = pixels[i + 3];
|
|
5899
|
+
if (alpha === 0) {
|
|
5900
|
+
if (pixels[i] || pixels[i + 1] || pixels[i + 2]) changed++;
|
|
5901
|
+
pixels[i] = 0;
|
|
5902
|
+
pixels[i + 1] = 0;
|
|
5903
|
+
pixels[i + 2] = 0;
|
|
5904
|
+
continue;
|
|
5905
|
+
}
|
|
5906
|
+
const key = pixels[i] << 16 | pixels[i + 1] << 8 | pixels[i + 2];
|
|
5907
|
+
let nearest = nearestOf.get(key);
|
|
5908
|
+
if (!nearest) {
|
|
5909
|
+
const pixel = { r: pixels[i], g: pixels[i + 1], b: pixels[i + 2] };
|
|
5910
|
+
nearest = colors[0];
|
|
5911
|
+
let distance = colorDistance(pixel, nearest);
|
|
5912
|
+
for (let j = 1; j < colors.length; j++) {
|
|
5913
|
+
const candidate = colors[j];
|
|
5914
|
+
const candidateDistance = colorDistance(pixel, candidate);
|
|
5915
|
+
if (candidateDistance < distance) {
|
|
5916
|
+
nearest = candidate;
|
|
5917
|
+
distance = candidateDistance;
|
|
5918
|
+
}
|
|
5919
|
+
}
|
|
5920
|
+
nearestOf.set(key, nearest);
|
|
5921
|
+
}
|
|
5922
|
+
if (pixels[i] !== nearest.r || pixels[i + 1] !== nearest.g || pixels[i + 2] !== nearest.b) changed++;
|
|
5923
|
+
pixels[i] = nearest.r;
|
|
5924
|
+
pixels[i + 1] = nearest.g;
|
|
5925
|
+
pixels[i + 2] = nearest.b;
|
|
5926
|
+
}
|
|
5927
|
+
return { png: { width: png.width, height: png.height, pixels }, changed };
|
|
5928
|
+
}
|
|
5929
|
+
function quantizeToPalette(png, palette) {
|
|
5930
|
+
return quantize(png, palette).png;
|
|
5931
|
+
}
|
|
5932
|
+
|
|
5933
|
+
// src/pipeline/postprocess.ts
|
|
5934
|
+
function postprocessFor(spec) {
|
|
5935
|
+
if (!spec.enforcePalette || spec.palette.length < 2) return void 0;
|
|
5936
|
+
return {
|
|
5937
|
+
palette: {
|
|
5938
|
+
colors: normalizePalette(spec.palette).map((color) => color.hex),
|
|
5939
|
+
dither: "none",
|
|
5940
|
+
distance: "redmean"
|
|
5941
|
+
}
|
|
5942
|
+
};
|
|
5943
|
+
}
|
|
5944
|
+
function samePostprocess(a, b) {
|
|
5945
|
+
const pa = a?.palette;
|
|
5946
|
+
const pb = b?.palette;
|
|
5947
|
+
if (!pa && !pb) return true;
|
|
5948
|
+
if (!pa || !pb) return false;
|
|
5949
|
+
return pa.dither === pb.dither && pa.distance === pb.distance && pa.colors.length === pb.colors.length && pa.colors.every((color, i) => color === pb.colors[i]);
|
|
5950
|
+
}
|
|
5951
|
+
function postprocessCurrent(entry, spec) {
|
|
5952
|
+
return samePostprocess(entry.postprocess, postprocessFor(spec));
|
|
5953
|
+
}
|
|
5954
|
+
function applyPostprocess(bytes, mediaType, record) {
|
|
5955
|
+
if (!record?.palette || mediaType !== MediaType.PNG) return { bytes, changed: false };
|
|
5956
|
+
const png = decodePng(bytes);
|
|
5957
|
+
const { png: snapped, changed } = quantize(png, record.palette.colors);
|
|
5958
|
+
if (!changed) return { bytes, changed: false };
|
|
5959
|
+
return { bytes: encodeRgbaPng(snapped.width, snapped.height, snapped.pixels), changed: true };
|
|
5960
|
+
}
|
|
5961
|
+
|
|
5962
|
+
// src/pipeline/mirror.ts
|
|
5963
|
+
function mirrorSourceHash(source) {
|
|
5964
|
+
return sha256(JSON.stringify(source.outputs.map((output) => output.sha256)));
|
|
5965
|
+
}
|
|
5966
|
+
function mirrorCurrent(entry, source) {
|
|
5967
|
+
return Boolean(entry.mirror && source && entry.mirror.sourceSha256 === mirrorSourceHash(source));
|
|
5968
|
+
}
|
|
5969
|
+
function mirroredRole(role) {
|
|
5970
|
+
if (role && role in MIRRORED_DIRECTION) return MIRRORED_DIRECTION[role];
|
|
5971
|
+
return role;
|
|
5972
|
+
}
|
|
5973
|
+
function flipHorizontal(png) {
|
|
5974
|
+
const { width, height, pixels } = decodePng(png);
|
|
5975
|
+
const flipped = Buffer.alloc(pixels.length);
|
|
5976
|
+
const stride = width * 4;
|
|
5977
|
+
for (let y = 0; y < height; y++) {
|
|
5978
|
+
const row = y * stride;
|
|
5979
|
+
for (let x = 0; x < width; x++) {
|
|
5980
|
+
pixels.copy(flipped, row + (width - 1 - x) * 4, row + x * 4, row + x * 4 + 4);
|
|
5981
|
+
}
|
|
5601
5982
|
}
|
|
5602
|
-
return
|
|
5983
|
+
return encodeRgbaPng(width, height, flipped);
|
|
5984
|
+
}
|
|
5985
|
+
async function deriveMirror(spec, lock, lockPath, opts = {}) {
|
|
5986
|
+
const log = opts.onProgress ?? (() => {
|
|
5987
|
+
});
|
|
5988
|
+
const mirror = spec.mirror;
|
|
5989
|
+
const key = lockKey(spec.styleId, spec.assetId);
|
|
5990
|
+
const sourceKey = lockKey(spec.styleId, mirror.sourceAssetId);
|
|
5991
|
+
const source = lock.entries[sourceKey];
|
|
5992
|
+
if (!source || source.status !== "downloaded" || !source.outputs.length) {
|
|
5993
|
+
throw new Error(`mirror source ${sourceKey} has no downloaded outputs`);
|
|
5994
|
+
}
|
|
5995
|
+
const members = source.outputs.map((output, index) => ({
|
|
5996
|
+
file: currentEntryOutputPath(source, mirror.sourceSpec, index),
|
|
5997
|
+
role: mirroredRole(output.role)
|
|
5998
|
+
}));
|
|
5999
|
+
if (members.every((member) => member.role && CHARACTER_DIRECTIONS_8.includes(member.role))) {
|
|
6000
|
+
members.sort((a, b) => CHARACTER_DIRECTIONS_8.indexOf(a.role) - CHARACTER_DIRECTIONS_8.indexOf(b.role));
|
|
6001
|
+
}
|
|
6002
|
+
const outputs = [];
|
|
6003
|
+
const written = [];
|
|
6004
|
+
for (const [index, member] of members.entries()) {
|
|
6005
|
+
const bytes = flipHorizontal(await (0, import_promises4.readFile)(member.file));
|
|
6006
|
+
const target = expectedOutputPath(spec, member.role, index, members.length, MediaType.PNG);
|
|
6007
|
+
await (0, import_promises4.mkdir)(import_node_path7.default.dirname(target), { recursive: true });
|
|
6008
|
+
const tmp = `${target}.pixelkiln.tmp`;
|
|
6009
|
+
await (0, import_promises4.writeFile)(tmp, bytes);
|
|
6010
|
+
await (0, import_promises4.rename)(tmp, target);
|
|
6011
|
+
written.push(target);
|
|
6012
|
+
outputs.push({
|
|
6013
|
+
path: portableOutputPath(target, spec.root),
|
|
6014
|
+
sha256: sha256(bytes),
|
|
6015
|
+
...member.role ? { role: member.role } : {},
|
|
6016
|
+
mediaType: MediaType.PNG
|
|
6017
|
+
});
|
|
6018
|
+
}
|
|
6019
|
+
const sourceMetadata = source.providerMetadata?.[source.provider] ?? {};
|
|
6020
|
+
const character = spec.character;
|
|
6021
|
+
const entry = upsert(lock, key, {
|
|
6022
|
+
styleId: spec.styleId,
|
|
6023
|
+
assetId: spec.assetId,
|
|
6024
|
+
specHash: spec.specHash,
|
|
6025
|
+
generator: spec.generator,
|
|
6026
|
+
tileFeature: null,
|
|
6027
|
+
prompt: "",
|
|
6028
|
+
width: spec.width,
|
|
6029
|
+
height: spec.height,
|
|
6030
|
+
revision: null,
|
|
6031
|
+
mirror: { sourceAssetId: mirror.sourceAssetId, sourceSha256: mirrorSourceHash(source) },
|
|
6032
|
+
supersededOutputs: [],
|
|
6033
|
+
jobId: null,
|
|
6034
|
+
submissionComplete: void 0,
|
|
6035
|
+
reviewObjectId: null,
|
|
6036
|
+
objectId: null,
|
|
6037
|
+
candidateIndex: null,
|
|
6038
|
+
status: "downloaded",
|
|
6039
|
+
error: null,
|
|
6040
|
+
outputs,
|
|
6041
|
+
providerMetadata: {
|
|
6042
|
+
[source.provider]: {
|
|
6043
|
+
...sourceMetadata.frameSet ? { frameSet: sourceMetadata.frameSet } : {},
|
|
6044
|
+
...character ? {
|
|
6045
|
+
character: {
|
|
6046
|
+
kind: character.kind,
|
|
6047
|
+
mirrorOf: mirror.sourceAssetId,
|
|
6048
|
+
...character.animation ? { direction: character.animation.direction } : {}
|
|
6049
|
+
}
|
|
6050
|
+
} : {}
|
|
6051
|
+
}
|
|
6052
|
+
},
|
|
6053
|
+
sourceUrl: null,
|
|
6054
|
+
sourceUrls: [],
|
|
6055
|
+
submittedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6056
|
+
downloadedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6057
|
+
history: [],
|
|
6058
|
+
cost: 0,
|
|
6059
|
+
costUnit: spec.costUnit,
|
|
6060
|
+
provider: source.provider,
|
|
6061
|
+
postprocess: postprocessFor(spec)
|
|
6062
|
+
});
|
|
6063
|
+
await saveLock(lockPath, lock);
|
|
6064
|
+
log(` mirror ${key} \u2190 ${sourceKey} (${written.length} file${written.length === 1 ? "" : "s"} flipped, no generation cost)`);
|
|
6065
|
+
return entry;
|
|
5603
6066
|
}
|
|
5604
|
-
|
|
5605
|
-
// src/pipeline/plan.ts
|
|
5606
|
-
var import_node_fs9 = require("fs");
|
|
5607
|
-
var import_node_path11 = __toESM(require("path"), 1);
|
|
5608
6067
|
|
|
5609
6068
|
// src/pipeline/quality-profile.ts
|
|
5610
6069
|
var import_node_fs7 = require("fs");
|
|
5611
|
-
var
|
|
6070
|
+
var import_node_path10 = __toESM(require("path"), 1);
|
|
5612
6071
|
|
|
5613
6072
|
// src/pipeline/refine.ts
|
|
5614
|
-
var
|
|
6073
|
+
var import_node_path9 = __toESM(require("path"), 1);
|
|
5615
6074
|
var import_node_child_process2 = require("child_process");
|
|
5616
|
-
var
|
|
6075
|
+
var import_promises6 = require("fs/promises");
|
|
5617
6076
|
var import_node_os2 = require("os");
|
|
5618
6077
|
var import_node_util = require("util");
|
|
5619
6078
|
|
|
5620
6079
|
// src/artifacts.ts
|
|
5621
|
-
var
|
|
6080
|
+
var import_node_path8 = __toESM(require("path"), 1);
|
|
5622
6081
|
var import_node_crypto5 = require("crypto");
|
|
5623
|
-
var
|
|
6082
|
+
var import_promises5 = require("fs/promises");
|
|
5624
6083
|
var activeTransactions = /* @__PURE__ */ new Set();
|
|
5625
6084
|
function message(error) {
|
|
5626
6085
|
return error instanceof Error ? error.message : String(error);
|
|
@@ -5629,7 +6088,7 @@ function digest(data) {
|
|
|
5629
6088
|
return (0, import_node_crypto5.createHash)("sha256").update(data).digest("hex");
|
|
5630
6089
|
}
|
|
5631
6090
|
function portableRelative(from, to) {
|
|
5632
|
-
return
|
|
6091
|
+
return import_node_path8.default.relative(from, import_node_path8.default.resolve(to)).split(import_node_path8.default.sep).join("/") || ".";
|
|
5633
6092
|
}
|
|
5634
6093
|
function canonical2(value) {
|
|
5635
6094
|
if (value === null || typeof value === "string" || typeof value === "boolean") return value;
|
|
@@ -5679,12 +6138,12 @@ function parseArtifactManifest(absolute, data) {
|
|
|
5679
6138
|
return raw;
|
|
5680
6139
|
}
|
|
5681
6140
|
async function readArtifactBundleManifest(manifestPath) {
|
|
5682
|
-
const absolute =
|
|
5683
|
-
return parseArtifactManifest(absolute, await (0,
|
|
6141
|
+
const absolute = import_node_path8.default.resolve(manifestPath);
|
|
6142
|
+
return parseArtifactManifest(absolute, await (0, import_promises5.readFile)(absolute, "utf8"));
|
|
5684
6143
|
}
|
|
5685
6144
|
async function readOptional(file) {
|
|
5686
6145
|
try {
|
|
5687
|
-
return await (0,
|
|
6146
|
+
return await (0, import_promises5.readFile)(file);
|
|
5688
6147
|
} catch (error) {
|
|
5689
6148
|
if (isCode(error, "ENOENT")) return null;
|
|
5690
6149
|
throw error;
|
|
@@ -5703,8 +6162,8 @@ function processIsAlive(pid) {
|
|
|
5703
6162
|
}
|
|
5704
6163
|
}
|
|
5705
6164
|
function validTemporaryPath(candidate, destination, type) {
|
|
5706
|
-
return
|
|
5707
|
-
`.${
|
|
6165
|
+
return import_node_path8.default.dirname(candidate) === import_node_path8.default.dirname(destination) && import_node_path8.default.basename(candidate).startsWith(
|
|
6166
|
+
`.${import_node_path8.default.basename(destination)}.pixelkiln-${type}-`
|
|
5708
6167
|
);
|
|
5709
6168
|
}
|
|
5710
6169
|
function parseTransaction(journal, data) {
|
|
@@ -5720,14 +6179,14 @@ function parseTransaction(journal, data) {
|
|
|
5720
6179
|
return raw;
|
|
5721
6180
|
}
|
|
5722
6181
|
async function removeTransactionFiles(journal) {
|
|
5723
|
-
await (0,
|
|
5724
|
-
await (0,
|
|
6182
|
+
await (0, import_promises5.rm)(journal, { force: true });
|
|
6183
|
+
await (0, import_promises5.rm)(transactionMarker(journal), { force: true });
|
|
5725
6184
|
}
|
|
5726
6185
|
async function recoverArtifactTransaction(recoveryFile, allowedDestinations) {
|
|
5727
|
-
const journal =
|
|
6186
|
+
const journal = import_node_path8.default.resolve(recoveryFile);
|
|
5728
6187
|
const bytes = await readOptional(journal);
|
|
5729
6188
|
if (!bytes) {
|
|
5730
|
-
await (0,
|
|
6189
|
+
await (0, import_promises5.rm)(transactionMarker(journal), { force: true });
|
|
5731
6190
|
return;
|
|
5732
6191
|
}
|
|
5733
6192
|
if (activeTransactions.has(journal)) {
|
|
@@ -5740,7 +6199,7 @@ async function recoverArtifactTransaction(recoveryFile, allowedDestinations) {
|
|
|
5740
6199
|
);
|
|
5741
6200
|
}
|
|
5742
6201
|
for (const entry of transaction.entries) {
|
|
5743
|
-
if (typeof entry.destination !== "string" || typeof entry.stage !== "string" || typeof entry.sha256 !== "string" || entry.backup !== void 0 && typeof entry.backup !== "string" || !allowedDestinations.has(
|
|
6202
|
+
if (typeof entry.destination !== "string" || typeof entry.stage !== "string" || typeof entry.sha256 !== "string" || entry.backup !== void 0 && typeof entry.backup !== "string" || !allowedDestinations.has(import_node_path8.default.resolve(entry.destination)) || !validTemporaryPath(entry.stage, entry.destination, "stage") || entry.backup !== void 0 && !validTemporaryPath(entry.backup, entry.destination, "backup")) {
|
|
5744
6203
|
throw new Error(
|
|
5745
6204
|
`Refusing unsafe artifact recovery from ${journal}; its destinations or temporary paths do not match the current bundle.`
|
|
5746
6205
|
);
|
|
@@ -5766,7 +6225,7 @@ async function recoverArtifactTransaction(recoveryFile, allowedDestinations) {
|
|
|
5766
6225
|
}
|
|
5767
6226
|
await remove2(entry.destination, errors);
|
|
5768
6227
|
try {
|
|
5769
|
-
await (0,
|
|
6228
|
+
await (0, import_promises5.rename)(entry.backup, entry.destination);
|
|
5770
6229
|
} catch (error) {
|
|
5771
6230
|
errors.push(
|
|
5772
6231
|
`could not restore ${entry.destination}; previous file remains at ${entry.backup}: ${message(error)}`
|
|
@@ -5786,7 +6245,7 @@ async function recoverArtifactTransaction(recoveryFile, allowedDestinations) {
|
|
|
5786
6245
|
await removeTransactionFiles(journal);
|
|
5787
6246
|
}
|
|
5788
6247
|
function createArtifactBundleManifest(manifestPath, outputs, provenance) {
|
|
5789
|
-
const root =
|
|
6248
|
+
const root = import_node_path8.default.dirname(import_node_path8.default.resolve(manifestPath));
|
|
5790
6249
|
const sources = provenance.sources.map((source) => ({
|
|
5791
6250
|
...source,
|
|
5792
6251
|
path: portableRelative(root, source.path)
|
|
@@ -5816,11 +6275,11 @@ function withArtifactManifest(manifestPath, outputs, provenance) {
|
|
|
5816
6275
|
];
|
|
5817
6276
|
}
|
|
5818
6277
|
async function writeManagedArtifactBundle(manifestPath, outputs, provenance, options = {}) {
|
|
5819
|
-
const absoluteManifest =
|
|
6278
|
+
const absoluteManifest = import_node_path8.default.resolve(manifestPath);
|
|
5820
6279
|
const recoveryFile = `${absoluteManifest}.transaction`;
|
|
5821
6280
|
const allowedDestinations = /* @__PURE__ */ new Set([
|
|
5822
6281
|
absoluteManifest,
|
|
5823
|
-
...outputs.map((output) =>
|
|
6282
|
+
...outputs.map((output) => import_node_path8.default.resolve(output.path))
|
|
5824
6283
|
]);
|
|
5825
6284
|
await recoverArtifactTransaction(recoveryFile, allowedDestinations);
|
|
5826
6285
|
const existingManifestBytes = await readOptional(absoluteManifest);
|
|
@@ -5838,13 +6297,13 @@ async function writeManagedArtifactBundle(manifestPath, outputs, provenance, opt
|
|
|
5838
6297
|
}
|
|
5839
6298
|
}
|
|
5840
6299
|
if (!options.force) {
|
|
5841
|
-
const root =
|
|
6300
|
+
const root = import_node_path8.default.dirname(absoluteManifest);
|
|
5842
6301
|
const recorded = new Map(
|
|
5843
|
-
previous?.outputs.map((output) => [
|
|
6302
|
+
previous?.outputs.map((output) => [import_node_path8.default.resolve(root, output.path), output.sha256]) ?? []
|
|
5844
6303
|
);
|
|
5845
6304
|
const conflicts = [];
|
|
5846
6305
|
for (const output of outputs) {
|
|
5847
|
-
const destination =
|
|
6306
|
+
const destination = import_node_path8.default.resolve(output.path);
|
|
5848
6307
|
const current = await readOptional(destination);
|
|
5849
6308
|
if (!current || digest(current) === digest(output.data)) continue;
|
|
5850
6309
|
const expected = recorded.get(destination);
|
|
@@ -5866,15 +6325,15 @@ Pass { force: true } (CLI: --force) to take ownership and replace the complete b
|
|
|
5866
6325
|
);
|
|
5867
6326
|
}
|
|
5868
6327
|
async function verifyArtifactBundle(manifestPath) {
|
|
5869
|
-
const absolute =
|
|
6328
|
+
const absolute = import_node_path8.default.resolve(manifestPath);
|
|
5870
6329
|
const raw = await readArtifactBundleManifest(absolute);
|
|
5871
|
-
const root =
|
|
6330
|
+
const root = import_node_path8.default.dirname(absolute);
|
|
5872
6331
|
const changedSources = [];
|
|
5873
6332
|
const changedOutputs = [];
|
|
5874
6333
|
for (const source of raw.sources) {
|
|
5875
6334
|
let actual = null;
|
|
5876
6335
|
try {
|
|
5877
|
-
actual = digest(await (0,
|
|
6336
|
+
actual = digest(await (0, import_promises5.readFile)(import_node_path8.default.resolve(root, source.path)));
|
|
5878
6337
|
} catch (error) {
|
|
5879
6338
|
if (!isCode(error, "ENOENT")) throw error;
|
|
5880
6339
|
}
|
|
@@ -5882,7 +6341,7 @@ async function verifyArtifactBundle(manifestPath) {
|
|
|
5882
6341
|
}
|
|
5883
6342
|
for (const output of raw.outputs) {
|
|
5884
6343
|
try {
|
|
5885
|
-
if (digest(await (0,
|
|
6344
|
+
if (digest(await (0, import_promises5.readFile)(import_node_path8.default.resolve(root, output.path))) !== output.sha256) {
|
|
5886
6345
|
changedOutputs.push(output.path);
|
|
5887
6346
|
}
|
|
5888
6347
|
} catch (error) {
|
|
@@ -5908,7 +6367,7 @@ function isCode(error, code) {
|
|
|
5908
6367
|
}
|
|
5909
6368
|
async function remove2(file, errors) {
|
|
5910
6369
|
try {
|
|
5911
|
-
await (0,
|
|
6370
|
+
await (0, import_promises5.rm)(file, { force: true });
|
|
5912
6371
|
} catch (error) {
|
|
5913
6372
|
errors?.push(`could not remove ${file}: ${message(error)}`);
|
|
5914
6373
|
}
|
|
@@ -5921,7 +6380,7 @@ async function rollback(prepared) {
|
|
|
5921
6380
|
for (const artifact of [...prepared].reverse()) {
|
|
5922
6381
|
if (!artifact.backup || !artifact.backedUp) continue;
|
|
5923
6382
|
try {
|
|
5924
|
-
await (0,
|
|
6383
|
+
await (0, import_promises5.rename)(artifact.backup, artifact.destination);
|
|
5925
6384
|
artifact.backedUp = false;
|
|
5926
6385
|
} catch (error) {
|
|
5927
6386
|
errors.push(
|
|
@@ -5938,7 +6397,7 @@ async function writeArtifactBundle(files, options = {}) {
|
|
|
5938
6397
|
if (!files.length) throw new Error("An artifact bundle must contain at least one file.");
|
|
5939
6398
|
const normalized = files.map((file) => {
|
|
5940
6399
|
if (!file.path.trim()) throw new Error("Artifact paths cannot be empty.");
|
|
5941
|
-
return { destination:
|
|
6400
|
+
return { destination: import_node_path8.default.resolve(file.path), data: Buffer.from(file.data) };
|
|
5942
6401
|
});
|
|
5943
6402
|
const destinations = /* @__PURE__ */ new Set();
|
|
5944
6403
|
for (const artifact of normalized) {
|
|
@@ -5954,7 +6413,7 @@ async function writeArtifactBundle(files, options = {}) {
|
|
|
5954
6413
|
const unchanged = [];
|
|
5955
6414
|
for (const artifact of normalized) {
|
|
5956
6415
|
try {
|
|
5957
|
-
const current = await (0,
|
|
6416
|
+
const current = await (0, import_promises5.readFile)(artifact.destination);
|
|
5958
6417
|
if (current.equals(artifact.data)) {
|
|
5959
6418
|
unchanged.push(artifact.destination);
|
|
5960
6419
|
} else {
|
|
@@ -5968,21 +6427,21 @@ async function writeArtifactBundle(files, options = {}) {
|
|
|
5968
6427
|
if (!changed.length) return { changed: [], unchanged };
|
|
5969
6428
|
const token = (0, import_node_crypto5.randomUUID)();
|
|
5970
6429
|
for (const [index, artifact] of changed.entries()) {
|
|
5971
|
-
const basename =
|
|
5972
|
-
artifact.stage =
|
|
5973
|
-
|
|
6430
|
+
const basename = import_node_path8.default.basename(artifact.destination);
|
|
6431
|
+
artifact.stage = import_node_path8.default.join(
|
|
6432
|
+
import_node_path8.default.dirname(artifact.destination),
|
|
5974
6433
|
`.${basename}.pixelkiln-stage-${token}-${index}`
|
|
5975
6434
|
);
|
|
5976
6435
|
if (artifact.existed) {
|
|
5977
|
-
artifact.backup =
|
|
5978
|
-
|
|
6436
|
+
artifact.backup = import_node_path8.default.join(
|
|
6437
|
+
import_node_path8.default.dirname(artifact.destination),
|
|
5979
6438
|
`.${basename}.pixelkiln-backup-${token}-${index}`
|
|
5980
6439
|
);
|
|
5981
6440
|
}
|
|
5982
6441
|
}
|
|
5983
|
-
const journal = options.recoveryFile ?
|
|
6442
|
+
const journal = options.recoveryFile ? import_node_path8.default.resolve(options.recoveryFile) : null;
|
|
5984
6443
|
if (journal) {
|
|
5985
|
-
await (0,
|
|
6444
|
+
await (0, import_promises5.mkdir)(import_node_path8.default.dirname(journal), { recursive: true });
|
|
5986
6445
|
const transaction = {
|
|
5987
6446
|
format: "pixelkiln-artifact-transaction",
|
|
5988
6447
|
version: 1,
|
|
@@ -5996,7 +6455,7 @@ async function writeArtifactBundle(files, options = {}) {
|
|
|
5996
6455
|
}))
|
|
5997
6456
|
};
|
|
5998
6457
|
try {
|
|
5999
|
-
await (0,
|
|
6458
|
+
await (0, import_promises5.writeFile)(journal, JSON.stringify(transaction, null, 2) + "\n", { flag: "wx" });
|
|
6000
6459
|
activeTransactions.add(journal);
|
|
6001
6460
|
} catch (error) {
|
|
6002
6461
|
throw new Error(
|
|
@@ -6007,9 +6466,9 @@ async function writeArtifactBundle(files, options = {}) {
|
|
|
6007
6466
|
}
|
|
6008
6467
|
try {
|
|
6009
6468
|
for (const [index, artifact] of changed.entries()) {
|
|
6010
|
-
await (0,
|
|
6469
|
+
await (0, import_promises5.mkdir)(import_node_path8.default.dirname(artifact.destination), { recursive: true });
|
|
6011
6470
|
await options.beforeStage?.(artifact.destination, index);
|
|
6012
|
-
await (0,
|
|
6471
|
+
await (0, import_promises5.writeFile)(artifact.stage, artifact.data, { flag: "wx" });
|
|
6013
6472
|
}
|
|
6014
6473
|
} catch (error) {
|
|
6015
6474
|
const cleanupErrors2 = await rollback(changed);
|
|
@@ -6022,17 +6481,17 @@ async function writeArtifactBundle(files, options = {}) {
|
|
|
6022
6481
|
try {
|
|
6023
6482
|
for (const artifact of changed) {
|
|
6024
6483
|
if (!artifact.existed) continue;
|
|
6025
|
-
await (0,
|
|
6484
|
+
await (0, import_promises5.rename)(artifact.destination, artifact.backup);
|
|
6026
6485
|
artifact.backedUp = true;
|
|
6027
6486
|
}
|
|
6028
6487
|
for (const [index, artifact] of changed.entries()) {
|
|
6029
6488
|
await options.beforePromote?.(artifact.destination, index);
|
|
6030
|
-
await (0,
|
|
6489
|
+
await (0, import_promises5.rename)(artifact.stage, artifact.destination);
|
|
6031
6490
|
artifact.stage = void 0;
|
|
6032
6491
|
artifact.promoted = true;
|
|
6033
6492
|
}
|
|
6034
6493
|
if (journal) {
|
|
6035
|
-
await (0,
|
|
6494
|
+
await (0, import_promises5.writeFile)(transactionMarker(journal), `${token}
|
|
6036
6495
|
`, { flag: "wx" });
|
|
6037
6496
|
durableCommit = true;
|
|
6038
6497
|
await options.afterCommit?.();
|
|
@@ -6074,69 +6533,6 @@ async function writeArtifactBundle(files, options = {}) {
|
|
|
6074
6533
|
};
|
|
6075
6534
|
}
|
|
6076
6535
|
|
|
6077
|
-
// src/palette.ts
|
|
6078
|
-
function normalizePalette(values) {
|
|
6079
|
-
const colors = /* @__PURE__ */ new Map();
|
|
6080
|
-
for (const value of values) {
|
|
6081
|
-
const rgb = parseHex(value);
|
|
6082
|
-
const hex2 = `#${rgb.r.toString(16).padStart(2, "0")}${rgb.g.toString(16).padStart(2, "0")}${rgb.b.toString(16).padStart(2, "0")}`;
|
|
6083
|
-
if (!colors.has(hex2)) colors.set(hex2, { hex: hex2, ...rgb });
|
|
6084
|
-
}
|
|
6085
|
-
if (colors.size < 2 || colors.size > 256) {
|
|
6086
|
-
throw new Error(`A palette must contain 2\u2013256 unique colors; got ${colors.size}.`);
|
|
6087
|
-
}
|
|
6088
|
-
return [...colors.values()];
|
|
6089
|
-
}
|
|
6090
|
-
function colorDistance(a, b) {
|
|
6091
|
-
const rmean = (a.r + b.r) / 2;
|
|
6092
|
-
const dr = a.r - b.r;
|
|
6093
|
-
const dg = a.g - b.g;
|
|
6094
|
-
const db = a.b - b.b;
|
|
6095
|
-
return Math.sqrt(
|
|
6096
|
-
(512 + rmean) * dr * dr / 256 + 4 * dg * dg + (767 - rmean) * db * db / 256
|
|
6097
|
-
);
|
|
6098
|
-
}
|
|
6099
|
-
function quantize(png, palette) {
|
|
6100
|
-
const colors = normalizePalette(palette);
|
|
6101
|
-
const pixels = Buffer.from(png.pixels);
|
|
6102
|
-
const nearestOf = /* @__PURE__ */ new Map();
|
|
6103
|
-
let changed = 0;
|
|
6104
|
-
for (let i = 0; i < pixels.length; i += 4) {
|
|
6105
|
-
const alpha = pixels[i + 3];
|
|
6106
|
-
if (alpha === 0) {
|
|
6107
|
-
if (pixels[i] || pixels[i + 1] || pixels[i + 2]) changed++;
|
|
6108
|
-
pixels[i] = 0;
|
|
6109
|
-
pixels[i + 1] = 0;
|
|
6110
|
-
pixels[i + 2] = 0;
|
|
6111
|
-
continue;
|
|
6112
|
-
}
|
|
6113
|
-
const key = pixels[i] << 16 | pixels[i + 1] << 8 | pixels[i + 2];
|
|
6114
|
-
let nearest = nearestOf.get(key);
|
|
6115
|
-
if (!nearest) {
|
|
6116
|
-
const pixel = { r: pixels[i], g: pixels[i + 1], b: pixels[i + 2] };
|
|
6117
|
-
nearest = colors[0];
|
|
6118
|
-
let distance = colorDistance(pixel, nearest);
|
|
6119
|
-
for (let j = 1; j < colors.length; j++) {
|
|
6120
|
-
const candidate = colors[j];
|
|
6121
|
-
const candidateDistance = colorDistance(pixel, candidate);
|
|
6122
|
-
if (candidateDistance < distance) {
|
|
6123
|
-
nearest = candidate;
|
|
6124
|
-
distance = candidateDistance;
|
|
6125
|
-
}
|
|
6126
|
-
}
|
|
6127
|
-
nearestOf.set(key, nearest);
|
|
6128
|
-
}
|
|
6129
|
-
if (pixels[i] !== nearest.r || pixels[i + 1] !== nearest.g || pixels[i + 2] !== nearest.b) changed++;
|
|
6130
|
-
pixels[i] = nearest.r;
|
|
6131
|
-
pixels[i + 1] = nearest.g;
|
|
6132
|
-
pixels[i + 2] = nearest.b;
|
|
6133
|
-
}
|
|
6134
|
-
return { png: { width: png.width, height: png.height, pixels }, changed };
|
|
6135
|
-
}
|
|
6136
|
-
function quantizeToPalette(png, palette) {
|
|
6137
|
-
return quantize(png, palette).png;
|
|
6138
|
-
}
|
|
6139
|
-
|
|
6140
6536
|
// src/pipeline/refine.ts
|
|
6141
6537
|
var execFileAsync = (0, import_node_util.promisify)(import_node_child_process2.execFile);
|
|
6142
6538
|
var PIXEL_ART_FIXER_MIN_SIDE = 16;
|
|
@@ -6336,16 +6732,16 @@ function sameFrameGrid(first, next) {
|
|
|
6336
6732
|
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;
|
|
6337
6733
|
}
|
|
6338
6734
|
async function refineAsset(options) {
|
|
6339
|
-
const source =
|
|
6340
|
-
const output =
|
|
6735
|
+
const source = import_node_path9.default.resolve(options.source);
|
|
6736
|
+
const output = import_node_path9.default.resolve(options.output);
|
|
6341
6737
|
if (source === output) throw new Error("Refinement source and output must be different files.");
|
|
6342
|
-
const record =
|
|
6738
|
+
const record = import_node_path9.default.resolve(qualityRecordPath(output));
|
|
6343
6739
|
const palette = normalizePalette(options.palette).map((color) => color.hex);
|
|
6344
6740
|
const minGridConfidence = requireConfidence(options.minGridConfidence ?? "high");
|
|
6345
6741
|
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.");
|
|
6346
6742
|
let sourcePng;
|
|
6347
6743
|
try {
|
|
6348
|
-
sourcePng = decodePng(await (0,
|
|
6744
|
+
sourcePng = decodePng(await (0, import_promises6.readFile)(source));
|
|
6349
6745
|
} catch (error) {
|
|
6350
6746
|
throw new Error(`Cannot read refinement source ${source}: ${message2(error)}`, { cause: error });
|
|
6351
6747
|
}
|
|
@@ -6362,8 +6758,8 @@ async function refineAsset(options) {
|
|
|
6362
6758
|
);
|
|
6363
6759
|
}
|
|
6364
6760
|
}
|
|
6365
|
-
const temp = await (0,
|
|
6366
|
-
const recovered =
|
|
6761
|
+
const temp = await (0, import_promises6.mkdtemp)(import_node_path9.default.join((0, import_node_os2.tmpdir)(), "pixelkiln-refine-"));
|
|
6762
|
+
const recovered = import_node_path9.default.join(temp, "native.png");
|
|
6367
6763
|
const fixerCommand = compatibilityCommand ?? options.fixerPython ?? process.env.PIXELKILN_PIXEL_FIXER_PYTHON ?? "python3";
|
|
6368
6764
|
const args = compatibilityCommand ? [...options.fixerArgsPrefix ?? [], source, "--extract", recovered] : ["-c", PYTHON_FIXER_BRIDGE, source, recovered];
|
|
6369
6765
|
try {
|
|
@@ -6384,7 +6780,7 @@ async function refineAsset(options) {
|
|
|
6384
6780
|
);
|
|
6385
6781
|
}
|
|
6386
6782
|
const detection = parseDetection(firstJsonObject(stdout));
|
|
6387
|
-
const native = decodePng(await (0,
|
|
6783
|
+
const native = decodePng(await (0, import_promises6.readFile)(recovered));
|
|
6388
6784
|
const quantized = quantizeToPalette(native, palette);
|
|
6389
6785
|
const png = encodeRgbaPng(quantized.width, quantized.height, quantized.pixels);
|
|
6390
6786
|
const audit = auditRefinement(
|
|
@@ -6434,7 +6830,7 @@ async function refineAsset(options) {
|
|
|
6434
6830
|
);
|
|
6435
6831
|
return { source, output, record, detection, palette, audit };
|
|
6436
6832
|
} finally {
|
|
6437
|
-
await (0,
|
|
6833
|
+
await (0, import_promises6.rm)(temp, { recursive: true, force: true });
|
|
6438
6834
|
}
|
|
6439
6835
|
}
|
|
6440
6836
|
async function refineFrameSet(options) {
|
|
@@ -6448,18 +6844,18 @@ async function refineFrameSet(options) {
|
|
|
6448
6844
|
if (roles.some((role) => !role.trim()) || new Set(roles).size !== roles.length) {
|
|
6449
6845
|
throw new Error("Frame-set roles must be non-empty and unique.");
|
|
6450
6846
|
}
|
|
6451
|
-
const output =
|
|
6452
|
-
const record =
|
|
6847
|
+
const output = import_node_path9.default.resolve(options.output);
|
|
6848
|
+
const record = import_node_path9.default.resolve(qualityRecordPath(output));
|
|
6453
6849
|
const palette = normalizeRefinementPalette(options.palette);
|
|
6454
6850
|
const finalOutputs = options.sources.map((source, index) => qualityFrameOutputPath(output, source.role, index));
|
|
6455
6851
|
if (new Set(finalOutputs).size !== finalOutputs.length) {
|
|
6456
6852
|
throw new Error("Frame-set roles resolve to duplicate output filenames.");
|
|
6457
6853
|
}
|
|
6458
|
-
const sourceSet = new Set(options.sources.map((source) =>
|
|
6854
|
+
const sourceSet = new Set(options.sources.map((source) => import_node_path9.default.resolve(source.path)));
|
|
6459
6855
|
if (finalOutputs.some((file) => sourceSet.has(file))) {
|
|
6460
6856
|
throw new Error("Frame-set refinement sources and outputs must be different files.");
|
|
6461
6857
|
}
|
|
6462
|
-
const temp = await (0,
|
|
6858
|
+
const temp = await (0, import_promises6.mkdtemp)(import_node_path9.default.join((0, import_node_os2.tmpdir)(), "pixelkiln-refine-frames-"));
|
|
6463
6859
|
try {
|
|
6464
6860
|
const frames = [];
|
|
6465
6861
|
const outputs = [];
|
|
@@ -6467,8 +6863,8 @@ async function refineFrameSet(options) {
|
|
|
6467
6863
|
const sourcePaths = [];
|
|
6468
6864
|
let firstOptions = null;
|
|
6469
6865
|
for (const [index, sourceEntry] of options.sources.entries()) {
|
|
6470
|
-
const source =
|
|
6471
|
-
const temporaryOutput =
|
|
6866
|
+
const source = import_node_path9.default.resolve(sourceEntry.path);
|
|
6867
|
+
const temporaryOutput = import_node_path9.default.join(temp, `${String(index).padStart(2, "0")}.png`);
|
|
6472
6868
|
const refined = await refineAsset({
|
|
6473
6869
|
source,
|
|
6474
6870
|
output: temporaryOutput,
|
|
@@ -6500,7 +6896,7 @@ async function refineFrameSet(options) {
|
|
|
6500
6896
|
});
|
|
6501
6897
|
outputs.push({
|
|
6502
6898
|
path: finalOutputs[index],
|
|
6503
|
-
data: await (0,
|
|
6899
|
+
data: await (0, import_promises6.readFile)(temporaryOutput)
|
|
6504
6900
|
});
|
|
6505
6901
|
}
|
|
6506
6902
|
const first = frames[0];
|
|
@@ -6543,23 +6939,23 @@ async function refineFrameSet(options) {
|
|
|
6543
6939
|
);
|
|
6544
6940
|
return {
|
|
6545
6941
|
sources: sourcePaths,
|
|
6546
|
-
outputs: outputs.map((item) =>
|
|
6942
|
+
outputs: outputs.map((item) => import_node_path9.default.resolve(item.path)),
|
|
6547
6943
|
record,
|
|
6548
6944
|
frames,
|
|
6549
6945
|
palette,
|
|
6550
6946
|
audit
|
|
6551
6947
|
};
|
|
6552
6948
|
} finally {
|
|
6553
|
-
await (0,
|
|
6949
|
+
await (0, import_promises6.rm)(temp, { recursive: true, force: true });
|
|
6554
6950
|
}
|
|
6555
6951
|
}
|
|
6556
6952
|
async function checkQualityRecord(recordPath) {
|
|
6557
|
-
const record =
|
|
6953
|
+
const record = import_node_path9.default.resolve(recordPath);
|
|
6558
6954
|
const manifest = await readArtifactBundleManifest(record);
|
|
6559
6955
|
const options = parseRecordOptions(manifest, record);
|
|
6560
6956
|
const verification = await verifyArtifactBundle(record);
|
|
6561
|
-
const sources = manifest.sources.map((source2) =>
|
|
6562
|
-
const outputs = manifest.outputs.map((output2) =>
|
|
6957
|
+
const sources = manifest.sources.map((source2) => import_node_path9.default.resolve(import_node_path9.default.dirname(record), source2.path));
|
|
6958
|
+
const outputs = manifest.outputs.map((output2) => import_node_path9.default.resolve(import_node_path9.default.dirname(record), output2.path));
|
|
6563
6959
|
const source = sources[0] ?? "";
|
|
6564
6960
|
const output = outputs[0] ?? "";
|
|
6565
6961
|
const expectedCount = options.frameSet?.count ?? 1;
|
|
@@ -6592,7 +6988,7 @@ async function checkQualityRecord(recordPath) {
|
|
|
6592
6988
|
async function approveQualityRecord(recordPath, approval) {
|
|
6593
6989
|
const reviewer = approval.reviewer.trim();
|
|
6594
6990
|
if (!reviewer) throw new Error("A human reviewer name is required.");
|
|
6595
|
-
const record =
|
|
6991
|
+
const record = import_node_path9.default.resolve(recordPath);
|
|
6596
6992
|
const manifest = await readArtifactBundleManifest(record);
|
|
6597
6993
|
const options = parseRecordOptions(manifest, record);
|
|
6598
6994
|
const verification = await verifyArtifactBundle(record);
|
|
@@ -6613,14 +7009,14 @@ async function approveQualityRecord(recordPath, approval) {
|
|
|
6613
7009
|
`A refinement record must contain exactly ${expectedCount} source(s) and output(s).`
|
|
6614
7010
|
);
|
|
6615
7011
|
}
|
|
6616
|
-
const root =
|
|
7012
|
+
const root = import_node_path9.default.dirname(record);
|
|
6617
7013
|
const outputs = await Promise.all(manifest.outputs.map(async (entry) => {
|
|
6618
|
-
const outputPath =
|
|
6619
|
-
return { path: outputPath, data: await (0,
|
|
7014
|
+
const outputPath = import_node_path9.default.resolve(root, entry.path);
|
|
7015
|
+
return { path: outputPath, data: await (0, import_promises6.readFile)(outputPath) };
|
|
6620
7016
|
}));
|
|
6621
7017
|
const sources = manifest.sources.map((source) => ({
|
|
6622
7018
|
...source,
|
|
6623
|
-
path:
|
|
7019
|
+
path: import_node_path9.default.resolve(root, source.path)
|
|
6624
7020
|
}));
|
|
6625
7021
|
const approved = {
|
|
6626
7022
|
...options,
|
|
@@ -6652,7 +7048,7 @@ async function qualitySource(spec, lock) {
|
|
|
6652
7048
|
if (spec.generator === "frames") {
|
|
6653
7049
|
return { items: null, reason: "frame-set quality requires ordered downloaded provider outputs" };
|
|
6654
7050
|
}
|
|
6655
|
-
const source =
|
|
7051
|
+
const source = import_node_path10.default.resolve(spec.root, spec.source);
|
|
6656
7052
|
return (0, import_node_fs7.existsSync)(source) ? { items: [{ role: "source", path: source }] } : { items: null, reason: `declared source is missing: ${spec.source}` };
|
|
6657
7053
|
}
|
|
6658
7054
|
const entry = lock.entries[lockKey(spec.styleId, spec.assetId)];
|
|
@@ -6719,7 +7115,7 @@ async function inspectQualityProfile(spec, lock) {
|
|
|
6719
7115
|
const expectedRevision = spec.quality.fixerRevision ?? PIXEL_ART_FIXER_REVISION;
|
|
6720
7116
|
const expectedTransparency = spec.quality.minTransparency ?? null;
|
|
6721
7117
|
const expectedFrameSet = spec.generator === "frames" ? { fps: spec.quality.fps ?? 12, count: source.items.length, roles: source.items.map((item) => item.role) } : null;
|
|
6722
|
-
const contractChanged = JSON.stringify(check.sources.map((item) =>
|
|
7118
|
+
const contractChanged = JSON.stringify(check.sources.map((item) => import_node_path10.default.resolve(item))) !== JSON.stringify(sourcePaths.map((item) => import_node_path10.default.resolve(item))) || JSON.stringify(check.outputs.map((item) => import_node_path10.default.resolve(item))) !== JSON.stringify(expectedOutputs.map((item) => import_node_path10.default.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);
|
|
6723
7119
|
if (contractChanged) {
|
|
6724
7120
|
return {
|
|
6725
7121
|
key,
|
|
@@ -6881,43 +7277,21 @@ async function requireApprovedQualitySources(specs, lock, assetIds, outputRoles)
|
|
|
6881
7277
|
return sources;
|
|
6882
7278
|
}
|
|
6883
7279
|
|
|
6884
|
-
// src/pipeline/postprocess.ts
|
|
6885
|
-
function postprocessFor(spec) {
|
|
6886
|
-
if (!spec.enforcePalette || spec.palette.length < 2) return void 0;
|
|
6887
|
-
return {
|
|
6888
|
-
palette: {
|
|
6889
|
-
colors: normalizePalette(spec.palette).map((color) => color.hex),
|
|
6890
|
-
dither: "none",
|
|
6891
|
-
distance: "redmean"
|
|
6892
|
-
}
|
|
6893
|
-
};
|
|
6894
|
-
}
|
|
6895
|
-
function samePostprocess(a, b) {
|
|
6896
|
-
const pa = a?.palette;
|
|
6897
|
-
const pb = b?.palette;
|
|
6898
|
-
if (!pa && !pb) return true;
|
|
6899
|
-
if (!pa || !pb) return false;
|
|
6900
|
-
return pa.dither === pb.dither && pa.distance === pb.distance && pa.colors.length === pb.colors.length && pa.colors.every((color, i) => color === pb.colors[i]);
|
|
6901
|
-
}
|
|
6902
|
-
function postprocessCurrent(entry, spec) {
|
|
6903
|
-
return samePostprocess(entry.postprocess, postprocessFor(spec));
|
|
6904
|
-
}
|
|
6905
|
-
function applyPostprocess(bytes, mediaType, record) {
|
|
6906
|
-
if (!record?.palette || mediaType !== MediaType.PNG) return { bytes, changed: false };
|
|
6907
|
-
const png = decodePng(bytes);
|
|
6908
|
-
const { png: snapped, changed } = quantize(png, record.palette.colors);
|
|
6909
|
-
if (!changed) return { bytes, changed: false };
|
|
6910
|
-
return { bytes: encodeRgbaPng(snapped.width, snapped.height, snapped.pixels), changed: true };
|
|
6911
|
-
}
|
|
6912
|
-
|
|
6913
7280
|
// src/pipeline/revision.ts
|
|
6914
7281
|
var import_node_fs8 = require("fs");
|
|
6915
|
-
var
|
|
7282
|
+
var import_node_path11 = __toESM(require("path"), 1);
|
|
6916
7283
|
async function inspectRevisionReadiness(spec, lock) {
|
|
7284
|
+
if (spec.mirror) return inspectMirror(spec, lock, /* @__PURE__ */ new Set());
|
|
6917
7285
|
if (spec.character?.parentSpec) return inspectCharacterParent(spec, lock, /* @__PURE__ */ new Set());
|
|
6918
7286
|
if (!spec.revision) return null;
|
|
6919
7287
|
return inspectRevision(spec, lock, /* @__PURE__ */ new Set());
|
|
6920
7288
|
}
|
|
7289
|
+
async function inspectMirror(spec, lock, seen) {
|
|
7290
|
+
const source = spec.mirror.sourceSpec;
|
|
7291
|
+
const label = `mirror source ${spec.styleId}/${spec.mirror.sourceAssetId}`;
|
|
7292
|
+
const dependency = await inspectParent(source, lock, seen);
|
|
7293
|
+
return dependency.ready ? { ready: true, reason: `${label} is current` } : { ready: false, reason: `${label} is not ready: ${dependency.reason}` };
|
|
7294
|
+
}
|
|
6921
7295
|
async function inspectCharacterParent(spec, lock, seen) {
|
|
6922
7296
|
const character = spec.character;
|
|
6923
7297
|
const parentSpec = character.parentSpec;
|
|
@@ -6971,6 +7345,10 @@ async function inspectParent(spec, lock, seen) {
|
|
|
6971
7345
|
if (seen.has(key)) return { ready: false, reason: `revision dependency cycle at ${key}` };
|
|
6972
7346
|
const nextSeen = new Set(seen);
|
|
6973
7347
|
nextSeen.add(key);
|
|
7348
|
+
if (spec.mirror) {
|
|
7349
|
+
const inputs = await inspectMirror(spec, lock, nextSeen);
|
|
7350
|
+
if (!inputs.ready) return inputs;
|
|
7351
|
+
}
|
|
6974
7352
|
if (spec.revision) {
|
|
6975
7353
|
const inputs = await inspectRevision(spec, lock, nextSeen);
|
|
6976
7354
|
if (!inputs.ready) return inputs;
|
|
@@ -6987,7 +7365,7 @@ async function inspectParent(spec, lock, seen) {
|
|
|
6987
7365
|
};
|
|
6988
7366
|
}
|
|
6989
7367
|
if (spec.source) {
|
|
6990
|
-
const source =
|
|
7368
|
+
const source = import_node_path11.default.resolve(spec.root, spec.source);
|
|
6991
7369
|
return (0, import_node_fs8.existsSync)(source) ? { ready: true, reason: "declared source is present" } : { ready: false, reason: `declared source is missing: ${spec.source}` };
|
|
6992
7370
|
}
|
|
6993
7371
|
const entry = lock.entries[key];
|
|
@@ -7057,7 +7435,7 @@ async function buildPlan(specs, lock, opts = {}) {
|
|
|
7057
7435
|
state = "missing";
|
|
7058
7436
|
reason = "--force";
|
|
7059
7437
|
} else if (!entry && spec.source && spec.generator !== "frames") {
|
|
7060
|
-
if ((0, import_node_fs9.existsSync)(
|
|
7438
|
+
if ((0, import_node_fs9.existsSync)(import_node_path12.default.resolve(spec.root, spec.source))) {
|
|
7061
7439
|
state = "ok";
|
|
7062
7440
|
reason = `placed from ${spec.source}; not generated`;
|
|
7063
7441
|
} else {
|
|
@@ -7070,11 +7448,17 @@ async function buildPlan(specs, lock, opts = {}) {
|
|
|
7070
7448
|
reason = "file on disk, no provenance recorded";
|
|
7071
7449
|
} else {
|
|
7072
7450
|
state = "missing";
|
|
7073
|
-
reason = "not in lockfile";
|
|
7451
|
+
reason = spec.mirror ? `not made yet; flipped from ${spec.mirror.sourceAssetId} (no generation cost)` : "not in lockfile";
|
|
7074
7452
|
}
|
|
7075
7453
|
} else if (entry.specHash !== spec.specHash) {
|
|
7076
7454
|
state = "stale";
|
|
7077
7455
|
reason = "prompt, size, or style changed";
|
|
7456
|
+
} else if (spec.mirror && !mirrorCurrent(entry, lock.entries[lockKey(spec.styleId, spec.mirror.sourceAssetId)])) {
|
|
7457
|
+
state = "stale";
|
|
7458
|
+
reason = `mirror source ${spec.mirror.sourceAssetId} changed; flip it again (no generation cost)`;
|
|
7459
|
+
} else if (spec.mirror && entry.status === "downloaded" && entry.outputs.some((_output, index) => !(0, import_node_fs9.existsSync)(currentEntryOutputPath(entry, spec, index)))) {
|
|
7460
|
+
state = "stale";
|
|
7461
|
+
reason = "mirror output missing on disk; flip it again (no generation cost)";
|
|
7078
7462
|
} else if (entry.status === "download-failed") {
|
|
7079
7463
|
state = "recoverable";
|
|
7080
7464
|
const force = entry.error?.includes("pass --force") ? " --force" : "";
|
|
@@ -7103,6 +7487,9 @@ async function buildPlan(specs, lock, opts = {}) {
|
|
|
7103
7487
|
} else if (await anyOutputModified(entry, spec)) {
|
|
7104
7488
|
state = "orphaned";
|
|
7105
7489
|
reason = "output modified since download";
|
|
7490
|
+
} else if (spec.mirror && !postprocessCurrent(entry, spec)) {
|
|
7491
|
+
state = "stale";
|
|
7492
|
+
reason = "palette enforcement changed; flip the source again once it is refreshed (no generation cost)";
|
|
7106
7493
|
} else if (!postprocessCurrent(entry, spec)) {
|
|
7107
7494
|
state = "recoverable";
|
|
7108
7495
|
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)";
|
|
@@ -7114,15 +7501,19 @@ async function buildPlan(specs, lock, opts = {}) {
|
|
|
7114
7501
|
items.push({ spec, key, state, reason, ...quality ? { quality } : {} });
|
|
7115
7502
|
}
|
|
7116
7503
|
const isActionable = (i) => i.state === "missing" || i.state === "stale" || i.state === "failed";
|
|
7117
|
-
const
|
|
7504
|
+
const stateOf = new Map(items.map((i) => [i.key, i.state]));
|
|
7118
7505
|
for (const item of items) {
|
|
7119
7506
|
if (!isActionable(item)) continue;
|
|
7120
|
-
const parent = item.spec.revision?.sourceSpec ?? item.spec.character?.parentSpec;
|
|
7507
|
+
const parent = item.spec.mirror?.sourceSpec ?? item.spec.revision?.sourceSpec ?? item.spec.character?.parentSpec;
|
|
7121
7508
|
if (!parent) continue;
|
|
7122
7509
|
const parentKey = lockKey(parent.styleId, parent.assetId);
|
|
7123
|
-
|
|
7510
|
+
const parentState = stateOf.get(parentKey);
|
|
7511
|
+
if (parentState && isActionable({ state: parentState })) {
|
|
7124
7512
|
item.state = "blocked";
|
|
7125
7513
|
item.reason = `parent ${parentKey} is being generated in this run; this follows in the next wave`;
|
|
7514
|
+
} else if (item.spec.mirror && (parentState === "recoverable" || parentState === "in-flight")) {
|
|
7515
|
+
item.state = "blocked";
|
|
7516
|
+
item.reason = `mirror source ${parentKey} is ${parentState}; this follows in the next wave`;
|
|
7126
7517
|
}
|
|
7127
7518
|
}
|
|
7128
7519
|
const actionable = items.filter(isActionable);
|
|
@@ -7180,13 +7571,13 @@ function summarize(plan) {
|
|
|
7180
7571
|
|
|
7181
7572
|
// src/project.ts
|
|
7182
7573
|
async function openProject(manifestPath = "pixelkiln.manifest.json", opts = {}) {
|
|
7183
|
-
const absolute =
|
|
7184
|
-
const root =
|
|
7574
|
+
const absolute = import_node_path13.default.resolve(manifestPath);
|
|
7575
|
+
const root = import_node_path13.default.dirname(absolute);
|
|
7185
7576
|
if (opts.env !== false) {
|
|
7186
7577
|
loadEnvFiles(root);
|
|
7187
|
-
if (
|
|
7578
|
+
if (import_node_path13.default.resolve(process.cwd()) !== root) loadEnvFiles(process.cwd());
|
|
7188
7579
|
}
|
|
7189
|
-
const lockPath =
|
|
7580
|
+
const lockPath = import_node_path13.default.resolve(opts.lockPath ?? import_node_path13.default.join(root, "pixelkiln.lock.json"));
|
|
7190
7581
|
const loaded = await loadManifest(absolute);
|
|
7191
7582
|
const specs = await resolveSpecs(loaded, { styles: opts.styles ?? [], assets: opts.assets ?? [] });
|
|
7192
7583
|
const lock = await loadLock(lockPath);
|
|
@@ -7253,9 +7644,9 @@ function shouldPersistSourceUrl(value) {
|
|
|
7253
7644
|
}
|
|
7254
7645
|
|
|
7255
7646
|
// src/recipes.ts
|
|
7256
|
-
var
|
|
7647
|
+
var import_node_path14 = __toESM(require("path"), 1);
|
|
7257
7648
|
var import_node_fs10 = require("fs");
|
|
7258
|
-
var
|
|
7649
|
+
var import_promises7 = require("fs/promises");
|
|
7259
7650
|
var import_node_url = require("url");
|
|
7260
7651
|
var import_zod3 = require("zod");
|
|
7261
7652
|
var SHA256_RE = /^[0-9a-f]{64}$/;
|
|
@@ -7264,7 +7655,7 @@ var VERSION_RE = /^\d+\.\d+\.\d+$/;
|
|
|
7264
7655
|
var RECIPE_FILE = "pixelkiln.recipe.json";
|
|
7265
7656
|
var RECIPE_DIR_TOKEN = "{{recipeDir}}";
|
|
7266
7657
|
var PortablePathSchema = import_zod3.z.string().min(1).refine((value) => {
|
|
7267
|
-
if (value.includes("\\") ||
|
|
7658
|
+
if (value.includes("\\") || import_node_path14.default.posix.isAbsolute(value)) return false;
|
|
7268
7659
|
const parts = value.split("/");
|
|
7269
7660
|
return !parts.includes("") && !parts.includes(".") && !parts.includes("..");
|
|
7270
7661
|
}, "expected a portable relative path without '.', '..', or backslashes");
|
|
@@ -7440,10 +7831,10 @@ function message3(error) {
|
|
|
7440
7831
|
return error instanceof Error ? error.message : String(error);
|
|
7441
7832
|
}
|
|
7442
7833
|
async function readRecipeFile(recipePath, bundled) {
|
|
7443
|
-
const absolute =
|
|
7834
|
+
const absolute = import_node_path14.default.resolve(recipePath);
|
|
7444
7835
|
let raw;
|
|
7445
7836
|
try {
|
|
7446
|
-
raw = JSON.parse(await (0,
|
|
7837
|
+
raw = JSON.parse(await (0, import_promises7.readFile)(absolute, "utf8"));
|
|
7447
7838
|
} catch (error) {
|
|
7448
7839
|
throw new Error(`Could not read recipe ${absolute}: ${message3(error)}`, { cause: error });
|
|
7449
7840
|
}
|
|
@@ -7454,13 +7845,13 @@ async function readRecipeFile(recipePath, bundled) {
|
|
|
7454
7845
|
` + parsed.error.issues.map((issue) => ` ${issue.path.join(".") || "$"}: ${issue.message}`).join("\n")
|
|
7455
7846
|
);
|
|
7456
7847
|
}
|
|
7457
|
-
return { recipe: parsed.data, authored: raw, path: absolute, dir:
|
|
7848
|
+
return { recipe: parsed.data, authored: raw, path: absolute, dir: import_node_path14.default.dirname(absolute), bundled };
|
|
7458
7849
|
}
|
|
7459
7850
|
async function walkRecipeFiles(root) {
|
|
7460
7851
|
if (!(0, import_node_fs10.existsSync)(root)) return [];
|
|
7461
7852
|
const out = [];
|
|
7462
|
-
for (const entry of await (0,
|
|
7463
|
-
const resolved =
|
|
7853
|
+
for (const entry of await (0, import_promises7.readdir)(root, { withFileTypes: true })) {
|
|
7854
|
+
const resolved = import_node_path14.default.join(root, entry.name);
|
|
7464
7855
|
if (entry.isDirectory()) out.push(...await walkRecipeFiles(resolved));
|
|
7465
7856
|
else if (entry.isFile() && entry.name === RECIPE_FILE) out.push(resolved);
|
|
7466
7857
|
}
|
|
@@ -7471,7 +7862,7 @@ function bundledRecipeRoot() {
|
|
|
7471
7862
|
}
|
|
7472
7863
|
async function listBundledRecipes(root = bundledRecipeRoot()) {
|
|
7473
7864
|
const recipes = await Promise.all(
|
|
7474
|
-
(await walkRecipeFiles(
|
|
7865
|
+
(await walkRecipeFiles(import_node_path14.default.resolve(root))).map((file) => readRecipeFile(file, true))
|
|
7475
7866
|
);
|
|
7476
7867
|
return recipes.sort(
|
|
7477
7868
|
(a, b) => a.recipe.id.localeCompare(b.recipe.id) || compareVersions(b.recipe.version, a.recipe.version)
|
|
@@ -7494,9 +7885,9 @@ function parseSelector(selector) {
|
|
|
7494
7885
|
return { id: match[1], version: match[2] };
|
|
7495
7886
|
}
|
|
7496
7887
|
async function resolveRecipe(target, bundledRoot = bundledRecipeRoot()) {
|
|
7497
|
-
const local =
|
|
7888
|
+
const local = import_node_path14.default.resolve(target);
|
|
7498
7889
|
if ((0, import_node_fs10.existsSync)(local)) {
|
|
7499
|
-
const recipePath =
|
|
7890
|
+
const recipePath = import_node_path14.default.basename(local) === RECIPE_FILE ? local : import_node_path14.default.join(local, RECIPE_FILE);
|
|
7500
7891
|
return readRecipeFile(recipePath, false);
|
|
7501
7892
|
}
|
|
7502
7893
|
if (target.startsWith(".") || target.startsWith("/") || target.includes("\\")) {
|
|
@@ -7510,7 +7901,7 @@ async function resolveRecipe(target, bundledRoot = bundledRecipeRoot()) {
|
|
|
7510
7901
|
return matches[0];
|
|
7511
7902
|
}
|
|
7512
7903
|
async function verifyFile(root, file) {
|
|
7513
|
-
const absolute =
|
|
7904
|
+
const absolute = import_node_path14.default.resolve(root, ...file.path.split("/"));
|
|
7514
7905
|
if (!(0, import_node_fs10.existsSync)(absolute)) {
|
|
7515
7906
|
return { path: file.path, expectedSha256: file.sha256, actualSha256: null, status: "missing" };
|
|
7516
7907
|
}
|
|
@@ -7526,7 +7917,7 @@ async function verifyRecipe(target, options = {}) {
|
|
|
7526
7917
|
const loaded = await resolveRecipe(target, options.bundledRoot);
|
|
7527
7918
|
const actualIntegrity = recipeDigest(loaded.authored);
|
|
7528
7919
|
const files = await Promise.all(loaded.recipe.files.map((file) => verifyFile(loaded.dir, file)));
|
|
7529
|
-
const modelRoot = options.modelRoot ?
|
|
7920
|
+
const modelRoot = options.modelRoot ? import_node_path14.default.resolve(options.modelRoot) : null;
|
|
7530
7921
|
const models = modelRoot ? await Promise.all(loaded.recipe.models.map(async (model) => ({
|
|
7531
7922
|
...await verifyFile(modelRoot, model),
|
|
7532
7923
|
source: model.source,
|
|
@@ -7560,7 +7951,7 @@ async function verifyRecipe(target, options = {}) {
|
|
|
7560
7951
|
};
|
|
7561
7952
|
}
|
|
7562
7953
|
function renderStyle(style, destination, cwd) {
|
|
7563
|
-
const portableDir =
|
|
7954
|
+
const portableDir = import_node_path14.default.relative(cwd, destination).split(import_node_path14.default.sep).join("/") || ".";
|
|
7564
7955
|
const replace = (value) => {
|
|
7565
7956
|
if (typeof value === "string") return value.replaceAll(RECIPE_DIR_TOKEN, portableDir);
|
|
7566
7957
|
if (Array.isArray(value)) return value.map(replace);
|
|
@@ -7572,25 +7963,25 @@ function renderStyle(style, destination, cwd) {
|
|
|
7572
7963
|
return replace(style);
|
|
7573
7964
|
}
|
|
7574
7965
|
async function installRecipe(target, options = {}) {
|
|
7575
|
-
const cwd =
|
|
7966
|
+
const cwd = import_node_path14.default.resolve(options.cwd ?? process.cwd());
|
|
7576
7967
|
const loaded = await resolveRecipe(target, options.bundledRoot);
|
|
7577
7968
|
const verification = await verifyRecipe(loaded.path);
|
|
7578
7969
|
if (!verification.ok) {
|
|
7579
7970
|
throw new Error(`Recipe ${loaded.recipe.id}@${loaded.recipe.version} failed integrity verification.`);
|
|
7580
7971
|
}
|
|
7581
|
-
const destination = options.out ?
|
|
7972
|
+
const destination = options.out ? import_node_path14.default.resolve(cwd, options.out) : import_node_path14.default.join(cwd, "pixelkiln-recipes", ...loaded.recipe.id.split("/"), loaded.recipe.version);
|
|
7582
7973
|
const sources = [
|
|
7583
7974
|
{ path: RECIPE_FILE, source: loaded.path },
|
|
7584
|
-
...loaded.recipe.files.map((file) => ({ path: file.path, source:
|
|
7975
|
+
...loaded.recipe.files.map((file) => ({ path: file.path, source: import_node_path14.default.join(loaded.dir, ...file.path.split("/")) }))
|
|
7585
7976
|
];
|
|
7586
7977
|
const files = await Promise.all(sources.map(async (file) => ({
|
|
7587
|
-
path:
|
|
7588
|
-
data: await (0,
|
|
7978
|
+
path: import_node_path14.default.join(destination, ...file.path.split("/")),
|
|
7979
|
+
data: await (0, import_promises7.readFile)(file.source)
|
|
7589
7980
|
})));
|
|
7590
7981
|
if (!options.force) {
|
|
7591
7982
|
for (const file of files) {
|
|
7592
7983
|
if (!(0, import_node_fs10.existsSync)(file.path)) continue;
|
|
7593
|
-
const current = await (0,
|
|
7984
|
+
const current = await (0, import_promises7.readFile)(file.path);
|
|
7594
7985
|
if (!current.equals(file.data)) {
|
|
7595
7986
|
throw new OverwriteRefusedError(
|
|
7596
7987
|
`Recipe destination has local changes: ${file.path}. Choose another --out or pass --force to replace declared recipe files.`,
|
|
@@ -7614,9 +8005,9 @@ async function installRecipe(target, options = {}) {
|
|
|
7614
8005
|
}
|
|
7615
8006
|
|
|
7616
8007
|
// src/pipeline/audit.ts
|
|
7617
|
-
var
|
|
8008
|
+
var import_promises8 = require("fs/promises");
|
|
7618
8009
|
var import_node_fs11 = require("fs");
|
|
7619
|
-
var
|
|
8010
|
+
var import_node_path15 = __toESM(require("path"), 1);
|
|
7620
8011
|
function paletteDistance(asset, reference) {
|
|
7621
8012
|
if (!asset.length || !reference.length) return 0;
|
|
7622
8013
|
let total = 0;
|
|
@@ -7657,7 +8048,7 @@ async function auditStyle(loaded, specs, styleId, lock) {
|
|
|
7657
8048
|
continue;
|
|
7658
8049
|
}
|
|
7659
8050
|
try {
|
|
7660
|
-
const png = decodePng(await (0,
|
|
8051
|
+
const png = decodePng(await (0, import_promises8.readFile)(output.absolutePath));
|
|
7661
8052
|
const palette = extractPalette(png, 12);
|
|
7662
8053
|
assets.push({
|
|
7663
8054
|
assetId: spec.assetId,
|
|
@@ -7681,10 +8072,10 @@ async function auditStyle(loaded, specs, styleId, lock) {
|
|
|
7681
8072
|
let referenceFromStyleImages = false;
|
|
7682
8073
|
const refPalettes = [];
|
|
7683
8074
|
for (const img of style.styleImages) {
|
|
7684
|
-
const abs =
|
|
8075
|
+
const abs = import_node_path15.default.resolve(loaded.root, img.path);
|
|
7685
8076
|
if (!(0, import_node_fs11.existsSync)(abs)) continue;
|
|
7686
8077
|
try {
|
|
7687
|
-
refPalettes.push(extractPalette(decodePng(await (0,
|
|
8078
|
+
refPalettes.push(extractPalette(decodePng(await (0, import_promises8.readFile)(abs)), 12));
|
|
7688
8079
|
} catch {
|
|
7689
8080
|
}
|
|
7690
8081
|
}
|
|
@@ -7760,15 +8151,15 @@ function hex(c) {
|
|
|
7760
8151
|
}
|
|
7761
8152
|
|
|
7762
8153
|
// src/pipeline/quality-regression.ts
|
|
7763
|
-
var
|
|
8154
|
+
var import_node_path16 = __toESM(require("path"), 1);
|
|
7764
8155
|
var import_node_fs12 = require("fs");
|
|
7765
|
-
var
|
|
8156
|
+
var import_promises9 = require("fs/promises");
|
|
7766
8157
|
var import_zod4 = require("zod");
|
|
7767
8158
|
var SHA256_RE2 = /^[0-9a-f]{64}$/;
|
|
7768
8159
|
var HEX_RE = /^#[0-9a-f]{6}$/;
|
|
7769
8160
|
var MAX_REDMEAN_DISTANCE = 765;
|
|
7770
8161
|
var QualityPathSchema = import_zod4.z.string().min(1).refine(
|
|
7771
|
-
(value) => !
|
|
8162
|
+
(value) => !import_node_path16.default.posix.isAbsolute(value) && !value.includes("\\"),
|
|
7772
8163
|
"expected a portable relative path"
|
|
7773
8164
|
);
|
|
7774
8165
|
var DEFAULT_QUALITY_TOLERANCES = {
|
|
@@ -7915,10 +8306,10 @@ function measureDecodedImageQuality(png, digest2) {
|
|
|
7915
8306
|
};
|
|
7916
8307
|
}
|
|
7917
8308
|
async function measureImageQuality(file) {
|
|
7918
|
-
const absolute =
|
|
8309
|
+
const absolute = import_node_path16.default.resolve(file);
|
|
7919
8310
|
let bytes;
|
|
7920
8311
|
try {
|
|
7921
|
-
bytes = await (0,
|
|
8312
|
+
bytes = await (0, import_promises9.readFile)(absolute);
|
|
7922
8313
|
} catch (error) {
|
|
7923
8314
|
throw new Error(`Cannot read quality image ${absolute}: ${message4(error)}`, { cause: error });
|
|
7924
8315
|
}
|
|
@@ -7937,11 +8328,11 @@ function resolveQualityInputs(raw, inputsFilePath) {
|
|
|
7937
8328
|
}
|
|
7938
8329
|
const ids = parsed.data.map((entry) => entry.id);
|
|
7939
8330
|
if (new Set(ids).size !== ids.length) throw new Error("--inputs contains a duplicate quality case id.");
|
|
7940
|
-
const root =
|
|
8331
|
+
const root = import_node_path16.default.dirname(import_node_path16.default.resolve(inputsFilePath));
|
|
7941
8332
|
return parsed.data.map((entry) => ({
|
|
7942
8333
|
id: entry.id,
|
|
7943
|
-
file:
|
|
7944
|
-
...entry.record ? { record:
|
|
8334
|
+
file: import_node_path16.default.resolve(root, entry.path),
|
|
8335
|
+
...entry.record ? { record: import_node_path16.default.resolve(root, entry.record) } : {},
|
|
7945
8336
|
tolerances: QualityToleranceSchema.parse({
|
|
7946
8337
|
...DEFAULT_QUALITY_TOLERANCES,
|
|
7947
8338
|
...entry.tolerances
|
|
@@ -7949,7 +8340,7 @@ function resolveQualityInputs(raw, inputsFilePath) {
|
|
|
7949
8340
|
}));
|
|
7950
8341
|
}
|
|
7951
8342
|
function portableRelative2(from, to) {
|
|
7952
|
-
return
|
|
8343
|
+
return import_node_path16.default.relative(from, import_node_path16.default.resolve(to)).split(import_node_path16.default.sep).join("/") || ".";
|
|
7953
8344
|
}
|
|
7954
8345
|
async function verifyRecordForImage(recordPath, imagePath) {
|
|
7955
8346
|
const verification = await verifyArtifactBundle(recordPath);
|
|
@@ -7964,20 +8355,20 @@ async function verifyRecordForImage(recordPath, imagePath) {
|
|
|
7964
8355
|
if (record.kind !== "refine" || options === null || typeof options !== "object" || options.schema !== "pixelkiln-quality") {
|
|
7965
8356
|
throw new Error(`Quality record ${recordPath} is not a PixelKiln refinement record.`);
|
|
7966
8357
|
}
|
|
7967
|
-
const outputs = record.outputs.map((output) =>
|
|
7968
|
-
if (!outputs.includes(
|
|
8358
|
+
const outputs = record.outputs.map((output) => import_node_path16.default.resolve(import_node_path16.default.dirname(recordPath), output.path));
|
|
8359
|
+
if (!outputs.includes(import_node_path16.default.resolve(imagePath))) {
|
|
7969
8360
|
throw new Error(`Quality record ${recordPath} does not own ${imagePath}.`);
|
|
7970
8361
|
}
|
|
7971
8362
|
}
|
|
7972
8363
|
async function snapshotQualityBaseline(inputs, baselinePath, options = {}) {
|
|
7973
8364
|
if (!inputs.length) throw new Error("Cannot snapshot an empty quality baseline.");
|
|
7974
|
-
const absolute =
|
|
8365
|
+
const absolute = import_node_path16.default.resolve(baselinePath);
|
|
7975
8366
|
for (const input of inputs) {
|
|
7976
|
-
if (
|
|
8367
|
+
if (import_node_path16.default.resolve(input.file) === absolute || input.record && import_node_path16.default.resolve(input.record) === absolute) {
|
|
7977
8368
|
throw new Error(`Quality baseline output would overwrite an input: ${absolute}.`);
|
|
7978
8369
|
}
|
|
7979
8370
|
}
|
|
7980
|
-
const root =
|
|
8371
|
+
const root = import_node_path16.default.dirname(absolute);
|
|
7981
8372
|
const cases = [];
|
|
7982
8373
|
for (const input of [...inputs].sort((a, b) => a.id.localeCompare(b.id))) {
|
|
7983
8374
|
const expected = await measureImageQuality(input.file);
|
|
@@ -8005,7 +8396,7 @@ async function snapshotQualityBaseline(inputs, baselinePath, options = {}) {
|
|
|
8005
8396
|
});
|
|
8006
8397
|
const data = Buffer.from(JSON.stringify(baseline, null, 2) + "\n");
|
|
8007
8398
|
if ((0, import_node_fs12.existsSync)(absolute) && !options.force) {
|
|
8008
|
-
const current = await (0,
|
|
8399
|
+
const current = await (0, import_promises9.readFile)(absolute);
|
|
8009
8400
|
if (!current.equals(data)) {
|
|
8010
8401
|
throw new OverwriteRefusedError(
|
|
8011
8402
|
`Quality baseline already exists with different content: ${absolute}. Pass --force to replace it.`,
|
|
@@ -8017,10 +8408,10 @@ async function snapshotQualityBaseline(inputs, baselinePath, options = {}) {
|
|
|
8017
8408
|
return { baseline, path: absolute, changed: result.changed.length > 0 };
|
|
8018
8409
|
}
|
|
8019
8410
|
async function readQualityBaseline(baselinePath) {
|
|
8020
|
-
const absolute =
|
|
8411
|
+
const absolute = import_node_path16.default.resolve(baselinePath);
|
|
8021
8412
|
let raw;
|
|
8022
8413
|
try {
|
|
8023
|
-
raw = JSON.parse(await (0,
|
|
8414
|
+
raw = JSON.parse(await (0, import_promises9.readFile)(absolute, "utf8"));
|
|
8024
8415
|
} catch (error) {
|
|
8025
8416
|
throw new Error(`Could not read quality baseline ${absolute}: ${message4(error)}`, { cause: error });
|
|
8026
8417
|
}
|
|
@@ -8090,12 +8481,12 @@ function regressionViolations(expected, actual, tolerances) {
|
|
|
8090
8481
|
return { violations, warnings };
|
|
8091
8482
|
}
|
|
8092
8483
|
async function checkQualityBaseline(baselinePath) {
|
|
8093
|
-
const absolute =
|
|
8484
|
+
const absolute = import_node_path16.default.resolve(baselinePath);
|
|
8094
8485
|
const baseline = await readQualityBaseline(absolute);
|
|
8095
|
-
const root =
|
|
8486
|
+
const root = import_node_path16.default.dirname(absolute);
|
|
8096
8487
|
const cases = [];
|
|
8097
8488
|
for (const entry of baseline.cases) {
|
|
8098
|
-
const file =
|
|
8489
|
+
const file = import_node_path16.default.resolve(root, entry.file);
|
|
8099
8490
|
let actual = null;
|
|
8100
8491
|
const violations = [];
|
|
8101
8492
|
const warnings = [];
|
|
@@ -8108,7 +8499,7 @@ async function checkQualityBaseline(baselinePath) {
|
|
|
8108
8499
|
violations.push(message4(error));
|
|
8109
8500
|
}
|
|
8110
8501
|
if (entry.record) {
|
|
8111
|
-
const recordPath =
|
|
8502
|
+
const recordPath = import_node_path16.default.resolve(root, entry.record.path);
|
|
8112
8503
|
try {
|
|
8113
8504
|
const recordHash = await sha256File(recordPath);
|
|
8114
8505
|
if (recordHash !== entry.record.sha256) violations.push("quality record hash changed");
|
|
@@ -8145,13 +8536,13 @@ async function checkQualityBaseline(baselinePath) {
|
|
|
8145
8536
|
|
|
8146
8537
|
// src/pipeline/cache-health.ts
|
|
8147
8538
|
var import_node_fs16 = require("fs");
|
|
8148
|
-
var
|
|
8149
|
-
var
|
|
8539
|
+
var import_promises12 = require("fs/promises");
|
|
8540
|
+
var import_node_path20 = __toESM(require("path"), 1);
|
|
8150
8541
|
|
|
8151
8542
|
// src/cache.ts
|
|
8152
|
-
var
|
|
8543
|
+
var import_promises10 = require("fs/promises");
|
|
8153
8544
|
var import_node_fs13 = require("fs");
|
|
8154
|
-
var
|
|
8545
|
+
var import_node_path17 = __toESM(require("path"), 1);
|
|
8155
8546
|
var import_zod5 = require("zod");
|
|
8156
8547
|
var HashCacheSchema = import_zod5.z.object({
|
|
8157
8548
|
version: import_zod5.z.literal(1),
|
|
@@ -8165,10 +8556,10 @@ function isSha256Hash(value) {
|
|
|
8165
8556
|
function parseCache(value) {
|
|
8166
8557
|
return HashCacheSchema.parse(value);
|
|
8167
8558
|
}
|
|
8168
|
-
async function loadCache(
|
|
8169
|
-
if (!(0, import_node_fs13.existsSync)(
|
|
8559
|
+
async function loadCache(path33) {
|
|
8560
|
+
if (!(0, import_node_fs13.existsSync)(path33)) return { version: 1, hashes: {} };
|
|
8170
8561
|
try {
|
|
8171
|
-
const parsed = HashCacheSchema.safeParse(JSON.parse(await (0,
|
|
8562
|
+
const parsed = HashCacheSchema.safeParse(JSON.parse(await (0, import_promises10.readFile)(path33, "utf8")));
|
|
8172
8563
|
if (!parsed.success) return { version: 1, hashes: {} };
|
|
8173
8564
|
return {
|
|
8174
8565
|
version: 1,
|
|
@@ -8180,20 +8571,20 @@ async function loadCache(path32) {
|
|
|
8180
8571
|
return { version: 1, hashes: {} };
|
|
8181
8572
|
}
|
|
8182
8573
|
}
|
|
8183
|
-
async function saveCache(
|
|
8574
|
+
async function saveCache(path33, cache) {
|
|
8184
8575
|
const sorted = {};
|
|
8185
8576
|
for (const key of Object.keys(cache.hashes).sort()) {
|
|
8186
8577
|
const hash = cache.hashes[key];
|
|
8187
8578
|
if (!isSha256Hash(hash)) throw new Error(`Refusing to cache invalid SHA-256 for ${key}`);
|
|
8188
8579
|
sorted[key] = hash;
|
|
8189
8580
|
}
|
|
8190
|
-
await (0,
|
|
8191
|
-
const tmp = `${
|
|
8581
|
+
await (0, import_promises10.mkdir)(import_node_path17.default.dirname(import_node_path17.default.resolve(path33)), { recursive: true });
|
|
8582
|
+
const tmp = `${path33}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
8192
8583
|
try {
|
|
8193
|
-
await (0,
|
|
8194
|
-
await (0,
|
|
8584
|
+
await (0, import_promises10.writeFile)(tmp, JSON.stringify({ version: 1, hashes: sorted }, null, 2) + "\n");
|
|
8585
|
+
await (0, import_promises10.rename)(tmp, path33);
|
|
8195
8586
|
} finally {
|
|
8196
|
-
await (0,
|
|
8587
|
+
await (0, import_promises10.rm)(tmp, { force: true });
|
|
8197
8588
|
}
|
|
8198
8589
|
}
|
|
8199
8590
|
function pruneCache(cache, liveIds, namespace = "") {
|
|
@@ -8216,21 +8607,22 @@ function cachePathFor(lockPath) {
|
|
|
8216
8607
|
|
|
8217
8608
|
// src/pipeline/history.ts
|
|
8218
8609
|
var import_node_fs15 = require("fs");
|
|
8219
|
-
var
|
|
8610
|
+
var import_node_path19 = __toESM(require("path"), 1);
|
|
8220
8611
|
|
|
8221
8612
|
// src/pipeline/fetch.ts
|
|
8222
8613
|
var import_node_fs14 = require("fs");
|
|
8223
|
-
var
|
|
8224
|
-
var
|
|
8614
|
+
var import_promises11 = require("fs/promises");
|
|
8615
|
+
var import_node_path18 = __toESM(require("path"), 1);
|
|
8225
8616
|
async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
8226
8617
|
const log = opts.onProgress ?? (() => {
|
|
8227
8618
|
});
|
|
8228
8619
|
const result = { downloaded: 0, skipped: 0, failed: 0, ...opts.refresh ? { unchanged: 0 } : {} };
|
|
8229
8620
|
const specByKey = new Map(specs.map((s) => [lockKey(s.styleId, s.assetId), s]));
|
|
8230
8621
|
normalizeLockOutputPaths(lock, specs);
|
|
8231
|
-
const cacheDir = opts.cacheDir === false ? null :
|
|
8622
|
+
const cacheDir = opts.cacheDir === false ? null : import_node_path18.default.resolve(opts.cacheDir ?? import_node_path18.default.join(import_node_path18.default.dirname(lockPath), ".pixelkiln", "cache"));
|
|
8232
8623
|
const pending = Object.entries(lock.entries).filter(([key, e]) => {
|
|
8233
8624
|
if (e.provider !== provider.id || !specByKey.has(key)) return false;
|
|
8625
|
+
if (specByKey.get(key)?.mirror) return false;
|
|
8234
8626
|
if (e.status === "selected" || e.status === "download-failed") return true;
|
|
8235
8627
|
if (e.status !== "downloaded") return false;
|
|
8236
8628
|
if (opts.refresh) return e.outputs.length > 0 && Boolean(e.sourceUrls?.length || e.sourceUrl);
|
|
@@ -8329,7 +8721,7 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
8329
8721
|
if (!source.url) throw new Error(`no durable source URL to refresh ${source.role ?? "asset"} from`);
|
|
8330
8722
|
fresh = await provider.download(source.url);
|
|
8331
8723
|
if (recorded && sha256(fresh) === recordedRaw && !reapply) {
|
|
8332
|
-
log(` current ${
|
|
8724
|
+
log(` current ${import_node_path18.default.relative(process.cwd(), target)}`);
|
|
8333
8725
|
outputs.push({ ...recorded, path: portableOutputPath(target, spec.root) });
|
|
8334
8726
|
continue;
|
|
8335
8727
|
}
|
|
@@ -8343,7 +8735,7 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
8343
8735
|
if (cacheDir) {
|
|
8344
8736
|
await cacheMedia(
|
|
8345
8737
|
cacheDir,
|
|
8346
|
-
await (0,
|
|
8738
|
+
await (0, import_promises11.readFile)(target),
|
|
8347
8739
|
recorded.mediaType ?? MediaType.PNG,
|
|
8348
8740
|
recorded.sha256
|
|
8349
8741
|
);
|
|
@@ -8352,10 +8744,10 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
8352
8744
|
continue;
|
|
8353
8745
|
}
|
|
8354
8746
|
if (reapply && !fresh) {
|
|
8355
|
-
if (!recorded.raw) raw = await (0,
|
|
8356
|
-
log(` reapply ${
|
|
8747
|
+
if (!recorded.raw) raw = await (0, import_promises11.readFile)(target);
|
|
8748
|
+
log(` reapply ${import_node_path18.default.relative(process.cwd(), target)}`);
|
|
8357
8749
|
} else {
|
|
8358
|
-
log(` changed ${
|
|
8750
|
+
log(` changed ${import_node_path18.default.relative(process.cwd(), target)} (upstream)`);
|
|
8359
8751
|
}
|
|
8360
8752
|
} else {
|
|
8361
8753
|
const superseded = (entry.supersededOutputs ?? []).find(
|
|
@@ -8374,7 +8766,7 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
8374
8766
|
}
|
|
8375
8767
|
}
|
|
8376
8768
|
log(
|
|
8377
|
-
` replace ${
|
|
8769
|
+
` replace ${import_node_path18.default.relative(process.cwd(), target)}` + (opts.force ? " (--force)" : " (previous tracked generation)")
|
|
8378
8770
|
);
|
|
8379
8771
|
}
|
|
8380
8772
|
}
|
|
@@ -8383,7 +8775,7 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
8383
8775
|
} else if (!raw && recorded && cacheDir) {
|
|
8384
8776
|
if (!reapply) final = await readCachedMedia(cacheDir, recorded.sha256, expectedMediaType);
|
|
8385
8777
|
if (!final && recordedRaw) raw = await readCachedMedia(cacheDir, recordedRaw, expectedMediaType);
|
|
8386
|
-
if (final || raw) log(` cached ${
|
|
8778
|
+
if (final || raw) log(` cached ${import_node_path18.default.relative(process.cwd(), target)}`);
|
|
8387
8779
|
}
|
|
8388
8780
|
if (!final && !raw) {
|
|
8389
8781
|
if (!source.url) {
|
|
@@ -8410,13 +8802,13 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
8410
8802
|
buf = processed.bytes;
|
|
8411
8803
|
if (processed.changed) {
|
|
8412
8804
|
rawHash = sha256(raw);
|
|
8413
|
-
log(` snapped ${
|
|
8805
|
+
log(` snapped ${import_node_path18.default.relative(process.cwd(), target)} to ${wanted.palette.colors.length} colours`);
|
|
8414
8806
|
}
|
|
8415
8807
|
}
|
|
8416
8808
|
if (cacheDir) await cacheMedia(cacheDir, buf, mediaType);
|
|
8417
|
-
await (0,
|
|
8809
|
+
await (0, import_promises11.mkdir)(import_node_path18.default.dirname(target), { recursive: true });
|
|
8418
8810
|
const tmp = `${target}.pixelkiln.tmp`;
|
|
8419
|
-
await (0,
|
|
8811
|
+
await (0, import_promises11.writeFile)(tmp, buf);
|
|
8420
8812
|
outputs.push({
|
|
8421
8813
|
path: portableOutputPath(target, spec.root),
|
|
8422
8814
|
sha256: sha256(buf),
|
|
@@ -8426,9 +8818,9 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
8426
8818
|
});
|
|
8427
8819
|
upsert(lock, key, { outputs: mergeOutputs(entry.outputs, outputs) });
|
|
8428
8820
|
await saveLock(lockPath, lock);
|
|
8429
|
-
await (0,
|
|
8821
|
+
await (0, import_promises11.rename)(tmp, target);
|
|
8430
8822
|
wrote = true;
|
|
8431
|
-
log(` wrote ${
|
|
8823
|
+
log(` wrote ${import_node_path18.default.relative(process.cwd(), target)}`);
|
|
8432
8824
|
}
|
|
8433
8825
|
if (opts.refresh && !wrote) {
|
|
8434
8826
|
if (rotated) {
|
|
@@ -8472,10 +8864,10 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
8472
8864
|
return result;
|
|
8473
8865
|
}
|
|
8474
8866
|
async function readCachedMedia(cacheDir, hash, mediaType) {
|
|
8475
|
-
const file =
|
|
8867
|
+
const file = import_node_path18.default.join(cacheDir, cacheFileName(hash, mediaType));
|
|
8476
8868
|
if (!(0, import_node_fs14.existsSync)(file)) return null;
|
|
8477
8869
|
try {
|
|
8478
|
-
const buf = await (0,
|
|
8870
|
+
const buf = await (0, import_promises11.readFile)(file);
|
|
8479
8871
|
if (sha256(buf) !== hash) return null;
|
|
8480
8872
|
validateMedia(buf, mediaType);
|
|
8481
8873
|
return buf;
|
|
@@ -8486,17 +8878,17 @@ async function readCachedMedia(cacheDir, hash, mediaType) {
|
|
|
8486
8878
|
async function cacheMedia(cacheDir, buf, mediaType, knownHash) {
|
|
8487
8879
|
const hash = knownHash ?? sha256(buf);
|
|
8488
8880
|
validateMedia(buf, mediaType);
|
|
8489
|
-
const file =
|
|
8881
|
+
const file = import_node_path18.default.join(cacheDir, cacheFileName(hash, mediaType));
|
|
8490
8882
|
if (await readCachedMedia(cacheDir, hash, mediaType)) return;
|
|
8491
|
-
await (0,
|
|
8883
|
+
await (0, import_promises11.mkdir)(cacheDir, { recursive: true });
|
|
8492
8884
|
const tmp = `${file}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
8493
|
-
await (0,
|
|
8885
|
+
await (0, import_promises11.writeFile)(tmp, buf);
|
|
8494
8886
|
try {
|
|
8495
|
-
await (0,
|
|
8887
|
+
await (0, import_promises11.rename)(tmp, file);
|
|
8496
8888
|
} catch (err) {
|
|
8497
8889
|
if (!(0, import_node_fs14.existsSync)(file)) throw err;
|
|
8498
8890
|
} finally {
|
|
8499
|
-
await (0,
|
|
8891
|
+
await (0, import_promises11.rm)(tmp, { force: true });
|
|
8500
8892
|
}
|
|
8501
8893
|
}
|
|
8502
8894
|
function mergeOutputs(previous, current) {
|
|
@@ -8615,7 +9007,7 @@ async function revertGeneration(provider, spec, lock, lockPath, opts) {
|
|
|
8615
9007
|
if (!(0, import_node_fs15.existsSync)(file)) continue;
|
|
8616
9008
|
if (await sha256File(file) !== entry.outputs[i].sha256 && !opts.force) {
|
|
8617
9009
|
throw new OverwriteRefusedError(
|
|
8618
|
-
`${
|
|
9010
|
+
`${import_node_path19.default.relative(process.cwd(), file)} was changed after download; keep it with pixelkiln edit, or pass --force to replace it`,
|
|
8619
9011
|
[file]
|
|
8620
9012
|
);
|
|
8621
9013
|
}
|
|
@@ -8667,10 +9059,10 @@ async function revertGeneration(provider, spec, lock, lockPath, opts) {
|
|
|
8667
9059
|
// src/pipeline/cache-health.ts
|
|
8668
9060
|
async function inspectCaches(lock, lockPath, options = {}) {
|
|
8669
9061
|
if (options.prune && !(0, import_node_fs16.existsSync)(lockPath)) {
|
|
8670
|
-
throw new Error(`Refusing to prune without an existing lockfile at ${
|
|
9062
|
+
throw new Error(`Refusing to prune without an existing lockfile at ${import_node_path20.default.resolve(lockPath)}`);
|
|
8671
9063
|
}
|
|
8672
|
-
const contentDir =
|
|
8673
|
-
const remotePath =
|
|
9064
|
+
const contentDir = import_node_path20.default.resolve(import_node_path20.default.dirname(lockPath), ".pixelkiln", "cache");
|
|
9065
|
+
const remotePath = import_node_path20.default.resolve(cachePathFor(lockPath));
|
|
8674
9066
|
const referenced = referencedHashes(lock);
|
|
8675
9067
|
let content = await inspectContentCache(contentDir, referenced);
|
|
8676
9068
|
let remoteHashes = await inspectRemoteHashCache(remotePath);
|
|
@@ -8682,7 +9074,7 @@ async function inspectCaches(lock, lockPath, options = {}) {
|
|
|
8682
9074
|
]);
|
|
8683
9075
|
for (const name of names) {
|
|
8684
9076
|
try {
|
|
8685
|
-
await (0,
|
|
9077
|
+
await (0, import_promises12.rm)(import_node_path20.default.join(contentDir, name), { force: true });
|
|
8686
9078
|
removed.contentFiles++;
|
|
8687
9079
|
} catch {
|
|
8688
9080
|
}
|
|
@@ -8691,7 +9083,7 @@ async function inspectCaches(lock, lockPath, options = {}) {
|
|
|
8691
9083
|
await saveCache(remotePath, { version: 1, hashes: {} });
|
|
8692
9084
|
removed.resetRemoteHashCache = true;
|
|
8693
9085
|
} else if (remoteHashes.invalidIds.length) {
|
|
8694
|
-
const cache = parseCache(JSON.parse(await (0,
|
|
9086
|
+
const cache = parseCache(JSON.parse(await (0, import_promises12.readFile)(remotePath, "utf8")));
|
|
8695
9087
|
for (const id of remoteHashes.invalidIds) delete cache.hashes[id];
|
|
8696
9088
|
removed.remoteHashEntries = remoteHashes.invalidIds.length;
|
|
8697
9089
|
await saveCache(remotePath, cache);
|
|
@@ -8723,19 +9115,19 @@ async function inspectContentCache(contentDir, referenced) {
|
|
|
8723
9115
|
return report;
|
|
8724
9116
|
}
|
|
8725
9117
|
const present = /* @__PURE__ */ new Set();
|
|
8726
|
-
const entries = await (0,
|
|
9118
|
+
const entries = await (0, import_promises12.readdir)(contentDir, { withFileTypes: true });
|
|
8727
9119
|
for (const entry of entries) {
|
|
8728
9120
|
if (!entry.isFile()) {
|
|
8729
9121
|
report.invalid.push({ name: entry.name, reason: "not a regular file" });
|
|
8730
9122
|
continue;
|
|
8731
9123
|
}
|
|
8732
9124
|
report.files++;
|
|
8733
|
-
const file =
|
|
9125
|
+
const file = import_node_path20.default.join(contentDir, entry.name);
|
|
8734
9126
|
const mediaType = mediaTypeFromExtension(entry.name);
|
|
8735
9127
|
const expected = mediaType ? entry.name.slice(0, -4) : "";
|
|
8736
9128
|
let bytes;
|
|
8737
9129
|
try {
|
|
8738
|
-
bytes = await (0,
|
|
9130
|
+
bytes = await (0, import_promises12.readFile)(file);
|
|
8739
9131
|
report.bytes += bytes.length;
|
|
8740
9132
|
} catch (err) {
|
|
8741
9133
|
report.invalid.push({
|
|
@@ -8782,7 +9174,7 @@ async function inspectRemoteHashCache(remotePath) {
|
|
|
8782
9174
|
if (!report.exists) return report;
|
|
8783
9175
|
let cache;
|
|
8784
9176
|
try {
|
|
8785
|
-
cache = parseCache(JSON.parse(await (0,
|
|
9177
|
+
cache = parseCache(JSON.parse(await (0, import_promises12.readFile)(remotePath, "utf8")));
|
|
8786
9178
|
} catch (err) {
|
|
8787
9179
|
report.error = err instanceof Error ? err.message : String(err);
|
|
8788
9180
|
return report;
|
|
@@ -8798,19 +9190,19 @@ async function inspectRemoteHashCache(remotePath) {
|
|
|
8798
9190
|
|
|
8799
9191
|
// src/pipeline/pack.ts
|
|
8800
9192
|
var import_node_fs17 = require("fs");
|
|
8801
|
-
var
|
|
9193
|
+
var import_node_path21 = __toESM(require("path"), 1);
|
|
8802
9194
|
var DEFAULT_FRAME_SET_FPS = 12;
|
|
8803
9195
|
function resolvePackInputs(raw, inputsFilePath) {
|
|
8804
9196
|
if (!Array.isArray(raw) || !raw.length) {
|
|
8805
9197
|
throw new Error("--inputs must be a non-empty JSON array of { id, path }");
|
|
8806
9198
|
}
|
|
8807
|
-
const baseDir =
|
|
9199
|
+
const baseDir = import_node_path21.default.dirname(import_node_path21.default.resolve(inputsFilePath));
|
|
8808
9200
|
return raw.map((entry, i) => {
|
|
8809
9201
|
const e = entry;
|
|
8810
9202
|
if (typeof e.id !== "string" || typeof e.path !== "string") {
|
|
8811
9203
|
throw new Error(`--inputs[${i}] needs string "id" and "path"`);
|
|
8812
9204
|
}
|
|
8813
|
-
return { id: e.id, path:
|
|
9205
|
+
return { id: e.id, path: import_node_path21.default.resolve(baseDir, e.path) };
|
|
8814
9206
|
});
|
|
8815
9207
|
}
|
|
8816
9208
|
function packSprites(inputs, options = {}) {
|
|
@@ -8882,7 +9274,7 @@ function packStyle(lock, styleId, manifestDir, options = {}) {
|
|
|
8882
9274
|
}
|
|
8883
9275
|
const inputs2 = Object.entries(options.sourceOverrides).map(([id, source]) => ({
|
|
8884
9276
|
id,
|
|
8885
|
-
path:
|
|
9277
|
+
path: import_node_path21.default.resolve(source)
|
|
8886
9278
|
}));
|
|
8887
9279
|
if (!inputs2.length) throw new Error(`No approved quality outputs for style "${styleId}".`);
|
|
8888
9280
|
const packed2 = packSprites(inputs2, options);
|
|
@@ -8906,7 +9298,7 @@ function packStyle(lock, styleId, manifestDir, options = {}) {
|
|
|
8906
9298
|
let outputs = resolveEntryOutputs(entry, id, manifestDir);
|
|
8907
9299
|
const source = sources[id];
|
|
8908
9300
|
if (source && outputs.length <= 1) {
|
|
8909
|
-
inputs.push({ id, path:
|
|
9301
|
+
inputs.push({ id, path: import_node_path21.default.resolve(manifestDir, source) });
|
|
8910
9302
|
continue;
|
|
8911
9303
|
}
|
|
8912
9304
|
if (source && sourceIsStem(source, entry, manifestDir)) {
|
|
@@ -8940,7 +9332,7 @@ function packStyle(lock, styleId, manifestDir, options = {}) {
|
|
|
8940
9332
|
}
|
|
8941
9333
|
}
|
|
8942
9334
|
for (const [id, source] of Object.entries(sources)) {
|
|
8943
|
-
if (!locked.has(id)) inputs.push({ id, path:
|
|
9335
|
+
if (!locked.has(id)) inputs.push({ id, path: import_node_path21.default.resolve(manifestDir, source) });
|
|
8944
9336
|
}
|
|
8945
9337
|
if (!inputs.length) {
|
|
8946
9338
|
throw new Error(`No readable sprites for style "${styleId}"; ${noOutput.length} skipped.`);
|
|
@@ -9048,7 +9440,7 @@ function mountStyle(lock, styleId, manifestDir, mount, cells, sources = {}, outp
|
|
|
9048
9440
|
const source = sources[id];
|
|
9049
9441
|
const entry = lock.entries[`${styleId}/${id}`];
|
|
9050
9442
|
if (source && !(entry && sourceIsStem(source, entry, manifestDir))) {
|
|
9051
|
-
placements.push({ id, path:
|
|
9443
|
+
placements.push({ id, path: import_node_path21.default.resolve(manifestDir, source), cell });
|
|
9052
9444
|
continue;
|
|
9053
9445
|
}
|
|
9054
9446
|
if (source && entry) {
|
|
@@ -9081,7 +9473,7 @@ function mountStyle(lock, styleId, manifestDir, mount, cells, sources = {}, outp
|
|
|
9081
9473
|
`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}`
|
|
9082
9474
|
);
|
|
9083
9475
|
}
|
|
9084
|
-
const basePng = mount.base ? (0, import_node_fs17.readFileSync)(
|
|
9476
|
+
const basePng = mount.base ? (0, import_node_fs17.readFileSync)(import_node_path21.default.resolve(manifestDir, mount.base)) : void 0;
|
|
9085
9477
|
const mounted = mountSprites(placements, {
|
|
9086
9478
|
cellWidth: mount.cellWidth,
|
|
9087
9479
|
cellHeight: mount.cellHeight,
|
|
@@ -9091,7 +9483,7 @@ function mountStyle(lock, styleId, manifestDir, mount, cells, sources = {}, outp
|
|
|
9091
9483
|
...mounted,
|
|
9092
9484
|
sources: mount.base ? [{
|
|
9093
9485
|
id: "$base",
|
|
9094
|
-
path:
|
|
9486
|
+
path: import_node_path21.default.resolve(manifestDir, mount.base),
|
|
9095
9487
|
sha256: sha256(basePng),
|
|
9096
9488
|
included: true
|
|
9097
9489
|
}, ...mounted.sources] : mounted.sources,
|
|
@@ -9464,7 +9856,7 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
|
|
|
9464
9856
|
});
|
|
9465
9857
|
const estimates = new Map(items.map((item) => [
|
|
9466
9858
|
item.key,
|
|
9467
|
-
validateCostEstimate(provider.id, provider.estimate(item.spec))
|
|
9859
|
+
item.spec.mirror ? { amount: 0, unit: item.spec.costUnit, candidates: 1 } : validateCostEstimate(provider.id, provider.estimate(item.spec))
|
|
9468
9860
|
]));
|
|
9469
9861
|
const units = new Set([...estimates.values()].map((estimate) => estimate.unit));
|
|
9470
9862
|
if (units.size > 1) {
|
|
@@ -9514,6 +9906,34 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
|
|
|
9514
9906
|
}
|
|
9515
9907
|
for (const { spec, key } of items) {
|
|
9516
9908
|
const estimate = estimates.get(key);
|
|
9909
|
+
if (spec.mirror) {
|
|
9910
|
+
try {
|
|
9911
|
+
await requireRevisionReady(spec, lock);
|
|
9912
|
+
await deriveMirror(spec, lock, lockPath, { onProgress: log });
|
|
9913
|
+
submitted++;
|
|
9914
|
+
} catch (err) {
|
|
9915
|
+
const message6 = err instanceof Error ? err.message : String(err);
|
|
9916
|
+
failed++;
|
|
9917
|
+
upsert(lock, key, {
|
|
9918
|
+
styleId: spec.styleId,
|
|
9919
|
+
assetId: spec.assetId,
|
|
9920
|
+
specHash: spec.specHash,
|
|
9921
|
+
generator: spec.generator,
|
|
9922
|
+
prompt: "",
|
|
9923
|
+
width: spec.width,
|
|
9924
|
+
height: spec.height,
|
|
9925
|
+
status: "failed",
|
|
9926
|
+
error: message6,
|
|
9927
|
+
outputs: lock.entries[key]?.outputs ?? [],
|
|
9928
|
+
cost: 0,
|
|
9929
|
+
costUnit: spec.costUnit,
|
|
9930
|
+
provider: provider.id
|
|
9931
|
+
});
|
|
9932
|
+
await saveLock(lockPath, lock);
|
|
9933
|
+
log(` FAILED ${key}: ${message6}`);
|
|
9934
|
+
}
|
|
9935
|
+
continue;
|
|
9936
|
+
}
|
|
9517
9937
|
await waitForSlot();
|
|
9518
9938
|
const since = Date.now() - lastSubmitAt;
|
|
9519
9939
|
if (since < spacing) await sleep(spacing - since);
|
|
@@ -9724,8 +10144,8 @@ async function poll(provider, lock, lockPath, opts = {}) {
|
|
|
9724
10144
|
|
|
9725
10145
|
// src/pipeline/doctor.ts
|
|
9726
10146
|
var import_node_fs18 = require("fs");
|
|
9727
|
-
var
|
|
9728
|
-
var
|
|
10147
|
+
var import_promises13 = require("fs/promises");
|
|
10148
|
+
var import_node_path22 = __toESM(require("path"), 1);
|
|
9729
10149
|
async function doctor(loaded, specs, lock, lockPath, opts = {}) {
|
|
9730
10150
|
const checks = [];
|
|
9731
10151
|
const add = (id, level, message6) => checks.push({ id, level, message: message6 });
|
|
@@ -9736,14 +10156,14 @@ async function doctor(loaded, specs, lock, lockPath, opts = {}) {
|
|
|
9736
10156
|
);
|
|
9737
10157
|
add("lock", "ok", `${Object.keys(lock.entries).length} valid v2 lock entr(ies)`);
|
|
9738
10158
|
const dirs = [...new Set(specs.flatMap((spec) => [
|
|
9739
|
-
|
|
9740
|
-
...spec.quality ? [
|
|
10159
|
+
import_node_path22.default.dirname(spec.outFile),
|
|
10160
|
+
...spec.quality ? [import_node_path22.default.dirname(spec.quality.outFile)] : []
|
|
9741
10161
|
]))];
|
|
9742
10162
|
const unwritable = [];
|
|
9743
10163
|
for (const dir of dirs) {
|
|
9744
10164
|
const ancestor = await nearestExistingDirectory(dir);
|
|
9745
10165
|
try {
|
|
9746
|
-
await (0,
|
|
10166
|
+
await (0, import_promises13.access)(ancestor, import_node_fs18.constants.W_OK);
|
|
9747
10167
|
} catch {
|
|
9748
10168
|
unwritable.push(dir);
|
|
9749
10169
|
}
|
|
@@ -9760,7 +10180,7 @@ async function doctor(loaded, specs, lock, lockPath, opts = {}) {
|
|
|
9760
10180
|
for (let index = 0; index < entry.outputs.length; index++) {
|
|
9761
10181
|
const output = entry.outputs[index];
|
|
9762
10182
|
const spec = specByKey.get(key);
|
|
9763
|
-
const absolute = spec ? currentEntryOutputPath(entry, spec, index) : resolveOutputPath(output.path,
|
|
10183
|
+
const absolute = spec ? currentEntryOutputPath(entry, spec, index) : resolveOutputPath(output.path, import_node_path22.default.dirname(lockPath));
|
|
9764
10184
|
const owner = outputOwners.get(absolute);
|
|
9765
10185
|
if (owner && owner !== key) duplicateOutputs.push(`${output.path} (${owner}, ${key})`);
|
|
9766
10186
|
outputOwners.set(absolute, key);
|
|
@@ -9775,7 +10195,7 @@ async function doctor(loaded, specs, lock, lockPath, opts = {}) {
|
|
|
9775
10195
|
} else {
|
|
9776
10196
|
add("lock-outputs", "ok", `${outputOwners.size} recorded output path(s) are uniquely owned`);
|
|
9777
10197
|
}
|
|
9778
|
-
const cacheDir =
|
|
10198
|
+
const cacheDir = import_node_path22.default.join(import_node_path22.default.dirname(lockPath), ".pixelkiln", "cache");
|
|
9779
10199
|
const stranded = [];
|
|
9780
10200
|
const invalidState = [];
|
|
9781
10201
|
const staleMapUrls = [];
|
|
@@ -9791,7 +10211,7 @@ async function doctor(loaded, specs, lock, lockPath, opts = {}) {
|
|
|
9791
10211
|
const hasSource = Boolean(entry.sourceUrl || entry.sourceUrls?.length);
|
|
9792
10212
|
let hasCache = false;
|
|
9793
10213
|
for (const output of entry.outputs) {
|
|
9794
|
-
const cached =
|
|
10214
|
+
const cached = import_node_path22.default.join(
|
|
9795
10215
|
cacheDir,
|
|
9796
10216
|
cacheFileName(output.sha256, output.mediaType ?? MediaType.PNG)
|
|
9797
10217
|
);
|
|
@@ -9888,10 +10308,10 @@ async function nearestExistingDirectory(start) {
|
|
|
9888
10308
|
let current = start;
|
|
9889
10309
|
for (; ; ) {
|
|
9890
10310
|
try {
|
|
9891
|
-
if ((await (0,
|
|
10311
|
+
if ((await (0, import_promises13.stat)(current)).isDirectory()) return current;
|
|
9892
10312
|
} catch {
|
|
9893
10313
|
}
|
|
9894
|
-
const parent =
|
|
10314
|
+
const parent = import_node_path22.default.dirname(current);
|
|
9895
10315
|
if (parent === current) return current;
|
|
9896
10316
|
current = parent;
|
|
9897
10317
|
}
|
|
@@ -9899,8 +10319,8 @@ async function nearestExistingDirectory(start) {
|
|
|
9899
10319
|
|
|
9900
10320
|
// src/pipeline/adopt.ts
|
|
9901
10321
|
var import_node_fs19 = require("fs");
|
|
9902
|
-
var
|
|
9903
|
-
var
|
|
10322
|
+
var import_promises14 = require("fs/promises");
|
|
10323
|
+
var import_node_path23 = __toESM(require("path"), 1);
|
|
9904
10324
|
async function adopt(provider, specs, lock, lockPath, opts = {}) {
|
|
9905
10325
|
const log = opts.onProgress ?? (() => {
|
|
9906
10326
|
});
|
|
@@ -9915,7 +10335,7 @@ async function adopt(provider, specs, lock, lockPath, opts = {}) {
|
|
|
9915
10335
|
unmatchedLocal.push(`${spec.styleId}/${spec.assetId} (no file at ${spec.outFile})`);
|
|
9916
10336
|
continue;
|
|
9917
10337
|
}
|
|
9918
|
-
const bytes = await (0,
|
|
10338
|
+
const bytes = await (0, import_promises14.readFile)(spec.outFile);
|
|
9919
10339
|
try {
|
|
9920
10340
|
decodePng(bytes);
|
|
9921
10341
|
} catch (err) {
|
|
@@ -10034,12 +10454,12 @@ async function tagAdopted(provider, specs, lock, opts = {}) {
|
|
|
10034
10454
|
|
|
10035
10455
|
// src/pick/server.ts
|
|
10036
10456
|
var import_node_fs21 = require("fs");
|
|
10037
|
-
var
|
|
10457
|
+
var import_promises17 = require("fs/promises");
|
|
10038
10458
|
|
|
10039
10459
|
// src/pipeline/init.ts
|
|
10040
|
-
var
|
|
10460
|
+
var import_promises15 = require("fs/promises");
|
|
10041
10461
|
var import_node_fs20 = require("fs");
|
|
10042
|
-
var
|
|
10462
|
+
var import_node_path24 = __toESM(require("path"), 1);
|
|
10043
10463
|
function pngSize(buf) {
|
|
10044
10464
|
if (buf.length < 24) return null;
|
|
10045
10465
|
const isPng = buf.readUInt32BE(0) === 2303741511 && buf.readUInt32BE(4) === 218765834;
|
|
@@ -10052,8 +10472,8 @@ function slugify(value) {
|
|
|
10052
10472
|
}
|
|
10053
10473
|
async function walk(dir, exclude) {
|
|
10054
10474
|
const out = [];
|
|
10055
|
-
for (const entry of await (0,
|
|
10056
|
-
const full =
|
|
10475
|
+
for (const entry of await (0, import_promises15.readdir)(dir, { withFileTypes: true })) {
|
|
10476
|
+
const full = import_node_path24.default.join(dir, entry.name);
|
|
10057
10477
|
if (exclude.some((x) => full.includes(x))) continue;
|
|
10058
10478
|
if (entry.isDirectory()) out.push(...await walk(full, exclude));
|
|
10059
10479
|
else if (entry.name.toLowerCase().endsWith(".png")) out.push(full);
|
|
@@ -10067,14 +10487,14 @@ async function scanAssets(root, opts = {}) {
|
|
|
10067
10487
|
const skipped = [];
|
|
10068
10488
|
const seen = /* @__PURE__ */ new Map();
|
|
10069
10489
|
for (const file of files.sort()) {
|
|
10070
|
-
const size = pngSize(await (0,
|
|
10490
|
+
const size = pngSize(await (0, import_promises15.readFile)(file));
|
|
10071
10491
|
if (!size) {
|
|
10072
10492
|
skipped.push(`${file} (not a readable PNG)`);
|
|
10073
10493
|
continue;
|
|
10074
10494
|
}
|
|
10075
|
-
const rel =
|
|
10076
|
-
const category =
|
|
10077
|
-
let id = slugify(
|
|
10495
|
+
const rel = import_node_path24.default.relative(root, file);
|
|
10496
|
+
const category = import_node_path24.default.dirname(rel) === "." ? "" : import_node_path24.default.dirname(rel);
|
|
10497
|
+
let id = slugify(import_node_path24.default.basename(rel));
|
|
10078
10498
|
if (seen.has(id)) {
|
|
10079
10499
|
const n = seen.get(id) + 1;
|
|
10080
10500
|
seen.set(id, n);
|
|
@@ -10525,7 +10945,7 @@ document.getElementById('submit').onclick = async () => {
|
|
|
10525
10945
|
|
|
10526
10946
|
// src/pick/review-server.ts
|
|
10527
10947
|
var import_node_http = require("http");
|
|
10528
|
-
var
|
|
10948
|
+
var import_promises16 = require("fs/promises");
|
|
10529
10949
|
function serveReviewPage(opts) {
|
|
10530
10950
|
const log = opts.onProgress ?? (() => {
|
|
10531
10951
|
});
|
|
@@ -10534,7 +10954,7 @@ function serveReviewPage(opts) {
|
|
|
10534
10954
|
if (req.method === "GET" && req.url && opts.assets?.has(req.url)) {
|
|
10535
10955
|
try {
|
|
10536
10956
|
const asset = opts.assets.get(req.url);
|
|
10537
|
-
const bytes = await (0,
|
|
10957
|
+
const bytes = await (0, import_promises16.readFile)(asset.path);
|
|
10538
10958
|
res.writeHead(200, {
|
|
10539
10959
|
"Content-Type": asset.contentType,
|
|
10540
10960
|
"Cache-Control": "no-store",
|
|
@@ -10616,7 +11036,7 @@ async function currentArt(entry, spec) {
|
|
|
10616
11036
|
let width = entry.width;
|
|
10617
11037
|
let height = entry.height;
|
|
10618
11038
|
if (contentType === "image/png") {
|
|
10619
|
-
const size = pngSize(await (0,
|
|
11039
|
+
const size = pngSize(await (0, import_promises17.readFile)(file));
|
|
10620
11040
|
if (size) ({ width, height } = size);
|
|
10621
11041
|
}
|
|
10622
11042
|
return { path: file, contentType, width, height };
|
|
@@ -10782,13 +11202,13 @@ async function runPicker(provider, lock, lockPath, opts = {}) {
|
|
|
10782
11202
|
// src/gallery/snapshot.ts
|
|
10783
11203
|
var import_node_crypto6 = require("crypto");
|
|
10784
11204
|
var import_node_fs24 = require("fs");
|
|
10785
|
-
var
|
|
10786
|
-
var
|
|
11205
|
+
var import_promises21 = require("fs/promises");
|
|
11206
|
+
var import_node_path29 = __toESM(require("path"), 1);
|
|
10787
11207
|
|
|
10788
11208
|
// src/workspace.ts
|
|
10789
|
-
var
|
|
11209
|
+
var import_promises18 = require("fs/promises");
|
|
10790
11210
|
var import_node_fs22 = require("fs");
|
|
10791
|
-
var
|
|
11211
|
+
var import_node_path25 = __toESM(require("path"), 1);
|
|
10792
11212
|
var import_zod6 = require("zod");
|
|
10793
11213
|
var WorkspaceProjectSchema = import_zod6.z.object({
|
|
10794
11214
|
id: import_zod6.z.string().min(1),
|
|
@@ -10816,7 +11236,7 @@ async function loadWorkspace(workspacePath) {
|
|
|
10816
11236
|
if (!(0, import_node_fs22.existsSync)(workspacePath)) return { version: 1, projects: [] };
|
|
10817
11237
|
let raw;
|
|
10818
11238
|
try {
|
|
10819
|
-
raw = JSON.parse(await (0,
|
|
11239
|
+
raw = JSON.parse(await (0, import_promises18.readFile)(workspacePath, "utf8"));
|
|
10820
11240
|
} catch (err) {
|
|
10821
11241
|
throw new ProjectError(
|
|
10822
11242
|
`Workspace catalog at ${workspacePath} is malformed:
|
|
@@ -10830,22 +11250,22 @@ async function saveWorkspace(workspacePath, ws) {
|
|
|
10830
11250
|
version: 1,
|
|
10831
11251
|
projects: [...ws.projects].sort((a, b) => a.id.localeCompare(b.id))
|
|
10832
11252
|
};
|
|
10833
|
-
await (0,
|
|
11253
|
+
await (0, import_promises18.mkdir)(import_node_path25.default.dirname(import_node_path25.default.resolve(workspacePath)), { recursive: true });
|
|
10834
11254
|
const tmp = `${workspacePath}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
10835
11255
|
try {
|
|
10836
|
-
await (0,
|
|
10837
|
-
await (0,
|
|
11256
|
+
await (0, import_promises18.writeFile)(tmp, JSON.stringify(sorted, null, 2) + "\n");
|
|
11257
|
+
await (0, import_promises18.rename)(tmp, workspacePath);
|
|
10838
11258
|
} finally {
|
|
10839
|
-
await (0,
|
|
11259
|
+
await (0, import_promises18.rm)(tmp, { force: true });
|
|
10840
11260
|
}
|
|
10841
11261
|
}
|
|
10842
11262
|
function toPortablePath(dir, absolute) {
|
|
10843
|
-
return
|
|
11263
|
+
return import_node_path25.default.relative(dir, absolute).split(import_node_path25.default.sep).join("/");
|
|
10844
11264
|
}
|
|
10845
11265
|
function resolveProject(dir, project) {
|
|
10846
11266
|
return {
|
|
10847
|
-
manifestPath:
|
|
10848
|
-
lockPath:
|
|
11267
|
+
manifestPath: import_node_path25.default.resolve(dir, project.manifest.split("/").join(import_node_path25.default.sep)),
|
|
11268
|
+
lockPath: import_node_path25.default.resolve(dir, project.lock.split("/").join(import_node_path25.default.sep))
|
|
10849
11269
|
};
|
|
10850
11270
|
}
|
|
10851
11271
|
function validateWorkspace(ws, dir) {
|
|
@@ -10858,7 +11278,7 @@ function validateWorkspace(ws, dir) {
|
|
|
10858
11278
|
const { manifestPath, lockPath } = resolveProject(dir, project);
|
|
10859
11279
|
lockOwners.set(lockPath, [...lockOwners.get(lockPath) ?? [], project.id]);
|
|
10860
11280
|
manifestOwners.set(manifestPath, [...manifestOwners.get(manifestPath) ?? [], project.id]);
|
|
10861
|
-
if (
|
|
11281
|
+
if (import_node_path25.default.isAbsolute(project.manifest) || import_node_path25.default.isAbsolute(project.lock)) {
|
|
10862
11282
|
diagnostics.push({
|
|
10863
11283
|
id: "absolute-path",
|
|
10864
11284
|
level: "warning",
|
|
@@ -10919,12 +11339,12 @@ function validateWorkspace(ws, dir) {
|
|
|
10919
11339
|
}
|
|
10920
11340
|
|
|
10921
11341
|
// src/gallery/edit.ts
|
|
10922
|
-
var
|
|
11342
|
+
var import_node_path28 = __toESM(require("path"), 1);
|
|
10923
11343
|
var import_zod9 = require("zod");
|
|
10924
11344
|
|
|
10925
11345
|
// src/manifest-edit.ts
|
|
10926
|
-
var
|
|
10927
|
-
var
|
|
11346
|
+
var import_promises19 = require("fs/promises");
|
|
11347
|
+
var import_node_path26 = __toESM(require("path"), 1);
|
|
10928
11348
|
var import_zod7 = require("zod");
|
|
10929
11349
|
var HexSha = import_zod7.z.string().regex(/^[0-9a-f]{64}$/);
|
|
10930
11350
|
var AssetPatchSchema = import_zod7.z.object({
|
|
@@ -11139,8 +11559,8 @@ function applyEdit(raw, edit) {
|
|
|
11139
11559
|
}
|
|
11140
11560
|
}
|
|
11141
11561
|
async function applyManifestEdit(manifestPath, edit) {
|
|
11142
|
-
const absolute =
|
|
11143
|
-
const original = await (0,
|
|
11562
|
+
const absolute = import_node_path26.default.resolve(manifestPath);
|
|
11563
|
+
const original = await (0, import_promises19.readFile)(absolute, "utf8");
|
|
11144
11564
|
const before = sha256(original);
|
|
11145
11565
|
if (before !== edit.expectedSha256) throw new ManifestDriftError(absolute);
|
|
11146
11566
|
let raw;
|
|
@@ -11154,12 +11574,12 @@ async function applyManifestEdit(manifestPath, edit) {
|
|
|
11154
11574
|
applyEdit(raw, edit);
|
|
11155
11575
|
const next = serializeLike(original, raw);
|
|
11156
11576
|
if (next === original) return { manifestPath: absolute, sha256: before, changed: false };
|
|
11157
|
-
const tmp =
|
|
11158
|
-
|
|
11159
|
-
`.${
|
|
11577
|
+
const tmp = import_node_path26.default.join(
|
|
11578
|
+
import_node_path26.default.dirname(absolute),
|
|
11579
|
+
`.${import_node_path26.default.basename(absolute)}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`
|
|
11160
11580
|
);
|
|
11161
11581
|
try {
|
|
11162
|
-
await (0,
|
|
11582
|
+
await (0, import_promises19.writeFile)(tmp, next);
|
|
11163
11583
|
try {
|
|
11164
11584
|
const loaded = await loadManifest(tmp);
|
|
11165
11585
|
await resolveSpecs(loaded);
|
|
@@ -11167,26 +11587,26 @@ async function applyManifestEdit(manifestPath, edit) {
|
|
|
11167
11587
|
const message6 = error instanceof Error ? error.message : String(error);
|
|
11168
11588
|
throw new ManifestEditError(message6.replace(tmp, absolute));
|
|
11169
11589
|
}
|
|
11170
|
-
await (0,
|
|
11590
|
+
await (0, import_promises19.rename)(tmp, absolute);
|
|
11171
11591
|
} finally {
|
|
11172
|
-
await (0,
|
|
11592
|
+
await (0, import_promises19.rm)(tmp, { force: true });
|
|
11173
11593
|
}
|
|
11174
11594
|
return { manifestPath: absolute, sha256: sha256(next), changed: true };
|
|
11175
11595
|
}
|
|
11176
11596
|
|
|
11177
11597
|
// src/pipeline/hand-edit.ts
|
|
11178
11598
|
var import_node_fs23 = require("fs");
|
|
11179
|
-
var
|
|
11180
|
-
var
|
|
11599
|
+
var import_promises20 = require("fs/promises");
|
|
11600
|
+
var import_node_path27 = __toESM(require("path"), 1);
|
|
11181
11601
|
var import_zod8 = require("zod");
|
|
11182
11602
|
var HAND_EDIT_DIR = "edits";
|
|
11183
11603
|
function handEditPath(loaded, spec) {
|
|
11184
11604
|
const style = loaded.manifest.styles[spec.styleId];
|
|
11185
|
-
const outDir =
|
|
11186
|
-
const relative =
|
|
11187
|
-
const inside = relative && !relative.startsWith("..") && !
|
|
11188
|
-
const file = inside ? relative :
|
|
11189
|
-
return
|
|
11605
|
+
const outDir = import_node_path27.default.resolve(loaded.root, style?.outDir ?? ".");
|
|
11606
|
+
const relative = import_node_path27.default.relative(outDir, spec.outFile);
|
|
11607
|
+
const inside = relative && !relative.startsWith("..") && !import_node_path27.default.isAbsolute(relative);
|
|
11608
|
+
const file = inside ? relative : import_node_path27.default.basename(spec.outFile);
|
|
11609
|
+
return import_node_path27.default.join(outDir, HAND_EDIT_DIR, file.replace(/\.gif$/i, ".png"));
|
|
11190
11610
|
}
|
|
11191
11611
|
function handEditBases(spec, lock) {
|
|
11192
11612
|
const entry = lock.entries[lockKey(spec.styleId, spec.assetId)];
|
|
@@ -11232,7 +11652,7 @@ async function startHandEdit(loaded, lock, spec, opts = {}) {
|
|
|
11232
11652
|
`${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`
|
|
11233
11653
|
);
|
|
11234
11654
|
}
|
|
11235
|
-
const existing = spec.source ?
|
|
11655
|
+
const existing = spec.source ? import_node_path27.default.resolve(loaded.root, spec.source) : null;
|
|
11236
11656
|
const editPath = existing ?? handEditPath(loaded, spec);
|
|
11237
11657
|
const members = handEditMembers(loaded, spec, lock, editPath);
|
|
11238
11658
|
let created = false;
|
|
@@ -11243,8 +11663,8 @@ async function startHandEdit(loaded, lock, spec, opts = {}) {
|
|
|
11243
11663
|
`${spec.styleId}/${spec.assetId} has no generated PNG on disk to start from; generate or restore it first`
|
|
11244
11664
|
);
|
|
11245
11665
|
}
|
|
11246
|
-
await (0,
|
|
11247
|
-
await (0,
|
|
11666
|
+
await (0, import_promises20.mkdir)(import_node_path27.default.dirname(member.path), { recursive: true });
|
|
11667
|
+
await (0, import_promises20.copyFile)(member.base, member.path);
|
|
11248
11668
|
created = true;
|
|
11249
11669
|
}
|
|
11250
11670
|
const source = portableOutputPath(editPath, loaded.root);
|
|
@@ -11322,7 +11742,7 @@ function handEditProjectPath(editPath) {
|
|
|
11322
11742
|
async function readHandEditCompanion(editPath) {
|
|
11323
11743
|
let text;
|
|
11324
11744
|
try {
|
|
11325
|
-
text = await (0,
|
|
11745
|
+
text = await (0, import_promises20.readFile)(handEditCompanionPath(editPath), "utf8");
|
|
11326
11746
|
} catch {
|
|
11327
11747
|
return null;
|
|
11328
11748
|
}
|
|
@@ -11365,7 +11785,7 @@ async function saveHandEdit(loaded, lock, spec, input) {
|
|
|
11365
11785
|
}
|
|
11366
11786
|
});
|
|
11367
11787
|
const bases = handEditBases(spec, lock);
|
|
11368
|
-
const expectedSize = bases?.[0] ? decodePng(await (0,
|
|
11788
|
+
const expectedSize = bases?.[0] ? decodePng(await (0, import_promises20.readFile)(bases[0].path)) : { width: spec.width, height: spec.height };
|
|
11369
11789
|
for (const [index, image] of decoded.entries()) {
|
|
11370
11790
|
if (image.width !== expectedSize.width || image.height !== expectedSize.height) {
|
|
11371
11791
|
const which = memberSet ? `member ${incoming[index].role ?? index}` : "the edit";
|
|
@@ -11381,7 +11801,7 @@ async function saveHandEdit(loaded, lock, spec, input) {
|
|
|
11381
11801
|
editor: input.editor,
|
|
11382
11802
|
protocol: input.protocol,
|
|
11383
11803
|
savedAt: (input.now ?? /* @__PURE__ */ new Date()).toISOString(),
|
|
11384
|
-
project: projectPath ?
|
|
11804
|
+
project: projectPath ? import_node_path27.default.basename(projectPath) : null,
|
|
11385
11805
|
outputs: incoming.map((frame, index) => ({
|
|
11386
11806
|
role: frame.role,
|
|
11387
11807
|
basedOn: basedOnHash(entry, index),
|
|
@@ -11411,10 +11831,10 @@ function basedOnHash(entry, index) {
|
|
|
11411
11831
|
async function replaceFile(file, bytes) {
|
|
11412
11832
|
const tmp = `${file}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
11413
11833
|
try {
|
|
11414
|
-
await (0,
|
|
11415
|
-
await (0,
|
|
11834
|
+
await (0, import_promises20.writeFile)(tmp, bytes);
|
|
11835
|
+
await (0, import_promises20.rename)(tmp, file);
|
|
11416
11836
|
} finally {
|
|
11417
|
-
await (0,
|
|
11837
|
+
await (0, import_promises20.rm)(tmp, { force: true });
|
|
11418
11838
|
}
|
|
11419
11839
|
}
|
|
11420
11840
|
function openInEditor(file, editor = process.env.PIXELKILN_EDITOR) {
|
|
@@ -11511,7 +11931,7 @@ function createGalleryEditHandler(opts) {
|
|
|
11511
11931
|
}
|
|
11512
11932
|
const result = await applyManifestEdit(manifestPath, parsed.data);
|
|
11513
11933
|
if (result.changed) {
|
|
11514
|
-
const relative =
|
|
11934
|
+
const relative = import_node_path28.default.relative(process.cwd(), result.manifestPath);
|
|
11515
11935
|
const shown = !relative || relative.startsWith("..") ? result.manifestPath : relative;
|
|
11516
11936
|
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;
|
|
11517
11937
|
log(` manifest edited: ${parsed.data.action === "add-asset" ? "added" : "changed"} ${subject} in ${shown}`);
|
|
@@ -11523,7 +11943,7 @@ function createGalleryEditHandler(opts) {
|
|
|
11523
11943
|
// src/gallery/snapshot.ts
|
|
11524
11944
|
var PROJECT_FILE_TYPE = "application/octet-stream";
|
|
11525
11945
|
function galleryMediaId(absolutePath) {
|
|
11526
|
-
return (0, import_node_crypto6.createHash)("sha256").update(
|
|
11946
|
+
return (0, import_node_crypto6.createHash)("sha256").update(import_node_path29.default.resolve(absolutePath)).digest("hex").slice(0, 24);
|
|
11527
11947
|
}
|
|
11528
11948
|
function galleryMediaRoute(id) {
|
|
11529
11949
|
return `/media/${id}`;
|
|
@@ -11542,7 +11962,7 @@ function describeHistory(media, entry, cacheDir) {
|
|
|
11542
11962
|
return (entry.history ?? []).map((generation, i) => {
|
|
11543
11963
|
const outputs = generation.outputs.map((output) => {
|
|
11544
11964
|
const mediaType = output.mediaType ?? MediaType.PNG;
|
|
11545
|
-
const file =
|
|
11965
|
+
const file = import_node_path29.default.join(cacheDir, cacheFileName(output.sha256, mediaType));
|
|
11546
11966
|
let url = null;
|
|
11547
11967
|
if ((0, import_node_fs24.existsSync)(file)) {
|
|
11548
11968
|
const id = galleryMediaId(file);
|
|
@@ -11573,7 +11993,7 @@ function describeHistory(media, entry, cacheDir) {
|
|
|
11573
11993
|
var MAX_COMPARED_BYTES = 8 * 1024 * 1024;
|
|
11574
11994
|
async function samePixels(a, b) {
|
|
11575
11995
|
try {
|
|
11576
|
-
const [left, right] = await Promise.all([(0,
|
|
11996
|
+
const [left, right] = await Promise.all([(0, import_promises21.readFile)(a), (0, import_promises21.readFile)(b)]);
|
|
11577
11997
|
if (left.length > MAX_COMPARED_BYTES || right.length > MAX_COMPARED_BYTES) return false;
|
|
11578
11998
|
const x = decodePng(left);
|
|
11579
11999
|
const y = decodePng(right);
|
|
@@ -11611,7 +12031,7 @@ function describeCharacter(spec, entry) {
|
|
|
11611
12031
|
}
|
|
11612
12032
|
async function fileInfo(absolutePath) {
|
|
11613
12033
|
try {
|
|
11614
|
-
const info = await (0,
|
|
12034
|
+
const info = await (0, import_promises21.stat)(absolutePath);
|
|
11615
12035
|
return info.isFile() ? { bytes: info.size, modifiedAt: info.mtime.toISOString() } : null;
|
|
11616
12036
|
} catch {
|
|
11617
12037
|
return null;
|
|
@@ -11643,9 +12063,9 @@ async function describeOutput(media, root, absolutePath, recorded = {}) {
|
|
|
11643
12063
|
async function describeQuality(media, root, inspection) {
|
|
11644
12064
|
const outputPaths = inspection.outputs ?? [inspection.output];
|
|
11645
12065
|
const outputs = await Promise.all(
|
|
11646
|
-
outputPaths.map((file) => describeOutput(media, root,
|
|
12066
|
+
outputPaths.map((file) => describeOutput(media, root, import_node_path29.default.resolve(root, file)))
|
|
11647
12067
|
);
|
|
11648
|
-
const recordPath =
|
|
12068
|
+
const recordPath = import_node_path29.default.resolve(root, inspection.record);
|
|
11649
12069
|
const recordExists = (0, import_node_fs24.existsSync)(recordPath);
|
|
11650
12070
|
let options = null;
|
|
11651
12071
|
let check = null;
|
|
@@ -11665,7 +12085,7 @@ async function describeQuality(media, root, inspection) {
|
|
|
11665
12085
|
return {
|
|
11666
12086
|
state: inspection.state,
|
|
11667
12087
|
reason: inspection.reason,
|
|
11668
|
-
output: portableOutputPath(
|
|
12088
|
+
output: portableOutputPath(import_node_path29.default.resolve(root, inspection.output), root),
|
|
11669
12089
|
record: portableOutputPath(recordPath, root),
|
|
11670
12090
|
recordExists,
|
|
11671
12091
|
outputs,
|
|
@@ -11702,9 +12122,9 @@ async function buildGallerySnapshot(opts) {
|
|
|
11702
12122
|
const media = /* @__PURE__ */ new Map();
|
|
11703
12123
|
const plan = await buildPlan(specs, lock);
|
|
11704
12124
|
const items = [];
|
|
11705
|
-
const manifestText = await (0,
|
|
12125
|
+
const manifestText = await (0, import_promises21.readFile)(loaded.path, "utf8");
|
|
11706
12126
|
const manifestSha256 = sha256(manifestText);
|
|
11707
|
-
const cacheDir =
|
|
12127
|
+
const cacheDir = import_node_path29.default.resolve(import_node_path29.default.dirname(opts.lockPath), ".pixelkiln", "cache");
|
|
11708
12128
|
let rawStyles = {};
|
|
11709
12129
|
try {
|
|
11710
12130
|
const raw = JSON.parse(manifestText);
|
|
@@ -11728,7 +12148,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
11728
12148
|
)
|
|
11729
12149
|
);
|
|
11730
12150
|
if (spec.source) {
|
|
11731
|
-
const editPath =
|
|
12151
|
+
const editPath = import_node_path29.default.resolve(root, spec.source);
|
|
11732
12152
|
const memberCount = sourceIsStem(spec.source, entry, root) ? entry.outputs.length : 1;
|
|
11733
12153
|
const editShas = await Promise.all(
|
|
11734
12154
|
Array.from({ length: memberCount }, async (_, index) => {
|
|
@@ -11777,7 +12197,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
11777
12197
|
editStatus = edits.some((member) => !member.exists) ? "missing" : editChanged.every((changed) => !changed) ? "same" : regenerated ? "regenerated-since" : "edited";
|
|
11778
12198
|
}
|
|
11779
12199
|
} else if (spec.source) {
|
|
11780
|
-
outputs = [await describeOutput(media, root,
|
|
12200
|
+
outputs = [await describeOutput(media, root, import_node_path29.default.resolve(root, spec.source))];
|
|
11781
12201
|
} else if (!entry && (0, import_node_fs24.existsSync)(spec.outFile)) {
|
|
11782
12202
|
outputs = [await describeOutput(media, root, spec.outFile)];
|
|
11783
12203
|
} else {
|
|
@@ -11817,6 +12237,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
11817
12237
|
currentSpecHash: spec.specHash,
|
|
11818
12238
|
revision: entry?.revision ?? null,
|
|
11819
12239
|
revisionParentKey: spec.revision ? lockKey(spec.styleId, spec.revision.sourceAssetId) : entry?.revision ? lockKey(spec.styleId, entry.revision.sourceAssetId) : null,
|
|
12240
|
+
mirrorOfKey: spec.mirror ? lockKey(spec.styleId, spec.mirror.sourceAssetId) : entry?.mirror ? lockKey(spec.styleId, entry.mirror.sourceAssetId) : null,
|
|
11820
12241
|
character: describeCharacter(spec, entry),
|
|
11821
12242
|
outputs,
|
|
11822
12243
|
quality,
|
|
@@ -11879,6 +12300,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
11879
12300
|
currentSpecHash: null,
|
|
11880
12301
|
revision: entry.revision,
|
|
11881
12302
|
revisionParentKey: entry.revision ? lockKey(entry.styleId, entry.revision.sourceAssetId) : null,
|
|
12303
|
+
mirrorOfKey: entry.mirror ? lockKey(entry.styleId, entry.mirror.sourceAssetId) : null,
|
|
11882
12304
|
character: describeCharacter(void 0, entry),
|
|
11883
12305
|
outputs,
|
|
11884
12306
|
quality: null,
|
|
@@ -11967,7 +12389,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
11967
12389
|
id: loaded.manifest.name,
|
|
11968
12390
|
name: loaded.manifest.name,
|
|
11969
12391
|
manifest: loaded.path,
|
|
11970
|
-
lock:
|
|
12392
|
+
lock: import_node_path29.default.resolve(opts.lockPath),
|
|
11971
12393
|
root,
|
|
11972
12394
|
provider: loaded.manifest.provider,
|
|
11973
12395
|
account: null,
|
|
@@ -11993,7 +12415,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
11993
12415
|
return { snapshot, media };
|
|
11994
12416
|
}
|
|
11995
12417
|
async function buildWorkspaceGallerySnapshot(opts) {
|
|
11996
|
-
const dir =
|
|
12418
|
+
const dir = import_node_path29.default.dirname(import_node_path29.default.resolve(opts.workspacePath));
|
|
11997
12419
|
const media = /* @__PURE__ */ new Map();
|
|
11998
12420
|
const projects = [];
|
|
11999
12421
|
const items = [];
|
|
@@ -12036,11 +12458,11 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
12036
12458
|
id: project.id,
|
|
12037
12459
|
name: loaded.manifest.name,
|
|
12038
12460
|
manifest: manifestPath,
|
|
12039
|
-
lock:
|
|
12461
|
+
lock: import_node_path29.default.resolve(lockPath),
|
|
12040
12462
|
root: loaded.root,
|
|
12041
12463
|
provider: loaded.manifest.provider,
|
|
12042
12464
|
account: project.account ?? null,
|
|
12043
|
-
manifestSha256: manifestSha256 ?? sha256(await (0,
|
|
12465
|
+
manifestSha256: manifestSha256 ?? sha256(await (0, import_promises21.readFile)(manifestPath, "utf8")),
|
|
12044
12466
|
historyLimit: historyLimit(loaded.manifest),
|
|
12045
12467
|
entries: Object.keys(lock.entries).length,
|
|
12046
12468
|
items: projectItems.length,
|
|
@@ -12052,8 +12474,8 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
12052
12474
|
id: project.id,
|
|
12053
12475
|
name: project.id,
|
|
12054
12476
|
manifest: manifestPath,
|
|
12055
|
-
lock:
|
|
12056
|
-
root:
|
|
12477
|
+
lock: import_node_path29.default.resolve(lockPath),
|
|
12478
|
+
root: import_node_path29.default.dirname(manifestPath),
|
|
12057
12479
|
provider: project.provider,
|
|
12058
12480
|
account: project.account ?? null,
|
|
12059
12481
|
manifestSha256: null,
|
|
@@ -12080,7 +12502,7 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
12080
12502
|
version: 1,
|
|
12081
12503
|
generatedAt: (opts.now ?? (() => /* @__PURE__ */ new Date()))().toISOString(),
|
|
12082
12504
|
project: null,
|
|
12083
|
-
workspace: { path:
|
|
12505
|
+
workspace: { path: import_node_path29.default.resolve(opts.workspacePath), dir, projects },
|
|
12084
12506
|
filter,
|
|
12085
12507
|
totals: {
|
|
12086
12508
|
entries: projects.reduce((sum, project) => sum + project.entries, 0),
|
|
@@ -12230,9 +12652,9 @@ var EDITOR_RELEASE_BASE = "https://github.com/gfargo/pixelkiln/releases/download
|
|
|
12230
12652
|
// src/editor/install.ts
|
|
12231
12653
|
var import_node_crypto7 = require("crypto");
|
|
12232
12654
|
var import_node_fs26 = require("fs");
|
|
12233
|
-
var
|
|
12655
|
+
var import_promises22 = require("fs/promises");
|
|
12234
12656
|
var import_node_os3 = __toESM(require("os"), 1);
|
|
12235
|
-
var
|
|
12657
|
+
var import_node_path30 = __toESM(require("path"), 1);
|
|
12236
12658
|
var EditorInstallError = class extends Error {
|
|
12237
12659
|
constructor(message6, file) {
|
|
12238
12660
|
super(message6);
|
|
@@ -12243,15 +12665,15 @@ var EditorInstallError = class extends Error {
|
|
|
12243
12665
|
};
|
|
12244
12666
|
function editorToolsRoot() {
|
|
12245
12667
|
const override = process.env.PIXELKILN_TOOLS_DIR?.trim();
|
|
12246
|
-
if (override) return
|
|
12247
|
-
if (process.platform === "darwin") return
|
|
12668
|
+
if (override) return import_node_path30.default.resolve(override);
|
|
12669
|
+
if (process.platform === "darwin") return import_node_path30.default.join(import_node_os3.default.homedir(), "Library", "Caches", "pixelkiln", "tools");
|
|
12248
12670
|
if (process.platform === "win32") {
|
|
12249
|
-
return
|
|
12671
|
+
return import_node_path30.default.join(process.env.LOCALAPPDATA ?? import_node_path30.default.join(import_node_os3.default.homedir(), "AppData", "Local"), "pixelkiln", "tools");
|
|
12250
12672
|
}
|
|
12251
|
-
return
|
|
12673
|
+
return import_node_path30.default.join(process.env.XDG_CACHE_HOME ?? import_node_path30.default.join(import_node_os3.default.homedir(), ".cache"), "pixelkiln", "tools");
|
|
12252
12674
|
}
|
|
12253
12675
|
function editorDir(pin = EDITOR_PIN, root = editorToolsRoot()) {
|
|
12254
|
-
return
|
|
12676
|
+
return import_node_path30.default.join(root, "pixelorama", pin.release ?? "unpublished");
|
|
12255
12677
|
}
|
|
12256
12678
|
function editorBaseUrl(pin = EDITOR_PIN) {
|
|
12257
12679
|
const override = process.env.PIXELKILN_EDITOR_URL?.trim();
|
|
@@ -12261,7 +12683,7 @@ function editorBaseUrl(pin = EDITOR_PIN) {
|
|
|
12261
12683
|
}
|
|
12262
12684
|
async function sha256Of(file) {
|
|
12263
12685
|
try {
|
|
12264
|
-
return (0, import_node_crypto7.createHash)("sha256").update(await (0,
|
|
12686
|
+
return (0, import_node_crypto7.createHash)("sha256").update(await (0, import_promises22.readFile)(file)).digest("hex");
|
|
12265
12687
|
} catch {
|
|
12266
12688
|
return null;
|
|
12267
12689
|
}
|
|
@@ -12274,8 +12696,8 @@ async function editorStatus(opts = {}) {
|
|
|
12274
12696
|
let totalBytes = 0;
|
|
12275
12697
|
for (const [name, expected] of Object.entries(pin.files)) {
|
|
12276
12698
|
totalBytes += expected.bytes;
|
|
12277
|
-
const file =
|
|
12278
|
-
const info = (0, import_node_fs26.existsSync)(file) ? await (0,
|
|
12699
|
+
const file = import_node_path30.default.join(dir, name);
|
|
12700
|
+
const info = (0, import_node_fs26.existsSync)(file) ? await (0, import_promises22.stat)(file).catch(() => null) : null;
|
|
12279
12701
|
if (info && info.size === expected.bytes && await sha256Of(file) === expected.sha256) {
|
|
12280
12702
|
installedBytes += expected.bytes;
|
|
12281
12703
|
} else {
|
|
@@ -12304,7 +12726,7 @@ async function installEditor(opts = {}) {
|
|
|
12304
12726
|
);
|
|
12305
12727
|
}
|
|
12306
12728
|
if (!baseUrl) throw new EditorInstallError("no download location for the editor build");
|
|
12307
|
-
await (0,
|
|
12729
|
+
await (0, import_promises22.mkdir)(dir, { recursive: true });
|
|
12308
12730
|
const downloaded = [];
|
|
12309
12731
|
const skipped = Object.keys(pin.files).filter((name) => !before.missing.includes(name));
|
|
12310
12732
|
let fetchedBytes = before.installedBytes;
|
|
@@ -12332,13 +12754,13 @@ async function installEditor(opts = {}) {
|
|
|
12332
12754
|
if (digest2 !== expected.sha256) {
|
|
12333
12755
|
throw new EditorInstallError(`${name}: sha256 ${digest2.slice(0, 12)}\u2026 does not match the pinned ${expected.sha256.slice(0, 12)}\u2026`, name);
|
|
12334
12756
|
}
|
|
12335
|
-
const final =
|
|
12757
|
+
const final = import_node_path30.default.join(dir, name);
|
|
12336
12758
|
const tmp = `${final}.${process.pid}.part`;
|
|
12337
12759
|
try {
|
|
12338
|
-
await (0,
|
|
12339
|
-
await (0,
|
|
12760
|
+
await (0, import_promises22.writeFile)(tmp, bytes);
|
|
12761
|
+
await (0, import_promises22.rename)(tmp, final);
|
|
12340
12762
|
} finally {
|
|
12341
|
-
await (0,
|
|
12763
|
+
await (0, import_promises22.rm)(tmp, { force: true });
|
|
12342
12764
|
}
|
|
12343
12765
|
fetchedBytes += bytes.length;
|
|
12344
12766
|
downloaded.push(name);
|
|
@@ -12378,12 +12800,12 @@ var EDITOR_CONTENT_TYPES = {
|
|
|
12378
12800
|
".json": "application/json; charset=utf-8"
|
|
12379
12801
|
};
|
|
12380
12802
|
function editorContentType(name) {
|
|
12381
|
-
return EDITOR_CONTENT_TYPES[
|
|
12803
|
+
return EDITOR_CONTENT_TYPES[import_node_path30.default.extname(name).toLowerCase()] ?? null;
|
|
12382
12804
|
}
|
|
12383
12805
|
|
|
12384
12806
|
// src/gallery/editor.ts
|
|
12385
|
-
var
|
|
12386
|
-
var
|
|
12807
|
+
var import_promises23 = require("fs/promises");
|
|
12808
|
+
var import_node_path31 = __toESM(require("path"), 1);
|
|
12387
12809
|
function editorRoute(release, name = "index.html") {
|
|
12388
12810
|
return `/editor/${encodeURIComponent(release)}/${encodeURIComponent(name)}`;
|
|
12389
12811
|
}
|
|
@@ -12456,7 +12878,7 @@ function createGalleryEditorHandlers(opts = {}) {
|
|
|
12456
12878
|
if (!status.installed) return null;
|
|
12457
12879
|
let bytes;
|
|
12458
12880
|
try {
|
|
12459
|
-
bytes = await (0,
|
|
12881
|
+
bytes = await (0, import_promises23.readFile)(import_node_path31.default.join(dir, name));
|
|
12460
12882
|
} catch {
|
|
12461
12883
|
verified = null;
|
|
12462
12884
|
return null;
|
|
@@ -12473,7 +12895,7 @@ function createGalleryEditorHandlers(opts = {}) {
|
|
|
12473
12895
|
// src/gallery/server.ts
|
|
12474
12896
|
var import_node_crypto8 = require("crypto");
|
|
12475
12897
|
var import_node_http2 = require("http");
|
|
12476
|
-
var
|
|
12898
|
+
var import_promises24 = require("fs/promises");
|
|
12477
12899
|
var MAX_EDIT_BYTES = 48 * 1024 * 1024;
|
|
12478
12900
|
var EDITOR_CSP = [
|
|
12479
12901
|
"default-src 'self'",
|
|
@@ -12632,7 +13054,7 @@ async function serveGallery(opts) {
|
|
|
12632
13054
|
const asset = reviewAssets.get(jobId)?.get(url2.pathname);
|
|
12633
13055
|
if (!asset) return fail(404, "no such review asset");
|
|
12634
13056
|
try {
|
|
12635
|
-
const bytes = await (0,
|
|
13057
|
+
const bytes = await (0, import_promises24.readFile)(asset.path);
|
|
12636
13058
|
res.writeHead(200, {
|
|
12637
13059
|
"Content-Type": asset.contentType,
|
|
12638
13060
|
"Cache-Control": "no-store",
|
|
@@ -12708,7 +13130,7 @@ async function serveGallery(opts) {
|
|
|
12708
13130
|
if (!asset) return fail(404, "no such gallery media");
|
|
12709
13131
|
let bytes;
|
|
12710
13132
|
try {
|
|
12711
|
-
bytes = await (0,
|
|
13133
|
+
bytes = await (0, import_promises24.readFile)(asset.path);
|
|
12712
13134
|
} catch {
|
|
12713
13135
|
return fail(404, "gallery media is no longer on disk; refresh the page");
|
|
12714
13136
|
}
|
|
@@ -13082,9 +13504,9 @@ function createGenerateHandlers(opts) {
|
|
|
13082
13504
|
}
|
|
13083
13505
|
|
|
13084
13506
|
// src/pipeline/salvage.ts
|
|
13085
|
-
var
|
|
13507
|
+
var import_promises25 = require("fs/promises");
|
|
13086
13508
|
var import_node_fs27 = require("fs");
|
|
13087
|
-
var
|
|
13509
|
+
var import_node_path32 = __toESM(require("path"), 1);
|
|
13088
13510
|
function providerClaimId(provider, objectId) {
|
|
13089
13511
|
return `${encodeURIComponent(provider)}:${encodeURIComponent(objectId)}`;
|
|
13090
13512
|
}
|
|
@@ -13094,7 +13516,7 @@ async function loadClaims(lockPaths, opts = {}) {
|
|
|
13094
13516
|
if (!(0, import_node_fs27.existsSync)(p)) throw new Error(`Claim lockfile not found: ${p}`);
|
|
13095
13517
|
let parsed;
|
|
13096
13518
|
try {
|
|
13097
|
-
parsed = parseLock(JSON.parse(await (0,
|
|
13519
|
+
parsed = parseLock(JSON.parse(await (0, import_promises25.readFile)(p, "utf8")));
|
|
13098
13520
|
} catch {
|
|
13099
13521
|
throw new Error(`Claim lockfile is malformed: ${p}`);
|
|
13100
13522
|
}
|
|
@@ -13147,20 +13569,20 @@ function matchOrphanStyle(prompt, manifest) {
|
|
|
13147
13569
|
return matchStyleByPattern(prompt, manifest);
|
|
13148
13570
|
}
|
|
13149
13571
|
async function loadSiblingManifests(ownManifestPath, workspaceManifestPaths, claimPaths) {
|
|
13150
|
-
const own =
|
|
13572
|
+
const own = import_node_path32.default.resolve(ownManifestPath);
|
|
13151
13573
|
const siblingManifestPaths = [
|
|
13152
13574
|
.../* @__PURE__ */ new Set([
|
|
13153
13575
|
...workspaceManifestPaths,
|
|
13154
|
-
...claimPaths.map((c) =>
|
|
13576
|
+
...claimPaths.map((c) => import_node_path32.default.join(import_node_path32.default.dirname(import_node_path32.default.resolve(c)), "pixelkiln.manifest.json"))
|
|
13155
13577
|
])
|
|
13156
13578
|
];
|
|
13157
13579
|
const siblings = [];
|
|
13158
13580
|
for (const siblingManifestPath of siblingManifestPaths) {
|
|
13159
|
-
if (
|
|
13581
|
+
if (import_node_path32.default.resolve(siblingManifestPath) === own) continue;
|
|
13160
13582
|
if (!(0, import_node_fs27.existsSync)(siblingManifestPath)) continue;
|
|
13161
13583
|
try {
|
|
13162
13584
|
const { manifest } = await loadManifest(siblingManifestPath);
|
|
13163
|
-
siblings.push({ label:
|
|
13585
|
+
siblings.push({ label: import_node_path32.default.basename(import_node_path32.default.dirname(siblingManifestPath)), manifest });
|
|
13164
13586
|
} catch {
|
|
13165
13587
|
}
|
|
13166
13588
|
}
|
|
@@ -13276,8 +13698,8 @@ async function applyTags(provider, decisions, existing, opts = {}) {
|
|
|
13276
13698
|
}
|
|
13277
13699
|
|
|
13278
13700
|
// src/pick/salvage-server.ts
|
|
13279
|
-
var
|
|
13280
|
-
var
|
|
13701
|
+
var import_promises26 = require("fs/promises");
|
|
13702
|
+
var import_node_path33 = __toESM(require("path"), 1);
|
|
13281
13703
|
|
|
13282
13704
|
// src/pick/salvage-sheet.ts
|
|
13283
13705
|
var escapeHtml3 = (s) => s.replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
@@ -13442,7 +13864,7 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
13442
13864
|
});
|
|
13443
13865
|
const html = renderSalvageSheet(orphans, {
|
|
13444
13866
|
styleId: ctx.styleId,
|
|
13445
|
-
importDir:
|
|
13867
|
+
importDir: import_node_path33.default.relative(process.cwd(), ctx.importDir) || "."
|
|
13446
13868
|
});
|
|
13447
13869
|
const byId = new Map(orphans.map((o) => [o.id, o]));
|
|
13448
13870
|
const existingTags = new Map(orphans.map((o) => [o.id, o.tags]));
|
|
@@ -13471,10 +13893,10 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
13471
13893
|
if (!buf.subarray(0, 8).equals(PNG_SIGNATURE2)) throw new Error("not a PNG");
|
|
13472
13894
|
decodePng(buf);
|
|
13473
13895
|
const assetId = idFromPrompt(orphan.prompt, taken);
|
|
13474
|
-
const rel =
|
|
13475
|
-
const outFile =
|
|
13476
|
-
await (0,
|
|
13477
|
-
await (0,
|
|
13896
|
+
const rel = import_node_path33.default.join("_salvaged", `${assetId}.png`);
|
|
13897
|
+
const outFile = import_node_path33.default.resolve(ctx.importDir, rel);
|
|
13898
|
+
await (0, import_promises26.mkdir)(import_node_path33.default.dirname(outFile), { recursive: true });
|
|
13899
|
+
await (0, import_promises26.writeFile)(outFile, buf);
|
|
13478
13900
|
ctx.manifest.assets[assetId] = {
|
|
13479
13901
|
prompt: orphan.prompt,
|
|
13480
13902
|
promptByStyle: {},
|
|
@@ -13506,7 +13928,7 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
13506
13928
|
sourceUrl: durableSource,
|
|
13507
13929
|
sourceUrls: durableSource ? [{ url: durableSource }] : [],
|
|
13508
13930
|
outputs: [{
|
|
13509
|
-
path: portableOutputPath(outFile,
|
|
13931
|
+
path: portableOutputPath(outFile, import_node_path33.default.dirname(ctx.manifestPath)),
|
|
13510
13932
|
sha256: sha256(buf)
|
|
13511
13933
|
}],
|
|
13512
13934
|
submittedAt: orphan.createdAt,
|
|
@@ -13530,9 +13952,9 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
13530
13952
|
}
|
|
13531
13953
|
}
|
|
13532
13954
|
await applyTags(provider, decisions, existingTags, { onProgress: log });
|
|
13533
|
-
const raw = JSON.parse(await (0,
|
|
13955
|
+
const raw = JSON.parse(await (0, import_promises26.readFile)(ctx.manifestPath, "utf8"));
|
|
13534
13956
|
for (const id of importedAssetIds) raw.assets[id] = ctx.manifest.assets[id];
|
|
13535
|
-
await (0,
|
|
13957
|
+
await (0, import_promises26.writeFile)(ctx.manifestPath, JSON.stringify(raw, null, 2) + "\n");
|
|
13536
13958
|
await saveLock(ctx.lockPath, ctx.lock);
|
|
13537
13959
|
return result;
|
|
13538
13960
|
}
|
|
@@ -13644,10 +14066,12 @@ async function workspaceStatus(ws, dir) {
|
|
|
13644
14066
|
BudgetError,
|
|
13645
14067
|
CHARACTER_DIRECTIONS_4,
|
|
13646
14068
|
CHARACTER_DIRECTIONS_8,
|
|
14069
|
+
CHARACTER_PROPORTION_PRESETS,
|
|
13647
14070
|
CharacterAnimationModeSchema,
|
|
13648
14071
|
CharacterAnimationSchema,
|
|
13649
14072
|
CharacterDirectionSchema,
|
|
13650
14073
|
CharacterModeSchema,
|
|
14074
|
+
CharacterProportionsSchema,
|
|
13651
14075
|
CharacterStateSchema,
|
|
13652
14076
|
ComfyUIProvider,
|
|
13653
14077
|
DEFAULT_HISTORY_LIMIT,
|
|
@@ -13675,6 +14099,7 @@ async function workspaceStatus(ws, dir) {
|
|
|
13675
14099
|
MAX_DOWNLOAD_BYTES,
|
|
13676
14100
|
MAX_HAND_EDIT_BYTES,
|
|
13677
14101
|
MAX_RETRIES,
|
|
14102
|
+
MIRRORED_DIRECTION,
|
|
13678
14103
|
ManifestDriftError,
|
|
13679
14104
|
ManifestEditError,
|
|
13680
14105
|
ManifestEditSchema,
|
|
@@ -13813,6 +14238,7 @@ async function workspaceStatus(ws, dir) {
|
|
|
13813
14238
|
portableOutputPath,
|
|
13814
14239
|
prepareReview,
|
|
13815
14240
|
primaryOutput,
|
|
14241
|
+
proportionsPayload,
|
|
13816
14242
|
providerCredentialEnvs,
|
|
13817
14243
|
providerFactory,
|
|
13818
14244
|
pushTags,
|