pixelkiln 0.61.0 → 0.63.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/dist/cli.js +952 -214
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +1034 -368
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +559 -210
- package/dist/index.d.ts +559 -210
- package/dist/index.js +997 -342
- package/dist/index.js.map +1 -1
- package/docs/CHARACTERS.md +39 -0
- package/docs/CLI.md +63 -3
- package/docs/ENDPOINTS.md +32 -6
- package/docs/GENERATORS.md +45 -0
- package/docs/MANIFEST.md +7 -7
- package/docs/PIXELLAB.md +6 -3
- package/docs/REVISIONS.md +128 -19
- package/package.json +1 -1
- package/schema/manifest.schema.json +24 -3
- package/schema/recipe.schema.json +2 -1
- package/skills/pixelkiln/SKILL.md +3 -1
- package/skills/pixelkiln/references/pixellab-roadmap.md +47 -20
- package/skills/pixelkiln/references/pixellab.md +24 -8
- package/skills/pixelkiln/references/revisions.md +26 -5
package/dist/index.cjs
CHANGED
|
@@ -52,6 +52,10 @@ __export(index_exports, {
|
|
|
52
52
|
EXIT_CODES: () => EXIT_CODES,
|
|
53
53
|
EditorInstallError: () => EditorInstallError,
|
|
54
54
|
FAKE_PNG: () => FAKE_PNG,
|
|
55
|
+
FONT_COST_GENERATIONS: () => FONT_COST_GENERATIONS,
|
|
56
|
+
FONT_GLYPH_SIZES: () => FONT_GLYPH_SIZES,
|
|
57
|
+
FONT_WEIGHTS: () => FONT_WEIGHTS,
|
|
58
|
+
FRAME_SET_REVISION_MODES: () => FRAME_SET_REVISION_MODES,
|
|
55
59
|
FakeProvider: () => FakeProvider,
|
|
56
60
|
GenerateRequestError: () => GenerateRequestError,
|
|
57
61
|
GenerateRequestSchema: () => GenerateRequestSchema,
|
|
@@ -68,6 +72,7 @@ __export(index_exports, {
|
|
|
68
72
|
MAX_DOWNLOAD_BYTES: () => MAX_DOWNLOAD_BYTES,
|
|
69
73
|
MAX_HAND_EDIT_BYTES: () => MAX_HAND_EDIT_BYTES,
|
|
70
74
|
MAX_RETRIES: () => MAX_RETRIES,
|
|
75
|
+
MEMBER_SET_REVISION_MODES: () => MEMBER_SET_REVISION_MODES,
|
|
71
76
|
MIRRORED_DIRECTION: () => MIRRORED_DIRECTION,
|
|
72
77
|
ManifestDriftError: () => ManifestDriftError,
|
|
73
78
|
ManifestEditError: () => ManifestEditError,
|
|
@@ -97,6 +102,8 @@ __export(index_exports, {
|
|
|
97
102
|
ScenarioProvider: () => ScenarioProvider,
|
|
98
103
|
StyleInputSchema: () => StyleInputSchema,
|
|
99
104
|
StyleSchema: () => StyleSchema,
|
|
105
|
+
UNZOOM_MAX_AREA: () => UNZOOM_MAX_AREA,
|
|
106
|
+
UNZOOM_MIN_SIDE: () => UNZOOM_MIN_SIDE,
|
|
100
107
|
UiElementSchema: () => UiElementSchema,
|
|
101
108
|
UiPieceSchema: () => UiPieceSchema,
|
|
102
109
|
UnsupportedCapabilityError: () => UnsupportedCapabilityError,
|
|
@@ -131,6 +138,7 @@ __export(index_exports, {
|
|
|
131
138
|
currentEntryOutputPath: () => currentEntryOutputPath,
|
|
132
139
|
currentOutputPath: () => currentOutputPath,
|
|
133
140
|
defaultOpenCommand: () => defaultOpenCommand,
|
|
141
|
+
defaultUnzoomOut: () => defaultUnzoomOut,
|
|
134
142
|
detachHandEdit: () => detachHandEdit,
|
|
135
143
|
detectMediaType: () => detectMediaType,
|
|
136
144
|
doctor: () => doctor,
|
|
@@ -148,10 +156,12 @@ __export(index_exports, {
|
|
|
148
156
|
fetchAssets: () => fetchAssets,
|
|
149
157
|
fetchWithRetry: () => fetchWithRetry,
|
|
150
158
|
findOrphans: () => findOrphans,
|
|
159
|
+
fontOutputPaths: () => fontOutputPaths,
|
|
151
160
|
formatCost: () => formatCost,
|
|
152
161
|
frameSetFps: () => frameSetFps,
|
|
153
162
|
galleryMediaId: () => galleryMediaId,
|
|
154
163
|
galleryMediaRoute: () => galleryMediaRoute,
|
|
164
|
+
generateFont: () => generateFont,
|
|
155
165
|
generationCost: () => generationCost,
|
|
156
166
|
groupOrphansByStyle: () => groupOrphansByStyle,
|
|
157
167
|
handEditBase: () => handEditBase,
|
|
@@ -287,6 +297,7 @@ __export(index_exports, {
|
|
|
287
297
|
tilesCost: () => tilesCost,
|
|
288
298
|
toPortablePath: () => toPortablePath,
|
|
289
299
|
totalSpend: () => totalSpend,
|
|
300
|
+
unzoomFile: () => unzoomFile,
|
|
290
301
|
upsert: () => upsert,
|
|
291
302
|
validateCostEstimate: () => validateCostEstimate,
|
|
292
303
|
validateMedia: () => validateMedia,
|
|
@@ -308,7 +319,7 @@ var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
|
|
|
308
319
|
// src/types.ts
|
|
309
320
|
var import_zod = require("zod");
|
|
310
321
|
var MediaTypeSchema = import_zod.z.enum(["image/png", "image/gif"]);
|
|
311
|
-
var GeneratorSchema = import_zod.z.enum(["1dir", "map", "pixflux", "tiles", "animation", "frames", "character", "terrain", "imagePro", "isometricTile", "objectPro", "uiAsset"]);
|
|
322
|
+
var GeneratorSchema = import_zod.z.enum(["1dir", "map", "pixflux", "tiles", "animation", "frames", "character", "terrain", "imagePro", "isometricTile", "objectPro", "uiAsset", "uiElement"]);
|
|
312
323
|
var GridConfidenceSchema = import_zod.z.enum(["low", "medium", "high"]);
|
|
313
324
|
var RevisionModeSchema = import_zod.z.enum([
|
|
314
325
|
"image-to-image",
|
|
@@ -317,7 +328,9 @@ var RevisionModeSchema = import_zod.z.enum([
|
|
|
317
328
|
"reduce-colors",
|
|
318
329
|
"correct-pixelart",
|
|
319
330
|
"animate",
|
|
320
|
-
"animate-pixminimax"
|
|
331
|
+
"animate-pixminimax",
|
|
332
|
+
"interpolate",
|
|
333
|
+
"edit-animation"
|
|
321
334
|
]);
|
|
322
335
|
function tileVariationCount(descriptions) {
|
|
323
336
|
return Math.max(1, descriptions) * 4;
|
|
@@ -498,6 +511,14 @@ var CharacterAnimationSchema = import_zod.z.object({
|
|
|
498
511
|
template: import_zod.z.string().min(1).optional(),
|
|
499
512
|
/** Direction to animate. */
|
|
500
513
|
direction: CharacterDirectionSchema.default("south"),
|
|
514
|
+
/**
|
|
515
|
+
* Shorthand for one loop in several directions, instead of `direction`.
|
|
516
|
+
* Expanded when the manifest loads into one loop per named direction
|
|
517
|
+
* (`<id>.<direction>`) plus a mirror for each direction whose flip is
|
|
518
|
+
* not named, exactly as if each had been written out. See
|
|
519
|
+
* `src/loop-directions.ts`.
|
|
520
|
+
*/
|
|
521
|
+
directions: import_zod.z.array(CharacterDirectionSchema).min(1).optional(),
|
|
501
522
|
/** Frames to generate without a template (v3). Even, 4 to 16. */
|
|
502
523
|
frames: import_zod.z.number().int().min(4).max(16).optional(),
|
|
503
524
|
/** Playback rate recorded with the frames; PixelLab does not store one. */
|
|
@@ -526,6 +547,13 @@ var CharacterAnimationSchema = import_zod.z.object({
|
|
|
526
547
|
/** v3 text loops only: let PixelLab expand the action into a fuller motion description first. */
|
|
527
548
|
enhancePrompt: import_zod.z.boolean().optional()
|
|
528
549
|
}).strict().superRefine((animation, context) => {
|
|
550
|
+
if (animation.directions !== void 0) {
|
|
551
|
+
context.addIssue({
|
|
552
|
+
code: import_zod.z.ZodIssueCode.custom,
|
|
553
|
+
message: "directions is expanded when the manifest loads; validate the loaded manifest, not the raw file",
|
|
554
|
+
path: ["directions"]
|
|
555
|
+
});
|
|
556
|
+
}
|
|
529
557
|
if (animation.frames !== void 0 && animation.frames % 2 !== 0) {
|
|
530
558
|
context.addIssue({ code: import_zod.z.ZodIssueCode.custom, message: "frames must be even", path: ["frames"] });
|
|
531
559
|
}
|
|
@@ -586,9 +614,13 @@ var RevisionSchema = import_zod.z.object({
|
|
|
586
614
|
* the provider layer, since it differs by mode).
|
|
587
615
|
*/
|
|
588
616
|
frames: import_zod.z.number().int().min(4).max(40).optional(),
|
|
589
|
-
/** `animate`/`animate-pixminimax` only: playback rate recorded with the frames; PixelLab does not store one. */
|
|
617
|
+
/** `animate`/`animate-pixminimax`/`interpolate`/`edit-animation` only: playback rate recorded with the frames; PixelLab does not store one. */
|
|
590
618
|
fps: import_zod.z.number().int().min(1).max(60).optional(),
|
|
591
|
-
/**
|
|
619
|
+
/**
|
|
620
|
+
* `animate`/`animate-pixminimax`: manifest-relative image pinning where
|
|
621
|
+
* the motion ends (interpolation instead of open-ended animation).
|
|
622
|
+
* `interpolate`: required, the ending keyframe; the parent is the start.
|
|
623
|
+
*/
|
|
592
624
|
lastFrame: import_zod.z.string().min(1).optional(),
|
|
593
625
|
/** `animate-pixminimax` only: facing direction, used only alongside `enhancePrompt` to hold the sprite's facing. */
|
|
594
626
|
direction: CharacterDirectionSchema.optional(),
|
|
@@ -632,14 +664,15 @@ var RevisionSchema = import_zod.z.object({
|
|
|
632
664
|
path: ["strength"]
|
|
633
665
|
});
|
|
634
666
|
}
|
|
635
|
-
|
|
667
|
+
const generative = revision.mode === "animate" || revision.mode === "animate-pixminimax" || revision.mode === "interpolate" || revision.mode === "edit-animation";
|
|
668
|
+
if (revision.strength !== void 0 && generative) {
|
|
636
669
|
context.addIssue({
|
|
637
670
|
code: import_zod.z.ZodIssueCode.custom,
|
|
638
|
-
message: `${revision.mode} revisions do not take a strength; use enhancePrompt`,
|
|
671
|
+
message: revision.mode === "animate" || revision.mode === "animate-pixminimax" ? `${revision.mode} revisions do not take a strength; use enhancePrompt` : `${revision.mode} revisions do not take a strength`,
|
|
639
672
|
path: ["strength"]
|
|
640
673
|
});
|
|
641
674
|
}
|
|
642
|
-
for (const field of ["frames", "
|
|
675
|
+
for (const field of ["frames", "enhancePrompt"]) {
|
|
643
676
|
if (revision[field] !== void 0 && revision.mode !== "animate" && revision.mode !== "animate-pixminimax") {
|
|
644
677
|
context.addIssue({
|
|
645
678
|
code: import_zod.z.ZodIssueCode.custom,
|
|
@@ -648,6 +681,27 @@ var RevisionSchema = import_zod.z.object({
|
|
|
648
681
|
});
|
|
649
682
|
}
|
|
650
683
|
}
|
|
684
|
+
if (revision.lastFrame !== void 0 && revision.mode !== "animate" && revision.mode !== "animate-pixminimax" && revision.mode !== "interpolate") {
|
|
685
|
+
context.addIssue({
|
|
686
|
+
code: import_zod.z.ZodIssueCode.custom,
|
|
687
|
+
message: "lastFrame applies to animate/animate-pixminimax/interpolate revisions only",
|
|
688
|
+
path: ["lastFrame"]
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
if (revision.mode === "interpolate" && revision.lastFrame === void 0) {
|
|
692
|
+
context.addIssue({
|
|
693
|
+
code: import_zod.z.ZodIssueCode.custom,
|
|
694
|
+
message: "interpolate revisions require lastFrame: the ending keyframe to interpolate toward",
|
|
695
|
+
path: ["lastFrame"]
|
|
696
|
+
});
|
|
697
|
+
}
|
|
698
|
+
if (revision.fps !== void 0 && !generative) {
|
|
699
|
+
context.addIssue({
|
|
700
|
+
code: import_zod.z.ZodIssueCode.custom,
|
|
701
|
+
message: "fps applies to animate/animate-pixminimax/interpolate/edit-animation revisions only",
|
|
702
|
+
path: ["fps"]
|
|
703
|
+
});
|
|
704
|
+
}
|
|
651
705
|
if (revision.direction !== void 0 && revision.mode !== "animate-pixminimax") {
|
|
652
706
|
context.addIssue({
|
|
653
707
|
code: import_zod.z.ZodIssueCode.custom,
|
|
@@ -659,6 +713,8 @@ var RevisionSchema = import_zod.z.object({
|
|
|
659
713
|
context.addIssue({ code: import_zod.z.ZodIssueCode.custom, message: "frames must be even", path: ["frames"] });
|
|
660
714
|
}
|
|
661
715
|
});
|
|
716
|
+
var FRAME_SET_REVISION_MODES = ["animate", "animate-pixminimax", "interpolate", "edit-animation"];
|
|
717
|
+
var MEMBER_SET_REVISION_MODES = ["reduce-colors", "correct-pixelart", "edit-animation"];
|
|
662
718
|
var StyleObjectSchema = import_zod.z.object({
|
|
663
719
|
/** Generation backend for this style. Omit to inherit the manifest default. */
|
|
664
720
|
provider: import_zod.z.string().min(1).optional(),
|
|
@@ -777,7 +833,7 @@ var StyleObjectSchema = import_zod.z.object({
|
|
|
777
833
|
*/
|
|
778
834
|
terrainMode: import_zod.z.enum(["standard", "pro"]).optional(),
|
|
779
835
|
/**
|
|
780
|
-
* `uiAsset`
|
|
836
|
+
* `uiAsset` and `uiElement` generators only. A natural-language palette hint sent as-is
|
|
781
837
|
* (e.g. "brown and gold"), distinct from `palette`'s hex-color array —
|
|
782
838
|
* this is a prompt-level steer, not a local quantization target.
|
|
783
839
|
*/
|
|
@@ -1655,6 +1711,21 @@ var CorrectPixelartResponseSchema = import_zod2.z.object({
|
|
|
1655
1711
|
images: import_zod2.z.array(import_zod2.z.object({ base64: import_zod2.z.string().min(1) }).passthrough()).min(1),
|
|
1656
1712
|
usage: import_zod2.z.unknown().optional()
|
|
1657
1713
|
}).passthrough();
|
|
1714
|
+
var UnzoomResponseSchema = import_zod2.z.object({
|
|
1715
|
+
image: import_zod2.z.object({ base64: import_zod2.z.string().min(1) }).passthrough(),
|
|
1716
|
+
original_size: import_zod2.z.object({ width: import_zod2.z.number().int(), height: import_zod2.z.number().int() }).passthrough(),
|
|
1717
|
+
unzoomed_size: import_zod2.z.object({ width: import_zod2.z.number().int(), height: import_zod2.z.number().int() }).passthrough(),
|
|
1718
|
+
zoom_factor_detected: import_zod2.z.number(),
|
|
1719
|
+
usage: import_zod2.z.unknown().optional()
|
|
1720
|
+
}).passthrough();
|
|
1721
|
+
var FontJobSchema = import_zod2.z.object({
|
|
1722
|
+
job_id: import_zod2.z.string().min(1),
|
|
1723
|
+
status: import_zod2.z.string(),
|
|
1724
|
+
glyph_px: import_zod2.z.number().int().nullable().optional(),
|
|
1725
|
+
download_atlas_url: import_zod2.z.string().nullable().optional(),
|
|
1726
|
+
download_ttf_url: import_zod2.z.string().nullable().optional(),
|
|
1727
|
+
usage: import_zod2.z.unknown().optional()
|
|
1728
|
+
}).passthrough();
|
|
1658
1729
|
var SelectFramesSchema = import_zod2.z.object({ created_object_ids: import_zod2.z.array(import_zod2.z.string()) }).passthrough();
|
|
1659
1730
|
var CharacterSubmitSchema = import_zod2.z.object({
|
|
1660
1731
|
background_job_id: import_zod2.z.string().min(1),
|
|
@@ -1739,6 +1810,17 @@ function validateResponse(schema, raw, operation) {
|
|
|
1739
1810
|
const issues = parsed.error.issues.slice(0, 4).map((i) => `${i.path.join(".") || "response"}: ${i.message}`).join("; ");
|
|
1740
1811
|
throw new Error(`Invalid PixelLab response for ${operation}: ${issues}`);
|
|
1741
1812
|
}
|
|
1813
|
+
function cleanupImages(args, operation) {
|
|
1814
|
+
const images = args.images ?? (args.image ? [args.image] : []);
|
|
1815
|
+
if (!images.length) throw new Error(`${operation} needs at least one image`);
|
|
1816
|
+
return images;
|
|
1817
|
+
}
|
|
1818
|
+
function cleanupResults(images, sent, operation) {
|
|
1819
|
+
if (images.length !== sent) {
|
|
1820
|
+
throw new Error(`Invalid PixelLab response for ${operation}: sent ${sent} image(s), got ${images.length} back`);
|
|
1821
|
+
}
|
|
1822
|
+
return images.map((image) => Buffer.from(image.base64, "base64"));
|
|
1823
|
+
}
|
|
1742
1824
|
var PixelLabError = class extends ProviderError {
|
|
1743
1825
|
constructor(message6, status, body) {
|
|
1744
1826
|
super("pixellab", message6, { status });
|
|
@@ -1767,9 +1849,9 @@ var PixelLabClient = class {
|
|
|
1767
1849
|
apiKey;
|
|
1768
1850
|
http;
|
|
1769
1851
|
/** Retries transport failures, 429, and 5xx; see http.ts for the policy. */
|
|
1770
|
-
async request(
|
|
1852
|
+
async request(path34, init) {
|
|
1771
1853
|
const auth = this.apiKey.startsWith("Bearer ") ? this.apiKey : `Bearer ${this.apiKey}`;
|
|
1772
|
-
const res = await this.http(`${BASE}${
|
|
1854
|
+
const res = await this.http(`${BASE}${path34}`, {
|
|
1773
1855
|
...init,
|
|
1774
1856
|
headers: {
|
|
1775
1857
|
Authorization: auth,
|
|
@@ -1779,13 +1861,13 @@ var PixelLabClient = class {
|
|
|
1779
1861
|
});
|
|
1780
1862
|
const text = await res.text();
|
|
1781
1863
|
if (!res.ok) {
|
|
1782
|
-
throw new PixelLabError(`${init?.method ?? "GET"} ${
|
|
1864
|
+
throw new PixelLabError(`${init?.method ?? "GET"} ${path34} \u2192 ${res.status}`, res.status, text);
|
|
1783
1865
|
}
|
|
1784
1866
|
if (!text) return {};
|
|
1785
1867
|
try {
|
|
1786
1868
|
return JSON.parse(text);
|
|
1787
1869
|
} catch {
|
|
1788
|
-
throw new Error(`${init?.method ?? "GET"} ${
|
|
1870
|
+
throw new Error(`${init?.method ?? "GET"} ${path34} returned invalid JSON`);
|
|
1789
1871
|
}
|
|
1790
1872
|
}
|
|
1791
1873
|
async balance() {
|
|
@@ -2171,10 +2253,10 @@ var PixelLabClient = class {
|
|
|
2171
2253
|
const palette = args.paletteSwatchBase64 ? { color_image: { type: "base64", base64: args.paletteSwatchBase64, format: "png" }, force_colors: true } : {};
|
|
2172
2254
|
const encode = (image) => ({ type: "base64", base64: image.base64, format: image.format });
|
|
2173
2255
|
const south = args.reference?.south;
|
|
2174
|
-
let
|
|
2256
|
+
let path34;
|
|
2175
2257
|
let body;
|
|
2176
2258
|
if (args.mode === "v3") {
|
|
2177
|
-
|
|
2259
|
+
path34 = "/create-character-v3";
|
|
2178
2260
|
body = {
|
|
2179
2261
|
description: args.description,
|
|
2180
2262
|
image_size: imageSize,
|
|
@@ -2188,7 +2270,7 @@ var PixelLabClient = class {
|
|
|
2188
2270
|
...args.enhancePrompt ? { enhance_prompt: true } : {}
|
|
2189
2271
|
};
|
|
2190
2272
|
} else if (args.mode === "pro-flash") {
|
|
2191
|
-
|
|
2273
|
+
path34 = "/create-character-pro-flash";
|
|
2192
2274
|
const traits = args.styleTraits;
|
|
2193
2275
|
body = {
|
|
2194
2276
|
description: args.description,
|
|
@@ -2211,7 +2293,7 @@ var PixelLabClient = class {
|
|
|
2211
2293
|
} : {}
|
|
2212
2294
|
};
|
|
2213
2295
|
} else if (args.mode === "pro") {
|
|
2214
|
-
|
|
2296
|
+
path34 = "/create-character-pro";
|
|
2215
2297
|
body = {
|
|
2216
2298
|
description: args.description,
|
|
2217
2299
|
image_size: imageSize,
|
|
@@ -2225,7 +2307,7 @@ var PixelLabClient = class {
|
|
|
2225
2307
|
...!south && args.styleCharacterId ? { style_character_id: args.styleCharacterId } : {}
|
|
2226
2308
|
};
|
|
2227
2309
|
} else {
|
|
2228
|
-
|
|
2310
|
+
path34 = args.directions === 4 ? "/create-character-with-4-directions" : "/create-character-with-8-directions";
|
|
2229
2311
|
body = {
|
|
2230
2312
|
description: args.description,
|
|
2231
2313
|
image_size: imageSize,
|
|
@@ -2242,8 +2324,8 @@ var PixelLabClient = class {
|
|
|
2242
2324
|
...palette
|
|
2243
2325
|
};
|
|
2244
2326
|
}
|
|
2245
|
-
const raw = await this.request(
|
|
2246
|
-
return validateResponse(CharacterSubmitSchema, raw,
|
|
2327
|
+
const raw = await this.request(path34, { method: "POST", body: JSON.stringify(body) });
|
|
2328
|
+
return validateResponse(CharacterSubmitSchema, raw, path34.slice(1));
|
|
2247
2329
|
}
|
|
2248
2330
|
/** A text edit of an existing character, applied to every direction and stored as a sibling. */
|
|
2249
2331
|
async createCharacterState(args) {
|
|
@@ -2405,20 +2487,52 @@ var PixelLabClient = class {
|
|
|
2405
2487
|
);
|
|
2406
2488
|
}
|
|
2407
2489
|
/**
|
|
2408
|
-
* `/
|
|
2409
|
-
*
|
|
2410
|
-
*
|
|
2411
|
-
*
|
|
2412
|
-
*
|
|
2413
|
-
*
|
|
2414
|
-
*
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2490
|
+
* `/generate-ui-v2` ("Generate UI (Pro)"): one UI element (a button, a
|
|
2491
|
+
* health bar, an inventory slot, a dialogue box) from a description,
|
|
2492
|
+
* guided by an optional concept image and a natural-language color
|
|
2493
|
+
* palette. A plain background job; unlike `/create-ui-asset` there is no
|
|
2494
|
+
* piece/element layout, and sizes start at 16px instead of 192. Its
|
|
2495
|
+
* completed shape has not been exercised live; `pollUiElement` reads it
|
|
2496
|
+
* defensively.
|
|
2497
|
+
*/
|
|
2498
|
+
async generateUiV2(args) {
|
|
2499
|
+
const body = {
|
|
2500
|
+
description: args.description,
|
|
2501
|
+
image_size: { width: args.width, height: args.height }
|
|
2502
|
+
};
|
|
2503
|
+
if (args.conceptImage) {
|
|
2504
|
+
body.concept_image = {
|
|
2505
|
+
image: args.conceptImage.image,
|
|
2506
|
+
size: { width: args.conceptImage.width, height: args.conceptImage.height }
|
|
2507
|
+
};
|
|
2508
|
+
}
|
|
2509
|
+
if (args.colorPalette) body.color_palette = args.colorPalette;
|
|
2510
|
+
if (args.noBackground != null) body.no_background = args.noBackground;
|
|
2511
|
+
if (args.seed != null) body.seed = args.seed;
|
|
2512
|
+
return validateResponse(
|
|
2513
|
+
RevisionJobSubmitSchema,
|
|
2514
|
+
await this.request("/generate-ui-v2", { method: "POST", body: JSON.stringify(body) }),
|
|
2515
|
+
"generate-ui-v2"
|
|
2516
|
+
);
|
|
2517
|
+
}
|
|
2518
|
+
/**
|
|
2519
|
+
* `/reduce-colors`, PixelLab's "Cleanup" tier: quantize one image, or
|
|
2520
|
+
* several same-size frames together onto ONE shared palette, synchronously
|
|
2521
|
+
* — no `background_job_id`, the result comes back in this same response,
|
|
2522
|
+
* like `createImagePixflux`. Passing every frame of an animation (or every
|
|
2523
|
+
* direction of a character) in one call is the endpoint's whole point:
|
|
2524
|
+
* frames quantized separately drift onto different palettes. The schema's
|
|
2525
|
+
* own response example is dollar-denominated (`usage: {type: "usd", usd:
|
|
2526
|
+
* 0.02}`), which — matching this codebase's repeated experience with
|
|
2527
|
+
* PixelLab's documented-vs-billed cost mismatches (`isometricTile`,
|
|
2528
|
+
* `objectPro`) — did not hold: confirmed live against a Tier 2 account at
|
|
2529
|
+
* exactly 0.1 generations for a 32x32 source (docs/REVISIONS.md).
|
|
2530
|
+
* `numColors` and `paletteImage` are mutually exclusive upstream; the
|
|
2531
|
+
* manifest schema already enforces that before this is ever called.
|
|
2419
2532
|
*/
|
|
2420
2533
|
async reduceColors(args) {
|
|
2421
|
-
const
|
|
2534
|
+
const images = cleanupImages(args, "reduce-colors");
|
|
2535
|
+
const body = { images };
|
|
2422
2536
|
if (args.numColors != null) body.num_colors = args.numColors;
|
|
2423
2537
|
if (args.paletteImage) body.palette_image = args.paletteImage;
|
|
2424
2538
|
if (args.dithering) body.dithering = args.dithering;
|
|
@@ -2428,8 +2542,10 @@ var PixelLabClient = class {
|
|
|
2428
2542
|
await this.request("/reduce-colors", { method: "POST", body: JSON.stringify(body) }),
|
|
2429
2543
|
"reduce-colors"
|
|
2430
2544
|
);
|
|
2545
|
+
const pngs = cleanupResults(res.images, images.length, "reduce-colors");
|
|
2431
2546
|
return {
|
|
2432
|
-
png:
|
|
2547
|
+
png: pngs[0],
|
|
2548
|
+
pngs,
|
|
2433
2549
|
paletteStripPng: Buffer.from(res.palette.base64, "base64"),
|
|
2434
2550
|
nColors: res.n_colors,
|
|
2435
2551
|
usage: res.usage
|
|
@@ -2438,19 +2554,117 @@ var PixelLabClient = class {
|
|
|
2438
2554
|
/**
|
|
2439
2555
|
* `/correct-pixelart`, PixelLab's "Cleanup" tier: sharpen edges and drop
|
|
2440
2556
|
* stray pixels without resizing, synchronously — same shape as
|
|
2441
|
-
* `reduceColors` above, no background job
|
|
2442
|
-
*
|
|
2557
|
+
* `reduceColors` above, no background job, and likewise corrects several
|
|
2558
|
+
* same-size frames together when given `images`. Cost is confirmed live
|
|
2559
|
+
* at 0.1 generations for a 32x32 source, not the schema's own
|
|
2443
2560
|
* dollar-denominated example (`usage: {type: "usd", usd: 0.02}`).
|
|
2444
2561
|
*/
|
|
2445
2562
|
async correctPixelart(args) {
|
|
2446
|
-
const
|
|
2563
|
+
const images = cleanupImages(args, "correct-pixelart");
|
|
2564
|
+
const body = { images };
|
|
2447
2565
|
if (args.strength != null) body.strength = args.strength;
|
|
2448
2566
|
const res = validateResponse(
|
|
2449
2567
|
CorrectPixelartResponseSchema,
|
|
2450
2568
|
await this.request("/correct-pixelart", { method: "POST", body: JSON.stringify(body) }),
|
|
2451
2569
|
"correct-pixelart"
|
|
2452
2570
|
);
|
|
2453
|
-
|
|
2571
|
+
const pngs = cleanupResults(res.images, images.length, "correct-pixelart");
|
|
2572
|
+
return { png: pngs[0], pngs, usage: res.usage };
|
|
2573
|
+
}
|
|
2574
|
+
/**
|
|
2575
|
+
* `/unzoom`: recover native-resolution pixel art from an upscaled image (a
|
|
2576
|
+
* 32x32 sprite saved at 512x512). Synchronous like the Cleanup tier.
|
|
2577
|
+
* PixelLab's own API overview names this "the most common cause of
|
|
2578
|
+
* disappointing output": run it on outside artwork before it becomes a
|
|
2579
|
+
* style or reference image. The input must be at least 256x256 and at
|
|
2580
|
+
* most 2048x2048 in area, and the result is opaque — transparency is
|
|
2581
|
+
* composited onto white before the grid is detected. `quantize` is 0 to
|
|
2582
|
+
* auto-detect a palette, -1 to keep every color, or 2-256 for exactly
|
|
2583
|
+
* that many.
|
|
2584
|
+
*/
|
|
2585
|
+
async unzoom(args) {
|
|
2586
|
+
const body = { image: args.image };
|
|
2587
|
+
if (args.quantize != null) body.quantize = args.quantize;
|
|
2588
|
+
const res = validateResponse(
|
|
2589
|
+
UnzoomResponseSchema,
|
|
2590
|
+
await this.request("/unzoom", { method: "POST", body: JSON.stringify(body) }),
|
|
2591
|
+
"unzoom"
|
|
2592
|
+
);
|
|
2593
|
+
return {
|
|
2594
|
+
png: Buffer.from(res.image.base64, "base64"),
|
|
2595
|
+
originalSize: { width: res.original_size.width, height: res.original_size.height },
|
|
2596
|
+
unzoomedSize: { width: res.unzoomed_size.width, height: res.unzoomed_size.height },
|
|
2597
|
+
zoomFactor: res.zoom_factor_detected,
|
|
2598
|
+
usage: res.usage
|
|
2599
|
+
};
|
|
2600
|
+
}
|
|
2601
|
+
/**
|
|
2602
|
+
* `/interpolation-v2` ("Interpolate (Pro)"): generate the in-between
|
|
2603
|
+
* frames from one keyframe to another, guided by a short `action`. Unlike
|
|
2604
|
+
* `last_frame` on `/animate-with-text-v3`, both ends are required and the
|
|
2605
|
+
* frame count is the model's own choice (its docs say "typically 4-8").
|
|
2606
|
+
* Output frames are 16 to 128 pixels per side. A plain background job;
|
|
2607
|
+
* its completed shape is read defensively by `pollAnimateRevision`.
|
|
2608
|
+
*/
|
|
2609
|
+
async interpolationV2(args) {
|
|
2610
|
+
const size = { width: args.width, height: args.height };
|
|
2611
|
+
const body = {
|
|
2612
|
+
start_image: { image: args.startImage, size },
|
|
2613
|
+
end_image: { image: args.endImage, size },
|
|
2614
|
+
action: args.action,
|
|
2615
|
+
image_size: size
|
|
2616
|
+
};
|
|
2617
|
+
if (args.seed != null) body.seed = args.seed;
|
|
2618
|
+
if (args.noBackground != null) body.no_background = args.noBackground;
|
|
2619
|
+
return validateResponse(
|
|
2620
|
+
RevisionJobSubmitSchema,
|
|
2621
|
+
await this.request("/interpolation-v2", { method: "POST", body: JSON.stringify(body) }),
|
|
2622
|
+
"interpolation-v2"
|
|
2623
|
+
);
|
|
2624
|
+
}
|
|
2625
|
+
/**
|
|
2626
|
+
* `/edit-animation-v2` ("Edit animation (Pro)"): apply one text edit
|
|
2627
|
+
* across every frame of an existing animation at once, so the change
|
|
2628
|
+
* stays consistent frame to frame. 2 to 16 frames, but the real ceiling
|
|
2629
|
+
* shrinks with frame size because every frame is packed into one grid:
|
|
2630
|
+
* 16 at up to 64px, 9 at 65-80px, 4 at 81-256px.
|
|
2631
|
+
*/
|
|
2632
|
+
async editAnimationV2(args) {
|
|
2633
|
+
const size = { width: args.width, height: args.height };
|
|
2634
|
+
const body = {
|
|
2635
|
+
description: args.description,
|
|
2636
|
+
frames: args.frames.map((image) => ({ image, size })),
|
|
2637
|
+
image_size: size
|
|
2638
|
+
};
|
|
2639
|
+
if (args.seed != null) body.seed = args.seed;
|
|
2640
|
+
if (args.noBackground != null) body.no_background = args.noBackground;
|
|
2641
|
+
return validateResponse(
|
|
2642
|
+
RevisionJobSubmitSchema,
|
|
2643
|
+
await this.request("/edit-animation-v2", { method: "POST", body: JSON.stringify(body) }),
|
|
2644
|
+
"edit-animation-v2"
|
|
2645
|
+
);
|
|
2646
|
+
}
|
|
2647
|
+
/**
|
|
2648
|
+
* `/generate-font-pro`: an 80-glyph pixel font (A-Z, a-z, 0-9, common
|
|
2649
|
+
* game-UI punctuation) from a style description, delivered as a glyph
|
|
2650
|
+
* atlas PNG plus a ready-to-use `.ttf`. Documented at 25 subscription
|
|
2651
|
+
* generations. Poll with `getFontJob`.
|
|
2652
|
+
*/
|
|
2653
|
+
async generateFontPro(args) {
|
|
2654
|
+
const body = { description: args.description, weight: args.weight };
|
|
2655
|
+
if (args.glyphPx != null) body.glyph_px = args.glyphPx;
|
|
2656
|
+
if (args.seed != null) body.seed = args.seed;
|
|
2657
|
+
if (args.fontName) body.font_name = args.fontName;
|
|
2658
|
+
return validateResponse(
|
|
2659
|
+
RevisionJobSubmitSchema,
|
|
2660
|
+
await this.request("/generate-font-pro", { method: "POST", body: JSON.stringify(body) }),
|
|
2661
|
+
"generate-font-pro"
|
|
2662
|
+
);
|
|
2663
|
+
}
|
|
2664
|
+
/** `GET /generate-font-pro/{job_id}`: status, then no-auth atlas and `.ttf` URLs once completed. */
|
|
2665
|
+
async getFontJob(jobId) {
|
|
2666
|
+
const raw = await this.request(`/generate-font-pro/${encodeURIComponent(jobId)}`);
|
|
2667
|
+
return validateResponse(FontJobSchema, raw, "generate-font-pro/{job_id}");
|
|
2454
2668
|
}
|
|
2455
2669
|
/**
|
|
2456
2670
|
* `/animate-with-text-v3`: animate a loose image from a text description,
|
|
@@ -3111,10 +3325,10 @@ var import_node_fs = require("fs");
|
|
|
3111
3325
|
function sha256(data) {
|
|
3112
3326
|
return (0, import_node_crypto.createHash)("sha256").update(data).digest("hex");
|
|
3113
3327
|
}
|
|
3114
|
-
async function sha256File(
|
|
3328
|
+
async function sha256File(path34) {
|
|
3115
3329
|
return new Promise((resolve, reject) => {
|
|
3116
3330
|
const hash = (0, import_node_crypto.createHash)("sha256");
|
|
3117
|
-
const stream = (0, import_node_fs.createReadStream)(
|
|
3331
|
+
const stream = (0, import_node_fs.createReadStream)(path34);
|
|
3118
3332
|
stream.on("data", (chunk2) => hash.update(chunk2));
|
|
3119
3333
|
stream.on("error", reject);
|
|
3120
3334
|
stream.on("end", () => resolve(hash.digest("hex")));
|
|
@@ -3142,7 +3356,7 @@ function specHash(spec, styleImageHashes, providerOptionIdentity = spec.provider
|
|
|
3142
3356
|
// tile fields are undefined for every other generator. `tileSize` is
|
|
3143
3357
|
// intentionally absent; width/height are derived from it, so it is
|
|
3144
3358
|
// already covered.
|
|
3145
|
-
noBackground: spec.generator === "pixflux" || spec.generator === "uiAsset" || spec.provider !== "pixellab" ? spec.noBackground : void 0,
|
|
3359
|
+
noBackground: spec.generator === "pixflux" || spec.generator === "uiAsset" || spec.generator === "uiElement" || spec.provider !== "pixellab" ? spec.noBackground : void 0,
|
|
3146
3360
|
tileType: spec.tileType,
|
|
3147
3361
|
tileView: spec.tileView,
|
|
3148
3362
|
tileFeature: spec.tileFeature,
|
|
@@ -3349,7 +3563,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
3349
3563
|
return new _PixelLabProvider(new PixelLabClient("download-only"));
|
|
3350
3564
|
}
|
|
3351
3565
|
supports(generator) {
|
|
3352
|
-
return generator === "1dir" || generator === "map" || generator === "pixflux" || generator === "tiles" || generator === "terrain" || generator === "imagePro" || generator === "character" || generator === "isometricTile" || generator === "objectPro" || generator === "uiAsset";
|
|
3566
|
+
return generator === "1dir" || generator === "map" || generator === "pixflux" || generator === "tiles" || generator === "terrain" || generator === "imagePro" || generator === "character" || generator === "isometricTile" || generator === "objectPro" || generator === "uiAsset" || generator === "uiElement";
|
|
3353
3567
|
}
|
|
3354
3568
|
/**
|
|
3355
3569
|
* `inpaint` (`/inpaint-v3`, a mask) and `image-to-image` (`/edit-images-v2`,
|
|
@@ -3359,12 +3573,15 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
3359
3573
|
* rather than a described edit — and `animate`/`animate-pixminimax`
|
|
3360
3574
|
* (`/animate-with-text-v3`, `/animate-pixminimax`), which animate any loose
|
|
3361
3575
|
* image from a text description with no character/object resource
|
|
3362
|
-
* required
|
|
3363
|
-
*
|
|
3364
|
-
*
|
|
3576
|
+
* required — and `interpolate` (`/interpolation-v2`, the in-betweens
|
|
3577
|
+
* from one keyframe to another) and `edit-animation`
|
|
3578
|
+
* (`/edit-animation-v2`, one text edit applied across a whole frame set).
|
|
3579
|
+
* `outpaint` does not: there is no canvas-expansion endpoint in the API,
|
|
3580
|
+
* matching docs/REVISIONS.md's note that no provider ships a tested
|
|
3581
|
+
* outpaint path yet.
|
|
3365
3582
|
*/
|
|
3366
3583
|
supportsRevision(mode2) {
|
|
3367
|
-
return mode2 === "inpaint" || mode2 === "image-to-image" || mode2 === "reduce-colors" || mode2 === "correct-pixelart" || mode2 === "animate" || mode2 === "animate-pixminimax";
|
|
3584
|
+
return mode2 === "inpaint" || mode2 === "image-to-image" || mode2 === "reduce-colors" || mode2 === "correct-pixelart" || mode2 === "animate" || mode2 === "animate-pixminimax" || mode2 === "interpolate" || mode2 === "edit-animation";
|
|
3368
3585
|
}
|
|
3369
3586
|
/** PixelLab's own constraints: submissions must be >2s apart, and
|
|
3370
3587
|
* background jobs in flight are capped by subscription tier (Tier 1=8,
|
|
@@ -3392,6 +3609,12 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
3392
3609
|
if (spec.revision?.mode === "reduce-colors" || spec.revision?.mode === "correct-pixelart") {
|
|
3393
3610
|
return { unit: "generations", amount: 0.1, candidates: 1 };
|
|
3394
3611
|
}
|
|
3612
|
+
if (spec.revision?.mode === "interpolate" || spec.revision?.mode === "edit-animation") {
|
|
3613
|
+
const width = spec.revision.sourceWidth ?? spec.width;
|
|
3614
|
+
const height = spec.revision.sourceHeight ?? spec.height;
|
|
3615
|
+
const side = spec.revision.mode === "edit-animation" ? Math.ceil(Math.sqrt(Math.max(1, spec.revision.sourceMembers?.length ?? 1))) : 1;
|
|
3616
|
+
return { unit: "generations", amount: generationCost(width * side, height * side, "1dir"), candidates: 1 };
|
|
3617
|
+
}
|
|
3395
3618
|
if (spec.revision?.mode === "animate" || spec.revision?.mode === "animate-pixminimax") {
|
|
3396
3619
|
const width = spec.revision.sourceWidth ?? spec.width;
|
|
3397
3620
|
const height = spec.revision.sourceHeight ?? spec.height;
|
|
@@ -3420,6 +3643,12 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
3420
3643
|
};
|
|
3421
3644
|
}
|
|
3422
3645
|
validate(spec, styleImages) {
|
|
3646
|
+
const members = spec.revision?.sourceMembers;
|
|
3647
|
+
if (spec.revision && members && !MEMBER_SET_REVISION_MODES.includes(spec.revision.mode)) {
|
|
3648
|
+
throw new Error(
|
|
3649
|
+
`PixelLab ${spec.revision.mode} reads one source image; ${spec.revision.sourceAssetId} is a ${members.length}-member set (${members[0].role}, ...). Revise one member through a declared source, or use reduce-colors, correct-pixelart, or edit-animation, which take the whole set`
|
|
3650
|
+
);
|
|
3651
|
+
}
|
|
3423
3652
|
if (spec.revision?.mode === "inpaint") {
|
|
3424
3653
|
const { sourceWidth: width, sourceHeight: height } = spec.revision;
|
|
3425
3654
|
if (width != null && height != null && (width < 32 || height < 32 || width > 512 || height > 512)) {
|
|
@@ -3428,9 +3657,10 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
3428
3657
|
}
|
|
3429
3658
|
if (spec.revision?.mode === "reduce-colors") {
|
|
3430
3659
|
const { sourceWidth: width, sourceHeight: height } = spec.revision;
|
|
3431
|
-
|
|
3660
|
+
const count = members?.length ?? 1;
|
|
3661
|
+
if (width != null && height != null && width * height * count > 512 * 512) {
|
|
3432
3662
|
throw new Error(
|
|
3433
|
-
`PixelLab reduce-colors source is ${width}x${height} (${width * height}px\xB2); the API takes at most 512x512 worth of pixels per call`
|
|
3663
|
+
`PixelLab reduce-colors source is ${count > 1 ? `${count} frames of ` : ""}${width}x${height} (${width * height * count}px\xB2 in all); the API takes at most 512x512 worth of pixels per call`
|
|
3434
3664
|
);
|
|
3435
3665
|
}
|
|
3436
3666
|
}
|
|
@@ -3454,6 +3684,36 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
3454
3684
|
);
|
|
3455
3685
|
}
|
|
3456
3686
|
}
|
|
3687
|
+
if (spec.revision?.mode === "interpolate") {
|
|
3688
|
+
const { sourceWidth: width, sourceHeight: height, lastFrameWidth, lastFrameHeight } = spec.revision;
|
|
3689
|
+
if (width != null && height != null && (width < 16 || height < 16 || width > 128 || height > 128)) {
|
|
3690
|
+
throw new Error(`PixelLab interpolate keyframes are ${width}x${height}; the API takes 16 to 128 pixels per side`);
|
|
3691
|
+
}
|
|
3692
|
+
if (lastFrameWidth != null && lastFrameHeight != null && width != null && height != null && (lastFrameWidth !== width || lastFrameHeight !== height)) {
|
|
3693
|
+
throw new Error(
|
|
3694
|
+
`PixelLab interpolate lastFrame is ${lastFrameWidth}x${lastFrameHeight}; the start keyframe is ${width}x${height} \u2014 they must match`
|
|
3695
|
+
);
|
|
3696
|
+
}
|
|
3697
|
+
}
|
|
3698
|
+
if (spec.revision?.mode === "edit-animation") {
|
|
3699
|
+
const { sourceWidth: width, sourceHeight: height, sourceAssetId } = spec.revision;
|
|
3700
|
+
if (width != null && height != null) {
|
|
3701
|
+
if (!members || members.length < 2) {
|
|
3702
|
+
throw new Error(
|
|
3703
|
+
`PixelLab edit-animation needs a frame set to edit; ${sourceAssetId} is a single image (revise an animation, a character's directions, or another frame-set revision)`
|
|
3704
|
+
);
|
|
3705
|
+
}
|
|
3706
|
+
if (width < 16 || height < 16 || width > 256 || height > 256) {
|
|
3707
|
+
throw new Error(`PixelLab edit-animation frames are ${width}x${height}; the API takes 16 to 256 pixels per side`);
|
|
3708
|
+
}
|
|
3709
|
+
const ceiling = editAnimationFrameCeiling(width, height);
|
|
3710
|
+
if (members.length > ceiling) {
|
|
3711
|
+
throw new Error(
|
|
3712
|
+
`PixelLab edit-animation takes at most ${ceiling} frames at ${width}x${height} (every frame is packed into one grid); ${sourceAssetId} has ${members.length}`
|
|
3713
|
+
);
|
|
3714
|
+
}
|
|
3715
|
+
}
|
|
3716
|
+
}
|
|
3457
3717
|
if (spec.generator === "1dir" && (spec.width < 32 || spec.width > 256)) {
|
|
3458
3718
|
throw new Error("PixelLab 1dir dimensions must be between 32 and 256 pixels");
|
|
3459
3719
|
}
|
|
@@ -3470,6 +3730,14 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
3470
3730
|
`PixelLab uiAsset is ${spec.width}x${spec.height}; the API takes 192 to 688 pixels per side (the exact ceiling also depends on aspect ratio \u2014 square tops out at 512, 16:9 at 688x384, 9:16 at 384x688, 4:3 at 600x448, 3:4 at 448x600)`
|
|
3471
3731
|
);
|
|
3472
3732
|
}
|
|
3733
|
+
if (spec.generator === "uiElement" && (spec.width < 16 || spec.height < 16 || spec.width > 792 || spec.height > 688)) {
|
|
3734
|
+
throw new Error(
|
|
3735
|
+
`PixelLab uiElement is ${spec.width}x${spec.height}; the API takes 16 to 792 wide and 16 to 688 tall (the exact ceiling depends on aspect ratio \u2014 square tops out at 512, 16:9 at 688x384)`
|
|
3736
|
+
);
|
|
3737
|
+
}
|
|
3738
|
+
if (spec.generator === "uiElement" && styleImages.length > 1) {
|
|
3739
|
+
throw new Error("PixelLab uiElement takes one concept image; list at most one styleImages entry");
|
|
3740
|
+
}
|
|
3473
3741
|
if (spec.generator === "map") {
|
|
3474
3742
|
requirePixelLabOption("map", "view", spec.view, ["low top-down", "high top-down", "side"]);
|
|
3475
3743
|
requirePixelLabOption("map", "outline", spec.outline, [
|
|
@@ -3553,8 +3821,8 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
3553
3821
|
throw new Error(`PixelLab isometricTile: size must be 16 to 64 pixels, got ${spec.width}`);
|
|
3554
3822
|
}
|
|
3555
3823
|
}
|
|
3556
|
-
if (spec.generator === "character") this.validateCharacter(spec, styleImages);
|
|
3557
|
-
if (spec.generator === "objectPro") this.validateObjectPro(spec, styleImages);
|
|
3824
|
+
if (spec.generator === "character" && !spec.revision) this.validateCharacter(spec, styleImages);
|
|
3825
|
+
if (spec.generator === "objectPro" && !spec.revision) this.validateObjectPro(spec, styleImages);
|
|
3558
3826
|
if ((spec.generator === "map" || spec.generator === "pixflux") && styleImages.length) {
|
|
3559
3827
|
throw new Error(`PixelLab ${spec.generator} does not support style images`);
|
|
3560
3828
|
}
|
|
@@ -4121,6 +4389,19 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
4121
4389
|
});
|
|
4122
4390
|
return { jobId: res2.ui_asset_id, metadata: { backgroundJobId: res2.background_job_id } };
|
|
4123
4391
|
}
|
|
4392
|
+
if (spec.generator === "uiElement") {
|
|
4393
|
+
const concept = styleImages[0];
|
|
4394
|
+
const res2 = await this.client.generateUiV2({
|
|
4395
|
+
description: spec.prompt,
|
|
4396
|
+
width: spec.width,
|
|
4397
|
+
height: spec.height,
|
|
4398
|
+
conceptImage: concept ? { image: { base64: concept.base64, format: concept.format }, width: concept.width, height: concept.height } : void 0,
|
|
4399
|
+
colorPalette: spec.uiColorPalette,
|
|
4400
|
+
noBackground: spec.noBackground,
|
|
4401
|
+
seed: spec.seed
|
|
4402
|
+
});
|
|
4403
|
+
return { jobId: res2.background_job_id };
|
|
4404
|
+
}
|
|
4124
4405
|
if (spec.generator === "pixflux") {
|
|
4125
4406
|
const swatch = spec.palette.length ? paletteSwatch(spec.palette).toString("base64") : void 0;
|
|
4126
4407
|
const { png } = await this.client.createImagePixflux({
|
|
@@ -4249,13 +4530,18 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
4249
4530
|
if (!revision.sourceSha256 || !revision.sourceFormat || revision.sourceWidth == null || revision.sourceHeight == null) {
|
|
4250
4531
|
throw new Error(`${spec.styleId}/${spec.assetId}: revision source is not ready`);
|
|
4251
4532
|
}
|
|
4252
|
-
const
|
|
4253
|
-
|
|
4254
|
-
|
|
4255
|
-
|
|
4256
|
-
|
|
4533
|
+
const sourceFiles = revision.sourceMembers ?? [{ file: revision.sourceFile, sha256: revision.sourceSha256, format: revision.sourceFormat }];
|
|
4534
|
+
const images = sourceFiles.map((member) => {
|
|
4535
|
+
const bytes = (0, import_node_fs2.readFileSync)(member.file);
|
|
4536
|
+
if (sha256(bytes) !== member.sha256) {
|
|
4537
|
+
throw new Error(`${spec.styleId}/${spec.assetId}: revision source changed after the manifest was resolved`);
|
|
4538
|
+
}
|
|
4539
|
+
return { base64: bytes.toString("base64"), format: member.format };
|
|
4540
|
+
});
|
|
4541
|
+
const image = images[0];
|
|
4257
4542
|
const width = revision.sourceWidth;
|
|
4258
4543
|
const height = revision.sourceHeight;
|
|
4544
|
+
const roles = revision.sourceMembers?.map((member) => member.role);
|
|
4259
4545
|
if (revision.mode === "inpaint") {
|
|
4260
4546
|
if (!revision.maskFile || !revision.maskSha256 || !revision.maskFormat) {
|
|
4261
4547
|
throw new Error(`${spec.styleId}/${spec.assetId}: revision mask is not ready`);
|
|
@@ -4288,34 +4574,45 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
4288
4574
|
paletteImage = { base64: paletteBytes.toString("base64"), format: revision.paletteImageFormat };
|
|
4289
4575
|
}
|
|
4290
4576
|
const res2 = await this.client.reduceColors({
|
|
4291
|
-
|
|
4577
|
+
images,
|
|
4292
4578
|
numColors: revision.numColors,
|
|
4293
4579
|
paletteImage,
|
|
4294
4580
|
dithering: revision.dithering,
|
|
4295
4581
|
ditheringStrength: revision.ditheringStrength
|
|
4296
4582
|
});
|
|
4297
|
-
|
|
4298
|
-
(0, import_node_fs2.writeFileSync)(import_node_path.default.join(_PixelLabProvider.cacheDir(), `${jobId}.png`), res2.png);
|
|
4299
|
-
return { jobId, metadata: { revisionUsage: res2.usage } };
|
|
4583
|
+
return this.cacheCleanupResult(res2.pngs, res2.usage, roles, revision.sourceFps);
|
|
4300
4584
|
}
|
|
4301
4585
|
if (revision.mode === "correct-pixelart") {
|
|
4302
|
-
const res2 = await this.client.correctPixelart({
|
|
4303
|
-
|
|
4304
|
-
|
|
4305
|
-
|
|
4586
|
+
const res2 = await this.client.correctPixelart({ images, strength: revision.strength });
|
|
4587
|
+
return this.cacheCleanupResult(res2.pngs, res2.usage, roles, revision.sourceFps);
|
|
4588
|
+
}
|
|
4589
|
+
if (revision.mode === "interpolate") {
|
|
4590
|
+
const lastFrame = this.readLastFrame(spec);
|
|
4591
|
+
if (!lastFrame) throw new Error(`${spec.styleId}/${spec.assetId}: interpolate needs lastFrame, the ending keyframe`);
|
|
4592
|
+
const res2 = await this.client.interpolationV2({
|
|
4593
|
+
startImage: image,
|
|
4594
|
+
endImage: lastFrame,
|
|
4595
|
+
width,
|
|
4596
|
+
height,
|
|
4597
|
+
action: spec.prompt,
|
|
4598
|
+
seed: spec.seed,
|
|
4599
|
+
noBackground: spec.noBackground
|
|
4600
|
+
});
|
|
4601
|
+
return { jobId: res2.background_job_id };
|
|
4602
|
+
}
|
|
4603
|
+
if (revision.mode === "edit-animation") {
|
|
4604
|
+
const res2 = await this.client.editAnimationV2({
|
|
4605
|
+
frames: images,
|
|
4606
|
+
width,
|
|
4607
|
+
height,
|
|
4608
|
+
description: spec.prompt,
|
|
4609
|
+
seed: spec.seed,
|
|
4610
|
+
noBackground: spec.noBackground
|
|
4611
|
+
});
|
|
4612
|
+
return { jobId: res2.background_job_id };
|
|
4306
4613
|
}
|
|
4307
4614
|
if (revision.mode === "animate" || revision.mode === "animate-pixminimax") {
|
|
4308
|
-
|
|
4309
|
-
if (revision.lastFrameFile) {
|
|
4310
|
-
if (!revision.lastFrameSha256 || !revision.lastFrameFormat) {
|
|
4311
|
-
throw new Error(`${spec.styleId}/${spec.assetId}: revision last frame is not ready`);
|
|
4312
|
-
}
|
|
4313
|
-
const lastFrameBytes = (0, import_node_fs2.readFileSync)(revision.lastFrameFile);
|
|
4314
|
-
if (sha256(lastFrameBytes) !== revision.lastFrameSha256) {
|
|
4315
|
-
throw new Error(`${spec.styleId}/${spec.assetId}: revision last frame changed after the manifest was resolved`);
|
|
4316
|
-
}
|
|
4317
|
-
lastFrame = { base64: lastFrameBytes.toString("base64"), format: revision.lastFrameFormat };
|
|
4318
|
-
}
|
|
4615
|
+
const lastFrame = this.readLastFrame(spec);
|
|
4319
4616
|
if (revision.mode === "animate") {
|
|
4320
4617
|
const res3 = await this.client.animateWithTextV3({
|
|
4321
4618
|
firstFrame: image,
|
|
@@ -4355,6 +4652,36 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
4355
4652
|
});
|
|
4356
4653
|
return { jobId: res.background_job_id };
|
|
4357
4654
|
}
|
|
4655
|
+
/** A revision's pinned ending frame, proven to be the bytes the spec hashed; undefined when none is declared. */
|
|
4656
|
+
readLastFrame(spec) {
|
|
4657
|
+
const revision = spec.revision;
|
|
4658
|
+
if (!revision.lastFrameFile) return void 0;
|
|
4659
|
+
if (!revision.lastFrameSha256 || !revision.lastFrameFormat) {
|
|
4660
|
+
throw new Error(`${spec.styleId}/${spec.assetId}: revision last frame is not ready`);
|
|
4661
|
+
}
|
|
4662
|
+
const bytes = (0, import_node_fs2.readFileSync)(revision.lastFrameFile);
|
|
4663
|
+
if (sha256(bytes) !== revision.lastFrameSha256) {
|
|
4664
|
+
throw new Error(`${spec.styleId}/${spec.assetId}: revision last frame changed after the manifest was resolved`);
|
|
4665
|
+
}
|
|
4666
|
+
return { base64: bytes.toString("base64"), format: revision.lastFrameFormat };
|
|
4667
|
+
}
|
|
4668
|
+
/**
|
|
4669
|
+
* Park a synchronous Cleanup-tier result the way pixflux parks its own:
|
|
4670
|
+
* one `<id>.png` for a single image, or `<id>-<n>.png` per member with the
|
|
4671
|
+
* member roles recorded so `pollCachedRevision` hands them back under the
|
|
4672
|
+
* same names the parent's set used.
|
|
4673
|
+
*/
|
|
4674
|
+
cacheCleanupResult(pngs, usage, roles, fps) {
|
|
4675
|
+
const jobId = (0, import_node_crypto2.randomUUID)();
|
|
4676
|
+
if (!roles) {
|
|
4677
|
+
(0, import_node_fs2.writeFileSync)(import_node_path.default.join(_PixelLabProvider.cacheDir(), `${jobId}.png`), pngs[0]);
|
|
4678
|
+
return { jobId, metadata: { revisionUsage: usage } };
|
|
4679
|
+
}
|
|
4680
|
+
for (const [index, png] of pngs.entries()) {
|
|
4681
|
+
(0, import_node_fs2.writeFileSync)(import_node_path.default.join(_PixelLabProvider.cacheDir(), `${jobId}-${index}.png`), png);
|
|
4682
|
+
}
|
|
4683
|
+
return { jobId, metadata: { revisionUsage: usage, revisionMembers: roles, ...fps != null ? { revisionFps: fps } : {} } };
|
|
4684
|
+
}
|
|
4358
4685
|
/**
|
|
4359
4686
|
* What a background job actually billed, once its own record is still
|
|
4360
4687
|
* around to ask; see `billedFromUsage`. A stale or already-cleaned-up job
|
|
@@ -4375,7 +4702,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
4375
4702
|
if (revisionMode === "reduce-colors" || revisionMode === "correct-pixelart") {
|
|
4376
4703
|
return this.pollCachedRevision(jobId, context);
|
|
4377
4704
|
}
|
|
4378
|
-
if (revisionMode === "animate" || revisionMode === "animate-pixminimax") {
|
|
4705
|
+
if (revisionMode === "animate" || revisionMode === "animate-pixminimax" || revisionMode === "interpolate" || revisionMode === "edit-animation") {
|
|
4379
4706
|
return this.pollAnimateRevision(jobId, context);
|
|
4380
4707
|
}
|
|
4381
4708
|
if (context?.spec?.revision) return this.pollRevision(jobId);
|
|
@@ -4398,6 +4725,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
4398
4725
|
if (generator === "character") return this.pollCharacter(jobId, context);
|
|
4399
4726
|
if (generator === "objectPro") return this.pollObjectPro(jobId, context);
|
|
4400
4727
|
if (generator === "uiAsset") return this.pollUiAsset(jobId, context);
|
|
4728
|
+
if (generator === "uiElement") return this.pollUiElement(jobId);
|
|
4401
4729
|
const backgroundJobId = context?.metadata?.backgroundJobId;
|
|
4402
4730
|
const obj = await this.client.getObject(jobId);
|
|
4403
4731
|
if (obj.status === "review") {
|
|
@@ -4424,15 +4752,32 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
4424
4752
|
* work.
|
|
4425
4753
|
*/
|
|
4426
4754
|
async pollCachedRevision(jobId, context) {
|
|
4427
|
-
const
|
|
4428
|
-
|
|
4755
|
+
const usage = context?.metadata?.revisionUsage;
|
|
4756
|
+
const roles = context?.metadata?.revisionMembers;
|
|
4757
|
+
const gone = {
|
|
4758
|
+
status: "failed",
|
|
4759
|
+
error: "revision result is no longer cached locally; re-run submit for this asset"
|
|
4760
|
+
};
|
|
4761
|
+
if (Array.isArray(roles) && roles.length) {
|
|
4762
|
+
const sources = roles.map((role, index) => ({
|
|
4763
|
+
url: `file://${import_node_path.default.join(_PixelLabProvider.cacheDir(), `${jobId}-${index}.png`)}`,
|
|
4764
|
+
role: String(role)
|
|
4765
|
+
}));
|
|
4766
|
+
if (sources.some((source) => !(0, import_node_fs2.existsSync)(source.url.slice("file://".length)))) return gone;
|
|
4767
|
+
const fps = context?.metadata?.revisionFps;
|
|
4768
|
+
const frames = sources.every((source) => source.role.startsWith("frame-"));
|
|
4429
4769
|
return {
|
|
4430
|
-
status: "
|
|
4431
|
-
|
|
4770
|
+
status: "ready",
|
|
4771
|
+
objectId: jobId,
|
|
4772
|
+
sourceUrl: sources[0].url,
|
|
4773
|
+
sources,
|
|
4774
|
+
...frames && typeof fps === "number" ? { metadata: { frameSet: { fps, count: sources.length } } } : {},
|
|
4775
|
+
billed: billedFromUsage(usage)
|
|
4432
4776
|
};
|
|
4433
4777
|
}
|
|
4778
|
+
const file = import_node_path.default.join(_PixelLabProvider.cacheDir(), `${jobId}.png`);
|
|
4779
|
+
if (!(0, import_node_fs2.existsSync)(file)) return gone;
|
|
4434
4780
|
const sourceUrl = `file://${file}`;
|
|
4435
|
-
const usage = context?.metadata?.revisionUsage;
|
|
4436
4781
|
return { status: "ready", objectId: jobId, sourceUrl, sources: [{ url: sourceUrl }], billed: billedFromUsage(usage) };
|
|
4437
4782
|
}
|
|
4438
4783
|
/**
|
|
@@ -4456,16 +4801,21 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
4456
4801
|
if (job.status !== "completed") return { status: "processing" };
|
|
4457
4802
|
const billed = billedFromUsage(job.usage);
|
|
4458
4803
|
const done = job.last_response ?? {};
|
|
4459
|
-
const
|
|
4460
|
-
const
|
|
4461
|
-
|
|
4462
|
-
|
|
4463
|
-
frameUrls,
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
|
|
4804
|
+
const revision = context?.spec?.revision;
|
|
4805
|
+
const fps = revision?.fps ?? revision?.sourceFps ?? 8;
|
|
4806
|
+
const memberRoles = revision?.mode === "edit-animation" ? revision.sourceMembers?.map((member) => member.role) : void 0;
|
|
4807
|
+
const review = (frameUrls) => {
|
|
4808
|
+
const roles = memberRoles?.length === frameUrls.length ? memberRoles : frameUrls.map((_, index) => `frame-${String(index).padStart(2, "0")}`);
|
|
4809
|
+
return {
|
|
4810
|
+
status: "review-set",
|
|
4811
|
+
objectId: jobId,
|
|
4812
|
+
frameUrls,
|
|
4813
|
+
sources: frameUrls.map((url, index) => ({ url, role: roles[index] })),
|
|
4814
|
+
fps,
|
|
4815
|
+
metadata: roles.every((role) => role.startsWith("frame-")) ? { frameSet: { fps, count: frameUrls.length } } : {},
|
|
4816
|
+
billed
|
|
4817
|
+
};
|
|
4818
|
+
};
|
|
4469
4819
|
for (const key of ["frame_urls", "frames", "images"]) {
|
|
4470
4820
|
const list = done[key];
|
|
4471
4821
|
if (!Array.isArray(list) || !list.length) continue;
|
|
@@ -4497,7 +4847,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
4497
4847
|
}
|
|
4498
4848
|
return {
|
|
4499
4849
|
status: "failed",
|
|
4500
|
-
error: `Invalid PixelLab response for animate job ${jobId}: completed with no recognized frame list (got: ${Object.keys(done).join(", ") || "no keys"}); update pollAnimateRevision in src/providers/pixellab.ts with the real shape`
|
|
4850
|
+
error: `Invalid PixelLab response for ${revision?.mode ?? "animate"} job ${jobId}: completed with no recognized frame list (got: ${Object.keys(done).join(", ") || "no keys"}); update pollAnimateRevision in src/providers/pixellab.ts with the real shape`
|
|
4501
4851
|
};
|
|
4502
4852
|
}
|
|
4503
4853
|
/**
|
|
@@ -4587,6 +4937,36 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
4587
4937
|
error: `Invalid PixelLab response for imagePro job ${jobId}: completed with no recognized images array (got: ${Object.keys(done).join(", ") || "no keys"}); this endpoint's completed shape has not been exercised live yet -- update pollImagePro in src/providers/pixellab.ts with the real shape`
|
|
4588
4938
|
};
|
|
4589
4939
|
}
|
|
4940
|
+
/**
|
|
4941
|
+
* `/generate-ui-v2` completes a plain background job whose
|
|
4942
|
+
* `last_response.images` (per its own docs and Python client) holds the
|
|
4943
|
+
* result. One image is the expected case and goes straight to selected;
|
|
4944
|
+
* should the endpoint ever return several, they go to review like
|
|
4945
|
+
* imagePro's candidates, decoded to the same local cache. The shape has
|
|
4946
|
+
* not been observed live, so anything else fails naming the keys received.
|
|
4947
|
+
*/
|
|
4948
|
+
async pollUiElement(jobId) {
|
|
4949
|
+
const job = await this.client.getBackgroundJob(jobId);
|
|
4950
|
+
if (job.status === "failed") return { status: "failed", error: "UI element job failed upstream" };
|
|
4951
|
+
if (job.status !== "completed") return { status: "processing" };
|
|
4952
|
+
const billed = billedFromUsage(job.usage);
|
|
4953
|
+
const done = job.last_response ?? {};
|
|
4954
|
+
const images = Array.isArray(done.images) ? done.images : done.image ? [done.image] : [];
|
|
4955
|
+
const urls = [];
|
|
4956
|
+
for (const [index, candidate] of images.entries()) {
|
|
4957
|
+
const base64 = extractBase64(candidate);
|
|
4958
|
+
if (!base64) continue;
|
|
4959
|
+
const file = import_node_path.default.join(_PixelLabProvider.cacheDir(), `${jobId}-${index}.png`);
|
|
4960
|
+
(0, import_node_fs2.writeFileSync)(file, Buffer.from(base64, "base64"));
|
|
4961
|
+
urls.push(`file://${file}`);
|
|
4962
|
+
}
|
|
4963
|
+
if (urls.length === 1) return { status: "ready", objectId: jobId, sourceUrl: urls[0], sources: [{ url: urls[0] }], billed };
|
|
4964
|
+
if (urls.length > 1) return { status: "review", candidateUrls: urls, billed };
|
|
4965
|
+
return {
|
|
4966
|
+
status: "failed",
|
|
4967
|
+
error: `Invalid PixelLab response for uiElement job ${jobId}: completed with no recognized image (got: ${Object.keys(done).join(", ") || "no keys"}); update pollUiElement in src/providers/pixellab.ts with the real shape`
|
|
4968
|
+
};
|
|
4969
|
+
}
|
|
4590
4970
|
/**
|
|
4591
4971
|
* Map objects need their own path because the `/map-objects/{id}` record is
|
|
4592
4972
|
* deleted upstream roughly 8 hours after creation while the image survives in
|
|
@@ -4747,9 +5127,9 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
4747
5127
|
if (!url) throw new Error(`tiles job ${jobId} has no variation at index ${index}`);
|
|
4748
5128
|
return { objectId: `${jobId}#${index}`, sourceUrl: url };
|
|
4749
5129
|
}
|
|
4750
|
-
if (generator === "imagePro") {
|
|
5130
|
+
if (generator === "imagePro" || generator === "uiElement") {
|
|
4751
5131
|
const file = import_node_path.default.join(_PixelLabProvider.cacheDir(), `${jobId}-${index}.png`);
|
|
4752
|
-
if (!(0, import_node_fs2.existsSync)(file)) throw new Error(
|
|
5132
|
+
if (!(0, import_node_fs2.existsSync)(file)) throw new Error(`${generator} job ${jobId} has no cached candidate at index ${index}`);
|
|
4753
5133
|
return { objectId: `${jobId}#${index}`, sourceUrl: `file://${file}` };
|
|
4754
5134
|
}
|
|
4755
5135
|
const promoted = await this.client.selectFrames(jobId, [index], commonTag);
|
|
@@ -4903,6 +5283,12 @@ function readPose(spec, what, image) {
|
|
|
4903
5283
|
}
|
|
4904
5284
|
return { base64: bytes.toString("base64"), format: image.format };
|
|
4905
5285
|
}
|
|
5286
|
+
function editAnimationFrameCeiling(width, height) {
|
|
5287
|
+
const side = Math.max(width, height);
|
|
5288
|
+
if (side <= 64) return 16;
|
|
5289
|
+
if (side <= 80) return 9;
|
|
5290
|
+
return 4;
|
|
5291
|
+
}
|
|
4906
5292
|
function rotationSources(character) {
|
|
4907
5293
|
const order = character.directions === 4 ? CHARACTER_DIRECTIONS_4 : CHARACTER_DIRECTIONS_8;
|
|
4908
5294
|
const sources = [];
|
|
@@ -4995,9 +5381,9 @@ var RetroDiffusionClient = class {
|
|
|
4995
5381
|
if (!response.ok) throw new ProviderError("retrodiffusion", `Retro Diffusion download failed (${response.status})`, { status: response.status });
|
|
4996
5382
|
return Buffer.from(await response.arrayBuffer());
|
|
4997
5383
|
}
|
|
4998
|
-
async call(
|
|
5384
|
+
async call(path34, init = {}) {
|
|
4999
5385
|
if (!this.token) throw new Error("RD_API_KEY is not set");
|
|
5000
|
-
const response = await this.request(`${this.baseUrl}${
|
|
5386
|
+
const response = await this.request(`${this.baseUrl}${path34}`, {
|
|
5001
5387
|
...init,
|
|
5002
5388
|
headers: {
|
|
5003
5389
|
"Content-Type": "application/json",
|
|
@@ -6039,14 +6425,15 @@ var ComfyUIProvider = class _ComfyUIProvider {
|
|
|
6039
6425
|
}
|
|
6040
6426
|
/**
|
|
6041
6427
|
* Everything else resolves generically to whatever the user's own
|
|
6042
|
-
* workflow does with `bindings.sourceImage`.
|
|
6043
|
-
*
|
|
6044
|
-
*
|
|
6045
|
-
*
|
|
6046
|
-
*
|
|
6428
|
+
* workflow does with `bindings.sourceImage`. The frame-set modes
|
|
6429
|
+
* (`animate`, `animate-pixminimax`, `interpolate`, `edit-animation`) are
|
|
6430
|
+
* the exception: they produce an ordered frame set, and this adapter's
|
|
6431
|
+
* revision path always writes a single output image (see `submit`/`fetch`
|
|
6432
|
+
* below) — a real structural gap, not a missing binding, so they are
|
|
6433
|
+
* rejected here rather than only failing once `validate` runs.
|
|
6047
6434
|
*/
|
|
6048
6435
|
supportsRevision(mode2) {
|
|
6049
|
-
return mode2
|
|
6436
|
+
return !FRAME_SET_REVISION_MODES.includes(mode2);
|
|
6050
6437
|
}
|
|
6051
6438
|
estimate(spec) {
|
|
6052
6439
|
return {
|
|
@@ -6099,6 +6486,11 @@ var ComfyUIProvider = class _ComfyUIProvider {
|
|
|
6099
6486
|
throw new Error("ComfyUI providerOptions.frames requires generator: frames");
|
|
6100
6487
|
}
|
|
6101
6488
|
if (spec.revision) {
|
|
6489
|
+
if (spec.revision.sourceMembers) {
|
|
6490
|
+
throw new Error(
|
|
6491
|
+
`ComfyUI revisions read one source image; ${spec.revision.sourceAssetId} is a ${spec.revision.sourceMembers.length}-member set (directions or frames)`
|
|
6492
|
+
);
|
|
6493
|
+
}
|
|
6102
6494
|
if (!options.bindings.sourceImage) {
|
|
6103
6495
|
throw new Error("ComfyUI revisions require bindings.sourceImage");
|
|
6104
6496
|
}
|
|
@@ -6933,6 +7325,91 @@ var import_promises3 = require("fs/promises");
|
|
|
6933
7325
|
var import_node_fs5 = require("fs");
|
|
6934
7326
|
var import_node_path5 = __toESM(require("path"), 1);
|
|
6935
7327
|
|
|
7328
|
+
// src/loop-directions.ts
|
|
7329
|
+
var SINGLE_OUTPUT_FIELDS = ["file", "cell", "remoteId", "source", "sourceByStyle", "outputRole"];
|
|
7330
|
+
var MIRROR_FIELDS = ["category", "tags", "styles"];
|
|
7331
|
+
function loopDirectionId(shorthandId, direction) {
|
|
7332
|
+
return `${shorthandId}.${direction}`;
|
|
7333
|
+
}
|
|
7334
|
+
function expandLoopDirections(raw) {
|
|
7335
|
+
const families = {};
|
|
7336
|
+
const issues = [];
|
|
7337
|
+
if (!isObject2(raw) || !isObject2(raw.assets)) return { raw, families, issues };
|
|
7338
|
+
const assets = raw.assets;
|
|
7339
|
+
if (!Object.values(assets).some((asset) => isObject2(asset) && isObject2(asset.animation) && "directions" in asset.animation)) {
|
|
7340
|
+
return { raw, families, issues };
|
|
7341
|
+
}
|
|
7342
|
+
const expanded = {};
|
|
7343
|
+
const claimed = new Set(Object.keys(assets));
|
|
7344
|
+
for (const [id, asset] of Object.entries(assets)) {
|
|
7345
|
+
if (!isObject2(asset) || !isObject2(asset.animation) || !("directions" in asset.animation)) {
|
|
7346
|
+
expanded[id] = asset;
|
|
7347
|
+
continue;
|
|
7348
|
+
}
|
|
7349
|
+
const where = `assets.${id}.animation.directions`;
|
|
7350
|
+
const { directions: rawDirections, ...animation } = asset.animation;
|
|
7351
|
+
const parsed = CharacterDirectionSchema.array().min(1).safeParse(rawDirections);
|
|
7352
|
+
if (!parsed.success) {
|
|
7353
|
+
issues.push(`${where}: expected a non-empty list of directions (${CharacterDirectionSchema.options.join(", ")})`);
|
|
7354
|
+
continue;
|
|
7355
|
+
}
|
|
7356
|
+
const directions = parsed.data;
|
|
7357
|
+
const duplicate = directions.find((direction, index) => directions.indexOf(direction) !== index);
|
|
7358
|
+
if (duplicate) {
|
|
7359
|
+
issues.push(`${where}: "${duplicate}" is listed twice`);
|
|
7360
|
+
continue;
|
|
7361
|
+
}
|
|
7362
|
+
if ("direction" in animation) {
|
|
7363
|
+
issues.push(`assets.${id}.animation: direction and directions are mutually exclusive; directions names every loop to make`);
|
|
7364
|
+
continue;
|
|
7365
|
+
}
|
|
7366
|
+
const single = SINGLE_OUTPUT_FIELDS.filter((field) => field in asset);
|
|
7367
|
+
if (single.length) {
|
|
7368
|
+
issues.push(
|
|
7369
|
+
`assets.${id}: ${single.join(", ")} name${single.length === 1 ? "s" : ""} one output, but directions makes ${directions.length} loops; declare the loops explicitly to set ${single.length === 1 ? "it" : "them"} per direction`
|
|
7370
|
+
);
|
|
7371
|
+
continue;
|
|
7372
|
+
}
|
|
7373
|
+
const loops = directions.map((direction) => ({ id: loopDirectionId(id, direction), direction }));
|
|
7374
|
+
const named = new Set(directions);
|
|
7375
|
+
const mirrors = [];
|
|
7376
|
+
for (const { id: loopId, direction } of loops) {
|
|
7377
|
+
const flipped = MIRRORED_DIRECTION[direction];
|
|
7378
|
+
if (flipped === direction || named.has(flipped)) continue;
|
|
7379
|
+
named.add(flipped);
|
|
7380
|
+
mirrors.push({ id: loopDirectionId(id, flipped), of: loopId });
|
|
7381
|
+
}
|
|
7382
|
+
const ids = [...loops.map((loop) => loop.id), ...mirrors.map((mirror) => mirror.id)];
|
|
7383
|
+
const collision = ids.find((expandedId) => claimed.has(expandedId));
|
|
7384
|
+
if (collision) {
|
|
7385
|
+
issues.push(`${where}: expands to "${collision}", which is already declared`);
|
|
7386
|
+
continue;
|
|
7387
|
+
}
|
|
7388
|
+
for (const expandedId of ids) claimed.add(expandedId);
|
|
7389
|
+
for (const loop of loops) {
|
|
7390
|
+
expanded[loop.id] = { ...asset, animation: { ...animation, direction: loop.direction } };
|
|
7391
|
+
}
|
|
7392
|
+
for (const mirror of mirrors) {
|
|
7393
|
+
const inherited = {};
|
|
7394
|
+
for (const field of MIRROR_FIELDS) if (field in asset) inherited[field] = asset[field];
|
|
7395
|
+
expanded[mirror.id] = { mirror: mirror.of, ...inherited };
|
|
7396
|
+
}
|
|
7397
|
+
families[id] = ids;
|
|
7398
|
+
}
|
|
7399
|
+
return { raw: { ...raw, assets: expanded }, families, issues };
|
|
7400
|
+
}
|
|
7401
|
+
function expandAssetFilter(ids, families) {
|
|
7402
|
+
if (!families) return [...ids];
|
|
7403
|
+
return [...new Set(ids.flatMap((id) => families[id] ?? [id]))];
|
|
7404
|
+
}
|
|
7405
|
+
function loopShorthandOf(assetId, families) {
|
|
7406
|
+
if (!families) return void 0;
|
|
7407
|
+
return Object.keys(families).find((id) => families[id].includes(assetId));
|
|
7408
|
+
}
|
|
7409
|
+
function isObject2(value) {
|
|
7410
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
7411
|
+
}
|
|
7412
|
+
|
|
6936
7413
|
// src/outputs.ts
|
|
6937
7414
|
var import_node_fs4 = require("fs");
|
|
6938
7415
|
var import_node_path4 = __toESM(require("path"), 1);
|
|
@@ -7251,7 +7728,12 @@ function frameSetFps(entry) {
|
|
|
7251
7728
|
async function loadManifest(manifestPath) {
|
|
7252
7729
|
const abs = import_node_path5.default.resolve(manifestPath);
|
|
7253
7730
|
if (!(0, import_node_fs5.existsSync)(abs)) throw new ProjectError(`No manifest at ${abs}`);
|
|
7254
|
-
const
|
|
7731
|
+
const expansion = expandLoopDirections(JSON.parse(await (0, import_promises3.readFile)(abs, "utf8")));
|
|
7732
|
+
if (expansion.issues.length) {
|
|
7733
|
+
throw new ProjectError(`Manifest at ${abs} is invalid:
|
|
7734
|
+
${expansion.issues.map((i) => ` ${i}`).join("\n")}`);
|
|
7735
|
+
}
|
|
7736
|
+
const input = ManifestInputSchema.safeParse(expansion.raw);
|
|
7255
7737
|
if (!input.success) {
|
|
7256
7738
|
const issues = formatManifestIssues(input.error.issues);
|
|
7257
7739
|
throw new ProjectError(`Manifest at ${abs} is invalid:
|
|
@@ -7336,7 +7818,8 @@ ${issues}`);
|
|
|
7336
7818
|
throw new ProjectError(`Manifest at ${abs} is invalid:
|
|
7337
7819
|
${unknownReferences.map((i) => ` ${i}`).join("\n")}`);
|
|
7338
7820
|
}
|
|
7339
|
-
|
|
7821
|
+
const loopFamilies = Object.keys(expansion.families).length ? { loopFamilies: expansion.families } : {};
|
|
7822
|
+
return { manifest: parsed.data, root: import_node_path5.default.dirname(abs), path: abs, ...loopFamilies };
|
|
7340
7823
|
}
|
|
7341
7824
|
function parentAssetId(asset) {
|
|
7342
7825
|
return asset?.revision?.from ?? asset?.state?.of ?? asset?.animation?.of;
|
|
@@ -7420,12 +7903,13 @@ async function resolveSpecs(loaded, filter) {
|
|
|
7420
7903
|
);
|
|
7421
7904
|
}
|
|
7422
7905
|
}
|
|
7423
|
-
|
|
7906
|
+
const assetFilter = expandAssetFilter(filter?.assets ?? [], loaded.loopFamilies);
|
|
7907
|
+
for (const unknownAsset of assetFilter) {
|
|
7424
7908
|
if (!manifest.assets[unknownAsset]) {
|
|
7425
7909
|
throw new UsageError(`Unknown asset "${unknownAsset}".`);
|
|
7426
7910
|
}
|
|
7427
7911
|
}
|
|
7428
|
-
const requestedAssetIds = new Set(
|
|
7912
|
+
const requestedAssetIds = new Set(assetFilter.length ? assetFilter : Object.keys(manifest.assets));
|
|
7429
7913
|
const resolutionAssetIds = new Set(requestedAssetIds);
|
|
7430
7914
|
for (const assetId of [...requestedAssetIds]) {
|
|
7431
7915
|
let current = parentAssetId(manifest.assets[assetId]);
|
|
@@ -7520,7 +8004,7 @@ async function resolveSpecs(loaded, filter) {
|
|
|
7520
8004
|
size = asset.size ?? style.size ?? 64;
|
|
7521
8005
|
width = size;
|
|
7522
8006
|
height = size;
|
|
7523
|
-
} else if (generator === "uiAsset") {
|
|
8007
|
+
} else if (generator === "uiAsset" || generator === "uiElement") {
|
|
7524
8008
|
width = asset.width ?? style.size ?? 256;
|
|
7525
8009
|
height = asset.height ?? style.size ?? 256;
|
|
7526
8010
|
size = Math.max(width, height);
|
|
@@ -7587,7 +8071,7 @@ async function resolveSpecs(loaded, filter) {
|
|
|
7587
8071
|
seed: style.seed,
|
|
7588
8072
|
uiPieces: generator === "uiAsset" ? asset.pieces : void 0,
|
|
7589
8073
|
uiElements: generator === "uiAsset" ? asset.elements : void 0,
|
|
7590
|
-
uiColorPalette: generator === "uiAsset" ? style.uiColorPalette : void 0,
|
|
8074
|
+
uiColorPalette: generator === "uiAsset" || generator === "uiElement" ? style.uiColorPalette : void 0,
|
|
7591
8075
|
palette: style.palette,
|
|
7592
8076
|
enforcePalette: style.enforcePalette,
|
|
7593
8077
|
noBackground: style.noBackground,
|
|
@@ -7627,8 +8111,11 @@ async function resolveSpecs(loaded, filter) {
|
|
|
7627
8111
|
terrainView: generator === "terrain" ? style.terrainView : void 0,
|
|
7628
8112
|
isometricTileSize: generator === "isometricTile" ? style.isometricTileSize : void 0,
|
|
7629
8113
|
isometricTileShape: generator === "isometricTile" ? style.isometricTileShape : void 0,
|
|
7630
|
-
|
|
7631
|
-
|
|
8114
|
+
// A revision in a character or objectPro style is a provider edit of
|
|
8115
|
+
// its parent's pixels, not a new character: giving it a base shape
|
|
8116
|
+
// would have packing, the gallery, and adoption treat it as one.
|
|
8117
|
+
...generator === "character" && !asset.revision ? { character: await resolveCharacterShape(asset, style, characterKind, { root, load: loadStyleImage }) } : {},
|
|
8118
|
+
...generator === "objectPro" && !asset.revision ? { objectPro: await resolveObjectProShape(assetId, asset, style, characterKind, { root, load: loadStyleImage }) } : {},
|
|
7632
8119
|
cost: generator === "tiles" ? tilesCost(tileSize, tileVariations) : generator === "terrain" ? tilesCost(size, terrainTiles) : generator === "isometricTile" ? isometricTileCost() : generationCost(width, height, generator),
|
|
7633
8120
|
costUnit: "generations",
|
|
7634
8121
|
candidates: generator === "tiles" ? tileVariations : generator === "terrain" ? terrainTiles : generator === "1dir" || generator === "imagePro" ? candidateCount(size) : 1
|
|
@@ -7779,8 +8266,11 @@ async function resolveSpecs(loaded, filter) {
|
|
|
7779
8266
|
);
|
|
7780
8267
|
}
|
|
7781
8268
|
const sourceSpec = await finalize(asset.revision.from);
|
|
7782
|
-
const
|
|
7783
|
-
const
|
|
8269
|
+
const sourceStem = sourceSpec.quality?.outFile ?? (sourceSpec.source ? import_node_path5.default.resolve(root, sourceSpec.source) : sourceSpec.outFile);
|
|
8270
|
+
const sourceMembers = await revisionSourceMembers(sourceStem, `revision source ${asset.revision.from}`);
|
|
8271
|
+
const sourceFile = sourceMembers?.[0]?.file ?? sourceStem;
|
|
8272
|
+
const sourceImage = sourceMembers ? { ...sourceMembers[0], hash: sha256(JSON.stringify(sourceMembers.map((member) => member.sha256))) } : await optionalRevisionImage(sourceFile, "revision source");
|
|
8273
|
+
const sourceFps = sourceSpec.character?.animation?.fps ?? sourceSpec.objectPro?.animation?.fps ?? sourceSpec.revision?.fps ?? sourceSpec.revision?.sourceFps;
|
|
7784
8274
|
const maskFile = asset.revision.mask ? import_node_path5.default.resolve(root, asset.revision.mask) : void 0;
|
|
7785
8275
|
const maskImage = maskFile ? await optionalRevisionImage(maskFile, "revision mask", "png") : null;
|
|
7786
8276
|
if (sourceImage && maskImage && (sourceImage.width !== maskImage.width || sourceImage.height !== maskImage.height)) {
|
|
@@ -7801,6 +8291,8 @@ async function resolveSpecs(loaded, filter) {
|
|
|
7801
8291
|
sourceHeight: sourceImage?.height ?? null,
|
|
7802
8292
|
sourceFormat: sourceImage?.format ?? null,
|
|
7803
8293
|
sourceSpec,
|
|
8294
|
+
...sourceMembers ? { sourceMembers } : {},
|
|
8295
|
+
...sourceMembers && sourceFps != null ? { sourceFps } : {},
|
|
7804
8296
|
...maskFile ? {
|
|
7805
8297
|
maskFile,
|
|
7806
8298
|
maskSha256: maskImage?.hash ?? null,
|
|
@@ -7950,6 +8442,38 @@ async function optionalRevisionImage(file, label, requiredFormat) {
|
|
|
7950
8442
|
}
|
|
7951
8443
|
return { hash: sha256(bytes), ...metadata };
|
|
7952
8444
|
}
|
|
8445
|
+
async function revisionSourceMembers(stem, label) {
|
|
8446
|
+
if ((0, import_node_fs5.existsSync)(stem)) return null;
|
|
8447
|
+
const at = (role) => memberPath(stem, role, 0, 2, MediaType.PNG);
|
|
8448
|
+
let roles = [];
|
|
8449
|
+
for (const set of [CHARACTER_DIRECTIONS_8, CHARACTER_DIRECTIONS_4]) {
|
|
8450
|
+
if (set.every((direction) => (0, import_node_fs5.existsSync)(at(direction)))) {
|
|
8451
|
+
roles = [...set];
|
|
8452
|
+
break;
|
|
8453
|
+
}
|
|
8454
|
+
}
|
|
8455
|
+
if (!roles.length) {
|
|
8456
|
+
for (let index = 0; (0, import_node_fs5.existsSync)(at(`frame-${String(index).padStart(2, "0")}`)); index++) {
|
|
8457
|
+
roles.push(`frame-${String(index).padStart(2, "0")}`);
|
|
8458
|
+
}
|
|
8459
|
+
}
|
|
8460
|
+
if (!roles.length) return null;
|
|
8461
|
+
const members = [];
|
|
8462
|
+
for (const role of roles) {
|
|
8463
|
+
const file = at(role);
|
|
8464
|
+
const image = await optionalRevisionImage(file, `${label} (${role})`);
|
|
8465
|
+
if (!image) return null;
|
|
8466
|
+
members.push({ role, file, sha256: image.hash, width: image.width, height: image.height, format: image.format });
|
|
8467
|
+
}
|
|
8468
|
+
const first = members[0];
|
|
8469
|
+
const odd = members.find((member) => member.width !== first.width || member.height !== first.height);
|
|
8470
|
+
if (odd) {
|
|
8471
|
+
throw new Error(
|
|
8472
|
+
`${label} is a set whose members differ in size (${first.role} is ${first.width}x${first.height}, ${odd.role} is ${odd.width}x${odd.height}); a revision reads every member as one same-size batch`
|
|
8473
|
+
);
|
|
8474
|
+
}
|
|
8475
|
+
return members;
|
|
8476
|
+
}
|
|
7953
8477
|
async function resolveStyleImages(loaded, styleId) {
|
|
7954
8478
|
const style = loaded.manifest.styles[styleId];
|
|
7955
8479
|
if (!style) throw new Error(`Unknown style "${styleId}"`);
|
|
@@ -9632,11 +10156,16 @@ async function inspectRevision(spec, lock, seen) {
|
|
|
9632
10156
|
reason: `revision source ${spec.styleId}/${revision.sourceAssetId} is not ready: ${dependency.reason}`
|
|
9633
10157
|
};
|
|
9634
10158
|
}
|
|
9635
|
-
if (
|
|
9636
|
-
|
|
9637
|
-
|
|
9638
|
-
|
|
9639
|
-
|
|
10159
|
+
if (revision.sourceMembers) {
|
|
10160
|
+
const members = await inspectSourceMembers(spec, lock);
|
|
10161
|
+
if (!members.ready) return members;
|
|
10162
|
+
} else {
|
|
10163
|
+
if (!revision.sourceSha256 || !(0, import_node_fs8.existsSync)(revision.sourceFile)) {
|
|
10164
|
+
return { ready: false, reason: `revision source is missing: ${revision.sourceFile}` };
|
|
10165
|
+
}
|
|
10166
|
+
if (await sha256File(revision.sourceFile) !== revision.sourceSha256) {
|
|
10167
|
+
return { ready: false, reason: "revision source changed after the manifest was resolved" };
|
|
10168
|
+
}
|
|
9640
10169
|
}
|
|
9641
10170
|
if (revision.maskFile) {
|
|
9642
10171
|
if (!revision.maskSha256 || !(0, import_node_fs8.existsSync)(revision.maskFile)) {
|
|
@@ -9646,8 +10175,39 @@ async function inspectRevision(spec, lock, seen) {
|
|
|
9646
10175
|
return { ready: false, reason: "revision mask changed after the manifest was resolved" };
|
|
9647
10176
|
}
|
|
9648
10177
|
}
|
|
10178
|
+
if (revision.lastFrameFile) {
|
|
10179
|
+
if (!revision.lastFrameSha256 || !(0, import_node_fs8.existsSync)(revision.lastFrameFile)) {
|
|
10180
|
+
return { ready: false, reason: `revision last frame is missing: ${revision.lastFrameFile}` };
|
|
10181
|
+
}
|
|
10182
|
+
if (await sha256File(revision.lastFrameFile) !== revision.lastFrameSha256) {
|
|
10183
|
+
return { ready: false, reason: "revision last frame changed after the manifest was resolved" };
|
|
10184
|
+
}
|
|
10185
|
+
}
|
|
9649
10186
|
return { ready: true, reason: "revision inputs are current" };
|
|
9650
10187
|
}
|
|
10188
|
+
async function inspectSourceMembers(spec, lock) {
|
|
10189
|
+
const revision = spec.revision;
|
|
10190
|
+
const members = revision.sourceMembers;
|
|
10191
|
+
for (const member of members) {
|
|
10192
|
+
if (!(0, import_node_fs8.existsSync)(member.file)) return { ready: false, reason: `revision source member is missing: ${member.file}` };
|
|
10193
|
+
if (await sha256File(member.file) !== member.sha256) {
|
|
10194
|
+
return { ready: false, reason: `revision source member changed after the manifest was resolved: ${member.file}` };
|
|
10195
|
+
}
|
|
10196
|
+
}
|
|
10197
|
+
const parent = revision.sourceSpec;
|
|
10198
|
+
if (parent.source) return { ready: true, reason: "declared source set is present" };
|
|
10199
|
+
const entry = lock.entries[lockKey(parent.styleId, parent.assetId)];
|
|
10200
|
+
if (!entry) return { ready: false, reason: "revision source is not in the lockfile" };
|
|
10201
|
+
const recorded = entry.outputs.map((_, index) => import_node_path11.default.resolve(currentEntryOutputPath(entry, parent, index))).sort();
|
|
10202
|
+
const found = members.map((member) => import_node_path11.default.resolve(member.file)).sort();
|
|
10203
|
+
if (recorded.length !== found.length || recorded.some((file, index) => file !== found[index])) {
|
|
10204
|
+
return {
|
|
10205
|
+
ready: false,
|
|
10206
|
+
reason: `revision source ${parent.assetId} records ${recorded.length} output(s) but ${found.length} member file(s) were found beside it; remove files left over from an earlier generation, or re-fetch the parent`
|
|
10207
|
+
};
|
|
10208
|
+
}
|
|
10209
|
+
return { ready: true, reason: "revision source set is current" };
|
|
10210
|
+
}
|
|
9651
10211
|
async function requireRevisionReady(spec, lock) {
|
|
9652
10212
|
const inspection = await inspectRevisionReadiness(spec, lock);
|
|
9653
10213
|
if (inspection && !inspection.ready) throw new Error(inspection.reason);
|
|
@@ -10482,16 +11042,99 @@ function hex(c) {
|
|
|
10482
11042
|
return `#${[c.r, c.g, c.b].map((v) => v.toString(16).padStart(2, "0")).join("")}`;
|
|
10483
11043
|
}
|
|
10484
11044
|
|
|
10485
|
-
// src/
|
|
10486
|
-
var import_node_path16 = __toESM(require("path"), 1);
|
|
11045
|
+
// src/pixellab-utils.ts
|
|
10487
11046
|
var import_node_fs12 = require("fs");
|
|
10488
11047
|
var import_promises9 = require("fs/promises");
|
|
11048
|
+
var import_node_path16 = __toESM(require("path"), 1);
|
|
11049
|
+
var UNZOOM_MIN_SIDE = 256;
|
|
11050
|
+
var UNZOOM_MAX_AREA = 2048 * 2048;
|
|
11051
|
+
function defaultUnzoomOut(input) {
|
|
11052
|
+
const ext = import_node_path16.default.extname(input);
|
|
11053
|
+
return `${ext ? input.slice(0, -ext.length) : input}.unzoomed.png`;
|
|
11054
|
+
}
|
|
11055
|
+
async function unzoomFile(client2, input, opts = {}) {
|
|
11056
|
+
const out = import_node_path16.default.resolve(opts.out ?? defaultUnzoomOut(input));
|
|
11057
|
+
if (import_node_path16.default.resolve(input) === out) throw new Error("unzoom will not overwrite its own input; pass a different --out");
|
|
11058
|
+
if ((0, import_node_fs12.existsSync)(out) && !opts.force) throw new Error(`${out} already exists; pass --force to replace it`);
|
|
11059
|
+
const bytes = await (0, import_promises9.readFile)(input);
|
|
11060
|
+
const metadata = imageMetadata(bytes);
|
|
11061
|
+
if (!metadata) throw new Error(`${input} is not a readable PNG or JPEG`);
|
|
11062
|
+
const { width, height, format } = metadata;
|
|
11063
|
+
if (width < UNZOOM_MIN_SIDE || height < UNZOOM_MIN_SIDE) {
|
|
11064
|
+
throw new Error(
|
|
11065
|
+
`${input} is ${width}x${height}; PixelLab's unzoom needs at least ${UNZOOM_MIN_SIDE}x${UNZOOM_MIN_SIDE} to find the pixel grid, so this is probably already at its native size`
|
|
11066
|
+
);
|
|
11067
|
+
}
|
|
11068
|
+
if (width * height > UNZOOM_MAX_AREA) {
|
|
11069
|
+
throw new Error(`${input} is ${width}x${height}; PixelLab's unzoom takes at most 2048x2048 worth of pixels`);
|
|
11070
|
+
}
|
|
11071
|
+
const res = await client2.unzoom({ image: { base64: bytes.toString("base64"), format }, quantize: opts.quantize });
|
|
11072
|
+
await (0, import_promises9.mkdir)(import_node_path16.default.dirname(out), { recursive: true });
|
|
11073
|
+
await (0, import_promises9.writeFile)(out, res.png);
|
|
11074
|
+
return { out, original: res.originalSize, unzoomed: res.unzoomedSize, zoomFactor: res.zoomFactor, usage: res.usage };
|
|
11075
|
+
}
|
|
11076
|
+
var FONT_WEIGHTS = ["Bold", "Regular"];
|
|
11077
|
+
var FONT_GLYPH_SIZES = [8, 16, 32, 64];
|
|
11078
|
+
var FONT_COST_GENERATIONS = 25;
|
|
11079
|
+
function fontOutputPaths(out) {
|
|
11080
|
+
const base = import_node_path16.default.resolve(out.replace(/\.(ttf|png)$/i, ""));
|
|
11081
|
+
return { ttf: `${base}.ttf`, atlas: `${base}.png` };
|
|
11082
|
+
}
|
|
11083
|
+
async function generateFont(client2, opts) {
|
|
11084
|
+
const { ttf, atlas } = fontOutputPaths(opts.out);
|
|
11085
|
+
if (!opts.force) {
|
|
11086
|
+
for (const file of [ttf, atlas]) {
|
|
11087
|
+
if ((0, import_node_fs12.existsSync)(file)) throw new Error(`${file} already exists; pass --force to replace it`);
|
|
11088
|
+
}
|
|
11089
|
+
}
|
|
11090
|
+
const sleep3 = opts.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
11091
|
+
const interval = opts.intervalMs ?? 5e3;
|
|
11092
|
+
const timeout = opts.timeoutMs ?? 10 * 6e4;
|
|
11093
|
+
const submitted = await client2.generateFontPro({
|
|
11094
|
+
description: opts.description,
|
|
11095
|
+
weight: opts.weight,
|
|
11096
|
+
glyphPx: opts.glyphPx,
|
|
11097
|
+
seed: opts.seed,
|
|
11098
|
+
fontName: opts.fontName
|
|
11099
|
+
});
|
|
11100
|
+
const jobId = submitted.background_job_id;
|
|
11101
|
+
const started = Date.now();
|
|
11102
|
+
for (; ; ) {
|
|
11103
|
+
const job = await client2.getFontJob(jobId);
|
|
11104
|
+
opts.onProgress?.(job.status);
|
|
11105
|
+
if (job.status === "failed") throw new Error(`PixelLab font job ${jobId} failed upstream`);
|
|
11106
|
+
if (job.status === "completed") {
|
|
11107
|
+
if (!job.download_ttf_url || !job.download_atlas_url) {
|
|
11108
|
+
throw new Error(`PixelLab font job ${jobId} completed without both download URLs`);
|
|
11109
|
+
}
|
|
11110
|
+
const [ttfBytes, atlasBytes] = await Promise.all([
|
|
11111
|
+
client2.download(job.download_ttf_url),
|
|
11112
|
+
client2.download(job.download_atlas_url)
|
|
11113
|
+
]);
|
|
11114
|
+
await (0, import_promises9.mkdir)(import_node_path16.default.dirname(ttf), { recursive: true });
|
|
11115
|
+
await (0, import_promises9.writeFile)(ttf, ttfBytes);
|
|
11116
|
+
await (0, import_promises9.writeFile)(atlas, atlasBytes);
|
|
11117
|
+
return { jobId, ttf, atlas, glyphPx: job.glyph_px ?? null, usage: job.usage };
|
|
11118
|
+
}
|
|
11119
|
+
if (Date.now() - started > timeout) {
|
|
11120
|
+
throw new Error(
|
|
11121
|
+
`PixelLab font job ${jobId} is still ${job.status} after ${Math.round(timeout / 1e3)} s; it keeps running upstream, but this command has stopped waiting`
|
|
11122
|
+
);
|
|
11123
|
+
}
|
|
11124
|
+
await sleep3(interval);
|
|
11125
|
+
}
|
|
11126
|
+
}
|
|
11127
|
+
|
|
11128
|
+
// src/pipeline/quality-regression.ts
|
|
11129
|
+
var import_node_path17 = __toESM(require("path"), 1);
|
|
11130
|
+
var import_node_fs13 = require("fs");
|
|
11131
|
+
var import_promises10 = require("fs/promises");
|
|
10489
11132
|
var import_zod4 = require("zod");
|
|
10490
11133
|
var SHA256_RE2 = /^[0-9a-f]{64}$/;
|
|
10491
11134
|
var HEX_RE = /^#[0-9a-f]{6}$/;
|
|
10492
11135
|
var MAX_REDMEAN_DISTANCE = 765;
|
|
10493
11136
|
var QualityPathSchema = import_zod4.z.string().min(1).refine(
|
|
10494
|
-
(value) => !
|
|
11137
|
+
(value) => !import_node_path17.default.posix.isAbsolute(value) && !value.includes("\\"),
|
|
10495
11138
|
"expected a portable relative path"
|
|
10496
11139
|
);
|
|
10497
11140
|
var DEFAULT_QUALITY_TOLERANCES = {
|
|
@@ -10638,10 +11281,10 @@ function measureDecodedImageQuality(png, digest2) {
|
|
|
10638
11281
|
};
|
|
10639
11282
|
}
|
|
10640
11283
|
async function measureImageQuality(file) {
|
|
10641
|
-
const absolute =
|
|
11284
|
+
const absolute = import_node_path17.default.resolve(file);
|
|
10642
11285
|
let bytes;
|
|
10643
11286
|
try {
|
|
10644
|
-
bytes = await (0,
|
|
11287
|
+
bytes = await (0, import_promises10.readFile)(absolute);
|
|
10645
11288
|
} catch (error) {
|
|
10646
11289
|
throw new Error(`Cannot read quality image ${absolute}: ${message4(error)}`, { cause: error });
|
|
10647
11290
|
}
|
|
@@ -10660,11 +11303,11 @@ function resolveQualityInputs(raw, inputsFilePath) {
|
|
|
10660
11303
|
}
|
|
10661
11304
|
const ids = parsed.data.map((entry) => entry.id);
|
|
10662
11305
|
if (new Set(ids).size !== ids.length) throw new Error("--inputs contains a duplicate quality case id.");
|
|
10663
|
-
const root =
|
|
11306
|
+
const root = import_node_path17.default.dirname(import_node_path17.default.resolve(inputsFilePath));
|
|
10664
11307
|
return parsed.data.map((entry) => ({
|
|
10665
11308
|
id: entry.id,
|
|
10666
|
-
file:
|
|
10667
|
-
...entry.record ? { record:
|
|
11309
|
+
file: import_node_path17.default.resolve(root, entry.path),
|
|
11310
|
+
...entry.record ? { record: import_node_path17.default.resolve(root, entry.record) } : {},
|
|
10668
11311
|
tolerances: QualityToleranceSchema.parse({
|
|
10669
11312
|
...DEFAULT_QUALITY_TOLERANCES,
|
|
10670
11313
|
...entry.tolerances
|
|
@@ -10672,7 +11315,7 @@ function resolveQualityInputs(raw, inputsFilePath) {
|
|
|
10672
11315
|
}));
|
|
10673
11316
|
}
|
|
10674
11317
|
function portableRelative2(from, to) {
|
|
10675
|
-
return
|
|
11318
|
+
return import_node_path17.default.relative(from, import_node_path17.default.resolve(to)).split(import_node_path17.default.sep).join("/") || ".";
|
|
10676
11319
|
}
|
|
10677
11320
|
async function verifyRecordForImage(recordPath, imagePath) {
|
|
10678
11321
|
const verification = await verifyArtifactBundle(recordPath);
|
|
@@ -10687,20 +11330,20 @@ async function verifyRecordForImage(recordPath, imagePath) {
|
|
|
10687
11330
|
if (record.kind !== "refine" || options === null || typeof options !== "object" || options.schema !== "pixelkiln-quality") {
|
|
10688
11331
|
throw new Error(`Quality record ${recordPath} is not a PixelKiln refinement record.`);
|
|
10689
11332
|
}
|
|
10690
|
-
const outputs = record.outputs.map((output) =>
|
|
10691
|
-
if (!outputs.includes(
|
|
11333
|
+
const outputs = record.outputs.map((output) => import_node_path17.default.resolve(import_node_path17.default.dirname(recordPath), output.path));
|
|
11334
|
+
if (!outputs.includes(import_node_path17.default.resolve(imagePath))) {
|
|
10692
11335
|
throw new Error(`Quality record ${recordPath} does not own ${imagePath}.`);
|
|
10693
11336
|
}
|
|
10694
11337
|
}
|
|
10695
11338
|
async function snapshotQualityBaseline(inputs, baselinePath, options = {}) {
|
|
10696
11339
|
if (!inputs.length) throw new Error("Cannot snapshot an empty quality baseline.");
|
|
10697
|
-
const absolute =
|
|
11340
|
+
const absolute = import_node_path17.default.resolve(baselinePath);
|
|
10698
11341
|
for (const input of inputs) {
|
|
10699
|
-
if (
|
|
11342
|
+
if (import_node_path17.default.resolve(input.file) === absolute || input.record && import_node_path17.default.resolve(input.record) === absolute) {
|
|
10700
11343
|
throw new Error(`Quality baseline output would overwrite an input: ${absolute}.`);
|
|
10701
11344
|
}
|
|
10702
11345
|
}
|
|
10703
|
-
const root =
|
|
11346
|
+
const root = import_node_path17.default.dirname(absolute);
|
|
10704
11347
|
const cases = [];
|
|
10705
11348
|
for (const input of [...inputs].sort((a, b) => a.id.localeCompare(b.id))) {
|
|
10706
11349
|
const expected = await measureImageQuality(input.file);
|
|
@@ -10727,8 +11370,8 @@ async function snapshotQualityBaseline(inputs, baselinePath, options = {}) {
|
|
|
10727
11370
|
cases
|
|
10728
11371
|
});
|
|
10729
11372
|
const data = Buffer.from(JSON.stringify(baseline, null, 2) + "\n");
|
|
10730
|
-
if ((0,
|
|
10731
|
-
const current = await (0,
|
|
11373
|
+
if ((0, import_node_fs13.existsSync)(absolute) && !options.force) {
|
|
11374
|
+
const current = await (0, import_promises10.readFile)(absolute);
|
|
10732
11375
|
if (!current.equals(data)) {
|
|
10733
11376
|
throw new OverwriteRefusedError(
|
|
10734
11377
|
`Quality baseline already exists with different content: ${absolute}. Pass --force to replace it.`,
|
|
@@ -10740,10 +11383,10 @@ async function snapshotQualityBaseline(inputs, baselinePath, options = {}) {
|
|
|
10740
11383
|
return { baseline, path: absolute, changed: result.changed.length > 0 };
|
|
10741
11384
|
}
|
|
10742
11385
|
async function readQualityBaseline(baselinePath) {
|
|
10743
|
-
const absolute =
|
|
11386
|
+
const absolute = import_node_path17.default.resolve(baselinePath);
|
|
10744
11387
|
let raw;
|
|
10745
11388
|
try {
|
|
10746
|
-
raw = JSON.parse(await (0,
|
|
11389
|
+
raw = JSON.parse(await (0, import_promises10.readFile)(absolute, "utf8"));
|
|
10747
11390
|
} catch (error) {
|
|
10748
11391
|
throw new Error(`Could not read quality baseline ${absolute}: ${message4(error)}`, { cause: error });
|
|
10749
11392
|
}
|
|
@@ -10813,12 +11456,12 @@ function regressionViolations(expected, actual, tolerances) {
|
|
|
10813
11456
|
return { violations, warnings };
|
|
10814
11457
|
}
|
|
10815
11458
|
async function checkQualityBaseline(baselinePath) {
|
|
10816
|
-
const absolute =
|
|
11459
|
+
const absolute = import_node_path17.default.resolve(baselinePath);
|
|
10817
11460
|
const baseline = await readQualityBaseline(absolute);
|
|
10818
|
-
const root =
|
|
11461
|
+
const root = import_node_path17.default.dirname(absolute);
|
|
10819
11462
|
const cases = [];
|
|
10820
11463
|
for (const entry of baseline.cases) {
|
|
10821
|
-
const file =
|
|
11464
|
+
const file = import_node_path17.default.resolve(root, entry.file);
|
|
10822
11465
|
let actual = null;
|
|
10823
11466
|
const violations = [];
|
|
10824
11467
|
const warnings = [];
|
|
@@ -10831,7 +11474,7 @@ async function checkQualityBaseline(baselinePath) {
|
|
|
10831
11474
|
violations.push(message4(error));
|
|
10832
11475
|
}
|
|
10833
11476
|
if (entry.record) {
|
|
10834
|
-
const recordPath =
|
|
11477
|
+
const recordPath = import_node_path17.default.resolve(root, entry.record.path);
|
|
10835
11478
|
try {
|
|
10836
11479
|
const recordHash = await sha256File(recordPath);
|
|
10837
11480
|
if (recordHash !== entry.record.sha256) violations.push("quality record hash changed");
|
|
@@ -10867,14 +11510,14 @@ async function checkQualityBaseline(baselinePath) {
|
|
|
10867
11510
|
}
|
|
10868
11511
|
|
|
10869
11512
|
// src/pipeline/cache-health.ts
|
|
10870
|
-
var
|
|
10871
|
-
var
|
|
10872
|
-
var
|
|
11513
|
+
var import_node_fs17 = require("fs");
|
|
11514
|
+
var import_promises13 = require("fs/promises");
|
|
11515
|
+
var import_node_path21 = __toESM(require("path"), 1);
|
|
10873
11516
|
|
|
10874
11517
|
// src/cache.ts
|
|
10875
|
-
var
|
|
10876
|
-
var
|
|
10877
|
-
var
|
|
11518
|
+
var import_promises11 = require("fs/promises");
|
|
11519
|
+
var import_node_fs14 = require("fs");
|
|
11520
|
+
var import_node_path18 = __toESM(require("path"), 1);
|
|
10878
11521
|
var import_zod5 = require("zod");
|
|
10879
11522
|
var HashCacheSchema = import_zod5.z.object({
|
|
10880
11523
|
version: import_zod5.z.literal(1),
|
|
@@ -10888,10 +11531,10 @@ function isSha256Hash(value) {
|
|
|
10888
11531
|
function parseCache(value) {
|
|
10889
11532
|
return HashCacheSchema.parse(value);
|
|
10890
11533
|
}
|
|
10891
|
-
async function loadCache(
|
|
10892
|
-
if (!(0,
|
|
11534
|
+
async function loadCache(path34) {
|
|
11535
|
+
if (!(0, import_node_fs14.existsSync)(path34)) return { version: 1, hashes: {} };
|
|
10893
11536
|
try {
|
|
10894
|
-
const parsed = HashCacheSchema.safeParse(JSON.parse(await (0,
|
|
11537
|
+
const parsed = HashCacheSchema.safeParse(JSON.parse(await (0, import_promises11.readFile)(path34, "utf8")));
|
|
10895
11538
|
if (!parsed.success) return { version: 1, hashes: {} };
|
|
10896
11539
|
return {
|
|
10897
11540
|
version: 1,
|
|
@@ -10903,20 +11546,20 @@ async function loadCache(path33) {
|
|
|
10903
11546
|
return { version: 1, hashes: {} };
|
|
10904
11547
|
}
|
|
10905
11548
|
}
|
|
10906
|
-
async function saveCache(
|
|
11549
|
+
async function saveCache(path34, cache) {
|
|
10907
11550
|
const sorted = {};
|
|
10908
11551
|
for (const key of Object.keys(cache.hashes).sort()) {
|
|
10909
11552
|
const hash = cache.hashes[key];
|
|
10910
11553
|
if (!isSha256Hash(hash)) throw new Error(`Refusing to cache invalid SHA-256 for ${key}`);
|
|
10911
11554
|
sorted[key] = hash;
|
|
10912
11555
|
}
|
|
10913
|
-
await (0,
|
|
10914
|
-
const tmp = `${
|
|
11556
|
+
await (0, import_promises11.mkdir)(import_node_path18.default.dirname(import_node_path18.default.resolve(path34)), { recursive: true });
|
|
11557
|
+
const tmp = `${path34}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
10915
11558
|
try {
|
|
10916
|
-
await (0,
|
|
10917
|
-
await (0,
|
|
11559
|
+
await (0, import_promises11.writeFile)(tmp, JSON.stringify({ version: 1, hashes: sorted }, null, 2) + "\n");
|
|
11560
|
+
await (0, import_promises11.rename)(tmp, path34);
|
|
10918
11561
|
} finally {
|
|
10919
|
-
await (0,
|
|
11562
|
+
await (0, import_promises11.rm)(tmp, { force: true });
|
|
10920
11563
|
}
|
|
10921
11564
|
}
|
|
10922
11565
|
function pruneCache(cache, liveIds, namespace = "") {
|
|
@@ -10938,20 +11581,20 @@ function cachePathFor(lockPath) {
|
|
|
10938
11581
|
}
|
|
10939
11582
|
|
|
10940
11583
|
// src/pipeline/history.ts
|
|
10941
|
-
var
|
|
10942
|
-
var
|
|
11584
|
+
var import_node_fs16 = require("fs");
|
|
11585
|
+
var import_node_path20 = __toESM(require("path"), 1);
|
|
10943
11586
|
|
|
10944
11587
|
// src/pipeline/fetch.ts
|
|
10945
|
-
var
|
|
10946
|
-
var
|
|
10947
|
-
var
|
|
11588
|
+
var import_node_fs15 = require("fs");
|
|
11589
|
+
var import_promises12 = require("fs/promises");
|
|
11590
|
+
var import_node_path19 = __toESM(require("path"), 1);
|
|
10948
11591
|
async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
10949
11592
|
const log = opts.onProgress ?? (() => {
|
|
10950
11593
|
});
|
|
10951
11594
|
const result = { downloaded: 0, skipped: 0, failed: 0, ...opts.refresh ? { unchanged: 0 } : {} };
|
|
10952
11595
|
const specByKey = new Map(specs.map((s) => [lockKey(s.styleId, s.assetId), s]));
|
|
10953
11596
|
normalizeLockOutputPaths(lock, specs);
|
|
10954
|
-
const cacheDir = opts.cacheDir === false ? null :
|
|
11597
|
+
const cacheDir = opts.cacheDir === false ? null : import_node_path19.default.resolve(opts.cacheDir ?? import_node_path19.default.join(import_node_path19.default.dirname(lockPath), ".pixelkiln", "cache"));
|
|
10955
11598
|
const pending = Object.entries(lock.entries).filter(([key, e]) => {
|
|
10956
11599
|
if (e.provider !== provider.id || !specByKey.has(key)) return false;
|
|
10957
11600
|
if (specByKey.get(key)?.mirror) return false;
|
|
@@ -10962,15 +11605,15 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
10962
11605
|
if (spec && e.specHash === spec.specHash && e.outputs.length && !postprocessCurrent(e, spec)) {
|
|
10963
11606
|
return Boolean(opts.force) || e.outputs.every((output) => {
|
|
10964
11607
|
const file = resolveOutputPath(output.path, spec.root);
|
|
10965
|
-
return (0,
|
|
11608
|
+
return (0, import_node_fs15.existsSync)(file) && sha256((0, import_node_fs15.readFileSync)(file)) === output.sha256;
|
|
10966
11609
|
});
|
|
10967
11610
|
}
|
|
10968
11611
|
if (!opts.repair) return false;
|
|
10969
11612
|
if (e.outputs.length === 0 || !spec) return true;
|
|
10970
11613
|
return e.outputs.some((output) => {
|
|
10971
11614
|
const file = resolveOutputPath(output.path, spec.root);
|
|
10972
|
-
if (!(0,
|
|
10973
|
-
const current = sha256((0,
|
|
11615
|
+
if (!(0, import_node_fs15.existsSync)(file)) return true;
|
|
11616
|
+
const current = sha256((0, import_node_fs15.readFileSync)(file));
|
|
10974
11617
|
if (current === output.sha256) return false;
|
|
10975
11618
|
return Boolean(opts.force) || (e.supersededOutputs ?? []).some((old) => old.sha256 === current);
|
|
10976
11619
|
});
|
|
@@ -11053,21 +11696,21 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
11053
11696
|
if (!source.url) throw new Error(`no durable source URL to refresh ${source.role ?? "asset"} from`);
|
|
11054
11697
|
fresh = await provider.download(source.url);
|
|
11055
11698
|
if (recorded && sha256(fresh) === recordedRaw && !reapply) {
|
|
11056
|
-
log(` current ${
|
|
11699
|
+
log(` current ${import_node_path19.default.relative(process.cwd(), target)}`);
|
|
11057
11700
|
outputs.push({ ...recorded, path: portableOutputPath(target, spec.root) });
|
|
11058
11701
|
continue;
|
|
11059
11702
|
}
|
|
11060
11703
|
}
|
|
11061
11704
|
let raw = null;
|
|
11062
11705
|
let final = null;
|
|
11063
|
-
if ((0,
|
|
11706
|
+
if ((0, import_node_fs15.existsSync)(target)) {
|
|
11064
11707
|
const currentHash = await sha256File(target);
|
|
11065
11708
|
if (recorded && currentHash === recorded.sha256) {
|
|
11066
11709
|
if (!fresh && !reapply) {
|
|
11067
11710
|
if (cacheDir) {
|
|
11068
11711
|
await cacheMedia(
|
|
11069
11712
|
cacheDir,
|
|
11070
|
-
await (0,
|
|
11713
|
+
await (0, import_promises12.readFile)(target),
|
|
11071
11714
|
recorded.mediaType ?? MediaType.PNG,
|
|
11072
11715
|
recorded.sha256
|
|
11073
11716
|
);
|
|
@@ -11076,10 +11719,10 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
11076
11719
|
continue;
|
|
11077
11720
|
}
|
|
11078
11721
|
if (reapply && !fresh) {
|
|
11079
|
-
if (!recorded.raw) raw = await (0,
|
|
11080
|
-
log(` reapply ${
|
|
11722
|
+
if (!recorded.raw) raw = await (0, import_promises12.readFile)(target);
|
|
11723
|
+
log(` reapply ${import_node_path19.default.relative(process.cwd(), target)}`);
|
|
11081
11724
|
} else {
|
|
11082
|
-
log(` changed ${
|
|
11725
|
+
log(` changed ${import_node_path19.default.relative(process.cwd(), target)} (upstream)`);
|
|
11083
11726
|
}
|
|
11084
11727
|
} else {
|
|
11085
11728
|
const superseded = (entry.supersededOutputs ?? []).find(
|
|
@@ -11100,14 +11743,14 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
11100
11743
|
}
|
|
11101
11744
|
if (outgoing && cacheDir) {
|
|
11102
11745
|
try {
|
|
11103
|
-
const oldBytes = await (0,
|
|
11746
|
+
const oldBytes = await (0, import_promises12.readFile)(target);
|
|
11104
11747
|
const oldMediaType = detectMediaType(oldBytes);
|
|
11105
11748
|
if (oldMediaType) await cacheMedia(cacheDir, oldBytes, oldMediaType, currentHash);
|
|
11106
11749
|
} catch {
|
|
11107
11750
|
}
|
|
11108
11751
|
}
|
|
11109
11752
|
log(
|
|
11110
|
-
` replace ${
|
|
11753
|
+
` replace ${import_node_path19.default.relative(process.cwd(), target)}` + (opts.force ? " (--force)" : " (previous tracked generation)")
|
|
11111
11754
|
);
|
|
11112
11755
|
}
|
|
11113
11756
|
}
|
|
@@ -11116,7 +11759,7 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
11116
11759
|
} else if (!raw && recorded && cacheDir) {
|
|
11117
11760
|
if (!reapply) final = await readCachedMedia(cacheDir, recorded.sha256, expectedMediaType);
|
|
11118
11761
|
if (!final && recordedRaw) raw = await readCachedMedia(cacheDir, recordedRaw, expectedMediaType);
|
|
11119
|
-
if (final || raw) log(` cached ${
|
|
11762
|
+
if (final || raw) log(` cached ${import_node_path19.default.relative(process.cwd(), target)}`);
|
|
11120
11763
|
}
|
|
11121
11764
|
if (!final && !raw) {
|
|
11122
11765
|
if (!source.url) {
|
|
@@ -11143,13 +11786,13 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
11143
11786
|
buf = processed.bytes;
|
|
11144
11787
|
if (processed.changed) {
|
|
11145
11788
|
rawHash = sha256(raw);
|
|
11146
|
-
log(` snapped ${
|
|
11789
|
+
log(` snapped ${import_node_path19.default.relative(process.cwd(), target)} to ${wanted.palette.colors.length} colours`);
|
|
11147
11790
|
}
|
|
11148
11791
|
}
|
|
11149
11792
|
if (cacheDir) await cacheMedia(cacheDir, buf, mediaType);
|
|
11150
|
-
await (0,
|
|
11793
|
+
await (0, import_promises12.mkdir)(import_node_path19.default.dirname(target), { recursive: true });
|
|
11151
11794
|
const tmp = `${target}.pixelkiln.tmp`;
|
|
11152
|
-
await (0,
|
|
11795
|
+
await (0, import_promises12.writeFile)(tmp, buf);
|
|
11153
11796
|
outputs.push({
|
|
11154
11797
|
path: portableOutputPath(target, spec.root),
|
|
11155
11798
|
sha256: sha256(buf),
|
|
@@ -11159,9 +11802,9 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
11159
11802
|
});
|
|
11160
11803
|
upsert(lock, key, { outputs: mergeOutputs(entry.outputs, outputs) });
|
|
11161
11804
|
await saveLock(lockPath, lock);
|
|
11162
|
-
await (0,
|
|
11805
|
+
await (0, import_promises12.rename)(tmp, target);
|
|
11163
11806
|
wrote = true;
|
|
11164
|
-
log(` wrote ${
|
|
11807
|
+
log(` wrote ${import_node_path19.default.relative(process.cwd(), target)}`);
|
|
11165
11808
|
}
|
|
11166
11809
|
if (opts.refresh && !wrote) {
|
|
11167
11810
|
if (rotated) {
|
|
@@ -11205,10 +11848,10 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
11205
11848
|
return result;
|
|
11206
11849
|
}
|
|
11207
11850
|
async function readCachedMedia(cacheDir, hash, mediaType) {
|
|
11208
|
-
const file =
|
|
11209
|
-
if (!(0,
|
|
11851
|
+
const file = import_node_path19.default.join(cacheDir, cacheFileName(hash, mediaType));
|
|
11852
|
+
if (!(0, import_node_fs15.existsSync)(file)) return null;
|
|
11210
11853
|
try {
|
|
11211
|
-
const buf = await (0,
|
|
11854
|
+
const buf = await (0, import_promises12.readFile)(file);
|
|
11212
11855
|
if (sha256(buf) !== hash) return null;
|
|
11213
11856
|
validateMedia(buf, mediaType);
|
|
11214
11857
|
return buf;
|
|
@@ -11219,17 +11862,17 @@ async function readCachedMedia(cacheDir, hash, mediaType) {
|
|
|
11219
11862
|
async function cacheMedia(cacheDir, buf, mediaType, knownHash) {
|
|
11220
11863
|
const hash = knownHash ?? sha256(buf);
|
|
11221
11864
|
validateMedia(buf, mediaType);
|
|
11222
|
-
const file =
|
|
11865
|
+
const file = import_node_path19.default.join(cacheDir, cacheFileName(hash, mediaType));
|
|
11223
11866
|
if (await readCachedMedia(cacheDir, hash, mediaType)) return;
|
|
11224
|
-
await (0,
|
|
11867
|
+
await (0, import_promises12.mkdir)(cacheDir, { recursive: true });
|
|
11225
11868
|
const tmp = `${file}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
11226
|
-
await (0,
|
|
11869
|
+
await (0, import_promises12.writeFile)(tmp, buf);
|
|
11227
11870
|
try {
|
|
11228
|
-
await (0,
|
|
11871
|
+
await (0, import_promises12.rename)(tmp, file);
|
|
11229
11872
|
} catch (err) {
|
|
11230
|
-
if (!(0,
|
|
11873
|
+
if (!(0, import_node_fs15.existsSync)(file)) throw err;
|
|
11231
11874
|
} finally {
|
|
11232
|
-
await (0,
|
|
11875
|
+
await (0, import_promises12.rm)(tmp, { force: true });
|
|
11233
11876
|
}
|
|
11234
11877
|
}
|
|
11235
11878
|
function mergeOutputs(previous, current) {
|
|
@@ -11346,10 +11989,10 @@ async function revertGeneration(provider, spec, lock, lockPath, opts) {
|
|
|
11346
11989
|
}
|
|
11347
11990
|
for (let i = 0; i < entry.outputs.length; i++) {
|
|
11348
11991
|
const file = currentEntryOutputPath(entry, spec, i);
|
|
11349
|
-
if (!(0,
|
|
11992
|
+
if (!(0, import_node_fs16.existsSync)(file)) continue;
|
|
11350
11993
|
if (await sha256File(file) !== entry.outputs[i].sha256 && !opts.force) {
|
|
11351
11994
|
throw new OverwriteRefusedError(
|
|
11352
|
-
`${
|
|
11995
|
+
`${import_node_path20.default.relative(process.cwd(), file)} was changed after download; keep it with pixelkiln edit, or pass --force to replace it`,
|
|
11353
11996
|
[file]
|
|
11354
11997
|
);
|
|
11355
11998
|
}
|
|
@@ -11401,11 +12044,11 @@ async function revertGeneration(provider, spec, lock, lockPath, opts) {
|
|
|
11401
12044
|
|
|
11402
12045
|
// src/pipeline/cache-health.ts
|
|
11403
12046
|
async function inspectCaches(lock, lockPath, options = {}) {
|
|
11404
|
-
if (options.prune && !(0,
|
|
11405
|
-
throw new Error(`Refusing to prune without an existing lockfile at ${
|
|
12047
|
+
if (options.prune && !(0, import_node_fs17.existsSync)(lockPath)) {
|
|
12048
|
+
throw new Error(`Refusing to prune without an existing lockfile at ${import_node_path21.default.resolve(lockPath)}`);
|
|
11406
12049
|
}
|
|
11407
|
-
const contentDir =
|
|
11408
|
-
const remotePath =
|
|
12050
|
+
const contentDir = import_node_path21.default.resolve(import_node_path21.default.dirname(lockPath), ".pixelkiln", "cache");
|
|
12051
|
+
const remotePath = import_node_path21.default.resolve(cachePathFor(lockPath));
|
|
11409
12052
|
const referenced = referencedHashes(lock);
|
|
11410
12053
|
let content = await inspectContentCache(contentDir, referenced);
|
|
11411
12054
|
let remoteHashes = await inspectRemoteHashCache(remotePath);
|
|
@@ -11417,7 +12060,7 @@ async function inspectCaches(lock, lockPath, options = {}) {
|
|
|
11417
12060
|
]);
|
|
11418
12061
|
for (const name of names) {
|
|
11419
12062
|
try {
|
|
11420
|
-
await (0,
|
|
12063
|
+
await (0, import_promises13.rm)(import_node_path21.default.join(contentDir, name), { force: true });
|
|
11421
12064
|
removed.contentFiles++;
|
|
11422
12065
|
} catch {
|
|
11423
12066
|
}
|
|
@@ -11426,7 +12069,7 @@ async function inspectCaches(lock, lockPath, options = {}) {
|
|
|
11426
12069
|
await saveCache(remotePath, { version: 1, hashes: {} });
|
|
11427
12070
|
removed.resetRemoteHashCache = true;
|
|
11428
12071
|
} else if (remoteHashes.invalidIds.length) {
|
|
11429
|
-
const cache = parseCache(JSON.parse(await (0,
|
|
12072
|
+
const cache = parseCache(JSON.parse(await (0, import_promises13.readFile)(remotePath, "utf8")));
|
|
11430
12073
|
for (const id of remoteHashes.invalidIds) delete cache.hashes[id];
|
|
11431
12074
|
removed.remoteHashEntries = remoteHashes.invalidIds.length;
|
|
11432
12075
|
await saveCache(remotePath, cache);
|
|
@@ -11444,7 +12087,7 @@ async function inspectCaches(lock, lockPath, options = {}) {
|
|
|
11444
12087
|
async function inspectContentCache(contentDir, referenced) {
|
|
11445
12088
|
const report = {
|
|
11446
12089
|
path: contentDir,
|
|
11447
|
-
exists: (0,
|
|
12090
|
+
exists: (0, import_node_fs17.existsSync)(contentDir),
|
|
11448
12091
|
files: 0,
|
|
11449
12092
|
bytes: 0,
|
|
11450
12093
|
valid: 0,
|
|
@@ -11458,19 +12101,19 @@ async function inspectContentCache(contentDir, referenced) {
|
|
|
11458
12101
|
return report;
|
|
11459
12102
|
}
|
|
11460
12103
|
const present = /* @__PURE__ */ new Set();
|
|
11461
|
-
const entries = await (0,
|
|
12104
|
+
const entries = await (0, import_promises13.readdir)(contentDir, { withFileTypes: true });
|
|
11462
12105
|
for (const entry of entries) {
|
|
11463
12106
|
if (!entry.isFile()) {
|
|
11464
12107
|
report.invalid.push({ name: entry.name, reason: "not a regular file" });
|
|
11465
12108
|
continue;
|
|
11466
12109
|
}
|
|
11467
12110
|
report.files++;
|
|
11468
|
-
const file =
|
|
12111
|
+
const file = import_node_path21.default.join(contentDir, entry.name);
|
|
11469
12112
|
const mediaType = mediaTypeFromExtension(entry.name);
|
|
11470
12113
|
const expected = mediaType ? entry.name.slice(0, -4) : "";
|
|
11471
12114
|
let bytes;
|
|
11472
12115
|
try {
|
|
11473
|
-
bytes = await (0,
|
|
12116
|
+
bytes = await (0, import_promises13.readFile)(file);
|
|
11474
12117
|
report.bytes += bytes.length;
|
|
11475
12118
|
} catch (err) {
|
|
11476
12119
|
report.invalid.push({
|
|
@@ -11508,7 +12151,7 @@ async function inspectContentCache(contentDir, referenced) {
|
|
|
11508
12151
|
async function inspectRemoteHashCache(remotePath) {
|
|
11509
12152
|
const report = {
|
|
11510
12153
|
path: remotePath,
|
|
11511
|
-
exists: (0,
|
|
12154
|
+
exists: (0, import_node_fs17.existsSync)(remotePath),
|
|
11512
12155
|
entries: 0,
|
|
11513
12156
|
valid: 0,
|
|
11514
12157
|
invalidIds: [],
|
|
@@ -11517,7 +12160,7 @@ async function inspectRemoteHashCache(remotePath) {
|
|
|
11517
12160
|
if (!report.exists) return report;
|
|
11518
12161
|
let cache;
|
|
11519
12162
|
try {
|
|
11520
|
-
cache = parseCache(JSON.parse(await (0,
|
|
12163
|
+
cache = parseCache(JSON.parse(await (0, import_promises13.readFile)(remotePath, "utf8")));
|
|
11521
12164
|
} catch (err) {
|
|
11522
12165
|
report.error = err instanceof Error ? err.message : String(err);
|
|
11523
12166
|
return report;
|
|
@@ -11532,20 +12175,20 @@ async function inspectRemoteHashCache(remotePath) {
|
|
|
11532
12175
|
}
|
|
11533
12176
|
|
|
11534
12177
|
// src/pipeline/pack.ts
|
|
11535
|
-
var
|
|
11536
|
-
var
|
|
12178
|
+
var import_node_fs18 = require("fs");
|
|
12179
|
+
var import_node_path22 = __toESM(require("path"), 1);
|
|
11537
12180
|
var DEFAULT_FRAME_SET_FPS = 12;
|
|
11538
12181
|
function resolvePackInputs(raw, inputsFilePath) {
|
|
11539
12182
|
if (!Array.isArray(raw) || !raw.length) {
|
|
11540
12183
|
throw new Error("--inputs must be a non-empty JSON array of { id, path }");
|
|
11541
12184
|
}
|
|
11542
|
-
const baseDir =
|
|
12185
|
+
const baseDir = import_node_path22.default.dirname(import_node_path22.default.resolve(inputsFilePath));
|
|
11543
12186
|
return raw.map((entry, i) => {
|
|
11544
12187
|
const e = entry;
|
|
11545
12188
|
if (typeof e.id !== "string" || typeof e.path !== "string") {
|
|
11546
12189
|
throw new Error(`--inputs[${i}] needs string "id" and "path"`);
|
|
11547
12190
|
}
|
|
11548
|
-
return { id: e.id, path:
|
|
12191
|
+
return { id: e.id, path: import_node_path22.default.resolve(baseDir, e.path) };
|
|
11549
12192
|
});
|
|
11550
12193
|
}
|
|
11551
12194
|
function packSprites(inputs, options = {}) {
|
|
@@ -11566,7 +12209,7 @@ function packSprites(inputs, options = {}) {
|
|
|
11566
12209
|
for (const input of ordered) {
|
|
11567
12210
|
let sourceHash = null;
|
|
11568
12211
|
try {
|
|
11569
|
-
const bytes = (0,
|
|
12212
|
+
const bytes = (0, import_node_fs18.readFileSync)(input.path);
|
|
11570
12213
|
sourceHash = sha256(bytes);
|
|
11571
12214
|
const png = decodePng(bytes);
|
|
11572
12215
|
sprites.push({ id: input.id, width: png.width, height: png.height, pixels: png.pixels });
|
|
@@ -11629,7 +12272,7 @@ function packStyle(lock, styleId, manifestDir, options = {}) {
|
|
|
11629
12272
|
}
|
|
11630
12273
|
const inputs2 = Object.entries(options.sourceOverrides).map(([id, source]) => ({
|
|
11631
12274
|
id,
|
|
11632
|
-
path:
|
|
12275
|
+
path: import_node_path22.default.resolve(source)
|
|
11633
12276
|
}));
|
|
11634
12277
|
if (!inputs2.length) throw new Error(`No approved quality outputs for style "${styleId}".`);
|
|
11635
12278
|
const packed2 = packSprites(inputs2, options);
|
|
@@ -11653,7 +12296,7 @@ function packStyle(lock, styleId, manifestDir, options = {}) {
|
|
|
11653
12296
|
let outputs = resolveEntryOutputs(entry, id, manifestDir);
|
|
11654
12297
|
const source = sources[id];
|
|
11655
12298
|
if (source && outputs.length <= 1) {
|
|
11656
|
-
inputs.push({ id, path:
|
|
12299
|
+
inputs.push({ id, path: import_node_path22.default.resolve(manifestDir, source) });
|
|
11657
12300
|
continue;
|
|
11658
12301
|
}
|
|
11659
12302
|
if (source && sourceIsStem(source, entry, manifestDir)) {
|
|
@@ -11687,7 +12330,7 @@ function packStyle(lock, styleId, manifestDir, options = {}) {
|
|
|
11687
12330
|
}
|
|
11688
12331
|
}
|
|
11689
12332
|
for (const [id, source] of Object.entries(sources)) {
|
|
11690
|
-
if (!locked.has(id)) inputs.push({ id, path:
|
|
12333
|
+
if (!locked.has(id)) inputs.push({ id, path: import_node_path22.default.resolve(manifestDir, source) });
|
|
11691
12334
|
}
|
|
11692
12335
|
if (!inputs.length) {
|
|
11693
12336
|
throw new Error(`No readable sprites for style "${styleId}"; ${noOutput.length} skipped.`);
|
|
@@ -11724,7 +12367,7 @@ function mountSprites(placements, options) {
|
|
|
11724
12367
|
for (const p of placements) {
|
|
11725
12368
|
let sourceHash = null;
|
|
11726
12369
|
try {
|
|
11727
|
-
const bytes = (0,
|
|
12370
|
+
const bytes = (0, import_node_fs18.readFileSync)(p.path);
|
|
11728
12371
|
sourceHash = sha256(bytes);
|
|
11729
12372
|
const png = decodePng(bytes);
|
|
11730
12373
|
if (png.width > cellWidth || png.height > cellHeight) {
|
|
@@ -11795,7 +12438,7 @@ function mountStyle(lock, styleId, manifestDir, mount, cells, sources = {}, outp
|
|
|
11795
12438
|
const source = sources[id];
|
|
11796
12439
|
const entry = lock.entries[`${styleId}/${id}`];
|
|
11797
12440
|
if (source && !(entry && sourceIsStem(source, entry, manifestDir))) {
|
|
11798
|
-
placements.push({ id, path:
|
|
12441
|
+
placements.push({ id, path: import_node_path22.default.resolve(manifestDir, source), cell });
|
|
11799
12442
|
continue;
|
|
11800
12443
|
}
|
|
11801
12444
|
if (source && entry) {
|
|
@@ -11828,7 +12471,7 @@ function mountStyle(lock, styleId, manifestDir, mount, cells, sources = {}, outp
|
|
|
11828
12471
|
`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}`
|
|
11829
12472
|
);
|
|
11830
12473
|
}
|
|
11831
|
-
const basePng = mount.base ? (0,
|
|
12474
|
+
const basePng = mount.base ? (0, import_node_fs18.readFileSync)(import_node_path22.default.resolve(manifestDir, mount.base)) : void 0;
|
|
11832
12475
|
const mounted = mountSprites(placements, {
|
|
11833
12476
|
cellWidth: mount.cellWidth,
|
|
11834
12477
|
cellHeight: mount.cellHeight,
|
|
@@ -11838,7 +12481,7 @@ function mountStyle(lock, styleId, manifestDir, mount, cells, sources = {}, outp
|
|
|
11838
12481
|
...mounted,
|
|
11839
12482
|
sources: mount.base ? [{
|
|
11840
12483
|
id: "$base",
|
|
11841
|
-
path:
|
|
12484
|
+
path: import_node_path22.default.resolve(manifestDir, mount.base),
|
|
11842
12485
|
sha256: sha256(basePng),
|
|
11843
12486
|
included: true
|
|
11844
12487
|
}, ...mounted.sources] : mounted.sources,
|
|
@@ -12616,9 +13259,9 @@ async function poll(provider, lock, lockPath, opts = {}) {
|
|
|
12616
13259
|
}
|
|
12617
13260
|
|
|
12618
13261
|
// src/pipeline/doctor.ts
|
|
12619
|
-
var
|
|
12620
|
-
var
|
|
12621
|
-
var
|
|
13262
|
+
var import_node_fs19 = require("fs");
|
|
13263
|
+
var import_promises14 = require("fs/promises");
|
|
13264
|
+
var import_node_path23 = __toESM(require("path"), 1);
|
|
12622
13265
|
async function doctor(loaded, specs, lock, lockPath, opts = {}) {
|
|
12623
13266
|
const checks = [];
|
|
12624
13267
|
const add = (id, level, message6) => checks.push({ id, level, message: message6 });
|
|
@@ -12629,14 +13272,14 @@ async function doctor(loaded, specs, lock, lockPath, opts = {}) {
|
|
|
12629
13272
|
);
|
|
12630
13273
|
add("lock", "ok", `${Object.keys(lock.entries).length} valid v2 lock entr(ies)`);
|
|
12631
13274
|
const dirs = [...new Set(specs.flatMap((spec) => [
|
|
12632
|
-
|
|
12633
|
-
...spec.quality ? [
|
|
13275
|
+
import_node_path23.default.dirname(spec.outFile),
|
|
13276
|
+
...spec.quality ? [import_node_path23.default.dirname(spec.quality.outFile)] : []
|
|
12634
13277
|
]))];
|
|
12635
13278
|
const unwritable = [];
|
|
12636
13279
|
for (const dir of dirs) {
|
|
12637
13280
|
const ancestor = await nearestExistingDirectory(dir);
|
|
12638
13281
|
try {
|
|
12639
|
-
await (0,
|
|
13282
|
+
await (0, import_promises14.access)(ancestor, import_node_fs19.constants.W_OK);
|
|
12640
13283
|
} catch {
|
|
12641
13284
|
unwritable.push(dir);
|
|
12642
13285
|
}
|
|
@@ -12653,7 +13296,7 @@ async function doctor(loaded, specs, lock, lockPath, opts = {}) {
|
|
|
12653
13296
|
for (let index = 0; index < entry.outputs.length; index++) {
|
|
12654
13297
|
const output = entry.outputs[index];
|
|
12655
13298
|
const spec = specByKey.get(key);
|
|
12656
|
-
const absolute = spec ? currentEntryOutputPath(entry, spec, index) : resolveOutputPath(output.path,
|
|
13299
|
+
const absolute = spec ? currentEntryOutputPath(entry, spec, index) : resolveOutputPath(output.path, import_node_path23.default.dirname(lockPath));
|
|
12657
13300
|
const owner = outputOwners.get(absolute);
|
|
12658
13301
|
if (owner && owner !== key) duplicateOutputs.push(`${output.path} (${owner}, ${key})`);
|
|
12659
13302
|
outputOwners.set(absolute, key);
|
|
@@ -12668,7 +13311,7 @@ async function doctor(loaded, specs, lock, lockPath, opts = {}) {
|
|
|
12668
13311
|
} else {
|
|
12669
13312
|
add("lock-outputs", "ok", `${outputOwners.size} recorded output path(s) are uniquely owned`);
|
|
12670
13313
|
}
|
|
12671
|
-
const cacheDir =
|
|
13314
|
+
const cacheDir = import_node_path23.default.join(import_node_path23.default.dirname(lockPath), ".pixelkiln", "cache");
|
|
12672
13315
|
const stranded = [];
|
|
12673
13316
|
const invalidState = [];
|
|
12674
13317
|
const staleMapUrls = [];
|
|
@@ -12684,11 +13327,11 @@ async function doctor(loaded, specs, lock, lockPath, opts = {}) {
|
|
|
12684
13327
|
const hasSource = Boolean(entry.sourceUrl || entry.sourceUrls?.length);
|
|
12685
13328
|
let hasCache = false;
|
|
12686
13329
|
for (const output of entry.outputs) {
|
|
12687
|
-
const cached =
|
|
13330
|
+
const cached = import_node_path23.default.join(
|
|
12688
13331
|
cacheDir,
|
|
12689
13332
|
cacheFileName(output.sha256, output.mediaType ?? MediaType.PNG)
|
|
12690
13333
|
);
|
|
12691
|
-
if ((0,
|
|
13334
|
+
if ((0, import_node_fs19.existsSync)(cached) && await sha256File(cached) === output.sha256) {
|
|
12692
13335
|
hasCache = true;
|
|
12693
13336
|
break;
|
|
12694
13337
|
}
|
|
@@ -12781,19 +13424,19 @@ async function nearestExistingDirectory(start) {
|
|
|
12781
13424
|
let current = start;
|
|
12782
13425
|
for (; ; ) {
|
|
12783
13426
|
try {
|
|
12784
|
-
if ((await (0,
|
|
13427
|
+
if ((await (0, import_promises14.stat)(current)).isDirectory()) return current;
|
|
12785
13428
|
} catch {
|
|
12786
13429
|
}
|
|
12787
|
-
const parent =
|
|
13430
|
+
const parent = import_node_path23.default.dirname(current);
|
|
12788
13431
|
if (parent === current) return current;
|
|
12789
13432
|
current = parent;
|
|
12790
13433
|
}
|
|
12791
13434
|
}
|
|
12792
13435
|
|
|
12793
13436
|
// src/pipeline/adopt.ts
|
|
12794
|
-
var
|
|
12795
|
-
var
|
|
12796
|
-
var
|
|
13437
|
+
var import_node_fs20 = require("fs");
|
|
13438
|
+
var import_promises15 = require("fs/promises");
|
|
13439
|
+
var import_node_path24 = __toESM(require("path"), 1);
|
|
12797
13440
|
async function adopt(provider, specs, lock, lockPath, opts = {}) {
|
|
12798
13441
|
const log = opts.onProgress ?? (() => {
|
|
12799
13442
|
});
|
|
@@ -12804,11 +13447,11 @@ async function adopt(provider, specs, lock, lockPath, opts = {}) {
|
|
|
12804
13447
|
const localByHash = /* @__PURE__ */ new Map();
|
|
12805
13448
|
const unmatchedLocal = [];
|
|
12806
13449
|
for (const spec of specs) {
|
|
12807
|
-
if (!(0,
|
|
13450
|
+
if (!(0, import_node_fs20.existsSync)(spec.outFile)) {
|
|
12808
13451
|
unmatchedLocal.push(`${spec.styleId}/${spec.assetId} (no file at ${spec.outFile})`);
|
|
12809
13452
|
continue;
|
|
12810
13453
|
}
|
|
12811
|
-
const bytes = await (0,
|
|
13454
|
+
const bytes = await (0, import_promises15.readFile)(spec.outFile);
|
|
12812
13455
|
try {
|
|
12813
13456
|
decodePng(bytes);
|
|
12814
13457
|
} catch (err) {
|
|
@@ -12926,13 +13569,13 @@ async function tagAdopted(provider, specs, lock, opts = {}) {
|
|
|
12926
13569
|
}
|
|
12927
13570
|
|
|
12928
13571
|
// src/pick/server.ts
|
|
12929
|
-
var
|
|
12930
|
-
var
|
|
13572
|
+
var import_node_fs22 = require("fs");
|
|
13573
|
+
var import_promises18 = require("fs/promises");
|
|
12931
13574
|
|
|
12932
13575
|
// src/pipeline/init.ts
|
|
12933
|
-
var
|
|
12934
|
-
var
|
|
12935
|
-
var
|
|
13576
|
+
var import_promises16 = require("fs/promises");
|
|
13577
|
+
var import_node_fs21 = require("fs");
|
|
13578
|
+
var import_node_path25 = __toESM(require("path"), 1);
|
|
12936
13579
|
function pngSize(buf) {
|
|
12937
13580
|
if (buf.length < 24) return null;
|
|
12938
13581
|
const isPng = buf.readUInt32BE(0) === 2303741511 && buf.readUInt32BE(4) === 218765834;
|
|
@@ -12945,8 +13588,8 @@ function slugify(value) {
|
|
|
12945
13588
|
}
|
|
12946
13589
|
async function walk(dir, exclude) {
|
|
12947
13590
|
const out = [];
|
|
12948
|
-
for (const entry of await (0,
|
|
12949
|
-
const full =
|
|
13591
|
+
for (const entry of await (0, import_promises16.readdir)(dir, { withFileTypes: true })) {
|
|
13592
|
+
const full = import_node_path25.default.join(dir, entry.name);
|
|
12950
13593
|
if (exclude.some((x) => full.includes(x))) continue;
|
|
12951
13594
|
if (entry.isDirectory()) out.push(...await walk(full, exclude));
|
|
12952
13595
|
else if (entry.name.toLowerCase().endsWith(".png")) out.push(full);
|
|
@@ -12960,14 +13603,14 @@ async function scanAssets(root, opts = {}) {
|
|
|
12960
13603
|
const skipped = [];
|
|
12961
13604
|
const seen = /* @__PURE__ */ new Map();
|
|
12962
13605
|
for (const file of files.sort()) {
|
|
12963
|
-
const size = pngSize(await (0,
|
|
13606
|
+
const size = pngSize(await (0, import_promises16.readFile)(file));
|
|
12964
13607
|
if (!size) {
|
|
12965
13608
|
skipped.push(`${file} (not a readable PNG)`);
|
|
12966
13609
|
continue;
|
|
12967
13610
|
}
|
|
12968
|
-
const rel =
|
|
12969
|
-
const category =
|
|
12970
|
-
let id = slugify(
|
|
13611
|
+
const rel = import_node_path25.default.relative(root, file);
|
|
13612
|
+
const category = import_node_path25.default.dirname(rel) === "." ? "" : import_node_path25.default.dirname(rel);
|
|
13613
|
+
let id = slugify(import_node_path25.default.basename(rel));
|
|
12971
13614
|
if (seen.has(id)) {
|
|
12972
13615
|
const n = seen.get(id) + 1;
|
|
12973
13616
|
seen.set(id, n);
|
|
@@ -13431,7 +14074,7 @@ document.getElementById('submit').onclick = async () => {
|
|
|
13431
14074
|
|
|
13432
14075
|
// src/pick/review-server.ts
|
|
13433
14076
|
var import_node_http = require("http");
|
|
13434
|
-
var
|
|
14077
|
+
var import_promises17 = require("fs/promises");
|
|
13435
14078
|
function serveReviewPage(opts) {
|
|
13436
14079
|
const log = opts.onProgress ?? (() => {
|
|
13437
14080
|
});
|
|
@@ -13440,7 +14083,7 @@ function serveReviewPage(opts) {
|
|
|
13440
14083
|
if (req.method === "GET" && req.url && opts.assets?.has(req.url)) {
|
|
13441
14084
|
try {
|
|
13442
14085
|
const asset = opts.assets.get(req.url);
|
|
13443
|
-
const bytes = await (0,
|
|
14086
|
+
const bytes = await (0, import_promises17.readFile)(asset.path);
|
|
13444
14087
|
res.writeHead(200, {
|
|
13445
14088
|
"Content-Type": asset.contentType,
|
|
13446
14089
|
"Cache-Control": "no-store",
|
|
@@ -13516,13 +14159,13 @@ async function currentArt(entry, spec) {
|
|
|
13516
14159
|
const previous = primaryOutput({ ...entry, outputs: entry.supersededOutputs });
|
|
13517
14160
|
if (!previous) return null;
|
|
13518
14161
|
const file = resolveOutputPath(previous.path, spec.root);
|
|
13519
|
-
if (!(0,
|
|
14162
|
+
if (!(0, import_node_fs22.existsSync)(file)) return null;
|
|
13520
14163
|
const contentType = previous.mediaType ?? mediaTypeFromExtension(file);
|
|
13521
14164
|
if (!contentType) return null;
|
|
13522
14165
|
let width = entry.width;
|
|
13523
14166
|
let height = entry.height;
|
|
13524
14167
|
if (contentType === "image/png") {
|
|
13525
|
-
const size = pngSize(await (0,
|
|
14168
|
+
const size = pngSize(await (0, import_promises18.readFile)(file));
|
|
13526
14169
|
if (size) ({ width, height } = size);
|
|
13527
14170
|
}
|
|
13528
14171
|
return { path: file, contentType, width, height };
|
|
@@ -13691,14 +14334,14 @@ async function runPicker(provider, lock, lockPath, opts = {}) {
|
|
|
13691
14334
|
|
|
13692
14335
|
// src/gallery/snapshot.ts
|
|
13693
14336
|
var import_node_crypto6 = require("crypto");
|
|
13694
|
-
var
|
|
13695
|
-
var
|
|
13696
|
-
var
|
|
14337
|
+
var import_node_fs25 = require("fs");
|
|
14338
|
+
var import_promises22 = require("fs/promises");
|
|
14339
|
+
var import_node_path30 = __toESM(require("path"), 1);
|
|
13697
14340
|
|
|
13698
14341
|
// src/workspace.ts
|
|
13699
|
-
var
|
|
13700
|
-
var
|
|
13701
|
-
var
|
|
14342
|
+
var import_promises19 = require("fs/promises");
|
|
14343
|
+
var import_node_fs23 = require("fs");
|
|
14344
|
+
var import_node_path26 = __toESM(require("path"), 1);
|
|
13702
14345
|
var import_zod6 = require("zod");
|
|
13703
14346
|
var WorkspaceProjectSchema = import_zod6.z.object({
|
|
13704
14347
|
id: import_zod6.z.string().min(1),
|
|
@@ -13723,10 +14366,10 @@ ${parsed.error.issues.slice(0, 5).map((i) => ` ${i.path.join(".")}: ${i.message
|
|
|
13723
14366
|
);
|
|
13724
14367
|
}
|
|
13725
14368
|
async function loadWorkspace(workspacePath) {
|
|
13726
|
-
if (!(0,
|
|
14369
|
+
if (!(0, import_node_fs23.existsSync)(workspacePath)) return { version: 1, projects: [] };
|
|
13727
14370
|
let raw;
|
|
13728
14371
|
try {
|
|
13729
|
-
raw = JSON.parse(await (0,
|
|
14372
|
+
raw = JSON.parse(await (0, import_promises19.readFile)(workspacePath, "utf8"));
|
|
13730
14373
|
} catch (err) {
|
|
13731
14374
|
throw new ProjectError(
|
|
13732
14375
|
`Workspace catalog at ${workspacePath} is malformed:
|
|
@@ -13740,22 +14383,22 @@ async function saveWorkspace(workspacePath, ws) {
|
|
|
13740
14383
|
version: 1,
|
|
13741
14384
|
projects: [...ws.projects].sort((a, b) => a.id.localeCompare(b.id))
|
|
13742
14385
|
};
|
|
13743
|
-
await (0,
|
|
14386
|
+
await (0, import_promises19.mkdir)(import_node_path26.default.dirname(import_node_path26.default.resolve(workspacePath)), { recursive: true });
|
|
13744
14387
|
const tmp = `${workspacePath}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
13745
14388
|
try {
|
|
13746
|
-
await (0,
|
|
13747
|
-
await (0,
|
|
14389
|
+
await (0, import_promises19.writeFile)(tmp, JSON.stringify(sorted, null, 2) + "\n");
|
|
14390
|
+
await (0, import_promises19.rename)(tmp, workspacePath);
|
|
13748
14391
|
} finally {
|
|
13749
|
-
await (0,
|
|
14392
|
+
await (0, import_promises19.rm)(tmp, { force: true });
|
|
13750
14393
|
}
|
|
13751
14394
|
}
|
|
13752
14395
|
function toPortablePath(dir, absolute) {
|
|
13753
|
-
return
|
|
14396
|
+
return import_node_path26.default.relative(dir, absolute).split(import_node_path26.default.sep).join("/");
|
|
13754
14397
|
}
|
|
13755
14398
|
function resolveProject(dir, project) {
|
|
13756
14399
|
return {
|
|
13757
|
-
manifestPath:
|
|
13758
|
-
lockPath:
|
|
14400
|
+
manifestPath: import_node_path26.default.resolve(dir, project.manifest.split("/").join(import_node_path26.default.sep)),
|
|
14401
|
+
lockPath: import_node_path26.default.resolve(dir, project.lock.split("/").join(import_node_path26.default.sep))
|
|
13759
14402
|
};
|
|
13760
14403
|
}
|
|
13761
14404
|
function validateWorkspace(ws, dir) {
|
|
@@ -13768,21 +14411,21 @@ function validateWorkspace(ws, dir) {
|
|
|
13768
14411
|
const { manifestPath, lockPath } = resolveProject(dir, project);
|
|
13769
14412
|
lockOwners.set(lockPath, [...lockOwners.get(lockPath) ?? [], project.id]);
|
|
13770
14413
|
manifestOwners.set(manifestPath, [...manifestOwners.get(manifestPath) ?? [], project.id]);
|
|
13771
|
-
if (
|
|
14414
|
+
if (import_node_path26.default.isAbsolute(project.manifest) || import_node_path26.default.isAbsolute(project.lock)) {
|
|
13772
14415
|
diagnostics.push({
|
|
13773
14416
|
id: "absolute-path",
|
|
13774
14417
|
level: "warning",
|
|
13775
14418
|
message: `project "${project.id}" stores an absolute path; the catalog will not resolve correctly if this tree is cloned or moved elsewhere`
|
|
13776
14419
|
});
|
|
13777
14420
|
}
|
|
13778
|
-
if (!(0,
|
|
14421
|
+
if (!(0, import_node_fs23.existsSync)(manifestPath)) {
|
|
13779
14422
|
diagnostics.push({
|
|
13780
14423
|
id: "missing-manifest",
|
|
13781
14424
|
level: "error",
|
|
13782
14425
|
message: `project "${project.id}" manifest not found: ${manifestPath}`
|
|
13783
14426
|
});
|
|
13784
14427
|
}
|
|
13785
|
-
if (!(0,
|
|
14428
|
+
if (!(0, import_node_fs23.existsSync)(lockPath)) {
|
|
13786
14429
|
diagnostics.push({
|
|
13787
14430
|
id: "missing-lock",
|
|
13788
14431
|
level: "error",
|
|
@@ -13829,12 +14472,12 @@ function validateWorkspace(ws, dir) {
|
|
|
13829
14472
|
}
|
|
13830
14473
|
|
|
13831
14474
|
// src/gallery/edit.ts
|
|
13832
|
-
var
|
|
14475
|
+
var import_node_path29 = __toESM(require("path"), 1);
|
|
13833
14476
|
var import_zod9 = require("zod");
|
|
13834
14477
|
|
|
13835
14478
|
// src/manifest-edit.ts
|
|
13836
|
-
var
|
|
13837
|
-
var
|
|
14479
|
+
var import_promises20 = require("fs/promises");
|
|
14480
|
+
var import_node_path27 = __toESM(require("path"), 1);
|
|
13838
14481
|
var import_zod7 = require("zod");
|
|
13839
14482
|
var HexSha = import_zod7.z.string().regex(/^[0-9a-f]{64}$/);
|
|
13840
14483
|
var AssetPatchSchema = import_zod7.z.object({
|
|
@@ -13990,11 +14633,21 @@ function assetStyles(raw, asset) {
|
|
|
13990
14633
|
const own = Array.isArray(asset.styles) ? asset.styles : [];
|
|
13991
14634
|
return own.length ? own : Object.keys(raw.styles ?? {});
|
|
13992
14635
|
}
|
|
14636
|
+
function declaredAssets(raw) {
|
|
14637
|
+
const expanded = expandLoopDirections(raw).raw;
|
|
14638
|
+
return new Set(Object.keys(expanded.assets ?? {}));
|
|
14639
|
+
}
|
|
14640
|
+
function undeclaredAsset(raw, assetId) {
|
|
14641
|
+
const shorthand = loopShorthandOf(assetId, expandLoopDirections(raw).families);
|
|
14642
|
+
return new ManifestEditError(
|
|
14643
|
+
shorthand ? `asset "${assetId}" is expanded from "${shorthand}"'s animation.directions; edit "${shorthand}" (it applies to every direction), or write this direction out as its own asset to change it alone` : `asset "${assetId}" is not declared by the manifest`
|
|
14644
|
+
);
|
|
14645
|
+
}
|
|
13993
14646
|
function applyEdit(raw, edit) {
|
|
13994
14647
|
raw.assets ??= {};
|
|
13995
14648
|
if (edit.action === "set-source" || edit.action === "clear-source") {
|
|
13996
14649
|
const asset2 = Object.hasOwn(raw.assets, edit.assetId) ? raw.assets[edit.assetId] : void 0;
|
|
13997
|
-
if (!asset2) throw
|
|
14650
|
+
if (!asset2) throw undeclaredAsset(raw, edit.assetId);
|
|
13998
14651
|
if (!raw.styles || !Object.hasOwn(raw.styles, edit.styleId)) {
|
|
13999
14652
|
throw new ManifestEditError(`unknown style "${edit.styleId}"`);
|
|
14000
14653
|
}
|
|
@@ -14046,7 +14699,7 @@ function applyEdit(raw, edit) {
|
|
|
14046
14699
|
return;
|
|
14047
14700
|
}
|
|
14048
14701
|
if (edit.action === "add-asset") {
|
|
14049
|
-
if (
|
|
14702
|
+
if (declaredAssets(raw).has(edit.assetId)) {
|
|
14050
14703
|
throw new ManifestEditError(`asset "${edit.assetId}" already exists`);
|
|
14051
14704
|
}
|
|
14052
14705
|
const { styles, tags, ...rest } = edit.asset;
|
|
@@ -14060,20 +14713,20 @@ function applyEdit(raw, edit) {
|
|
|
14060
14713
|
}
|
|
14061
14714
|
asset2.styles = styles;
|
|
14062
14715
|
}
|
|
14063
|
-
if (edit.asset.revision && !
|
|
14716
|
+
if (edit.asset.revision && !declaredAssets(raw).has(edit.asset.revision.from)) {
|
|
14064
14717
|
throw new ManifestEditError(`revision parent "${edit.asset.revision.from}" is not declared by the manifest`);
|
|
14065
14718
|
}
|
|
14066
|
-
if (edit.asset.state && !
|
|
14719
|
+
if (edit.asset.state && !declaredAssets(raw).has(edit.asset.state.of)) {
|
|
14067
14720
|
throw new ManifestEditError(`state parent "${edit.asset.state.of}" is not declared by the manifest`);
|
|
14068
14721
|
}
|
|
14069
|
-
if (edit.asset.animation && !
|
|
14722
|
+
if (edit.asset.animation && !declaredAssets(raw).has(edit.asset.animation.of)) {
|
|
14070
14723
|
throw new ManifestEditError(`animation parent "${edit.asset.animation.of}" is not declared by the manifest`);
|
|
14071
14724
|
}
|
|
14072
14725
|
raw.assets[edit.assetId] = asset2;
|
|
14073
14726
|
return;
|
|
14074
14727
|
}
|
|
14075
14728
|
const asset = Object.hasOwn(raw.assets, edit.assetId) ? raw.assets[edit.assetId] : void 0;
|
|
14076
|
-
if (!asset) throw
|
|
14729
|
+
if (!asset) throw undeclaredAsset(raw, edit.assetId);
|
|
14077
14730
|
const { patch } = edit;
|
|
14078
14731
|
if (patch.prompt !== void 0) asset.prompt = patch.prompt;
|
|
14079
14732
|
if (patch.promptForStyle) {
|
|
@@ -14097,8 +14750,8 @@ function applyEdit(raw, edit) {
|
|
|
14097
14750
|
}
|
|
14098
14751
|
}
|
|
14099
14752
|
async function applyManifestEdit(manifestPath, edit) {
|
|
14100
|
-
const absolute =
|
|
14101
|
-
const original = await (0,
|
|
14753
|
+
const absolute = import_node_path27.default.resolve(manifestPath);
|
|
14754
|
+
const original = await (0, import_promises20.readFile)(absolute, "utf8");
|
|
14102
14755
|
const before = sha256(original);
|
|
14103
14756
|
if (before !== edit.expectedSha256) throw new ManifestDriftError(absolute);
|
|
14104
14757
|
let raw;
|
|
@@ -14112,12 +14765,12 @@ async function applyManifestEdit(manifestPath, edit) {
|
|
|
14112
14765
|
applyEdit(raw, edit);
|
|
14113
14766
|
const next = serializeLike(original, raw);
|
|
14114
14767
|
if (next === original) return { manifestPath: absolute, sha256: before, changed: false };
|
|
14115
|
-
const tmp =
|
|
14116
|
-
|
|
14117
|
-
`.${
|
|
14768
|
+
const tmp = import_node_path27.default.join(
|
|
14769
|
+
import_node_path27.default.dirname(absolute),
|
|
14770
|
+
`.${import_node_path27.default.basename(absolute)}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`
|
|
14118
14771
|
);
|
|
14119
14772
|
try {
|
|
14120
|
-
await (0,
|
|
14773
|
+
await (0, import_promises20.writeFile)(tmp, next);
|
|
14121
14774
|
try {
|
|
14122
14775
|
const loaded = await loadManifest(tmp);
|
|
14123
14776
|
await resolveSpecs(loaded);
|
|
@@ -14125,26 +14778,26 @@ async function applyManifestEdit(manifestPath, edit) {
|
|
|
14125
14778
|
const message6 = error instanceof Error ? error.message : String(error);
|
|
14126
14779
|
throw new ManifestEditError(message6.replace(tmp, absolute));
|
|
14127
14780
|
}
|
|
14128
|
-
await (0,
|
|
14781
|
+
await (0, import_promises20.rename)(tmp, absolute);
|
|
14129
14782
|
} finally {
|
|
14130
|
-
await (0,
|
|
14783
|
+
await (0, import_promises20.rm)(tmp, { force: true });
|
|
14131
14784
|
}
|
|
14132
14785
|
return { manifestPath: absolute, sha256: sha256(next), changed: true };
|
|
14133
14786
|
}
|
|
14134
14787
|
|
|
14135
14788
|
// src/pipeline/hand-edit.ts
|
|
14136
|
-
var
|
|
14137
|
-
var
|
|
14138
|
-
var
|
|
14789
|
+
var import_node_fs24 = require("fs");
|
|
14790
|
+
var import_promises21 = require("fs/promises");
|
|
14791
|
+
var import_node_path28 = __toESM(require("path"), 1);
|
|
14139
14792
|
var import_zod8 = require("zod");
|
|
14140
14793
|
var HAND_EDIT_DIR = "edits";
|
|
14141
14794
|
function handEditPath(loaded, spec) {
|
|
14142
14795
|
const style = loaded.manifest.styles[spec.styleId];
|
|
14143
|
-
const outDir =
|
|
14144
|
-
const relative =
|
|
14145
|
-
const inside = relative && !relative.startsWith("..") && !
|
|
14146
|
-
const file = inside ? relative :
|
|
14147
|
-
return
|
|
14796
|
+
const outDir = import_node_path28.default.resolve(loaded.root, style?.outDir ?? ".");
|
|
14797
|
+
const relative = import_node_path28.default.relative(outDir, spec.outFile);
|
|
14798
|
+
const inside = relative && !relative.startsWith("..") && !import_node_path28.default.isAbsolute(relative);
|
|
14799
|
+
const file = inside ? relative : import_node_path28.default.basename(spec.outFile);
|
|
14800
|
+
return import_node_path28.default.join(outDir, HAND_EDIT_DIR, file.replace(/\.gif$/i, ".png"));
|
|
14148
14801
|
}
|
|
14149
14802
|
function handEditBases(spec, lock) {
|
|
14150
14803
|
const entry = lock.entries[lockKey(spec.styleId, spec.assetId)];
|
|
@@ -14157,10 +14810,10 @@ function handEditBases(spec, lock) {
|
|
|
14157
14810
|
}));
|
|
14158
14811
|
if (members.some((member) => member.mediaType && member.mediaType !== "image/png")) return null;
|
|
14159
14812
|
if (entry.outputs.length === 1 && !primaryOutput(entry)) return null;
|
|
14160
|
-
if (!members.every((member) => (0,
|
|
14813
|
+
if (!members.every((member) => (0, import_node_fs24.existsSync)(member.path))) return null;
|
|
14161
14814
|
return members.map(({ role, path: file }) => ({ role, path: file }));
|
|
14162
14815
|
}
|
|
14163
|
-
return (0,
|
|
14816
|
+
return (0, import_node_fs24.existsSync)(spec.outFile) ? [{ role: null, path: spec.outFile }] : null;
|
|
14164
14817
|
}
|
|
14165
14818
|
function handEditBase(spec, lock) {
|
|
14166
14819
|
const bases = handEditBases(spec, lock);
|
|
@@ -14190,19 +14843,19 @@ async function startHandEdit(loaded, lock, spec, opts = {}) {
|
|
|
14190
14843
|
`${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`
|
|
14191
14844
|
);
|
|
14192
14845
|
}
|
|
14193
|
-
const existing = spec.source ?
|
|
14846
|
+
const existing = spec.source ? import_node_path28.default.resolve(loaded.root, spec.source) : null;
|
|
14194
14847
|
const editPath = existing ?? handEditPath(loaded, spec);
|
|
14195
14848
|
const members = handEditMembers(loaded, spec, lock, editPath);
|
|
14196
14849
|
let created = false;
|
|
14197
14850
|
for (const member of members) {
|
|
14198
|
-
if ((0,
|
|
14851
|
+
if ((0, import_node_fs24.existsSync)(member.path)) continue;
|
|
14199
14852
|
if (!member.base) {
|
|
14200
14853
|
throw new ManifestEditError(
|
|
14201
14854
|
`${spec.styleId}/${spec.assetId} has no generated PNG on disk to start from; generate or restore it first`
|
|
14202
14855
|
);
|
|
14203
14856
|
}
|
|
14204
|
-
await (0,
|
|
14205
|
-
await (0,
|
|
14857
|
+
await (0, import_promises21.mkdir)(import_node_path28.default.dirname(member.path), { recursive: true });
|
|
14858
|
+
await (0, import_promises21.copyFile)(member.base, member.path);
|
|
14206
14859
|
created = true;
|
|
14207
14860
|
}
|
|
14208
14861
|
const source = portableOutputPath(editPath, loaded.root);
|
|
@@ -14280,7 +14933,7 @@ function handEditProjectPath(editPath) {
|
|
|
14280
14933
|
async function readHandEditCompanion(editPath) {
|
|
14281
14934
|
let text;
|
|
14282
14935
|
try {
|
|
14283
|
-
text = await (0,
|
|
14936
|
+
text = await (0, import_promises21.readFile)(handEditCompanionPath(editPath), "utf8");
|
|
14284
14937
|
} catch {
|
|
14285
14938
|
return null;
|
|
14286
14939
|
}
|
|
@@ -14323,7 +14976,7 @@ async function saveHandEdit(loaded, lock, spec, input) {
|
|
|
14323
14976
|
}
|
|
14324
14977
|
});
|
|
14325
14978
|
const bases = handEditBases(spec, lock);
|
|
14326
|
-
const expectedSize = bases?.[0] ? decodePng(await (0,
|
|
14979
|
+
const expectedSize = bases?.[0] ? decodePng(await (0, import_promises21.readFile)(bases[0].path)) : { width: spec.width, height: spec.height };
|
|
14327
14980
|
for (const [index, image] of decoded.entries()) {
|
|
14328
14981
|
if (image.width !== expectedSize.width || image.height !== expectedSize.height) {
|
|
14329
14982
|
const which = memberSet ? `member ${incoming[index].role ?? index}` : "the edit";
|
|
@@ -14339,7 +14992,7 @@ async function saveHandEdit(loaded, lock, spec, input) {
|
|
|
14339
14992
|
editor: input.editor,
|
|
14340
14993
|
protocol: input.protocol,
|
|
14341
14994
|
savedAt: (input.now ?? /* @__PURE__ */ new Date()).toISOString(),
|
|
14342
|
-
project: projectPath ?
|
|
14995
|
+
project: projectPath ? import_node_path28.default.basename(projectPath) : null,
|
|
14343
14996
|
outputs: incoming.map((frame, index) => ({
|
|
14344
14997
|
role: frame.role,
|
|
14345
14998
|
basedOn: basedOnHash(entry, index),
|
|
@@ -14369,10 +15022,10 @@ function basedOnHash(entry, index) {
|
|
|
14369
15022
|
async function replaceFile(file, bytes) {
|
|
14370
15023
|
const tmp = `${file}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
14371
15024
|
try {
|
|
14372
|
-
await (0,
|
|
14373
|
-
await (0,
|
|
15025
|
+
await (0, import_promises21.writeFile)(tmp, bytes);
|
|
15026
|
+
await (0, import_promises21.rename)(tmp, file);
|
|
14374
15027
|
} finally {
|
|
14375
|
-
await (0,
|
|
15028
|
+
await (0, import_promises21.rm)(tmp, { force: true });
|
|
14376
15029
|
}
|
|
14377
15030
|
}
|
|
14378
15031
|
function openInEditor(file, editor = process.env.PIXELKILN_EDITOR) {
|
|
@@ -14469,7 +15122,7 @@ function createGalleryEditHandler(opts) {
|
|
|
14469
15122
|
}
|
|
14470
15123
|
const result = await applyManifestEdit(manifestPath, parsed.data);
|
|
14471
15124
|
if (result.changed) {
|
|
14472
|
-
const relative =
|
|
15125
|
+
const relative = import_node_path29.default.relative(process.cwd(), result.manifestPath);
|
|
14473
15126
|
const shown = !relative || relative.startsWith("..") ? result.manifestPath : relative;
|
|
14474
15127
|
const subject = parsed.data.action === "patch-style" ? `style ${parsed.data.styleId}` : parsed.data.action === "set-source" || parsed.data.action === "clear-source" ? `${parsed.data.styleId}/${parsed.data.assetId} source` : parsed.data.assetId;
|
|
14475
15128
|
log(` manifest edited: ${parsed.data.action === "add-asset" ? "added" : "changed"} ${subject} in ${shown}`);
|
|
@@ -14481,7 +15134,7 @@ function createGalleryEditHandler(opts) {
|
|
|
14481
15134
|
// src/gallery/snapshot.ts
|
|
14482
15135
|
var PROJECT_FILE_TYPE = "application/octet-stream";
|
|
14483
15136
|
function galleryMediaId(absolutePath) {
|
|
14484
|
-
return (0, import_node_crypto6.createHash)("sha256").update(
|
|
15137
|
+
return (0, import_node_crypto6.createHash)("sha256").update(import_node_path30.default.resolve(absolutePath)).digest("hex").slice(0, 24);
|
|
14485
15138
|
}
|
|
14486
15139
|
function galleryMediaRoute(id) {
|
|
14487
15140
|
return `/media/${id}`;
|
|
@@ -14500,9 +15153,9 @@ function describeHistory(media, entry, cacheDir) {
|
|
|
14500
15153
|
return (entry.history ?? []).map((generation, i) => {
|
|
14501
15154
|
const outputs = generation.outputs.map((output) => {
|
|
14502
15155
|
const mediaType = output.mediaType ?? MediaType.PNG;
|
|
14503
|
-
const file =
|
|
15156
|
+
const file = import_node_path30.default.join(cacheDir, cacheFileName(output.sha256, mediaType));
|
|
14504
15157
|
let url = null;
|
|
14505
|
-
if ((0,
|
|
15158
|
+
if ((0, import_node_fs25.existsSync)(file)) {
|
|
14506
15159
|
const id = galleryMediaId(file);
|
|
14507
15160
|
media.set(id, { path: file, contentType: mediaType });
|
|
14508
15161
|
url = `${galleryMediaRoute(id)}?v=${output.sha256.slice(0, 24)}`;
|
|
@@ -14534,7 +15187,7 @@ function describeHistory(media, entry, cacheDir) {
|
|
|
14534
15187
|
var MAX_COMPARED_BYTES = 8 * 1024 * 1024;
|
|
14535
15188
|
async function samePixels(a, b) {
|
|
14536
15189
|
try {
|
|
14537
|
-
const [left, right] = await Promise.all([(0,
|
|
15190
|
+
const [left, right] = await Promise.all([(0, import_promises22.readFile)(a), (0, import_promises22.readFile)(b)]);
|
|
14538
15191
|
if (left.length > MAX_COMPARED_BYTES || right.length > MAX_COMPARED_BYTES) return false;
|
|
14539
15192
|
const x = decodePng(left);
|
|
14540
15193
|
const y = decodePng(right);
|
|
@@ -14546,6 +15199,7 @@ async function samePixels(a, b) {
|
|
|
14546
15199
|
var metadataFps = (entry) => entry ? frameSetFps(entry) : null;
|
|
14547
15200
|
function describeCharacter(spec, entry) {
|
|
14548
15201
|
const recordedCharacter = entry?.providerMetadata?.[entry.provider]?.character;
|
|
15202
|
+
if (spec?.revision) return null;
|
|
14549
15203
|
if (spec?.character) {
|
|
14550
15204
|
const character = spec.character;
|
|
14551
15205
|
return {
|
|
@@ -14599,7 +15253,7 @@ function describeCharacter(spec, entry) {
|
|
|
14599
15253
|
}
|
|
14600
15254
|
async function fileInfo(absolutePath) {
|
|
14601
15255
|
try {
|
|
14602
|
-
const info = await (0,
|
|
15256
|
+
const info = await (0, import_promises22.stat)(absolutePath);
|
|
14603
15257
|
return info.isFile() ? { bytes: info.size, modifiedAt: info.mtime.toISOString() } : null;
|
|
14604
15258
|
} catch {
|
|
14605
15259
|
return null;
|
|
@@ -14631,10 +15285,10 @@ async function describeOutput(media, root, absolutePath, recorded = {}) {
|
|
|
14631
15285
|
async function describeQuality(media, root, inspection) {
|
|
14632
15286
|
const outputPaths = inspection.outputs ?? [inspection.output];
|
|
14633
15287
|
const outputs = await Promise.all(
|
|
14634
|
-
outputPaths.map((file) => describeOutput(media, root,
|
|
15288
|
+
outputPaths.map((file) => describeOutput(media, root, import_node_path30.default.resolve(root, file)))
|
|
14635
15289
|
);
|
|
14636
|
-
const recordPath =
|
|
14637
|
-
const recordExists = (0,
|
|
15290
|
+
const recordPath = import_node_path30.default.resolve(root, inspection.record);
|
|
15291
|
+
const recordExists = (0, import_node_fs25.existsSync)(recordPath);
|
|
14638
15292
|
let options = null;
|
|
14639
15293
|
let check = null;
|
|
14640
15294
|
if (recordExists) {
|
|
@@ -14653,7 +15307,7 @@ async function describeQuality(media, root, inspection) {
|
|
|
14653
15307
|
return {
|
|
14654
15308
|
state: inspection.state,
|
|
14655
15309
|
reason: inspection.reason,
|
|
14656
|
-
output: portableOutputPath(
|
|
15310
|
+
output: portableOutputPath(import_node_path30.default.resolve(root, inspection.output), root),
|
|
14657
15311
|
record: portableOutputPath(recordPath, root),
|
|
14658
15312
|
recordExists,
|
|
14659
15313
|
outputs,
|
|
@@ -14686,13 +15340,14 @@ async function describeQuality(media, root, inspection) {
|
|
|
14686
15340
|
}
|
|
14687
15341
|
async function buildGallerySnapshot(opts) {
|
|
14688
15342
|
const { loaded, specs, lock } = opts;
|
|
15343
|
+
const filter = opts.filter?.assets?.length ? { ...opts.filter, assets: expandAssetFilter(opts.filter.assets, loaded.loopFamilies) } : opts.filter;
|
|
14689
15344
|
const root = loaded.root;
|
|
14690
15345
|
const media = /* @__PURE__ */ new Map();
|
|
14691
15346
|
const plan = await buildPlan(specs, lock);
|
|
14692
15347
|
const items = [];
|
|
14693
|
-
const manifestText = await (0,
|
|
15348
|
+
const manifestText = await (0, import_promises22.readFile)(loaded.path, "utf8");
|
|
14694
15349
|
const manifestSha256 = sha256(manifestText);
|
|
14695
|
-
const cacheDir =
|
|
15350
|
+
const cacheDir = import_node_path30.default.resolve(import_node_path30.default.dirname(opts.lockPath), ".pixelkiln", "cache");
|
|
14696
15351
|
let rawStyles = {};
|
|
14697
15352
|
try {
|
|
14698
15353
|
const raw = JSON.parse(manifestText);
|
|
@@ -14716,12 +15371,12 @@ async function buildGallerySnapshot(opts) {
|
|
|
14716
15371
|
)
|
|
14717
15372
|
);
|
|
14718
15373
|
if (spec.source) {
|
|
14719
|
-
const editPath =
|
|
15374
|
+
const editPath = import_node_path30.default.resolve(root, spec.source);
|
|
14720
15375
|
const memberCount = sourceIsStem(spec.source, entry, root) ? entry.outputs.length : 1;
|
|
14721
15376
|
const editShas = await Promise.all(
|
|
14722
15377
|
Array.from({ length: memberCount }, async (_, index) => {
|
|
14723
15378
|
const file = sourceOutputPath(spec.source, entry, index, root);
|
|
14724
|
-
return { file, sha256: (0,
|
|
15379
|
+
return { file, sha256: (0, import_node_fs25.existsSync)(file) ? await sha256File(file) : null };
|
|
14725
15380
|
})
|
|
14726
15381
|
);
|
|
14727
15382
|
edits = await Promise.all(editShas.map(({ file, sha256: editSha }) => describeOutput(media, root, file, { sha256: editSha })));
|
|
@@ -14729,7 +15384,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
14729
15384
|
const companion = await readHandEditCompanion(editPath);
|
|
14730
15385
|
if (companion) {
|
|
14731
15386
|
const projectPath = handEditProjectPath(editPath);
|
|
14732
|
-
const project = companion.project && (0,
|
|
15387
|
+
const project = companion.project && (0, import_node_fs25.existsSync)(projectPath) ? await fileInfo(projectPath) : null;
|
|
14733
15388
|
let projectUrl = null;
|
|
14734
15389
|
if (project) {
|
|
14735
15390
|
const id = galleryMediaId(projectPath);
|
|
@@ -14765,8 +15420,8 @@ async function buildGallerySnapshot(opts) {
|
|
|
14765
15420
|
editStatus = edits.some((member) => !member.exists) ? "missing" : editChanged.every((changed) => !changed) ? "same" : regenerated ? "regenerated-since" : "edited";
|
|
14766
15421
|
}
|
|
14767
15422
|
} else if (spec.source) {
|
|
14768
|
-
outputs = [await describeOutput(media, root,
|
|
14769
|
-
} else if (!entry && (0,
|
|
15423
|
+
outputs = [await describeOutput(media, root, import_node_path30.default.resolve(root, spec.source))];
|
|
15424
|
+
} else if (!entry && (0, import_node_fs25.existsSync)(spec.outFile)) {
|
|
14770
15425
|
outputs = [await describeOutput(media, root, spec.outFile)];
|
|
14771
15426
|
} else {
|
|
14772
15427
|
outputs = [];
|
|
@@ -14830,7 +15485,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
14830
15485
|
for (const [key, entry] of Object.entries(lock.entries)) {
|
|
14831
15486
|
if (shown.has(key)) continue;
|
|
14832
15487
|
if (isDeclared(loaded, entry.styleId, entry.assetId)) continue;
|
|
14833
|
-
if (!matchesFilter(
|
|
15488
|
+
if (!matchesFilter(filter, entry.styleId, entry.assetId)) continue;
|
|
14834
15489
|
const outputs = await Promise.all(
|
|
14835
15490
|
entry.outputs.map(
|
|
14836
15491
|
(output) => describeOutput(media, root, resolveOutputPath(output.path, root), output)
|
|
@@ -14959,7 +15614,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
14959
15614
|
id: loaded.manifest.name,
|
|
14960
15615
|
name: loaded.manifest.name,
|
|
14961
15616
|
manifest: loaded.path,
|
|
14962
|
-
lock:
|
|
15617
|
+
lock: import_node_path30.default.resolve(opts.lockPath),
|
|
14963
15618
|
root,
|
|
14964
15619
|
provider: loaded.manifest.provider,
|
|
14965
15620
|
account: null,
|
|
@@ -14985,7 +15640,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
14985
15640
|
return { snapshot, media };
|
|
14986
15641
|
}
|
|
14987
15642
|
async function buildWorkspaceGallerySnapshot(opts) {
|
|
14988
|
-
const dir =
|
|
15643
|
+
const dir = import_node_path30.default.dirname(import_node_path30.default.resolve(opts.workspacePath));
|
|
14989
15644
|
const media = /* @__PURE__ */ new Map();
|
|
14990
15645
|
const projects = [];
|
|
14991
15646
|
const items = [];
|
|
@@ -14996,7 +15651,7 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
14996
15651
|
try {
|
|
14997
15652
|
const loaded = await loadManifest(manifestPath);
|
|
14998
15653
|
const styleFilter = filter.styles.filter((id) => loaded.manifest.styles[id]);
|
|
14999
|
-
const assetFilter = filter.assets.filter((id) => loaded.manifest.assets[id]);
|
|
15654
|
+
const assetFilter = expandAssetFilter(filter.assets, loaded.loopFamilies).filter((id) => loaded.manifest.assets[id]);
|
|
15000
15655
|
const excluded = filter.styles.length > 0 && styleFilter.length === 0 || filter.assets.length > 0 && assetFilter.length === 0;
|
|
15001
15656
|
const lock = await loadLock(lockPath);
|
|
15002
15657
|
let projectItems = [];
|
|
@@ -15028,11 +15683,11 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
15028
15683
|
id: project.id,
|
|
15029
15684
|
name: loaded.manifest.name,
|
|
15030
15685
|
manifest: manifestPath,
|
|
15031
|
-
lock:
|
|
15686
|
+
lock: import_node_path30.default.resolve(lockPath),
|
|
15032
15687
|
root: loaded.root,
|
|
15033
15688
|
provider: loaded.manifest.provider,
|
|
15034
15689
|
account: project.account ?? null,
|
|
15035
|
-
manifestSha256: manifestSha256 ?? sha256(await (0,
|
|
15690
|
+
manifestSha256: manifestSha256 ?? sha256(await (0, import_promises22.readFile)(manifestPath, "utf8")),
|
|
15036
15691
|
historyLimit: historyLimit(loaded.manifest),
|
|
15037
15692
|
entries: Object.keys(lock.entries).length,
|
|
15038
15693
|
items: projectItems.length,
|
|
@@ -15044,8 +15699,8 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
15044
15699
|
id: project.id,
|
|
15045
15700
|
name: project.id,
|
|
15046
15701
|
manifest: manifestPath,
|
|
15047
|
-
lock:
|
|
15048
|
-
root:
|
|
15702
|
+
lock: import_node_path30.default.resolve(lockPath),
|
|
15703
|
+
root: import_node_path30.default.dirname(manifestPath),
|
|
15049
15704
|
provider: project.provider,
|
|
15050
15705
|
account: project.account ?? null,
|
|
15051
15706
|
manifestSha256: null,
|
|
@@ -15072,7 +15727,7 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
15072
15727
|
version: 1,
|
|
15073
15728
|
generatedAt: (opts.now ?? (() => /* @__PURE__ */ new Date()))().toISOString(),
|
|
15074
15729
|
project: null,
|
|
15075
|
-
workspace: { path:
|
|
15730
|
+
workspace: { path: import_node_path30.default.resolve(opts.workspacePath), dir, projects },
|
|
15076
15731
|
filter,
|
|
15077
15732
|
totals: {
|
|
15078
15733
|
entries: projects.reduce((sum, project) => sum + project.entries, 0),
|
|
@@ -15089,13 +15744,13 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
15089
15744
|
}
|
|
15090
15745
|
|
|
15091
15746
|
// src/gallery/page.ts
|
|
15092
|
-
var
|
|
15747
|
+
var import_node_fs26 = require("fs");
|
|
15093
15748
|
var CLIENT_DIR = new URL("./client/", importMetaUrl);
|
|
15094
15749
|
var client;
|
|
15095
15750
|
function clientAssets() {
|
|
15096
15751
|
client ??= {
|
|
15097
|
-
css: (0,
|
|
15098
|
-
js: (0,
|
|
15752
|
+
css: (0, import_node_fs26.readFileSync)(new URL("gallery.css", CLIENT_DIR), "utf8"),
|
|
15753
|
+
js: (0, import_node_fs26.readFileSync)(new URL("gallery.js", CLIENT_DIR), "utf8")
|
|
15099
15754
|
};
|
|
15100
15755
|
return client;
|
|
15101
15756
|
}
|
|
@@ -15221,10 +15876,10 @@ var EDITOR_RELEASE_BASE = "https://github.com/gfargo/pixelkiln/releases/download
|
|
|
15221
15876
|
|
|
15222
15877
|
// src/editor/install.ts
|
|
15223
15878
|
var import_node_crypto7 = require("crypto");
|
|
15224
|
-
var
|
|
15225
|
-
var
|
|
15879
|
+
var import_node_fs27 = require("fs");
|
|
15880
|
+
var import_promises23 = require("fs/promises");
|
|
15226
15881
|
var import_node_os3 = __toESM(require("os"), 1);
|
|
15227
|
-
var
|
|
15882
|
+
var import_node_path31 = __toESM(require("path"), 1);
|
|
15228
15883
|
var EditorInstallError = class extends Error {
|
|
15229
15884
|
constructor(message6, file) {
|
|
15230
15885
|
super(message6);
|
|
@@ -15235,15 +15890,15 @@ var EditorInstallError = class extends Error {
|
|
|
15235
15890
|
};
|
|
15236
15891
|
function editorToolsRoot() {
|
|
15237
15892
|
const override = process.env.PIXELKILN_TOOLS_DIR?.trim();
|
|
15238
|
-
if (override) return
|
|
15239
|
-
if (process.platform === "darwin") return
|
|
15893
|
+
if (override) return import_node_path31.default.resolve(override);
|
|
15894
|
+
if (process.platform === "darwin") return import_node_path31.default.join(import_node_os3.default.homedir(), "Library", "Caches", "pixelkiln", "tools");
|
|
15240
15895
|
if (process.platform === "win32") {
|
|
15241
|
-
return
|
|
15896
|
+
return import_node_path31.default.join(process.env.LOCALAPPDATA ?? import_node_path31.default.join(import_node_os3.default.homedir(), "AppData", "Local"), "pixelkiln", "tools");
|
|
15242
15897
|
}
|
|
15243
|
-
return
|
|
15898
|
+
return import_node_path31.default.join(process.env.XDG_CACHE_HOME ?? import_node_path31.default.join(import_node_os3.default.homedir(), ".cache"), "pixelkiln", "tools");
|
|
15244
15899
|
}
|
|
15245
15900
|
function editorDir(pin = EDITOR_PIN, root = editorToolsRoot()) {
|
|
15246
|
-
return
|
|
15901
|
+
return import_node_path31.default.join(root, "pixelorama", pin.release ?? "unpublished");
|
|
15247
15902
|
}
|
|
15248
15903
|
function editorBaseUrl(pin = EDITOR_PIN) {
|
|
15249
15904
|
const override = process.env.PIXELKILN_EDITOR_URL?.trim();
|
|
@@ -15253,7 +15908,7 @@ function editorBaseUrl(pin = EDITOR_PIN) {
|
|
|
15253
15908
|
}
|
|
15254
15909
|
async function sha256Of(file) {
|
|
15255
15910
|
try {
|
|
15256
|
-
return (0, import_node_crypto7.createHash)("sha256").update(await (0,
|
|
15911
|
+
return (0, import_node_crypto7.createHash)("sha256").update(await (0, import_promises23.readFile)(file)).digest("hex");
|
|
15257
15912
|
} catch {
|
|
15258
15913
|
return null;
|
|
15259
15914
|
}
|
|
@@ -15266,8 +15921,8 @@ async function editorStatus(opts = {}) {
|
|
|
15266
15921
|
let totalBytes = 0;
|
|
15267
15922
|
for (const [name, expected] of Object.entries(pin.files)) {
|
|
15268
15923
|
totalBytes += expected.bytes;
|
|
15269
|
-
const file =
|
|
15270
|
-
const info = (0,
|
|
15924
|
+
const file = import_node_path31.default.join(dir, name);
|
|
15925
|
+
const info = (0, import_node_fs27.existsSync)(file) ? await (0, import_promises23.stat)(file).catch(() => null) : null;
|
|
15271
15926
|
if (info && info.size === expected.bytes && await sha256Of(file) === expected.sha256) {
|
|
15272
15927
|
installedBytes += expected.bytes;
|
|
15273
15928
|
} else {
|
|
@@ -15296,7 +15951,7 @@ async function installEditor(opts = {}) {
|
|
|
15296
15951
|
);
|
|
15297
15952
|
}
|
|
15298
15953
|
if (!baseUrl) throw new EditorInstallError("no download location for the editor build");
|
|
15299
|
-
await (0,
|
|
15954
|
+
await (0, import_promises23.mkdir)(dir, { recursive: true });
|
|
15300
15955
|
const downloaded = [];
|
|
15301
15956
|
const skipped = Object.keys(pin.files).filter((name) => !before.missing.includes(name));
|
|
15302
15957
|
let fetchedBytes = before.installedBytes;
|
|
@@ -15324,13 +15979,13 @@ async function installEditor(opts = {}) {
|
|
|
15324
15979
|
if (digest2 !== expected.sha256) {
|
|
15325
15980
|
throw new EditorInstallError(`${name}: sha256 ${digest2.slice(0, 12)}\u2026 does not match the pinned ${expected.sha256.slice(0, 12)}\u2026`, name);
|
|
15326
15981
|
}
|
|
15327
|
-
const final =
|
|
15982
|
+
const final = import_node_path31.default.join(dir, name);
|
|
15328
15983
|
const tmp = `${final}.${process.pid}.part`;
|
|
15329
15984
|
try {
|
|
15330
|
-
await (0,
|
|
15331
|
-
await (0,
|
|
15985
|
+
await (0, import_promises23.writeFile)(tmp, bytes);
|
|
15986
|
+
await (0, import_promises23.rename)(tmp, final);
|
|
15332
15987
|
} finally {
|
|
15333
|
-
await (0,
|
|
15988
|
+
await (0, import_promises23.rm)(tmp, { force: true });
|
|
15334
15989
|
}
|
|
15335
15990
|
fetchedBytes += bytes.length;
|
|
15336
15991
|
downloaded.push(name);
|
|
@@ -15370,12 +16025,12 @@ var EDITOR_CONTENT_TYPES = {
|
|
|
15370
16025
|
".json": "application/json; charset=utf-8"
|
|
15371
16026
|
};
|
|
15372
16027
|
function editorContentType(name) {
|
|
15373
|
-
return EDITOR_CONTENT_TYPES[
|
|
16028
|
+
return EDITOR_CONTENT_TYPES[import_node_path31.default.extname(name).toLowerCase()] ?? null;
|
|
15374
16029
|
}
|
|
15375
16030
|
|
|
15376
16031
|
// src/gallery/editor.ts
|
|
15377
|
-
var
|
|
15378
|
-
var
|
|
16032
|
+
var import_promises24 = require("fs/promises");
|
|
16033
|
+
var import_node_path32 = __toESM(require("path"), 1);
|
|
15379
16034
|
function editorRoute(release, name = "index.html") {
|
|
15380
16035
|
return `/editor/${encodeURIComponent(release)}/${encodeURIComponent(name)}`;
|
|
15381
16036
|
}
|
|
@@ -15448,7 +16103,7 @@ function createGalleryEditorHandlers(opts = {}) {
|
|
|
15448
16103
|
if (!status.installed) return null;
|
|
15449
16104
|
let bytes;
|
|
15450
16105
|
try {
|
|
15451
|
-
bytes = await (0,
|
|
16106
|
+
bytes = await (0, import_promises24.readFile)(import_node_path32.default.join(dir, name));
|
|
15452
16107
|
} catch {
|
|
15453
16108
|
verified = null;
|
|
15454
16109
|
return null;
|
|
@@ -15465,7 +16120,7 @@ function createGalleryEditorHandlers(opts = {}) {
|
|
|
15465
16120
|
// src/gallery/server.ts
|
|
15466
16121
|
var import_node_crypto8 = require("crypto");
|
|
15467
16122
|
var import_node_http2 = require("http");
|
|
15468
|
-
var
|
|
16123
|
+
var import_promises25 = require("fs/promises");
|
|
15469
16124
|
var MAX_EDIT_BYTES = 48 * 1024 * 1024;
|
|
15470
16125
|
var EDITOR_CSP = [
|
|
15471
16126
|
"default-src 'self'",
|
|
@@ -15624,7 +16279,7 @@ async function serveGallery(opts) {
|
|
|
15624
16279
|
const asset = reviewAssets.get(jobId)?.get(url2.pathname);
|
|
15625
16280
|
if (!asset) return fail(404, "no such review asset");
|
|
15626
16281
|
try {
|
|
15627
|
-
const bytes = await (0,
|
|
16282
|
+
const bytes = await (0, import_promises25.readFile)(asset.path);
|
|
15628
16283
|
res.writeHead(200, {
|
|
15629
16284
|
"Content-Type": asset.contentType,
|
|
15630
16285
|
"Cache-Control": "no-store",
|
|
@@ -15700,7 +16355,7 @@ async function serveGallery(opts) {
|
|
|
15700
16355
|
if (!asset) return fail(404, "no such gallery media");
|
|
15701
16356
|
let bytes;
|
|
15702
16357
|
try {
|
|
15703
|
-
bytes = await (0,
|
|
16358
|
+
bytes = await (0, import_promises25.readFile)(asset.path);
|
|
15704
16359
|
} catch {
|
|
15705
16360
|
return fail(404, "gallery media is no longer on disk; refresh the page");
|
|
15706
16361
|
}
|
|
@@ -16074,19 +16729,19 @@ function createGenerateHandlers(opts) {
|
|
|
16074
16729
|
}
|
|
16075
16730
|
|
|
16076
16731
|
// src/pipeline/salvage.ts
|
|
16077
|
-
var
|
|
16078
|
-
var
|
|
16079
|
-
var
|
|
16732
|
+
var import_promises26 = require("fs/promises");
|
|
16733
|
+
var import_node_fs28 = require("fs");
|
|
16734
|
+
var import_node_path33 = __toESM(require("path"), 1);
|
|
16080
16735
|
function providerClaimId(provider, objectId) {
|
|
16081
16736
|
return `${encodeURIComponent(provider)}:${encodeURIComponent(objectId)}`;
|
|
16082
16737
|
}
|
|
16083
16738
|
async function loadClaims(lockPaths, opts = {}) {
|
|
16084
16739
|
const claimed = /* @__PURE__ */ new Set();
|
|
16085
16740
|
for (const p of lockPaths) {
|
|
16086
|
-
if (!(0,
|
|
16741
|
+
if (!(0, import_node_fs28.existsSync)(p)) throw new Error(`Claim lockfile not found: ${p}`);
|
|
16087
16742
|
let parsed;
|
|
16088
16743
|
try {
|
|
16089
|
-
parsed = parseLock(JSON.parse(await (0,
|
|
16744
|
+
parsed = parseLock(JSON.parse(await (0, import_promises26.readFile)(p, "utf8")));
|
|
16090
16745
|
} catch {
|
|
16091
16746
|
throw new Error(`Claim lockfile is malformed: ${p}`);
|
|
16092
16747
|
}
|
|
@@ -16139,20 +16794,20 @@ function matchOrphanStyle(prompt, manifest) {
|
|
|
16139
16794
|
return matchStyleByPattern(prompt, manifest);
|
|
16140
16795
|
}
|
|
16141
16796
|
async function loadSiblingManifests(ownManifestPath, workspaceManifestPaths, claimPaths) {
|
|
16142
|
-
const own =
|
|
16797
|
+
const own = import_node_path33.default.resolve(ownManifestPath);
|
|
16143
16798
|
const siblingManifestPaths = [
|
|
16144
16799
|
.../* @__PURE__ */ new Set([
|
|
16145
16800
|
...workspaceManifestPaths,
|
|
16146
|
-
...claimPaths.map((c) =>
|
|
16801
|
+
...claimPaths.map((c) => import_node_path33.default.join(import_node_path33.default.dirname(import_node_path33.default.resolve(c)), "pixelkiln.manifest.json"))
|
|
16147
16802
|
])
|
|
16148
16803
|
];
|
|
16149
16804
|
const siblings = [];
|
|
16150
16805
|
for (const siblingManifestPath of siblingManifestPaths) {
|
|
16151
|
-
if (
|
|
16152
|
-
if (!(0,
|
|
16806
|
+
if (import_node_path33.default.resolve(siblingManifestPath) === own) continue;
|
|
16807
|
+
if (!(0, import_node_fs28.existsSync)(siblingManifestPath)) continue;
|
|
16153
16808
|
try {
|
|
16154
16809
|
const { manifest } = await loadManifest(siblingManifestPath);
|
|
16155
|
-
siblings.push({ label:
|
|
16810
|
+
siblings.push({ label: import_node_path33.default.basename(import_node_path33.default.dirname(siblingManifestPath)), manifest });
|
|
16156
16811
|
} catch {
|
|
16157
16812
|
}
|
|
16158
16813
|
}
|
|
@@ -16268,8 +16923,8 @@ async function applyTags(provider, decisions, existing, opts = {}) {
|
|
|
16268
16923
|
}
|
|
16269
16924
|
|
|
16270
16925
|
// src/pick/salvage-server.ts
|
|
16271
|
-
var
|
|
16272
|
-
var
|
|
16926
|
+
var import_promises27 = require("fs/promises");
|
|
16927
|
+
var import_node_path34 = __toESM(require("path"), 1);
|
|
16273
16928
|
|
|
16274
16929
|
// src/pick/salvage-sheet.ts
|
|
16275
16930
|
var escapeHtml3 = (s) => s.replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
@@ -16434,7 +17089,7 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
16434
17089
|
});
|
|
16435
17090
|
const html = renderSalvageSheet(orphans, {
|
|
16436
17091
|
styleId: ctx.styleId,
|
|
16437
|
-
importDir:
|
|
17092
|
+
importDir: import_node_path34.default.relative(process.cwd(), ctx.importDir) || "."
|
|
16438
17093
|
});
|
|
16439
17094
|
const byId = new Map(orphans.map((o) => [o.id, o]));
|
|
16440
17095
|
const existingTags = new Map(orphans.map((o) => [o.id, o.tags]));
|
|
@@ -16463,10 +17118,10 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
16463
17118
|
if (!buf.subarray(0, 8).equals(PNG_SIGNATURE2)) throw new Error("not a PNG");
|
|
16464
17119
|
decodePng(buf);
|
|
16465
17120
|
const assetId = idFromPrompt(orphan.prompt, taken);
|
|
16466
|
-
const rel =
|
|
16467
|
-
const outFile =
|
|
16468
|
-
await (0,
|
|
16469
|
-
await (0,
|
|
17121
|
+
const rel = import_node_path34.default.join("_salvaged", `${assetId}.png`);
|
|
17122
|
+
const outFile = import_node_path34.default.resolve(ctx.importDir, rel);
|
|
17123
|
+
await (0, import_promises27.mkdir)(import_node_path34.default.dirname(outFile), { recursive: true });
|
|
17124
|
+
await (0, import_promises27.writeFile)(outFile, buf);
|
|
16470
17125
|
ctx.manifest.assets[assetId] = {
|
|
16471
17126
|
prompt: orphan.prompt,
|
|
16472
17127
|
promptByStyle: {},
|
|
@@ -16498,7 +17153,7 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
16498
17153
|
sourceUrl: durableSource,
|
|
16499
17154
|
sourceUrls: durableSource ? [{ url: durableSource }] : [],
|
|
16500
17155
|
outputs: [{
|
|
16501
|
-
path: portableOutputPath(outFile,
|
|
17156
|
+
path: portableOutputPath(outFile, import_node_path34.default.dirname(ctx.manifestPath)),
|
|
16502
17157
|
sha256: sha256(buf)
|
|
16503
17158
|
}],
|
|
16504
17159
|
submittedAt: orphan.createdAt,
|
|
@@ -16522,9 +17177,9 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
16522
17177
|
}
|
|
16523
17178
|
}
|
|
16524
17179
|
await applyTags(provider, decisions, existingTags, { onProgress: log });
|
|
16525
|
-
const raw = JSON.parse(await (0,
|
|
17180
|
+
const raw = JSON.parse(await (0, import_promises27.readFile)(ctx.manifestPath, "utf8"));
|
|
16526
17181
|
for (const id of importedAssetIds) raw.assets[id] = ctx.manifest.assets[id];
|
|
16527
|
-
await (0,
|
|
17182
|
+
await (0, import_promises27.writeFile)(ctx.manifestPath, JSON.stringify(raw, null, 2) + "\n");
|
|
16528
17183
|
await saveLock(ctx.lockPath, ctx.lock);
|
|
16529
17184
|
return result;
|
|
16530
17185
|
}
|
|
@@ -16654,6 +17309,10 @@ async function workspaceStatus(ws, dir) {
|
|
|
16654
17309
|
EXIT_CODES,
|
|
16655
17310
|
EditorInstallError,
|
|
16656
17311
|
FAKE_PNG,
|
|
17312
|
+
FONT_COST_GENERATIONS,
|
|
17313
|
+
FONT_GLYPH_SIZES,
|
|
17314
|
+
FONT_WEIGHTS,
|
|
17315
|
+
FRAME_SET_REVISION_MODES,
|
|
16657
17316
|
FakeProvider,
|
|
16658
17317
|
GenerateRequestError,
|
|
16659
17318
|
GenerateRequestSchema,
|
|
@@ -16670,6 +17329,7 @@ async function workspaceStatus(ws, dir) {
|
|
|
16670
17329
|
MAX_DOWNLOAD_BYTES,
|
|
16671
17330
|
MAX_HAND_EDIT_BYTES,
|
|
16672
17331
|
MAX_RETRIES,
|
|
17332
|
+
MEMBER_SET_REVISION_MODES,
|
|
16673
17333
|
MIRRORED_DIRECTION,
|
|
16674
17334
|
ManifestDriftError,
|
|
16675
17335
|
ManifestEditError,
|
|
@@ -16699,6 +17359,8 @@ async function workspaceStatus(ws, dir) {
|
|
|
16699
17359
|
ScenarioProvider,
|
|
16700
17360
|
StyleInputSchema,
|
|
16701
17361
|
StyleSchema,
|
|
17362
|
+
UNZOOM_MAX_AREA,
|
|
17363
|
+
UNZOOM_MIN_SIDE,
|
|
16702
17364
|
UiElementSchema,
|
|
16703
17365
|
UiPieceSchema,
|
|
16704
17366
|
UnsupportedCapabilityError,
|
|
@@ -16733,6 +17395,7 @@ async function workspaceStatus(ws, dir) {
|
|
|
16733
17395
|
currentEntryOutputPath,
|
|
16734
17396
|
currentOutputPath,
|
|
16735
17397
|
defaultOpenCommand,
|
|
17398
|
+
defaultUnzoomOut,
|
|
16736
17399
|
detachHandEdit,
|
|
16737
17400
|
detectMediaType,
|
|
16738
17401
|
doctor,
|
|
@@ -16750,10 +17413,12 @@ async function workspaceStatus(ws, dir) {
|
|
|
16750
17413
|
fetchAssets,
|
|
16751
17414
|
fetchWithRetry,
|
|
16752
17415
|
findOrphans,
|
|
17416
|
+
fontOutputPaths,
|
|
16753
17417
|
formatCost,
|
|
16754
17418
|
frameSetFps,
|
|
16755
17419
|
galleryMediaId,
|
|
16756
17420
|
galleryMediaRoute,
|
|
17421
|
+
generateFont,
|
|
16757
17422
|
generationCost,
|
|
16758
17423
|
groupOrphansByStyle,
|
|
16759
17424
|
handEditBase,
|
|
@@ -16889,6 +17554,7 @@ async function workspaceStatus(ws, dir) {
|
|
|
16889
17554
|
tilesCost,
|
|
16890
17555
|
toPortablePath,
|
|
16891
17556
|
totalSpend,
|
|
17557
|
+
unzoomFile,
|
|
16892
17558
|
upsert,
|
|
16893
17559
|
validateCostEstimate,
|
|
16894
17560
|
validateMedia,
|