pixelkiln 0.83.0 → 0.85.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/PROVIDERS.md +3 -3
- package/dist/cli.js +957 -611
- package/dist/cli.js.map +1 -1
- package/dist/client/gallery.css +2 -0
- package/dist/client/gallery.js +3366 -3087
- package/dist/index.cjs +876 -530
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +792 -71
- package/dist/index.d.ts +792 -71
- package/dist/index.js +783 -437
- package/dist/index.js.map +1 -1
- package/docs/MANIFEST.md +10 -6
- package/docs/REVISIONS.md +66 -15
- package/docs/SCENARIO.md +174 -7
- package/package.json +1 -1
- package/schema/manifest.schema.json +22 -0
- package/skills/pixelkiln/references/pixellab-roadmap.md +10 -0
- package/skills/pixelkiln/references/pixellab.md +6 -3
- package/skills/pixelkiln/references/revisions.md +13 -7
- package/skills/pixelkiln/references/scenario.md +8 -4
package/dist/index.cjs
CHANGED
|
@@ -334,6 +334,7 @@ var RevisionModeSchema = import_zod.z.enum([
|
|
|
334
334
|
"outpaint",
|
|
335
335
|
"reduce-colors",
|
|
336
336
|
"correct-pixelart",
|
|
337
|
+
"remove-background",
|
|
337
338
|
"animate",
|
|
338
339
|
"animate-pixminimax",
|
|
339
340
|
"animate-skeleton",
|
|
@@ -665,7 +666,22 @@ var RevisionSchema = import_zod.z.object({
|
|
|
665
666
|
* `interpolate`: required, the ending keyframe; the parent is the start.
|
|
666
667
|
*/
|
|
667
668
|
lastFrame: import_zod.z.string().min(1).optional(),
|
|
668
|
-
/**
|
|
669
|
+
/**
|
|
670
|
+
* `animate`/`animate-pixminimax` only: colour de-flicker sensitivity.
|
|
671
|
+
* Frames whose foreground drifts from the first frame beyond this are
|
|
672
|
+
* corrected toward it; 0 corrects every frame and higher values correct
|
|
673
|
+
* fewer. Omit for PixelLab's default.
|
|
674
|
+
*/
|
|
675
|
+
driftThreshold: import_zod.z.number().min(0).optional(),
|
|
676
|
+
/**
|
|
677
|
+
* `animate-pixminimax` only: one noun phrase for what the parent shows.
|
|
678
|
+
* PixelLab reads it off the image when omitted, and can decline some
|
|
679
|
+
* images, failing the job until it is sent.
|
|
680
|
+
*/
|
|
681
|
+
subjectDescription: import_zod.z.string().min(1).max(300).optional(),
|
|
682
|
+
/** `animate-pixminimax` only: the pose the parent is in, read off the image by PixelLab when omitted. */
|
|
683
|
+
initialPose: import_zod.z.string().min(1).max(300).optional(),
|
|
684
|
+
/** `animate-pixminimax` only: facing direction, one of the model's caption fields, so it also holds the sprite's facing. */
|
|
669
685
|
direction: CharacterDirectionSchema.optional(),
|
|
670
686
|
/** `animate`/`animate-pixminimax` only: let PixelLab expand the action into a fuller motion description first. */
|
|
671
687
|
enhancePrompt: import_zod.z.boolean().optional(),
|
|
@@ -685,12 +701,15 @@ var RevisionSchema = import_zod.z.object({
|
|
|
685
701
|
* same `mannequin`/`bear`/`cat`/`dog`/`horse`/`lion` vocabulary.
|
|
686
702
|
*/
|
|
687
703
|
skeletonTemplate: import_zod.z.string().min(1).optional(),
|
|
704
|
+
/** `remove-background` only: `simple` is faster and suits flat backgrounds; `complex` handles detailed edges. PixelLab defaults to `simple`. */
|
|
705
|
+
removalTask: import_zod.z.enum(["simple", "complex"]).optional(),
|
|
688
706
|
/**
|
|
689
|
-
* `animate-skeleton
|
|
707
|
+
* `animate-skeleton`: what the character *looks like*, a noun
|
|
690
708
|
* phrase (colours, clothing, held items) — distinct from the asset's
|
|
691
709
|
* own `prompt`, which this mode sends as the motion's short label
|
|
692
710
|
* (PixelLab's `action`), the same `prompt`-as-motion convention
|
|
693
|
-
* `animate`/`animate-pixminimax` already use.
|
|
711
|
+
* `animate`/`animate-pixminimax` already use. `remove-background`: a
|
|
712
|
+
* short description of the foreground, which PixelLab uses as a hint.
|
|
694
713
|
*/
|
|
695
714
|
description: import_zod.z.string().min(1).optional(),
|
|
696
715
|
/**
|
|
@@ -807,7 +826,44 @@ var RevisionSchema = import_zod.z.object({
|
|
|
807
826
|
path: ["keypointsFile"]
|
|
808
827
|
});
|
|
809
828
|
}
|
|
810
|
-
|
|
829
|
+
if (revision.driftThreshold !== void 0 && revision.mode !== "animate" && revision.mode !== "animate-pixminimax") {
|
|
830
|
+
context.addIssue({
|
|
831
|
+
code: import_zod.z.ZodIssueCode.custom,
|
|
832
|
+
message: "driftThreshold applies to animate/animate-pixminimax revisions only",
|
|
833
|
+
path: ["driftThreshold"]
|
|
834
|
+
});
|
|
835
|
+
}
|
|
836
|
+
for (const field of ["subjectDescription", "initialPose"]) {
|
|
837
|
+
if (revision[field] !== void 0 && revision.mode !== "animate-pixminimax") {
|
|
838
|
+
context.addIssue({
|
|
839
|
+
code: import_zod.z.ZodIssueCode.custom,
|
|
840
|
+
message: `${field} applies to animate-pixminimax revisions only`,
|
|
841
|
+
path: [field]
|
|
842
|
+
});
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
if (revision.removalTask !== void 0 && revision.mode !== "remove-background") {
|
|
846
|
+
context.addIssue({
|
|
847
|
+
code: import_zod.z.ZodIssueCode.custom,
|
|
848
|
+
message: "removalTask applies to remove-background revisions only",
|
|
849
|
+
path: ["removalTask"]
|
|
850
|
+
});
|
|
851
|
+
}
|
|
852
|
+
if (revision.strength !== void 0 && revision.mode === "remove-background") {
|
|
853
|
+
context.addIssue({
|
|
854
|
+
code: import_zod.z.ZodIssueCode.custom,
|
|
855
|
+
message: "remove-background revisions do not take a strength",
|
|
856
|
+
path: ["strength"]
|
|
857
|
+
});
|
|
858
|
+
}
|
|
859
|
+
if (revision.description !== void 0 && revision.mode !== "animate-skeleton" && revision.mode !== "remove-background") {
|
|
860
|
+
context.addIssue({
|
|
861
|
+
code: import_zod.z.ZodIssueCode.custom,
|
|
862
|
+
message: "description applies to animate-skeleton/remove-background revisions only",
|
|
863
|
+
path: ["description"]
|
|
864
|
+
});
|
|
865
|
+
}
|
|
866
|
+
for (const field of ["keypointsFile", "skeletonTemplate"]) {
|
|
811
867
|
if (revision[field] !== void 0 && revision.mode !== "animate-skeleton") {
|
|
812
868
|
context.addIssue({
|
|
813
869
|
code: import_zod.z.ZodIssueCode.custom,
|
|
@@ -1461,6 +1517,10 @@ var LockEntrySchema = import_zod.z.object({
|
|
|
1461
1517
|
keypointsSha256: import_zod.z.string().regex(/^[0-9a-f]{64}$/).optional(),
|
|
1462
1518
|
skeletonTemplate: import_zod.z.string().min(1).optional(),
|
|
1463
1519
|
description: import_zod.z.string().min(1).optional(),
|
|
1520
|
+
removalTask: import_zod.z.enum(["simple", "complex"]).optional(),
|
|
1521
|
+
driftThreshold: import_zod.z.number().min(0).optional(),
|
|
1522
|
+
subjectDescription: import_zod.z.string().min(1).max(300).optional(),
|
|
1523
|
+
initialPose: import_zod.z.string().min(1).max(300).optional(),
|
|
1464
1524
|
engine: import_zod.z.enum(["pro-flash"]).optional()
|
|
1465
1525
|
}).strict().nullable().default(null),
|
|
1466
1526
|
/** For a mirror: the asset it flips and a hash over that asset's output hashes when this was made. */
|
|
@@ -1889,6 +1949,7 @@ var CorrectPixelartResponseSchema = import_zod3.z.object({
|
|
|
1889
1949
|
images: import_zod3.z.array(import_zod3.z.object({ base64: import_zod3.z.string().min(1) }).passthrough()).min(1),
|
|
1890
1950
|
usage: import_zod3.z.unknown().optional()
|
|
1891
1951
|
}).passthrough();
|
|
1952
|
+
var RemoveBackgroundResponseSchema = import_zod3.z.object({ image: import_zod3.z.object({ base64: import_zod3.z.string().min(1) }).passthrough(), usage: import_zod3.z.unknown().optional() }).passthrough();
|
|
1892
1953
|
var ProFlashImageSubmitSchema = import_zod3.z.object({
|
|
1893
1954
|
background_job_id: import_zod3.z.string().min(1),
|
|
1894
1955
|
image_id: import_zod3.z.string().min(1),
|
|
@@ -2080,9 +2141,9 @@ var PixelLabClient = class {
|
|
|
2080
2141
|
apiKey;
|
|
2081
2142
|
http;
|
|
2082
2143
|
/** Retries transport failures, 429, and 5xx; see http.ts for the policy. */
|
|
2083
|
-
async request(
|
|
2144
|
+
async request(path38, init) {
|
|
2084
2145
|
const auth = this.apiKey.startsWith("Bearer ") ? this.apiKey : `Bearer ${this.apiKey}`;
|
|
2085
|
-
const res = await this.http(`${BASE}${
|
|
2146
|
+
const res = await this.http(`${BASE}${path38}`, {
|
|
2086
2147
|
...init,
|
|
2087
2148
|
headers: {
|
|
2088
2149
|
Authorization: auth,
|
|
@@ -2092,13 +2153,13 @@ var PixelLabClient = class {
|
|
|
2092
2153
|
});
|
|
2093
2154
|
const text = await res.text();
|
|
2094
2155
|
if (!res.ok) {
|
|
2095
|
-
throw new PixelLabError(`${init?.method ?? "GET"} ${
|
|
2156
|
+
throw new PixelLabError(`${init?.method ?? "GET"} ${path38} \u2192 ${res.status}`, res.status, text);
|
|
2096
2157
|
}
|
|
2097
2158
|
if (!text) return {};
|
|
2098
2159
|
try {
|
|
2099
2160
|
return JSON.parse(text);
|
|
2100
2161
|
} catch {
|
|
2101
|
-
throw new Error(`${init?.method ?? "GET"} ${
|
|
2162
|
+
throw new Error(`${init?.method ?? "GET"} ${path38} returned invalid JSON`);
|
|
2102
2163
|
}
|
|
2103
2164
|
}
|
|
2104
2165
|
async balance() {
|
|
@@ -2503,10 +2564,10 @@ var PixelLabClient = class {
|
|
|
2503
2564
|
const palette = args.paletteSwatchBase64 ? { color_image: { type: "base64", base64: args.paletteSwatchBase64, format: "png" }, force_colors: true } : {};
|
|
2504
2565
|
const encode = (image) => ({ type: "base64", base64: image.base64, format: image.format });
|
|
2505
2566
|
const south = args.reference?.south;
|
|
2506
|
-
let
|
|
2567
|
+
let path38;
|
|
2507
2568
|
let body;
|
|
2508
2569
|
if (args.mode === "v3") {
|
|
2509
|
-
|
|
2570
|
+
path38 = "/create-character-v3";
|
|
2510
2571
|
body = {
|
|
2511
2572
|
description: args.description,
|
|
2512
2573
|
image_size: imageSize,
|
|
@@ -2520,7 +2581,7 @@ var PixelLabClient = class {
|
|
|
2520
2581
|
...args.enhancePrompt ? { enhance_prompt: true } : {}
|
|
2521
2582
|
};
|
|
2522
2583
|
} else if (args.mode === "pro-flash") {
|
|
2523
|
-
|
|
2584
|
+
path38 = "/create-character-pro-flash";
|
|
2524
2585
|
const traits = args.styleTraits;
|
|
2525
2586
|
body = {
|
|
2526
2587
|
description: args.description,
|
|
@@ -2544,7 +2605,7 @@ var PixelLabClient = class {
|
|
|
2544
2605
|
} : {}
|
|
2545
2606
|
};
|
|
2546
2607
|
} else if (args.mode === "pro") {
|
|
2547
|
-
|
|
2608
|
+
path38 = "/create-character-pro";
|
|
2548
2609
|
body = {
|
|
2549
2610
|
description: args.description,
|
|
2550
2611
|
image_size: imageSize,
|
|
@@ -2558,7 +2619,7 @@ var PixelLabClient = class {
|
|
|
2558
2619
|
...!south && args.styleCharacterId ? { style_character_id: args.styleCharacterId } : {}
|
|
2559
2620
|
};
|
|
2560
2621
|
} else {
|
|
2561
|
-
|
|
2622
|
+
path38 = args.directions === 4 ? "/create-character-with-4-directions" : "/create-character-with-8-directions";
|
|
2562
2623
|
body = {
|
|
2563
2624
|
description: args.description,
|
|
2564
2625
|
image_size: imageSize,
|
|
@@ -2575,8 +2636,8 @@ var PixelLabClient = class {
|
|
|
2575
2636
|
...palette
|
|
2576
2637
|
};
|
|
2577
2638
|
}
|
|
2578
|
-
const raw = await this.request(
|
|
2579
|
-
return validateResponse(CharacterSubmitSchema, raw,
|
|
2639
|
+
const raw = await this.request(path38, { method: "POST", body: JSON.stringify(body) });
|
|
2640
|
+
return validateResponse(CharacterSubmitSchema, raw, path38.slice(1));
|
|
2580
2641
|
}
|
|
2581
2642
|
/** A text edit of an existing character, applied to every direction and stored as a sibling. */
|
|
2582
2643
|
async createCharacterState(args) {
|
|
@@ -2953,6 +3014,27 @@ var PixelLabClient = class {
|
|
|
2953
3014
|
const pngs = cleanupResults(res.images, images.length, "correct-pixelart");
|
|
2954
3015
|
return { png: pngs[0], pngs, usage: res.usage };
|
|
2955
3016
|
}
|
|
3017
|
+
/**
|
|
3018
|
+
* `/remove-background`: a transparent PNG from one pixel-art image,
|
|
3019
|
+
* synchronously, like the rest of the Cleanup tier. The API takes at most
|
|
3020
|
+
* 400x400 worth of area, one image per call. `simple` is faster and suits
|
|
3021
|
+
* flat backgrounds; `complex` is slower and handles detailed edges. The
|
|
3022
|
+
* schema's response example is dollar-denominated (`usd: 0.01`); the
|
|
3023
|
+
* subscription cost measured 1 generation (docs/ENDPOINTS.md), and the
|
|
3024
|
+
* default `simple` task is the one measured.
|
|
3025
|
+
*/
|
|
3026
|
+
async removeBackground(args) {
|
|
3027
|
+
const body = { image: args.image, image_size: { width: args.width, height: args.height } };
|
|
3028
|
+
if (args.task) body.background_removal_task = args.task === "complex" ? "remove_complex_background" : "remove_simple_background";
|
|
3029
|
+
if (args.text) body.text = args.text;
|
|
3030
|
+
if (args.seed != null) body.seed = args.seed;
|
|
3031
|
+
const res = validateResponse(
|
|
3032
|
+
RemoveBackgroundResponseSchema,
|
|
3033
|
+
await this.request("/remove-background", { method: "POST", body: JSON.stringify(body) }),
|
|
3034
|
+
"remove-background"
|
|
3035
|
+
);
|
|
3036
|
+
return { png: Buffer.from(res.image.base64, "base64"), usage: res.usage };
|
|
3037
|
+
}
|
|
2956
3038
|
/**
|
|
2957
3039
|
* `/unzoom`: recover native-resolution pixel art from an upscaled image (a
|
|
2958
3040
|
* 32x32 sprite saved at 512x512). Synchronous like the Cleanup tier.
|
|
@@ -3061,6 +3143,7 @@ var PixelLabClient = class {
|
|
|
3061
3143
|
*/
|
|
3062
3144
|
async animateWithTextV3(args) {
|
|
3063
3145
|
const body = { first_frame: args.firstFrame, action: args.action };
|
|
3146
|
+
if (args.driftThreshold != null) body.drift_threshold = args.driftThreshold;
|
|
3064
3147
|
if (args.lastFrame) body.last_frame = args.lastFrame;
|
|
3065
3148
|
if (args.frameCount != null) body.frame_count = args.frameCount;
|
|
3066
3149
|
if (args.seed != null) body.seed = args.seed;
|
|
@@ -3080,6 +3163,10 @@ var PixelLabClient = class {
|
|
|
3080
3163
|
*/
|
|
3081
3164
|
async animatePixminimax(args) {
|
|
3082
3165
|
const body = { first_frame: args.firstFrame, description: args.description };
|
|
3166
|
+
if (args.view) body.view = args.view;
|
|
3167
|
+
if (args.subjectDescription) body.subject_description = args.subjectDescription;
|
|
3168
|
+
if (args.initialPose) body.initial_pose = args.initialPose;
|
|
3169
|
+
if (args.driftThreshold != null) body.drift_threshold = args.driftThreshold;
|
|
3083
3170
|
if (args.lastFrame) body.last_frame = args.lastFrame;
|
|
3084
3171
|
if (args.frameCount != null) body.frame_count = args.frameCount;
|
|
3085
3172
|
if (args.seed != null) body.seed = args.seed;
|
|
@@ -3115,6 +3202,7 @@ var PixelLabClient = class {
|
|
|
3115
3202
|
keypoints: args.keypoints,
|
|
3116
3203
|
direction: args.direction
|
|
3117
3204
|
};
|
|
3205
|
+
if (args.view) body.view = args.view;
|
|
3118
3206
|
if (args.templateId) body.template_id = args.templateId;
|
|
3119
3207
|
if (args.action) body.action = args.action;
|
|
3120
3208
|
if (args.description) body.description = args.description;
|
|
@@ -3762,10 +3850,10 @@ var import_node_fs = require("fs");
|
|
|
3762
3850
|
function sha256(data) {
|
|
3763
3851
|
return (0, import_node_crypto.createHash)("sha256").update(data).digest("hex");
|
|
3764
3852
|
}
|
|
3765
|
-
async function sha256File(
|
|
3853
|
+
async function sha256File(path38) {
|
|
3766
3854
|
return new Promise((resolve, reject) => {
|
|
3767
3855
|
const hash = (0, import_node_crypto.createHash)("sha256");
|
|
3768
|
-
const stream = (0, import_node_fs.createReadStream)(
|
|
3856
|
+
const stream = (0, import_node_fs.createReadStream)(path38);
|
|
3769
3857
|
stream.on("data", (chunk2) => hash.update(chunk2));
|
|
3770
3858
|
stream.on("error", reject);
|
|
3771
3859
|
stream.on("end", () => resolve(hash.digest("hex")));
|
|
@@ -3889,6 +3977,10 @@ function specHash(spec, styleImageHashes, providerOptionIdentity = spec.provider
|
|
|
3889
3977
|
keypointsSha256: spec.revision.keypointsSha256,
|
|
3890
3978
|
skeletonTemplate: spec.revision.skeletonTemplate,
|
|
3891
3979
|
description: spec.revision.description,
|
|
3980
|
+
removalTask: spec.revision.removalTask,
|
|
3981
|
+
driftThreshold: spec.revision.driftThreshold,
|
|
3982
|
+
subjectDescription: spec.revision.subjectDescription,
|
|
3983
|
+
initialPose: spec.revision.initialPose,
|
|
3892
3984
|
engine: spec.revision.engine
|
|
3893
3985
|
} : void 0
|
|
3894
3986
|
})
|
|
@@ -4064,7 +4156,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
4064
4156
|
* outpaint path yet.
|
|
4065
4157
|
*/
|
|
4066
4158
|
supportsRevision(mode2) {
|
|
4067
|
-
return mode2 === "inpaint" || mode2 === "image-to-image" || mode2 === "reduce-colors" || mode2 === "correct-pixelart" || mode2 === "animate" || mode2 === "animate-pixminimax" || mode2 === "animate-skeleton" || mode2 === "interpolate" || mode2 === "edit-animation";
|
|
4159
|
+
return mode2 === "inpaint" || mode2 === "image-to-image" || mode2 === "reduce-colors" || mode2 === "correct-pixelart" || mode2 === "remove-background" || mode2 === "animate" || mode2 === "animate-pixminimax" || mode2 === "animate-skeleton" || mode2 === "interpolate" || mode2 === "edit-animation";
|
|
4068
4160
|
}
|
|
4069
4161
|
/** PixelLab's own constraints: submissions must be >2s apart, and
|
|
4070
4162
|
* background jobs in flight are capped by subscription tier (Tier 1=8,
|
|
@@ -4092,6 +4184,9 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
4092
4184
|
if (spec.batch?.role === "member") {
|
|
4093
4185
|
return { unit: "generations", amount: 0, candidates: 1 };
|
|
4094
4186
|
}
|
|
4187
|
+
if (spec.revision?.mode === "remove-background") {
|
|
4188
|
+
return { unit: "generations", amount: 1, candidates: 1 };
|
|
4189
|
+
}
|
|
4095
4190
|
if (spec.revision?.mode === "reduce-colors" || spec.revision?.mode === "correct-pixelart") {
|
|
4096
4191
|
return { unit: "generations", amount: 0.1, candidates: 1 };
|
|
4097
4192
|
}
|
|
@@ -4180,6 +4275,12 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
4180
4275
|
throw new Error(`PixelLab correct-pixelart source is ${width}x${height}; the API takes at most 1024 pixels per side`);
|
|
4181
4276
|
}
|
|
4182
4277
|
}
|
|
4278
|
+
if (spec.revision?.mode === "remove-background") {
|
|
4279
|
+
const { sourceWidth: width, sourceHeight: height } = spec.revision;
|
|
4280
|
+
if (width != null && height != null && (width > 400 || height > 400)) {
|
|
4281
|
+
throw new Error(`PixelLab remove-background source is ${width}x${height}; the API takes at most 400x400`);
|
|
4282
|
+
}
|
|
4283
|
+
}
|
|
4183
4284
|
if (spec.revision?.mode === "animate-skeleton") {
|
|
4184
4285
|
const { sourceWidth: width, sourceHeight: height, frames, skeletonTemplate } = spec.revision;
|
|
4185
4286
|
if (width != null && height != null && (width > 256 || height > 256)) {
|
|
@@ -4200,6 +4301,9 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
4200
4301
|
if (mode2 === "animate" && frames != null && frames > 16) {
|
|
4201
4302
|
throw new Error(`PixelLab animate takes 4 to 16 frames; ${frames} is too many (use animate-pixminimax for up to 40)`);
|
|
4202
4303
|
}
|
|
4304
|
+
if (mode2 === "animate-pixminimax" && frames != null && frames % 4 !== 0) {
|
|
4305
|
+
throw new Error(`PixelLab animate-pixminimax takes a multiple of 4 frames (4 to 40); ${frames} is not one`);
|
|
4306
|
+
}
|
|
4203
4307
|
if (lastFrameWidth != null && lastFrameHeight != null && width != null && height != null && (lastFrameWidth !== width || lastFrameHeight !== height)) {
|
|
4204
4308
|
throw new Error(
|
|
4205
4309
|
`PixelLab ${mode2} lastFrame is ${lastFrameWidth}x${lastFrameHeight}; source is ${width}x${height} \u2014 they must match`
|
|
@@ -5291,6 +5395,18 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5291
5395
|
const res2 = await this.client.correctPixelart({ images, strength: revision.strength });
|
|
5292
5396
|
return this.cacheCleanupResult(res2.pngs, res2.usage, roles, revision.sourceFps);
|
|
5293
5397
|
}
|
|
5398
|
+
if (revision.mode === "remove-background") {
|
|
5399
|
+
if (width == null || height == null) throw new Error(`${spec.styleId}/${spec.assetId}: remove-background needs the source's size`);
|
|
5400
|
+
const res2 = await this.client.removeBackground({
|
|
5401
|
+
image,
|
|
5402
|
+
width,
|
|
5403
|
+
height,
|
|
5404
|
+
task: revision.removalTask,
|
|
5405
|
+
text: revision.description,
|
|
5406
|
+
seed: spec.seed
|
|
5407
|
+
});
|
|
5408
|
+
return this.cacheCleanupResult([res2.png], res2.usage, roles, revision.sourceFps);
|
|
5409
|
+
}
|
|
5294
5410
|
if (revision.mode === "interpolate") {
|
|
5295
5411
|
const lastFrame = this.readLastFrame(spec);
|
|
5296
5412
|
if (!lastFrame) throw new Error(`${spec.styleId}/${spec.assetId}: interpolate needs lastFrame, the ending keyframe`);
|
|
@@ -5326,7 +5442,8 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5326
5442
|
frameCount: revision.frames,
|
|
5327
5443
|
seed: spec.seed,
|
|
5328
5444
|
noBackground: spec.noBackground,
|
|
5329
|
-
enhancePrompt: revision.enhancePrompt
|
|
5445
|
+
enhancePrompt: revision.enhancePrompt,
|
|
5446
|
+
driftThreshold: revision.driftThreshold
|
|
5330
5447
|
});
|
|
5331
5448
|
return { jobId: res3.background_job_id };
|
|
5332
5449
|
}
|
|
@@ -5338,7 +5455,11 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5338
5455
|
seed: spec.seed,
|
|
5339
5456
|
noBackground: spec.noBackground,
|
|
5340
5457
|
enhancePrompt: revision.enhancePrompt,
|
|
5341
|
-
direction: revision.direction
|
|
5458
|
+
direction: revision.direction,
|
|
5459
|
+
...spec.view === "side" || spec.view === "low top-down" || spec.view === "high top-down" ? { view: spec.view } : {},
|
|
5460
|
+
subjectDescription: revision.subjectDescription,
|
|
5461
|
+
initialPose: revision.initialPose,
|
|
5462
|
+
driftThreshold: revision.driftThreshold
|
|
5342
5463
|
});
|
|
5343
5464
|
return { jobId: res2.background_job_id };
|
|
5344
5465
|
}
|
|
@@ -5355,6 +5476,9 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5355
5476
|
firstFrameKeypoints: revision.skeleton.firstFrameKeypoints,
|
|
5356
5477
|
keypoints: revision.skeleton.frames,
|
|
5357
5478
|
direction: revision.direction,
|
|
5479
|
+
// The style's camera, when it is one this endpoint knows; its enum is
|
|
5480
|
+
// narrower than a style's free-string `view`.
|
|
5481
|
+
...spec.view === "side" || spec.view === "low top-down" || spec.view === "high top-down" ? { view: spec.view } : {},
|
|
5358
5482
|
templateId: revision.skeletonTemplate,
|
|
5359
5483
|
action: spec.prompt,
|
|
5360
5484
|
description: revision.description,
|
|
@@ -5425,7 +5549,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5425
5549
|
}
|
|
5426
5550
|
async poll(jobId, generator, context) {
|
|
5427
5551
|
const revisionMode = context?.spec?.revision?.mode;
|
|
5428
|
-
if (revisionMode === "reduce-colors" || revisionMode === "correct-pixelart") {
|
|
5552
|
+
if (revisionMode === "reduce-colors" || revisionMode === "correct-pixelart" || revisionMode === "remove-background") {
|
|
5429
5553
|
return this.pollCachedRevision(jobId, context);
|
|
5430
5554
|
}
|
|
5431
5555
|
if (revisionMode === "animate" || revisionMode === "animate-pixminimax" || revisionMode === "animate-skeleton" || revisionMode === "interpolate" || revisionMode === "edit-animation") {
|
|
@@ -6133,9 +6257,9 @@ var RetroDiffusionClient = class {
|
|
|
6133
6257
|
if (!response.ok) throw new ProviderError("retrodiffusion", `Retro Diffusion download failed (${response.status})`, { status: response.status });
|
|
6134
6258
|
return Buffer.from(await response.arrayBuffer());
|
|
6135
6259
|
}
|
|
6136
|
-
async call(
|
|
6260
|
+
async call(path38, init = {}) {
|
|
6137
6261
|
if (!this.token) throw new Error("RD_API_KEY is not set");
|
|
6138
|
-
const response = await this.request(`${this.baseUrl}${
|
|
6262
|
+
const response = await this.request(`${this.baseUrl}${path38}`, {
|
|
6139
6263
|
...init,
|
|
6140
6264
|
headers: {
|
|
6141
6265
|
"Content-Type": "application/json",
|
|
@@ -6444,8 +6568,13 @@ function retroError(value) {
|
|
|
6444
6568
|
}
|
|
6445
6569
|
|
|
6446
6570
|
// src/providers/scenario.ts
|
|
6571
|
+
var import_node_crypto3 = require("crypto");
|
|
6572
|
+
var import_node_fs3 = require("fs");
|
|
6573
|
+
var import_node_os2 = __toESM(require("os"), 1);
|
|
6574
|
+
var import_node_path2 = __toESM(require("path"), 1);
|
|
6447
6575
|
var DEFAULT_BASE_URL2 = "https://api.cloud.scenario.com/v1";
|
|
6448
6576
|
var SOURCE_PROTOCOL2 = "scenario:";
|
|
6577
|
+
var SUBMIT_RETRIES = 8;
|
|
6449
6578
|
var PROTECTED_PARAMETERS = /* @__PURE__ */ new Set([
|
|
6450
6579
|
"dryRun",
|
|
6451
6580
|
"height",
|
|
@@ -6492,7 +6621,11 @@ var ScenarioClient = class {
|
|
|
6492
6621
|
async submit(modelId, body, projectId) {
|
|
6493
6622
|
const response = await this.call(
|
|
6494
6623
|
`/generate/custom/${encodeURIComponent(modelId)}`,
|
|
6495
|
-
|
|
6624
|
+
// A 429 here means this account's concurrent-job limit refused the
|
|
6625
|
+
// request, so nothing was accepted or billed; wait it out instead of
|
|
6626
|
+
// failing the asset. The default four retries give up after about half
|
|
6627
|
+
// a minute, before a running job has finished.
|
|
6628
|
+
{ method: "POST", body: JSON.stringify(body), retries: SUBMIT_RETRIES },
|
|
6496
6629
|
{ projectId }
|
|
6497
6630
|
);
|
|
6498
6631
|
const job = response?.job;
|
|
@@ -6500,6 +6633,29 @@ var ScenarioClient = class {
|
|
|
6500
6633
|
if (!jobId) throw new Error("Scenario did not return an async job id");
|
|
6501
6634
|
return jobId;
|
|
6502
6635
|
}
|
|
6636
|
+
/**
|
|
6637
|
+
* Uploads one image (POST /assets) and returns its asset id, which a model's
|
|
6638
|
+
* reference input takes. Free, and idempotent per image: the id is kept by
|
|
6639
|
+
* content hash so a rerun does not upload the same bytes again.
|
|
6640
|
+
*/
|
|
6641
|
+
async uploadImage(image, projectId) {
|
|
6642
|
+
const bytes = Buffer.from(image.base64, "base64");
|
|
6643
|
+
const digest2 = sha256(bytes);
|
|
6644
|
+
const cached = readUploadCache()[uploadCacheKey(this.baseUrl, projectId, digest2)];
|
|
6645
|
+
if (cached) return cached;
|
|
6646
|
+
const response = await this.call(
|
|
6647
|
+
"/assets",
|
|
6648
|
+
{
|
|
6649
|
+
method: "POST",
|
|
6650
|
+
body: JSON.stringify({ image: image.base64, name: `pixelkiln-${digest2.slice(0, 12)}.${image.format === "png" ? "png" : "jpg"}` })
|
|
6651
|
+
},
|
|
6652
|
+
{ projectId }
|
|
6653
|
+
);
|
|
6654
|
+
const id = response?.asset?.id;
|
|
6655
|
+
if (typeof id !== "string" || !id) throw new Error("Scenario did not return an uploaded asset id");
|
|
6656
|
+
writeUploadCache(uploadCacheKey(this.baseUrl, projectId, digest2), id);
|
|
6657
|
+
return id;
|
|
6658
|
+
}
|
|
6503
6659
|
async job(id, projectId) {
|
|
6504
6660
|
const response = await this.call(`/jobs/${encodeURIComponent(id)}`, {}, { projectId });
|
|
6505
6661
|
const raw = response?.job;
|
|
@@ -6626,6 +6782,14 @@ var ScenarioProvider = class _ScenarioProvider {
|
|
|
6626
6782
|
supports(generator) {
|
|
6627
6783
|
return generator === "map";
|
|
6628
6784
|
}
|
|
6785
|
+
/**
|
|
6786
|
+
* An edit is a generation with the parent as a reference image, which the
|
|
6787
|
+
* editing models (GPT Image, Gemini, FLUX) take. Masked inpainting and the
|
|
6788
|
+
* PixelLab-specific modes have no common Scenario input.
|
|
6789
|
+
*/
|
|
6790
|
+
supportsRevision(mode2) {
|
|
6791
|
+
return mode2 === "image-to-image";
|
|
6792
|
+
}
|
|
6629
6793
|
estimate(spec) {
|
|
6630
6794
|
const options = scenarioOptions(spec);
|
|
6631
6795
|
return {
|
|
@@ -6646,11 +6810,31 @@ var ScenarioProvider = class _ScenarioProvider {
|
|
|
6646
6810
|
if (!Number.isInteger(count) || count < 1 || count > 4) {
|
|
6647
6811
|
throw new Error("Scenario numOutputs must be a whole number from 1 to 4");
|
|
6648
6812
|
}
|
|
6649
|
-
if (spec.width
|
|
6650
|
-
throw new Error("Scenario width and height must be
|
|
6813
|
+
if (!Number.isInteger(spec.width) || spec.width < 16 || spec.width > 4096 || !Number.isInteger(spec.height) || spec.height < 16 || spec.height > 4096) {
|
|
6814
|
+
throw new Error("Scenario width and height must be whole numbers from 16 to 4096");
|
|
6651
6815
|
}
|
|
6652
|
-
|
|
6653
|
-
|
|
6816
|
+
const revision = spec.revision;
|
|
6817
|
+
if (revision) {
|
|
6818
|
+
if (revision.sourceMembers?.length) {
|
|
6819
|
+
throw new Error(
|
|
6820
|
+
`Scenario edits one image; ${revision.sourceAssetId} is a ${revision.sourceMembers.length}-member set`
|
|
6821
|
+
);
|
|
6822
|
+
}
|
|
6823
|
+
if (revision.strength != null) {
|
|
6824
|
+
throw new Error("Scenario image-to-image takes no strength; use the model's own parameters");
|
|
6825
|
+
}
|
|
6826
|
+
}
|
|
6827
|
+
const parameter = referenceParameter(options);
|
|
6828
|
+
if ((styleImages.length || revision) && Object.hasOwn(options.parameters ?? {}, parameter)) {
|
|
6829
|
+
throw new Error(
|
|
6830
|
+
`Scenario parameters.${parameter} is the reference-image input and is filled from styleImages and revisions`
|
|
6831
|
+
);
|
|
6832
|
+
}
|
|
6833
|
+
if (styleImages.length + (revision ? 1 : 0) > 1 && options.referenceArray === false) {
|
|
6834
|
+
throw new Error(`Scenario ${parameter} takes one image; use one style image or a revision, not both`);
|
|
6835
|
+
}
|
|
6836
|
+
if (options.referenceParameter != null && (typeof options.referenceParameter !== "string" || !options.referenceParameter.trim())) {
|
|
6837
|
+
throw new Error("Scenario referenceParameter must be a non-empty string");
|
|
6654
6838
|
}
|
|
6655
6839
|
if (options.projectId != null && (typeof options.projectId !== "string" || !options.projectId.trim())) {
|
|
6656
6840
|
throw new Error("Scenario projectId must be a non-empty string");
|
|
@@ -6663,7 +6847,12 @@ var ScenarioProvider = class _ScenarioProvider {
|
|
|
6663
6847
|
async submit(spec, styleImages) {
|
|
6664
6848
|
this.validate(spec, styleImages);
|
|
6665
6849
|
const options = scenarioOptions(spec);
|
|
6666
|
-
const
|
|
6850
|
+
const references = [];
|
|
6851
|
+
if (spec.revision) references.push(readRevisionSource(spec));
|
|
6852
|
+
for (const image of styleImages) references.push({ base64: image.base64, format: image.format });
|
|
6853
|
+
const referenceAssetIds = [];
|
|
6854
|
+
for (const image of references) referenceAssetIds.push(await this.client.uploadImage(image, options.projectId));
|
|
6855
|
+
const body = requestBody(spec, options, referenceAssetIds);
|
|
6667
6856
|
const quote = await this.client.quote(options.modelId, body, options.projectId);
|
|
6668
6857
|
if (quote.total > options.maxComputeUnits + Number.EPSILON) {
|
|
6669
6858
|
throw new Error(
|
|
@@ -6680,7 +6869,8 @@ var ScenarioProvider = class _ScenarioProvider {
|
|
|
6680
6869
|
quotedComputeUnits: quote.total,
|
|
6681
6870
|
creativeUnitsCost: quote.creativeUnitsCost,
|
|
6682
6871
|
costDetails: quote.costDetails,
|
|
6683
|
-
ipDetectionCost: quote.ipDetectionCost
|
|
6872
|
+
ipDetectionCost: quote.ipDetectionCost,
|
|
6873
|
+
...referenceAssetIds.length ? { referenceAssetIds } : {}
|
|
6684
6874
|
}
|
|
6685
6875
|
};
|
|
6686
6876
|
}
|
|
@@ -6702,7 +6892,13 @@ var ScenarioProvider = class _ScenarioProvider {
|
|
|
6702
6892
|
}
|
|
6703
6893
|
const assetIds = scenarioAssetIds(job);
|
|
6704
6894
|
if (!assetIds.length) return { status: "failed", error: "Scenario job returned no asset IDs" };
|
|
6705
|
-
|
|
6895
|
+
let assets;
|
|
6896
|
+
try {
|
|
6897
|
+
assets = await this.assets(assetIds, reference.projectId);
|
|
6898
|
+
} catch (error) {
|
|
6899
|
+
if (error instanceof UnsupportedScenarioOutput) return { status: "failed", error: error.message };
|
|
6900
|
+
throw error;
|
|
6901
|
+
}
|
|
6706
6902
|
if (assets.length > 1) {
|
|
6707
6903
|
return {
|
|
6708
6904
|
status: "review",
|
|
@@ -6757,6 +6953,32 @@ var ScenarioProvider = class _ScenarioProvider {
|
|
|
6757
6953
|
return assets.sort((left, right) => left.outputIndex - right.outputIndex || (jobOrder.get(left.id) ?? Number.MAX_SAFE_INTEGER) - (jobOrder.get(right.id) ?? Number.MAX_SAFE_INTEGER) || left.id.localeCompare(right.id));
|
|
6758
6954
|
}
|
|
6759
6955
|
};
|
|
6956
|
+
function uploadCachePath() {
|
|
6957
|
+
return process.env.SCENARIO_UPLOAD_CACHE ?? import_node_path2.default.join(import_node_os2.default.tmpdir(), "pixelkiln-scenario", "uploads.json");
|
|
6958
|
+
}
|
|
6959
|
+
function uploadCacheKey(baseUrl, projectId, digest2) {
|
|
6960
|
+
return (0, import_node_crypto3.createHash)("sha256").update(`${baseUrl}
|
|
6961
|
+
${projectId ?? ""}
|
|
6962
|
+
${digest2}`).digest("hex");
|
|
6963
|
+
}
|
|
6964
|
+
function readUploadCache() {
|
|
6965
|
+
try {
|
|
6966
|
+
const value = JSON.parse((0, import_node_fs3.readFileSync)(uploadCachePath(), "utf8"));
|
|
6967
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
6968
|
+
} catch {
|
|
6969
|
+
return {};
|
|
6970
|
+
}
|
|
6971
|
+
}
|
|
6972
|
+
function writeUploadCache(key, assetId) {
|
|
6973
|
+
try {
|
|
6974
|
+
const file = uploadCachePath();
|
|
6975
|
+
(0, import_node_fs3.mkdirSync)(import_node_path2.default.dirname(file), { recursive: true });
|
|
6976
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
6977
|
+
(0, import_node_fs3.writeFileSync)(tmp, JSON.stringify({ ...readUploadCache(), [key]: assetId }));
|
|
6978
|
+
(0, import_node_fs3.renameSync)(tmp, file);
|
|
6979
|
+
} catch {
|
|
6980
|
+
}
|
|
6981
|
+
}
|
|
6760
6982
|
function scenarioOptions(spec) {
|
|
6761
6983
|
return spec.providerOptions;
|
|
6762
6984
|
}
|
|
@@ -6772,9 +6994,25 @@ function scenarioBaseUrl(value) {
|
|
|
6772
6994
|
}
|
|
6773
6995
|
return url.href.replace(/\/$/, "");
|
|
6774
6996
|
}
|
|
6775
|
-
function
|
|
6997
|
+
function referenceParameter(options) {
|
|
6998
|
+
return options.referenceParameter?.trim() || "referenceImages";
|
|
6999
|
+
}
|
|
7000
|
+
function readRevisionSource(spec) {
|
|
7001
|
+
const revision = spec.revision;
|
|
7002
|
+
if (!revision.sourceFile || !revision.sourceSha256 || !revision.sourceFormat) {
|
|
7003
|
+
throw new Error(`${spec.styleId}/${spec.assetId}: revision source is not ready`);
|
|
7004
|
+
}
|
|
7005
|
+
const bytes = (0, import_node_fs3.readFileSync)(revision.sourceFile);
|
|
7006
|
+
if (sha256(bytes) !== revision.sourceSha256) {
|
|
7007
|
+
throw new Error(`${spec.styleId}/${spec.assetId}: revision source changed after the manifest was resolved`);
|
|
7008
|
+
}
|
|
7009
|
+
return { base64: bytes.toString("base64"), format: revision.sourceFormat };
|
|
7010
|
+
}
|
|
7011
|
+
function requestBody(spec, options, referenceAssetIds = []) {
|
|
7012
|
+
const parameter = referenceParameter(options);
|
|
6776
7013
|
return {
|
|
6777
7014
|
...options.parameters ?? {},
|
|
7015
|
+
...referenceAssetIds.length ? { [parameter]: options.referenceArray === false ? referenceAssetIds[0] : referenceAssetIds } : {},
|
|
6778
7016
|
prompt: spec.prompt,
|
|
6779
7017
|
width: spec.width,
|
|
6780
7018
|
height: spec.height,
|
|
@@ -6864,9 +7102,11 @@ function scenarioAssetIds(job) {
|
|
|
6864
7102
|
function assetDownloadUrl(asset) {
|
|
6865
7103
|
return asset.originalFileUrl || asset.url;
|
|
6866
7104
|
}
|
|
7105
|
+
var UnsupportedScenarioOutput = class extends Error {
|
|
7106
|
+
};
|
|
6867
7107
|
function assertPngAsset(asset) {
|
|
6868
7108
|
if (asset.originalMimeType && asset.originalMimeType !== MediaType.PNG) {
|
|
6869
|
-
throw new
|
|
7109
|
+
throw new UnsupportedScenarioOutput(
|
|
6870
7110
|
`Scenario asset ${asset.id} is ${asset.originalMimeType}; PixelKiln's Scenario MVP requires PNG output`
|
|
6871
7111
|
);
|
|
6872
7112
|
}
|
|
@@ -6931,9 +7171,9 @@ function redactScenarioError(value, secrets) {
|
|
|
6931
7171
|
}
|
|
6932
7172
|
|
|
6933
7173
|
// src/providers/comfyui.ts
|
|
6934
|
-
var
|
|
7174
|
+
var import_node_crypto4 = require("crypto");
|
|
6935
7175
|
var import_promises = require("fs/promises");
|
|
6936
|
-
var
|
|
7176
|
+
var import_node_path3 = __toESM(require("path"), 1);
|
|
6937
7177
|
var DEFAULT_BASE_URL3 = "http://127.0.0.1:8188";
|
|
6938
7178
|
var BUILTIN_BINDINGS = /* @__PURE__ */ new Set([
|
|
6939
7179
|
"prompt",
|
|
@@ -6960,7 +7200,7 @@ var ComfyUIClient = class {
|
|
|
6960
7200
|
async submit(workflow) {
|
|
6961
7201
|
const value = await this.json("prompt", {
|
|
6962
7202
|
method: "POST",
|
|
6963
|
-
body: JSON.stringify({ prompt: workflow, client_id: (0,
|
|
7203
|
+
body: JSON.stringify({ prompt: workflow, client_id: (0, import_node_crypto4.randomUUID)() })
|
|
6964
7204
|
});
|
|
6965
7205
|
if (!isObject(value)) throw new Error("ComfyUI returned an invalid queue response");
|
|
6966
7206
|
const promptId = value.prompt_id;
|
|
@@ -7070,7 +7310,7 @@ var ComfyUIProvider = class _ComfyUIProvider {
|
|
|
7070
7310
|
}
|
|
7071
7311
|
async resolveOptions(value, context) {
|
|
7072
7312
|
const options = parseOptions(value);
|
|
7073
|
-
const workflowPath =
|
|
7313
|
+
const workflowPath = import_node_path3.default.resolve(context.root, options.workflowFile);
|
|
7074
7314
|
let raw;
|
|
7075
7315
|
try {
|
|
7076
7316
|
raw = await (0, import_promises.readFile)(workflowPath, "utf8");
|
|
@@ -7125,7 +7365,7 @@ var ComfyUIProvider = class _ComfyUIProvider {
|
|
|
7125
7365
|
if (typeof item !== "string") {
|
|
7126
7366
|
throw new Error(`ComfyUI ${label} must be a manifest-relative PNG or JPEG path`);
|
|
7127
7367
|
}
|
|
7128
|
-
const file =
|
|
7368
|
+
const file = import_node_path3.default.resolve(context.root, item);
|
|
7129
7369
|
let bytes;
|
|
7130
7370
|
try {
|
|
7131
7371
|
bytes = await (0, import_promises.readFile)(file);
|
|
@@ -7837,7 +8077,7 @@ function canonical(value) {
|
|
|
7837
8077
|
}
|
|
7838
8078
|
|
|
7839
8079
|
// src/providers/fake.ts
|
|
7840
|
-
var
|
|
8080
|
+
var import_node_crypto5 = require("crypto");
|
|
7841
8081
|
var FAKE_PNG = Buffer.from(
|
|
7842
8082
|
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==",
|
|
7843
8083
|
"base64"
|
|
@@ -7939,7 +8179,7 @@ var FakeProvider = class {
|
|
|
7939
8179
|
}
|
|
7940
8180
|
async download(url) {
|
|
7941
8181
|
if (!url.startsWith("fake://")) throw new Error(`fake provider cannot download ${url}`);
|
|
7942
|
-
const salt = (0,
|
|
8182
|
+
const salt = (0, import_node_crypto5.createHash)("sha256").update(url).digest();
|
|
7943
8183
|
return Buffer.concat([FAKE_PNG, salt.subarray(0, 4)]);
|
|
7944
8184
|
}
|
|
7945
8185
|
async balance() {
|
|
@@ -8087,8 +8327,8 @@ registerProvider({
|
|
|
8087
8327
|
|
|
8088
8328
|
// src/manifest.ts
|
|
8089
8329
|
var import_promises3 = require("fs/promises");
|
|
8090
|
-
var
|
|
8091
|
-
var
|
|
8330
|
+
var import_node_fs6 = require("fs");
|
|
8331
|
+
var import_node_path6 = __toESM(require("path"), 1);
|
|
8092
8332
|
|
|
8093
8333
|
// src/loop-directions.ts
|
|
8094
8334
|
var SINGLE_OUTPUT_FIELDS = ["file", "cell", "remoteId", "source", "sourceByStyle", "outputRole"];
|
|
@@ -8176,15 +8416,15 @@ function isObject2(value) {
|
|
|
8176
8416
|
}
|
|
8177
8417
|
|
|
8178
8418
|
// src/outputs.ts
|
|
8179
|
-
var
|
|
8180
|
-
var
|
|
8419
|
+
var import_node_fs5 = require("fs");
|
|
8420
|
+
var import_node_path5 = __toESM(require("path"), 1);
|
|
8181
8421
|
|
|
8182
8422
|
// src/lock.ts
|
|
8183
8423
|
var import_promises2 = require("fs/promises");
|
|
8184
|
-
var
|
|
8185
|
-
var
|
|
8424
|
+
var import_node_fs4 = require("fs");
|
|
8425
|
+
var import_node_path4 = __toESM(require("path"), 1);
|
|
8186
8426
|
async function loadLock(lockPath) {
|
|
8187
|
-
if (!(0,
|
|
8427
|
+
if (!(0, import_node_fs4.existsSync)(lockPath)) return { version: 2, entries: {} };
|
|
8188
8428
|
try {
|
|
8189
8429
|
return parseLock(JSON.parse(await (0, import_promises2.readFile)(lockPath, "utf8")));
|
|
8190
8430
|
} catch (err) {
|
|
@@ -8199,7 +8439,7 @@ var saveQueues = /* @__PURE__ */ new Map();
|
|
|
8199
8439
|
var dirtyPatches = /* @__PURE__ */ new WeakMap();
|
|
8200
8440
|
var dirtyDeletes = /* @__PURE__ */ new WeakMap();
|
|
8201
8441
|
function saveLock(lockPath, lock) {
|
|
8202
|
-
const queueKey =
|
|
8442
|
+
const queueKey = import_node_path4.default.resolve(lockPath);
|
|
8203
8443
|
const previous = saveQueues.get(queueKey) ?? Promise.resolve();
|
|
8204
8444
|
const next = previous.catch(() => {
|
|
8205
8445
|
}).then(() => writeLockNow(lockPath, lock));
|
|
@@ -8211,7 +8451,7 @@ function saveLock(lockPath, lock) {
|
|
|
8211
8451
|
return next;
|
|
8212
8452
|
}
|
|
8213
8453
|
async function writeLockNow(lockPath, lock) {
|
|
8214
|
-
await (0, import_promises2.mkdir)(
|
|
8454
|
+
await (0, import_promises2.mkdir)(import_node_path4.default.dirname(import_node_path4.default.resolve(lockPath)), { recursive: true });
|
|
8215
8455
|
const release = await acquireFileLock(lockPath);
|
|
8216
8456
|
try {
|
|
8217
8457
|
await writeLockWhileHeld(lockPath, lock);
|
|
@@ -8221,7 +8461,7 @@ async function writeLockNow(lockPath, lock) {
|
|
|
8221
8461
|
}
|
|
8222
8462
|
async function writeLockWhileHeld(file, lock) {
|
|
8223
8463
|
let disk = { version: 2, entries: {} };
|
|
8224
|
-
if ((0,
|
|
8464
|
+
if ((0, import_node_fs4.existsSync)(file)) {
|
|
8225
8465
|
try {
|
|
8226
8466
|
disk = parseLock(JSON.parse(await (0, import_promises2.readFile)(file, "utf8")));
|
|
8227
8467
|
} catch (err) {
|
|
@@ -8356,22 +8596,22 @@ function totalSpend(lock, unit = "generations") {
|
|
|
8356
8596
|
|
|
8357
8597
|
// src/outputs.ts
|
|
8358
8598
|
function portableOutputPath(file, manifestDir) {
|
|
8359
|
-
const root =
|
|
8360
|
-
const absolute =
|
|
8361
|
-
const relative =
|
|
8362
|
-
const value =
|
|
8363
|
-
return value.split(
|
|
8599
|
+
const root = import_node_path5.default.resolve(manifestDir);
|
|
8600
|
+
const absolute = import_node_path5.default.isAbsolute(file) ? import_node_path5.default.normalize(file) : import_node_path5.default.resolve(root, file);
|
|
8601
|
+
const relative = import_node_path5.default.relative(root, absolute);
|
|
8602
|
+
const value = import_node_path5.default.isAbsolute(relative) ? absolute : relative;
|
|
8603
|
+
return value.split(import_node_path5.default.sep).join("/");
|
|
8364
8604
|
}
|
|
8365
8605
|
function resolveOutputPath(recordedPath, manifestDir) {
|
|
8366
|
-
if (
|
|
8367
|
-
if (
|
|
8368
|
-
return
|
|
8606
|
+
if (import_node_path5.default.isAbsolute(recordedPath)) return import_node_path5.default.normalize(recordedPath);
|
|
8607
|
+
if (import_node_path5.default.win32.isAbsolute(recordedPath)) return recordedPath;
|
|
8608
|
+
return import_node_path5.default.resolve(manifestDir, recordedPath.split(/[\\/]/).join(import_node_path5.default.sep));
|
|
8369
8609
|
}
|
|
8370
8610
|
function expectedOutputPath(spec, role, index, total, mediaType) {
|
|
8371
8611
|
return memberPath(spec.outFile, role, index, total, mediaType);
|
|
8372
8612
|
}
|
|
8373
8613
|
function memberPath(file, role, index, total, mediaType) {
|
|
8374
|
-
const originalExt =
|
|
8614
|
+
const originalExt = import_node_path5.default.extname(file);
|
|
8375
8615
|
const ext = mediaType ? mediaExtension(mediaType) : originalExt || ".png";
|
|
8376
8616
|
const stem = originalExt ? file.slice(0, -originalExt.length) : file;
|
|
8377
8617
|
if (total === 1) return `${stem}${ext}`;
|
|
@@ -8385,10 +8625,10 @@ function isFrameSetEntry(entry) {
|
|
|
8385
8625
|
return entry.generator === "frames" && isMemberSetEntry(entry);
|
|
8386
8626
|
}
|
|
8387
8627
|
function sourceIsStem(source, entry, manifestDir) {
|
|
8388
|
-
return isMemberSetEntry(entry) && !(0,
|
|
8628
|
+
return isMemberSetEntry(entry) && !(0, import_node_fs5.existsSync)(import_node_path5.default.resolve(manifestDir, source));
|
|
8389
8629
|
}
|
|
8390
8630
|
function sourceOutputPath(source, entry, index, manifestDir) {
|
|
8391
|
-
const absolute =
|
|
8631
|
+
const absolute = import_node_path5.default.resolve(manifestDir, source);
|
|
8392
8632
|
if (!sourceIsStem(source, entry, manifestDir)) return absolute;
|
|
8393
8633
|
const output = entry.outputs[index];
|
|
8394
8634
|
return memberPath(absolute, output.role, index, entry.outputs.length, output.mediaType);
|
|
@@ -8475,7 +8715,7 @@ function resolveSpecOutputs(spec, lock, manifestDir) {
|
|
|
8475
8715
|
id: spec.assetId,
|
|
8476
8716
|
index: 0,
|
|
8477
8717
|
path: spec.outFile,
|
|
8478
|
-
absolutePath:
|
|
8718
|
+
absolutePath: import_node_path5.default.resolve(spec.outFile),
|
|
8479
8719
|
sha256: ""
|
|
8480
8720
|
}];
|
|
8481
8721
|
}
|
|
@@ -8491,8 +8731,8 @@ function frameSetFps(entry) {
|
|
|
8491
8731
|
|
|
8492
8732
|
// src/manifest.ts
|
|
8493
8733
|
async function loadManifest(manifestPath) {
|
|
8494
|
-
const abs =
|
|
8495
|
-
if (!(0,
|
|
8734
|
+
const abs = import_node_path6.default.resolve(manifestPath);
|
|
8735
|
+
if (!(0, import_node_fs6.existsSync)(abs)) throw new ProjectError(`No manifest at ${abs}`);
|
|
8496
8736
|
const expansion = expandLoopDirections(JSON.parse(await (0, import_promises3.readFile)(abs, "utf8")));
|
|
8497
8737
|
if (expansion.issues.length) {
|
|
8498
8738
|
throw new ProjectError(`Manifest at ${abs} is invalid:
|
|
@@ -8584,7 +8824,7 @@ ${issues}`);
|
|
|
8584
8824
|
${unknownReferences.map((i) => ` ${i}`).join("\n")}`);
|
|
8585
8825
|
}
|
|
8586
8826
|
const loopFamilies = Object.keys(expansion.families).length ? { loopFamilies: expansion.families } : {};
|
|
8587
|
-
return { manifest: parsed.data, root:
|
|
8827
|
+
return { manifest: parsed.data, root: import_node_path6.default.dirname(abs), path: abs, ...loopFamilies };
|
|
8588
8828
|
}
|
|
8589
8829
|
function parentAssetId(asset) {
|
|
8590
8830
|
return asset?.revision?.from ?? asset?.state?.of ?? asset?.animation?.of ?? asset?.portrait?.of ?? asset?.outfit?.of;
|
|
@@ -8685,10 +8925,10 @@ async function resolveSpecs(loaded, filter) {
|
|
|
8685
8925
|
}
|
|
8686
8926
|
const styleImageCache = /* @__PURE__ */ new Map();
|
|
8687
8927
|
async function loadStyleImage(rel, what = "Style image") {
|
|
8688
|
-
const abs =
|
|
8928
|
+
const abs = import_node_path6.default.resolve(root, rel);
|
|
8689
8929
|
let hit = styleImageCache.get(abs);
|
|
8690
8930
|
if (!hit) {
|
|
8691
|
-
if (!(0,
|
|
8931
|
+
if (!(0, import_node_fs6.existsSync)(abs)) throw new Error(`${what} not found: ${abs}`);
|
|
8692
8932
|
const buf = await (0, import_promises3.readFile)(abs);
|
|
8693
8933
|
const metadata = imageMetadata(buf);
|
|
8694
8934
|
if (!metadata) throw new Error(`${what} is not a readable PNG or JPEG: ${abs}`);
|
|
@@ -8715,7 +8955,7 @@ async function resolveSpecs(loaded, filter) {
|
|
|
8715
8955
|
styleImageDimensions.push(loadedImage);
|
|
8716
8956
|
}
|
|
8717
8957
|
const resolvedImages = style.styleImages.map((image) => {
|
|
8718
|
-
const hit = styleImageCache.get(
|
|
8958
|
+
const hit = styleImageCache.get(import_node_path6.default.resolve(root, image.path));
|
|
8719
8959
|
return { base64: hit.base64, width: hit.width, height: hit.height, format: hit.format };
|
|
8720
8960
|
});
|
|
8721
8961
|
const styleSpecs = /* @__PURE__ */ new Map();
|
|
@@ -8816,9 +9056,9 @@ async function resolveSpecs(loaded, filter) {
|
|
|
8816
9056
|
} else {
|
|
8817
9057
|
prompt = [style.promptPrefix, subject, style.promptSuffix].map((p) => p.trim()).filter(Boolean).join(", ");
|
|
8818
9058
|
}
|
|
8819
|
-
const relFile = asset.file ??
|
|
8820
|
-
const outFile =
|
|
8821
|
-
const qualityOutFile = style.quality ?
|
|
9059
|
+
const relFile = asset.file ?? import_node_path6.default.join(asset.category ?? "", `${assetId}.png`);
|
|
9060
|
+
const outFile = import_node_path6.default.resolve(root, style.outDir, relFile);
|
|
9061
|
+
const qualityOutFile = style.quality ? import_node_path6.default.resolve(root, style.quality.outDir, pngPath(relFile)) : void 0;
|
|
8822
9062
|
const tileSize = generator === "tiles" ? size : style.tileSize ?? 32;
|
|
8823
9063
|
const tileVariations = tileFeatureOutputCount(generator === "tiles" ? style.tileFeature : void 0) ?? tileVariationCount(countNumberedDescriptions(prompt));
|
|
8824
9064
|
const terrainTiles = generator === "terrain" ? terrainTileCount(style.terrainTransitionSize) : 0;
|
|
@@ -8913,7 +9153,7 @@ async function resolveSpecs(loaded, filter) {
|
|
|
8913
9153
|
minGridConfidence: style.quality.minGridConfidence,
|
|
8914
9154
|
...style.quality.minTransparency == null ? {} : { minTransparency: style.quality.minTransparency },
|
|
8915
9155
|
...style.quality.fixerRevision ? { fixerRevision: style.quality.fixerRevision } : {},
|
|
8916
|
-
...style.quality.fixerPython ? { fixerPython:
|
|
9156
|
+
...style.quality.fixerPython ? { fixerPython: import_node_path6.default.resolve(root, style.quality.fixerPython) } : {},
|
|
8917
9157
|
...style.quality.fps == null ? {} : { fps: style.quality.fps }
|
|
8918
9158
|
}
|
|
8919
9159
|
} : {},
|
|
@@ -8996,7 +9236,7 @@ async function resolveSpecs(loaded, filter) {
|
|
|
8996
9236
|
const file = memberPath(anchorSpec.outFile, "south", 0, anchorSpec.character.directions, MediaType.PNG);
|
|
8997
9237
|
resolved.character = {
|
|
8998
9238
|
...resolved.character,
|
|
8999
|
-
styleAnchor: { assetId: anchorId, spec: anchorSpec, file, sha256: (0,
|
|
9239
|
+
styleAnchor: { assetId: anchorId, spec: anchorSpec, file, sha256: (0, import_node_fs6.existsSync)(file) ? await sha256File(file) : null }
|
|
9000
9240
|
};
|
|
9001
9241
|
}
|
|
9002
9242
|
const characterParent = asset.state?.of ?? asset.animation?.of ?? asset.portrait?.of;
|
|
@@ -9009,7 +9249,7 @@ async function resolveSpecs(loaded, filter) {
|
|
|
9009
9249
|
parentAssetId: characterParent,
|
|
9010
9250
|
parentSpec,
|
|
9011
9251
|
parentFile,
|
|
9012
|
-
parentSha256: (0,
|
|
9252
|
+
parentSha256: (0, import_node_fs6.existsSync)(parentFile) ? await sha256File(parentFile) : null,
|
|
9013
9253
|
// A state and its animations keep the engine and rotations of the base.
|
|
9014
9254
|
mode: resolved.character.kind === "base" ? resolved.character.mode : parentSpec.character?.mode ?? resolved.character.mode,
|
|
9015
9255
|
directions: resolved.character.kind === "base" ? resolved.character.directions : parentSpec.character?.directions ?? resolved.character.directions
|
|
@@ -9027,7 +9267,7 @@ async function resolveSpecs(loaded, filter) {
|
|
|
9027
9267
|
parentAssetId: characterParent,
|
|
9028
9268
|
parentSpec,
|
|
9029
9269
|
parentFile,
|
|
9030
|
-
parentSha256: (0,
|
|
9270
|
+
parentSha256: (0, import_node_fs6.existsSync)(parentFile) ? await sha256File(parentFile) : null,
|
|
9031
9271
|
// A state and its animations keep the base's own rotation count.
|
|
9032
9272
|
directions: resolved.objectPro.kind === "base" ? resolved.objectPro.directions : parentSpec.objectPro.directions
|
|
9033
9273
|
};
|
|
@@ -9049,7 +9289,7 @@ async function resolveSpecs(loaded, filter) {
|
|
|
9049
9289
|
animation: sourceSpec.character.animation,
|
|
9050
9290
|
outfit: {
|
|
9051
9291
|
reference: {
|
|
9052
|
-
path:
|
|
9292
|
+
path: import_node_path6.default.resolve(root, asset.outfit.reference),
|
|
9053
9293
|
sha256: referenceImage.hash,
|
|
9054
9294
|
width: referenceImage.width,
|
|
9055
9295
|
height: referenceImage.height,
|
|
@@ -9071,23 +9311,23 @@ async function resolveSpecs(loaded, filter) {
|
|
|
9071
9311
|
);
|
|
9072
9312
|
}
|
|
9073
9313
|
const sourceSpec = await finalize(asset.revision.from);
|
|
9074
|
-
const sourceStem = sourceSpec.quality?.outFile ?? (sourceSpec.source ?
|
|
9314
|
+
const sourceStem = sourceSpec.quality?.outFile ?? (sourceSpec.source ? import_node_path6.default.resolve(root, sourceSpec.source) : sourceSpec.outFile);
|
|
9075
9315
|
const sourceMembers = await revisionSourceMembers(sourceStem, `revision source ${asset.revision.from}`);
|
|
9076
9316
|
const sourceFile = sourceMembers?.[0]?.file ?? sourceStem;
|
|
9077
9317
|
const sourceImage = sourceMembers ? { ...sourceMembers[0], hash: sha256(JSON.stringify(sourceMembers.map((member) => member.sha256))) } : await optionalRevisionImage(sourceFile, "revision source");
|
|
9078
9318
|
const sourceFps = sourceSpec.character?.animation?.fps ?? sourceSpec.objectPro?.animation?.fps ?? sourceSpec.revision?.fps ?? sourceSpec.revision?.sourceFps;
|
|
9079
|
-
const maskFile = asset.revision.mask ?
|
|
9319
|
+
const maskFile = asset.revision.mask ? import_node_path6.default.resolve(root, asset.revision.mask) : void 0;
|
|
9080
9320
|
const maskImage = maskFile ? await optionalRevisionImage(maskFile, "revision mask", "png") : null;
|
|
9081
9321
|
if (sourceImage && maskImage && (sourceImage.width !== maskImage.width || sourceImage.height !== maskImage.height)) {
|
|
9082
9322
|
throw new Error(
|
|
9083
9323
|
`Revision mask for ${styleId}/${assetId} is ${maskImage.width}x${maskImage.height}; source ${asset.revision.from} is ${sourceImage.width}x${sourceImage.height}`
|
|
9084
9324
|
);
|
|
9085
9325
|
}
|
|
9086
|
-
const paletteImageFile = asset.revision.paletteImage ?
|
|
9326
|
+
const paletteImageFile = asset.revision.paletteImage ? import_node_path6.default.resolve(root, asset.revision.paletteImage) : void 0;
|
|
9087
9327
|
const paletteImage = paletteImageFile ? await optionalRevisionImage(paletteImageFile, "revision palette image", "png") : null;
|
|
9088
|
-
const lastFrameFile = asset.revision.lastFrame ?
|
|
9328
|
+
const lastFrameFile = asset.revision.lastFrame ? import_node_path6.default.resolve(root, asset.revision.lastFrame) : void 0;
|
|
9089
9329
|
const lastFrame = lastFrameFile ? await optionalRevisionImage(lastFrameFile, "revision last frame") : null;
|
|
9090
|
-
const keypointsFile = asset.revision.keypointsFile ?
|
|
9330
|
+
const keypointsFile = asset.revision.keypointsFile ? import_node_path6.default.resolve(root, asset.revision.keypointsFile) : void 0;
|
|
9091
9331
|
const keypoints = keypointsFile ? await optionalSkeletonSet(keypointsFile) : null;
|
|
9092
9332
|
resolved.revision = {
|
|
9093
9333
|
mode: asset.revision.mode,
|
|
@@ -9141,6 +9381,10 @@ async function resolveSpecs(loaded, filter) {
|
|
|
9141
9381
|
} : {},
|
|
9142
9382
|
...asset.revision.skeletonTemplate ? { skeletonTemplate: asset.revision.skeletonTemplate } : {},
|
|
9143
9383
|
...asset.revision.description ? { description: asset.revision.description } : {},
|
|
9384
|
+
...asset.revision.removalTask ? { removalTask: asset.revision.removalTask } : {},
|
|
9385
|
+
...asset.revision.driftThreshold == null ? {} : { driftThreshold: asset.revision.driftThreshold },
|
|
9386
|
+
...asset.revision.subjectDescription ? { subjectDescription: asset.revision.subjectDescription } : {},
|
|
9387
|
+
...asset.revision.initialPose ? { initialPose: asset.revision.initialPose } : {},
|
|
9144
9388
|
...asset.revision.engine ? { engine: asset.revision.engine } : {}
|
|
9145
9389
|
};
|
|
9146
9390
|
}
|
|
@@ -9250,7 +9494,7 @@ async function resolveSpecs(loaded, filter) {
|
|
|
9250
9494
|
return specs;
|
|
9251
9495
|
}
|
|
9252
9496
|
function pngPath(file) {
|
|
9253
|
-
const extension =
|
|
9497
|
+
const extension = import_node_path6.default.extname(file);
|
|
9254
9498
|
return extension ? `${file.slice(0, -extension.length)}.png` : `${file}.png`;
|
|
9255
9499
|
}
|
|
9256
9500
|
async function resolveMapScene(root, styleId, assetId, generator, scene, provider) {
|
|
@@ -9260,12 +9504,12 @@ async function resolveMapScene(root, styleId, assetId, generator, scene, provide
|
|
|
9260
9504
|
if (!provider.supportsMapScene) {
|
|
9261
9505
|
throw new Error(`assets.${assetId}.scene: provider "${provider.id}" cannot draw into a scene; PixelLab's /map-objects can`);
|
|
9262
9506
|
}
|
|
9263
|
-
const file =
|
|
9507
|
+
const file = import_node_path6.default.resolve(root, scene.image);
|
|
9264
9508
|
const image = await optionalRevisionImage(file, "scene image", "png");
|
|
9265
9509
|
const base = { file, sha256: image?.hash ?? null, width: image?.width ?? null, height: image?.height ?? null };
|
|
9266
9510
|
const placement = scene.placement;
|
|
9267
9511
|
if ("mask" in placement) {
|
|
9268
|
-
const maskFile =
|
|
9512
|
+
const maskFile = import_node_path6.default.resolve(root, placement.mask);
|
|
9269
9513
|
const mask = await optionalRevisionImage(maskFile, "scene mask", "png");
|
|
9270
9514
|
if (image && mask && (image.width !== mask.width || image.height !== mask.height)) {
|
|
9271
9515
|
throw new Error(
|
|
@@ -9280,7 +9524,7 @@ async function resolveMapScene(root, styleId, assetId, generator, scene, provide
|
|
|
9280
9524
|
return "oval" in placement ? { ...base, placement: { type: "oval", fraction: placement.oval } } : { ...base, placement: { type: "rectangle", fraction: placement.rectangle } };
|
|
9281
9525
|
}
|
|
9282
9526
|
async function optionalRevisionImage(file, label, requiredFormat) {
|
|
9283
|
-
if (!(0,
|
|
9527
|
+
if (!(0, import_node_fs6.existsSync)(file)) return null;
|
|
9284
9528
|
const bytes = await (0, import_promises3.readFile)(file);
|
|
9285
9529
|
const metadata = imageMetadata(bytes);
|
|
9286
9530
|
if (!metadata) throw new Error(`${label} is not a readable PNG or JPEG: ${file}`);
|
|
@@ -9290,7 +9534,7 @@ async function optionalRevisionImage(file, label, requiredFormat) {
|
|
|
9290
9534
|
return { hash: sha256(bytes), ...metadata };
|
|
9291
9535
|
}
|
|
9292
9536
|
async function optionalSkeletonSet(file) {
|
|
9293
|
-
if (!(0,
|
|
9537
|
+
if (!(0, import_node_fs6.existsSync)(file)) return null;
|
|
9294
9538
|
const bytes = await (0, import_promises3.readFile)(file);
|
|
9295
9539
|
let json;
|
|
9296
9540
|
try {
|
|
@@ -9301,17 +9545,17 @@ async function optionalSkeletonSet(file) {
|
|
|
9301
9545
|
return { hash: sha256(bytes), skeleton: parseSkeletonSet(json, file) };
|
|
9302
9546
|
}
|
|
9303
9547
|
async function revisionSourceMembers(stem, label) {
|
|
9304
|
-
if ((0,
|
|
9548
|
+
if ((0, import_node_fs6.existsSync)(stem)) return null;
|
|
9305
9549
|
const at = (role) => memberPath(stem, role, 0, 2, MediaType.PNG);
|
|
9306
9550
|
let roles = [];
|
|
9307
9551
|
for (const set of [CHARACTER_DIRECTIONS_8, CHARACTER_DIRECTIONS_4]) {
|
|
9308
|
-
if (set.every((direction) => (0,
|
|
9552
|
+
if (set.every((direction) => (0, import_node_fs6.existsSync)(at(direction)))) {
|
|
9309
9553
|
roles = [...set];
|
|
9310
9554
|
break;
|
|
9311
9555
|
}
|
|
9312
9556
|
}
|
|
9313
9557
|
if (!roles.length) {
|
|
9314
|
-
for (let index = 0; (0,
|
|
9558
|
+
for (let index = 0; (0, import_node_fs6.existsSync)(at(`frame-${String(index).padStart(2, "0")}`)); index++) {
|
|
9315
9559
|
roles.push(`frame-${String(index).padStart(2, "0")}`);
|
|
9316
9560
|
}
|
|
9317
9561
|
}
|
|
@@ -9337,7 +9581,7 @@ async function resolveStyleImages(loaded, styleId) {
|
|
|
9337
9581
|
if (!style) throw new Error(`Unknown style "${styleId}"`);
|
|
9338
9582
|
const out = [];
|
|
9339
9583
|
for (const img of style.styleImages) {
|
|
9340
|
-
const file =
|
|
9584
|
+
const file = import_node_path6.default.resolve(loaded.root, img.path);
|
|
9341
9585
|
const buf = await (0, import_promises3.readFile)(file);
|
|
9342
9586
|
const metadata = imageMetadata(buf);
|
|
9343
9587
|
if (!metadata) throw new Error(`Style image is not a readable PNG or JPEG: ${file}`);
|
|
@@ -9368,7 +9612,7 @@ async function resolveCharacterShape(asset, style, kind, files) {
|
|
|
9368
9612
|
};
|
|
9369
9613
|
if (asset.concept) {
|
|
9370
9614
|
const image = await files.load(asset.concept, "Concept image");
|
|
9371
|
-
shape.concept = { path:
|
|
9615
|
+
shape.concept = { path: import_node_path6.default.resolve(files.root, asset.concept), sha256: image.hash, width: image.width, height: image.height, format: image.format };
|
|
9372
9616
|
}
|
|
9373
9617
|
if (asset.reference) {
|
|
9374
9618
|
const byDirection = typeof asset.reference === "string" ? { south: asset.reference } : asset.reference;
|
|
@@ -9376,7 +9620,7 @@ async function resolveCharacterShape(asset, style, kind, files) {
|
|
|
9376
9620
|
for (const [direction, rel] of Object.entries(byDirection)) {
|
|
9377
9621
|
const image = await files.load(rel, `Reference sprite (${direction})`);
|
|
9378
9622
|
shape.reference[direction] = {
|
|
9379
|
-
path:
|
|
9623
|
+
path: import_node_path6.default.resolve(files.root, rel),
|
|
9380
9624
|
sha256: image.hash,
|
|
9381
9625
|
width: image.width,
|
|
9382
9626
|
height: image.height,
|
|
@@ -9397,7 +9641,7 @@ async function resolveCharacterShape(asset, style, kind, files) {
|
|
|
9397
9641
|
const animationMode = asset.animation.mode ?? (asset.animation.template ? "template" : "v3");
|
|
9398
9642
|
const pose = async (rel, what) => {
|
|
9399
9643
|
const image = await files.load(rel, what);
|
|
9400
|
-
return { path:
|
|
9644
|
+
return { path: import_node_path6.default.resolve(files.root, rel), sha256: image.hash, width: image.width, height: image.height, format: image.format };
|
|
9401
9645
|
};
|
|
9402
9646
|
shape.animation = {
|
|
9403
9647
|
mode: animationMode,
|
|
@@ -9428,7 +9672,7 @@ async function resolveObjectProShape(assetId, asset, style, kind, files) {
|
|
|
9428
9672
|
const south = typeof asset.reference === "string" ? asset.reference : asset.reference.south;
|
|
9429
9673
|
if (south) {
|
|
9430
9674
|
const image = await files.load(south, "Reference sprite");
|
|
9431
|
-
shape.reference = { path:
|
|
9675
|
+
shape.reference = { path: import_node_path6.default.resolve(files.root, south), sha256: image.hash, width: image.width, height: image.height, format: image.format };
|
|
9432
9676
|
}
|
|
9433
9677
|
}
|
|
9434
9678
|
if (asset.state) {
|
|
@@ -9456,7 +9700,7 @@ async function resolveObjectProShape(assetId, asset, style, kind, files) {
|
|
|
9456
9700
|
}
|
|
9457
9701
|
const pose = async (rel, what) => {
|
|
9458
9702
|
const image = await files.load(rel, what);
|
|
9459
|
-
return { path:
|
|
9703
|
+
return { path: import_node_path6.default.resolve(files.root, rel), sha256: image.hash, width: image.width, height: image.height, format: image.format };
|
|
9460
9704
|
};
|
|
9461
9705
|
shape.animation = {
|
|
9462
9706
|
mode: asset.animation.mode ?? "v3",
|
|
@@ -9476,11 +9720,11 @@ function characterDirections(directions) {
|
|
|
9476
9720
|
}
|
|
9477
9721
|
|
|
9478
9722
|
// src/project.ts
|
|
9479
|
-
var
|
|
9723
|
+
var import_node_path14 = __toESM(require("path"), 1);
|
|
9480
9724
|
|
|
9481
9725
|
// src/env.ts
|
|
9482
|
-
var
|
|
9483
|
-
var
|
|
9726
|
+
var import_node_fs7 = require("fs");
|
|
9727
|
+
var import_node_path7 = __toESM(require("path"), 1);
|
|
9484
9728
|
function loadEnvFiles(dir) {
|
|
9485
9729
|
const loaded = [];
|
|
9486
9730
|
for (const [file, vars] of envFiles(dir)) {
|
|
@@ -9494,10 +9738,10 @@ function loadEnvFiles(dir) {
|
|
|
9494
9738
|
function envFiles(dir) {
|
|
9495
9739
|
const out = [];
|
|
9496
9740
|
for (const name of [".env.local", ".env"]) {
|
|
9497
|
-
const file =
|
|
9498
|
-
if (!(0,
|
|
9741
|
+
const file = import_node_path7.default.join(dir, name);
|
|
9742
|
+
if (!(0, import_node_fs7.existsSync)(file)) continue;
|
|
9499
9743
|
try {
|
|
9500
|
-
out.push([file, parseEnv((0,
|
|
9744
|
+
out.push([file, parseEnv((0, import_node_fs7.readFileSync)(file, "utf8"))]);
|
|
9501
9745
|
} catch {
|
|
9502
9746
|
}
|
|
9503
9747
|
}
|
|
@@ -9523,12 +9767,12 @@ function parseEnv(contents) {
|
|
|
9523
9767
|
}
|
|
9524
9768
|
|
|
9525
9769
|
// src/pipeline/plan.ts
|
|
9526
|
-
var
|
|
9527
|
-
var
|
|
9770
|
+
var import_node_fs10 = require("fs");
|
|
9771
|
+
var import_node_path13 = __toESM(require("path"), 1);
|
|
9528
9772
|
|
|
9529
9773
|
// src/pipeline/mirror.ts
|
|
9530
9774
|
var import_promises4 = require("fs/promises");
|
|
9531
|
-
var
|
|
9775
|
+
var import_node_path8 = __toESM(require("path"), 1);
|
|
9532
9776
|
|
|
9533
9777
|
// src/palette.ts
|
|
9534
9778
|
function normalizePalette(values) {
|
|
@@ -9667,7 +9911,7 @@ async function deriveMirror(spec, lock, lockPath, opts = {}) {
|
|
|
9667
9911
|
for (const [index, member] of members.entries()) {
|
|
9668
9912
|
const bytes = flipHorizontal(await (0, import_promises4.readFile)(member.file));
|
|
9669
9913
|
const target = expectedOutputPath(spec, member.role, index, members.length, MediaType.PNG);
|
|
9670
|
-
await (0, import_promises4.mkdir)(
|
|
9914
|
+
await (0, import_promises4.mkdir)(import_node_path8.default.dirname(target), { recursive: true });
|
|
9671
9915
|
const tmp = `${target}.pixelkiln.tmp`;
|
|
9672
9916
|
await (0, import_promises4.writeFile)(tmp, bytes);
|
|
9673
9917
|
await (0, import_promises4.rename)(tmp, target);
|
|
@@ -9729,29 +9973,29 @@ async function deriveMirror(spec, lock, lockPath, opts = {}) {
|
|
|
9729
9973
|
}
|
|
9730
9974
|
|
|
9731
9975
|
// src/pipeline/quality-profile.ts
|
|
9732
|
-
var
|
|
9733
|
-
var
|
|
9976
|
+
var import_node_fs8 = require("fs");
|
|
9977
|
+
var import_node_path11 = __toESM(require("path"), 1);
|
|
9734
9978
|
|
|
9735
9979
|
// src/pipeline/refine.ts
|
|
9736
|
-
var
|
|
9980
|
+
var import_node_path10 = __toESM(require("path"), 1);
|
|
9737
9981
|
var import_node_child_process2 = require("child_process");
|
|
9738
9982
|
var import_promises6 = require("fs/promises");
|
|
9739
|
-
var
|
|
9983
|
+
var import_node_os3 = require("os");
|
|
9740
9984
|
var import_node_util = require("util");
|
|
9741
9985
|
|
|
9742
9986
|
// src/artifacts.ts
|
|
9743
|
-
var
|
|
9744
|
-
var
|
|
9987
|
+
var import_node_path9 = __toESM(require("path"), 1);
|
|
9988
|
+
var import_node_crypto6 = require("crypto");
|
|
9745
9989
|
var import_promises5 = require("fs/promises");
|
|
9746
9990
|
var activeTransactions = /* @__PURE__ */ new Set();
|
|
9747
9991
|
function message(error) {
|
|
9748
9992
|
return error instanceof Error ? error.message : String(error);
|
|
9749
9993
|
}
|
|
9750
9994
|
function digest(data) {
|
|
9751
|
-
return (0,
|
|
9995
|
+
return (0, import_node_crypto6.createHash)("sha256").update(data).digest("hex");
|
|
9752
9996
|
}
|
|
9753
9997
|
function portableRelative(from, to) {
|
|
9754
|
-
return
|
|
9998
|
+
return import_node_path9.default.relative(from, import_node_path9.default.resolve(to)).split(import_node_path9.default.sep).join("/") || ".";
|
|
9755
9999
|
}
|
|
9756
10000
|
function canonical2(value) {
|
|
9757
10001
|
if (value === null || typeof value === "string" || typeof value === "boolean") return value;
|
|
@@ -9801,7 +10045,7 @@ function parseArtifactManifest(absolute, data) {
|
|
|
9801
10045
|
return raw;
|
|
9802
10046
|
}
|
|
9803
10047
|
async function readArtifactBundleManifest(manifestPath) {
|
|
9804
|
-
const absolute =
|
|
10048
|
+
const absolute = import_node_path9.default.resolve(manifestPath);
|
|
9805
10049
|
return parseArtifactManifest(absolute, await (0, import_promises5.readFile)(absolute, "utf8"));
|
|
9806
10050
|
}
|
|
9807
10051
|
async function readOptional(file) {
|
|
@@ -9825,8 +10069,8 @@ function processIsAlive(pid) {
|
|
|
9825
10069
|
}
|
|
9826
10070
|
}
|
|
9827
10071
|
function validTemporaryPath(candidate, destination, type) {
|
|
9828
|
-
return
|
|
9829
|
-
`.${
|
|
10072
|
+
return import_node_path9.default.dirname(candidate) === import_node_path9.default.dirname(destination) && import_node_path9.default.basename(candidate).startsWith(
|
|
10073
|
+
`.${import_node_path9.default.basename(destination)}.pixelkiln-${type}-`
|
|
9830
10074
|
);
|
|
9831
10075
|
}
|
|
9832
10076
|
function parseTransaction(journal, data) {
|
|
@@ -9846,7 +10090,7 @@ async function removeTransactionFiles(journal) {
|
|
|
9846
10090
|
await (0, import_promises5.rm)(transactionMarker(journal), { force: true });
|
|
9847
10091
|
}
|
|
9848
10092
|
async function recoverArtifactTransaction(recoveryFile, allowedDestinations) {
|
|
9849
|
-
const journal =
|
|
10093
|
+
const journal = import_node_path9.default.resolve(recoveryFile);
|
|
9850
10094
|
const bytes = await readOptional(journal);
|
|
9851
10095
|
if (!bytes) {
|
|
9852
10096
|
await (0, import_promises5.rm)(transactionMarker(journal), { force: true });
|
|
@@ -9862,7 +10106,7 @@ async function recoverArtifactTransaction(recoveryFile, allowedDestinations) {
|
|
|
9862
10106
|
);
|
|
9863
10107
|
}
|
|
9864
10108
|
for (const entry of transaction.entries) {
|
|
9865
|
-
if (typeof entry.destination !== "string" || typeof entry.stage !== "string" || typeof entry.sha256 !== "string" || entry.backup !== void 0 && typeof entry.backup !== "string" || !allowedDestinations.has(
|
|
10109
|
+
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_path9.default.resolve(entry.destination)) || !validTemporaryPath(entry.stage, entry.destination, "stage") || entry.backup !== void 0 && !validTemporaryPath(entry.backup, entry.destination, "backup")) {
|
|
9866
10110
|
throw new Error(
|
|
9867
10111
|
`Refusing unsafe artifact recovery from ${journal}; its destinations or temporary paths do not match the current bundle.`
|
|
9868
10112
|
);
|
|
@@ -9908,7 +10152,7 @@ async function recoverArtifactTransaction(recoveryFile, allowedDestinations) {
|
|
|
9908
10152
|
await removeTransactionFiles(journal);
|
|
9909
10153
|
}
|
|
9910
10154
|
function createArtifactBundleManifest(manifestPath, outputs, provenance) {
|
|
9911
|
-
const root =
|
|
10155
|
+
const root = import_node_path9.default.dirname(import_node_path9.default.resolve(manifestPath));
|
|
9912
10156
|
const sources = provenance.sources.map((source) => ({
|
|
9913
10157
|
...source,
|
|
9914
10158
|
path: portableRelative(root, source.path)
|
|
@@ -9938,11 +10182,11 @@ function withArtifactManifest(manifestPath, outputs, provenance) {
|
|
|
9938
10182
|
];
|
|
9939
10183
|
}
|
|
9940
10184
|
async function writeManagedArtifactBundle(manifestPath, outputs, provenance, options = {}) {
|
|
9941
|
-
const absoluteManifest =
|
|
10185
|
+
const absoluteManifest = import_node_path9.default.resolve(manifestPath);
|
|
9942
10186
|
const recoveryFile = `${absoluteManifest}.transaction`;
|
|
9943
10187
|
const allowedDestinations = /* @__PURE__ */ new Set([
|
|
9944
10188
|
absoluteManifest,
|
|
9945
|
-
...outputs.map((output) =>
|
|
10189
|
+
...outputs.map((output) => import_node_path9.default.resolve(output.path))
|
|
9946
10190
|
]);
|
|
9947
10191
|
await recoverArtifactTransaction(recoveryFile, allowedDestinations);
|
|
9948
10192
|
const existingManifestBytes = await readOptional(absoluteManifest);
|
|
@@ -9960,13 +10204,13 @@ async function writeManagedArtifactBundle(manifestPath, outputs, provenance, opt
|
|
|
9960
10204
|
}
|
|
9961
10205
|
}
|
|
9962
10206
|
if (!options.force) {
|
|
9963
|
-
const root =
|
|
10207
|
+
const root = import_node_path9.default.dirname(absoluteManifest);
|
|
9964
10208
|
const recorded = new Map(
|
|
9965
|
-
previous?.outputs.map((output) => [
|
|
10209
|
+
previous?.outputs.map((output) => [import_node_path9.default.resolve(root, output.path), output.sha256]) ?? []
|
|
9966
10210
|
);
|
|
9967
10211
|
const conflicts = [];
|
|
9968
10212
|
for (const output of outputs) {
|
|
9969
|
-
const destination =
|
|
10213
|
+
const destination = import_node_path9.default.resolve(output.path);
|
|
9970
10214
|
const current = await readOptional(destination);
|
|
9971
10215
|
if (!current || digest(current) === digest(output.data)) continue;
|
|
9972
10216
|
const expected = recorded.get(destination);
|
|
@@ -9988,15 +10232,15 @@ Pass { force: true } (CLI: --force) to take ownership and replace the complete b
|
|
|
9988
10232
|
);
|
|
9989
10233
|
}
|
|
9990
10234
|
async function verifyArtifactBundle(manifestPath) {
|
|
9991
|
-
const absolute =
|
|
10235
|
+
const absolute = import_node_path9.default.resolve(manifestPath);
|
|
9992
10236
|
const raw = await readArtifactBundleManifest(absolute);
|
|
9993
|
-
const root =
|
|
10237
|
+
const root = import_node_path9.default.dirname(absolute);
|
|
9994
10238
|
const changedSources = [];
|
|
9995
10239
|
const changedOutputs = [];
|
|
9996
10240
|
for (const source of raw.sources) {
|
|
9997
10241
|
let actual = null;
|
|
9998
10242
|
try {
|
|
9999
|
-
actual = digest(await (0, import_promises5.readFile)(
|
|
10243
|
+
actual = digest(await (0, import_promises5.readFile)(import_node_path9.default.resolve(root, source.path)));
|
|
10000
10244
|
} catch (error) {
|
|
10001
10245
|
if (!isCode(error, "ENOENT")) throw error;
|
|
10002
10246
|
}
|
|
@@ -10004,7 +10248,7 @@ async function verifyArtifactBundle(manifestPath) {
|
|
|
10004
10248
|
}
|
|
10005
10249
|
for (const output of raw.outputs) {
|
|
10006
10250
|
try {
|
|
10007
|
-
if (digest(await (0, import_promises5.readFile)(
|
|
10251
|
+
if (digest(await (0, import_promises5.readFile)(import_node_path9.default.resolve(root, output.path))) !== output.sha256) {
|
|
10008
10252
|
changedOutputs.push(output.path);
|
|
10009
10253
|
}
|
|
10010
10254
|
} catch (error) {
|
|
@@ -10060,7 +10304,7 @@ async function writeArtifactBundle(files, options = {}) {
|
|
|
10060
10304
|
if (!files.length) throw new Error("An artifact bundle must contain at least one file.");
|
|
10061
10305
|
const normalized = files.map((file) => {
|
|
10062
10306
|
if (!file.path.trim()) throw new Error("Artifact paths cannot be empty.");
|
|
10063
|
-
return { destination:
|
|
10307
|
+
return { destination: import_node_path9.default.resolve(file.path), data: Buffer.from(file.data) };
|
|
10064
10308
|
});
|
|
10065
10309
|
const destinations = /* @__PURE__ */ new Set();
|
|
10066
10310
|
for (const artifact of normalized) {
|
|
@@ -10088,23 +10332,23 @@ async function writeArtifactBundle(files, options = {}) {
|
|
|
10088
10332
|
}
|
|
10089
10333
|
}
|
|
10090
10334
|
if (!changed.length) return { changed: [], unchanged };
|
|
10091
|
-
const token = (0,
|
|
10335
|
+
const token = (0, import_node_crypto6.randomUUID)();
|
|
10092
10336
|
for (const [index, artifact] of changed.entries()) {
|
|
10093
|
-
const basename =
|
|
10094
|
-
artifact.stage =
|
|
10095
|
-
|
|
10337
|
+
const basename = import_node_path9.default.basename(artifact.destination);
|
|
10338
|
+
artifact.stage = import_node_path9.default.join(
|
|
10339
|
+
import_node_path9.default.dirname(artifact.destination),
|
|
10096
10340
|
`.${basename}.pixelkiln-stage-${token}-${index}`
|
|
10097
10341
|
);
|
|
10098
10342
|
if (artifact.existed) {
|
|
10099
|
-
artifact.backup =
|
|
10100
|
-
|
|
10343
|
+
artifact.backup = import_node_path9.default.join(
|
|
10344
|
+
import_node_path9.default.dirname(artifact.destination),
|
|
10101
10345
|
`.${basename}.pixelkiln-backup-${token}-${index}`
|
|
10102
10346
|
);
|
|
10103
10347
|
}
|
|
10104
10348
|
}
|
|
10105
|
-
const journal = options.recoveryFile ?
|
|
10349
|
+
const journal = options.recoveryFile ? import_node_path9.default.resolve(options.recoveryFile) : null;
|
|
10106
10350
|
if (journal) {
|
|
10107
|
-
await (0, import_promises5.mkdir)(
|
|
10351
|
+
await (0, import_promises5.mkdir)(import_node_path9.default.dirname(journal), { recursive: true });
|
|
10108
10352
|
const transaction = {
|
|
10109
10353
|
format: "pixelkiln-artifact-transaction",
|
|
10110
10354
|
version: 1,
|
|
@@ -10129,7 +10373,7 @@ async function writeArtifactBundle(files, options = {}) {
|
|
|
10129
10373
|
}
|
|
10130
10374
|
try {
|
|
10131
10375
|
for (const [index, artifact] of changed.entries()) {
|
|
10132
|
-
await (0, import_promises5.mkdir)(
|
|
10376
|
+
await (0, import_promises5.mkdir)(import_node_path9.default.dirname(artifact.destination), { recursive: true });
|
|
10133
10377
|
await options.beforeStage?.(artifact.destination, index);
|
|
10134
10378
|
await (0, import_promises5.writeFile)(artifact.stage, artifact.data, { flag: "wx" });
|
|
10135
10379
|
}
|
|
@@ -10395,10 +10639,10 @@ function sameFrameGrid(first, next) {
|
|
|
10395
10639
|
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;
|
|
10396
10640
|
}
|
|
10397
10641
|
async function refineAsset(options) {
|
|
10398
|
-
const source =
|
|
10399
|
-
const output =
|
|
10642
|
+
const source = import_node_path10.default.resolve(options.source);
|
|
10643
|
+
const output = import_node_path10.default.resolve(options.output);
|
|
10400
10644
|
if (source === output) throw new Error("Refinement source and output must be different files.");
|
|
10401
|
-
const record =
|
|
10645
|
+
const record = import_node_path10.default.resolve(qualityRecordPath(output));
|
|
10402
10646
|
const palette = normalizePalette(options.palette).map((color) => color.hex);
|
|
10403
10647
|
const minGridConfidence = requireConfidence(options.minGridConfidence ?? "high");
|
|
10404
10648
|
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.");
|
|
@@ -10421,8 +10665,8 @@ async function refineAsset(options) {
|
|
|
10421
10665
|
);
|
|
10422
10666
|
}
|
|
10423
10667
|
}
|
|
10424
|
-
const temp = await (0, import_promises6.mkdtemp)(
|
|
10425
|
-
const recovered =
|
|
10668
|
+
const temp = await (0, import_promises6.mkdtemp)(import_node_path10.default.join((0, import_node_os3.tmpdir)(), "pixelkiln-refine-"));
|
|
10669
|
+
const recovered = import_node_path10.default.join(temp, "native.png");
|
|
10426
10670
|
const fixerCommand = compatibilityCommand ?? options.fixerPython ?? process.env.PIXELKILN_PIXEL_FIXER_PYTHON ?? "python3";
|
|
10427
10671
|
const args = compatibilityCommand ? [...options.fixerArgsPrefix ?? [], source, "--extract", recovered] : ["-c", PYTHON_FIXER_BRIDGE, source, recovered];
|
|
10428
10672
|
try {
|
|
@@ -10507,18 +10751,18 @@ async function refineFrameSet(options) {
|
|
|
10507
10751
|
if (roles.some((role) => !role.trim()) || new Set(roles).size !== roles.length) {
|
|
10508
10752
|
throw new Error("Frame-set roles must be non-empty and unique.");
|
|
10509
10753
|
}
|
|
10510
|
-
const output =
|
|
10511
|
-
const record =
|
|
10754
|
+
const output = import_node_path10.default.resolve(options.output);
|
|
10755
|
+
const record = import_node_path10.default.resolve(qualityRecordPath(output));
|
|
10512
10756
|
const palette = normalizeRefinementPalette(options.palette);
|
|
10513
10757
|
const finalOutputs = options.sources.map((source, index) => qualityFrameOutputPath(output, source.role, index));
|
|
10514
10758
|
if (new Set(finalOutputs).size !== finalOutputs.length) {
|
|
10515
10759
|
throw new Error("Frame-set roles resolve to duplicate output filenames.");
|
|
10516
10760
|
}
|
|
10517
|
-
const sourceSet = new Set(options.sources.map((source) =>
|
|
10761
|
+
const sourceSet = new Set(options.sources.map((source) => import_node_path10.default.resolve(source.path)));
|
|
10518
10762
|
if (finalOutputs.some((file) => sourceSet.has(file))) {
|
|
10519
10763
|
throw new Error("Frame-set refinement sources and outputs must be different files.");
|
|
10520
10764
|
}
|
|
10521
|
-
const temp = await (0, import_promises6.mkdtemp)(
|
|
10765
|
+
const temp = await (0, import_promises6.mkdtemp)(import_node_path10.default.join((0, import_node_os3.tmpdir)(), "pixelkiln-refine-frames-"));
|
|
10522
10766
|
try {
|
|
10523
10767
|
const frames = [];
|
|
10524
10768
|
const outputs = [];
|
|
@@ -10526,8 +10770,8 @@ async function refineFrameSet(options) {
|
|
|
10526
10770
|
const sourcePaths = [];
|
|
10527
10771
|
let firstOptions = null;
|
|
10528
10772
|
for (const [index, sourceEntry] of options.sources.entries()) {
|
|
10529
|
-
const source =
|
|
10530
|
-
const temporaryOutput =
|
|
10773
|
+
const source = import_node_path10.default.resolve(sourceEntry.path);
|
|
10774
|
+
const temporaryOutput = import_node_path10.default.join(temp, `${String(index).padStart(2, "0")}.png`);
|
|
10531
10775
|
const refined = await refineAsset({
|
|
10532
10776
|
source,
|
|
10533
10777
|
output: temporaryOutput,
|
|
@@ -10602,7 +10846,7 @@ async function refineFrameSet(options) {
|
|
|
10602
10846
|
);
|
|
10603
10847
|
return {
|
|
10604
10848
|
sources: sourcePaths,
|
|
10605
|
-
outputs: outputs.map((item) =>
|
|
10849
|
+
outputs: outputs.map((item) => import_node_path10.default.resolve(item.path)),
|
|
10606
10850
|
record,
|
|
10607
10851
|
frames,
|
|
10608
10852
|
palette,
|
|
@@ -10613,12 +10857,12 @@ async function refineFrameSet(options) {
|
|
|
10613
10857
|
}
|
|
10614
10858
|
}
|
|
10615
10859
|
async function checkQualityRecord(recordPath) {
|
|
10616
|
-
const record =
|
|
10860
|
+
const record = import_node_path10.default.resolve(recordPath);
|
|
10617
10861
|
const manifest = await readArtifactBundleManifest(record);
|
|
10618
10862
|
const options = parseRecordOptions(manifest, record);
|
|
10619
10863
|
const verification = await verifyArtifactBundle(record);
|
|
10620
|
-
const sources = manifest.sources.map((source2) =>
|
|
10621
|
-
const outputs = manifest.outputs.map((output2) =>
|
|
10864
|
+
const sources = manifest.sources.map((source2) => import_node_path10.default.resolve(import_node_path10.default.dirname(record), source2.path));
|
|
10865
|
+
const outputs = manifest.outputs.map((output2) => import_node_path10.default.resolve(import_node_path10.default.dirname(record), output2.path));
|
|
10622
10866
|
const source = sources[0] ?? "";
|
|
10623
10867
|
const output = outputs[0] ?? "";
|
|
10624
10868
|
const expectedCount = options.frameSet?.count ?? 1;
|
|
@@ -10651,7 +10895,7 @@ async function checkQualityRecord(recordPath) {
|
|
|
10651
10895
|
async function approveQualityRecord(recordPath, approval) {
|
|
10652
10896
|
const reviewer = approval.reviewer.trim();
|
|
10653
10897
|
if (!reviewer) throw new Error("A human reviewer name is required.");
|
|
10654
|
-
const record =
|
|
10898
|
+
const record = import_node_path10.default.resolve(recordPath);
|
|
10655
10899
|
const manifest = await readArtifactBundleManifest(record);
|
|
10656
10900
|
const options = parseRecordOptions(manifest, record);
|
|
10657
10901
|
const verification = await verifyArtifactBundle(record);
|
|
@@ -10672,14 +10916,14 @@ async function approveQualityRecord(recordPath, approval) {
|
|
|
10672
10916
|
`A refinement record must contain exactly ${expectedCount} source(s) and output(s).`
|
|
10673
10917
|
);
|
|
10674
10918
|
}
|
|
10675
|
-
const root =
|
|
10919
|
+
const root = import_node_path10.default.dirname(record);
|
|
10676
10920
|
const outputs = await Promise.all(manifest.outputs.map(async (entry) => {
|
|
10677
|
-
const outputPath =
|
|
10921
|
+
const outputPath = import_node_path10.default.resolve(root, entry.path);
|
|
10678
10922
|
return { path: outputPath, data: await (0, import_promises6.readFile)(outputPath) };
|
|
10679
10923
|
}));
|
|
10680
10924
|
const sources = manifest.sources.map((source) => ({
|
|
10681
10925
|
...source,
|
|
10682
|
-
path:
|
|
10926
|
+
path: import_node_path10.default.resolve(root, source.path)
|
|
10683
10927
|
}));
|
|
10684
10928
|
const approved = {
|
|
10685
10929
|
...options,
|
|
@@ -10711,8 +10955,8 @@ async function qualitySource(spec, lock) {
|
|
|
10711
10955
|
if (spec.generator === "frames") {
|
|
10712
10956
|
return { items: null, reason: "frame-set quality requires ordered downloaded provider outputs" };
|
|
10713
10957
|
}
|
|
10714
|
-
const source =
|
|
10715
|
-
return (0,
|
|
10958
|
+
const source = import_node_path11.default.resolve(spec.root, spec.source);
|
|
10959
|
+
return (0, import_node_fs8.existsSync)(source) ? { items: [{ role: "source", path: source }] } : { items: null, reason: `declared source is missing: ${spec.source}` };
|
|
10716
10960
|
}
|
|
10717
10961
|
const entry = lock.entries[lockKey(spec.styleId, spec.assetId)];
|
|
10718
10962
|
if (!entry) return { items: null, reason: "raw provider output is not in the lockfile" };
|
|
@@ -10741,7 +10985,7 @@ async function qualitySource(spec, lock) {
|
|
|
10741
10985
|
}
|
|
10742
10986
|
if (role) roles.add(role);
|
|
10743
10987
|
const source = currentEntryOutputPath(entry, spec, index);
|
|
10744
|
-
if (!(0,
|
|
10988
|
+
if (!(0, import_node_fs8.existsSync)(source)) return { items: null, reason: `raw provider output is missing: ${source}` };
|
|
10745
10989
|
if (await sha256File(source) !== output.sha256) {
|
|
10746
10990
|
return { items: null, reason: "raw provider output was modified after download" };
|
|
10747
10991
|
}
|
|
@@ -10760,7 +11004,7 @@ async function inspectQualityProfile(spec, lock) {
|
|
|
10760
11004
|
}
|
|
10761
11005
|
const sourcePaths = source.items.map((item) => item.path);
|
|
10762
11006
|
const expectedOutputs = spec.generator === "frames" ? source.items.map((item, index) => qualityFrameOutputPath(output, item.role, index)) : [output];
|
|
10763
|
-
if (!(0,
|
|
11007
|
+
if (!(0, import_node_fs8.existsSync)(record)) {
|
|
10764
11008
|
return {
|
|
10765
11009
|
key,
|
|
10766
11010
|
state: "needs-refinement",
|
|
@@ -10778,7 +11022,7 @@ async function inspectQualityProfile(spec, lock) {
|
|
|
10778
11022
|
const expectedRevision = spec.quality.fixerRevision ?? PIXEL_ART_FIXER_REVISION;
|
|
10779
11023
|
const expectedTransparency = spec.quality.minTransparency ?? null;
|
|
10780
11024
|
const expectedFrameSet = spec.generator === "frames" ? { fps: spec.quality.fps ?? 12, count: source.items.length, roles: source.items.map((item) => item.role) } : null;
|
|
10781
|
-
const contractChanged = JSON.stringify(check.sources.map((item) =>
|
|
11025
|
+
const contractChanged = JSON.stringify(check.sources.map((item) => import_node_path11.default.resolve(item))) !== JSON.stringify(sourcePaths.map((item) => import_node_path11.default.resolve(item))) || JSON.stringify(check.outputs.map((item) => import_node_path11.default.resolve(item))) !== JSON.stringify(expectedOutputs.map((item) => import_node_path11.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);
|
|
10782
11026
|
if (contractChanged) {
|
|
10783
11027
|
return {
|
|
10784
11028
|
key,
|
|
@@ -10941,8 +11185,8 @@ async function requireApprovedQualitySources(specs, lock, assetIds, outputRoles)
|
|
|
10941
11185
|
}
|
|
10942
11186
|
|
|
10943
11187
|
// src/pipeline/revision.ts
|
|
10944
|
-
var
|
|
10945
|
-
var
|
|
11188
|
+
var import_node_fs9 = require("fs");
|
|
11189
|
+
var import_node_path12 = __toESM(require("path"), 1);
|
|
10946
11190
|
async function inspectRevisionReadiness(spec, lock) {
|
|
10947
11191
|
if (spec.mirror) return inspectMirror(spec, lock, /* @__PURE__ */ new Set());
|
|
10948
11192
|
if (spec.character?.kind === "outfit") return inspectOutfit(spec, lock, /* @__PURE__ */ new Set());
|
|
@@ -10958,7 +11202,7 @@ async function inspectStyleAnchor(spec, lock, seen) {
|
|
|
10958
11202
|
const label = `style anchor ${spec.styleId}/${anchor.assetId}`;
|
|
10959
11203
|
const dependency = await inspectParent(anchor.spec, lock, seen);
|
|
10960
11204
|
if (!dependency.ready) return { ready: false, reason: `${label} is not ready: ${dependency.reason}` };
|
|
10961
|
-
if (!anchor.sha256 || !(0,
|
|
11205
|
+
if (!anchor.sha256 || !(0, import_node_fs9.existsSync)(anchor.file)) return { ready: false, reason: `${label} has no generated south-facing file yet` };
|
|
10962
11206
|
if (await sha256File(anchor.file) !== anchor.sha256) {
|
|
10963
11207
|
return { ready: false, reason: `${label} changed after the manifest was resolved` };
|
|
10964
11208
|
}
|
|
@@ -10972,7 +11216,7 @@ async function inspectScene(spec) {
|
|
|
10972
11216
|
const files = [{ label: "scene image", file: scene.file, sha256: scene.sha256 }];
|
|
10973
11217
|
if (scene.placement.type === "mask") files.push({ label: "scene mask", file: scene.placement.file, sha256: scene.placement.sha256 });
|
|
10974
11218
|
for (const { label, file, sha256: sha2562 } of files) {
|
|
10975
|
-
if (!sha2562 || !(0,
|
|
11219
|
+
if (!sha2562 || !(0, import_node_fs9.existsSync)(file)) return { ready: false, reason: `${label} does not exist yet: ${file}` };
|
|
10976
11220
|
if (await sha256File(file) !== sha2562) return { ready: false, reason: `${label} changed after the manifest was resolved` };
|
|
10977
11221
|
}
|
|
10978
11222
|
return { ready: true, reason: "scene is current" };
|
|
@@ -10989,7 +11233,7 @@ async function inspectOutfit(spec, lock, seen) {
|
|
|
10989
11233
|
const label = `outfit source ${spec.styleId}/${character.parentAssetId}`;
|
|
10990
11234
|
const dependency = await inspectParent(character.parentSpec, lock, seen);
|
|
10991
11235
|
if (!dependency.ready) return { ready: false, reason: `${label} is not ready: ${dependency.reason}` };
|
|
10992
|
-
if (!(0,
|
|
11236
|
+
if (!(0, import_node_fs9.existsSync)(outfit.reference.path)) {
|
|
10993
11237
|
return { ready: false, reason: `outfit reference is missing: ${outfit.reference.path}` };
|
|
10994
11238
|
}
|
|
10995
11239
|
if (await sha256File(outfit.reference.path) !== outfit.reference.sha256) {
|
|
@@ -11003,7 +11247,7 @@ async function inspectCharacterParent(spec, lock, seen) {
|
|
|
11003
11247
|
const label = `${character.kind} parent ${spec.styleId}/${character.parentAssetId}`;
|
|
11004
11248
|
const dependency = await inspectParent(parentSpec, lock, seen);
|
|
11005
11249
|
if (!dependency.ready) return { ready: false, reason: `${label} is not ready: ${dependency.reason}` };
|
|
11006
|
-
if (!character.parentSha256 || !character.parentFile || !(0,
|
|
11250
|
+
if (!character.parentSha256 || !character.parentFile || !(0, import_node_fs9.existsSync)(character.parentFile)) {
|
|
11007
11251
|
return { ready: false, reason: `${label} has no generated south-facing file yet` };
|
|
11008
11252
|
}
|
|
11009
11253
|
if (await sha256File(character.parentFile) !== character.parentSha256) {
|
|
@@ -11021,7 +11265,7 @@ async function inspectObjectProParent(spec, lock, seen) {
|
|
|
11021
11265
|
const label = `${object.kind} parent ${spec.styleId}/${object.parentAssetId}`;
|
|
11022
11266
|
const dependency = await inspectParent(parentSpec, lock, seen);
|
|
11023
11267
|
if (!dependency.ready) return { ready: false, reason: `${label} is not ready: ${dependency.reason}` };
|
|
11024
|
-
if (!object.parentSha256 || !object.parentFile || !(0,
|
|
11268
|
+
if (!object.parentSha256 || !object.parentFile || !(0, import_node_fs9.existsSync)(object.parentFile)) {
|
|
11025
11269
|
return { ready: false, reason: `${label} has no generated south-facing file yet` };
|
|
11026
11270
|
}
|
|
11027
11271
|
if (await sha256File(object.parentFile) !== object.parentSha256) {
|
|
@@ -11047,7 +11291,7 @@ async function inspectRevision(spec, lock, seen) {
|
|
|
11047
11291
|
const members = await inspectSourceMembers(spec, lock);
|
|
11048
11292
|
if (!members.ready) return members;
|
|
11049
11293
|
} else {
|
|
11050
|
-
if (!revision.sourceSha256 || !(0,
|
|
11294
|
+
if (!revision.sourceSha256 || !(0, import_node_fs9.existsSync)(revision.sourceFile)) {
|
|
11051
11295
|
return { ready: false, reason: `revision source is missing: ${revision.sourceFile}` };
|
|
11052
11296
|
}
|
|
11053
11297
|
if (await sha256File(revision.sourceFile) !== revision.sourceSha256) {
|
|
@@ -11055,7 +11299,7 @@ async function inspectRevision(spec, lock, seen) {
|
|
|
11055
11299
|
}
|
|
11056
11300
|
}
|
|
11057
11301
|
if (revision.maskFile) {
|
|
11058
|
-
if (!revision.maskSha256 || !(0,
|
|
11302
|
+
if (!revision.maskSha256 || !(0, import_node_fs9.existsSync)(revision.maskFile)) {
|
|
11059
11303
|
return { ready: false, reason: `revision mask is missing: ${revision.maskFile}` };
|
|
11060
11304
|
}
|
|
11061
11305
|
if (await sha256File(revision.maskFile) !== revision.maskSha256) {
|
|
@@ -11063,7 +11307,7 @@ async function inspectRevision(spec, lock, seen) {
|
|
|
11063
11307
|
}
|
|
11064
11308
|
}
|
|
11065
11309
|
if (revision.lastFrameFile) {
|
|
11066
|
-
if (!revision.lastFrameSha256 || !(0,
|
|
11310
|
+
if (!revision.lastFrameSha256 || !(0, import_node_fs9.existsSync)(revision.lastFrameFile)) {
|
|
11067
11311
|
return { ready: false, reason: `revision last frame is missing: ${revision.lastFrameFile}` };
|
|
11068
11312
|
}
|
|
11069
11313
|
if (await sha256File(revision.lastFrameFile) !== revision.lastFrameSha256) {
|
|
@@ -11076,7 +11320,7 @@ async function inspectSourceMembers(spec, lock) {
|
|
|
11076
11320
|
const revision = spec.revision;
|
|
11077
11321
|
const members = revision.sourceMembers;
|
|
11078
11322
|
for (const member of members) {
|
|
11079
|
-
if (!(0,
|
|
11323
|
+
if (!(0, import_node_fs9.existsSync)(member.file)) return { ready: false, reason: `revision source member is missing: ${member.file}` };
|
|
11080
11324
|
if (await sha256File(member.file) !== member.sha256) {
|
|
11081
11325
|
return { ready: false, reason: `revision source member changed after the manifest was resolved: ${member.file}` };
|
|
11082
11326
|
}
|
|
@@ -11085,8 +11329,8 @@ async function inspectSourceMembers(spec, lock) {
|
|
|
11085
11329
|
if (parent.source) return { ready: true, reason: "declared source set is present" };
|
|
11086
11330
|
const entry = lock.entries[lockKey(parent.styleId, parent.assetId)];
|
|
11087
11331
|
if (!entry) return { ready: false, reason: "revision source is not in the lockfile" };
|
|
11088
|
-
const recorded = entry.outputs.map((_, index) =>
|
|
11089
|
-
const found = members.map((member) =>
|
|
11332
|
+
const recorded = entry.outputs.map((_, index) => import_node_path12.default.resolve(currentEntryOutputPath(entry, parent, index))).sort();
|
|
11333
|
+
const found = members.map((member) => import_node_path12.default.resolve(member.file)).sort();
|
|
11090
11334
|
if (recorded.length !== found.length || recorded.some((file, index) => file !== found[index])) {
|
|
11091
11335
|
return {
|
|
11092
11336
|
ready: false,
|
|
@@ -11135,8 +11379,8 @@ async function inspectParent(spec, lock, seen) {
|
|
|
11135
11379
|
};
|
|
11136
11380
|
}
|
|
11137
11381
|
if (spec.source) {
|
|
11138
|
-
const source =
|
|
11139
|
-
return (0,
|
|
11382
|
+
const source = import_node_path12.default.resolve(spec.root, spec.source);
|
|
11383
|
+
return (0, import_node_fs9.existsSync)(source) ? { ready: true, reason: "declared source is present" } : { ready: false, reason: `declared source is missing: ${spec.source}` };
|
|
11140
11384
|
}
|
|
11141
11385
|
const entry = lock.entries[key];
|
|
11142
11386
|
if (!entry) return { ready: false, reason: "parent is not in the lockfile" };
|
|
@@ -11150,7 +11394,7 @@ async function inspectParent(spec, lock, seen) {
|
|
|
11150
11394
|
for (let index = 0; index < entry.outputs.length; index++) {
|
|
11151
11395
|
const output = entry.outputs[index];
|
|
11152
11396
|
const file = currentEntryOutputPath(entry, spec, index);
|
|
11153
|
-
if (!(0,
|
|
11397
|
+
if (!(0, import_node_fs9.existsSync)(file)) return { ready: false, reason: `parent output is missing: ${file}` };
|
|
11154
11398
|
if (await sha256File(file) !== output.sha256) {
|
|
11155
11399
|
return { ready: false, reason: `parent output was modified: ${file}` };
|
|
11156
11400
|
}
|
|
@@ -11214,7 +11458,7 @@ async function buildPlan(specs, lock, opts = {}) {
|
|
|
11214
11458
|
state = "missing";
|
|
11215
11459
|
reason = "--force";
|
|
11216
11460
|
} else if (!entry && spec.source && spec.generator !== "frames") {
|
|
11217
|
-
if ((0,
|
|
11461
|
+
if ((0, import_node_fs10.existsSync)(import_node_path13.default.resolve(spec.root, spec.source))) {
|
|
11218
11462
|
state = "ok";
|
|
11219
11463
|
reason = `placed from ${spec.source}; not generated`;
|
|
11220
11464
|
} else {
|
|
@@ -11222,7 +11466,7 @@ async function buildPlan(specs, lock, opts = {}) {
|
|
|
11222
11466
|
reason = `declared source is missing: ${spec.source}`;
|
|
11223
11467
|
}
|
|
11224
11468
|
} else if (!entry) {
|
|
11225
|
-
if ((0,
|
|
11469
|
+
if ((0, import_node_fs10.existsSync)(spec.outFile)) {
|
|
11226
11470
|
state = "untracked";
|
|
11227
11471
|
reason = "file on disk, no provenance recorded";
|
|
11228
11472
|
} else {
|
|
@@ -11235,7 +11479,7 @@ async function buildPlan(specs, lock, opts = {}) {
|
|
|
11235
11479
|
} else if (spec.mirror && !mirrorCurrent(entry, lock.entries[lockKey(spec.styleId, spec.mirror.sourceAssetId)])) {
|
|
11236
11480
|
state = "stale";
|
|
11237
11481
|
reason = `mirror source ${spec.mirror.sourceAssetId} changed; flip it again (no generation cost)`;
|
|
11238
|
-
} else if (spec.mirror && entry.status === "downloaded" && entry.outputs.some((_output, index) => !(0,
|
|
11482
|
+
} else if (spec.mirror && entry.status === "downloaded" && entry.outputs.some((_output, index) => !(0, import_node_fs10.existsSync)(currentEntryOutputPath(entry, spec, index)))) {
|
|
11239
11483
|
state = "stale";
|
|
11240
11484
|
reason = "mirror output missing on disk; flip it again (no generation cost)";
|
|
11241
11485
|
} else if (spec.character?.kind === "outfit" && entry.outfit?.sourceSha256 !== mirrorSourceHash(lock.entries[lockKey(spec.styleId, spec.character.parentAssetId)] ?? { outputs: [] })) {
|
|
@@ -11263,9 +11507,9 @@ async function buildPlan(specs, lock, opts = {}) {
|
|
|
11263
11507
|
} else if (entry.status !== "downloaded") {
|
|
11264
11508
|
state = "in-flight";
|
|
11265
11509
|
reason = `awaiting ${entry.status}`;
|
|
11266
|
-
} else if (entry.outputs.length === 0 || entry.outputs.some((_output, index) => !(0,
|
|
11510
|
+
} else if (entry.outputs.length === 0 || entry.outputs.some((_output, index) => !(0, import_node_fs10.existsSync)(currentEntryOutputPath(entry, spec, index)))) {
|
|
11267
11511
|
state = "orphaned";
|
|
11268
|
-
reason = entry.outputs.length === 0 ? "no recorded output files" : `missing on disk: ${entry.outputs.filter((_output, index) => !(0,
|
|
11512
|
+
reason = entry.outputs.length === 0 ? "no recorded output files" : `missing on disk: ${entry.outputs.filter((_output, index) => !(0, import_node_fs10.existsSync)(currentEntryOutputPath(entry, spec, index))).map((o) => o.role ?? "asset").join(", ")}`;
|
|
11269
11513
|
} else if (await anyOutputModified(entry, spec)) {
|
|
11270
11514
|
state = "orphaned";
|
|
11271
11515
|
reason = "output modified since download";
|
|
@@ -11356,13 +11600,13 @@ function summarize(plan) {
|
|
|
11356
11600
|
|
|
11357
11601
|
// src/project.ts
|
|
11358
11602
|
async function openProject(manifestPath = "pixelkiln.manifest.json", opts = {}) {
|
|
11359
|
-
const absolute =
|
|
11360
|
-
const root =
|
|
11603
|
+
const absolute = import_node_path14.default.resolve(manifestPath);
|
|
11604
|
+
const root = import_node_path14.default.dirname(absolute);
|
|
11361
11605
|
if (opts.env !== false) {
|
|
11362
11606
|
loadEnvFiles(root);
|
|
11363
|
-
if (
|
|
11607
|
+
if (import_node_path14.default.resolve(process.cwd()) !== root) loadEnvFiles(process.cwd());
|
|
11364
11608
|
}
|
|
11365
|
-
const lockPath =
|
|
11609
|
+
const lockPath = import_node_path14.default.resolve(opts.lockPath ?? import_node_path14.default.join(root, "pixelkiln.lock.json"));
|
|
11366
11610
|
const loaded = await loadManifest(absolute);
|
|
11367
11611
|
const specs = await resolveSpecs(loaded, { styles: opts.styles ?? [], assets: opts.assets ?? [] });
|
|
11368
11612
|
const lock = await loadLock(lockPath);
|
|
@@ -11429,8 +11673,8 @@ function shouldPersistSourceUrl(value) {
|
|
|
11429
11673
|
}
|
|
11430
11674
|
|
|
11431
11675
|
// src/recipes.ts
|
|
11432
|
-
var
|
|
11433
|
-
var
|
|
11676
|
+
var import_node_path15 = __toESM(require("path"), 1);
|
|
11677
|
+
var import_node_fs11 = require("fs");
|
|
11434
11678
|
var import_promises7 = require("fs/promises");
|
|
11435
11679
|
var import_node_url = require("url");
|
|
11436
11680
|
var import_zod4 = require("zod");
|
|
@@ -11440,7 +11684,7 @@ var VERSION_RE = /^\d+\.\d+\.\d+$/;
|
|
|
11440
11684
|
var RECIPE_FILE = "pixelkiln.recipe.json";
|
|
11441
11685
|
var RECIPE_DIR_TOKEN = "{{recipeDir}}";
|
|
11442
11686
|
var PortablePathSchema = import_zod4.z.string().min(1).refine((value) => {
|
|
11443
|
-
if (value.includes("\\") ||
|
|
11687
|
+
if (value.includes("\\") || import_node_path15.default.posix.isAbsolute(value)) return false;
|
|
11444
11688
|
const parts = value.split("/");
|
|
11445
11689
|
return !parts.includes("") && !parts.includes(".") && !parts.includes("..");
|
|
11446
11690
|
}, "expected a portable relative path without '.', '..', or backslashes");
|
|
@@ -11616,7 +11860,7 @@ function message3(error) {
|
|
|
11616
11860
|
return error instanceof Error ? error.message : String(error);
|
|
11617
11861
|
}
|
|
11618
11862
|
async function readRecipeFile(recipePath, bundled) {
|
|
11619
|
-
const absolute =
|
|
11863
|
+
const absolute = import_node_path15.default.resolve(recipePath);
|
|
11620
11864
|
let raw;
|
|
11621
11865
|
try {
|
|
11622
11866
|
raw = JSON.parse(await (0, import_promises7.readFile)(absolute, "utf8"));
|
|
@@ -11630,13 +11874,13 @@ async function readRecipeFile(recipePath, bundled) {
|
|
|
11630
11874
|
` + parsed.error.issues.map((issue) => ` ${issue.path.join(".") || "$"}: ${issue.message}`).join("\n")
|
|
11631
11875
|
);
|
|
11632
11876
|
}
|
|
11633
|
-
return { recipe: parsed.data, authored: raw, path: absolute, dir:
|
|
11877
|
+
return { recipe: parsed.data, authored: raw, path: absolute, dir: import_node_path15.default.dirname(absolute), bundled };
|
|
11634
11878
|
}
|
|
11635
11879
|
async function walkRecipeFiles(root) {
|
|
11636
|
-
if (!(0,
|
|
11880
|
+
if (!(0, import_node_fs11.existsSync)(root)) return [];
|
|
11637
11881
|
const out = [];
|
|
11638
11882
|
for (const entry of await (0, import_promises7.readdir)(root, { withFileTypes: true })) {
|
|
11639
|
-
const resolved =
|
|
11883
|
+
const resolved = import_node_path15.default.join(root, entry.name);
|
|
11640
11884
|
if (entry.isDirectory()) out.push(...await walkRecipeFiles(resolved));
|
|
11641
11885
|
else if (entry.isFile() && entry.name === RECIPE_FILE) out.push(resolved);
|
|
11642
11886
|
}
|
|
@@ -11647,7 +11891,7 @@ function bundledRecipeRoot() {
|
|
|
11647
11891
|
}
|
|
11648
11892
|
async function listBundledRecipes(root = bundledRecipeRoot()) {
|
|
11649
11893
|
const recipes = await Promise.all(
|
|
11650
|
-
(await walkRecipeFiles(
|
|
11894
|
+
(await walkRecipeFiles(import_node_path15.default.resolve(root))).map((file) => readRecipeFile(file, true))
|
|
11651
11895
|
);
|
|
11652
11896
|
return recipes.sort(
|
|
11653
11897
|
(a, b) => a.recipe.id.localeCompare(b.recipe.id) || compareVersions(b.recipe.version, a.recipe.version)
|
|
@@ -11670,9 +11914,9 @@ function parseSelector(selector) {
|
|
|
11670
11914
|
return { id: match[1], version: match[2] };
|
|
11671
11915
|
}
|
|
11672
11916
|
async function resolveRecipe(target, bundledRoot = bundledRecipeRoot()) {
|
|
11673
|
-
const local =
|
|
11674
|
-
if ((0,
|
|
11675
|
-
const recipePath =
|
|
11917
|
+
const local = import_node_path15.default.resolve(target);
|
|
11918
|
+
if ((0, import_node_fs11.existsSync)(local)) {
|
|
11919
|
+
const recipePath = import_node_path15.default.basename(local) === RECIPE_FILE ? local : import_node_path15.default.join(local, RECIPE_FILE);
|
|
11676
11920
|
return readRecipeFile(recipePath, false);
|
|
11677
11921
|
}
|
|
11678
11922
|
if (target.startsWith(".") || target.startsWith("/") || target.includes("\\")) {
|
|
@@ -11686,8 +11930,8 @@ async function resolveRecipe(target, bundledRoot = bundledRecipeRoot()) {
|
|
|
11686
11930
|
return matches[0];
|
|
11687
11931
|
}
|
|
11688
11932
|
async function verifyFile(root, file) {
|
|
11689
|
-
const absolute =
|
|
11690
|
-
if (!(0,
|
|
11933
|
+
const absolute = import_node_path15.default.resolve(root, ...file.path.split("/"));
|
|
11934
|
+
if (!(0, import_node_fs11.existsSync)(absolute)) {
|
|
11691
11935
|
return { path: file.path, expectedSha256: file.sha256, actualSha256: null, status: "missing" };
|
|
11692
11936
|
}
|
|
11693
11937
|
const actualSha256 = await sha256File(absolute);
|
|
@@ -11702,7 +11946,7 @@ async function verifyRecipe(target, options = {}) {
|
|
|
11702
11946
|
const loaded = await resolveRecipe(target, options.bundledRoot);
|
|
11703
11947
|
const actualIntegrity = recipeDigest(loaded.authored);
|
|
11704
11948
|
const files = await Promise.all(loaded.recipe.files.map((file) => verifyFile(loaded.dir, file)));
|
|
11705
|
-
const modelRoot = options.modelRoot ?
|
|
11949
|
+
const modelRoot = options.modelRoot ? import_node_path15.default.resolve(options.modelRoot) : null;
|
|
11706
11950
|
const models = modelRoot ? await Promise.all(loaded.recipe.models.map(async (model) => ({
|
|
11707
11951
|
...await verifyFile(modelRoot, model),
|
|
11708
11952
|
source: model.source,
|
|
@@ -11736,7 +11980,7 @@ async function verifyRecipe(target, options = {}) {
|
|
|
11736
11980
|
};
|
|
11737
11981
|
}
|
|
11738
11982
|
function renderStyle(style, destination, cwd) {
|
|
11739
|
-
const portableDir =
|
|
11983
|
+
const portableDir = import_node_path15.default.relative(cwd, destination).split(import_node_path15.default.sep).join("/") || ".";
|
|
11740
11984
|
const replace = (value) => {
|
|
11741
11985
|
if (typeof value === "string") return value.replaceAll(RECIPE_DIR_TOKEN, portableDir);
|
|
11742
11986
|
if (Array.isArray(value)) return value.map(replace);
|
|
@@ -11748,24 +11992,24 @@ function renderStyle(style, destination, cwd) {
|
|
|
11748
11992
|
return replace(style);
|
|
11749
11993
|
}
|
|
11750
11994
|
async function installRecipe(target, options = {}) {
|
|
11751
|
-
const cwd =
|
|
11995
|
+
const cwd = import_node_path15.default.resolve(options.cwd ?? process.cwd());
|
|
11752
11996
|
const loaded = await resolveRecipe(target, options.bundledRoot);
|
|
11753
11997
|
const verification = await verifyRecipe(loaded.path);
|
|
11754
11998
|
if (!verification.ok) {
|
|
11755
11999
|
throw new Error(`Recipe ${loaded.recipe.id}@${loaded.recipe.version} failed integrity verification.`);
|
|
11756
12000
|
}
|
|
11757
|
-
const destination = options.out ?
|
|
12001
|
+
const destination = options.out ? import_node_path15.default.resolve(cwd, options.out) : import_node_path15.default.join(cwd, "pixelkiln-recipes", ...loaded.recipe.id.split("/"), loaded.recipe.version);
|
|
11758
12002
|
const sources = [
|
|
11759
12003
|
{ path: RECIPE_FILE, source: loaded.path },
|
|
11760
|
-
...loaded.recipe.files.map((file) => ({ path: file.path, source:
|
|
12004
|
+
...loaded.recipe.files.map((file) => ({ path: file.path, source: import_node_path15.default.join(loaded.dir, ...file.path.split("/")) }))
|
|
11761
12005
|
];
|
|
11762
12006
|
const files = await Promise.all(sources.map(async (file) => ({
|
|
11763
|
-
path:
|
|
12007
|
+
path: import_node_path15.default.join(destination, ...file.path.split("/")),
|
|
11764
12008
|
data: await (0, import_promises7.readFile)(file.source)
|
|
11765
12009
|
})));
|
|
11766
12010
|
if (!options.force) {
|
|
11767
12011
|
for (const file of files) {
|
|
11768
|
-
if (!(0,
|
|
12012
|
+
if (!(0, import_node_fs11.existsSync)(file.path)) continue;
|
|
11769
12013
|
const current = await (0, import_promises7.readFile)(file.path);
|
|
11770
12014
|
if (!current.equals(file.data)) {
|
|
11771
12015
|
throw new OverwriteRefusedError(
|
|
@@ -11791,8 +12035,8 @@ async function installRecipe(target, options = {}) {
|
|
|
11791
12035
|
|
|
11792
12036
|
// src/pipeline/audit.ts
|
|
11793
12037
|
var import_promises8 = require("fs/promises");
|
|
11794
|
-
var
|
|
11795
|
-
var
|
|
12038
|
+
var import_node_fs12 = require("fs");
|
|
12039
|
+
var import_node_path16 = __toESM(require("path"), 1);
|
|
11796
12040
|
function paletteDistance(asset, reference) {
|
|
11797
12041
|
if (!asset.length || !reference.length) return 0;
|
|
11798
12042
|
let total = 0;
|
|
@@ -11828,7 +12072,7 @@ async function auditStyle(loaded, specs, styleId, lock) {
|
|
|
11828
12072
|
const unreadable = [];
|
|
11829
12073
|
for (const spec of mine) {
|
|
11830
12074
|
for (const output of resolveSpecOutputs(spec, lock, loaded.root)) {
|
|
11831
|
-
if (!(0,
|
|
12075
|
+
if (!(0, import_node_fs12.existsSync)(output.absolutePath)) {
|
|
11832
12076
|
missing.push(output.id);
|
|
11833
12077
|
continue;
|
|
11834
12078
|
}
|
|
@@ -11857,8 +12101,8 @@ async function auditStyle(loaded, specs, styleId, lock) {
|
|
|
11857
12101
|
let referenceFromStyleImages = false;
|
|
11858
12102
|
const refPalettes = [];
|
|
11859
12103
|
for (const img of style.styleImages) {
|
|
11860
|
-
const abs =
|
|
11861
|
-
if (!(0,
|
|
12104
|
+
const abs = import_node_path16.default.resolve(loaded.root, img.path);
|
|
12105
|
+
if (!(0, import_node_fs12.existsSync)(abs)) continue;
|
|
11862
12106
|
try {
|
|
11863
12107
|
refPalettes.push(extractPalette(decodePng(await (0, import_promises8.readFile)(abs)), 12));
|
|
11864
12108
|
} catch {
|
|
@@ -11936,19 +12180,19 @@ function hex(c) {
|
|
|
11936
12180
|
}
|
|
11937
12181
|
|
|
11938
12182
|
// src/pixellab-utils.ts
|
|
11939
|
-
var
|
|
12183
|
+
var import_node_fs13 = require("fs");
|
|
11940
12184
|
var import_promises9 = require("fs/promises");
|
|
11941
|
-
var
|
|
12185
|
+
var import_node_path17 = __toESM(require("path"), 1);
|
|
11942
12186
|
var UNZOOM_MIN_SIDE = 256;
|
|
11943
12187
|
var UNZOOM_MAX_AREA = 2048 * 2048;
|
|
11944
12188
|
function defaultUnzoomOut(input) {
|
|
11945
|
-
const ext =
|
|
12189
|
+
const ext = import_node_path17.default.extname(input);
|
|
11946
12190
|
return `${ext ? input.slice(0, -ext.length) : input}.unzoomed.png`;
|
|
11947
12191
|
}
|
|
11948
12192
|
async function unzoomFile(client2, input, opts = {}) {
|
|
11949
|
-
const out =
|
|
11950
|
-
if (
|
|
11951
|
-
if ((0,
|
|
12193
|
+
const out = import_node_path17.default.resolve(opts.out ?? defaultUnzoomOut(input));
|
|
12194
|
+
if (import_node_path17.default.resolve(input) === out) throw new Error("unzoom will not overwrite its own input; pass a different --out");
|
|
12195
|
+
if ((0, import_node_fs13.existsSync)(out) && !opts.force) throw new Error(`${out} already exists; pass --force to replace it`);
|
|
11952
12196
|
const bytes = await (0, import_promises9.readFile)(input);
|
|
11953
12197
|
const metadata = imageMetadata(bytes);
|
|
11954
12198
|
if (!metadata) throw new Error(`${input} is not a readable PNG or JPEG`);
|
|
@@ -11962,7 +12206,7 @@ async function unzoomFile(client2, input, opts = {}) {
|
|
|
11962
12206
|
throw new Error(`${input} is ${width}x${height}; PixelLab's unzoom takes at most 2048x2048 worth of pixels`);
|
|
11963
12207
|
}
|
|
11964
12208
|
const res = await client2.unzoom({ image: { base64: bytes.toString("base64"), format }, quantize: opts.quantize });
|
|
11965
|
-
await (0, import_promises9.mkdir)(
|
|
12209
|
+
await (0, import_promises9.mkdir)(import_node_path17.default.dirname(out), { recursive: true });
|
|
11966
12210
|
await (0, import_promises9.writeFile)(out, res.png);
|
|
11967
12211
|
return { out, original: res.originalSize, unzoomed: res.unzoomedSize, zoomFactor: res.zoomFactor, usage: res.usage };
|
|
11968
12212
|
}
|
|
@@ -11970,14 +12214,14 @@ var FONT_WEIGHTS = ["Bold", "Regular"];
|
|
|
11970
12214
|
var FONT_GLYPH_SIZES = [8, 16, 32, 64];
|
|
11971
12215
|
var FONT_COST_GENERATIONS = 25;
|
|
11972
12216
|
function fontOutputPaths(out) {
|
|
11973
|
-
const base =
|
|
12217
|
+
const base = import_node_path17.default.resolve(out.replace(/\.(ttf|png)$/i, ""));
|
|
11974
12218
|
return { ttf: `${base}.ttf`, atlas: `${base}.png` };
|
|
11975
12219
|
}
|
|
11976
12220
|
async function generateFont(client2, opts) {
|
|
11977
12221
|
const { ttf, atlas } = fontOutputPaths(opts.out);
|
|
11978
12222
|
if (!opts.force) {
|
|
11979
12223
|
for (const file of [ttf, atlas]) {
|
|
11980
|
-
if ((0,
|
|
12224
|
+
if ((0, import_node_fs13.existsSync)(file)) throw new Error(`${file} already exists; pass --force to replace it`);
|
|
11981
12225
|
}
|
|
11982
12226
|
}
|
|
11983
12227
|
const sleep3 = opts.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
@@ -12004,7 +12248,7 @@ async function generateFont(client2, opts) {
|
|
|
12004
12248
|
client2.download(job.download_ttf_url),
|
|
12005
12249
|
client2.download(job.download_atlas_url)
|
|
12006
12250
|
]);
|
|
12007
|
-
await (0, import_promises9.mkdir)(
|
|
12251
|
+
await (0, import_promises9.mkdir)(import_node_path17.default.dirname(ttf), { recursive: true });
|
|
12008
12252
|
await (0, import_promises9.writeFile)(ttf, ttfBytes);
|
|
12009
12253
|
await (0, import_promises9.writeFile)(atlas, atlasBytes);
|
|
12010
12254
|
return { jobId, ttf, atlas, glyphPx: job.glyph_px ?? null, usage: job.usage };
|
|
@@ -12019,15 +12263,15 @@ async function generateFont(client2, opts) {
|
|
|
12019
12263
|
}
|
|
12020
12264
|
|
|
12021
12265
|
// src/pipeline/quality-regression.ts
|
|
12022
|
-
var
|
|
12023
|
-
var
|
|
12266
|
+
var import_node_path18 = __toESM(require("path"), 1);
|
|
12267
|
+
var import_node_fs14 = require("fs");
|
|
12024
12268
|
var import_promises10 = require("fs/promises");
|
|
12025
12269
|
var import_zod5 = require("zod");
|
|
12026
12270
|
var SHA256_RE2 = /^[0-9a-f]{64}$/;
|
|
12027
12271
|
var HEX_RE = /^#[0-9a-f]{6}$/;
|
|
12028
12272
|
var MAX_REDMEAN_DISTANCE = 765;
|
|
12029
12273
|
var QualityPathSchema = import_zod5.z.string().min(1).refine(
|
|
12030
|
-
(value) => !
|
|
12274
|
+
(value) => !import_node_path18.default.posix.isAbsolute(value) && !value.includes("\\"),
|
|
12031
12275
|
"expected a portable relative path"
|
|
12032
12276
|
);
|
|
12033
12277
|
var DEFAULT_QUALITY_TOLERANCES = {
|
|
@@ -12174,7 +12418,7 @@ function measureDecodedImageQuality(png, digest2) {
|
|
|
12174
12418
|
};
|
|
12175
12419
|
}
|
|
12176
12420
|
async function measureImageQuality(file) {
|
|
12177
|
-
const absolute =
|
|
12421
|
+
const absolute = import_node_path18.default.resolve(file);
|
|
12178
12422
|
let bytes;
|
|
12179
12423
|
try {
|
|
12180
12424
|
bytes = await (0, import_promises10.readFile)(absolute);
|
|
@@ -12196,11 +12440,11 @@ function resolveQualityInputs(raw, inputsFilePath) {
|
|
|
12196
12440
|
}
|
|
12197
12441
|
const ids = parsed.data.map((entry) => entry.id);
|
|
12198
12442
|
if (new Set(ids).size !== ids.length) throw new Error("--inputs contains a duplicate quality case id.");
|
|
12199
|
-
const root =
|
|
12443
|
+
const root = import_node_path18.default.dirname(import_node_path18.default.resolve(inputsFilePath));
|
|
12200
12444
|
return parsed.data.map((entry) => ({
|
|
12201
12445
|
id: entry.id,
|
|
12202
|
-
file:
|
|
12203
|
-
...entry.record ? { record:
|
|
12446
|
+
file: import_node_path18.default.resolve(root, entry.path),
|
|
12447
|
+
...entry.record ? { record: import_node_path18.default.resolve(root, entry.record) } : {},
|
|
12204
12448
|
tolerances: QualityToleranceSchema.parse({
|
|
12205
12449
|
...DEFAULT_QUALITY_TOLERANCES,
|
|
12206
12450
|
...entry.tolerances
|
|
@@ -12208,7 +12452,7 @@ function resolveQualityInputs(raw, inputsFilePath) {
|
|
|
12208
12452
|
}));
|
|
12209
12453
|
}
|
|
12210
12454
|
function portableRelative2(from, to) {
|
|
12211
|
-
return
|
|
12455
|
+
return import_node_path18.default.relative(from, import_node_path18.default.resolve(to)).split(import_node_path18.default.sep).join("/") || ".";
|
|
12212
12456
|
}
|
|
12213
12457
|
async function verifyRecordForImage(recordPath, imagePath) {
|
|
12214
12458
|
const verification = await verifyArtifactBundle(recordPath);
|
|
@@ -12223,20 +12467,20 @@ async function verifyRecordForImage(recordPath, imagePath) {
|
|
|
12223
12467
|
if (record.kind !== "refine" || options === null || typeof options !== "object" || options.schema !== "pixelkiln-quality") {
|
|
12224
12468
|
throw new Error(`Quality record ${recordPath} is not a PixelKiln refinement record.`);
|
|
12225
12469
|
}
|
|
12226
|
-
const outputs = record.outputs.map((output) =>
|
|
12227
|
-
if (!outputs.includes(
|
|
12470
|
+
const outputs = record.outputs.map((output) => import_node_path18.default.resolve(import_node_path18.default.dirname(recordPath), output.path));
|
|
12471
|
+
if (!outputs.includes(import_node_path18.default.resolve(imagePath))) {
|
|
12228
12472
|
throw new Error(`Quality record ${recordPath} does not own ${imagePath}.`);
|
|
12229
12473
|
}
|
|
12230
12474
|
}
|
|
12231
12475
|
async function snapshotQualityBaseline(inputs, baselinePath, options = {}) {
|
|
12232
12476
|
if (!inputs.length) throw new Error("Cannot snapshot an empty quality baseline.");
|
|
12233
|
-
const absolute =
|
|
12477
|
+
const absolute = import_node_path18.default.resolve(baselinePath);
|
|
12234
12478
|
for (const input of inputs) {
|
|
12235
|
-
if (
|
|
12479
|
+
if (import_node_path18.default.resolve(input.file) === absolute || input.record && import_node_path18.default.resolve(input.record) === absolute) {
|
|
12236
12480
|
throw new Error(`Quality baseline output would overwrite an input: ${absolute}.`);
|
|
12237
12481
|
}
|
|
12238
12482
|
}
|
|
12239
|
-
const root =
|
|
12483
|
+
const root = import_node_path18.default.dirname(absolute);
|
|
12240
12484
|
const cases = [];
|
|
12241
12485
|
for (const input of [...inputs].sort((a, b) => a.id.localeCompare(b.id))) {
|
|
12242
12486
|
const expected = await measureImageQuality(input.file);
|
|
@@ -12263,7 +12507,7 @@ async function snapshotQualityBaseline(inputs, baselinePath, options = {}) {
|
|
|
12263
12507
|
cases
|
|
12264
12508
|
});
|
|
12265
12509
|
const data = Buffer.from(JSON.stringify(baseline, null, 2) + "\n");
|
|
12266
|
-
if ((0,
|
|
12510
|
+
if ((0, import_node_fs14.existsSync)(absolute) && !options.force) {
|
|
12267
12511
|
const current = await (0, import_promises10.readFile)(absolute);
|
|
12268
12512
|
if (!current.equals(data)) {
|
|
12269
12513
|
throw new OverwriteRefusedError(
|
|
@@ -12276,7 +12520,7 @@ async function snapshotQualityBaseline(inputs, baselinePath, options = {}) {
|
|
|
12276
12520
|
return { baseline, path: absolute, changed: result.changed.length > 0 };
|
|
12277
12521
|
}
|
|
12278
12522
|
async function readQualityBaseline(baselinePath) {
|
|
12279
|
-
const absolute =
|
|
12523
|
+
const absolute = import_node_path18.default.resolve(baselinePath);
|
|
12280
12524
|
let raw;
|
|
12281
12525
|
try {
|
|
12282
12526
|
raw = JSON.parse(await (0, import_promises10.readFile)(absolute, "utf8"));
|
|
@@ -12349,12 +12593,12 @@ function regressionViolations(expected, actual, tolerances) {
|
|
|
12349
12593
|
return { violations, warnings };
|
|
12350
12594
|
}
|
|
12351
12595
|
async function checkQualityBaseline(baselinePath) {
|
|
12352
|
-
const absolute =
|
|
12596
|
+
const absolute = import_node_path18.default.resolve(baselinePath);
|
|
12353
12597
|
const baseline = await readQualityBaseline(absolute);
|
|
12354
|
-
const root =
|
|
12598
|
+
const root = import_node_path18.default.dirname(absolute);
|
|
12355
12599
|
const cases = [];
|
|
12356
12600
|
for (const entry of baseline.cases) {
|
|
12357
|
-
const file =
|
|
12601
|
+
const file = import_node_path18.default.resolve(root, entry.file);
|
|
12358
12602
|
let actual = null;
|
|
12359
12603
|
const violations = [];
|
|
12360
12604
|
const warnings = [];
|
|
@@ -12367,7 +12611,7 @@ async function checkQualityBaseline(baselinePath) {
|
|
|
12367
12611
|
violations.push(message4(error));
|
|
12368
12612
|
}
|
|
12369
12613
|
if (entry.record) {
|
|
12370
|
-
const recordPath =
|
|
12614
|
+
const recordPath = import_node_path18.default.resolve(root, entry.record.path);
|
|
12371
12615
|
try {
|
|
12372
12616
|
const recordHash = await sha256File(recordPath);
|
|
12373
12617
|
if (recordHash !== entry.record.sha256) violations.push("quality record hash changed");
|
|
@@ -12403,14 +12647,14 @@ async function checkQualityBaseline(baselinePath) {
|
|
|
12403
12647
|
}
|
|
12404
12648
|
|
|
12405
12649
|
// src/pipeline/cache-health.ts
|
|
12406
|
-
var
|
|
12650
|
+
var import_node_fs18 = require("fs");
|
|
12407
12651
|
var import_promises13 = require("fs/promises");
|
|
12408
|
-
var
|
|
12652
|
+
var import_node_path22 = __toESM(require("path"), 1);
|
|
12409
12653
|
|
|
12410
12654
|
// src/cache.ts
|
|
12411
12655
|
var import_promises11 = require("fs/promises");
|
|
12412
|
-
var
|
|
12413
|
-
var
|
|
12656
|
+
var import_node_fs15 = require("fs");
|
|
12657
|
+
var import_node_path19 = __toESM(require("path"), 1);
|
|
12414
12658
|
var import_zod6 = require("zod");
|
|
12415
12659
|
var HashCacheSchema = import_zod6.z.object({
|
|
12416
12660
|
version: import_zod6.z.literal(1),
|
|
@@ -12424,10 +12668,10 @@ function isSha256Hash(value) {
|
|
|
12424
12668
|
function parseCache(value) {
|
|
12425
12669
|
return HashCacheSchema.parse(value);
|
|
12426
12670
|
}
|
|
12427
|
-
async function loadCache(
|
|
12428
|
-
if (!(0,
|
|
12671
|
+
async function loadCache(path38) {
|
|
12672
|
+
if (!(0, import_node_fs15.existsSync)(path38)) return { version: 1, hashes: {} };
|
|
12429
12673
|
try {
|
|
12430
|
-
const parsed = HashCacheSchema.safeParse(JSON.parse(await (0, import_promises11.readFile)(
|
|
12674
|
+
const parsed = HashCacheSchema.safeParse(JSON.parse(await (0, import_promises11.readFile)(path38, "utf8")));
|
|
12431
12675
|
if (!parsed.success) return { version: 1, hashes: {} };
|
|
12432
12676
|
return {
|
|
12433
12677
|
version: 1,
|
|
@@ -12439,18 +12683,18 @@ async function loadCache(path36) {
|
|
|
12439
12683
|
return { version: 1, hashes: {} };
|
|
12440
12684
|
}
|
|
12441
12685
|
}
|
|
12442
|
-
async function saveCache(
|
|
12686
|
+
async function saveCache(path38, cache) {
|
|
12443
12687
|
const sorted = {};
|
|
12444
12688
|
for (const key of Object.keys(cache.hashes).sort()) {
|
|
12445
12689
|
const hash = cache.hashes[key];
|
|
12446
12690
|
if (!isSha256Hash(hash)) throw new Error(`Refusing to cache invalid SHA-256 for ${key}`);
|
|
12447
12691
|
sorted[key] = hash;
|
|
12448
12692
|
}
|
|
12449
|
-
await (0, import_promises11.mkdir)(
|
|
12450
|
-
const tmp = `${
|
|
12693
|
+
await (0, import_promises11.mkdir)(import_node_path19.default.dirname(import_node_path19.default.resolve(path38)), { recursive: true });
|
|
12694
|
+
const tmp = `${path38}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
12451
12695
|
try {
|
|
12452
12696
|
await (0, import_promises11.writeFile)(tmp, JSON.stringify({ version: 1, hashes: sorted }, null, 2) + "\n");
|
|
12453
|
-
await (0, import_promises11.rename)(tmp,
|
|
12697
|
+
await (0, import_promises11.rename)(tmp, path38);
|
|
12454
12698
|
} finally {
|
|
12455
12699
|
await (0, import_promises11.rm)(tmp, { force: true });
|
|
12456
12700
|
}
|
|
@@ -12474,20 +12718,20 @@ function cachePathFor(lockPath) {
|
|
|
12474
12718
|
}
|
|
12475
12719
|
|
|
12476
12720
|
// src/pipeline/history.ts
|
|
12477
|
-
var
|
|
12478
|
-
var
|
|
12721
|
+
var import_node_fs17 = require("fs");
|
|
12722
|
+
var import_node_path21 = __toESM(require("path"), 1);
|
|
12479
12723
|
|
|
12480
12724
|
// src/pipeline/fetch.ts
|
|
12481
|
-
var
|
|
12725
|
+
var import_node_fs16 = require("fs");
|
|
12482
12726
|
var import_promises12 = require("fs/promises");
|
|
12483
|
-
var
|
|
12727
|
+
var import_node_path20 = __toESM(require("path"), 1);
|
|
12484
12728
|
async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
12485
12729
|
const log = opts.onProgress ?? (() => {
|
|
12486
12730
|
});
|
|
12487
12731
|
const result = { downloaded: 0, skipped: 0, failed: 0, ...opts.refresh ? { unchanged: 0 } : {} };
|
|
12488
12732
|
const specByKey = new Map(specs.map((s) => [lockKey(s.styleId, s.assetId), s]));
|
|
12489
12733
|
normalizeLockOutputPaths(lock, specs);
|
|
12490
|
-
const cacheDir = opts.cacheDir === false ? null :
|
|
12734
|
+
const cacheDir = opts.cacheDir === false ? null : import_node_path20.default.resolve(opts.cacheDir ?? import_node_path20.default.join(import_node_path20.default.dirname(lockPath), ".pixelkiln", "cache"));
|
|
12491
12735
|
const pending = Object.entries(lock.entries).filter(([key, e]) => {
|
|
12492
12736
|
if (e.provider !== provider.id || !specByKey.has(key)) return false;
|
|
12493
12737
|
if (specByKey.get(key)?.mirror) return false;
|
|
@@ -12498,15 +12742,15 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
12498
12742
|
if (spec && e.specHash === spec.specHash && e.outputs.length && !postprocessCurrent(e, spec)) {
|
|
12499
12743
|
return Boolean(opts.force) || e.outputs.every((output) => {
|
|
12500
12744
|
const file = resolveOutputPath(output.path, spec.root);
|
|
12501
|
-
return (0,
|
|
12745
|
+
return (0, import_node_fs16.existsSync)(file) && sha256((0, import_node_fs16.readFileSync)(file)) === output.sha256;
|
|
12502
12746
|
});
|
|
12503
12747
|
}
|
|
12504
12748
|
if (!opts.repair) return false;
|
|
12505
12749
|
if (e.outputs.length === 0 || !spec) return true;
|
|
12506
12750
|
return e.outputs.some((output) => {
|
|
12507
12751
|
const file = resolveOutputPath(output.path, spec.root);
|
|
12508
|
-
if (!(0,
|
|
12509
|
-
const current = sha256((0,
|
|
12752
|
+
if (!(0, import_node_fs16.existsSync)(file)) return true;
|
|
12753
|
+
const current = sha256((0, import_node_fs16.readFileSync)(file));
|
|
12510
12754
|
if (current === output.sha256) return false;
|
|
12511
12755
|
return Boolean(opts.force) || (e.supersededOutputs ?? []).some((old) => old.sha256 === current);
|
|
12512
12756
|
});
|
|
@@ -12589,14 +12833,14 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
12589
12833
|
if (!source.url) throw new Error(`no durable source URL to refresh ${source.role ?? "asset"} from`);
|
|
12590
12834
|
fresh = await provider.download(source.url);
|
|
12591
12835
|
if (recorded && sha256(fresh) === recordedRaw && !reapply) {
|
|
12592
|
-
log(` current ${
|
|
12836
|
+
log(` current ${import_node_path20.default.relative(process.cwd(), target)}`);
|
|
12593
12837
|
outputs.push({ ...recorded, path: portableOutputPath(target, spec.root) });
|
|
12594
12838
|
continue;
|
|
12595
12839
|
}
|
|
12596
12840
|
}
|
|
12597
12841
|
let raw = null;
|
|
12598
12842
|
let final = null;
|
|
12599
|
-
if ((0,
|
|
12843
|
+
if ((0, import_node_fs16.existsSync)(target)) {
|
|
12600
12844
|
const currentHash = await sha256File(target);
|
|
12601
12845
|
if (recorded && currentHash === recorded.sha256) {
|
|
12602
12846
|
if (!fresh && !reapply) {
|
|
@@ -12613,9 +12857,9 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
12613
12857
|
}
|
|
12614
12858
|
if (reapply && !fresh) {
|
|
12615
12859
|
if (!recorded.raw) raw = await (0, import_promises12.readFile)(target);
|
|
12616
|
-
log(` reapply ${
|
|
12860
|
+
log(` reapply ${import_node_path20.default.relative(process.cwd(), target)}`);
|
|
12617
12861
|
} else {
|
|
12618
|
-
log(` changed ${
|
|
12862
|
+
log(` changed ${import_node_path20.default.relative(process.cwd(), target)} (upstream)`);
|
|
12619
12863
|
}
|
|
12620
12864
|
} else {
|
|
12621
12865
|
const superseded = (entry.supersededOutputs ?? []).find(
|
|
@@ -12643,7 +12887,7 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
12643
12887
|
}
|
|
12644
12888
|
}
|
|
12645
12889
|
log(
|
|
12646
|
-
` replace ${
|
|
12890
|
+
` replace ${import_node_path20.default.relative(process.cwd(), target)}` + (opts.force ? " (--force)" : " (previous tracked generation)")
|
|
12647
12891
|
);
|
|
12648
12892
|
}
|
|
12649
12893
|
}
|
|
@@ -12652,7 +12896,7 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
12652
12896
|
} else if (!raw && recorded && cacheDir) {
|
|
12653
12897
|
if (!reapply) final = await readCachedMedia(cacheDir, recorded.sha256, expectedMediaType);
|
|
12654
12898
|
if (!final && recordedRaw) raw = await readCachedMedia(cacheDir, recordedRaw, expectedMediaType);
|
|
12655
|
-
if (final || raw) log(` cached ${
|
|
12899
|
+
if (final || raw) log(` cached ${import_node_path20.default.relative(process.cwd(), target)}`);
|
|
12656
12900
|
}
|
|
12657
12901
|
if (!final && !raw) {
|
|
12658
12902
|
if (!source.url) {
|
|
@@ -12679,11 +12923,11 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
12679
12923
|
buf = processed.bytes;
|
|
12680
12924
|
if (processed.changed) {
|
|
12681
12925
|
rawHash = sha256(raw);
|
|
12682
|
-
log(` snapped ${
|
|
12926
|
+
log(` snapped ${import_node_path20.default.relative(process.cwd(), target)} to ${wanted.palette.colors.length} colours`);
|
|
12683
12927
|
}
|
|
12684
12928
|
}
|
|
12685
12929
|
if (cacheDir) await cacheMedia(cacheDir, buf, mediaType);
|
|
12686
|
-
await (0, import_promises12.mkdir)(
|
|
12930
|
+
await (0, import_promises12.mkdir)(import_node_path20.default.dirname(target), { recursive: true });
|
|
12687
12931
|
const tmp = `${target}.pixelkiln.tmp`;
|
|
12688
12932
|
await (0, import_promises12.writeFile)(tmp, buf);
|
|
12689
12933
|
outputs.push({
|
|
@@ -12697,7 +12941,7 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
12697
12941
|
await saveLock(lockPath, lock);
|
|
12698
12942
|
await (0, import_promises12.rename)(tmp, target);
|
|
12699
12943
|
wrote = true;
|
|
12700
|
-
log(` wrote ${
|
|
12944
|
+
log(` wrote ${import_node_path20.default.relative(process.cwd(), target)}`);
|
|
12701
12945
|
}
|
|
12702
12946
|
if (opts.refresh && !wrote) {
|
|
12703
12947
|
if (rotated) {
|
|
@@ -12741,8 +12985,8 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
12741
12985
|
return result;
|
|
12742
12986
|
}
|
|
12743
12987
|
async function readCachedMedia(cacheDir, hash, mediaType) {
|
|
12744
|
-
const file =
|
|
12745
|
-
if (!(0,
|
|
12988
|
+
const file = import_node_path20.default.join(cacheDir, cacheFileName(hash, mediaType));
|
|
12989
|
+
if (!(0, import_node_fs16.existsSync)(file)) return null;
|
|
12746
12990
|
try {
|
|
12747
12991
|
const buf = await (0, import_promises12.readFile)(file);
|
|
12748
12992
|
if (sha256(buf) !== hash) return null;
|
|
@@ -12755,7 +12999,7 @@ async function readCachedMedia(cacheDir, hash, mediaType) {
|
|
|
12755
12999
|
async function cacheMedia(cacheDir, buf, mediaType, knownHash) {
|
|
12756
13000
|
const hash = knownHash ?? sha256(buf);
|
|
12757
13001
|
validateMedia(buf, mediaType);
|
|
12758
|
-
const file =
|
|
13002
|
+
const file = import_node_path20.default.join(cacheDir, cacheFileName(hash, mediaType));
|
|
12759
13003
|
if (await readCachedMedia(cacheDir, hash, mediaType)) return;
|
|
12760
13004
|
await (0, import_promises12.mkdir)(cacheDir, { recursive: true });
|
|
12761
13005
|
const tmp = `${file}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
@@ -12763,7 +13007,7 @@ async function cacheMedia(cacheDir, buf, mediaType, knownHash) {
|
|
|
12763
13007
|
try {
|
|
12764
13008
|
await (0, import_promises12.rename)(tmp, file);
|
|
12765
13009
|
} catch (err) {
|
|
12766
|
-
if (!(0,
|
|
13010
|
+
if (!(0, import_node_fs16.existsSync)(file)) throw err;
|
|
12767
13011
|
} finally {
|
|
12768
13012
|
await (0, import_promises12.rm)(tmp, { force: true });
|
|
12769
13013
|
}
|
|
@@ -12882,10 +13126,10 @@ async function revertGeneration(provider, spec, lock, lockPath, opts) {
|
|
|
12882
13126
|
}
|
|
12883
13127
|
for (let i = 0; i < entry.outputs.length; i++) {
|
|
12884
13128
|
const file = currentEntryOutputPath(entry, spec, i);
|
|
12885
|
-
if (!(0,
|
|
13129
|
+
if (!(0, import_node_fs17.existsSync)(file)) continue;
|
|
12886
13130
|
if (await sha256File(file) !== entry.outputs[i].sha256 && !opts.force) {
|
|
12887
13131
|
throw new OverwriteRefusedError(
|
|
12888
|
-
`${
|
|
13132
|
+
`${import_node_path21.default.relative(process.cwd(), file)} was changed after download; keep it with pixelkiln edit, or pass --force to replace it`,
|
|
12889
13133
|
[file]
|
|
12890
13134
|
);
|
|
12891
13135
|
}
|
|
@@ -12937,11 +13181,11 @@ async function revertGeneration(provider, spec, lock, lockPath, opts) {
|
|
|
12937
13181
|
|
|
12938
13182
|
// src/pipeline/cache-health.ts
|
|
12939
13183
|
async function inspectCaches(lock, lockPath, options = {}) {
|
|
12940
|
-
if (options.prune && !(0,
|
|
12941
|
-
throw new Error(`Refusing to prune without an existing lockfile at ${
|
|
13184
|
+
if (options.prune && !(0, import_node_fs18.existsSync)(lockPath)) {
|
|
13185
|
+
throw new Error(`Refusing to prune without an existing lockfile at ${import_node_path22.default.resolve(lockPath)}`);
|
|
12942
13186
|
}
|
|
12943
|
-
const contentDir =
|
|
12944
|
-
const remotePath =
|
|
13187
|
+
const contentDir = import_node_path22.default.resolve(import_node_path22.default.dirname(lockPath), ".pixelkiln", "cache");
|
|
13188
|
+
const remotePath = import_node_path22.default.resolve(cachePathFor(lockPath));
|
|
12945
13189
|
const referenced = referencedHashes(lock);
|
|
12946
13190
|
let content = await inspectContentCache(contentDir, referenced);
|
|
12947
13191
|
let remoteHashes = await inspectRemoteHashCache(remotePath);
|
|
@@ -12953,7 +13197,7 @@ async function inspectCaches(lock, lockPath, options = {}) {
|
|
|
12953
13197
|
]);
|
|
12954
13198
|
for (const name of names) {
|
|
12955
13199
|
try {
|
|
12956
|
-
await (0, import_promises13.rm)(
|
|
13200
|
+
await (0, import_promises13.rm)(import_node_path22.default.join(contentDir, name), { force: true });
|
|
12957
13201
|
removed.contentFiles++;
|
|
12958
13202
|
} catch {
|
|
12959
13203
|
}
|
|
@@ -12980,7 +13224,7 @@ async function inspectCaches(lock, lockPath, options = {}) {
|
|
|
12980
13224
|
async function inspectContentCache(contentDir, referenced) {
|
|
12981
13225
|
const report = {
|
|
12982
13226
|
path: contentDir,
|
|
12983
|
-
exists: (0,
|
|
13227
|
+
exists: (0, import_node_fs18.existsSync)(contentDir),
|
|
12984
13228
|
files: 0,
|
|
12985
13229
|
bytes: 0,
|
|
12986
13230
|
valid: 0,
|
|
@@ -13001,7 +13245,7 @@ async function inspectContentCache(contentDir, referenced) {
|
|
|
13001
13245
|
continue;
|
|
13002
13246
|
}
|
|
13003
13247
|
report.files++;
|
|
13004
|
-
const file =
|
|
13248
|
+
const file = import_node_path22.default.join(contentDir, entry.name);
|
|
13005
13249
|
const mediaType = mediaTypeFromExtension(entry.name);
|
|
13006
13250
|
const expected = mediaType ? entry.name.slice(0, -4) : "";
|
|
13007
13251
|
let bytes;
|
|
@@ -13044,7 +13288,7 @@ async function inspectContentCache(contentDir, referenced) {
|
|
|
13044
13288
|
async function inspectRemoteHashCache(remotePath) {
|
|
13045
13289
|
const report = {
|
|
13046
13290
|
path: remotePath,
|
|
13047
|
-
exists: (0,
|
|
13291
|
+
exists: (0, import_node_fs18.existsSync)(remotePath),
|
|
13048
13292
|
entries: 0,
|
|
13049
13293
|
valid: 0,
|
|
13050
13294
|
invalidIds: [],
|
|
@@ -13068,20 +13312,20 @@ async function inspectRemoteHashCache(remotePath) {
|
|
|
13068
13312
|
}
|
|
13069
13313
|
|
|
13070
13314
|
// src/pipeline/pack.ts
|
|
13071
|
-
var
|
|
13072
|
-
var
|
|
13315
|
+
var import_node_fs19 = require("fs");
|
|
13316
|
+
var import_node_path23 = __toESM(require("path"), 1);
|
|
13073
13317
|
var DEFAULT_FRAME_SET_FPS = 12;
|
|
13074
13318
|
function resolvePackInputs(raw, inputsFilePath) {
|
|
13075
13319
|
if (!Array.isArray(raw) || !raw.length) {
|
|
13076
13320
|
throw new Error("--inputs must be a non-empty JSON array of { id, path }");
|
|
13077
13321
|
}
|
|
13078
|
-
const baseDir =
|
|
13322
|
+
const baseDir = import_node_path23.default.dirname(import_node_path23.default.resolve(inputsFilePath));
|
|
13079
13323
|
return raw.map((entry, i) => {
|
|
13080
13324
|
const e = entry;
|
|
13081
13325
|
if (typeof e.id !== "string" || typeof e.path !== "string") {
|
|
13082
13326
|
throw new Error(`--inputs[${i}] needs string "id" and "path"`);
|
|
13083
13327
|
}
|
|
13084
|
-
return { id: e.id, path:
|
|
13328
|
+
return { id: e.id, path: import_node_path23.default.resolve(baseDir, e.path) };
|
|
13085
13329
|
});
|
|
13086
13330
|
}
|
|
13087
13331
|
function packSprites(inputs, options = {}) {
|
|
@@ -13102,7 +13346,7 @@ function packSprites(inputs, options = {}) {
|
|
|
13102
13346
|
for (const input of ordered) {
|
|
13103
13347
|
let sourceHash = null;
|
|
13104
13348
|
try {
|
|
13105
|
-
const bytes = (0,
|
|
13349
|
+
const bytes = (0, import_node_fs19.readFileSync)(input.path);
|
|
13106
13350
|
sourceHash = sha256(bytes);
|
|
13107
13351
|
const png = decodePng(bytes);
|
|
13108
13352
|
sprites.push({ id: input.id, width: png.width, height: png.height, pixels: png.pixels });
|
|
@@ -13165,7 +13409,7 @@ function packStyle(lock, styleId, manifestDir, options = {}) {
|
|
|
13165
13409
|
}
|
|
13166
13410
|
const inputs2 = Object.entries(options.sourceOverrides).map(([id, source]) => ({
|
|
13167
13411
|
id,
|
|
13168
|
-
path:
|
|
13412
|
+
path: import_node_path23.default.resolve(source)
|
|
13169
13413
|
}));
|
|
13170
13414
|
if (!inputs2.length) throw new Error(`No approved quality outputs for style "${styleId}".`);
|
|
13171
13415
|
const packed2 = packSprites(inputs2, options);
|
|
@@ -13189,7 +13433,7 @@ function packStyle(lock, styleId, manifestDir, options = {}) {
|
|
|
13189
13433
|
let outputs = resolveEntryOutputs(entry, id, manifestDir);
|
|
13190
13434
|
const source = sources[id];
|
|
13191
13435
|
if (source && outputs.length <= 1) {
|
|
13192
|
-
inputs.push({ id, path:
|
|
13436
|
+
inputs.push({ id, path: import_node_path23.default.resolve(manifestDir, source) });
|
|
13193
13437
|
continue;
|
|
13194
13438
|
}
|
|
13195
13439
|
if (source && sourceIsStem(source, entry, manifestDir)) {
|
|
@@ -13223,7 +13467,7 @@ function packStyle(lock, styleId, manifestDir, options = {}) {
|
|
|
13223
13467
|
}
|
|
13224
13468
|
}
|
|
13225
13469
|
for (const [id, source] of Object.entries(sources)) {
|
|
13226
|
-
if (!locked.has(id)) inputs.push({ id, path:
|
|
13470
|
+
if (!locked.has(id)) inputs.push({ id, path: import_node_path23.default.resolve(manifestDir, source) });
|
|
13227
13471
|
}
|
|
13228
13472
|
if (!inputs.length) {
|
|
13229
13473
|
throw new Error(`No readable sprites for style "${styleId}"; ${noOutput.length} skipped.`);
|
|
@@ -13260,7 +13504,7 @@ function mountSprites(placements, options) {
|
|
|
13260
13504
|
for (const p of placements) {
|
|
13261
13505
|
let sourceHash = null;
|
|
13262
13506
|
try {
|
|
13263
|
-
const bytes = (0,
|
|
13507
|
+
const bytes = (0, import_node_fs19.readFileSync)(p.path);
|
|
13264
13508
|
sourceHash = sha256(bytes);
|
|
13265
13509
|
const png = decodePng(bytes);
|
|
13266
13510
|
if (png.width > cellWidth || png.height > cellHeight) {
|
|
@@ -13331,7 +13575,7 @@ function mountStyle(lock, styleId, manifestDir, mount, cells, sources = {}, outp
|
|
|
13331
13575
|
const source = sources[id];
|
|
13332
13576
|
const entry = lock.entries[`${styleId}/${id}`];
|
|
13333
13577
|
if (source && !(entry && sourceIsStem(source, entry, manifestDir))) {
|
|
13334
|
-
placements.push({ id, path:
|
|
13578
|
+
placements.push({ id, path: import_node_path23.default.resolve(manifestDir, source), cell });
|
|
13335
13579
|
continue;
|
|
13336
13580
|
}
|
|
13337
13581
|
if (source && entry) {
|
|
@@ -13364,7 +13608,7 @@ function mountStyle(lock, styleId, manifestDir, mount, cells, sources = {}, outp
|
|
|
13364
13608
|
`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}`
|
|
13365
13609
|
);
|
|
13366
13610
|
}
|
|
13367
|
-
const basePng = mount.base ? (0,
|
|
13611
|
+
const basePng = mount.base ? (0, import_node_fs19.readFileSync)(import_node_path23.default.resolve(manifestDir, mount.base)) : void 0;
|
|
13368
13612
|
const mounted = mountSprites(placements, {
|
|
13369
13613
|
cellWidth: mount.cellWidth,
|
|
13370
13614
|
cellHeight: mount.cellHeight,
|
|
@@ -13374,7 +13618,7 @@ function mountStyle(lock, styleId, manifestDir, mount, cells, sources = {}, outp
|
|
|
13374
13618
|
...mounted,
|
|
13375
13619
|
sources: mount.base ? [{
|
|
13376
13620
|
id: "$base",
|
|
13377
|
-
path:
|
|
13621
|
+
path: import_node_path23.default.resolve(manifestDir, mount.base),
|
|
13378
13622
|
sha256: sha256(basePng),
|
|
13379
13623
|
included: true
|
|
13380
13624
|
}, ...mounted.sources] : mounted.sources,
|
|
@@ -13966,6 +14210,10 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
|
|
|
13966
14210
|
...spec.revision.keypointsSha256 ? { keypointsSha256: spec.revision.keypointsSha256 } : {},
|
|
13967
14211
|
...spec.revision.skeletonTemplate ? { skeletonTemplate: spec.revision.skeletonTemplate } : {},
|
|
13968
14212
|
...spec.revision.description ? { description: spec.revision.description } : {},
|
|
14213
|
+
...spec.revision.removalTask ? { removalTask: spec.revision.removalTask } : {},
|
|
14214
|
+
...spec.revision.driftThreshold == null ? {} : { driftThreshold: spec.revision.driftThreshold },
|
|
14215
|
+
...spec.revision.subjectDescription ? { subjectDescription: spec.revision.subjectDescription } : {},
|
|
14216
|
+
...spec.revision.initialPose ? { initialPose: spec.revision.initialPose } : {},
|
|
13969
14217
|
...spec.revision.engine ? { engine: spec.revision.engine } : {}
|
|
13970
14218
|
} : null,
|
|
13971
14219
|
outfit: spec.character?.kind === "outfit" && outfitParentEntry ? {
|
|
@@ -14180,9 +14428,9 @@ async function poll(provider, lock, lockPath, opts = {}) {
|
|
|
14180
14428
|
}
|
|
14181
14429
|
|
|
14182
14430
|
// src/pipeline/doctor.ts
|
|
14183
|
-
var
|
|
14431
|
+
var import_node_fs20 = require("fs");
|
|
14184
14432
|
var import_promises14 = require("fs/promises");
|
|
14185
|
-
var
|
|
14433
|
+
var import_node_path24 = __toESM(require("path"), 1);
|
|
14186
14434
|
async function doctor(loaded, specs, lock, lockPath, opts = {}) {
|
|
14187
14435
|
const checks = [];
|
|
14188
14436
|
const add = (id, level, message6) => checks.push({ id, level, message: message6 });
|
|
@@ -14193,14 +14441,14 @@ async function doctor(loaded, specs, lock, lockPath, opts = {}) {
|
|
|
14193
14441
|
);
|
|
14194
14442
|
add("lock", "ok", `${Object.keys(lock.entries).length} valid v2 lock entr(ies)`);
|
|
14195
14443
|
const dirs = [...new Set(specs.flatMap((spec) => [
|
|
14196
|
-
|
|
14197
|
-
...spec.quality ? [
|
|
14444
|
+
import_node_path24.default.dirname(spec.outFile),
|
|
14445
|
+
...spec.quality ? [import_node_path24.default.dirname(spec.quality.outFile)] : []
|
|
14198
14446
|
]))];
|
|
14199
14447
|
const unwritable = [];
|
|
14200
14448
|
for (const dir of dirs) {
|
|
14201
14449
|
const ancestor = await nearestExistingDirectory(dir);
|
|
14202
14450
|
try {
|
|
14203
|
-
await (0, import_promises14.access)(ancestor,
|
|
14451
|
+
await (0, import_promises14.access)(ancestor, import_node_fs20.constants.W_OK);
|
|
14204
14452
|
} catch {
|
|
14205
14453
|
unwritable.push(dir);
|
|
14206
14454
|
}
|
|
@@ -14217,7 +14465,7 @@ async function doctor(loaded, specs, lock, lockPath, opts = {}) {
|
|
|
14217
14465
|
for (let index = 0; index < entry.outputs.length; index++) {
|
|
14218
14466
|
const output = entry.outputs[index];
|
|
14219
14467
|
const spec = specByKey.get(key);
|
|
14220
|
-
const absolute = spec ? currentEntryOutputPath(entry, spec, index) : resolveOutputPath(output.path,
|
|
14468
|
+
const absolute = spec ? currentEntryOutputPath(entry, spec, index) : resolveOutputPath(output.path, import_node_path24.default.dirname(lockPath));
|
|
14221
14469
|
const owner = outputOwners.get(absolute);
|
|
14222
14470
|
if (owner && owner !== key) duplicateOutputs.push(`${output.path} (${owner}, ${key})`);
|
|
14223
14471
|
outputOwners.set(absolute, key);
|
|
@@ -14232,7 +14480,7 @@ async function doctor(loaded, specs, lock, lockPath, opts = {}) {
|
|
|
14232
14480
|
} else {
|
|
14233
14481
|
add("lock-outputs", "ok", `${outputOwners.size} recorded output path(s) are uniquely owned`);
|
|
14234
14482
|
}
|
|
14235
|
-
const cacheDir =
|
|
14483
|
+
const cacheDir = import_node_path24.default.join(import_node_path24.default.dirname(lockPath), ".pixelkiln", "cache");
|
|
14236
14484
|
const stranded = [];
|
|
14237
14485
|
const invalidState = [];
|
|
14238
14486
|
const staleMapUrls = [];
|
|
@@ -14248,11 +14496,11 @@ async function doctor(loaded, specs, lock, lockPath, opts = {}) {
|
|
|
14248
14496
|
const hasSource = Boolean(entry.sourceUrl || entry.sourceUrls?.length);
|
|
14249
14497
|
let hasCache = false;
|
|
14250
14498
|
for (const output of entry.outputs) {
|
|
14251
|
-
const cached =
|
|
14499
|
+
const cached = import_node_path24.default.join(
|
|
14252
14500
|
cacheDir,
|
|
14253
14501
|
cacheFileName(output.sha256, output.mediaType ?? MediaType.PNG)
|
|
14254
14502
|
);
|
|
14255
|
-
if ((0,
|
|
14503
|
+
if ((0, import_node_fs20.existsSync)(cached) && await sha256File(cached) === output.sha256) {
|
|
14256
14504
|
hasCache = true;
|
|
14257
14505
|
break;
|
|
14258
14506
|
}
|
|
@@ -14348,16 +14596,16 @@ async function nearestExistingDirectory(start) {
|
|
|
14348
14596
|
if ((await (0, import_promises14.stat)(current)).isDirectory()) return current;
|
|
14349
14597
|
} catch {
|
|
14350
14598
|
}
|
|
14351
|
-
const parent =
|
|
14599
|
+
const parent = import_node_path24.default.dirname(current);
|
|
14352
14600
|
if (parent === current) return current;
|
|
14353
14601
|
current = parent;
|
|
14354
14602
|
}
|
|
14355
14603
|
}
|
|
14356
14604
|
|
|
14357
14605
|
// src/pipeline/adopt.ts
|
|
14358
|
-
var
|
|
14606
|
+
var import_node_fs21 = require("fs");
|
|
14359
14607
|
var import_promises15 = require("fs/promises");
|
|
14360
|
-
var
|
|
14608
|
+
var import_node_path25 = __toESM(require("path"), 1);
|
|
14361
14609
|
async function adopt(provider, specs, lock, lockPath, opts = {}) {
|
|
14362
14610
|
const log = opts.onProgress ?? (() => {
|
|
14363
14611
|
});
|
|
@@ -14368,7 +14616,7 @@ async function adopt(provider, specs, lock, lockPath, opts = {}) {
|
|
|
14368
14616
|
const localByHash = /* @__PURE__ */ new Map();
|
|
14369
14617
|
const unmatchedLocal = [];
|
|
14370
14618
|
for (const spec of specs) {
|
|
14371
|
-
if (!(0,
|
|
14619
|
+
if (!(0, import_node_fs21.existsSync)(spec.outFile)) {
|
|
14372
14620
|
unmatchedLocal.push(`${spec.styleId}/${spec.assetId} (no file at ${spec.outFile})`);
|
|
14373
14621
|
continue;
|
|
14374
14622
|
}
|
|
@@ -14490,13 +14738,13 @@ async function tagAdopted(provider, specs, lock, opts = {}) {
|
|
|
14490
14738
|
}
|
|
14491
14739
|
|
|
14492
14740
|
// src/pick/server.ts
|
|
14493
|
-
var
|
|
14741
|
+
var import_node_fs23 = require("fs");
|
|
14494
14742
|
var import_promises18 = require("fs/promises");
|
|
14495
14743
|
|
|
14496
14744
|
// src/pipeline/init.ts
|
|
14497
14745
|
var import_promises16 = require("fs/promises");
|
|
14498
|
-
var
|
|
14499
|
-
var
|
|
14746
|
+
var import_node_fs22 = require("fs");
|
|
14747
|
+
var import_node_path26 = __toESM(require("path"), 1);
|
|
14500
14748
|
function pngSize(buf) {
|
|
14501
14749
|
if (buf.length < 24) return null;
|
|
14502
14750
|
const isPng = buf.readUInt32BE(0) === 2303741511 && buf.readUInt32BE(4) === 218765834;
|
|
@@ -14510,7 +14758,7 @@ function slugify(value) {
|
|
|
14510
14758
|
async function walk(dir, exclude) {
|
|
14511
14759
|
const out = [];
|
|
14512
14760
|
for (const entry of await (0, import_promises16.readdir)(dir, { withFileTypes: true })) {
|
|
14513
|
-
const full =
|
|
14761
|
+
const full = import_node_path26.default.join(dir, entry.name);
|
|
14514
14762
|
if (exclude.some((x) => full.includes(x))) continue;
|
|
14515
14763
|
if (entry.isDirectory()) out.push(...await walk(full, exclude));
|
|
14516
14764
|
else if (entry.name.toLowerCase().endsWith(".png")) out.push(full);
|
|
@@ -14529,9 +14777,9 @@ async function scanAssets(root, opts = {}) {
|
|
|
14529
14777
|
skipped.push(`${file} (not a readable PNG)`);
|
|
14530
14778
|
continue;
|
|
14531
14779
|
}
|
|
14532
|
-
const rel =
|
|
14533
|
-
const category =
|
|
14534
|
-
let id = slugify(
|
|
14780
|
+
const rel = import_node_path26.default.relative(root, file);
|
|
14781
|
+
const category = import_node_path26.default.dirname(rel) === "." ? "" : import_node_path26.default.dirname(rel);
|
|
14782
|
+
let id = slugify(import_node_path26.default.basename(rel));
|
|
14535
14783
|
if (seen.has(id)) {
|
|
14536
14784
|
const n = seen.get(id) + 1;
|
|
14537
14785
|
seen.set(id, n);
|
|
@@ -15080,7 +15328,7 @@ async function currentArt(entry, spec) {
|
|
|
15080
15328
|
const previous = primaryOutput({ ...entry, outputs: entry.supersededOutputs });
|
|
15081
15329
|
if (!previous) return null;
|
|
15082
15330
|
const file = resolveOutputPath(previous.path, spec.root);
|
|
15083
|
-
if (!(0,
|
|
15331
|
+
if (!(0, import_node_fs23.existsSync)(file)) return null;
|
|
15084
15332
|
const contentType = previous.mediaType ?? mediaTypeFromExtension(file);
|
|
15085
15333
|
if (!contentType) return null;
|
|
15086
15334
|
let width = entry.width;
|
|
@@ -15254,15 +15502,15 @@ async function runPicker(provider, lock, lockPath, opts = {}) {
|
|
|
15254
15502
|
}
|
|
15255
15503
|
|
|
15256
15504
|
// src/gallery/snapshot.ts
|
|
15257
|
-
var
|
|
15258
|
-
var
|
|
15259
|
-
var
|
|
15260
|
-
var
|
|
15505
|
+
var import_node_crypto7 = require("crypto");
|
|
15506
|
+
var import_node_fs29 = require("fs");
|
|
15507
|
+
var import_promises26 = require("fs/promises");
|
|
15508
|
+
var import_node_path34 = __toESM(require("path"), 1);
|
|
15261
15509
|
|
|
15262
15510
|
// src/workspace.ts
|
|
15263
15511
|
var import_promises19 = require("fs/promises");
|
|
15264
|
-
var
|
|
15265
|
-
var
|
|
15512
|
+
var import_node_fs24 = require("fs");
|
|
15513
|
+
var import_node_path27 = __toESM(require("path"), 1);
|
|
15266
15514
|
var import_zod7 = require("zod");
|
|
15267
15515
|
var WorkspaceProjectSchema = import_zod7.z.object({
|
|
15268
15516
|
id: import_zod7.z.string().min(1),
|
|
@@ -15287,7 +15535,7 @@ ${parsed.error.issues.slice(0, 5).map((i) => ` ${i.path.join(".")}: ${i.message
|
|
|
15287
15535
|
);
|
|
15288
15536
|
}
|
|
15289
15537
|
async function loadWorkspace(workspacePath) {
|
|
15290
|
-
if (!(0,
|
|
15538
|
+
if (!(0, import_node_fs24.existsSync)(workspacePath)) return { version: 1, projects: [] };
|
|
15291
15539
|
let raw;
|
|
15292
15540
|
try {
|
|
15293
15541
|
raw = JSON.parse(await (0, import_promises19.readFile)(workspacePath, "utf8"));
|
|
@@ -15304,7 +15552,7 @@ async function saveWorkspace(workspacePath, ws) {
|
|
|
15304
15552
|
version: 1,
|
|
15305
15553
|
projects: [...ws.projects].sort((a, b) => a.id.localeCompare(b.id))
|
|
15306
15554
|
};
|
|
15307
|
-
await (0, import_promises19.mkdir)(
|
|
15555
|
+
await (0, import_promises19.mkdir)(import_node_path27.default.dirname(import_node_path27.default.resolve(workspacePath)), { recursive: true });
|
|
15308
15556
|
const tmp = `${workspacePath}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
15309
15557
|
try {
|
|
15310
15558
|
await (0, import_promises19.writeFile)(tmp, JSON.stringify(sorted, null, 2) + "\n");
|
|
@@ -15314,12 +15562,12 @@ async function saveWorkspace(workspacePath, ws) {
|
|
|
15314
15562
|
}
|
|
15315
15563
|
}
|
|
15316
15564
|
function toPortablePath(dir, absolute) {
|
|
15317
|
-
return
|
|
15565
|
+
return import_node_path27.default.relative(dir, absolute).split(import_node_path27.default.sep).join("/");
|
|
15318
15566
|
}
|
|
15319
15567
|
function resolveProject(dir, project) {
|
|
15320
15568
|
return {
|
|
15321
|
-
manifestPath:
|
|
15322
|
-
lockPath:
|
|
15569
|
+
manifestPath: import_node_path27.default.resolve(dir, project.manifest.split("/").join(import_node_path27.default.sep)),
|
|
15570
|
+
lockPath: import_node_path27.default.resolve(dir, project.lock.split("/").join(import_node_path27.default.sep))
|
|
15323
15571
|
};
|
|
15324
15572
|
}
|
|
15325
15573
|
function validateWorkspace(ws, dir) {
|
|
@@ -15332,21 +15580,21 @@ function validateWorkspace(ws, dir) {
|
|
|
15332
15580
|
const { manifestPath, lockPath } = resolveProject(dir, project);
|
|
15333
15581
|
lockOwners.set(lockPath, [...lockOwners.get(lockPath) ?? [], project.id]);
|
|
15334
15582
|
manifestOwners.set(manifestPath, [...manifestOwners.get(manifestPath) ?? [], project.id]);
|
|
15335
|
-
if (
|
|
15583
|
+
if (import_node_path27.default.isAbsolute(project.manifest) || import_node_path27.default.isAbsolute(project.lock)) {
|
|
15336
15584
|
diagnostics.push({
|
|
15337
15585
|
id: "absolute-path",
|
|
15338
15586
|
level: "warning",
|
|
15339
15587
|
message: `project "${project.id}" stores an absolute path; the catalog will not resolve correctly if this tree is cloned or moved elsewhere`
|
|
15340
15588
|
});
|
|
15341
15589
|
}
|
|
15342
|
-
if (!(0,
|
|
15590
|
+
if (!(0, import_node_fs24.existsSync)(manifestPath)) {
|
|
15343
15591
|
diagnostics.push({
|
|
15344
15592
|
id: "missing-manifest",
|
|
15345
15593
|
level: "error",
|
|
15346
15594
|
message: `project "${project.id}" manifest not found: ${manifestPath}`
|
|
15347
15595
|
});
|
|
15348
15596
|
}
|
|
15349
|
-
if (!(0,
|
|
15597
|
+
if (!(0, import_node_fs24.existsSync)(lockPath)) {
|
|
15350
15598
|
diagnostics.push({
|
|
15351
15599
|
id: "missing-lock",
|
|
15352
15600
|
level: "error",
|
|
@@ -15393,15 +15641,16 @@ function validateWorkspace(ws, dir) {
|
|
|
15393
15641
|
}
|
|
15394
15642
|
|
|
15395
15643
|
// src/gallery/edit.ts
|
|
15396
|
-
var
|
|
15397
|
-
var
|
|
15398
|
-
var
|
|
15644
|
+
var import_promises25 = require("fs/promises");
|
|
15645
|
+
var import_node_path33 = __toESM(require("path"), 1);
|
|
15646
|
+
var import_zod13 = require("zod");
|
|
15399
15647
|
|
|
15400
15648
|
// src/manifest-edit.ts
|
|
15401
15649
|
var import_promises20 = require("fs/promises");
|
|
15402
|
-
var
|
|
15650
|
+
var import_node_path28 = __toESM(require("path"), 1);
|
|
15403
15651
|
var import_zod8 = require("zod");
|
|
15404
15652
|
var HexSha = import_zod8.z.string().regex(/^[0-9a-f]{64}$/);
|
|
15653
|
+
var ProjectFileSchema = import_zod8.z.string().min(1).refine((file) => !import_node_path28.default.isAbsolute(file) && !file.split(/[\\/]/).includes(".."), "must be a path inside the project");
|
|
15405
15654
|
var AssetPatchSchema = import_zod8.z.object({
|
|
15406
15655
|
prompt: import_zod8.z.string().optional(),
|
|
15407
15656
|
/** Per-style override for the style being viewed; null removes it. */
|
|
@@ -15450,10 +15699,11 @@ var NewAssetSchema = import_zod8.z.object({
|
|
|
15450
15699
|
styles: import_zod8.z.array(import_zod8.z.string().min(1)).optional(),
|
|
15451
15700
|
/**
|
|
15452
15701
|
* A controlled regeneration of another asset in the same manifest,
|
|
15453
|
-
* instead of a fresh generation.
|
|
15454
|
-
* `
|
|
15455
|
-
*
|
|
15456
|
-
*
|
|
15702
|
+
* instead of a fresh generation. Every mode but `outpaint` can be
|
|
15703
|
+
* created here: `inpaint` names a mask file the gallery's mask editor
|
|
15704
|
+
* wrote first, `interpolate` names an existing image as its ending
|
|
15705
|
+
* keyframe, and `animate-skeleton` names the keypoints file the gallery
|
|
15706
|
+
* wrote first.
|
|
15457
15707
|
*/
|
|
15458
15708
|
revision: import_zod8.z.discriminatedUnion("mode", [
|
|
15459
15709
|
import_zod8.z.object({
|
|
@@ -15470,15 +15720,38 @@ var NewAssetSchema = import_zod8.z.object({
|
|
|
15470
15720
|
dithering: RevisionDitheringSchema.optional(),
|
|
15471
15721
|
ditheringStrength: import_zod8.z.number().min(0).max(10).optional()
|
|
15472
15722
|
}).strict(),
|
|
15723
|
+
import_zod8.z.object({
|
|
15724
|
+
mode: import_zod8.z.literal("inpaint"),
|
|
15725
|
+
from: import_zod8.z.string().min(1),
|
|
15726
|
+
/** Black-and-white PNG the same size as the parent; white is repainted. */
|
|
15727
|
+
mask: ProjectFileSchema
|
|
15728
|
+
}).strict(),
|
|
15729
|
+
import_zod8.z.object({
|
|
15730
|
+
mode: import_zod8.z.literal("interpolate"),
|
|
15731
|
+
from: import_zod8.z.string().min(1),
|
|
15732
|
+
/** The ending keyframe: an image already in the project, the parent's size. */
|
|
15733
|
+
lastFrame: ProjectFileSchema,
|
|
15734
|
+
fps: import_zod8.z.number().int().min(1).max(60).optional()
|
|
15735
|
+
}).strict(),
|
|
15473
15736
|
import_zod8.z.object({ mode: import_zod8.z.literal("correct-pixelart"), from: import_zod8.z.string().min(1), strength: import_zod8.z.number().min(0).max(1).optional() }).strict(),
|
|
15737
|
+
import_zod8.z.object({
|
|
15738
|
+
mode: import_zod8.z.literal("remove-background"),
|
|
15739
|
+
from: import_zod8.z.string().min(1),
|
|
15740
|
+
removalTask: import_zod8.z.enum(["simple", "complex"]).optional(),
|
|
15741
|
+
/** A short description of the foreground, sent as PixelLab's text hint. */
|
|
15742
|
+
description: import_zod8.z.string().min(1).optional()
|
|
15743
|
+
}).strict(),
|
|
15474
15744
|
import_zod8.z.object({
|
|
15475
15745
|
mode: import_zod8.z.enum(["animate", "animate-pixminimax"]),
|
|
15476
15746
|
from: import_zod8.z.string().min(1),
|
|
15477
15747
|
frames: import_zod8.z.number().int().min(4).max(40).refine((n) => n % 2 === 0, "frames must be even").optional(),
|
|
15478
15748
|
fps: import_zod8.z.number().int().min(1).max(60).optional(),
|
|
15479
15749
|
enhancePrompt: import_zod8.z.boolean().optional(),
|
|
15480
|
-
|
|
15481
|
-
|
|
15750
|
+
driftThreshold: import_zod8.z.number().min(0).optional(),
|
|
15751
|
+
/** `animate-pixminimax` only; the loader refuses these on `animate`. */
|
|
15752
|
+
direction: CharacterDirectionSchema.optional(),
|
|
15753
|
+
subjectDescription: import_zod8.z.string().min(1).max(300).optional(),
|
|
15754
|
+
initialPose: import_zod8.z.string().min(1).max(300).optional()
|
|
15482
15755
|
}).strict(),
|
|
15483
15756
|
import_zod8.z.object({
|
|
15484
15757
|
mode: import_zod8.z.literal("edit-animation"),
|
|
@@ -15796,7 +16069,7 @@ function applyEdit(raw, edit) {
|
|
|
15796
16069
|
}
|
|
15797
16070
|
}
|
|
15798
16071
|
async function withCandidate(manifestPath, edit, use, unchanged) {
|
|
15799
|
-
const absolute =
|
|
16072
|
+
const absolute = import_node_path28.default.resolve(manifestPath);
|
|
15800
16073
|
const original = await (0, import_promises20.readFile)(absolute, "utf8");
|
|
15801
16074
|
if (sha256(original) !== edit.expectedSha256) throw new ManifestDriftError(absolute);
|
|
15802
16075
|
let raw;
|
|
@@ -15810,9 +16083,9 @@ async function withCandidate(manifestPath, edit, use, unchanged) {
|
|
|
15810
16083
|
applyEdit(raw, edit);
|
|
15811
16084
|
const next = serializeLike(original, raw);
|
|
15812
16085
|
if (next === original) return unchanged(absolute, original);
|
|
15813
|
-
const tmp =
|
|
15814
|
-
|
|
15815
|
-
`.${
|
|
16086
|
+
const tmp = import_node_path28.default.join(
|
|
16087
|
+
import_node_path28.default.dirname(absolute),
|
|
16088
|
+
`.${import_node_path28.default.basename(absolute)}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`
|
|
15816
16089
|
);
|
|
15817
16090
|
try {
|
|
15818
16091
|
await (0, import_promises20.writeFile)(tmp, next);
|
|
@@ -15841,18 +16114,18 @@ async function applyManifestEdit(manifestPath, edit) {
|
|
|
15841
16114
|
}
|
|
15842
16115
|
|
|
15843
16116
|
// src/pipeline/hand-edit.ts
|
|
15844
|
-
var
|
|
16117
|
+
var import_node_fs25 = require("fs");
|
|
15845
16118
|
var import_promises21 = require("fs/promises");
|
|
15846
|
-
var
|
|
16119
|
+
var import_node_path29 = __toESM(require("path"), 1);
|
|
15847
16120
|
var import_zod9 = require("zod");
|
|
15848
16121
|
var HAND_EDIT_DIR = "edits";
|
|
15849
16122
|
function handEditPath(loaded, spec) {
|
|
15850
16123
|
const style = loaded.manifest.styles[spec.styleId];
|
|
15851
|
-
const outDir =
|
|
15852
|
-
const relative =
|
|
15853
|
-
const inside = relative && !relative.startsWith("..") && !
|
|
15854
|
-
const file = inside ? relative :
|
|
15855
|
-
return
|
|
16124
|
+
const outDir = import_node_path29.default.resolve(loaded.root, style?.outDir ?? ".");
|
|
16125
|
+
const relative = import_node_path29.default.relative(outDir, spec.outFile);
|
|
16126
|
+
const inside = relative && !relative.startsWith("..") && !import_node_path29.default.isAbsolute(relative);
|
|
16127
|
+
const file = inside ? relative : import_node_path29.default.basename(spec.outFile);
|
|
16128
|
+
return import_node_path29.default.join(outDir, HAND_EDIT_DIR, file.replace(/\.gif$/i, ".png"));
|
|
15856
16129
|
}
|
|
15857
16130
|
function handEditBases(spec, lock) {
|
|
15858
16131
|
const entry = lock.entries[lockKey(spec.styleId, spec.assetId)];
|
|
@@ -15865,10 +16138,10 @@ function handEditBases(spec, lock) {
|
|
|
15865
16138
|
}));
|
|
15866
16139
|
if (members.some((member) => member.mediaType && member.mediaType !== "image/png")) return null;
|
|
15867
16140
|
if (entry.outputs.length === 1 && !primaryOutput(entry)) return null;
|
|
15868
|
-
if (!members.every((member) => (0,
|
|
16141
|
+
if (!members.every((member) => (0, import_node_fs25.existsSync)(member.path))) return null;
|
|
15869
16142
|
return members.map(({ role, path: file }) => ({ role, path: file }));
|
|
15870
16143
|
}
|
|
15871
|
-
return (0,
|
|
16144
|
+
return (0, import_node_fs25.existsSync)(spec.outFile) ? [{ role: null, path: spec.outFile }] : null;
|
|
15872
16145
|
}
|
|
15873
16146
|
function handEditBase(spec, lock) {
|
|
15874
16147
|
const bases = handEditBases(spec, lock);
|
|
@@ -15898,18 +16171,18 @@ async function startHandEdit(loaded, lock, spec, opts = {}) {
|
|
|
15898
16171
|
`${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`
|
|
15899
16172
|
);
|
|
15900
16173
|
}
|
|
15901
|
-
const existing = spec.source ?
|
|
16174
|
+
const existing = spec.source ? import_node_path29.default.resolve(loaded.root, spec.source) : null;
|
|
15902
16175
|
const editPath = existing ?? handEditPath(loaded, spec);
|
|
15903
16176
|
const members = handEditMembers(loaded, spec, lock, editPath);
|
|
15904
16177
|
let created = false;
|
|
15905
16178
|
for (const member of members) {
|
|
15906
|
-
if ((0,
|
|
16179
|
+
if ((0, import_node_fs25.existsSync)(member.path)) continue;
|
|
15907
16180
|
if (!member.base) {
|
|
15908
16181
|
throw new ManifestEditError(
|
|
15909
16182
|
`${spec.styleId}/${spec.assetId} has no generated PNG on disk to start from; generate or restore it first`
|
|
15910
16183
|
);
|
|
15911
16184
|
}
|
|
15912
|
-
await (0, import_promises21.mkdir)(
|
|
16185
|
+
await (0, import_promises21.mkdir)(import_node_path29.default.dirname(member.path), { recursive: true });
|
|
15913
16186
|
await (0, import_promises21.copyFile)(member.base, member.path);
|
|
15914
16187
|
created = true;
|
|
15915
16188
|
}
|
|
@@ -16047,7 +16320,7 @@ async function saveHandEdit(loaded, lock, spec, input) {
|
|
|
16047
16320
|
editor: input.editor,
|
|
16048
16321
|
protocol: input.protocol,
|
|
16049
16322
|
savedAt: (input.now ?? /* @__PURE__ */ new Date()).toISOString(),
|
|
16050
|
-
project: projectPath ?
|
|
16323
|
+
project: projectPath ? import_node_path29.default.basename(projectPath) : null,
|
|
16051
16324
|
outputs: incoming.map((frame, index) => ({
|
|
16052
16325
|
role: frame.role,
|
|
16053
16326
|
basedOn: basedOnHash(entry, index),
|
|
@@ -16089,9 +16362,9 @@ function openInEditor(file, editor = process.env.PIXELKILN_EDITOR) {
|
|
|
16089
16362
|
}
|
|
16090
16363
|
|
|
16091
16364
|
// src/gallery/upload.ts
|
|
16092
|
-
var
|
|
16365
|
+
var import_node_fs26 = require("fs");
|
|
16093
16366
|
var import_promises22 = require("fs/promises");
|
|
16094
|
-
var
|
|
16367
|
+
var import_node_path30 = __toESM(require("path"), 1);
|
|
16095
16368
|
var import_zod10 = require("zod");
|
|
16096
16369
|
var MAX_UPLOAD_BYTES = 8 * 1024 * 1024;
|
|
16097
16370
|
var UploadImageSchema = import_zod10.z.object({
|
|
@@ -16104,12 +16377,12 @@ var UploadImageSchema = import_zod10.z.object({
|
|
|
16104
16377
|
overwrite: import_zod10.z.boolean().optional()
|
|
16105
16378
|
}).strict();
|
|
16106
16379
|
async function saveProjectImage(root, request) {
|
|
16107
|
-
const file =
|
|
16108
|
-
const relative =
|
|
16109
|
-
if (relative.startsWith("..") ||
|
|
16380
|
+
const file = import_node_path30.default.resolve(root, request.path);
|
|
16381
|
+
const relative = import_node_path30.default.relative(root, file);
|
|
16382
|
+
if (relative.startsWith("..") || import_node_path30.default.isAbsolute(relative)) {
|
|
16110
16383
|
throw new ManifestEditError("an upload must stay inside the project");
|
|
16111
16384
|
}
|
|
16112
|
-
const extension =
|
|
16385
|
+
const extension = import_node_path30.default.extname(file).toLowerCase();
|
|
16113
16386
|
const expected = extension === ".png" ? "png" : extension === ".jpg" || extension === ".jpeg" ? "jpeg" : null;
|
|
16114
16387
|
if (!expected) throw new ManifestEditError("an upload must be a .png, .jpg, or .jpeg path");
|
|
16115
16388
|
const bytes = Buffer.from(request.base64, "base64");
|
|
@@ -16121,70 +16394,126 @@ async function saveProjectImage(root, request) {
|
|
|
16121
16394
|
if (metadata.format !== expected) {
|
|
16122
16395
|
throw new ManifestEditError(`the upload is a ${metadata.format.toUpperCase()}; name it ${metadata.format === "png" ? ".png" : ".jpg"}`);
|
|
16123
16396
|
}
|
|
16124
|
-
if ((0,
|
|
16125
|
-
throw new ManifestEditError(`${relative.split(
|
|
16397
|
+
if ((0, import_node_fs26.existsSync)(file) && !request.overwrite) {
|
|
16398
|
+
throw new ManifestEditError(`${relative.split(import_node_path30.default.sep).join("/")} already exists; choose another name, or replace it`);
|
|
16126
16399
|
}
|
|
16127
|
-
await (0, import_promises22.mkdir)(
|
|
16400
|
+
await (0, import_promises22.mkdir)(import_node_path30.default.dirname(file), { recursive: true });
|
|
16128
16401
|
await (0, import_promises22.writeFile)(file, bytes);
|
|
16129
|
-
return relative.split(
|
|
16402
|
+
return relative.split(import_node_path30.default.sep).join("/");
|
|
16130
16403
|
}
|
|
16131
16404
|
|
|
16132
|
-
// src/gallery/
|
|
16133
|
-
var
|
|
16405
|
+
// src/gallery/image-revision.ts
|
|
16406
|
+
var import_node_fs27 = require("fs");
|
|
16134
16407
|
var import_promises23 = require("fs/promises");
|
|
16135
|
-
var
|
|
16408
|
+
var import_node_path31 = __toESM(require("path"), 1);
|
|
16136
16409
|
var import_zod11 = require("zod");
|
|
16137
|
-
var
|
|
16138
|
-
|
|
16139
|
-
styleId: import_zod11.z.string().min(1),
|
|
16140
|
-
assetId: import_zod11.z.string().min(1),
|
|
16141
|
-
frames: import_zod11.z.number().int().min(3).max(15).optional()
|
|
16142
|
-
}).strict();
|
|
16143
|
-
var SkeletonAnimationRequestSchema = import_zod11.z.object({
|
|
16144
|
-
action: import_zod11.z.literal("create-skeleton-animation"),
|
|
16410
|
+
var InpaintRevisionRequestSchema = import_zod11.z.object({
|
|
16411
|
+
action: import_zod11.z.literal("create-inpaint-revision"),
|
|
16145
16412
|
project: import_zod11.z.string().min(1).optional(),
|
|
16146
16413
|
expectedSha256: import_zod11.z.string().regex(/^[0-9a-f]{64}$/),
|
|
16147
16414
|
styleId: import_zod11.z.string().min(1),
|
|
16148
|
-
/** The sprite
|
|
16415
|
+
/** The sprite being repainted. */
|
|
16149
16416
|
from: import_zod11.z.string().min(1),
|
|
16150
16417
|
assetId: import_zod11.z.string().min(1).regex(/^[^/\\]+$/, "asset ids cannot contain slashes"),
|
|
16151
|
-
/**
|
|
16418
|
+
/** What to paint into the masked area. */
|
|
16152
16419
|
prompt: import_zod11.z.string().min(1),
|
|
16420
|
+
/** Manifest-relative `.png` destination for the mask. */
|
|
16421
|
+
maskPath: import_zod11.z.string().min(1),
|
|
16422
|
+
maskBase64: import_zod11.z.string().regex(/^[A-Za-z0-9+/]*={0,2}$/),
|
|
16423
|
+
/** Replace a file already at `maskPath`. */
|
|
16424
|
+
overwrite: import_zod11.z.boolean().optional()
|
|
16425
|
+
}).strict();
|
|
16426
|
+
async function createInpaintRevision(manifestPath, request) {
|
|
16427
|
+
const root = import_node_path31.default.dirname(import_node_path31.default.resolve(manifestPath));
|
|
16428
|
+
if (import_node_path31.default.extname(request.maskPath).toLowerCase() !== ".png") {
|
|
16429
|
+
throw new ManifestEditError("the mask must be a .png path inside the project");
|
|
16430
|
+
}
|
|
16431
|
+
const bytes = Buffer.from(request.maskBase64, "base64");
|
|
16432
|
+
const metadata = imageMetadata(bytes);
|
|
16433
|
+
if (!metadata || metadata.format !== "png") throw new ManifestEditError("the mask is not a readable PNG");
|
|
16434
|
+
const file = import_node_path31.default.resolve(root, request.maskPath);
|
|
16435
|
+
const previous = (0, import_node_fs27.existsSync)(file) ? await (0, import_promises23.readFile)(file) : null;
|
|
16436
|
+
const mask = await saveProjectImage(root, {
|
|
16437
|
+
action: "upload-image",
|
|
16438
|
+
path: request.maskPath,
|
|
16439
|
+
base64: request.maskBase64,
|
|
16440
|
+
...request.overwrite ? { overwrite: true } : {}
|
|
16441
|
+
});
|
|
16442
|
+
try {
|
|
16443
|
+
await applyManifestEdit(manifestPath, {
|
|
16444
|
+
action: "add-asset",
|
|
16445
|
+
assetId: request.assetId,
|
|
16446
|
+
expectedSha256: request.expectedSha256,
|
|
16447
|
+
asset: {
|
|
16448
|
+
prompt: request.prompt,
|
|
16449
|
+
styles: [request.styleId],
|
|
16450
|
+
revision: { mode: "inpaint", from: request.from, mask }
|
|
16451
|
+
}
|
|
16452
|
+
});
|
|
16453
|
+
} catch (error) {
|
|
16454
|
+
if (previous) await (0, import_promises23.writeFile)(file, previous);
|
|
16455
|
+
else await (0, import_promises23.rm)(file, { force: true });
|
|
16456
|
+
throw error;
|
|
16457
|
+
}
|
|
16458
|
+
return mask;
|
|
16459
|
+
}
|
|
16460
|
+
|
|
16461
|
+
// src/gallery/skeleton.ts
|
|
16462
|
+
var import_node_fs28 = require("fs");
|
|
16463
|
+
var import_promises24 = require("fs/promises");
|
|
16464
|
+
var import_node_path32 = __toESM(require("path"), 1);
|
|
16465
|
+
var import_zod12 = require("zod");
|
|
16466
|
+
var SkeletonEstimateRequestSchema = import_zod12.z.object({
|
|
16467
|
+
project: import_zod12.z.string().min(1).optional(),
|
|
16468
|
+
styleId: import_zod12.z.string().min(1),
|
|
16469
|
+
assetId: import_zod12.z.string().min(1),
|
|
16470
|
+
frames: import_zod12.z.number().int().min(3).max(15).optional()
|
|
16471
|
+
}).strict();
|
|
16472
|
+
var SkeletonAnimationRequestSchema = import_zod12.z.object({
|
|
16473
|
+
action: import_zod12.z.literal("create-skeleton-animation"),
|
|
16474
|
+
project: import_zod12.z.string().min(1).optional(),
|
|
16475
|
+
expectedSha256: import_zod12.z.string().regex(/^[0-9a-f]{64}$/),
|
|
16476
|
+
styleId: import_zod12.z.string().min(1),
|
|
16477
|
+
/** The sprite the animation poses. */
|
|
16478
|
+
from: import_zod12.z.string().min(1),
|
|
16479
|
+
assetId: import_zod12.z.string().min(1).regex(/^[^/\\]+$/, "asset ids cannot contain slashes"),
|
|
16480
|
+
/** The motion, sent as PixelLab's `action`. */
|
|
16481
|
+
prompt: import_zod12.z.string().min(1),
|
|
16153
16482
|
direction: CharacterDirectionSchema,
|
|
16154
|
-
description:
|
|
16155
|
-
skeletonTemplate:
|
|
16483
|
+
description: import_zod12.z.string().min(1).optional(),
|
|
16484
|
+
skeletonTemplate: import_zod12.z.string().min(1).optional(),
|
|
16156
16485
|
/** Manifest-relative JSON path inside the project. */
|
|
16157
|
-
keypointsFile:
|
|
16486
|
+
keypointsFile: import_zod12.z.string().min(1),
|
|
16158
16487
|
/** Poses to write to `keypointsFile`; omitted to use a file that already exists. */
|
|
16159
16488
|
set: SkeletonSetSchema.optional(),
|
|
16160
16489
|
/** Replace an existing `keypointsFile` with `set`. */
|
|
16161
|
-
overwrite:
|
|
16490
|
+
overwrite: import_zod12.z.boolean().optional()
|
|
16162
16491
|
}).strict();
|
|
16163
|
-
var SkeletonKeypointsUpdateSchema =
|
|
16164
|
-
action:
|
|
16165
|
-
project:
|
|
16166
|
-
styleId:
|
|
16167
|
-
assetId:
|
|
16492
|
+
var SkeletonKeypointsUpdateSchema = import_zod12.z.object({
|
|
16493
|
+
action: import_zod12.z.literal("update-skeleton-keypoints"),
|
|
16494
|
+
project: import_zod12.z.string().min(1).optional(),
|
|
16495
|
+
styleId: import_zod12.z.string().min(1),
|
|
16496
|
+
assetId: import_zod12.z.string().min(1),
|
|
16168
16497
|
/** The file's SHA-256 when the page loaded it; anything else on disk is a concurrent edit. */
|
|
16169
|
-
expectedSha256:
|
|
16498
|
+
expectedSha256: import_zod12.z.string().regex(/^[0-9a-f]{64}$/),
|
|
16170
16499
|
set: SkeletonSetSchema
|
|
16171
16500
|
}).strict();
|
|
16172
16501
|
async function createSkeletonAnimation(manifestPath, request) {
|
|
16173
|
-
const root =
|
|
16174
|
-
const file =
|
|
16175
|
-
const relative =
|
|
16176
|
-
if (!request.keypointsFile.endsWith(".json") || relative.startsWith("..") ||
|
|
16502
|
+
const root = import_node_path32.default.dirname(import_node_path32.default.resolve(manifestPath));
|
|
16503
|
+
const file = import_node_path32.default.resolve(root, request.keypointsFile);
|
|
16504
|
+
const relative = import_node_path32.default.relative(root, file);
|
|
16505
|
+
if (!request.keypointsFile.endsWith(".json") || relative.startsWith("..") || import_node_path32.default.isAbsolute(relative)) {
|
|
16177
16506
|
throw new ManifestEditError("the keypoints file must be a .json path inside the project");
|
|
16178
16507
|
}
|
|
16179
|
-
const existed = (0,
|
|
16508
|
+
const existed = (0, import_node_fs28.existsSync)(file);
|
|
16180
16509
|
let previous = null;
|
|
16181
16510
|
if (request.set) {
|
|
16182
16511
|
if (existed && !request.overwrite) {
|
|
16183
16512
|
throw new ManifestEditError(`${request.keypointsFile} already exists; choose another path, or replace it`);
|
|
16184
16513
|
}
|
|
16185
|
-
if (existed) previous = await (0,
|
|
16186
|
-
await (0,
|
|
16187
|
-
await (0,
|
|
16514
|
+
if (existed) previous = await (0, import_promises24.readFile)(file);
|
|
16515
|
+
await (0, import_promises24.mkdir)(import_node_path32.default.dirname(file), { recursive: true });
|
|
16516
|
+
await (0, import_promises24.writeFile)(file, JSON.stringify(request.set, null, 2) + "\n");
|
|
16188
16517
|
} else if (!existed) {
|
|
16189
16518
|
throw new ManifestEditError(`${request.keypointsFile} does not exist; paste poses or estimate them from the sprite`);
|
|
16190
16519
|
}
|
|
@@ -16199,7 +16528,7 @@ async function createSkeletonAnimation(manifestPath, request) {
|
|
|
16199
16528
|
revision: {
|
|
16200
16529
|
mode: "animate-skeleton",
|
|
16201
16530
|
from: request.from,
|
|
16202
|
-
keypointsFile: relative.split(
|
|
16531
|
+
keypointsFile: relative.split(import_node_path32.default.sep).join("/"),
|
|
16203
16532
|
direction: request.direction,
|
|
16204
16533
|
...request.description ? { description: request.description } : {},
|
|
16205
16534
|
...request.skeletonTemplate ? { skeletonTemplate: request.skeletonTemplate } : {}
|
|
@@ -16208,8 +16537,8 @@ async function createSkeletonAnimation(manifestPath, request) {
|
|
|
16208
16537
|
});
|
|
16209
16538
|
} catch (error) {
|
|
16210
16539
|
if (request.set) {
|
|
16211
|
-
if (previous) await (0,
|
|
16212
|
-
else await (0,
|
|
16540
|
+
if (previous) await (0, import_promises24.writeFile)(file, previous);
|
|
16541
|
+
else await (0, import_promises24.rm)(file, { force: true });
|
|
16213
16542
|
}
|
|
16214
16543
|
throw error;
|
|
16215
16544
|
}
|
|
@@ -16220,64 +16549,64 @@ async function updateSkeletonKeypoints(ctx, request) {
|
|
|
16220
16549
|
if (!spec) throw new ManifestEditError(`"${key}" is not declared by the manifest`);
|
|
16221
16550
|
const file = spec.revision?.mode === "animate-skeleton" ? spec.revision.keypointsFile : void 0;
|
|
16222
16551
|
if (!file) throw new ManifestEditError(`${key} is not an animate-skeleton revision`);
|
|
16223
|
-
const relative =
|
|
16224
|
-
if (relative.startsWith("..") ||
|
|
16552
|
+
const relative = import_node_path32.default.relative(ctx.loaded.root, file);
|
|
16553
|
+
if (relative.startsWith("..") || import_node_path32.default.isAbsolute(relative)) {
|
|
16225
16554
|
throw new ManifestEditError(`${key}'s keypoints file is outside the project; edit it by hand`);
|
|
16226
16555
|
}
|
|
16227
|
-
const current = (0,
|
|
16556
|
+
const current = (0, import_node_fs28.existsSync)(file) ? sha256(await (0, import_promises24.readFile)(file)) : null;
|
|
16228
16557
|
if (current !== request.expectedSha256) {
|
|
16229
16558
|
throw Object.assign(
|
|
16230
|
-
new Error(`${relative.split(
|
|
16559
|
+
new Error(`${relative.split(import_node_path32.default.sep).join("/")} changed on disk since this page loaded it; press Refresh and edit again`),
|
|
16231
16560
|
{ status: 409 }
|
|
16232
16561
|
);
|
|
16233
16562
|
}
|
|
16234
|
-
await (0,
|
|
16235
|
-
return relative.split(
|
|
16563
|
+
await (0, import_promises24.writeFile)(file, JSON.stringify(request.set, null, 2) + "\n");
|
|
16564
|
+
return relative.split(import_node_path32.default.sep).join("/");
|
|
16236
16565
|
}
|
|
16237
16566
|
|
|
16238
16567
|
// src/gallery/edit.ts
|
|
16239
|
-
var Base64 =
|
|
16240
|
-
var HandEditRequestSchema =
|
|
16241
|
-
|
|
16242
|
-
action:
|
|
16243
|
-
project:
|
|
16244
|
-
assetId:
|
|
16245
|
-
styleId:
|
|
16246
|
-
expectedSha256:
|
|
16568
|
+
var Base64 = import_zod13.z.string().regex(/^[A-Za-z0-9+/]*={0,2}$/).max(Math.ceil(MAX_HAND_EDIT_BYTES / 3) * 4);
|
|
16569
|
+
var HandEditRequestSchema = import_zod13.z.discriminatedUnion("action", [
|
|
16570
|
+
import_zod13.z.object({
|
|
16571
|
+
action: import_zod13.z.literal("start-edit"),
|
|
16572
|
+
project: import_zod13.z.string().min(1).optional(),
|
|
16573
|
+
assetId: import_zod13.z.string().min(1),
|
|
16574
|
+
styleId: import_zod13.z.string().min(1),
|
|
16575
|
+
expectedSha256: import_zod13.z.string().regex(/^[0-9a-f]{64}$/),
|
|
16247
16576
|
/** Also launch the author's editor on the file. */
|
|
16248
|
-
open:
|
|
16577
|
+
open: import_zod13.z.boolean().optional()
|
|
16249
16578
|
}).strict(),
|
|
16250
|
-
|
|
16251
|
-
action:
|
|
16252
|
-
project:
|
|
16253
|
-
assetId:
|
|
16254
|
-
styleId:
|
|
16255
|
-
expectedSha256:
|
|
16579
|
+
import_zod13.z.object({
|
|
16580
|
+
action: import_zod13.z.literal("detach-edit"),
|
|
16581
|
+
project: import_zod13.z.string().min(1).optional(),
|
|
16582
|
+
assetId: import_zod13.z.string().min(1),
|
|
16583
|
+
styleId: import_zod13.z.string().min(1),
|
|
16584
|
+
expectedSha256: import_zod13.z.string().regex(/^[0-9a-f]{64}$/)
|
|
16256
16585
|
}).strict(),
|
|
16257
16586
|
/** From the in-browser editor: the page holds the bytes, the server validates and writes. */
|
|
16258
|
-
|
|
16259
|
-
action:
|
|
16260
|
-
project:
|
|
16261
|
-
assetId:
|
|
16262
|
-
styleId:
|
|
16263
|
-
expectedSha256:
|
|
16587
|
+
import_zod13.z.object({
|
|
16588
|
+
action: import_zod13.z.literal("save-edit"),
|
|
16589
|
+
project: import_zod13.z.string().min(1).optional(),
|
|
16590
|
+
assetId: import_zod13.z.string().min(1),
|
|
16591
|
+
styleId: import_zod13.z.string().min(1),
|
|
16592
|
+
expectedSha256: import_zod13.z.string().regex(/^[0-9a-f]{64}$/),
|
|
16264
16593
|
/** The flattened image; for a frame set, `frames` instead. */
|
|
16265
16594
|
png: Base64.min(1).optional(),
|
|
16266
16595
|
/** Every frame of a set, tagged with the role it was opened under (null for one added in the editor). */
|
|
16267
|
-
frames:
|
|
16596
|
+
frames: import_zod13.z.array(import_zod13.z.object({ role: import_zod13.z.string().min(1).nullable(), png: Base64.min(1) }).strict()).min(1).max(256).optional(),
|
|
16268
16597
|
/** The editor's layered project file, kept beside the edit. */
|
|
16269
16598
|
pxo: Base64.optional(),
|
|
16270
16599
|
/** Editor identity from the bridge's ready message. */
|
|
16271
|
-
editor:
|
|
16272
|
-
protocol:
|
|
16600
|
+
editor: import_zod13.z.string().min(1).max(200),
|
|
16601
|
+
protocol: import_zod13.z.number().int().positive()
|
|
16273
16602
|
}).strict()
|
|
16274
16603
|
]);
|
|
16275
16604
|
var UNDO_DEPTH = 20;
|
|
16276
|
-
var UndoRequestSchema =
|
|
16277
|
-
action:
|
|
16278
|
-
project:
|
|
16605
|
+
var UndoRequestSchema = import_zod13.z.object({
|
|
16606
|
+
action: import_zod13.z.literal("undo-edit"),
|
|
16607
|
+
project: import_zod13.z.string().min(1).optional(),
|
|
16279
16608
|
/** The manifest as the page last saw it; undo refuses a manifest someone else has changed since. */
|
|
16280
|
-
expectedSha256:
|
|
16609
|
+
expectedSha256: import_zod13.z.string().regex(/^[0-9a-f]{64}$/)
|
|
16281
16610
|
}).strict();
|
|
16282
16611
|
function createGalleryEditHandler(opts) {
|
|
16283
16612
|
const log = opts.onProgress ?? (() => {
|
|
@@ -16286,7 +16615,7 @@ function createGalleryEditHandler(opts) {
|
|
|
16286
16615
|
const history = /* @__PURE__ */ new Map();
|
|
16287
16616
|
const manifestOf = async (project) => {
|
|
16288
16617
|
try {
|
|
16289
|
-
return
|
|
16618
|
+
return import_node_path33.default.resolve(await opts.manifestFor(typeof project === "string" ? project : void 0));
|
|
16290
16619
|
} catch {
|
|
16291
16620
|
return null;
|
|
16292
16621
|
}
|
|
@@ -16296,7 +16625,7 @@ function createGalleryEditHandler(opts) {
|
|
|
16296
16625
|
if (undo.success) {
|
|
16297
16626
|
const manifestPath2 = await manifestOf(undo.data.project);
|
|
16298
16627
|
if (!manifestPath2) throw new ManifestEditError("no manifest to undo an edit in");
|
|
16299
|
-
const current = await (0,
|
|
16628
|
+
const current = await (0, import_promises25.readFile)(manifestPath2, "utf8");
|
|
16300
16629
|
if (sha256(current) !== undo.data.expectedSha256) throw new ManifestDriftError(manifestPath2);
|
|
16301
16630
|
const stack = history.get(manifestPath2) ?? [];
|
|
16302
16631
|
const last = stack[stack.length - 1];
|
|
@@ -16306,17 +16635,17 @@ function createGalleryEditHandler(opts) {
|
|
|
16306
16635
|
throw Object.assign(new Error("the manifest changed outside the gallery since its last edit; undo would discard that change"), { status: 409 });
|
|
16307
16636
|
}
|
|
16308
16637
|
const tmp = manifestPath2 + ".pixelkiln-undo";
|
|
16309
|
-
await (0,
|
|
16310
|
-
await (0,
|
|
16638
|
+
await (0, import_promises25.writeFile)(tmp, last.before);
|
|
16639
|
+
await (0, import_promises25.rename)(tmp, manifestPath2);
|
|
16311
16640
|
stack.pop();
|
|
16312
|
-
log(` manifest edit undone in ${
|
|
16641
|
+
log(` manifest edit undone in ${import_node_path33.default.relative(process.cwd(), manifestPath2) || manifestPath2}`);
|
|
16313
16642
|
return opts.reload();
|
|
16314
16643
|
}
|
|
16315
16644
|
const manifestPath = await manifestOf(body?.project);
|
|
16316
|
-
const before = manifestPath ? await (0,
|
|
16645
|
+
const before = manifestPath ? await (0, import_promises25.readFile)(manifestPath, "utf8").catch(() => null) : null;
|
|
16317
16646
|
const build = await apply(body);
|
|
16318
16647
|
if (manifestPath && before !== null) {
|
|
16319
|
-
const after = await (0,
|
|
16648
|
+
const after = await (0, import_promises25.readFile)(manifestPath, "utf8").catch(() => null);
|
|
16320
16649
|
if (after !== null && after !== before) {
|
|
16321
16650
|
const stack = history.get(manifestPath) ?? [];
|
|
16322
16651
|
stack.push({ before, afterSha: sha256(after) });
|
|
@@ -16371,7 +16700,7 @@ function createEditApplier(opts, log) {
|
|
|
16371
16700
|
} catch (error) {
|
|
16372
16701
|
throw new ManifestEditError(error instanceof Error ? error.message : String(error));
|
|
16373
16702
|
}
|
|
16374
|
-
const written = await saveProjectImage(
|
|
16703
|
+
const written = await saveProjectImage(import_node_path33.default.dirname(import_node_path33.default.resolve(manifestPath2)), upload.data);
|
|
16375
16704
|
log(` image added: ${written}`);
|
|
16376
16705
|
return opts.reload();
|
|
16377
16706
|
}
|
|
@@ -16407,6 +16736,23 @@ function createEditApplier(opts, log) {
|
|
|
16407
16736
|
"invalid skeleton animation: " + skeleton.error.issues.slice(0, 3).map((issue) => `${issue.path.join(".")}: ${issue.message}`).join("; ")
|
|
16408
16737
|
);
|
|
16409
16738
|
}
|
|
16739
|
+
const inpaint = InpaintRevisionRequestSchema.safeParse(body);
|
|
16740
|
+
if (inpaint.success) {
|
|
16741
|
+
let manifestPath2;
|
|
16742
|
+
try {
|
|
16743
|
+
manifestPath2 = await opts.manifestFor(inpaint.data.project);
|
|
16744
|
+
} catch (error) {
|
|
16745
|
+
throw new ManifestEditError(error instanceof Error ? error.message : String(error));
|
|
16746
|
+
}
|
|
16747
|
+
const mask = await createInpaintRevision(manifestPath2, inpaint.data);
|
|
16748
|
+
log(` manifest edited: added inpaint revision ${inpaint.data.assetId} (mask ${mask})`);
|
|
16749
|
+
return opts.reload();
|
|
16750
|
+
}
|
|
16751
|
+
if (body?.action === "create-inpaint-revision") {
|
|
16752
|
+
throw new ManifestEditError(
|
|
16753
|
+
"invalid inpaint revision: " + inpaint.error.issues.slice(0, 3).map((issue) => `${issue.path.join(".")}: ${issue.message}`).join("; ")
|
|
16754
|
+
);
|
|
16755
|
+
}
|
|
16410
16756
|
const parsed = ManifestEditSchema.safeParse(body);
|
|
16411
16757
|
if (!parsed.success) {
|
|
16412
16758
|
throw new ManifestEditError(
|
|
@@ -16421,7 +16767,7 @@ function createEditApplier(opts, log) {
|
|
|
16421
16767
|
}
|
|
16422
16768
|
const result = await applyManifestEdit(manifestPath, parsed.data);
|
|
16423
16769
|
if (result.changed) {
|
|
16424
|
-
const relative =
|
|
16770
|
+
const relative = import_node_path33.default.relative(process.cwd(), result.manifestPath);
|
|
16425
16771
|
const shown = !relative || relative.startsWith("..") ? result.manifestPath : relative;
|
|
16426
16772
|
log(` manifest edited: ${describeEdit(parsed.data)} in ${shown}`);
|
|
16427
16773
|
}
|
|
@@ -16449,7 +16795,7 @@ function describeEdit(edit) {
|
|
|
16449
16795
|
// src/gallery/snapshot.ts
|
|
16450
16796
|
var PROJECT_FILE_TYPE = "application/octet-stream";
|
|
16451
16797
|
function galleryMediaId(absolutePath) {
|
|
16452
|
-
return (0,
|
|
16798
|
+
return (0, import_node_crypto7.createHash)("sha256").update(import_node_path34.default.resolve(absolutePath)).digest("hex").slice(0, 24);
|
|
16453
16799
|
}
|
|
16454
16800
|
function galleryMediaRoute(id) {
|
|
16455
16801
|
return `/media/${id}`;
|
|
@@ -16468,9 +16814,9 @@ function describeHistory(media, entry, cacheDir) {
|
|
|
16468
16814
|
return (entry.history ?? []).map((generation, i) => {
|
|
16469
16815
|
const outputs = generation.outputs.map((output) => {
|
|
16470
16816
|
const mediaType = output.mediaType ?? MediaType.PNG;
|
|
16471
|
-
const file =
|
|
16817
|
+
const file = import_node_path34.default.join(cacheDir, cacheFileName(output.sha256, mediaType));
|
|
16472
16818
|
let url = null;
|
|
16473
|
-
if ((0,
|
|
16819
|
+
if ((0, import_node_fs29.existsSync)(file)) {
|
|
16474
16820
|
const id = galleryMediaId(file);
|
|
16475
16821
|
media.set(id, { path: file, contentType: mediaType });
|
|
16476
16822
|
url = `${galleryMediaRoute(id)}?v=${output.sha256.slice(0, 24)}`;
|
|
@@ -16502,7 +16848,7 @@ function describeHistory(media, entry, cacheDir) {
|
|
|
16502
16848
|
var MAX_COMPARED_BYTES = 8 * 1024 * 1024;
|
|
16503
16849
|
async function samePixels(a, b) {
|
|
16504
16850
|
try {
|
|
16505
|
-
const [left, right] = await Promise.all([(0,
|
|
16851
|
+
const [left, right] = await Promise.all([(0, import_promises26.readFile)(a), (0, import_promises26.readFile)(b)]);
|
|
16506
16852
|
if (left.length > MAX_COMPARED_BYTES || right.length > MAX_COMPARED_BYTES) return false;
|
|
16507
16853
|
const x = decodePng(left);
|
|
16508
16854
|
const y = decodePng(right);
|
|
@@ -16568,7 +16914,7 @@ function describeCharacter(spec, entry) {
|
|
|
16568
16914
|
}
|
|
16569
16915
|
async function fileInfo(absolutePath) {
|
|
16570
16916
|
try {
|
|
16571
|
-
const info = await (0,
|
|
16917
|
+
const info = await (0, import_promises26.stat)(absolutePath);
|
|
16572
16918
|
return info.isFile() ? { bytes: info.size, modifiedAt: info.mtime.toISOString() } : null;
|
|
16573
16919
|
} catch {
|
|
16574
16920
|
return null;
|
|
@@ -16600,10 +16946,10 @@ async function describeOutput(media, root, absolutePath, recorded = {}) {
|
|
|
16600
16946
|
async function describeQuality(media, root, inspection) {
|
|
16601
16947
|
const outputPaths = inspection.outputs ?? [inspection.output];
|
|
16602
16948
|
const outputs = await Promise.all(
|
|
16603
|
-
outputPaths.map((file) => describeOutput(media, root,
|
|
16949
|
+
outputPaths.map((file) => describeOutput(media, root, import_node_path34.default.resolve(root, file)))
|
|
16604
16950
|
);
|
|
16605
|
-
const recordPath =
|
|
16606
|
-
const recordExists = (0,
|
|
16951
|
+
const recordPath = import_node_path34.default.resolve(root, inspection.record);
|
|
16952
|
+
const recordExists = (0, import_node_fs29.existsSync)(recordPath);
|
|
16607
16953
|
let options = null;
|
|
16608
16954
|
let check = null;
|
|
16609
16955
|
if (recordExists) {
|
|
@@ -16622,7 +16968,7 @@ async function describeQuality(media, root, inspection) {
|
|
|
16622
16968
|
return {
|
|
16623
16969
|
state: inspection.state,
|
|
16624
16970
|
reason: inspection.reason,
|
|
16625
|
-
output: portableOutputPath(
|
|
16971
|
+
output: portableOutputPath(import_node_path34.default.resolve(root, inspection.output), root),
|
|
16626
16972
|
record: portableOutputPath(recordPath, root),
|
|
16627
16973
|
recordExists,
|
|
16628
16974
|
outputs,
|
|
@@ -16660,9 +17006,9 @@ async function buildGallerySnapshot(opts) {
|
|
|
16660
17006
|
const media = /* @__PURE__ */ new Map();
|
|
16661
17007
|
const plan = await buildPlan(specs, lock);
|
|
16662
17008
|
const items = [];
|
|
16663
|
-
const manifestText = await (0,
|
|
17009
|
+
const manifestText = await (0, import_promises26.readFile)(loaded.path, "utf8");
|
|
16664
17010
|
const manifestSha256 = sha256(manifestText);
|
|
16665
|
-
const cacheDir =
|
|
17011
|
+
const cacheDir = import_node_path34.default.resolve(import_node_path34.default.dirname(opts.lockPath), ".pixelkiln", "cache");
|
|
16666
17012
|
let rawStyles = {};
|
|
16667
17013
|
try {
|
|
16668
17014
|
const raw = JSON.parse(manifestText);
|
|
@@ -16686,12 +17032,12 @@ async function buildGallerySnapshot(opts) {
|
|
|
16686
17032
|
)
|
|
16687
17033
|
);
|
|
16688
17034
|
if (spec.source) {
|
|
16689
|
-
const editPath =
|
|
17035
|
+
const editPath = import_node_path34.default.resolve(root, spec.source);
|
|
16690
17036
|
const memberCount = sourceIsStem(spec.source, entry, root) ? entry.outputs.length : 1;
|
|
16691
17037
|
const editShas = await Promise.all(
|
|
16692
17038
|
Array.from({ length: memberCount }, async (_, index) => {
|
|
16693
17039
|
const file = sourceOutputPath(spec.source, entry, index, root);
|
|
16694
|
-
return { file, sha256: (0,
|
|
17040
|
+
return { file, sha256: (0, import_node_fs29.existsSync)(file) ? await sha256File(file) : null };
|
|
16695
17041
|
})
|
|
16696
17042
|
);
|
|
16697
17043
|
edits = await Promise.all(editShas.map(({ file, sha256: editSha }) => describeOutput(media, root, file, { sha256: editSha })));
|
|
@@ -16699,7 +17045,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
16699
17045
|
const companion = await readHandEditCompanion(editPath);
|
|
16700
17046
|
if (companion) {
|
|
16701
17047
|
const projectPath = handEditProjectPath(editPath);
|
|
16702
|
-
const project = companion.project && (0,
|
|
17048
|
+
const project = companion.project && (0, import_node_fs29.existsSync)(projectPath) ? await fileInfo(projectPath) : null;
|
|
16703
17049
|
let projectUrl = null;
|
|
16704
17050
|
if (project) {
|
|
16705
17051
|
const id = galleryMediaId(projectPath);
|
|
@@ -16735,8 +17081,8 @@ async function buildGallerySnapshot(opts) {
|
|
|
16735
17081
|
editStatus = edits.some((member) => !member.exists) ? "missing" : editChanged.every((changed) => !changed) ? "same" : regenerated ? "regenerated-since" : "edited";
|
|
16736
17082
|
}
|
|
16737
17083
|
} else if (spec.source) {
|
|
16738
|
-
outputs = [await describeOutput(media, root,
|
|
16739
|
-
} else if (!entry && (0,
|
|
17084
|
+
outputs = [await describeOutput(media, root, import_node_path34.default.resolve(root, spec.source))];
|
|
17085
|
+
} else if (!entry && (0, import_node_fs29.existsSync)(spec.outFile)) {
|
|
16740
17086
|
outputs = [await describeOutput(media, root, spec.outFile)];
|
|
16741
17087
|
} else {
|
|
16742
17088
|
outputs = [];
|
|
@@ -16939,7 +17285,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
16939
17285
|
id: loaded.manifest.name,
|
|
16940
17286
|
name: loaded.manifest.name,
|
|
16941
17287
|
manifest: loaded.path,
|
|
16942
|
-
lock:
|
|
17288
|
+
lock: import_node_path34.default.resolve(opts.lockPath),
|
|
16943
17289
|
root,
|
|
16944
17290
|
provider: loaded.manifest.provider,
|
|
16945
17291
|
account: null,
|
|
@@ -16965,7 +17311,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
16965
17311
|
return { snapshot, media };
|
|
16966
17312
|
}
|
|
16967
17313
|
async function buildWorkspaceGallerySnapshot(opts) {
|
|
16968
|
-
const dir =
|
|
17314
|
+
const dir = import_node_path34.default.dirname(import_node_path34.default.resolve(opts.workspacePath));
|
|
16969
17315
|
const media = /* @__PURE__ */ new Map();
|
|
16970
17316
|
const projects = [];
|
|
16971
17317
|
const items = [];
|
|
@@ -17008,11 +17354,11 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
17008
17354
|
id: project.id,
|
|
17009
17355
|
name: loaded.manifest.name,
|
|
17010
17356
|
manifest: manifestPath,
|
|
17011
|
-
lock:
|
|
17357
|
+
lock: import_node_path34.default.resolve(lockPath),
|
|
17012
17358
|
root: loaded.root,
|
|
17013
17359
|
provider: loaded.manifest.provider,
|
|
17014
17360
|
account: project.account ?? null,
|
|
17015
|
-
manifestSha256: manifestSha256 ?? sha256(await (0,
|
|
17361
|
+
manifestSha256: manifestSha256 ?? sha256(await (0, import_promises26.readFile)(manifestPath, "utf8")),
|
|
17016
17362
|
historyLimit: historyLimit(loaded.manifest),
|
|
17017
17363
|
entries: Object.keys(lock.entries).length,
|
|
17018
17364
|
items: projectItems.length,
|
|
@@ -17024,8 +17370,8 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
17024
17370
|
id: project.id,
|
|
17025
17371
|
name: project.id,
|
|
17026
17372
|
manifest: manifestPath,
|
|
17027
|
-
lock:
|
|
17028
|
-
root:
|
|
17373
|
+
lock: import_node_path34.default.resolve(lockPath),
|
|
17374
|
+
root: import_node_path34.default.dirname(manifestPath),
|
|
17029
17375
|
provider: project.provider,
|
|
17030
17376
|
account: project.account ?? null,
|
|
17031
17377
|
manifestSha256: null,
|
|
@@ -17052,7 +17398,7 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
17052
17398
|
version: 1,
|
|
17053
17399
|
generatedAt: (opts.now ?? (() => /* @__PURE__ */ new Date()))().toISOString(),
|
|
17054
17400
|
project: null,
|
|
17055
|
-
workspace: { path:
|
|
17401
|
+
workspace: { path: import_node_path34.default.resolve(opts.workspacePath), dir, projects },
|
|
17056
17402
|
filter,
|
|
17057
17403
|
totals: {
|
|
17058
17404
|
entries: projects.reduce((sum, project) => sum + project.entries, 0),
|
|
@@ -17069,7 +17415,7 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
17069
17415
|
}
|
|
17070
17416
|
|
|
17071
17417
|
// src/gallery/page.ts
|
|
17072
|
-
var
|
|
17418
|
+
var import_node_fs30 = require("fs");
|
|
17073
17419
|
|
|
17074
17420
|
// src/gallery/client-bundle.ts
|
|
17075
17421
|
var import_node_module = require("module");
|
|
@@ -17109,8 +17455,8 @@ var client;
|
|
|
17109
17455
|
function clientAssets() {
|
|
17110
17456
|
const prebuilt = new URL("gallery.js", CLIENT_DIR);
|
|
17111
17457
|
client ??= {
|
|
17112
|
-
css: (0,
|
|
17113
|
-
js: (0,
|
|
17458
|
+
css: (0, import_node_fs30.readFileSync)(new URL("gallery.css", CLIENT_DIR), "utf8"),
|
|
17459
|
+
js: (0, import_node_fs30.existsSync)(prebuilt) ? (0, import_node_fs30.readFileSync)(prebuilt, "utf8") : bundleGalleryClient()
|
|
17114
17460
|
};
|
|
17115
17461
|
return client;
|
|
17116
17462
|
}
|
|
@@ -17252,11 +17598,11 @@ var EDITOR_PIN = pin_default;
|
|
|
17252
17598
|
var EDITOR_RELEASE_BASE = "https://github.com/gfargo/pixelkiln/releases/download";
|
|
17253
17599
|
|
|
17254
17600
|
// src/editor/install.ts
|
|
17255
|
-
var
|
|
17256
|
-
var
|
|
17257
|
-
var
|
|
17258
|
-
var
|
|
17259
|
-
var
|
|
17601
|
+
var import_node_crypto8 = require("crypto");
|
|
17602
|
+
var import_node_fs31 = require("fs");
|
|
17603
|
+
var import_promises27 = require("fs/promises");
|
|
17604
|
+
var import_node_os4 = __toESM(require("os"), 1);
|
|
17605
|
+
var import_node_path35 = __toESM(require("path"), 1);
|
|
17260
17606
|
var EditorInstallError = class extends Error {
|
|
17261
17607
|
constructor(message6, file) {
|
|
17262
17608
|
super(message6);
|
|
@@ -17267,15 +17613,15 @@ var EditorInstallError = class extends Error {
|
|
|
17267
17613
|
};
|
|
17268
17614
|
function editorToolsRoot() {
|
|
17269
17615
|
const override = process.env.PIXELKILN_TOOLS_DIR?.trim();
|
|
17270
|
-
if (override) return
|
|
17271
|
-
if (process.platform === "darwin") return
|
|
17616
|
+
if (override) return import_node_path35.default.resolve(override);
|
|
17617
|
+
if (process.platform === "darwin") return import_node_path35.default.join(import_node_os4.default.homedir(), "Library", "Caches", "pixelkiln", "tools");
|
|
17272
17618
|
if (process.platform === "win32") {
|
|
17273
|
-
return
|
|
17619
|
+
return import_node_path35.default.join(process.env.LOCALAPPDATA ?? import_node_path35.default.join(import_node_os4.default.homedir(), "AppData", "Local"), "pixelkiln", "tools");
|
|
17274
17620
|
}
|
|
17275
|
-
return
|
|
17621
|
+
return import_node_path35.default.join(process.env.XDG_CACHE_HOME ?? import_node_path35.default.join(import_node_os4.default.homedir(), ".cache"), "pixelkiln", "tools");
|
|
17276
17622
|
}
|
|
17277
17623
|
function editorDir(pin = EDITOR_PIN, root = editorToolsRoot()) {
|
|
17278
|
-
return
|
|
17624
|
+
return import_node_path35.default.join(root, "pixelorama", pin.release ?? "unpublished");
|
|
17279
17625
|
}
|
|
17280
17626
|
function editorBaseUrl(pin = EDITOR_PIN) {
|
|
17281
17627
|
const override = process.env.PIXELKILN_EDITOR_URL?.trim();
|
|
@@ -17285,7 +17631,7 @@ function editorBaseUrl(pin = EDITOR_PIN) {
|
|
|
17285
17631
|
}
|
|
17286
17632
|
async function sha256Of(file) {
|
|
17287
17633
|
try {
|
|
17288
|
-
return (0,
|
|
17634
|
+
return (0, import_node_crypto8.createHash)("sha256").update(await (0, import_promises27.readFile)(file)).digest("hex");
|
|
17289
17635
|
} catch {
|
|
17290
17636
|
return null;
|
|
17291
17637
|
}
|
|
@@ -17298,8 +17644,8 @@ async function editorStatus(opts = {}) {
|
|
|
17298
17644
|
let totalBytes = 0;
|
|
17299
17645
|
for (const [name, expected] of Object.entries(pin.files)) {
|
|
17300
17646
|
totalBytes += expected.bytes;
|
|
17301
|
-
const file =
|
|
17302
|
-
const info = (0,
|
|
17647
|
+
const file = import_node_path35.default.join(dir, name);
|
|
17648
|
+
const info = (0, import_node_fs31.existsSync)(file) ? await (0, import_promises27.stat)(file).catch(() => null) : null;
|
|
17303
17649
|
if (info && info.size === expected.bytes && await sha256Of(file) === expected.sha256) {
|
|
17304
17650
|
installedBytes += expected.bytes;
|
|
17305
17651
|
} else {
|
|
@@ -17328,7 +17674,7 @@ async function installEditor(opts = {}) {
|
|
|
17328
17674
|
);
|
|
17329
17675
|
}
|
|
17330
17676
|
if (!baseUrl) throw new EditorInstallError("no download location for the editor build");
|
|
17331
|
-
await (0,
|
|
17677
|
+
await (0, import_promises27.mkdir)(dir, { recursive: true });
|
|
17332
17678
|
const downloaded = [];
|
|
17333
17679
|
const skipped = Object.keys(pin.files).filter((name) => !before.missing.includes(name));
|
|
17334
17680
|
let fetchedBytes = before.installedBytes;
|
|
@@ -17352,17 +17698,17 @@ async function installEditor(opts = {}) {
|
|
|
17352
17698
|
if (bytes.length !== expected.bytes) {
|
|
17353
17699
|
throw new EditorInstallError(`${name}: expected ${expected.bytes} bytes, got ${bytes.length}`, name);
|
|
17354
17700
|
}
|
|
17355
|
-
const digest2 = (0,
|
|
17701
|
+
const digest2 = (0, import_node_crypto8.createHash)("sha256").update(bytes).digest("hex");
|
|
17356
17702
|
if (digest2 !== expected.sha256) {
|
|
17357
17703
|
throw new EditorInstallError(`${name}: sha256 ${digest2.slice(0, 12)}\u2026 does not match the pinned ${expected.sha256.slice(0, 12)}\u2026`, name);
|
|
17358
17704
|
}
|
|
17359
|
-
const final =
|
|
17705
|
+
const final = import_node_path35.default.join(dir, name);
|
|
17360
17706
|
const tmp = `${final}.${process.pid}.part`;
|
|
17361
17707
|
try {
|
|
17362
|
-
await (0,
|
|
17363
|
-
await (0,
|
|
17708
|
+
await (0, import_promises27.writeFile)(tmp, bytes);
|
|
17709
|
+
await (0, import_promises27.rename)(tmp, final);
|
|
17364
17710
|
} finally {
|
|
17365
|
-
await (0,
|
|
17711
|
+
await (0, import_promises27.rm)(tmp, { force: true });
|
|
17366
17712
|
}
|
|
17367
17713
|
fetchedBytes += bytes.length;
|
|
17368
17714
|
downloaded.push(name);
|
|
@@ -17402,12 +17748,12 @@ var EDITOR_CONTENT_TYPES = {
|
|
|
17402
17748
|
".json": "application/json; charset=utf-8"
|
|
17403
17749
|
};
|
|
17404
17750
|
function editorContentType(name) {
|
|
17405
|
-
return EDITOR_CONTENT_TYPES[
|
|
17751
|
+
return EDITOR_CONTENT_TYPES[import_node_path35.default.extname(name).toLowerCase()] ?? null;
|
|
17406
17752
|
}
|
|
17407
17753
|
|
|
17408
17754
|
// src/gallery/editor.ts
|
|
17409
|
-
var
|
|
17410
|
-
var
|
|
17755
|
+
var import_promises28 = require("fs/promises");
|
|
17756
|
+
var import_node_path36 = __toESM(require("path"), 1);
|
|
17411
17757
|
function editorRoute(release, name = "index.html") {
|
|
17412
17758
|
return `/editor/${encodeURIComponent(release)}/${encodeURIComponent(name)}`;
|
|
17413
17759
|
}
|
|
@@ -17480,7 +17826,7 @@ function createGalleryEditorHandlers(opts = {}) {
|
|
|
17480
17826
|
if (!status.installed) return null;
|
|
17481
17827
|
let bytes;
|
|
17482
17828
|
try {
|
|
17483
|
-
bytes = await (0,
|
|
17829
|
+
bytes = await (0, import_promises28.readFile)(import_node_path36.default.join(dir, name));
|
|
17484
17830
|
} catch {
|
|
17485
17831
|
verified = null;
|
|
17486
17832
|
return null;
|
|
@@ -17495,9 +17841,9 @@ function createGalleryEditorHandlers(opts = {}) {
|
|
|
17495
17841
|
}
|
|
17496
17842
|
|
|
17497
17843
|
// src/gallery/server.ts
|
|
17498
|
-
var
|
|
17844
|
+
var import_node_crypto9 = require("crypto");
|
|
17499
17845
|
var import_node_http2 = require("http");
|
|
17500
|
-
var
|
|
17846
|
+
var import_promises29 = require("fs/promises");
|
|
17501
17847
|
var MAX_EDIT_BYTES = 48 * 1024 * 1024;
|
|
17502
17848
|
var EDITOR_CSP = [
|
|
17503
17849
|
"default-src 'self'",
|
|
@@ -17525,7 +17871,7 @@ async function readJsonBody(req) {
|
|
|
17525
17871
|
async function serveGallery(opts) {
|
|
17526
17872
|
const log = opts.onProgress ?? (() => {
|
|
17527
17873
|
});
|
|
17528
|
-
const session = opts.edit || opts.generate || opts.editor || opts.skeleton || opts.price ? (0,
|
|
17874
|
+
const session = opts.edit || opts.generate || opts.editor || opts.skeleton || opts.price ? (0, import_node_crypto9.randomBytes)(16).toString("hex") : null;
|
|
17529
17875
|
let media = /* @__PURE__ */ new Map();
|
|
17530
17876
|
let loading = null;
|
|
17531
17877
|
const reviewAssets = /* @__PURE__ */ new Map();
|
|
@@ -17678,7 +18024,7 @@ async function serveGallery(opts) {
|
|
|
17678
18024
|
const asset = reviewAssets.get(jobId)?.get(url2.pathname);
|
|
17679
18025
|
if (!asset) return fail(404, "no such review asset");
|
|
17680
18026
|
try {
|
|
17681
|
-
const bytes = await (0,
|
|
18027
|
+
const bytes = await (0, import_promises29.readFile)(asset.path);
|
|
17682
18028
|
res.writeHead(200, {
|
|
17683
18029
|
"Content-Type": asset.contentType,
|
|
17684
18030
|
"Cache-Control": "no-store",
|
|
@@ -17729,7 +18075,7 @@ async function serveGallery(opts) {
|
|
|
17729
18075
|
try {
|
|
17730
18076
|
if (url2.pathname === "/") {
|
|
17731
18077
|
const { snapshot } = await load();
|
|
17732
|
-
const nonce = (0,
|
|
18078
|
+
const nonce = (0, import_node_crypto9.randomBytes)(16).toString("base64");
|
|
17733
18079
|
res.writeHead(200, {
|
|
17734
18080
|
"Content-Type": "text/html; charset=utf-8",
|
|
17735
18081
|
"Cache-Control": "no-store",
|
|
@@ -17761,7 +18107,7 @@ async function serveGallery(opts) {
|
|
|
17761
18107
|
if (!asset) return fail(404, "no such gallery media");
|
|
17762
18108
|
let bytes;
|
|
17763
18109
|
try {
|
|
17764
|
-
bytes = await (0,
|
|
18110
|
+
bytes = await (0, import_promises29.readFile)(asset.path);
|
|
17765
18111
|
} catch {
|
|
17766
18112
|
return fail(404, "gallery media is no longer on disk; refresh the page");
|
|
17767
18113
|
}
|
|
@@ -17804,28 +18150,28 @@ async function serveGallery(opts) {
|
|
|
17804
18150
|
}
|
|
17805
18151
|
|
|
17806
18152
|
// src/gallery/generate.ts
|
|
17807
|
-
var
|
|
17808
|
-
var
|
|
17809
|
-
var GenerateRequestSchema =
|
|
17810
|
-
project:
|
|
17811
|
-
keys:
|
|
18153
|
+
var import_node_crypto10 = require("crypto");
|
|
18154
|
+
var import_zod14 = require("zod");
|
|
18155
|
+
var GenerateRequestSchema = import_zod14.z.object({
|
|
18156
|
+
project: import_zod14.z.string().min(1).optional(),
|
|
18157
|
+
keys: import_zod14.z.array(import_zod14.z.string().min(1)).min(1).max(500),
|
|
17812
18158
|
/**
|
|
17813
18159
|
* Regenerate work that is up to date, as `gen --force` does, and let the
|
|
17814
18160
|
* fetch replace a locally modified or untracked file. With `restore`, put
|
|
17815
18161
|
* the recorded bytes back over a modified file.
|
|
17816
18162
|
*/
|
|
17817
|
-
force:
|
|
18163
|
+
force: import_zod14.z.boolean().optional(),
|
|
17818
18164
|
/** Advance in-flight, review, or selected work without submitting anything. */
|
|
17819
|
-
resume:
|
|
18165
|
+
resume: import_zod14.z.boolean().optional(),
|
|
17820
18166
|
/** Re-download downloaded outputs and replace files whose object changed upstream. */
|
|
17821
|
-
refresh:
|
|
18167
|
+
refresh: import_zod14.z.boolean().optional(),
|
|
17822
18168
|
/** Put recorded outputs back on disk from the cache or the provider, without generating. */
|
|
17823
|
-
restore:
|
|
18169
|
+
restore: import_zod14.z.boolean().optional(),
|
|
17824
18170
|
/**
|
|
17825
18171
|
* Bring a replaced generation back: 1-based position in the record's
|
|
17826
18172
|
* history (newest first) or an output hash prefix. One key at a time.
|
|
17827
18173
|
*/
|
|
17828
|
-
revert:
|
|
18174
|
+
revert: import_zod14.z.string().min(1).max(64).optional()
|
|
17829
18175
|
}).strict().refine((request) => [request.resume, request.refresh, request.restore, request.revert !== void 0].filter(Boolean).length <= 1, {
|
|
17830
18176
|
message: "resume, refresh, restore, and revert are exclusive"
|
|
17831
18177
|
}).refine((request) => request.revert === void 0 || request.keys.length === 1, { message: "revert takes one key" });
|
|
@@ -18104,7 +18450,7 @@ function createGenerateHandlers(opts) {
|
|
|
18104
18450
|
}
|
|
18105
18451
|
}
|
|
18106
18452
|
const job = {
|
|
18107
|
-
id: (0,
|
|
18453
|
+
id: (0, import_node_crypto10.randomBytes)(8).toString("hex"),
|
|
18108
18454
|
project: request.project ?? null,
|
|
18109
18455
|
keys: request.keys,
|
|
18110
18456
|
mode: request.refresh ? "refresh" : request.resume ? "resume" : request.restore ? "restore" : request.revert !== void 0 ? "revert" : "generate",
|
|
@@ -18171,19 +18517,19 @@ function createGenerateHandlers(opts) {
|
|
|
18171
18517
|
}
|
|
18172
18518
|
|
|
18173
18519
|
// src/pipeline/salvage.ts
|
|
18174
|
-
var
|
|
18175
|
-
var
|
|
18176
|
-
var
|
|
18520
|
+
var import_promises30 = require("fs/promises");
|
|
18521
|
+
var import_node_fs32 = require("fs");
|
|
18522
|
+
var import_node_path37 = __toESM(require("path"), 1);
|
|
18177
18523
|
function providerClaimId(provider, objectId) {
|
|
18178
18524
|
return `${encodeURIComponent(provider)}:${encodeURIComponent(objectId)}`;
|
|
18179
18525
|
}
|
|
18180
18526
|
async function loadClaims(lockPaths, opts = {}) {
|
|
18181
18527
|
const claimed = /* @__PURE__ */ new Set();
|
|
18182
18528
|
for (const p of lockPaths) {
|
|
18183
|
-
if (!(0,
|
|
18529
|
+
if (!(0, import_node_fs32.existsSync)(p)) throw new Error(`Claim lockfile not found: ${p}`);
|
|
18184
18530
|
let parsed;
|
|
18185
18531
|
try {
|
|
18186
|
-
parsed = parseLock(JSON.parse(await (0,
|
|
18532
|
+
parsed = parseLock(JSON.parse(await (0, import_promises30.readFile)(p, "utf8")));
|
|
18187
18533
|
} catch {
|
|
18188
18534
|
throw new Error(`Claim lockfile is malformed: ${p}`);
|
|
18189
18535
|
}
|
|
@@ -18287,20 +18633,20 @@ function matchOrphanStyle(prompt, manifest) {
|
|
|
18287
18633
|
return matchStyleByPattern(prompt, manifest);
|
|
18288
18634
|
}
|
|
18289
18635
|
async function loadSiblingManifests(ownManifestPath, workspaceManifestPaths, claimPaths) {
|
|
18290
|
-
const own =
|
|
18636
|
+
const own = import_node_path37.default.resolve(ownManifestPath);
|
|
18291
18637
|
const siblingManifestPaths = [
|
|
18292
18638
|
.../* @__PURE__ */ new Set([
|
|
18293
18639
|
...workspaceManifestPaths,
|
|
18294
|
-
...claimPaths.map((c) =>
|
|
18640
|
+
...claimPaths.map((c) => import_node_path37.default.join(import_node_path37.default.dirname(import_node_path37.default.resolve(c)), "pixelkiln.manifest.json"))
|
|
18295
18641
|
])
|
|
18296
18642
|
];
|
|
18297
18643
|
const siblings = [];
|
|
18298
18644
|
for (const siblingManifestPath of siblingManifestPaths) {
|
|
18299
|
-
if (
|
|
18300
|
-
if (!(0,
|
|
18645
|
+
if (import_node_path37.default.resolve(siblingManifestPath) === own) continue;
|
|
18646
|
+
if (!(0, import_node_fs32.existsSync)(siblingManifestPath)) continue;
|
|
18301
18647
|
try {
|
|
18302
18648
|
const { manifest } = await loadManifest(siblingManifestPath);
|
|
18303
|
-
siblings.push({ label:
|
|
18649
|
+
siblings.push({ label: import_node_path37.default.basename(import_node_path37.default.dirname(siblingManifestPath)), manifest });
|
|
18304
18650
|
} catch {
|
|
18305
18651
|
}
|
|
18306
18652
|
}
|
|
@@ -18420,8 +18766,8 @@ async function applyTags(provider, decisions, existing, opts = {}) {
|
|
|
18420
18766
|
}
|
|
18421
18767
|
|
|
18422
18768
|
// src/pick/salvage-server.ts
|
|
18423
|
-
var
|
|
18424
|
-
var
|
|
18769
|
+
var import_promises31 = require("fs/promises");
|
|
18770
|
+
var import_node_path38 = __toESM(require("path"), 1);
|
|
18425
18771
|
|
|
18426
18772
|
// src/pick/salvage-sheet.ts
|
|
18427
18773
|
var escapeHtml3 = (s) => s.replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
@@ -18587,7 +18933,7 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
18587
18933
|
});
|
|
18588
18934
|
const html = renderSalvageSheet(orphans, {
|
|
18589
18935
|
styleId: ctx.styleId,
|
|
18590
|
-
importDir:
|
|
18936
|
+
importDir: import_node_path38.default.relative(process.cwd(), ctx.importDir) || "."
|
|
18591
18937
|
});
|
|
18592
18938
|
const byId = new Map(orphans.map((o) => [o.id, o]));
|
|
18593
18939
|
const existingTags = new Map(orphans.map((o) => [o.id, o.tags]));
|
|
@@ -18643,10 +18989,10 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
18643
18989
|
if (!buf.subarray(0, 8).equals(PNG_SIGNATURE2)) throw new Error("not a PNG");
|
|
18644
18990
|
decodePng(buf);
|
|
18645
18991
|
const assetId = idFromPrompt(orphan.prompt, taken);
|
|
18646
|
-
const rel =
|
|
18647
|
-
const outFile =
|
|
18648
|
-
await (0,
|
|
18649
|
-
await (0,
|
|
18992
|
+
const rel = import_node_path38.default.join("_salvaged", `${assetId}.png`);
|
|
18993
|
+
const outFile = import_node_path38.default.resolve(ctx.importDir, rel);
|
|
18994
|
+
await (0, import_promises31.mkdir)(import_node_path38.default.dirname(outFile), { recursive: true });
|
|
18995
|
+
await (0, import_promises31.writeFile)(outFile, buf);
|
|
18650
18996
|
ctx.manifest.assets[assetId] = {
|
|
18651
18997
|
prompt: orphan.prompt,
|
|
18652
18998
|
promptByStyle: {},
|
|
@@ -18678,7 +19024,7 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
18678
19024
|
sourceUrl: durableSource,
|
|
18679
19025
|
sourceUrls: durableSource ? [{ url: durableSource }] : [],
|
|
18680
19026
|
outputs: [{
|
|
18681
|
-
path: portableOutputPath(outFile,
|
|
19027
|
+
path: portableOutputPath(outFile, import_node_path38.default.dirname(ctx.manifestPath)),
|
|
18682
19028
|
sha256: sha256(buf)
|
|
18683
19029
|
}],
|
|
18684
19030
|
submittedAt: orphan.createdAt,
|
|
@@ -18702,9 +19048,9 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
18702
19048
|
}
|
|
18703
19049
|
}
|
|
18704
19050
|
await applyTags(provider, decisions, existingTags, { onProgress: log, characters: characterMembers });
|
|
18705
|
-
const raw = JSON.parse(await (0,
|
|
19051
|
+
const raw = JSON.parse(await (0, import_promises31.readFile)(ctx.manifestPath, "utf8"));
|
|
18706
19052
|
for (const id of importedAssetIds) raw.assets[id] = ctx.manifest.assets[id];
|
|
18707
|
-
await (0,
|
|
19053
|
+
await (0, import_promises31.writeFile)(ctx.manifestPath, JSON.stringify(raw, null, 2) + "\n");
|
|
18708
19054
|
await saveLock(ctx.lockPath, ctx.lock);
|
|
18709
19055
|
if (characterAssetIds.length) result.characterAssetIds = characterAssetIds;
|
|
18710
19056
|
return result;
|