pixelkiln 0.87.0 → 0.88.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/PROVIDERS.md +2 -1
- package/README.md +4 -4
- package/dist/cli.js +1247 -593
- package/dist/cli.js.map +1 -1
- package/dist/client/gallery.js +1 -1
- package/dist/index.cjs +1257 -647
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +786 -101
- package/dist/index.d.ts +786 -101
- package/dist/index.js +1161 -574
- package/dist/index.js.map +1 -1
- package/docs/CLI.md +69 -5
- package/docs/ENDPOINTS.md +63 -9
- package/docs/GENERATORS.md +72 -2
- package/docs/MANIFEST.md +3 -2
- package/docs/PIXELLAB.md +1 -0
- package/package.json +1 -1
- package/schema/manifest.schema.json +47 -2
- package/schema/recipe.schema.json +2 -1
- package/skills/pixelkiln/SKILL.md +8 -3
- package/skills/pixelkiln/references/pixellab-roadmap.md +16 -15
- package/skills/pixelkiln/references/pixellab.md +11 -3
package/dist/index.cjs
CHANGED
|
@@ -32,6 +32,7 @@ var index_exports = {};
|
|
|
32
32
|
__export(index_exports, {
|
|
33
33
|
AssetBatchSchema: () => AssetBatchSchema,
|
|
34
34
|
AssetSchema: () => AssetSchema,
|
|
35
|
+
AssetVfxSchema: () => AssetVfxSchema,
|
|
35
36
|
BudgetError: () => BudgetError,
|
|
36
37
|
CHARACTER_DIRECTIONS_4: () => CHARACTER_DIRECTIONS_4,
|
|
37
38
|
CHARACTER_DIRECTIONS_8: () => CHARACTER_DIRECTIONS_8,
|
|
@@ -88,12 +89,14 @@ __export(index_exports, {
|
|
|
88
89
|
OverwriteRefusedError: () => OverwriteRefusedError,
|
|
89
90
|
PIXELATE_COST_GENERATIONS: () => PIXELATE_COST_GENERATIONS,
|
|
90
91
|
PIXELATE_MAX_SIDE: () => PIXELATE_MAX_SIDE,
|
|
92
|
+
PIXELLAB_MCP_URL: () => PIXELLAB_MCP_URL,
|
|
91
93
|
PIXEL_ART_FIXER_REVISION: () => PIXEL_ART_FIXER_REVISION,
|
|
92
94
|
PIXEL_ART_FIXER_URL: () => PIXEL_ART_FIXER_URL,
|
|
93
95
|
PRO_TIER_PRICES: () => PRO_TIER_PRICES,
|
|
94
96
|
PixelKilnError: () => PixelKilnError,
|
|
95
97
|
PixelLabClient: () => PixelLabClient,
|
|
96
98
|
PixelLabError: () => PixelLabError,
|
|
99
|
+
PixelLabMcpClient: () => PixelLabMcpClient,
|
|
97
100
|
PixelLabProvider: () => PixelLabProvider,
|
|
98
101
|
PostprocessSchema: () => PostprocessSchema,
|
|
99
102
|
ProjectError: () => ProjectError,
|
|
@@ -116,12 +119,15 @@ __export(index_exports, {
|
|
|
116
119
|
UiPieceSchema: () => UiPieceSchema,
|
|
117
120
|
UnsupportedCapabilityError: () => UnsupportedCapabilityError,
|
|
118
121
|
UsageError: () => UsageError,
|
|
122
|
+
VFX_TOOL: () => VFX_TOOL,
|
|
123
|
+
VfxPartSchema: () => VfxPartSchema,
|
|
119
124
|
WorkspaceProjectSchema: () => WorkspaceProjectSchema,
|
|
120
125
|
WorkspaceSchema: () => WorkspaceSchema,
|
|
121
126
|
adopt: () => adopt,
|
|
122
127
|
applyManifestEdit: () => applyManifestEdit,
|
|
123
128
|
applyTags: () => applyTags,
|
|
124
129
|
approveQualityRecord: () => approveQualityRecord,
|
|
130
|
+
assetInStyle: () => assetInStyle,
|
|
125
131
|
auditStyle: () => auditStyle,
|
|
126
132
|
availableProviders: () => availableProviders,
|
|
127
133
|
backoffMs: () => backoffMs,
|
|
@@ -148,6 +154,7 @@ __export(index_exports, {
|
|
|
148
154
|
defaultOpenCommand: () => defaultOpenCommand,
|
|
149
155
|
defaultPixelateOut: () => defaultPixelateOut,
|
|
150
156
|
defaultUnzoomOut: () => defaultUnzoomOut,
|
|
157
|
+
describeVfx: () => describeVfx,
|
|
151
158
|
detachHandEdit: () => detachHandEdit,
|
|
152
159
|
detectMediaType: () => detectMediaType,
|
|
153
160
|
doctor: () => doctor,
|
|
@@ -168,6 +175,7 @@ __export(index_exports, {
|
|
|
168
175
|
fontOutputPaths: () => fontOutputPaths,
|
|
169
176
|
formatCost: () => formatCost,
|
|
170
177
|
frameSetFps: () => frameSetFps,
|
|
178
|
+
frameSetLoop: () => frameSetLoop,
|
|
171
179
|
galleryContentSecurityPolicy: () => galleryContentSecurityPolicy,
|
|
172
180
|
galleryMediaId: () => galleryMediaId,
|
|
173
181
|
galleryMediaRoute: () => galleryMediaRoute,
|
|
@@ -190,9 +198,11 @@ __export(index_exports, {
|
|
|
190
198
|
inspectRevisionReadiness: () => inspectRevisionReadiness,
|
|
191
199
|
installEditor: () => installEditor,
|
|
192
200
|
installRecipe: () => installRecipe,
|
|
201
|
+
isBusyReply: () => isBusyReply,
|
|
193
202
|
isFrameSetEntry: () => isFrameSetEntry,
|
|
194
203
|
isMemberSetEntry: () => isMemberSetEntry,
|
|
195
204
|
isSensitiveSourceUrl: () => isSensitiveSourceUrl,
|
|
205
|
+
isVfxReference: () => isVfxReference,
|
|
196
206
|
isometricTileCost: () => isometricTileCost,
|
|
197
207
|
listBundledRecipes: () => listBundledRecipes,
|
|
198
208
|
loadClaims: () => loadClaims,
|
|
@@ -201,7 +211,9 @@ __export(index_exports, {
|
|
|
201
211
|
loadSiblingManifests: () => loadSiblingManifests,
|
|
202
212
|
loadWorkspace: () => loadWorkspace,
|
|
203
213
|
lockKey: () => lockKey,
|
|
214
|
+
makeVfx: () => makeVfx,
|
|
204
215
|
matchOrphanStyle: () => matchOrphanStyle,
|
|
216
|
+
mcpClientFromEnv: () => mcpClientFromEnv,
|
|
205
217
|
measureBalanceChange: () => measureBalanceChange,
|
|
206
218
|
measureDecodedImageQuality: () => measureDecodedImageQuality,
|
|
207
219
|
measureImageQuality: () => measureImageQuality,
|
|
@@ -225,8 +237,12 @@ __export(index_exports, {
|
|
|
225
237
|
paletteDistance: () => paletteDistance,
|
|
226
238
|
parentAssetId: () => parentAssetId,
|
|
227
239
|
parseLock: () => parseLock,
|
|
240
|
+
parseMcpBody: () => parseMcpBody,
|
|
228
241
|
parseProFlashQuote: () => parseProFlashQuote,
|
|
229
242
|
parseTerrainDescriptions: () => parseTerrainDescriptions,
|
|
243
|
+
parseVfxExport: () => parseVfxExport,
|
|
244
|
+
parseVfxRecipe: () => parseVfxRecipe,
|
|
245
|
+
parseVfxStep: () => parseVfxStep,
|
|
230
246
|
parseWorkspace: () => parseWorkspace,
|
|
231
247
|
pickHistory: () => pickHistory,
|
|
232
248
|
pixelateFile: () => pixelateFile,
|
|
@@ -246,6 +262,8 @@ __export(index_exports, {
|
|
|
246
262
|
readArtifactBundleManifest: () => readArtifactBundleManifest,
|
|
247
263
|
readHandEditCompanion: () => readHandEditCompanion,
|
|
248
264
|
readQualityBaseline: () => readQualityBaseline,
|
|
265
|
+
readSavedVfxRecipe: () => readSavedVfxRecipe,
|
|
266
|
+
readVfxRecipe: () => readVfxRecipe,
|
|
249
267
|
recipeDigest: () => recipeDigest,
|
|
250
268
|
referencedHashes: () => referencedHashes,
|
|
251
269
|
refineAsset: () => refineAsset,
|
|
@@ -276,6 +294,7 @@ __export(index_exports, {
|
|
|
276
294
|
resolveSpecs: () => resolveSpecs,
|
|
277
295
|
resolveStyleImages: () => resolveStyleImages,
|
|
278
296
|
resolveStyleOutputs: () => resolveStyleOutputs,
|
|
297
|
+
resolveVfxSprite: () => resolveVfxSprite,
|
|
279
298
|
resumeActions: () => resumeActions,
|
|
280
299
|
resumeCommandForStatus: () => resumeCommandForStatus,
|
|
281
300
|
retireGeneration: () => retireGeneration,
|
|
@@ -283,6 +302,7 @@ __export(index_exports, {
|
|
|
283
302
|
revertGeneration: () => revertGeneration,
|
|
284
303
|
runPicker: () => runPicker,
|
|
285
304
|
runSalvage: () => runSalvage,
|
|
305
|
+
runVfxWorkbench: () => runVfxWorkbench,
|
|
286
306
|
saveHandEdit: () => saveHandEdit,
|
|
287
307
|
saveLock: () => saveLock,
|
|
288
308
|
saveWorkspace: () => saveWorkspace,
|
|
@@ -293,6 +313,7 @@ __export(index_exports, {
|
|
|
293
313
|
sha256File: () => sha256File,
|
|
294
314
|
shouldPersistSourceUrl: () => shouldPersistSourceUrl,
|
|
295
315
|
shouldRetry: () => shouldRetry,
|
|
316
|
+
sliceVfxSheet: () => sliceVfxSheet,
|
|
296
317
|
slugify: () => slugify,
|
|
297
318
|
snapshotQualityBaseline: () => snapshotQualityBaseline,
|
|
298
319
|
sourceIsStem: () => sourceIsStem,
|
|
@@ -317,6 +338,8 @@ __export(index_exports, {
|
|
|
317
338
|
validateWorkspace: () => validateWorkspace,
|
|
318
339
|
verifyArtifactBundle: () => verifyArtifactBundle,
|
|
319
340
|
verifyRecipe: () => verifyRecipe,
|
|
341
|
+
vfxOutputPaths: () => vfxOutputPaths,
|
|
342
|
+
vfxRecipeSha256: () => vfxRecipeSha256,
|
|
320
343
|
withArtifactManifest: () => withArtifactManifest,
|
|
321
344
|
workspaceClaims: () => workspaceClaims,
|
|
322
345
|
workspaceStatus: () => workspaceStatus,
|
|
@@ -332,7 +355,7 @@ var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
|
|
|
332
355
|
// src/types.ts
|
|
333
356
|
var import_zod = require("zod");
|
|
334
357
|
var MediaTypeSchema = import_zod.z.enum(["image/png", "image/gif"]);
|
|
335
|
-
var GeneratorSchema = import_zod.z.enum(["1dir", "map", "pixflux", "tiles", "animation", "frames", "character", "terrain", "imagePro", "isometricTile", "objectPro", "uiAsset", "uiElement", "imageProFlash"]);
|
|
358
|
+
var GeneratorSchema = import_zod.z.enum(["1dir", "map", "pixflux", "tiles", "animation", "frames", "character", "terrain", "imagePro", "isometricTile", "objectPro", "uiAsset", "uiElement", "imageProFlash", "vfx"]);
|
|
336
359
|
var GridConfidenceSchema = import_zod.z.enum(["low", "medium", "high"]);
|
|
337
360
|
var RevisionModeSchema = import_zod.z.enum([
|
|
338
361
|
"image-to-image",
|
|
@@ -365,6 +388,7 @@ function candidateCount(size) {
|
|
|
365
388
|
return 1;
|
|
366
389
|
}
|
|
367
390
|
function generationCost(width, height, generator = "map") {
|
|
391
|
+
if (generator === "vfx") return 0;
|
|
368
392
|
if (generator === "map" || generator === "pixflux") return 1;
|
|
369
393
|
if (generator === "imagePro") return proTierCost(width, height);
|
|
370
394
|
const px = width * height;
|
|
@@ -549,6 +573,29 @@ var MapSceneSchema = import_zod.z.object({
|
|
|
549
573
|
image: import_zod.z.string().min(1),
|
|
550
574
|
placement: MapScenePlacementSchema.default({ oval: 0.3 })
|
|
551
575
|
}).strict();
|
|
576
|
+
var VfxPartSchema = import_zod.z.object({ name: import_zod.z.string().min(1) }).catchall(import_zod.z.unknown());
|
|
577
|
+
var AssetVfxSchema = import_zod.z.object({
|
|
578
|
+
preset: import_zod.z.string().min(1).optional(),
|
|
579
|
+
recipe: import_zod.z.string().min(1).optional(),
|
|
580
|
+
parts: import_zod.z.array(VfxPartSchema).optional(),
|
|
581
|
+
sprite: import_zod.z.string().min(1).optional(),
|
|
582
|
+
fps: import_zod.z.number().int().min(1).max(60).optional(),
|
|
583
|
+
name: import_zod.z.string().min(1).optional()
|
|
584
|
+
}).strict().superRefine((vfx, context) => {
|
|
585
|
+
if (vfx.preset && vfx.recipe) {
|
|
586
|
+
context.addIssue({ code: import_zod.z.ZodIssueCode.custom, message: "start a vfx from a preset or a recipe, not both", path: ["recipe"] });
|
|
587
|
+
}
|
|
588
|
+
if (!vfx.preset && !vfx.recipe && !vfx.parts?.length) {
|
|
589
|
+
context.addIssue({ code: import_zod.z.ZodIssueCode.custom, message: "a vfx needs a preset, a recipe, or at least one part", path: ["preset"] });
|
|
590
|
+
}
|
|
591
|
+
if (vfx.sprite && /^(https?|data):/i.test(vfx.sprite)) {
|
|
592
|
+
context.addIssue({
|
|
593
|
+
code: import_zod.z.ZodIssueCode.custom,
|
|
594
|
+
message: "a vfx sprite is a manifest-relative image or a character:/object:/gallery: reference; a URL has no content to track",
|
|
595
|
+
path: ["sprite"]
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
});
|
|
552
599
|
var CharacterOutfitSchema = import_zod.z.object({
|
|
553
600
|
/** The loop (a character animation asset) to re-clothe, in the same style. */
|
|
554
601
|
of: import_zod.z.string().min(1),
|
|
@@ -1182,7 +1229,7 @@ var StyleSchema = StyleObjectSchema.refine((s) => !(s.tileFeature && s.styleImag
|
|
|
1182
1229
|
message: "tileFeature and styleImages cannot be combined; a connectable set derives its own tile geometry, so remove one or the other",
|
|
1183
1230
|
path: ["tileFeature"]
|
|
1184
1231
|
}).superRefine((style, ctx) => {
|
|
1185
|
-
if (style.quality && (style.generator === "tiles" || style.generator === "terrain" || style.generator === "animation")) {
|
|
1232
|
+
if (style.quality && (style.generator === "tiles" || style.generator === "terrain" || style.generator === "animation" || style.generator === "vfx")) {
|
|
1186
1233
|
ctx.addIssue({
|
|
1187
1234
|
code: import_zod.z.ZodIssueCode.custom,
|
|
1188
1235
|
message: "quality profiles currently support single-image generators only",
|
|
@@ -1312,6 +1359,8 @@ var AssetSchema = import_zod.z.object({
|
|
|
1312
1359
|
outfit: CharacterOutfitSchema.optional(),
|
|
1313
1360
|
/** `map` styles only: draw this object in the style of a scene image. See `MapSceneSchema`. */
|
|
1314
1361
|
scene: MapSceneSchema.optional(),
|
|
1362
|
+
/** `vfx` styles only: the effect to render. See `AssetVfxSchema`. */
|
|
1363
|
+
vfx: AssetVfxSchema.optional(),
|
|
1315
1364
|
/** `character` styles, `standard` humanoid bases: this character's proportions, over the style's. */
|
|
1316
1365
|
proportions: CharacterProportionsSchema.optional(),
|
|
1317
1366
|
/**
|
|
@@ -1388,7 +1437,7 @@ var AssetSchema = import_zod.z.object({
|
|
|
1388
1437
|
path: ["revision"]
|
|
1389
1438
|
});
|
|
1390
1439
|
}
|
|
1391
|
-
const shapes = [asset.revision && "revision", asset.state && "state", asset.animation && "animation", asset.mirror && "mirror", asset.batch && "batch", asset.portrait && "portrait", asset.outfit && "outfit", asset.scene && "scene"].filter(Boolean);
|
|
1440
|
+
const shapes = [asset.revision && "revision", asset.state && "state", asset.animation && "animation", asset.mirror && "mirror", asset.batch && "batch", asset.portrait && "portrait", asset.outfit && "outfit", asset.scene && "scene", asset.vfx && "vfx"].filter(Boolean);
|
|
1392
1441
|
if (shapes.length > 1) {
|
|
1393
1442
|
context.addIssue({
|
|
1394
1443
|
code: import_zod.z.ZodIssueCode.custom,
|
|
@@ -1403,7 +1452,7 @@ var AssetSchema = import_zod.z.object({
|
|
|
1403
1452
|
path: ["mirror"]
|
|
1404
1453
|
});
|
|
1405
1454
|
}
|
|
1406
|
-
if (asset.prompt === void 0 && !asset.mirror && !asset.portrait && !asset.outfit && !asset.animation?.template) {
|
|
1455
|
+
if (asset.prompt === void 0 && !asset.mirror && !asset.portrait && !asset.outfit && !asset.animation?.template && !asset.vfx) {
|
|
1407
1456
|
context.addIssue({
|
|
1408
1457
|
code: import_zod.z.ZodIssueCode.custom,
|
|
1409
1458
|
message: "Required",
|
|
@@ -2163,9 +2212,9 @@ var PixelLabClient = class {
|
|
|
2163
2212
|
apiKey;
|
|
2164
2213
|
http;
|
|
2165
2214
|
/** Retries transport failures, 429, and 5xx; see http.ts for the policy. */
|
|
2166
|
-
async request(
|
|
2215
|
+
async request(path39, init) {
|
|
2167
2216
|
const auth = this.apiKey.startsWith("Bearer ") ? this.apiKey : `Bearer ${this.apiKey}`;
|
|
2168
|
-
const res = await this.http(`${BASE}${
|
|
2217
|
+
const res = await this.http(`${BASE}${path39}`, {
|
|
2169
2218
|
...init,
|
|
2170
2219
|
headers: {
|
|
2171
2220
|
Authorization: auth,
|
|
@@ -2175,13 +2224,13 @@ var PixelLabClient = class {
|
|
|
2175
2224
|
});
|
|
2176
2225
|
const text = await res.text();
|
|
2177
2226
|
if (!res.ok) {
|
|
2178
|
-
throw new PixelLabError(`${init?.method ?? "GET"} ${
|
|
2227
|
+
throw new PixelLabError(`${init?.method ?? "GET"} ${path39} \u2192 ${res.status}`, res.status, text);
|
|
2179
2228
|
}
|
|
2180
2229
|
if (!text) return {};
|
|
2181
2230
|
try {
|
|
2182
2231
|
return JSON.parse(text);
|
|
2183
2232
|
} catch {
|
|
2184
|
-
throw new Error(`${init?.method ?? "GET"} ${
|
|
2233
|
+
throw new Error(`${init?.method ?? "GET"} ${path39} returned invalid JSON`);
|
|
2185
2234
|
}
|
|
2186
2235
|
}
|
|
2187
2236
|
async balance() {
|
|
@@ -2586,10 +2635,10 @@ var PixelLabClient = class {
|
|
|
2586
2635
|
const palette = args.paletteSwatchBase64 ? { color_image: { type: "base64", base64: args.paletteSwatchBase64, format: "png" }, force_colors: true } : {};
|
|
2587
2636
|
const encode = (image) => ({ type: "base64", base64: image.base64, format: image.format });
|
|
2588
2637
|
const south = args.reference?.south;
|
|
2589
|
-
let
|
|
2638
|
+
let path39;
|
|
2590
2639
|
let body;
|
|
2591
2640
|
if (args.mode === "v3") {
|
|
2592
|
-
|
|
2641
|
+
path39 = "/create-character-v3";
|
|
2593
2642
|
body = {
|
|
2594
2643
|
description: args.description,
|
|
2595
2644
|
image_size: imageSize,
|
|
@@ -2603,7 +2652,7 @@ var PixelLabClient = class {
|
|
|
2603
2652
|
...args.enhancePrompt ? { enhance_prompt: true } : {}
|
|
2604
2653
|
};
|
|
2605
2654
|
} else if (args.mode === "pro-flash") {
|
|
2606
|
-
|
|
2655
|
+
path39 = "/create-character-pro-flash";
|
|
2607
2656
|
const traits = args.styleTraits;
|
|
2608
2657
|
body = {
|
|
2609
2658
|
description: args.description,
|
|
@@ -2627,7 +2676,7 @@ var PixelLabClient = class {
|
|
|
2627
2676
|
} : {}
|
|
2628
2677
|
};
|
|
2629
2678
|
} else if (args.mode === "pro") {
|
|
2630
|
-
|
|
2679
|
+
path39 = "/create-character-pro";
|
|
2631
2680
|
body = {
|
|
2632
2681
|
description: args.description,
|
|
2633
2682
|
image_size: imageSize,
|
|
@@ -2641,7 +2690,7 @@ var PixelLabClient = class {
|
|
|
2641
2690
|
...!south && args.styleCharacterId ? { style_character_id: args.styleCharacterId } : {}
|
|
2642
2691
|
};
|
|
2643
2692
|
} else {
|
|
2644
|
-
|
|
2693
|
+
path39 = args.directions === 4 ? "/create-character-with-4-directions" : "/create-character-with-8-directions";
|
|
2645
2694
|
body = {
|
|
2646
2695
|
description: args.description,
|
|
2647
2696
|
image_size: imageSize,
|
|
@@ -2658,8 +2707,8 @@ var PixelLabClient = class {
|
|
|
2658
2707
|
...palette
|
|
2659
2708
|
};
|
|
2660
2709
|
}
|
|
2661
|
-
const raw = await this.request(
|
|
2662
|
-
return validateResponse(CharacterSubmitSchema, raw,
|
|
2710
|
+
const raw = await this.request(path39, { method: "POST", body: JSON.stringify(body) });
|
|
2711
|
+
return validateResponse(CharacterSubmitSchema, raw, path39.slice(1));
|
|
2663
2712
|
}
|
|
2664
2713
|
/** A text edit of an existing character, applied to every direction and stored as a sibling. */
|
|
2665
2714
|
async createCharacterState(args) {
|
|
@@ -2959,8 +3008,8 @@ var PixelLabClient = class {
|
|
|
2959
3008
|
/**
|
|
2960
3009
|
* `/image-to-pixelart-pro-flash`: a photo, painting, or render in, pixel
|
|
2961
3010
|
* art out, with no size to pass: PixelLab detects the native pixel scale
|
|
2962
|
-
* and picks the output size (a 256x256 render came back 108x109
|
|
2963
|
-
*
|
|
3011
|
+
* and picks the output size (a 256x256 render came back 108x109). The
|
|
3012
|
+
* result is opaque, on a near-white background. A flat 6 generations, billed exactly that live
|
|
2964
3013
|
* in October 2026. Sources over 2048px on the longer side are scaled down
|
|
2965
3014
|
* upstream. The finished job carries the image at `last_response.images[0]`.
|
|
2966
3015
|
*/
|
|
@@ -3884,10 +3933,10 @@ function imageMetadata(buf) {
|
|
|
3884
3933
|
}
|
|
3885
3934
|
|
|
3886
3935
|
// src/providers/pixellab.ts
|
|
3887
|
-
var
|
|
3936
|
+
var import_node_fs3 = require("fs");
|
|
3888
3937
|
var import_node_crypto2 = require("crypto");
|
|
3889
3938
|
var import_node_os = __toESM(require("os"), 1);
|
|
3890
|
-
var
|
|
3939
|
+
var import_node_path2 = __toESM(require("path"), 1);
|
|
3891
3940
|
|
|
3892
3941
|
// src/hash.ts
|
|
3893
3942
|
var import_node_crypto = require("crypto");
|
|
@@ -3895,16 +3944,17 @@ var import_node_fs = require("fs");
|
|
|
3895
3944
|
function sha256(data) {
|
|
3896
3945
|
return (0, import_node_crypto.createHash)("sha256").update(data).digest("hex");
|
|
3897
3946
|
}
|
|
3898
|
-
async function sha256File(
|
|
3947
|
+
async function sha256File(path39) {
|
|
3899
3948
|
return new Promise((resolve, reject) => {
|
|
3900
3949
|
const hash = (0, import_node_crypto.createHash)("sha256");
|
|
3901
|
-
const stream = (0, import_node_fs.createReadStream)(
|
|
3950
|
+
const stream = (0, import_node_fs.createReadStream)(path39);
|
|
3902
3951
|
stream.on("data", (chunk2) => hash.update(chunk2));
|
|
3903
3952
|
stream.on("error", reject);
|
|
3904
3953
|
stream.on("end", () => resolve(hash.digest("hex")));
|
|
3905
3954
|
});
|
|
3906
3955
|
}
|
|
3907
3956
|
function specHash(spec, styleImageHashes, providerOptionIdentity = spec.providerOptions, providerInputIdentity = spec.providerInputs ?? {}) {
|
|
3957
|
+
const effect = Boolean(spec.vfx);
|
|
3908
3958
|
return sha256(
|
|
3909
3959
|
JSON.stringify({
|
|
3910
3960
|
// Preserve every existing PixelLab hash while making a provider switch
|
|
@@ -3913,15 +3963,15 @@ function specHash(spec, styleImageHashes, providerOptionIdentity = spec.provider
|
|
|
3913
3963
|
providerOptions: providerOptionIdentity && (typeof providerOptionIdentity !== "object" || Object.keys(providerOptionIdentity).length > 0) ? providerOptionIdentity : void 0,
|
|
3914
3964
|
providerInputs: providerInputIdentity && (typeof providerInputIdentity !== "object" || Object.keys(providerInputIdentity).length > 0) ? providerInputIdentity : void 0,
|
|
3915
3965
|
generator: spec.generator,
|
|
3916
|
-
prompt: spec.prompt,
|
|
3917
|
-
width: spec.width,
|
|
3918
|
-
height: spec.height,
|
|
3919
|
-
view: spec.view,
|
|
3920
|
-
outline: spec.outline ?? null,
|
|
3921
|
-
shading: spec.shading ?? null,
|
|
3922
|
-
detail: spec.detail ?? null,
|
|
3923
|
-
seed: spec.seed ?? null,
|
|
3924
|
-
palette: spec.palette,
|
|
3966
|
+
prompt: effect ? void 0 : spec.prompt,
|
|
3967
|
+
width: effect ? void 0 : spec.width,
|
|
3968
|
+
height: effect ? void 0 : spec.height,
|
|
3969
|
+
view: effect ? void 0 : spec.view,
|
|
3970
|
+
outline: effect ? void 0 : spec.outline ?? null,
|
|
3971
|
+
shading: effect ? void 0 : spec.shading ?? null,
|
|
3972
|
+
detail: effect ? void 0 : spec.detail ?? null,
|
|
3973
|
+
seed: effect ? void 0 : spec.seed ?? null,
|
|
3974
|
+
palette: effect ? void 0 : spec.palette,
|
|
3925
3975
|
// `noBackground` only reaches the wire for pixflux and uiAsset; the
|
|
3926
3976
|
// tile fields are undefined for every other generator. `tileSize` is
|
|
3927
3977
|
// intentionally absent; width/height are derived from it, so it is
|
|
@@ -3996,6 +4046,15 @@ function specHash(spec, styleImageHashes, providerOptionIdentity = spec.provider
|
|
|
3996
4046
|
styleTraits: spec.objectPro.styleTraits,
|
|
3997
4047
|
reference: spec.objectPro.reference?.sha256
|
|
3998
4048
|
} : void 0,
|
|
4049
|
+
// A recipe and a local sprite enter as their contents' hashes, never
|
|
4050
|
+
// their paths; the My effects name is not identity.
|
|
4051
|
+
vfx: spec.vfx ? {
|
|
4052
|
+
preset: spec.vfx.preset,
|
|
4053
|
+
recipe: spec.vfx.recipe?.sha256,
|
|
4054
|
+
parts: spec.vfx.parts,
|
|
4055
|
+
fps: spec.vfx.fps,
|
|
4056
|
+
sprite: spec.vfx.sprite ? "reference" in spec.vfx.sprite ? spec.vfx.sprite.reference : spec.vfx.sprite.sha256 : void 0
|
|
4057
|
+
} : void 0,
|
|
3999
4058
|
// A mirror's bytes come from its source's recorded outputs; the plan
|
|
4000
4059
|
// compares those directly, so only the choice of source is identity.
|
|
4001
4060
|
mirror: spec.mirror ? { of: spec.mirror.sourceAssetId } : void 0,
|
|
@@ -4033,6 +4092,337 @@ function specHash(spec, styleImageHashes, providerOptionIdentity = spec.provider
|
|
|
4033
4092
|
);
|
|
4034
4093
|
}
|
|
4035
4094
|
|
|
4095
|
+
// src/pixellab-mcp.ts
|
|
4096
|
+
var PIXELLAB_MCP_URL = "https://api.pixellab.ai/mcp";
|
|
4097
|
+
var PROTOCOL_VERSION = "2025-06-18";
|
|
4098
|
+
function parseMcpBody(body) {
|
|
4099
|
+
const trimmed = body.trim();
|
|
4100
|
+
if (trimmed.startsWith("{")) return JSON.parse(trimmed);
|
|
4101
|
+
let last;
|
|
4102
|
+
for (const line of body.split(/\r?\n/)) {
|
|
4103
|
+
if (!line.startsWith("data:")) continue;
|
|
4104
|
+
const payload = line.slice(5).trim();
|
|
4105
|
+
if (!payload) continue;
|
|
4106
|
+
const message6 = JSON.parse(payload);
|
|
4107
|
+
if ("id" in message6) last = message6;
|
|
4108
|
+
}
|
|
4109
|
+
if (!last) throw new Error(`PixelLab MCP reply had no JSON-RPC response: ${body.slice(0, 200)}`);
|
|
4110
|
+
return last;
|
|
4111
|
+
}
|
|
4112
|
+
function isBusyReply(text) {
|
|
4113
|
+
return /\bbusy\b.*\bretry\b/i.test(text);
|
|
4114
|
+
}
|
|
4115
|
+
var PixelLabMcpClient = class {
|
|
4116
|
+
constructor(apiKey, opts = {}) {
|
|
4117
|
+
this.apiKey = apiKey;
|
|
4118
|
+
if (!apiKey) throw new Error("PIXELLAB_API_KEY is required");
|
|
4119
|
+
this.url = opts.url ?? PIXELLAB_MCP_URL;
|
|
4120
|
+
this.http = fetchWithRetry(opts.fetch, { timeoutMs: opts.timeoutMs ?? 18e4 });
|
|
4121
|
+
this.busyRetries = opts.busyRetries ?? 4;
|
|
4122
|
+
this.sleep = opts.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
4123
|
+
}
|
|
4124
|
+
apiKey;
|
|
4125
|
+
http;
|
|
4126
|
+
url;
|
|
4127
|
+
busyRetries;
|
|
4128
|
+
sleep;
|
|
4129
|
+
nextId = 1;
|
|
4130
|
+
/**
|
|
4131
|
+
* Call one tool and return its text and images. A tool-level error
|
|
4132
|
+
* (`isError`) throws with the tool's own message, after a few spaced
|
|
4133
|
+
* retries when the tool only said it was busy.
|
|
4134
|
+
*/
|
|
4135
|
+
async callTool(name, args) {
|
|
4136
|
+
for (let attempt = 0; ; attempt++) {
|
|
4137
|
+
const reply = await this.callOnce(name, args);
|
|
4138
|
+
if (!reply.isError) return { text: reply.text, images: reply.images };
|
|
4139
|
+
if (attempt < this.busyRetries && isBusyReply(reply.text)) {
|
|
4140
|
+
await this.sleep(2e3 * 2 ** attempt);
|
|
4141
|
+
continue;
|
|
4142
|
+
}
|
|
4143
|
+
throw new Error(`PixelLab ${name}: ${reply.text || "the tool reported an error"}`);
|
|
4144
|
+
}
|
|
4145
|
+
}
|
|
4146
|
+
async callOnce(name, args) {
|
|
4147
|
+
const auth = this.apiKey.startsWith("Bearer ") ? this.apiKey : `Bearer ${this.apiKey}`;
|
|
4148
|
+
const id = this.nextId++;
|
|
4149
|
+
const res = await this.http(this.url, {
|
|
4150
|
+
method: "POST",
|
|
4151
|
+
headers: {
|
|
4152
|
+
Authorization: auth,
|
|
4153
|
+
"Content-Type": "application/json",
|
|
4154
|
+
Accept: "application/json, text/event-stream",
|
|
4155
|
+
"MCP-Protocol-Version": PROTOCOL_VERSION
|
|
4156
|
+
},
|
|
4157
|
+
body: JSON.stringify({ jsonrpc: "2.0", id, method: "tools/call", params: { name, arguments: args } })
|
|
4158
|
+
});
|
|
4159
|
+
const body = await res.text();
|
|
4160
|
+
if (!res.ok) throw new PixelLabError(`MCP ${name} \u2192 ${res.status}`, res.status, body);
|
|
4161
|
+
const message6 = parseMcpBody(body);
|
|
4162
|
+
const error = message6.error;
|
|
4163
|
+
if (error) throw new Error(`PixelLab MCP ${name} failed: ${error.message ?? JSON.stringify(error)}`);
|
|
4164
|
+
const result = message6.result ?? {};
|
|
4165
|
+
const text = (result.content ?? []).filter((block) => block.type === "text" && typeof block.text === "string").map((block) => block.text).join("\n");
|
|
4166
|
+
const images = (result.content ?? []).filter((block) => block.type === "image" && typeof block.data === "string").map((block) => ({ mimeType: String(block.mimeType ?? "image/png"), data: block.data }));
|
|
4167
|
+
return { text, images, isError: Boolean(result.isError) };
|
|
4168
|
+
}
|
|
4169
|
+
};
|
|
4170
|
+
function mcpClientFromEnv() {
|
|
4171
|
+
const key = process.env.PIXELLAB_API_KEY;
|
|
4172
|
+
if (!key) {
|
|
4173
|
+
throw new Error(
|
|
4174
|
+
"PIXELLAB_API_KEY is not set.\nLooked in the environment, and in .env.local / .env beside the manifest and in the current directory."
|
|
4175
|
+
);
|
|
4176
|
+
}
|
|
4177
|
+
return new PixelLabMcpClient(key);
|
|
4178
|
+
}
|
|
4179
|
+
|
|
4180
|
+
// src/pixellab-vfx.ts
|
|
4181
|
+
var import_node_fs2 = require("fs");
|
|
4182
|
+
var import_promises = require("fs/promises");
|
|
4183
|
+
var import_node_path = __toESM(require("path"), 1);
|
|
4184
|
+
var VFX_TOOL = "vfx_workbench";
|
|
4185
|
+
var UUID = "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}";
|
|
4186
|
+
function parseVfxStep(text) {
|
|
4187
|
+
const match = new RegExp(`effect step: (${UUID})`).exec(text);
|
|
4188
|
+
if (!match) throw new Error(`PixelLab vfx_workbench returned no step id:
|
|
4189
|
+
${text.slice(0, 600)}`);
|
|
4190
|
+
return match[1];
|
|
4191
|
+
}
|
|
4192
|
+
function parseVfxRecipe(text) {
|
|
4193
|
+
const lines = text.split(/\r?\n/);
|
|
4194
|
+
const at = lines.findIndex((line) => line.trim() === "working recipe:");
|
|
4195
|
+
const json = at >= 0 ? lines[at + 1]?.trim() : void 0;
|
|
4196
|
+
if (!json?.startsWith("{")) return null;
|
|
4197
|
+
return JSON.parse(json);
|
|
4198
|
+
}
|
|
4199
|
+
function parseVfxExport(text) {
|
|
4200
|
+
const effectId = new RegExp(`effect id (${UUID})`).exec(text)?.[1];
|
|
4201
|
+
const download = /download (https:\/\/\S+\/): (.+)$/m.exec(text);
|
|
4202
|
+
const size = /(\d+)x(\d+), (\d+) frames @ ([\d.]+) fps, ([^;\n]+)/.exec(text);
|
|
4203
|
+
if (!effectId || !download || !size) {
|
|
4204
|
+
throw new Error(`PixelLab vfx_workbench export reply was not understood:
|
|
4205
|
+
${text.slice(0, 600)}`);
|
|
4206
|
+
}
|
|
4207
|
+
const anchor = /anchor[^\n]*? at (-?\d+),\s*(-?\d+)/.exec(text);
|
|
4208
|
+
return {
|
|
4209
|
+
effectId,
|
|
4210
|
+
editUrl: /(https:\/\/pixellab\.ai\/create-vfx\?effect=[0-9a-f-]+)/.exec(text)?.[1] ?? null,
|
|
4211
|
+
width: Number(size[1]),
|
|
4212
|
+
height: Number(size[2]),
|
|
4213
|
+
frames: Number(size[3]),
|
|
4214
|
+
fps: Number(size[4]),
|
|
4215
|
+
loop: !/plays once/.test(size[5]),
|
|
4216
|
+
anchor: anchor ? { x: Number(anchor[1]), y: Number(anchor[2]) } : null,
|
|
4217
|
+
downloadBase: download[1],
|
|
4218
|
+
files: download[2].split(",").map((file) => file.trim()).filter(Boolean),
|
|
4219
|
+
imageId: new RegExp(`image id: (${UUID})`).exec(text)?.[1] ?? null
|
|
4220
|
+
};
|
|
4221
|
+
}
|
|
4222
|
+
async function describeVfx(client2, topic) {
|
|
4223
|
+
return (await client2.callTool(VFX_TOOL, { argv: ["describe", topic] })).text;
|
|
4224
|
+
}
|
|
4225
|
+
function vfxOutputPaths(out) {
|
|
4226
|
+
const base = import_node_path.default.resolve(out.replace(/\.(png|gif|json|zip)$/i, "").replace(/\.vfx$/i, ""));
|
|
4227
|
+
return {
|
|
4228
|
+
sheet: `${base}.png`,
|
|
4229
|
+
gif: `${base}.gif`,
|
|
4230
|
+
atlas: `${base}.json`,
|
|
4231
|
+
provenance: `${base}.vfx.json`,
|
|
4232
|
+
gamePack: `${base}-game.zip`
|
|
4233
|
+
};
|
|
4234
|
+
}
|
|
4235
|
+
function isVfxReference(sprite) {
|
|
4236
|
+
return /^(character|object|gallery):/.test(sprite) || /^(https?|data):/.test(sprite);
|
|
4237
|
+
}
|
|
4238
|
+
async function resolveVfxSprite(sprite) {
|
|
4239
|
+
if (isVfxReference(sprite)) return sprite;
|
|
4240
|
+
const bytes = await (0, import_promises.readFile)(sprite);
|
|
4241
|
+
const metadata = imageMetadata(bytes);
|
|
4242
|
+
if (!metadata) throw new Error(`${sprite} is not a readable PNG or JPEG`);
|
|
4243
|
+
return `data:image/${metadata.format};base64,${bytes.toString("base64")}`;
|
|
4244
|
+
}
|
|
4245
|
+
async function readVfxRecipe(file) {
|
|
4246
|
+
const parsed = JSON.parse(await (0, import_promises.readFile)(file, "utf8"));
|
|
4247
|
+
const record = parsed.tool === VFX_TOOL && parsed.recipe && typeof parsed.recipe === "object";
|
|
4248
|
+
const recipe = record ? parsed.recipe : parsed;
|
|
4249
|
+
if (!Array.isArray(recipe.layers)) throw new Error(`${file} is not a VFX recipe: it has no "layers" list`);
|
|
4250
|
+
const recorded = record ? parsed.source?.sprite : void 0;
|
|
4251
|
+
if (typeof recorded !== "string") return { recipe };
|
|
4252
|
+
const sprite = isVfxReference(recorded) ? recorded : import_node_path.default.resolve(import_node_path.default.dirname(import_node_path.default.resolve(file)), recorded);
|
|
4253
|
+
return { recipe, sprite };
|
|
4254
|
+
}
|
|
4255
|
+
async function defaultDownload(url) {
|
|
4256
|
+
const http = fetchWithRetry();
|
|
4257
|
+
const res = await http(url);
|
|
4258
|
+
if (!res.ok) throw new Error(`download ${url} \u2192 ${res.status}`);
|
|
4259
|
+
const buf = Buffer.from(await res.arrayBuffer());
|
|
4260
|
+
if (buf.length > MAX_DOWNLOAD_BYTES) throw new Error(`download ${url} exceeds the ${MAX_DOWNLOAD_BYTES}-byte safety limit`);
|
|
4261
|
+
return buf;
|
|
4262
|
+
}
|
|
4263
|
+
function localPathFor(file, paths) {
|
|
4264
|
+
if (/-game\.zip$/i.test(file)) return paths.gamePack;
|
|
4265
|
+
if (/\.gif$/i.test(file)) return paths.gif;
|
|
4266
|
+
if (/\.json$/i.test(file)) return paths.atlas;
|
|
4267
|
+
if (/\.png$/i.test(file)) return paths.sheet;
|
|
4268
|
+
return null;
|
|
4269
|
+
}
|
|
4270
|
+
async function runVfxWorkbench(client2, opts) {
|
|
4271
|
+
const starts = [opts.preset, opts.recipe, opts.effect].filter((value) => value !== void 0).length;
|
|
4272
|
+
if (starts > 1) throw new Error("Start a VFX from one of a preset, a recipe, or a saved effect, not several");
|
|
4273
|
+
if (starts === 0 && !opts.parts?.length) {
|
|
4274
|
+
throw new Error("A VFX needs something to start from: a preset, a recipe, a saved effect, or at least one part");
|
|
4275
|
+
}
|
|
4276
|
+
const progress = opts.onProgress ?? (() => {
|
|
4277
|
+
});
|
|
4278
|
+
const call = async (argv) => (await client2.callTool(VFX_TOOL, { argv, ...opts.notes ? { notes: opts.notes } : {} })).text;
|
|
4279
|
+
const startArgv = ["start"];
|
|
4280
|
+
if (opts.sprite) startArgv.push("--sprite", opts.sprite);
|
|
4281
|
+
if (opts.preset) startArgv.push("--preset", opts.preset);
|
|
4282
|
+
if (opts.effect) startArgv.push("--effect", opts.effect);
|
|
4283
|
+
progress(opts.preset ? `starting from preset ${opts.preset}` : opts.effect ? `starting from saved effect ${opts.effect}` : "starting");
|
|
4284
|
+
const started = await call(startArgv);
|
|
4285
|
+
let step = parseVfxStep(started);
|
|
4286
|
+
const savedName = opts.effect ? parseVfxRecipe(started)?.name : opts.recipe?.name;
|
|
4287
|
+
const name = opts.name ?? (typeof savedName === "string" ? savedName : void 0) ?? opts.defaultName;
|
|
4288
|
+
if (opts.recipe) {
|
|
4289
|
+
progress("rendering the recipe");
|
|
4290
|
+
step = parseVfxStep(await call([step, "render", JSON.stringify({ recipe: opts.recipe })]));
|
|
4291
|
+
}
|
|
4292
|
+
if (opts.parts?.length) {
|
|
4293
|
+
progress(`adding ${opts.parts.map((part) => part.name).join(", ")}`);
|
|
4294
|
+
step = parseVfxStep(await call([step, "add_part", JSON.stringify({ parts: opts.parts })]));
|
|
4295
|
+
}
|
|
4296
|
+
if (opts.fps !== void 0) {
|
|
4297
|
+
progress(`setting ${opts.fps} fps`);
|
|
4298
|
+
step = parseVfxStep(await call([step, "render", JSON.stringify({ patch: [{ op: "set", path: "fps", value: opts.fps }] })]));
|
|
4299
|
+
}
|
|
4300
|
+
const exportArgv = [step, "export", "--name", name];
|
|
4301
|
+
if (opts.replace) exportArgv.push("--replace", opts.replace);
|
|
4302
|
+
if (opts.gamePack) exportArgv.push("--game-pack");
|
|
4303
|
+
progress(opts.gamePack ? "exporting with a game pack" : "exporting");
|
|
4304
|
+
const exported = parseVfxExport(await call(exportArgv));
|
|
4305
|
+
await opts.onExported?.(exported, name);
|
|
4306
|
+
progress("reading the saved recipe");
|
|
4307
|
+
const recipe = parseVfxRecipe(await call(["start", "--effect", exported.effectId]));
|
|
4308
|
+
return { exported, recipe, name };
|
|
4309
|
+
}
|
|
4310
|
+
async function readSavedVfxRecipe(client2, effectId) {
|
|
4311
|
+
try {
|
|
4312
|
+
return parseVfxRecipe((await client2.callTool(VFX_TOOL, { argv: ["start", "--effect", effectId] })).text);
|
|
4313
|
+
} catch (error) {
|
|
4314
|
+
if (error instanceof Error && /no saved effect/i.test(error.message)) return null;
|
|
4315
|
+
throw error;
|
|
4316
|
+
}
|
|
4317
|
+
}
|
|
4318
|
+
function vfxRecipeSha256(recipe) {
|
|
4319
|
+
const { name: _name, notes: _notes, ...rest } = recipe;
|
|
4320
|
+
return sha256(JSON.stringify(rest));
|
|
4321
|
+
}
|
|
4322
|
+
function sliceVfxSheet(sheet, facts, atlas) {
|
|
4323
|
+
const image = decodePng(sheet);
|
|
4324
|
+
const listed = atlas?.frames;
|
|
4325
|
+
const rects = Array.isArray(listed) && listed.length === facts.frames ? listed.map((entry) => entry.frame) : Array.from({ length: facts.frames }, (_, index) => {
|
|
4326
|
+
const columns = Math.max(1, Math.floor(image.width / facts.width));
|
|
4327
|
+
return { x: index % columns * facts.width, y: Math.floor(index / columns) * facts.height, w: facts.width, h: facts.height };
|
|
4328
|
+
});
|
|
4329
|
+
return rects.map((rect, index) => {
|
|
4330
|
+
if (rect.x < 0 || rect.y < 0 || rect.x + rect.w > image.width || rect.y + rect.h > image.height) {
|
|
4331
|
+
throw new Error(
|
|
4332
|
+
`VFX frame ${index + 1} (${rect.w}x${rect.h} at ${rect.x},${rect.y}) falls outside the ${image.width}x${image.height} sheet`
|
|
4333
|
+
);
|
|
4334
|
+
}
|
|
4335
|
+
const out = Buffer.alloc(rect.w * rect.h * 4);
|
|
4336
|
+
for (let y = 0; y < rect.h; y++) {
|
|
4337
|
+
const from = ((rect.y + y) * image.width + rect.x) * 4;
|
|
4338
|
+
image.pixels.copy(out, y * rect.w * 4, from, from + rect.w * 4);
|
|
4339
|
+
}
|
|
4340
|
+
return encodeRgbaPng(rect.w, rect.h, out);
|
|
4341
|
+
});
|
|
4342
|
+
}
|
|
4343
|
+
async function makeVfx(client2, opts) {
|
|
4344
|
+
const starts = [opts.preset, opts.recipe, opts.effect].filter((value) => value !== void 0).length;
|
|
4345
|
+
if (starts > 1) throw new Error("Start a VFX from one of a preset, a recipe, or a saved effect, not several");
|
|
4346
|
+
if (starts === 0 && !opts.parts?.length) {
|
|
4347
|
+
throw new Error("A VFX needs something to start from: a preset, a recipe, a saved effect, or at least one part");
|
|
4348
|
+
}
|
|
4349
|
+
const paths = vfxOutputPaths(opts.out);
|
|
4350
|
+
const wanted = [paths.sheet, paths.gif, paths.atlas, paths.provenance, ...opts.gamePack ? [paths.gamePack] : []];
|
|
4351
|
+
if (!opts.force) {
|
|
4352
|
+
for (const file of wanted) {
|
|
4353
|
+
if ((0, import_node_fs2.existsSync)(file)) throw new Error(`${file} already exists; pass --force to replace it`);
|
|
4354
|
+
}
|
|
4355
|
+
}
|
|
4356
|
+
const progress = opts.onProgress ?? (() => {
|
|
4357
|
+
});
|
|
4358
|
+
const { exported, recipe, name } = await runVfxWorkbench(client2, {
|
|
4359
|
+
preset: opts.preset,
|
|
4360
|
+
recipe: opts.recipe,
|
|
4361
|
+
effect: opts.effect,
|
|
4362
|
+
parts: opts.parts,
|
|
4363
|
+
sprite: opts.sprite ? await resolveVfxSprite(opts.sprite) : void 0,
|
|
4364
|
+
fps: opts.fps,
|
|
4365
|
+
name: opts.name,
|
|
4366
|
+
defaultName: opts.preset ?? import_node_path.default.basename(paths.sheet, ".png"),
|
|
4367
|
+
replace: opts.replace ?? opts.effect,
|
|
4368
|
+
gamePack: opts.gamePack,
|
|
4369
|
+
notes: "pixelkiln vfx",
|
|
4370
|
+
onProgress: progress
|
|
4371
|
+
});
|
|
4372
|
+
const download = opts.download ?? defaultDownload;
|
|
4373
|
+
const written = { sheet: paths.sheet, gif: paths.gif, atlas: paths.atlas, provenance: paths.provenance };
|
|
4374
|
+
await (0, import_promises.mkdir)(import_node_path.default.dirname(paths.sheet), { recursive: true });
|
|
4375
|
+
for (const file of exported.files) {
|
|
4376
|
+
const target = localPathFor(file, paths);
|
|
4377
|
+
if (!target) continue;
|
|
4378
|
+
let bytes = await download(`${exported.downloadBase}${encodeURIComponent(file)}`);
|
|
4379
|
+
if (target === paths.atlas) {
|
|
4380
|
+
const atlas = JSON.parse(bytes.toString("utf8"));
|
|
4381
|
+
if (atlas.meta) atlas.meta.image = import_node_path.default.basename(paths.sheet);
|
|
4382
|
+
bytes = Buffer.from(`${JSON.stringify(atlas, null, 2)}
|
|
4383
|
+
`);
|
|
4384
|
+
}
|
|
4385
|
+
await (0, import_promises.writeFile)(target, bytes);
|
|
4386
|
+
if (target === paths.gamePack) written.gamePack = target;
|
|
4387
|
+
}
|
|
4388
|
+
for (const file of [paths.sheet, paths.gif, paths.atlas]) {
|
|
4389
|
+
if (!(0, import_node_fs2.existsSync)(file)) throw new Error(`PixelLab's export listed no file for ${import_node_path.default.basename(file)}: ${exported.files.join(", ")}`);
|
|
4390
|
+
}
|
|
4391
|
+
const spriteRecord = !opts.sprite || /^data:/.test(opts.sprite) ? void 0 : isVfxReference(opts.sprite) ? opts.sprite : import_node_path.default.relative(import_node_path.default.dirname(paths.provenance), import_node_path.default.resolve(opts.sprite)).split(import_node_path.default.sep).join("/");
|
|
4392
|
+
const provenance = {
|
|
4393
|
+
version: 1,
|
|
4394
|
+
provider: "pixellab",
|
|
4395
|
+
tool: VFX_TOOL,
|
|
4396
|
+
name,
|
|
4397
|
+
effectId: exported.effectId,
|
|
4398
|
+
editUrl: exported.editUrl,
|
|
4399
|
+
source: {
|
|
4400
|
+
...opts.preset ? { preset: opts.preset } : {},
|
|
4401
|
+
...opts.effect ? { effect: opts.effect } : {},
|
|
4402
|
+
...opts.recipe ? { recipeFile: true } : {},
|
|
4403
|
+
...opts.parts?.length ? { parts: opts.parts } : {},
|
|
4404
|
+
...spriteRecord ? { sprite: spriteRecord } : {}
|
|
4405
|
+
},
|
|
4406
|
+
width: exported.width,
|
|
4407
|
+
height: exported.height,
|
|
4408
|
+
frames: exported.frames,
|
|
4409
|
+
fps: exported.fps,
|
|
4410
|
+
loop: exported.loop,
|
|
4411
|
+
anchor: exported.anchor,
|
|
4412
|
+
files: {
|
|
4413
|
+
sheet: import_node_path.default.basename(paths.sheet),
|
|
4414
|
+
gif: import_node_path.default.basename(paths.gif),
|
|
4415
|
+
atlas: import_node_path.default.basename(paths.atlas),
|
|
4416
|
+
...written.gamePack ? { gamePack: import_node_path.default.basename(written.gamePack) } : {}
|
|
4417
|
+
},
|
|
4418
|
+
exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4419
|
+
recipe
|
|
4420
|
+
};
|
|
4421
|
+
await (0, import_promises.writeFile)(paths.provenance, `${JSON.stringify(provenance, null, 2)}
|
|
4422
|
+
`);
|
|
4423
|
+
return { ...exported, paths: written, recipe };
|
|
4424
|
+
}
|
|
4425
|
+
|
|
4036
4426
|
// src/providers/pixellab.ts
|
|
4037
4427
|
function pixelLabObjectUrl(generator, objectId) {
|
|
4038
4428
|
if (!objectId) return null;
|
|
@@ -4047,6 +4437,7 @@ function pixelLabObjectUrl(generator, objectId) {
|
|
|
4047
4437
|
const characterId = objectId.split("#")[0];
|
|
4048
4438
|
return characterId ? `https://www.pixellab.ai/create-character/${encodeURIComponent(characterId)}` : null;
|
|
4049
4439
|
}
|
|
4440
|
+
if (generator === "vfx") return `https://pixellab.ai/create-vfx?effect=${encodeURIComponent(objectId)}`;
|
|
4050
4441
|
return null;
|
|
4051
4442
|
}
|
|
4052
4443
|
function billedFromUsage(usage) {
|
|
@@ -4163,13 +4554,20 @@ function decodeObjectAnimationJob(jobId) {
|
|
|
4163
4554
|
return null;
|
|
4164
4555
|
}
|
|
4165
4556
|
var PixelLabProvider = class _PixelLabProvider {
|
|
4166
|
-
|
|
4557
|
+
/**
|
|
4558
|
+
* `mcp` reaches the tools PixelLab offers only over MCP (the VFX
|
|
4559
|
+
* workbench). Offline and download-only providers have none, and a `vfx`
|
|
4560
|
+
* submission through one says so.
|
|
4561
|
+
*/
|
|
4562
|
+
constructor(client2, mcp) {
|
|
4167
4563
|
this.client = client2;
|
|
4564
|
+
this.mcp = mcp;
|
|
4168
4565
|
}
|
|
4169
4566
|
client;
|
|
4567
|
+
mcp;
|
|
4170
4568
|
id = "pixellab";
|
|
4171
4569
|
static fromEnv() {
|
|
4172
|
-
return new _PixelLabProvider(clientFromEnv());
|
|
4570
|
+
return new _PixelLabProvider(clientFromEnv(), mcpClientFromEnv());
|
|
4173
4571
|
}
|
|
4174
4572
|
/** Public storage URLs and the local content cache do not require API auth. */
|
|
4175
4573
|
static forDownloads() {
|
|
@@ -4180,7 +4578,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
4180
4578
|
return new _PixelLabProvider(new PixelLabClient("download-only"));
|
|
4181
4579
|
}
|
|
4182
4580
|
supports(generator) {
|
|
4183
|
-
return generator === "1dir" || generator === "map" || generator === "pixflux" || generator === "tiles" || generator === "terrain" || generator === "imagePro" || generator === "character" || generator === "isometricTile" || generator === "objectPro" || generator === "uiAsset" || generator === "uiElement" || generator === "imageProFlash";
|
|
4581
|
+
return generator === "1dir" || generator === "map" || generator === "pixflux" || generator === "tiles" || generator === "terrain" || generator === "imagePro" || generator === "character" || generator === "isometricTile" || generator === "objectPro" || generator === "uiAsset" || generator === "uiElement" || generator === "imageProFlash" || generator === "vfx";
|
|
4184
4582
|
}
|
|
4185
4583
|
/** `/map-objects` style matching: `background_image` plus `inpainting`. */
|
|
4186
4584
|
supportsMapScene = true;
|
|
@@ -4222,11 +4620,12 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
4222
4620
|
* is a file read.
|
|
4223
4621
|
*/
|
|
4224
4622
|
static cacheDir(namespace = "pixflux") {
|
|
4225
|
-
const dir =
|
|
4226
|
-
(0,
|
|
4623
|
+
const dir = import_node_path2.default.join(import_node_os.default.tmpdir(), `pixelkiln-${namespace}`);
|
|
4624
|
+
(0, import_node_fs3.mkdirSync)(dir, { recursive: true });
|
|
4227
4625
|
return dir;
|
|
4228
4626
|
}
|
|
4229
4627
|
estimate(spec) {
|
|
4628
|
+
if (spec.vfx) return { unit: "generations", amount: 0, candidates: 1 };
|
|
4230
4629
|
if (spec.batch?.role === "member") {
|
|
4231
4630
|
return { unit: "generations", amount: 0, candidates: 1 };
|
|
4232
4631
|
}
|
|
@@ -4284,6 +4683,9 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
4284
4683
|
};
|
|
4285
4684
|
}
|
|
4286
4685
|
validate(spec, styleImages) {
|
|
4686
|
+
if (spec.generator === "vfx" && styleImages.length) {
|
|
4687
|
+
throw new Error("PixelLab vfx takes no style images; an effect's look comes from its preset, parts, or recipe");
|
|
4688
|
+
}
|
|
4287
4689
|
const members = spec.revision?.sourceMembers;
|
|
4288
4690
|
if (spec.revision && members && !MEMBER_SET_REVISION_MODES.includes(spec.revision.mode)) {
|
|
4289
4691
|
throw new Error(
|
|
@@ -4568,7 +4970,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
4568
4970
|
}
|
|
4569
4971
|
}
|
|
4570
4972
|
if (endFrame) {
|
|
4571
|
-
const start = startFrame ?? (character.parentFile && (0,
|
|
4973
|
+
const start = startFrame ?? (character.parentFile && (0, import_node_fs3.existsSync)(character.parentFile) ? imageMetadata((0, import_node_fs3.readFileSync)(character.parentFile)) : null);
|
|
4572
4974
|
if (start && (start.width !== endFrame.width || start.height !== endFrame.height)) {
|
|
4573
4975
|
throw new Error(
|
|
4574
4976
|
`${label}: end frame is ${endFrame.width}x${endFrame.height} but the ${startFrame ? "start frame" : "character's rotation"} is ${start.width}x${start.height}; PixelLab interpolates between frames of one size`
|
|
@@ -4726,7 +5128,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
4726
5128
|
}
|
|
4727
5129
|
}
|
|
4728
5130
|
if (endFrame) {
|
|
4729
|
-
const start = startFrame ?? (object.parentFile && (0,
|
|
5131
|
+
const start = startFrame ?? (object.parentFile && (0, import_node_fs3.existsSync)(object.parentFile) ? imageMetadata((0, import_node_fs3.readFileSync)(object.parentFile)) : null);
|
|
4730
5132
|
if (start && (start.width !== endFrame.width || start.height !== endFrame.height)) {
|
|
4731
5133
|
throw new Error(
|
|
4732
5134
|
`${label}: end frame is ${endFrame.width}x${endFrame.height} but the ${startFrame ? "start frame" : "object's rotation"} is ${start.width}x${start.height}; PixelLab interpolates between frames of one size`
|
|
@@ -4781,13 +5183,13 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
4781
5183
|
);
|
|
4782
5184
|
}
|
|
4783
5185
|
const frames = parentOutputs.slice(0, 16).map((output) => {
|
|
4784
|
-
const bytes = (0,
|
|
5186
|
+
const bytes = (0, import_node_fs3.readFileSync)(output.path);
|
|
4785
5187
|
const dims = imageMetadata(bytes);
|
|
4786
5188
|
if (!dims) throw new Error(`${spec.styleId}/${spec.assetId}: outfit source frame is not a readable PNG: ${output.path}`);
|
|
4787
5189
|
return { base64: bytes.toString("base64"), format: "png", width: dims.width, height: dims.height };
|
|
4788
5190
|
});
|
|
4789
5191
|
const outfit = character.outfit;
|
|
4790
|
-
const referenceBytes = (0,
|
|
5192
|
+
const referenceBytes = (0, import_node_fs3.readFileSync)(outfit.reference.path);
|
|
4791
5193
|
if (sha256(referenceBytes) !== outfit.reference.sha256) {
|
|
4792
5194
|
throw new Error(`${spec.styleId}/${spec.assetId}: outfit reference changed after the manifest was resolved: ${outfit.reference.path}`);
|
|
4793
5195
|
}
|
|
@@ -4830,7 +5232,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
4830
5232
|
}
|
|
4831
5233
|
if (character.kind === "portrait") {
|
|
4832
5234
|
const parentFile = character.parentFile;
|
|
4833
|
-
const bytes = (0,
|
|
5235
|
+
const bytes = (0, import_node_fs3.readFileSync)(parentFile);
|
|
4834
5236
|
if (character.parentSha256 && sha256(bytes) !== character.parentSha256) {
|
|
4835
5237
|
throw new Error(`${spec.styleId}/${spec.assetId}: portrait source changed after the manifest was resolved: ${parentFile}`);
|
|
4836
5238
|
}
|
|
@@ -5036,8 +5438,8 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5036
5438
|
const sources = images.map((image, index) => {
|
|
5037
5439
|
if (!image.base64) throw new Error(`outfit transfer frame ${index} has no image data`);
|
|
5038
5440
|
const role = meta?.frameRoles?.[index] ?? void 0;
|
|
5039
|
-
const file =
|
|
5040
|
-
(0,
|
|
5441
|
+
const file = import_node_path2.default.join(dir, `${jobId}-${index}.png`);
|
|
5442
|
+
(0, import_node_fs3.writeFileSync)(file, Buffer.from(image.base64, "base64"));
|
|
5041
5443
|
return { url: `file://${file}`, ...role ? { role } : {} };
|
|
5042
5444
|
});
|
|
5043
5445
|
return {
|
|
@@ -5188,6 +5590,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5188
5590
|
async submit(spec, styleImages, context) {
|
|
5189
5591
|
this.validate(spec, styleImages);
|
|
5190
5592
|
if (spec.revision) return this.submitRevision(spec);
|
|
5593
|
+
if (spec.vfx) return this.submitVfx(spec, context);
|
|
5191
5594
|
if (spec.generator === "character") return this.submitCharacter(spec, styleImages, context);
|
|
5192
5595
|
if (spec.generator === "objectPro") return this.submitObjectPro(spec, styleImages, context);
|
|
5193
5596
|
if (spec.generator === "uiAsset") {
|
|
@@ -5241,7 +5644,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5241
5644
|
seed: spec.seed
|
|
5242
5645
|
});
|
|
5243
5646
|
const jobId = (0, import_node_crypto2.randomUUID)();
|
|
5244
|
-
(0,
|
|
5647
|
+
(0, import_node_fs3.writeFileSync)(import_node_path2.default.join(_PixelLabProvider.cacheDir(), `${jobId}.png`), png);
|
|
5245
5648
|
return { jobId };
|
|
5246
5649
|
}
|
|
5247
5650
|
if (spec.generator === "tiles") {
|
|
@@ -5361,7 +5764,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5361
5764
|
}
|
|
5362
5765
|
const sourceFiles = revision.sourceMembers ?? [{ file: revision.sourceFile, sha256: revision.sourceSha256, format: revision.sourceFormat }];
|
|
5363
5766
|
const images = sourceFiles.map((member) => {
|
|
5364
|
-
const bytes = (0,
|
|
5767
|
+
const bytes = (0, import_node_fs3.readFileSync)(member.file);
|
|
5365
5768
|
if (sha256(bytes) !== member.sha256) {
|
|
5366
5769
|
throw new Error(`${spec.styleId}/${spec.assetId}: revision source changed after the manifest was resolved`);
|
|
5367
5770
|
}
|
|
@@ -5376,7 +5779,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5376
5779
|
if (!revision.maskFile || !revision.maskSha256 || !revision.maskFormat) {
|
|
5377
5780
|
throw new Error(`${spec.styleId}/${spec.assetId}: revision mask is not ready`);
|
|
5378
5781
|
}
|
|
5379
|
-
const maskBytes = (0,
|
|
5782
|
+
const maskBytes = (0, import_node_fs3.readFileSync)(revision.maskFile);
|
|
5380
5783
|
if (sha256(maskBytes) !== revision.maskSha256) {
|
|
5381
5784
|
throw new Error(`${spec.styleId}/${spec.assetId}: revision mask changed after the manifest was resolved`);
|
|
5382
5785
|
}
|
|
@@ -5401,7 +5804,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5401
5804
|
if (!revision.maskFile || !revision.maskSha256 || !revision.maskFormat) {
|
|
5402
5805
|
throw new Error(`${spec.styleId}/${spec.assetId}: revision mask is not ready`);
|
|
5403
5806
|
}
|
|
5404
|
-
const maskBytes = (0,
|
|
5807
|
+
const maskBytes = (0, import_node_fs3.readFileSync)(revision.maskFile);
|
|
5405
5808
|
if (sha256(maskBytes) !== revision.maskSha256) {
|
|
5406
5809
|
throw new Error(`${spec.styleId}/${spec.assetId}: revision mask changed after the manifest was resolved`);
|
|
5407
5810
|
}
|
|
@@ -5422,7 +5825,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5422
5825
|
if (!revision.paletteImageSha256 || !revision.paletteImageFormat) {
|
|
5423
5826
|
throw new Error(`${spec.styleId}/${spec.assetId}: revision palette image is not ready`);
|
|
5424
5827
|
}
|
|
5425
|
-
const paletteBytes = (0,
|
|
5828
|
+
const paletteBytes = (0, import_node_fs3.readFileSync)(revision.paletteImageFile);
|
|
5426
5829
|
if (sha256(paletteBytes) !== revision.paletteImageSha256) {
|
|
5427
5830
|
throw new Error(`${spec.styleId}/${spec.assetId}: revision palette image changed after the manifest was resolved`);
|
|
5428
5831
|
}
|
|
@@ -5513,7 +5916,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5513
5916
|
if (!revision.keypointsFile || !revision.keypointsSha256 || !revision.skeleton) {
|
|
5514
5917
|
throw new Error(`${spec.styleId}/${spec.assetId}: revision keypoints are not ready`);
|
|
5515
5918
|
}
|
|
5516
|
-
const keypointsBytes = (0,
|
|
5919
|
+
const keypointsBytes = (0, import_node_fs3.readFileSync)(revision.keypointsFile);
|
|
5517
5920
|
if (sha256(keypointsBytes) !== revision.keypointsSha256) {
|
|
5518
5921
|
throw new Error(`${spec.styleId}/${spec.assetId}: revision keypoints changed after the manifest was resolved`);
|
|
5519
5922
|
}
|
|
@@ -5555,7 +5958,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5555
5958
|
if (!revision.lastFrameSha256 || !revision.lastFrameFormat) {
|
|
5556
5959
|
throw new Error(`${spec.styleId}/${spec.assetId}: revision last frame is not ready`);
|
|
5557
5960
|
}
|
|
5558
|
-
const bytes = (0,
|
|
5961
|
+
const bytes = (0, import_node_fs3.readFileSync)(revision.lastFrameFile);
|
|
5559
5962
|
if (sha256(bytes) !== revision.lastFrameSha256) {
|
|
5560
5963
|
throw new Error(`${spec.styleId}/${spec.assetId}: revision last frame changed after the manifest was resolved`);
|
|
5561
5964
|
}
|
|
@@ -5570,11 +5973,11 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5570
5973
|
cacheCleanupResult(pngs, usage, roles, fps) {
|
|
5571
5974
|
const jobId = (0, import_node_crypto2.randomUUID)();
|
|
5572
5975
|
if (!roles) {
|
|
5573
|
-
(0,
|
|
5976
|
+
(0, import_node_fs3.writeFileSync)(import_node_path2.default.join(_PixelLabProvider.cacheDir(), `${jobId}.png`), pngs[0]);
|
|
5574
5977
|
return { jobId, metadata: { revisionUsage: usage } };
|
|
5575
5978
|
}
|
|
5576
5979
|
for (const [index, png] of pngs.entries()) {
|
|
5577
|
-
(0,
|
|
5980
|
+
(0, import_node_fs3.writeFileSync)(import_node_path2.default.join(_PixelLabProvider.cacheDir(), `${jobId}-${index}.png`), png);
|
|
5578
5981
|
}
|
|
5579
5982
|
return { jobId, metadata: { revisionUsage: usage, revisionMembers: roles, ...fps != null ? { revisionFps: fps } : {} } };
|
|
5580
5983
|
}
|
|
@@ -5602,10 +6005,11 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5602
6005
|
return this.pollAnimateRevision(jobId, context);
|
|
5603
6006
|
}
|
|
5604
6007
|
if (context?.spec?.revision) return this.pollRevision(jobId);
|
|
6008
|
+
if (generator === "vfx") return this.pollVfx(jobId, context);
|
|
5605
6009
|
if (generator === "imageProFlash") return this.pollRevision(jobId);
|
|
5606
6010
|
if (generator === "pixflux") {
|
|
5607
|
-
const file =
|
|
5608
|
-
if ((0,
|
|
6011
|
+
const file = import_node_path2.default.join(_PixelLabProvider.cacheDir(), `${jobId}.png`);
|
|
6012
|
+
if ((0, import_node_fs3.existsSync)(file)) {
|
|
5609
6013
|
const sourceUrl = `file://${file}`;
|
|
5610
6014
|
return { status: "ready", objectId: jobId, sourceUrl, sources: [{ url: sourceUrl }] };
|
|
5611
6015
|
}
|
|
@@ -5648,6 +6052,120 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5648
6052
|
* background job to ask afterwards the way `billedForJob` does for async
|
|
5649
6053
|
* work.
|
|
5650
6054
|
*/
|
|
6055
|
+
/**
|
|
6056
|
+
* Render a `vfx` asset with the VFX workbench, which is synchronous and
|
|
6057
|
+
* free. The frames are cut from the exported sheet and parked in the
|
|
6058
|
+
* cache like pixflux's bytes; the export's download folder is recorded so
|
|
6059
|
+
* `poll` can cut them again if the cache is cleared.
|
|
6060
|
+
*
|
|
6061
|
+
* A regeneration exports over the effect this asset rendered last time, so
|
|
6062
|
+
* My effects keeps one entry per asset, unless that effect has changed on
|
|
6063
|
+
* pixellab.ai since (someone moved its sliders) or is gone. Then a new
|
|
6064
|
+
* effect is saved and the edited one is left alone.
|
|
6065
|
+
*/
|
|
6066
|
+
async submitVfx(spec, context) {
|
|
6067
|
+
const vfx = spec.vfx;
|
|
6068
|
+
if (!this.mcp) throw new Error("PixelLab vfx renders over PixelLab's MCP server, which this provider was built without");
|
|
6069
|
+
let sprite;
|
|
6070
|
+
if (vfx.sprite && "reference" in vfx.sprite) {
|
|
6071
|
+
sprite = vfx.sprite.reference;
|
|
6072
|
+
} else if (vfx.sprite) {
|
|
6073
|
+
const { file } = vfx.sprite;
|
|
6074
|
+
if (!(0, import_node_fs3.existsSync)(file)) throw new Error(`vfx sprite does not exist yet: ${file}`);
|
|
6075
|
+
const bytes = (0, import_node_fs3.readFileSync)(file);
|
|
6076
|
+
if (sha256(bytes) !== vfx.sprite.sha256) {
|
|
6077
|
+
throw new Error(`vfx sprite changed after the manifest was resolved: ${file}; run the command again`);
|
|
6078
|
+
}
|
|
6079
|
+
const format = imageMetadata(bytes)?.format ?? "png";
|
|
6080
|
+
sprite = `data:image/${format};base64,${bytes.toString("base64")}`;
|
|
6081
|
+
}
|
|
6082
|
+
const previous = context?.replacedMetadata?.vfx ?? context?.lastGenerationMetadata?.vfx;
|
|
6083
|
+
let replace;
|
|
6084
|
+
let newEffect;
|
|
6085
|
+
if (previous?.effectId) {
|
|
6086
|
+
const upstream = await readSavedVfxRecipe(this.mcp, previous.effectId);
|
|
6087
|
+
if (!upstream) newEffect = `effect ${previous.effectId} is no longer in My effects`;
|
|
6088
|
+
else if (!previous.recipe || vfxRecipeSha256(upstream) !== vfxRecipeSha256(previous.recipe)) {
|
|
6089
|
+
newEffect = `effect ${previous.effectId} changed on pixellab.ai since it was rendered; it was kept`;
|
|
6090
|
+
} else replace = previous.effectId;
|
|
6091
|
+
}
|
|
6092
|
+
const jobId = (0, import_node_crypto2.randomUUID)();
|
|
6093
|
+
const facts = (exported2, name2) => ({
|
|
6094
|
+
effectId: exported2.effectId,
|
|
6095
|
+
editUrl: exported2.editUrl,
|
|
6096
|
+
name: name2,
|
|
6097
|
+
imageId: exported2.imageId,
|
|
6098
|
+
width: exported2.width,
|
|
6099
|
+
height: exported2.height,
|
|
6100
|
+
frames: exported2.frames,
|
|
6101
|
+
fps: exported2.fps,
|
|
6102
|
+
loop: exported2.loop,
|
|
6103
|
+
anchor: exported2.anchor,
|
|
6104
|
+
downloadBase: exported2.downloadBase,
|
|
6105
|
+
files: exported2.files,
|
|
6106
|
+
replaced: replace ?? null,
|
|
6107
|
+
...newEffect ? { newEffect } : {}
|
|
6108
|
+
});
|
|
6109
|
+
const { exported, recipe, name } = await runVfxWorkbench(this.mcp, {
|
|
6110
|
+
preset: vfx.preset,
|
|
6111
|
+
recipe: vfx.recipe?.recipe,
|
|
6112
|
+
parts: vfx.parts,
|
|
6113
|
+
sprite,
|
|
6114
|
+
fps: vfx.fps,
|
|
6115
|
+
name: vfx.name,
|
|
6116
|
+
defaultName: vfx.name,
|
|
6117
|
+
replace,
|
|
6118
|
+
notes: `pixelkiln ${spec.styleId}/${spec.assetId}`,
|
|
6119
|
+
// The effect exists from here on; record it before anything else can fail.
|
|
6120
|
+
onExported: (done, effectName) => context?.checkpoint({ jobId, complete: true, metadata: { vfx: facts(done, effectName) } })
|
|
6121
|
+
});
|
|
6122
|
+
await this.cacheVfxFrames(jobId, exported);
|
|
6123
|
+
return { jobId, metadata: { vfx: { ...facts(exported, name), recipe } } };
|
|
6124
|
+
}
|
|
6125
|
+
/** Cut an export's sheet into frames, with a sidecar `poll` reads without any lock metadata. */
|
|
6126
|
+
async cacheVfxFrames(jobId, exported) {
|
|
6127
|
+
const sheetFile = exported.files.find((file) => /\.png$/i.test(file));
|
|
6128
|
+
const atlasFile = exported.files.find((file) => /\.json$/i.test(file));
|
|
6129
|
+
if (!sheetFile) throw new Error(`PixelLab's vfx export listed no spritesheet: ${exported.files.join(", ")}`);
|
|
6130
|
+
const sheet = await this.client.download(`${exported.downloadBase}${encodeURIComponent(sheetFile)}`);
|
|
6131
|
+
const atlas = atlasFile ? JSON.parse((await this.client.download(`${exported.downloadBase}${encodeURIComponent(atlasFile)}`)).toString("utf8")) : void 0;
|
|
6132
|
+
const frames = sliceVfxSheet(sheet, exported, atlas);
|
|
6133
|
+
const dir = _PixelLabProvider.cacheDir("vfx");
|
|
6134
|
+
frames.forEach((frame, index) => (0, import_node_fs3.writeFileSync)(import_node_path2.default.join(dir, `${jobId}-${index}.png`), frame));
|
|
6135
|
+
(0, import_node_fs3.writeFileSync)(
|
|
6136
|
+
import_node_path2.default.join(dir, `${jobId}.json`),
|
|
6137
|
+
JSON.stringify({ effectId: exported.effectId, count: frames.length, fps: exported.fps, loop: exported.loop })
|
|
6138
|
+
);
|
|
6139
|
+
}
|
|
6140
|
+
async pollVfx(jobId, context) {
|
|
6141
|
+
const dir = _PixelLabProvider.cacheDir("vfx");
|
|
6142
|
+
const sidecar = import_node_path2.default.join(dir, `${jobId}.json`);
|
|
6143
|
+
const cached = () => {
|
|
6144
|
+
if (!(0, import_node_fs3.existsSync)(sidecar)) return null;
|
|
6145
|
+
const facts2 = JSON.parse((0, import_node_fs3.readFileSync)(sidecar, "utf8"));
|
|
6146
|
+
const files2 = Array.from({ length: facts2.count }, (_, index) => import_node_path2.default.join(dir, `${jobId}-${index}.png`));
|
|
6147
|
+
return files2.every((file) => (0, import_node_fs3.existsSync)(file)) ? { facts: facts2, files: files2 } : null;
|
|
6148
|
+
};
|
|
6149
|
+
let found = cached();
|
|
6150
|
+
const recorded = context?.metadata?.vfx;
|
|
6151
|
+
if (!found && recorded?.downloadBase && Array.isArray(recorded.files)) {
|
|
6152
|
+
await this.cacheVfxFrames(jobId, recorded);
|
|
6153
|
+
found = cached();
|
|
6154
|
+
}
|
|
6155
|
+
if (!found) {
|
|
6156
|
+
return { status: "failed", error: "vfx frames are no longer cached locally; re-run submit for this asset (free)" };
|
|
6157
|
+
}
|
|
6158
|
+
const { facts, files } = found;
|
|
6159
|
+
const sources = files.length === 1 ? [{ url: `file://${files[0]}` }] : files.map((file, index) => ({ url: `file://${file}`, role: `frame-${String(index).padStart(2, "0")}` }));
|
|
6160
|
+
return {
|
|
6161
|
+
status: "ready",
|
|
6162
|
+
objectId: facts.effectId,
|
|
6163
|
+
sourceUrl: sources[0].url,
|
|
6164
|
+
sources,
|
|
6165
|
+
...files.length > 1 ? { metadata: { frameSet: { fps: facts.fps, count: files.length, loop: facts.loop } } } : {},
|
|
6166
|
+
billed: null
|
|
6167
|
+
};
|
|
6168
|
+
}
|
|
5651
6169
|
async pollCachedRevision(jobId, context) {
|
|
5652
6170
|
const usage = context?.metadata?.revisionUsage;
|
|
5653
6171
|
const roles = context?.metadata?.revisionMembers;
|
|
@@ -5657,10 +6175,10 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5657
6175
|
};
|
|
5658
6176
|
if (Array.isArray(roles) && roles.length) {
|
|
5659
6177
|
const sources = roles.map((role, index) => ({
|
|
5660
|
-
url: `file://${
|
|
6178
|
+
url: `file://${import_node_path2.default.join(_PixelLabProvider.cacheDir(), `${jobId}-${index}.png`)}`,
|
|
5661
6179
|
role: String(role)
|
|
5662
6180
|
}));
|
|
5663
|
-
if (sources.some((source) => !(0,
|
|
6181
|
+
if (sources.some((source) => !(0, import_node_fs3.existsSync)(source.url.slice("file://".length)))) return gone;
|
|
5664
6182
|
const fps = context?.metadata?.revisionFps;
|
|
5665
6183
|
const frames = sources.every((source) => source.role.startsWith("frame-"));
|
|
5666
6184
|
return {
|
|
@@ -5672,8 +6190,8 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5672
6190
|
billed: billedFromUsage(usage)
|
|
5673
6191
|
};
|
|
5674
6192
|
}
|
|
5675
|
-
const file =
|
|
5676
|
-
if (!(0,
|
|
6193
|
+
const file = import_node_path2.default.join(_PixelLabProvider.cacheDir(), `${jobId}.png`);
|
|
6194
|
+
if (!(0, import_node_fs3.existsSync)(file)) return gone;
|
|
5677
6195
|
const sourceUrl = `file://${file}`;
|
|
5678
6196
|
return { status: "ready", objectId: jobId, sourceUrl, sources: [{ url: sourceUrl }], billed: billedFromUsage(usage) };
|
|
5679
6197
|
}
|
|
@@ -5729,8 +6247,8 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5729
6247
|
}
|
|
5730
6248
|
const base64 = extractBase64(item);
|
|
5731
6249
|
if (base64) {
|
|
5732
|
-
const file =
|
|
5733
|
-
(0,
|
|
6250
|
+
const file = import_node_path2.default.join(_PixelLabProvider.cacheDir(), `${jobId}-${index}.png`);
|
|
6251
|
+
(0, import_node_fs3.writeFileSync)(file, Buffer.from(base64, "base64"));
|
|
5734
6252
|
urls.push(`file://${file}`);
|
|
5735
6253
|
continue;
|
|
5736
6254
|
}
|
|
@@ -5780,8 +6298,8 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5780
6298
|
const candidate = done[key];
|
|
5781
6299
|
const base64 = extractBase64(candidate);
|
|
5782
6300
|
if (base64) {
|
|
5783
|
-
const file =
|
|
5784
|
-
(0,
|
|
6301
|
+
const file = import_node_path2.default.join(_PixelLabProvider.cacheDir(), `${jobId}.png`);
|
|
6302
|
+
(0, import_node_fs3.writeFileSync)(file, Buffer.from(base64, "base64"));
|
|
5785
6303
|
const sourceUrl = `file://${file}`;
|
|
5786
6304
|
return { status: "ready", objectId: jobId, sourceUrl, sources: [{ url: sourceUrl }], billed };
|
|
5787
6305
|
}
|
|
@@ -5789,8 +6307,8 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5789
6307
|
if (Array.isArray(done.images) && done.images.length) {
|
|
5790
6308
|
const base64 = extractBase64(done.images[0]);
|
|
5791
6309
|
if (base64) {
|
|
5792
|
-
const file =
|
|
5793
|
-
(0,
|
|
6310
|
+
const file = import_node_path2.default.join(_PixelLabProvider.cacheDir(), `${jobId}.png`);
|
|
6311
|
+
(0, import_node_fs3.writeFileSync)(file, Buffer.from(base64, "base64"));
|
|
5794
6312
|
const sourceUrl = `file://${file}`;
|
|
5795
6313
|
return { status: "ready", objectId: jobId, sourceUrl, sources: [{ url: sourceUrl }], billed };
|
|
5796
6314
|
}
|
|
@@ -5823,8 +6341,8 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5823
6341
|
for (const [index, candidate] of done.images.entries()) {
|
|
5824
6342
|
const base64 = extractBase64(candidate);
|
|
5825
6343
|
if (!base64) continue;
|
|
5826
|
-
const file =
|
|
5827
|
-
(0,
|
|
6344
|
+
const file = import_node_path2.default.join(_PixelLabProvider.cacheDir(), `${jobId}-${index}.png`);
|
|
6345
|
+
(0, import_node_fs3.writeFileSync)(file, Buffer.from(base64, "base64"));
|
|
5828
6346
|
urls.push(`file://${file}`);
|
|
5829
6347
|
}
|
|
5830
6348
|
if (urls.length) return { status: "review", candidateUrls: urls, billed };
|
|
@@ -5853,8 +6371,8 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5853
6371
|
for (const [index, candidate] of images.entries()) {
|
|
5854
6372
|
const base64 = extractBase64(candidate);
|
|
5855
6373
|
if (!base64) continue;
|
|
5856
|
-
const file =
|
|
5857
|
-
(0,
|
|
6374
|
+
const file = import_node_path2.default.join(_PixelLabProvider.cacheDir(), `${jobId}-${index}.png`);
|
|
6375
|
+
(0, import_node_fs3.writeFileSync)(file, Buffer.from(base64, "base64"));
|
|
5858
6376
|
urls.push(`file://${file}`);
|
|
5859
6377
|
}
|
|
5860
6378
|
if (urls.length === 1) return { status: "ready", objectId: jobId, sourceUrl: urls[0], sources: [{ url: urls[0] }], billed };
|
|
@@ -5951,8 +6469,8 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5951
6469
|
for (const [index, tile] of tileset.tiles.entries()) {
|
|
5952
6470
|
const slug2 = tile.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "tile";
|
|
5953
6471
|
const role = `tile-${String(index).padStart(2, "0")}-${slug2}`;
|
|
5954
|
-
const file =
|
|
5955
|
-
(0,
|
|
6472
|
+
const file = import_node_path2.default.join(_PixelLabProvider.cacheDir(), `${tilesetId}-${index}.png`);
|
|
6473
|
+
(0, import_node_fs3.writeFileSync)(file, Buffer.from(tile.image.base64, "base64"));
|
|
5956
6474
|
sources.push({ url: `file://${file}`, role });
|
|
5957
6475
|
terrainTiles.push({ role, corners: tile.corners, pattern4x4: tile.pattern_4x4 });
|
|
5958
6476
|
}
|
|
@@ -5981,8 +6499,8 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5981
6499
|
const { image, usage } = await this.client.getIsometricTile(tileId);
|
|
5982
6500
|
const backgroundJobId = context?.metadata?.backgroundJobId;
|
|
5983
6501
|
const billed = billedFromUsage(usage) ?? await this.billedForJob(backgroundJobId);
|
|
5984
|
-
const file =
|
|
5985
|
-
(0,
|
|
6502
|
+
const file = import_node_path2.default.join(_PixelLabProvider.cacheDir(), `${tileId}.png`);
|
|
6503
|
+
(0, import_node_fs3.writeFileSync)(file, Buffer.from(image.base64, "base64"));
|
|
5986
6504
|
return {
|
|
5987
6505
|
status: "ready",
|
|
5988
6506
|
objectId: tileId,
|
|
@@ -6025,8 +6543,8 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
6025
6543
|
return { objectId: `${jobId}#${index}`, sourceUrl: url };
|
|
6026
6544
|
}
|
|
6027
6545
|
if (generator === "imagePro" || generator === "uiElement") {
|
|
6028
|
-
const file =
|
|
6029
|
-
if (!(0,
|
|
6546
|
+
const file = import_node_path2.default.join(_PixelLabProvider.cacheDir(), `${jobId}-${index}.png`);
|
|
6547
|
+
if (!(0, import_node_fs3.existsSync)(file)) throw new Error(`${generator} job ${jobId} has no cached candidate at index ${index}`);
|
|
6030
6548
|
return { objectId: `${jobId}#${index}`, sourceUrl: `file://${file}` };
|
|
6031
6549
|
}
|
|
6032
6550
|
const promoted = await this.client.selectFrames(jobId, [index], commonTag);
|
|
@@ -6038,7 +6556,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
6038
6556
|
return { objectId, sourceUrl: firstUrl(obj?.rotation_urls) ?? obj?.preview_url ?? null };
|
|
6039
6557
|
}
|
|
6040
6558
|
async download(url) {
|
|
6041
|
-
if (url.startsWith("file://")) return (0,
|
|
6559
|
+
if (url.startsWith("file://")) return (0, import_node_fs3.readFileSync)(url.slice("file://".length));
|
|
6042
6560
|
return this.client.download(url);
|
|
6043
6561
|
}
|
|
6044
6562
|
async balance() {
|
|
@@ -6160,7 +6678,7 @@ function extractBase64(value) {
|
|
|
6160
6678
|
function readReference(spec, reference) {
|
|
6161
6679
|
const images = {};
|
|
6162
6680
|
for (const [direction, image] of Object.entries(reference)) {
|
|
6163
|
-
const bytes = (0,
|
|
6681
|
+
const bytes = (0, import_node_fs3.readFileSync)(image.path);
|
|
6164
6682
|
if (sha256(bytes) !== image.sha256) {
|
|
6165
6683
|
throw new Error(`${spec.styleId}/${spec.assetId}: reference (${direction}) changed after the manifest was resolved: ${image.path}`);
|
|
6166
6684
|
}
|
|
@@ -6177,7 +6695,7 @@ function requireStyleObjectId(spec, context) {
|
|
|
6177
6695
|
return context.styleObjectId;
|
|
6178
6696
|
}
|
|
6179
6697
|
function readPose(spec, what, image) {
|
|
6180
|
-
const bytes = (0,
|
|
6698
|
+
const bytes = (0, import_node_fs3.readFileSync)(image.path);
|
|
6181
6699
|
if (sha256(bytes) !== image.sha256) {
|
|
6182
6700
|
throw new Error(`${spec.styleId}/${spec.assetId}: ${what} changed after the manifest was resolved: ${image.path}`);
|
|
6183
6701
|
}
|
|
@@ -6242,7 +6760,7 @@ function findObjectAnimation(object, name, direction, animationId) {
|
|
|
6242
6760
|
function mapScenePayload(spec) {
|
|
6243
6761
|
const scene = spec.scene;
|
|
6244
6762
|
const read = (file, hash, label) => {
|
|
6245
|
-
const bytes = (0,
|
|
6763
|
+
const bytes = (0, import_node_fs3.readFileSync)(file);
|
|
6246
6764
|
if (!hash || sha256(bytes) !== hash) {
|
|
6247
6765
|
throw new Error(`${spec.styleId}/${spec.assetId}: ${label} changed after the manifest was resolved`);
|
|
6248
6766
|
}
|
|
@@ -6303,9 +6821,9 @@ var RetroDiffusionClient = class {
|
|
|
6303
6821
|
if (!response.ok) throw new ProviderError("retrodiffusion", `Retro Diffusion download failed (${response.status})`, { status: response.status });
|
|
6304
6822
|
return Buffer.from(await response.arrayBuffer());
|
|
6305
6823
|
}
|
|
6306
|
-
async call(
|
|
6824
|
+
async call(path39, init = {}) {
|
|
6307
6825
|
if (!this.token) throw new Error("RD_API_KEY is not set");
|
|
6308
|
-
const response = await this.request(`${this.baseUrl}${
|
|
6826
|
+
const response = await this.request(`${this.baseUrl}${path39}`, {
|
|
6309
6827
|
...init,
|
|
6310
6828
|
headers: {
|
|
6311
6829
|
"Content-Type": "application/json",
|
|
@@ -6615,9 +7133,9 @@ function retroError(value) {
|
|
|
6615
7133
|
|
|
6616
7134
|
// src/providers/scenario.ts
|
|
6617
7135
|
var import_node_crypto3 = require("crypto");
|
|
6618
|
-
var
|
|
7136
|
+
var import_node_fs4 = require("fs");
|
|
6619
7137
|
var import_node_os2 = __toESM(require("os"), 1);
|
|
6620
|
-
var
|
|
7138
|
+
var import_node_path3 = __toESM(require("path"), 1);
|
|
6621
7139
|
var DEFAULT_BASE_URL2 = "https://api.cloud.scenario.com/v1";
|
|
6622
7140
|
var SOURCE_PROTOCOL2 = "scenario:";
|
|
6623
7141
|
var SUBMIT_RETRIES = 8;
|
|
@@ -7000,7 +7518,7 @@ var ScenarioProvider = class _ScenarioProvider {
|
|
|
7000
7518
|
}
|
|
7001
7519
|
};
|
|
7002
7520
|
function uploadCachePath() {
|
|
7003
|
-
return process.env.SCENARIO_UPLOAD_CACHE ??
|
|
7521
|
+
return process.env.SCENARIO_UPLOAD_CACHE ?? import_node_path3.default.join(import_node_os2.default.tmpdir(), "pixelkiln-scenario", "uploads.json");
|
|
7004
7522
|
}
|
|
7005
7523
|
function uploadCacheKey(baseUrl, projectId, digest2) {
|
|
7006
7524
|
return (0, import_node_crypto3.createHash)("sha256").update(`${baseUrl}
|
|
@@ -7009,7 +7527,7 @@ ${digest2}`).digest("hex");
|
|
|
7009
7527
|
}
|
|
7010
7528
|
function readUploadCache() {
|
|
7011
7529
|
try {
|
|
7012
|
-
const value = JSON.parse((0,
|
|
7530
|
+
const value = JSON.parse((0, import_node_fs4.readFileSync)(uploadCachePath(), "utf8"));
|
|
7013
7531
|
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
7014
7532
|
} catch {
|
|
7015
7533
|
return {};
|
|
@@ -7018,10 +7536,10 @@ function readUploadCache() {
|
|
|
7018
7536
|
function writeUploadCache(key, assetId) {
|
|
7019
7537
|
try {
|
|
7020
7538
|
const file = uploadCachePath();
|
|
7021
|
-
(0,
|
|
7539
|
+
(0, import_node_fs4.mkdirSync)(import_node_path3.default.dirname(file), { recursive: true });
|
|
7022
7540
|
const tmp = `${file}.${process.pid}.tmp`;
|
|
7023
|
-
(0,
|
|
7024
|
-
(0,
|
|
7541
|
+
(0, import_node_fs4.writeFileSync)(tmp, JSON.stringify({ ...readUploadCache(), [key]: assetId }));
|
|
7542
|
+
(0, import_node_fs4.renameSync)(tmp, file);
|
|
7025
7543
|
} catch {
|
|
7026
7544
|
}
|
|
7027
7545
|
}
|
|
@@ -7048,7 +7566,7 @@ function readRevisionSource(spec) {
|
|
|
7048
7566
|
if (!revision.sourceFile || !revision.sourceSha256 || !revision.sourceFormat) {
|
|
7049
7567
|
throw new Error(`${spec.styleId}/${spec.assetId}: revision source is not ready`);
|
|
7050
7568
|
}
|
|
7051
|
-
const bytes = (0,
|
|
7569
|
+
const bytes = (0, import_node_fs4.readFileSync)(revision.sourceFile);
|
|
7052
7570
|
if (sha256(bytes) !== revision.sourceSha256) {
|
|
7053
7571
|
throw new Error(`${spec.styleId}/${spec.assetId}: revision source changed after the manifest was resolved`);
|
|
7054
7572
|
}
|
|
@@ -7218,8 +7736,8 @@ function redactScenarioError(value, secrets) {
|
|
|
7218
7736
|
|
|
7219
7737
|
// src/providers/comfyui.ts
|
|
7220
7738
|
var import_node_crypto4 = require("crypto");
|
|
7221
|
-
var
|
|
7222
|
-
var
|
|
7739
|
+
var import_promises2 = require("fs/promises");
|
|
7740
|
+
var import_node_path4 = __toESM(require("path"), 1);
|
|
7223
7741
|
var DEFAULT_BASE_URL3 = "http://127.0.0.1:8188";
|
|
7224
7742
|
var BUILTIN_BINDINGS = /* @__PURE__ */ new Set([
|
|
7225
7743
|
"prompt",
|
|
@@ -7356,10 +7874,10 @@ var ComfyUIProvider = class _ComfyUIProvider {
|
|
|
7356
7874
|
}
|
|
7357
7875
|
async resolveOptions(value, context) {
|
|
7358
7876
|
const options = parseOptions(value);
|
|
7359
|
-
const workflowPath =
|
|
7877
|
+
const workflowPath = import_node_path4.default.resolve(context.root, options.workflowFile);
|
|
7360
7878
|
let raw;
|
|
7361
7879
|
try {
|
|
7362
|
-
raw = await (0,
|
|
7880
|
+
raw = await (0, import_promises2.readFile)(workflowPath, "utf8");
|
|
7363
7881
|
} catch (err) {
|
|
7364
7882
|
throw new Error(
|
|
7365
7883
|
`ComfyUI workflow for style "${context.styleId}" could not be read at ${workflowPath}: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -7411,10 +7929,10 @@ var ComfyUIProvider = class _ComfyUIProvider {
|
|
|
7411
7929
|
if (typeof item !== "string") {
|
|
7412
7930
|
throw new Error(`ComfyUI ${label} must be a manifest-relative PNG or JPEG path`);
|
|
7413
7931
|
}
|
|
7414
|
-
const file =
|
|
7932
|
+
const file = import_node_path4.default.resolve(context.root, item);
|
|
7415
7933
|
let bytes;
|
|
7416
7934
|
try {
|
|
7417
|
-
bytes = await (0,
|
|
7935
|
+
bytes = await (0, import_promises2.readFile)(file);
|
|
7418
7936
|
} catch (error) {
|
|
7419
7937
|
throw new Error(
|
|
7420
7938
|
`ComfyUI ${label} could not be read at ${file}: ${error instanceof Error ? error.message : String(error)}`
|
|
@@ -7571,7 +8089,7 @@ var ComfyUIProvider = class _ComfyUIProvider {
|
|
|
7571
8089
|
setBinding(workflow2, binding, input);
|
|
7572
8090
|
return;
|
|
7573
8091
|
}
|
|
7574
|
-
const bytes = await (0,
|
|
8092
|
+
const bytes = await (0, import_promises2.readFile)(input.path);
|
|
7575
8093
|
if (sha256(bytes) !== input.sha256) {
|
|
7576
8094
|
throw new Error(`ComfyUI provider input "${name}" changed before upload`);
|
|
7577
8095
|
}
|
|
@@ -7638,7 +8156,7 @@ var ComfyUIProvider = class _ComfyUIProvider {
|
|
|
7638
8156
|
if (!spec.revision.sourceSha256 || !spec.revision.sourceFormat) {
|
|
7639
8157
|
throw new Error("ComfyUI revision source is not ready");
|
|
7640
8158
|
}
|
|
7641
|
-
const sourceBytes = await (0,
|
|
8159
|
+
const sourceBytes = await (0, import_promises2.readFile)(spec.revision.sourceFile);
|
|
7642
8160
|
if (sha256(sourceBytes) !== spec.revision.sourceSha256) {
|
|
7643
8161
|
throw new Error("ComfyUI revision source changed before upload");
|
|
7644
8162
|
}
|
|
@@ -7652,7 +8170,7 @@ var ComfyUIProvider = class _ComfyUIProvider {
|
|
|
7652
8170
|
if (!spec.revision.maskSha256 || !spec.revision.maskFormat) {
|
|
7653
8171
|
throw new Error("ComfyUI revision mask is not ready");
|
|
7654
8172
|
}
|
|
7655
|
-
const maskBytes = await (0,
|
|
8173
|
+
const maskBytes = await (0, import_promises2.readFile)(spec.revision.maskFile);
|
|
7656
8174
|
if (sha256(maskBytes) !== spec.revision.maskSha256) {
|
|
7657
8175
|
throw new Error("ComfyUI revision mask changed before upload");
|
|
7658
8176
|
}
|
|
@@ -8372,9 +8890,9 @@ registerProvider({
|
|
|
8372
8890
|
});
|
|
8373
8891
|
|
|
8374
8892
|
// src/manifest.ts
|
|
8375
|
-
var
|
|
8376
|
-
var
|
|
8377
|
-
var
|
|
8893
|
+
var import_promises4 = require("fs/promises");
|
|
8894
|
+
var import_node_fs7 = require("fs");
|
|
8895
|
+
var import_node_path7 = __toESM(require("path"), 1);
|
|
8378
8896
|
|
|
8379
8897
|
// src/loop-directions.ts
|
|
8380
8898
|
var SINGLE_OUTPUT_FIELDS = ["file", "cell", "remoteId", "source", "sourceByStyle", "outputRole"];
|
|
@@ -8462,17 +8980,17 @@ function isObject2(value) {
|
|
|
8462
8980
|
}
|
|
8463
8981
|
|
|
8464
8982
|
// src/outputs.ts
|
|
8465
|
-
var
|
|
8466
|
-
var
|
|
8983
|
+
var import_node_fs6 = require("fs");
|
|
8984
|
+
var import_node_path6 = __toESM(require("path"), 1);
|
|
8467
8985
|
|
|
8468
8986
|
// src/lock.ts
|
|
8469
|
-
var
|
|
8470
|
-
var
|
|
8471
|
-
var
|
|
8987
|
+
var import_promises3 = require("fs/promises");
|
|
8988
|
+
var import_node_fs5 = require("fs");
|
|
8989
|
+
var import_node_path5 = __toESM(require("path"), 1);
|
|
8472
8990
|
async function loadLock(lockPath) {
|
|
8473
|
-
if (!(0,
|
|
8991
|
+
if (!(0, import_node_fs5.existsSync)(lockPath)) return { version: 2, entries: {} };
|
|
8474
8992
|
try {
|
|
8475
|
-
return parseLock(JSON.parse(await (0,
|
|
8993
|
+
return parseLock(JSON.parse(await (0, import_promises3.readFile)(lockPath, "utf8")));
|
|
8476
8994
|
} catch (err) {
|
|
8477
8995
|
throw new ProjectError(
|
|
8478
8996
|
`Lockfile at ${lockPath} is malformed:
|
|
@@ -8485,7 +9003,7 @@ var saveQueues = /* @__PURE__ */ new Map();
|
|
|
8485
9003
|
var dirtyPatches = /* @__PURE__ */ new WeakMap();
|
|
8486
9004
|
var dirtyDeletes = /* @__PURE__ */ new WeakMap();
|
|
8487
9005
|
function saveLock(lockPath, lock) {
|
|
8488
|
-
const queueKey =
|
|
9006
|
+
const queueKey = import_node_path5.default.resolve(lockPath);
|
|
8489
9007
|
const previous = saveQueues.get(queueKey) ?? Promise.resolve();
|
|
8490
9008
|
const next = previous.catch(() => {
|
|
8491
9009
|
}).then(() => writeLockNow(lockPath, lock));
|
|
@@ -8497,7 +9015,7 @@ function saveLock(lockPath, lock) {
|
|
|
8497
9015
|
return next;
|
|
8498
9016
|
}
|
|
8499
9017
|
async function writeLockNow(lockPath, lock) {
|
|
8500
|
-
await (0,
|
|
9018
|
+
await (0, import_promises3.mkdir)(import_node_path5.default.dirname(import_node_path5.default.resolve(lockPath)), { recursive: true });
|
|
8501
9019
|
const release = await acquireFileLock(lockPath);
|
|
8502
9020
|
try {
|
|
8503
9021
|
await writeLockWhileHeld(lockPath, lock);
|
|
@@ -8507,9 +9025,9 @@ async function writeLockNow(lockPath, lock) {
|
|
|
8507
9025
|
}
|
|
8508
9026
|
async function writeLockWhileHeld(file, lock) {
|
|
8509
9027
|
let disk = { version: 2, entries: {} };
|
|
8510
|
-
if ((0,
|
|
9028
|
+
if ((0, import_node_fs5.existsSync)(file)) {
|
|
8511
9029
|
try {
|
|
8512
|
-
disk = parseLock(JSON.parse(await (0,
|
|
9030
|
+
disk = parseLock(JSON.parse(await (0, import_promises3.readFile)(file, "utf8")));
|
|
8513
9031
|
} catch (err) {
|
|
8514
9032
|
throw new ProjectError(
|
|
8515
9033
|
`Refusing to overwrite malformed lockfile at ${file}: ${err instanceof Error ? err.message : String(err)}`,
|
|
@@ -8538,10 +9056,10 @@ async function writeLockWhileHeld(file, lock) {
|
|
|
8538
9056
|
}
|
|
8539
9057
|
const tmp = `${file}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
8540
9058
|
try {
|
|
8541
|
-
await (0,
|
|
8542
|
-
await (0,
|
|
9059
|
+
await (0, import_promises3.writeFile)(tmp, JSON.stringify({ version: 2, entries: sorted }, null, 2) + "\n");
|
|
9060
|
+
await (0, import_promises3.rename)(tmp, file);
|
|
8543
9061
|
} finally {
|
|
8544
|
-
await (0,
|
|
9062
|
+
await (0, import_promises3.rm)(tmp, { force: true });
|
|
8545
9063
|
}
|
|
8546
9064
|
const currentDirty = dirtyPatches.get(lock);
|
|
8547
9065
|
if (currentDirty) {
|
|
@@ -8604,18 +9122,18 @@ async function acquireFileLock(file) {
|
|
|
8604
9122
|
const deadline = Date.now() + FILE_LOCK_TIMEOUT_MS;
|
|
8605
9123
|
for (; ; ) {
|
|
8606
9124
|
try {
|
|
8607
|
-
await (0,
|
|
9125
|
+
await (0, import_promises3.mkdir)(lockDir);
|
|
8608
9126
|
return async () => {
|
|
8609
|
-
await (0,
|
|
9127
|
+
await (0, import_promises3.rmdir)(lockDir).catch(() => {
|
|
8610
9128
|
});
|
|
8611
9129
|
};
|
|
8612
9130
|
} catch (err) {
|
|
8613
9131
|
const code = err.code;
|
|
8614
9132
|
if (code !== "EEXIST") throw err;
|
|
8615
9133
|
try {
|
|
8616
|
-
const age = Date.now() - (await (0,
|
|
9134
|
+
const age = Date.now() - (await (0, import_promises3.stat)(lockDir)).mtimeMs;
|
|
8617
9135
|
if (age > STALE_FILE_LOCK_MS) {
|
|
8618
|
-
await (0,
|
|
9136
|
+
await (0, import_promises3.rmdir)(lockDir);
|
|
8619
9137
|
continue;
|
|
8620
9138
|
}
|
|
8621
9139
|
} catch (lockErr) {
|
|
@@ -8642,22 +9160,22 @@ function totalSpend(lock, unit = "generations") {
|
|
|
8642
9160
|
|
|
8643
9161
|
// src/outputs.ts
|
|
8644
9162
|
function portableOutputPath(file, manifestDir) {
|
|
8645
|
-
const root =
|
|
8646
|
-
const absolute =
|
|
8647
|
-
const relative =
|
|
8648
|
-
const value =
|
|
8649
|
-
return value.split(
|
|
9163
|
+
const root = import_node_path6.default.resolve(manifestDir);
|
|
9164
|
+
const absolute = import_node_path6.default.isAbsolute(file) ? import_node_path6.default.normalize(file) : import_node_path6.default.resolve(root, file);
|
|
9165
|
+
const relative = import_node_path6.default.relative(root, absolute);
|
|
9166
|
+
const value = import_node_path6.default.isAbsolute(relative) ? absolute : relative;
|
|
9167
|
+
return value.split(import_node_path6.default.sep).join("/");
|
|
8650
9168
|
}
|
|
8651
9169
|
function resolveOutputPath(recordedPath, manifestDir) {
|
|
8652
|
-
if (
|
|
8653
|
-
if (
|
|
8654
|
-
return
|
|
9170
|
+
if (import_node_path6.default.isAbsolute(recordedPath)) return import_node_path6.default.normalize(recordedPath);
|
|
9171
|
+
if (import_node_path6.default.win32.isAbsolute(recordedPath)) return recordedPath;
|
|
9172
|
+
return import_node_path6.default.resolve(manifestDir, recordedPath.split(/[\\/]/).join(import_node_path6.default.sep));
|
|
8655
9173
|
}
|
|
8656
9174
|
function expectedOutputPath(spec, role, index, total, mediaType) {
|
|
8657
9175
|
return memberPath(spec.outFile, role, index, total, mediaType);
|
|
8658
9176
|
}
|
|
8659
9177
|
function memberPath(file, role, index, total, mediaType) {
|
|
8660
|
-
const originalExt =
|
|
9178
|
+
const originalExt = import_node_path6.default.extname(file);
|
|
8661
9179
|
const ext = mediaType ? mediaExtension(mediaType) : originalExt || ".png";
|
|
8662
9180
|
const stem = originalExt ? file.slice(0, -originalExt.length) : file;
|
|
8663
9181
|
if (total === 1) return `${stem}${ext}`;
|
|
@@ -8671,10 +9189,10 @@ function isFrameSetEntry(entry) {
|
|
|
8671
9189
|
return entry.generator === "frames" && isMemberSetEntry(entry);
|
|
8672
9190
|
}
|
|
8673
9191
|
function sourceIsStem(source, entry, manifestDir) {
|
|
8674
|
-
return isMemberSetEntry(entry) && !(0,
|
|
9192
|
+
return isMemberSetEntry(entry) && !(0, import_node_fs6.existsSync)(import_node_path6.default.resolve(manifestDir, source));
|
|
8675
9193
|
}
|
|
8676
9194
|
function sourceOutputPath(source, entry, index, manifestDir) {
|
|
8677
|
-
const absolute =
|
|
9195
|
+
const absolute = import_node_path6.default.resolve(manifestDir, source);
|
|
8678
9196
|
if (!sourceIsStem(source, entry, manifestDir)) return absolute;
|
|
8679
9197
|
const output = entry.outputs[index];
|
|
8680
9198
|
return memberPath(absolute, output.role, index, entry.outputs.length, output.mediaType);
|
|
@@ -8761,7 +9279,7 @@ function resolveSpecOutputs(spec, lock, manifestDir) {
|
|
|
8761
9279
|
id: spec.assetId,
|
|
8762
9280
|
index: 0,
|
|
8763
9281
|
path: spec.outFile,
|
|
8764
|
-
absolutePath:
|
|
9282
|
+
absolutePath: import_node_path6.default.resolve(spec.outFile),
|
|
8765
9283
|
sha256: ""
|
|
8766
9284
|
}];
|
|
8767
9285
|
}
|
|
@@ -8774,12 +9292,17 @@ function frameSetFps(entry) {
|
|
|
8774
9292
|
}
|
|
8775
9293
|
return null;
|
|
8776
9294
|
}
|
|
9295
|
+
function frameSetLoop(entry) {
|
|
9296
|
+
const frameSet = entry.providerMetadata?.[entry.provider]?.frameSet;
|
|
9297
|
+
const loop = frameSet && typeof frameSet === "object" ? frameSet.loop : void 0;
|
|
9298
|
+
return typeof loop === "boolean" ? loop : null;
|
|
9299
|
+
}
|
|
8777
9300
|
|
|
8778
9301
|
// src/manifest.ts
|
|
8779
9302
|
async function loadManifest(manifestPath) {
|
|
8780
|
-
const abs =
|
|
8781
|
-
if (!(0,
|
|
8782
|
-
const expansion = expandLoopDirections(JSON.parse(await (0,
|
|
9303
|
+
const abs = import_node_path7.default.resolve(manifestPath);
|
|
9304
|
+
if (!(0, import_node_fs7.existsSync)(abs)) throw new ProjectError(`No manifest at ${abs}`);
|
|
9305
|
+
const expansion = expandLoopDirections(JSON.parse(await (0, import_promises4.readFile)(abs, "utf8")));
|
|
8783
9306
|
if (expansion.issues.length) {
|
|
8784
9307
|
throw new ProjectError(`Manifest at ${abs} is invalid:
|
|
8785
9308
|
${expansion.issues.map((i) => ` ${i}`).join("\n")}`);
|
|
@@ -8834,7 +9357,7 @@ ${issues}`);
|
|
|
8834
9357
|
if (fromStyle) {
|
|
8835
9358
|
if (!styleIds.has(fromStyle)) {
|
|
8836
9359
|
unknownReferences.push(`assets.${assetId}.revision.fromStyle: unknown style "${fromStyle}"`);
|
|
8837
|
-
} else if (parent && parent
|
|
9360
|
+
} else if (parent && !assetInStyle(parent, fromStyle, parsed.data.styles[fromStyle])) {
|
|
8838
9361
|
unknownReferences.push(
|
|
8839
9362
|
`assets.${assetId}.revision.fromStyle: "${asset.revision.from}" is not in style "${fromStyle}"`
|
|
8840
9363
|
);
|
|
@@ -8881,7 +9404,11 @@ ${issues}`);
|
|
|
8881
9404
|
${unknownReferences.map((i) => ` ${i}`).join("\n")}`);
|
|
8882
9405
|
}
|
|
8883
9406
|
const loopFamilies = Object.keys(expansion.families).length ? { loopFamilies: expansion.families } : {};
|
|
8884
|
-
return { manifest: parsed.data, root:
|
|
9407
|
+
return { manifest: parsed.data, root: import_node_path7.default.dirname(abs), path: abs, ...loopFamilies };
|
|
9408
|
+
}
|
|
9409
|
+
function assetInStyle(asset, styleId, style) {
|
|
9410
|
+
if (asset.styles.length) return asset.styles.includes(styleId);
|
|
9411
|
+
return style?.generator === "vfx" === Boolean(asset.vfx);
|
|
8885
9412
|
}
|
|
8886
9413
|
function parentAssetId(asset) {
|
|
8887
9414
|
return asset?.revision?.from ?? asset?.state?.of ?? asset?.animation?.of ?? asset?.portrait?.of ?? asset?.outfit?.of;
|
|
@@ -8985,8 +9512,8 @@ async function resolveSpecs(loaded, filter) {
|
|
|
8985
9512
|
const needed = /* @__PURE__ */ new Set();
|
|
8986
9513
|
for (const assetId of resolutionAssetIds) {
|
|
8987
9514
|
const asset = manifest.assets[assetId];
|
|
8988
|
-
if (asset
|
|
8989
|
-
const fromStyle = asset
|
|
9515
|
+
if (!asset || !assetInStyle(asset, styleId, manifest.styles[styleId])) continue;
|
|
9516
|
+
const fromStyle = asset.revision?.fromStyle;
|
|
8990
9517
|
if (fromStyle && fromStyle !== styleId) needed.add(fromStyle);
|
|
8991
9518
|
}
|
|
8992
9519
|
styleDependencies.set(styleId, needed);
|
|
@@ -9015,11 +9542,11 @@ async function resolveSpecs(loaded, filter) {
|
|
|
9015
9542
|
const styleFinalizers = /* @__PURE__ */ new Map();
|
|
9016
9543
|
const styleImageCache = /* @__PURE__ */ new Map();
|
|
9017
9544
|
async function loadStyleImage(rel, what = "Style image") {
|
|
9018
|
-
const abs =
|
|
9545
|
+
const abs = import_node_path7.default.resolve(root, rel);
|
|
9019
9546
|
let hit = styleImageCache.get(abs);
|
|
9020
9547
|
if (!hit) {
|
|
9021
|
-
if (!(0,
|
|
9022
|
-
const buf = await (0,
|
|
9548
|
+
if (!(0, import_node_fs7.existsSync)(abs)) throw new Error(`${what} not found: ${abs}`);
|
|
9549
|
+
const buf = await (0, import_promises4.readFile)(abs);
|
|
9023
9550
|
const metadata = imageMetadata(buf);
|
|
9024
9551
|
if (!metadata) throw new Error(`${what} is not a readable PNG or JPEG: ${abs}`);
|
|
9025
9552
|
if (metadata.width < 1 || metadata.height < 1) {
|
|
@@ -9045,14 +9572,14 @@ async function resolveSpecs(loaded, filter) {
|
|
|
9045
9572
|
styleImageDimensions.push(loadedImage);
|
|
9046
9573
|
}
|
|
9047
9574
|
const resolvedImages = style.styleImages.map((image) => {
|
|
9048
|
-
const hit = styleImageCache.get(
|
|
9575
|
+
const hit = styleImageCache.get(import_node_path7.default.resolve(root, image.path));
|
|
9049
9576
|
return { base64: hit.base64, width: hit.width, height: hit.height, format: hit.format };
|
|
9050
9577
|
});
|
|
9051
9578
|
const styleSpecs = /* @__PURE__ */ new Map();
|
|
9052
9579
|
const providerInputIdentities = /* @__PURE__ */ new Map();
|
|
9053
9580
|
for (const [assetId, asset] of Object.entries(manifest.assets)) {
|
|
9054
9581
|
if (!resolutionAssetIds.has(assetId)) continue;
|
|
9055
|
-
if (asset
|
|
9582
|
+
if (!assetInStyle(asset, styleId, style)) continue;
|
|
9056
9583
|
const generator = style.generator;
|
|
9057
9584
|
if (!activeProvider.supports(generator)) {
|
|
9058
9585
|
throw new Error(`Provider "${activeProvider.id}" does not support generator "${generator}"`);
|
|
@@ -9072,6 +9599,15 @@ async function resolveSpecs(loaded, filter) {
|
|
|
9072
9599
|
const providerInputs = inputResolution.inputs;
|
|
9073
9600
|
const providerInputIdentity = inputResolution.identity ?? providerInputs;
|
|
9074
9601
|
providerInputIdentities.set(assetId, providerInputIdentity);
|
|
9602
|
+
if (asset.vfx && generator !== "vfx") {
|
|
9603
|
+
throw new Error(`assets.${assetId}: vfx needs a vfx style; "${styleId}" generates ${generator}`);
|
|
9604
|
+
}
|
|
9605
|
+
if (generator === "vfx" && !asset.vfx && !asset.mirror && !asset.revision) {
|
|
9606
|
+
throw new Error(
|
|
9607
|
+
`assets.${assetId}: style "${styleId}" renders effects; give the asset a vfx block, or make it a mirror or revision of one`
|
|
9608
|
+
);
|
|
9609
|
+
}
|
|
9610
|
+
const vfx = generator === "vfx" && asset.vfx ? await resolveVfxShape(root, assetId, asset.vfx, `pixelkiln:${styleId}/${assetId}`) : void 0;
|
|
9075
9611
|
let width;
|
|
9076
9612
|
let height;
|
|
9077
9613
|
let size;
|
|
@@ -9099,6 +9635,11 @@ async function resolveSpecs(loaded, filter) {
|
|
|
9099
9635
|
size = asset.size ?? style.size ?? 64;
|
|
9100
9636
|
width = size;
|
|
9101
9637
|
height = size;
|
|
9638
|
+
} else if (generator === "vfx") {
|
|
9639
|
+
const canvas = vfx?.recipe?.recipe.canvas;
|
|
9640
|
+
width = typeof canvas?.w === "number" ? canvas.w : asset.width ?? style.size ?? 64;
|
|
9641
|
+
height = typeof canvas?.h === "number" ? canvas.h : asset.height ?? style.size ?? 64;
|
|
9642
|
+
size = Math.max(width, height);
|
|
9102
9643
|
} else if (generator === "uiAsset" || generator === "uiElement") {
|
|
9103
9644
|
width = asset.width ?? style.size ?? 256;
|
|
9104
9645
|
height = asset.height ?? style.size ?? 256;
|
|
@@ -9141,14 +9682,14 @@ async function resolveSpecs(loaded, filter) {
|
|
|
9141
9682
|
terrainUpperDescription = wrap(parsed.upper);
|
|
9142
9683
|
terrainTransitionDescription = parsed.transition ? wrap(parsed.transition) : void 0;
|
|
9143
9684
|
prompt = subject.trim();
|
|
9144
|
-
} else if ((generator === "character" || generator === "objectPro") && characterKind !== "base") {
|
|
9685
|
+
} else if ((generator === "character" || generator === "objectPro") && characterKind !== "base" || vfx) {
|
|
9145
9686
|
prompt = subject.trim();
|
|
9146
9687
|
} else {
|
|
9147
9688
|
prompt = [style.promptPrefix, subject, style.promptSuffix].map((p) => p.trim()).filter(Boolean).join(", ");
|
|
9148
9689
|
}
|
|
9149
|
-
const relFile = asset.file ??
|
|
9150
|
-
const outFile =
|
|
9151
|
-
const qualityOutFile = style.quality ?
|
|
9690
|
+
const relFile = asset.file ?? import_node_path7.default.join(asset.category ?? "", `${assetId}.png`);
|
|
9691
|
+
const outFile = import_node_path7.default.resolve(root, style.outDir, relFile);
|
|
9692
|
+
const qualityOutFile = style.quality ? import_node_path7.default.resolve(root, style.quality.outDir, pngPath(relFile)) : void 0;
|
|
9152
9693
|
const tileSize = generator === "tiles" ? size : style.tileSize ?? 32;
|
|
9153
9694
|
const tileVariations = tileFeatureOutputCount(generator === "tiles" ? style.tileFeature : void 0) ?? tileVariationCount(countNumberedDescriptions(prompt));
|
|
9154
9695
|
const terrainTiles = generator === "terrain" ? terrainTileCount(style.terrainTransitionSize) : 0;
|
|
@@ -9219,6 +9760,7 @@ async function resolveSpecs(loaded, filter) {
|
|
|
9219
9760
|
// A portrait needs a character style (refused above), so an
|
|
9220
9761
|
// objectPro asset is only ever a base, state, or animation.
|
|
9221
9762
|
...generator === "objectPro" && !asset.revision && characterKind !== "portrait" ? { objectPro: await resolveObjectProShape(assetId, asset, style, characterKind, { root, load: loadStyleImage }) } : {},
|
|
9763
|
+
...vfx ? { vfx } : {},
|
|
9222
9764
|
cost: generator === "tiles" ? tilesCost(tileSize, tileVariations) : generator === "terrain" ? tilesCost(size, terrainTiles) : generator === "isometricTile" ? isometricTileCost() : generationCost(width, height, generator),
|
|
9223
9765
|
costUnit: "generations",
|
|
9224
9766
|
candidates: generator === "tiles" ? tileVariations : generator === "terrain" ? terrainTiles : generator === "1dir" || generator === "imagePro" ? candidateCount(size) : 1
|
|
@@ -9243,7 +9785,7 @@ async function resolveSpecs(loaded, filter) {
|
|
|
9243
9785
|
minGridConfidence: style.quality.minGridConfidence,
|
|
9244
9786
|
...style.quality.minTransparency == null ? {} : { minTransparency: style.quality.minTransparency },
|
|
9245
9787
|
...style.quality.fixerRevision ? { fixerRevision: style.quality.fixerRevision } : {},
|
|
9246
|
-
...style.quality.fixerPython ? { fixerPython:
|
|
9788
|
+
...style.quality.fixerPython ? { fixerPython: import_node_path7.default.resolve(root, style.quality.fixerPython) } : {},
|
|
9247
9789
|
...style.quality.fps == null ? {} : { fps: style.quality.fps }
|
|
9248
9790
|
}
|
|
9249
9791
|
} : {},
|
|
@@ -9326,7 +9868,7 @@ async function resolveSpecs(loaded, filter) {
|
|
|
9326
9868
|
const file = memberPath(anchorSpec.outFile, "south", 0, anchorSpec.character.directions, MediaType.PNG);
|
|
9327
9869
|
resolved.character = {
|
|
9328
9870
|
...resolved.character,
|
|
9329
|
-
styleAnchor: { assetId: anchorId, spec: anchorSpec, file, sha256: (0,
|
|
9871
|
+
styleAnchor: { assetId: anchorId, spec: anchorSpec, file, sha256: (0, import_node_fs7.existsSync)(file) ? await sha256File(file) : null }
|
|
9330
9872
|
};
|
|
9331
9873
|
}
|
|
9332
9874
|
const characterParent = asset.state?.of ?? asset.animation?.of ?? asset.portrait?.of;
|
|
@@ -9339,7 +9881,7 @@ async function resolveSpecs(loaded, filter) {
|
|
|
9339
9881
|
parentAssetId: characterParent,
|
|
9340
9882
|
parentSpec,
|
|
9341
9883
|
parentFile,
|
|
9342
|
-
parentSha256: (0,
|
|
9884
|
+
parentSha256: (0, import_node_fs7.existsSync)(parentFile) ? await sha256File(parentFile) : null,
|
|
9343
9885
|
// A state and its animations keep the engine and rotations of the base.
|
|
9344
9886
|
mode: resolved.character.kind === "base" ? resolved.character.mode : parentSpec.character?.mode ?? resolved.character.mode,
|
|
9345
9887
|
directions: resolved.character.kind === "base" ? resolved.character.directions : parentSpec.character?.directions ?? resolved.character.directions
|
|
@@ -9357,7 +9899,7 @@ async function resolveSpecs(loaded, filter) {
|
|
|
9357
9899
|
parentAssetId: characterParent,
|
|
9358
9900
|
parentSpec,
|
|
9359
9901
|
parentFile,
|
|
9360
|
-
parentSha256: (0,
|
|
9902
|
+
parentSha256: (0, import_node_fs7.existsSync)(parentFile) ? await sha256File(parentFile) : null,
|
|
9361
9903
|
// A state and its animations keep the base's own rotation count.
|
|
9362
9904
|
directions: resolved.objectPro.kind === "base" ? resolved.objectPro.directions : parentSpec.objectPro.directions
|
|
9363
9905
|
};
|
|
@@ -9379,7 +9921,7 @@ async function resolveSpecs(loaded, filter) {
|
|
|
9379
9921
|
animation: sourceSpec.character.animation,
|
|
9380
9922
|
outfit: {
|
|
9381
9923
|
reference: {
|
|
9382
|
-
path:
|
|
9924
|
+
path: import_node_path7.default.resolve(root, asset.outfit.reference),
|
|
9383
9925
|
sha256: referenceImage.hash,
|
|
9384
9926
|
width: referenceImage.width,
|
|
9385
9927
|
height: referenceImage.height,
|
|
@@ -9402,23 +9944,23 @@ async function resolveSpecs(loaded, filter) {
|
|
|
9402
9944
|
}
|
|
9403
9945
|
const sourceStyleId = asset.revision.fromStyle && asset.revision.fromStyle !== styleId ? asset.revision.fromStyle : void 0;
|
|
9404
9946
|
const sourceSpec = sourceStyleId ? await styleFinalizers.get(sourceStyleId)(asset.revision.from) : await finalize(asset.revision.from);
|
|
9405
|
-
const sourceStem = sourceSpec.quality?.outFile ?? (sourceSpec.source ?
|
|
9947
|
+
const sourceStem = sourceSpec.quality?.outFile ?? (sourceSpec.source ? import_node_path7.default.resolve(root, sourceSpec.source) : sourceSpec.outFile);
|
|
9406
9948
|
const sourceMembers = await revisionSourceMembers(sourceStem, `revision source ${asset.revision.from}`);
|
|
9407
9949
|
const sourceFile = sourceMembers?.[0]?.file ?? sourceStem;
|
|
9408
9950
|
const sourceImage = sourceMembers ? { ...sourceMembers[0], hash: sha256(JSON.stringify(sourceMembers.map((member) => member.sha256))) } : await optionalRevisionImage(sourceFile, "revision source");
|
|
9409
9951
|
const sourceFps = sourceSpec.character?.animation?.fps ?? sourceSpec.objectPro?.animation?.fps ?? sourceSpec.revision?.fps ?? sourceSpec.revision?.sourceFps;
|
|
9410
|
-
const maskFile = asset.revision.mask ?
|
|
9952
|
+
const maskFile = asset.revision.mask ? import_node_path7.default.resolve(root, asset.revision.mask) : void 0;
|
|
9411
9953
|
const maskImage = maskFile ? await optionalRevisionImage(maskFile, "revision mask", "png") : null;
|
|
9412
9954
|
if (sourceImage && maskImage && (sourceImage.width !== maskImage.width || sourceImage.height !== maskImage.height)) {
|
|
9413
9955
|
throw new Error(
|
|
9414
9956
|
`Revision mask for ${styleId}/${assetId} is ${maskImage.width}x${maskImage.height}; source ${asset.revision.from} is ${sourceImage.width}x${sourceImage.height}`
|
|
9415
9957
|
);
|
|
9416
9958
|
}
|
|
9417
|
-
const paletteImageFile = asset.revision.paletteImage ?
|
|
9959
|
+
const paletteImageFile = asset.revision.paletteImage ? import_node_path7.default.resolve(root, asset.revision.paletteImage) : void 0;
|
|
9418
9960
|
const paletteImage = paletteImageFile ? await optionalRevisionImage(paletteImageFile, "revision palette image", "png") : null;
|
|
9419
|
-
const lastFrameFile = asset.revision.lastFrame ?
|
|
9961
|
+
const lastFrameFile = asset.revision.lastFrame ? import_node_path7.default.resolve(root, asset.revision.lastFrame) : void 0;
|
|
9420
9962
|
const lastFrame = lastFrameFile ? await optionalRevisionImage(lastFrameFile, "revision last frame") : null;
|
|
9421
|
-
const keypointsFile = asset.revision.keypointsFile ?
|
|
9963
|
+
const keypointsFile = asset.revision.keypointsFile ? import_node_path7.default.resolve(root, asset.revision.keypointsFile) : void 0;
|
|
9422
9964
|
const keypoints = keypointsFile ? await optionalSkeletonSet(keypointsFile) : null;
|
|
9423
9965
|
resolved.revision = {
|
|
9424
9966
|
mode: asset.revision.mode,
|
|
@@ -9588,7 +10130,7 @@ async function resolveSpecs(loaded, filter) {
|
|
|
9588
10130
|
return specs;
|
|
9589
10131
|
}
|
|
9590
10132
|
function pngPath(file) {
|
|
9591
|
-
const extension =
|
|
10133
|
+
const extension = import_node_path7.default.extname(file);
|
|
9592
10134
|
return extension ? `${file.slice(0, -extension.length)}.png` : `${file}.png`;
|
|
9593
10135
|
}
|
|
9594
10136
|
async function resolveMapScene(root, styleId, assetId, generator, scene, provider) {
|
|
@@ -9598,12 +10140,12 @@ async function resolveMapScene(root, styleId, assetId, generator, scene, provide
|
|
|
9598
10140
|
if (!provider.supportsMapScene) {
|
|
9599
10141
|
throw new Error(`assets.${assetId}.scene: provider "${provider.id}" cannot draw into a scene; PixelLab's /map-objects can`);
|
|
9600
10142
|
}
|
|
9601
|
-
const file =
|
|
10143
|
+
const file = import_node_path7.default.resolve(root, scene.image);
|
|
9602
10144
|
const image = await optionalRevisionImage(file, "scene image", "png");
|
|
9603
10145
|
const base = { file, sha256: image?.hash ?? null, width: image?.width ?? null, height: image?.height ?? null };
|
|
9604
10146
|
const placement = scene.placement;
|
|
9605
10147
|
if ("mask" in placement) {
|
|
9606
|
-
const maskFile =
|
|
10148
|
+
const maskFile = import_node_path7.default.resolve(root, placement.mask);
|
|
9607
10149
|
const mask = await optionalRevisionImage(maskFile, "scene mask", "png");
|
|
9608
10150
|
if (image && mask && (image.width !== mask.width || image.height !== mask.height)) {
|
|
9609
10151
|
throw new Error(
|
|
@@ -9617,9 +10159,38 @@ async function resolveMapScene(root, styleId, assetId, generator, scene, provide
|
|
|
9617
10159
|
}
|
|
9618
10160
|
return "oval" in placement ? { ...base, placement: { type: "oval", fraction: placement.oval } } : { ...base, placement: { type: "rectangle", fraction: placement.rectangle } };
|
|
9619
10161
|
}
|
|
10162
|
+
async function resolveVfxShape(root, assetId, vfx, defaultName) {
|
|
10163
|
+
let recipe;
|
|
10164
|
+
let recordedSprite;
|
|
10165
|
+
if (vfx.recipe) {
|
|
10166
|
+
const file = import_node_path7.default.resolve(root, vfx.recipe);
|
|
10167
|
+
if (!(0, import_node_fs7.existsSync)(file)) throw new Error(`assets.${assetId}.vfx.recipe: VFX recipe not found: ${file}`);
|
|
10168
|
+
const read = await readVfxRecipe(file).catch((error) => {
|
|
10169
|
+
throw new Error(`assets.${assetId}.vfx.recipe: ${error instanceof Error ? error.message : String(error)}`);
|
|
10170
|
+
});
|
|
10171
|
+
recipe = { file, sha256: vfxRecipeSha256(read.recipe), recipe: read.recipe };
|
|
10172
|
+
recordedSprite = read.sprite;
|
|
10173
|
+
}
|
|
10174
|
+
const spriteRef = vfx.sprite ? isVfxReference(vfx.sprite) ? vfx.sprite : import_node_path7.default.resolve(root, vfx.sprite) : recordedSprite;
|
|
10175
|
+
let sprite;
|
|
10176
|
+
if (spriteRef && isVfxReference(spriteRef)) {
|
|
10177
|
+
sprite = { reference: spriteRef };
|
|
10178
|
+
} else if (spriteRef) {
|
|
10179
|
+
const image = await optionalRevisionImage(spriteRef, `assets.${assetId}.vfx.sprite`);
|
|
10180
|
+
sprite = { file: spriteRef, sha256: image?.hash ?? null, width: image?.width ?? null, height: image?.height ?? null, format: image?.format ?? null };
|
|
10181
|
+
}
|
|
10182
|
+
return {
|
|
10183
|
+
...vfx.preset ? { preset: vfx.preset } : {},
|
|
10184
|
+
...recipe ? { recipe } : {},
|
|
10185
|
+
...vfx.parts?.length ? { parts: vfx.parts } : {},
|
|
10186
|
+
...sprite ? { sprite } : {},
|
|
10187
|
+
...vfx.fps !== void 0 ? { fps: vfx.fps } : {},
|
|
10188
|
+
name: vfx.name ?? defaultName
|
|
10189
|
+
};
|
|
10190
|
+
}
|
|
9620
10191
|
async function optionalRevisionImage(file, label, requiredFormat) {
|
|
9621
|
-
if (!(0,
|
|
9622
|
-
const bytes = await (0,
|
|
10192
|
+
if (!(0, import_node_fs7.existsSync)(file)) return null;
|
|
10193
|
+
const bytes = await (0, import_promises4.readFile)(file);
|
|
9623
10194
|
const metadata = imageMetadata(bytes);
|
|
9624
10195
|
if (!metadata) throw new Error(`${label} is not a readable PNG or JPEG: ${file}`);
|
|
9625
10196
|
if (requiredFormat && metadata.format !== requiredFormat) {
|
|
@@ -9628,8 +10199,8 @@ async function optionalRevisionImage(file, label, requiredFormat) {
|
|
|
9628
10199
|
return { hash: sha256(bytes), ...metadata };
|
|
9629
10200
|
}
|
|
9630
10201
|
async function optionalSkeletonSet(file) {
|
|
9631
|
-
if (!(0,
|
|
9632
|
-
const bytes = await (0,
|
|
10202
|
+
if (!(0, import_node_fs7.existsSync)(file)) return null;
|
|
10203
|
+
const bytes = await (0, import_promises4.readFile)(file);
|
|
9633
10204
|
let json;
|
|
9634
10205
|
try {
|
|
9635
10206
|
json = JSON.parse(bytes.toString("utf8"));
|
|
@@ -9639,17 +10210,17 @@ async function optionalSkeletonSet(file) {
|
|
|
9639
10210
|
return { hash: sha256(bytes), skeleton: parseSkeletonSet(json, file) };
|
|
9640
10211
|
}
|
|
9641
10212
|
async function revisionSourceMembers(stem, label) {
|
|
9642
|
-
if ((0,
|
|
10213
|
+
if ((0, import_node_fs7.existsSync)(stem)) return null;
|
|
9643
10214
|
const at = (role) => memberPath(stem, role, 0, 2, MediaType.PNG);
|
|
9644
10215
|
let roles = [];
|
|
9645
10216
|
for (const set of [CHARACTER_DIRECTIONS_8, CHARACTER_DIRECTIONS_4]) {
|
|
9646
|
-
if (set.every((direction) => (0,
|
|
10217
|
+
if (set.every((direction) => (0, import_node_fs7.existsSync)(at(direction)))) {
|
|
9647
10218
|
roles = [...set];
|
|
9648
10219
|
break;
|
|
9649
10220
|
}
|
|
9650
10221
|
}
|
|
9651
10222
|
if (!roles.length) {
|
|
9652
|
-
for (let index = 0; (0,
|
|
10223
|
+
for (let index = 0; (0, import_node_fs7.existsSync)(at(`frame-${String(index).padStart(2, "0")}`)); index++) {
|
|
9653
10224
|
roles.push(`frame-${String(index).padStart(2, "0")}`);
|
|
9654
10225
|
}
|
|
9655
10226
|
}
|
|
@@ -9675,8 +10246,8 @@ async function resolveStyleImages(loaded, styleId) {
|
|
|
9675
10246
|
if (!style) throw new Error(`Unknown style "${styleId}"`);
|
|
9676
10247
|
const out = [];
|
|
9677
10248
|
for (const img of style.styleImages) {
|
|
9678
|
-
const file =
|
|
9679
|
-
const buf = await (0,
|
|
10249
|
+
const file = import_node_path7.default.resolve(loaded.root, img.path);
|
|
10250
|
+
const buf = await (0, import_promises4.readFile)(file);
|
|
9680
10251
|
const metadata = imageMetadata(buf);
|
|
9681
10252
|
if (!metadata) throw new Error(`Style image is not a readable PNG or JPEG: ${file}`);
|
|
9682
10253
|
if (metadata.width < 1 || metadata.height < 1) {
|
|
@@ -9706,7 +10277,7 @@ async function resolveCharacterShape(asset, style, kind, files) {
|
|
|
9706
10277
|
};
|
|
9707
10278
|
if (asset.concept) {
|
|
9708
10279
|
const image = await files.load(asset.concept, "Concept image");
|
|
9709
|
-
shape.concept = { path:
|
|
10280
|
+
shape.concept = { path: import_node_path7.default.resolve(files.root, asset.concept), sha256: image.hash, width: image.width, height: image.height, format: image.format };
|
|
9710
10281
|
}
|
|
9711
10282
|
if (asset.reference) {
|
|
9712
10283
|
const byDirection = typeof asset.reference === "string" ? { south: asset.reference } : asset.reference;
|
|
@@ -9714,7 +10285,7 @@ async function resolveCharacterShape(asset, style, kind, files) {
|
|
|
9714
10285
|
for (const [direction, rel] of Object.entries(byDirection)) {
|
|
9715
10286
|
const image = await files.load(rel, `Reference sprite (${direction})`);
|
|
9716
10287
|
shape.reference[direction] = {
|
|
9717
|
-
path:
|
|
10288
|
+
path: import_node_path7.default.resolve(files.root, rel),
|
|
9718
10289
|
sha256: image.hash,
|
|
9719
10290
|
width: image.width,
|
|
9720
10291
|
height: image.height,
|
|
@@ -9735,7 +10306,7 @@ async function resolveCharacterShape(asset, style, kind, files) {
|
|
|
9735
10306
|
const animationMode = asset.animation.mode ?? (asset.animation.template ? "template" : "v3");
|
|
9736
10307
|
const pose = async (rel, what) => {
|
|
9737
10308
|
const image = await files.load(rel, what);
|
|
9738
|
-
return { path:
|
|
10309
|
+
return { path: import_node_path7.default.resolve(files.root, rel), sha256: image.hash, width: image.width, height: image.height, format: image.format };
|
|
9739
10310
|
};
|
|
9740
10311
|
shape.animation = {
|
|
9741
10312
|
mode: animationMode,
|
|
@@ -9766,7 +10337,7 @@ async function resolveObjectProShape(assetId, asset, style, kind, files) {
|
|
|
9766
10337
|
const south = typeof asset.reference === "string" ? asset.reference : asset.reference.south;
|
|
9767
10338
|
if (south) {
|
|
9768
10339
|
const image = await files.load(south, "Reference sprite");
|
|
9769
|
-
shape.reference = { path:
|
|
10340
|
+
shape.reference = { path: import_node_path7.default.resolve(files.root, south), sha256: image.hash, width: image.width, height: image.height, format: image.format };
|
|
9770
10341
|
}
|
|
9771
10342
|
}
|
|
9772
10343
|
if (asset.state) {
|
|
@@ -9794,7 +10365,7 @@ async function resolveObjectProShape(assetId, asset, style, kind, files) {
|
|
|
9794
10365
|
}
|
|
9795
10366
|
const pose = async (rel, what) => {
|
|
9796
10367
|
const image = await files.load(rel, what);
|
|
9797
|
-
return { path:
|
|
10368
|
+
return { path: import_node_path7.default.resolve(files.root, rel), sha256: image.hash, width: image.width, height: image.height, format: image.format };
|
|
9798
10369
|
};
|
|
9799
10370
|
shape.animation = {
|
|
9800
10371
|
mode: asset.animation.mode ?? "v3",
|
|
@@ -9814,11 +10385,11 @@ function characterDirections(directions) {
|
|
|
9814
10385
|
}
|
|
9815
10386
|
|
|
9816
10387
|
// src/project.ts
|
|
9817
|
-
var
|
|
10388
|
+
var import_node_path15 = __toESM(require("path"), 1);
|
|
9818
10389
|
|
|
9819
10390
|
// src/env.ts
|
|
9820
|
-
var
|
|
9821
|
-
var
|
|
10391
|
+
var import_node_fs8 = require("fs");
|
|
10392
|
+
var import_node_path8 = __toESM(require("path"), 1);
|
|
9822
10393
|
function loadEnvFiles(dir) {
|
|
9823
10394
|
const loaded = [];
|
|
9824
10395
|
for (const [file, vars] of envFiles(dir)) {
|
|
@@ -9832,10 +10403,10 @@ function loadEnvFiles(dir) {
|
|
|
9832
10403
|
function envFiles(dir) {
|
|
9833
10404
|
const out = [];
|
|
9834
10405
|
for (const name of [".env.local", ".env"]) {
|
|
9835
|
-
const file =
|
|
9836
|
-
if (!(0,
|
|
10406
|
+
const file = import_node_path8.default.join(dir, name);
|
|
10407
|
+
if (!(0, import_node_fs8.existsSync)(file)) continue;
|
|
9837
10408
|
try {
|
|
9838
|
-
out.push([file, parseEnv((0,
|
|
10409
|
+
out.push([file, parseEnv((0, import_node_fs8.readFileSync)(file, "utf8"))]);
|
|
9839
10410
|
} catch {
|
|
9840
10411
|
}
|
|
9841
10412
|
}
|
|
@@ -9861,12 +10432,12 @@ function parseEnv(contents) {
|
|
|
9861
10432
|
}
|
|
9862
10433
|
|
|
9863
10434
|
// src/pipeline/plan.ts
|
|
9864
|
-
var
|
|
9865
|
-
var
|
|
10435
|
+
var import_node_fs11 = require("fs");
|
|
10436
|
+
var import_node_path14 = __toESM(require("path"), 1);
|
|
9866
10437
|
|
|
9867
10438
|
// src/pipeline/mirror.ts
|
|
9868
|
-
var
|
|
9869
|
-
var
|
|
10439
|
+
var import_promises5 = require("fs/promises");
|
|
10440
|
+
var import_node_path9 = __toESM(require("path"), 1);
|
|
9870
10441
|
|
|
9871
10442
|
// src/palette.ts
|
|
9872
10443
|
function normalizePalette(values) {
|
|
@@ -10003,12 +10574,12 @@ async function deriveMirror(spec, lock, lockPath, opts = {}) {
|
|
|
10003
10574
|
const outputs = [];
|
|
10004
10575
|
const written = [];
|
|
10005
10576
|
for (const [index, member] of members.entries()) {
|
|
10006
|
-
const bytes = flipHorizontal(await (0,
|
|
10577
|
+
const bytes = flipHorizontal(await (0, import_promises5.readFile)(member.file));
|
|
10007
10578
|
const target = expectedOutputPath(spec, member.role, index, members.length, MediaType.PNG);
|
|
10008
|
-
await (0,
|
|
10579
|
+
await (0, import_promises5.mkdir)(import_node_path9.default.dirname(target), { recursive: true });
|
|
10009
10580
|
const tmp = `${target}.pixelkiln.tmp`;
|
|
10010
|
-
await (0,
|
|
10011
|
-
await (0,
|
|
10581
|
+
await (0, import_promises5.writeFile)(tmp, bytes);
|
|
10582
|
+
await (0, import_promises5.rename)(tmp, target);
|
|
10012
10583
|
written.push(target);
|
|
10013
10584
|
outputs.push({
|
|
10014
10585
|
path: portableOutputPath(target, spec.root),
|
|
@@ -10067,20 +10638,20 @@ async function deriveMirror(spec, lock, lockPath, opts = {}) {
|
|
|
10067
10638
|
}
|
|
10068
10639
|
|
|
10069
10640
|
// src/pipeline/quality-profile.ts
|
|
10070
|
-
var
|
|
10071
|
-
var
|
|
10641
|
+
var import_node_fs9 = require("fs");
|
|
10642
|
+
var import_node_path12 = __toESM(require("path"), 1);
|
|
10072
10643
|
|
|
10073
10644
|
// src/pipeline/refine.ts
|
|
10074
|
-
var
|
|
10645
|
+
var import_node_path11 = __toESM(require("path"), 1);
|
|
10075
10646
|
var import_node_child_process2 = require("child_process");
|
|
10076
|
-
var
|
|
10647
|
+
var import_promises7 = require("fs/promises");
|
|
10077
10648
|
var import_node_os3 = require("os");
|
|
10078
10649
|
var import_node_util = require("util");
|
|
10079
10650
|
|
|
10080
10651
|
// src/artifacts.ts
|
|
10081
|
-
var
|
|
10652
|
+
var import_node_path10 = __toESM(require("path"), 1);
|
|
10082
10653
|
var import_node_crypto6 = require("crypto");
|
|
10083
|
-
var
|
|
10654
|
+
var import_promises6 = require("fs/promises");
|
|
10084
10655
|
var activeTransactions = /* @__PURE__ */ new Set();
|
|
10085
10656
|
function message(error) {
|
|
10086
10657
|
return error instanceof Error ? error.message : String(error);
|
|
@@ -10089,7 +10660,7 @@ function digest(data) {
|
|
|
10089
10660
|
return (0, import_node_crypto6.createHash)("sha256").update(data).digest("hex");
|
|
10090
10661
|
}
|
|
10091
10662
|
function portableRelative(from, to) {
|
|
10092
|
-
return
|
|
10663
|
+
return import_node_path10.default.relative(from, import_node_path10.default.resolve(to)).split(import_node_path10.default.sep).join("/") || ".";
|
|
10093
10664
|
}
|
|
10094
10665
|
function canonical2(value) {
|
|
10095
10666
|
if (value === null || typeof value === "string" || typeof value === "boolean") return value;
|
|
@@ -10139,12 +10710,12 @@ function parseArtifactManifest(absolute, data) {
|
|
|
10139
10710
|
return raw;
|
|
10140
10711
|
}
|
|
10141
10712
|
async function readArtifactBundleManifest(manifestPath) {
|
|
10142
|
-
const absolute =
|
|
10143
|
-
return parseArtifactManifest(absolute, await (0,
|
|
10713
|
+
const absolute = import_node_path10.default.resolve(manifestPath);
|
|
10714
|
+
return parseArtifactManifest(absolute, await (0, import_promises6.readFile)(absolute, "utf8"));
|
|
10144
10715
|
}
|
|
10145
10716
|
async function readOptional(file) {
|
|
10146
10717
|
try {
|
|
10147
|
-
return await (0,
|
|
10718
|
+
return await (0, import_promises6.readFile)(file);
|
|
10148
10719
|
} catch (error) {
|
|
10149
10720
|
if (isCode(error, "ENOENT")) return null;
|
|
10150
10721
|
throw error;
|
|
@@ -10163,8 +10734,8 @@ function processIsAlive(pid) {
|
|
|
10163
10734
|
}
|
|
10164
10735
|
}
|
|
10165
10736
|
function validTemporaryPath(candidate, destination, type) {
|
|
10166
|
-
return
|
|
10167
|
-
`.${
|
|
10737
|
+
return import_node_path10.default.dirname(candidate) === import_node_path10.default.dirname(destination) && import_node_path10.default.basename(candidate).startsWith(
|
|
10738
|
+
`.${import_node_path10.default.basename(destination)}.pixelkiln-${type}-`
|
|
10168
10739
|
);
|
|
10169
10740
|
}
|
|
10170
10741
|
function parseTransaction(journal, data) {
|
|
@@ -10180,14 +10751,14 @@ function parseTransaction(journal, data) {
|
|
|
10180
10751
|
return raw;
|
|
10181
10752
|
}
|
|
10182
10753
|
async function removeTransactionFiles(journal) {
|
|
10183
|
-
await (0,
|
|
10184
|
-
await (0,
|
|
10754
|
+
await (0, import_promises6.rm)(journal, { force: true });
|
|
10755
|
+
await (0, import_promises6.rm)(transactionMarker(journal), { force: true });
|
|
10185
10756
|
}
|
|
10186
10757
|
async function recoverArtifactTransaction(recoveryFile, allowedDestinations) {
|
|
10187
|
-
const journal =
|
|
10758
|
+
const journal = import_node_path10.default.resolve(recoveryFile);
|
|
10188
10759
|
const bytes = await readOptional(journal);
|
|
10189
10760
|
if (!bytes) {
|
|
10190
|
-
await (0,
|
|
10761
|
+
await (0, import_promises6.rm)(transactionMarker(journal), { force: true });
|
|
10191
10762
|
return;
|
|
10192
10763
|
}
|
|
10193
10764
|
if (activeTransactions.has(journal)) {
|
|
@@ -10200,7 +10771,7 @@ async function recoverArtifactTransaction(recoveryFile, allowedDestinations) {
|
|
|
10200
10771
|
);
|
|
10201
10772
|
}
|
|
10202
10773
|
for (const entry of transaction.entries) {
|
|
10203
|
-
if (typeof entry.destination !== "string" || typeof entry.stage !== "string" || typeof entry.sha256 !== "string" || entry.backup !== void 0 && typeof entry.backup !== "string" || !allowedDestinations.has(
|
|
10774
|
+
if (typeof entry.destination !== "string" || typeof entry.stage !== "string" || typeof entry.sha256 !== "string" || entry.backup !== void 0 && typeof entry.backup !== "string" || !allowedDestinations.has(import_node_path10.default.resolve(entry.destination)) || !validTemporaryPath(entry.stage, entry.destination, "stage") || entry.backup !== void 0 && !validTemporaryPath(entry.backup, entry.destination, "backup")) {
|
|
10204
10775
|
throw new Error(
|
|
10205
10776
|
`Refusing unsafe artifact recovery from ${journal}; its destinations or temporary paths do not match the current bundle.`
|
|
10206
10777
|
);
|
|
@@ -10226,7 +10797,7 @@ async function recoverArtifactTransaction(recoveryFile, allowedDestinations) {
|
|
|
10226
10797
|
}
|
|
10227
10798
|
await remove2(entry.destination, errors);
|
|
10228
10799
|
try {
|
|
10229
|
-
await (0,
|
|
10800
|
+
await (0, import_promises6.rename)(entry.backup, entry.destination);
|
|
10230
10801
|
} catch (error) {
|
|
10231
10802
|
errors.push(
|
|
10232
10803
|
`could not restore ${entry.destination}; previous file remains at ${entry.backup}: ${message(error)}`
|
|
@@ -10246,7 +10817,7 @@ async function recoverArtifactTransaction(recoveryFile, allowedDestinations) {
|
|
|
10246
10817
|
await removeTransactionFiles(journal);
|
|
10247
10818
|
}
|
|
10248
10819
|
function createArtifactBundleManifest(manifestPath, outputs, provenance) {
|
|
10249
|
-
const root =
|
|
10820
|
+
const root = import_node_path10.default.dirname(import_node_path10.default.resolve(manifestPath));
|
|
10250
10821
|
const sources = provenance.sources.map((source) => ({
|
|
10251
10822
|
...source,
|
|
10252
10823
|
path: portableRelative(root, source.path)
|
|
@@ -10276,11 +10847,11 @@ function withArtifactManifest(manifestPath, outputs, provenance) {
|
|
|
10276
10847
|
];
|
|
10277
10848
|
}
|
|
10278
10849
|
async function writeManagedArtifactBundle(manifestPath, outputs, provenance, options = {}) {
|
|
10279
|
-
const absoluteManifest =
|
|
10850
|
+
const absoluteManifest = import_node_path10.default.resolve(manifestPath);
|
|
10280
10851
|
const recoveryFile = `${absoluteManifest}.transaction`;
|
|
10281
10852
|
const allowedDestinations = /* @__PURE__ */ new Set([
|
|
10282
10853
|
absoluteManifest,
|
|
10283
|
-
...outputs.map((output) =>
|
|
10854
|
+
...outputs.map((output) => import_node_path10.default.resolve(output.path))
|
|
10284
10855
|
]);
|
|
10285
10856
|
await recoverArtifactTransaction(recoveryFile, allowedDestinations);
|
|
10286
10857
|
const existingManifestBytes = await readOptional(absoluteManifest);
|
|
@@ -10298,13 +10869,13 @@ async function writeManagedArtifactBundle(manifestPath, outputs, provenance, opt
|
|
|
10298
10869
|
}
|
|
10299
10870
|
}
|
|
10300
10871
|
if (!options.force) {
|
|
10301
|
-
const root =
|
|
10872
|
+
const root = import_node_path10.default.dirname(absoluteManifest);
|
|
10302
10873
|
const recorded = new Map(
|
|
10303
|
-
previous?.outputs.map((output) => [
|
|
10874
|
+
previous?.outputs.map((output) => [import_node_path10.default.resolve(root, output.path), output.sha256]) ?? []
|
|
10304
10875
|
);
|
|
10305
10876
|
const conflicts = [];
|
|
10306
10877
|
for (const output of outputs) {
|
|
10307
|
-
const destination =
|
|
10878
|
+
const destination = import_node_path10.default.resolve(output.path);
|
|
10308
10879
|
const current = await readOptional(destination);
|
|
10309
10880
|
if (!current || digest(current) === digest(output.data)) continue;
|
|
10310
10881
|
const expected = recorded.get(destination);
|
|
@@ -10326,15 +10897,15 @@ Pass { force: true } (CLI: --force) to take ownership and replace the complete b
|
|
|
10326
10897
|
);
|
|
10327
10898
|
}
|
|
10328
10899
|
async function verifyArtifactBundle(manifestPath) {
|
|
10329
|
-
const absolute =
|
|
10900
|
+
const absolute = import_node_path10.default.resolve(manifestPath);
|
|
10330
10901
|
const raw = await readArtifactBundleManifest(absolute);
|
|
10331
|
-
const root =
|
|
10902
|
+
const root = import_node_path10.default.dirname(absolute);
|
|
10332
10903
|
const changedSources = [];
|
|
10333
10904
|
const changedOutputs = [];
|
|
10334
10905
|
for (const source of raw.sources) {
|
|
10335
10906
|
let actual = null;
|
|
10336
10907
|
try {
|
|
10337
|
-
actual = digest(await (0,
|
|
10908
|
+
actual = digest(await (0, import_promises6.readFile)(import_node_path10.default.resolve(root, source.path)));
|
|
10338
10909
|
} catch (error) {
|
|
10339
10910
|
if (!isCode(error, "ENOENT")) throw error;
|
|
10340
10911
|
}
|
|
@@ -10342,7 +10913,7 @@ async function verifyArtifactBundle(manifestPath) {
|
|
|
10342
10913
|
}
|
|
10343
10914
|
for (const output of raw.outputs) {
|
|
10344
10915
|
try {
|
|
10345
|
-
if (digest(await (0,
|
|
10916
|
+
if (digest(await (0, import_promises6.readFile)(import_node_path10.default.resolve(root, output.path))) !== output.sha256) {
|
|
10346
10917
|
changedOutputs.push(output.path);
|
|
10347
10918
|
}
|
|
10348
10919
|
} catch (error) {
|
|
@@ -10368,7 +10939,7 @@ function isCode(error, code) {
|
|
|
10368
10939
|
}
|
|
10369
10940
|
async function remove2(file, errors) {
|
|
10370
10941
|
try {
|
|
10371
|
-
await (0,
|
|
10942
|
+
await (0, import_promises6.rm)(file, { force: true });
|
|
10372
10943
|
} catch (error) {
|
|
10373
10944
|
errors?.push(`could not remove ${file}: ${message(error)}`);
|
|
10374
10945
|
}
|
|
@@ -10381,7 +10952,7 @@ async function rollback(prepared) {
|
|
|
10381
10952
|
for (const artifact of [...prepared].reverse()) {
|
|
10382
10953
|
if (!artifact.backup || !artifact.backedUp) continue;
|
|
10383
10954
|
try {
|
|
10384
|
-
await (0,
|
|
10955
|
+
await (0, import_promises6.rename)(artifact.backup, artifact.destination);
|
|
10385
10956
|
artifact.backedUp = false;
|
|
10386
10957
|
} catch (error) {
|
|
10387
10958
|
errors.push(
|
|
@@ -10398,7 +10969,7 @@ async function writeArtifactBundle(files, options = {}) {
|
|
|
10398
10969
|
if (!files.length) throw new Error("An artifact bundle must contain at least one file.");
|
|
10399
10970
|
const normalized = files.map((file) => {
|
|
10400
10971
|
if (!file.path.trim()) throw new Error("Artifact paths cannot be empty.");
|
|
10401
|
-
return { destination:
|
|
10972
|
+
return { destination: import_node_path10.default.resolve(file.path), data: Buffer.from(file.data) };
|
|
10402
10973
|
});
|
|
10403
10974
|
const destinations = /* @__PURE__ */ new Set();
|
|
10404
10975
|
for (const artifact of normalized) {
|
|
@@ -10414,7 +10985,7 @@ async function writeArtifactBundle(files, options = {}) {
|
|
|
10414
10985
|
const unchanged = [];
|
|
10415
10986
|
for (const artifact of normalized) {
|
|
10416
10987
|
try {
|
|
10417
|
-
const current = await (0,
|
|
10988
|
+
const current = await (0, import_promises6.readFile)(artifact.destination);
|
|
10418
10989
|
if (current.equals(artifact.data)) {
|
|
10419
10990
|
unchanged.push(artifact.destination);
|
|
10420
10991
|
} else {
|
|
@@ -10428,21 +10999,21 @@ async function writeArtifactBundle(files, options = {}) {
|
|
|
10428
10999
|
if (!changed.length) return { changed: [], unchanged };
|
|
10429
11000
|
const token = (0, import_node_crypto6.randomUUID)();
|
|
10430
11001
|
for (const [index, artifact] of changed.entries()) {
|
|
10431
|
-
const basename =
|
|
10432
|
-
artifact.stage =
|
|
10433
|
-
|
|
11002
|
+
const basename = import_node_path10.default.basename(artifact.destination);
|
|
11003
|
+
artifact.stage = import_node_path10.default.join(
|
|
11004
|
+
import_node_path10.default.dirname(artifact.destination),
|
|
10434
11005
|
`.${basename}.pixelkiln-stage-${token}-${index}`
|
|
10435
11006
|
);
|
|
10436
11007
|
if (artifact.existed) {
|
|
10437
|
-
artifact.backup =
|
|
10438
|
-
|
|
11008
|
+
artifact.backup = import_node_path10.default.join(
|
|
11009
|
+
import_node_path10.default.dirname(artifact.destination),
|
|
10439
11010
|
`.${basename}.pixelkiln-backup-${token}-${index}`
|
|
10440
11011
|
);
|
|
10441
11012
|
}
|
|
10442
11013
|
}
|
|
10443
|
-
const journal = options.recoveryFile ?
|
|
11014
|
+
const journal = options.recoveryFile ? import_node_path10.default.resolve(options.recoveryFile) : null;
|
|
10444
11015
|
if (journal) {
|
|
10445
|
-
await (0,
|
|
11016
|
+
await (0, import_promises6.mkdir)(import_node_path10.default.dirname(journal), { recursive: true });
|
|
10446
11017
|
const transaction = {
|
|
10447
11018
|
format: "pixelkiln-artifact-transaction",
|
|
10448
11019
|
version: 1,
|
|
@@ -10456,7 +11027,7 @@ async function writeArtifactBundle(files, options = {}) {
|
|
|
10456
11027
|
}))
|
|
10457
11028
|
};
|
|
10458
11029
|
try {
|
|
10459
|
-
await (0,
|
|
11030
|
+
await (0, import_promises6.writeFile)(journal, JSON.stringify(transaction, null, 2) + "\n", { flag: "wx" });
|
|
10460
11031
|
activeTransactions.add(journal);
|
|
10461
11032
|
} catch (error) {
|
|
10462
11033
|
throw new Error(
|
|
@@ -10467,9 +11038,9 @@ async function writeArtifactBundle(files, options = {}) {
|
|
|
10467
11038
|
}
|
|
10468
11039
|
try {
|
|
10469
11040
|
for (const [index, artifact] of changed.entries()) {
|
|
10470
|
-
await (0,
|
|
11041
|
+
await (0, import_promises6.mkdir)(import_node_path10.default.dirname(artifact.destination), { recursive: true });
|
|
10471
11042
|
await options.beforeStage?.(artifact.destination, index);
|
|
10472
|
-
await (0,
|
|
11043
|
+
await (0, import_promises6.writeFile)(artifact.stage, artifact.data, { flag: "wx" });
|
|
10473
11044
|
}
|
|
10474
11045
|
} catch (error) {
|
|
10475
11046
|
const cleanupErrors2 = await rollback(changed);
|
|
@@ -10482,17 +11053,17 @@ async function writeArtifactBundle(files, options = {}) {
|
|
|
10482
11053
|
try {
|
|
10483
11054
|
for (const artifact of changed) {
|
|
10484
11055
|
if (!artifact.existed) continue;
|
|
10485
|
-
await (0,
|
|
11056
|
+
await (0, import_promises6.rename)(artifact.destination, artifact.backup);
|
|
10486
11057
|
artifact.backedUp = true;
|
|
10487
11058
|
}
|
|
10488
11059
|
for (const [index, artifact] of changed.entries()) {
|
|
10489
11060
|
await options.beforePromote?.(artifact.destination, index);
|
|
10490
|
-
await (0,
|
|
11061
|
+
await (0, import_promises6.rename)(artifact.stage, artifact.destination);
|
|
10491
11062
|
artifact.stage = void 0;
|
|
10492
11063
|
artifact.promoted = true;
|
|
10493
11064
|
}
|
|
10494
11065
|
if (journal) {
|
|
10495
|
-
await (0,
|
|
11066
|
+
await (0, import_promises6.writeFile)(transactionMarker(journal), `${token}
|
|
10496
11067
|
`, { flag: "wx" });
|
|
10497
11068
|
durableCommit = true;
|
|
10498
11069
|
await options.afterCommit?.();
|
|
@@ -10733,16 +11304,16 @@ function sameFrameGrid(first, next) {
|
|
|
10733
11304
|
return next.detection.stepX === first.detection.stepX && next.detection.stepY === first.detection.stepY && next.detection.columns === first.detection.columns && next.detection.rows === first.detection.rows && phaseMatches(next.detection.phaseX, first.detection.phaseX, first.detection.stepX) && phaseMatches(next.detection.phaseY, first.detection.phaseY, first.detection.stepY) && next.audit.width === first.audit.width && next.audit.height === first.audit.height && next.sourceWidth === first.sourceWidth && next.sourceHeight === first.sourceHeight;
|
|
10734
11305
|
}
|
|
10735
11306
|
async function refineAsset(options) {
|
|
10736
|
-
const source =
|
|
10737
|
-
const output =
|
|
11307
|
+
const source = import_node_path11.default.resolve(options.source);
|
|
11308
|
+
const output = import_node_path11.default.resolve(options.output);
|
|
10738
11309
|
if (source === output) throw new Error("Refinement source and output must be different files.");
|
|
10739
|
-
const record =
|
|
11310
|
+
const record = import_node_path11.default.resolve(qualityRecordPath(output));
|
|
10740
11311
|
const palette = normalizePalette(options.palette).map((color) => color.hex);
|
|
10741
11312
|
const minGridConfidence = requireConfidence(options.minGridConfidence ?? "high");
|
|
10742
11313
|
if (options.minTransparency !== void 0 && (!Number.isFinite(options.minTransparency) || options.minTransparency < 0 || options.minTransparency > 1)) throw new Error("minTransparency must be between 0 and 1.");
|
|
10743
11314
|
let sourcePng;
|
|
10744
11315
|
try {
|
|
10745
|
-
sourcePng = decodePng(await (0,
|
|
11316
|
+
sourcePng = decodePng(await (0, import_promises7.readFile)(source));
|
|
10746
11317
|
} catch (error) {
|
|
10747
11318
|
throw new Error(`Cannot read refinement source ${source}: ${message2(error)}`, { cause: error });
|
|
10748
11319
|
}
|
|
@@ -10759,8 +11330,8 @@ async function refineAsset(options) {
|
|
|
10759
11330
|
);
|
|
10760
11331
|
}
|
|
10761
11332
|
}
|
|
10762
|
-
const temp = await (0,
|
|
10763
|
-
const recovered =
|
|
11333
|
+
const temp = await (0, import_promises7.mkdtemp)(import_node_path11.default.join((0, import_node_os3.tmpdir)(), "pixelkiln-refine-"));
|
|
11334
|
+
const recovered = import_node_path11.default.join(temp, "native.png");
|
|
10764
11335
|
const fixerCommand = compatibilityCommand ?? options.fixerPython ?? process.env.PIXELKILN_PIXEL_FIXER_PYTHON ?? "python3";
|
|
10765
11336
|
const args = compatibilityCommand ? [...options.fixerArgsPrefix ?? [], source, "--extract", recovered] : ["-c", PYTHON_FIXER_BRIDGE, source, recovered];
|
|
10766
11337
|
try {
|
|
@@ -10781,7 +11352,7 @@ async function refineAsset(options) {
|
|
|
10781
11352
|
);
|
|
10782
11353
|
}
|
|
10783
11354
|
const detection = parseDetection(firstJsonObject(stdout));
|
|
10784
|
-
const native = decodePng(await (0,
|
|
11355
|
+
const native = decodePng(await (0, import_promises7.readFile)(recovered));
|
|
10785
11356
|
const quantized = quantizeToPalette(native, palette);
|
|
10786
11357
|
const png = encodeRgbaPng(quantized.width, quantized.height, quantized.pixels);
|
|
10787
11358
|
const audit = auditRefinement(
|
|
@@ -10831,7 +11402,7 @@ async function refineAsset(options) {
|
|
|
10831
11402
|
);
|
|
10832
11403
|
return { source, output, record, detection, palette, audit };
|
|
10833
11404
|
} finally {
|
|
10834
|
-
await (0,
|
|
11405
|
+
await (0, import_promises7.rm)(temp, { recursive: true, force: true });
|
|
10835
11406
|
}
|
|
10836
11407
|
}
|
|
10837
11408
|
async function refineFrameSet(options) {
|
|
@@ -10845,18 +11416,18 @@ async function refineFrameSet(options) {
|
|
|
10845
11416
|
if (roles.some((role) => !role.trim()) || new Set(roles).size !== roles.length) {
|
|
10846
11417
|
throw new Error("Frame-set roles must be non-empty and unique.");
|
|
10847
11418
|
}
|
|
10848
|
-
const output =
|
|
10849
|
-
const record =
|
|
11419
|
+
const output = import_node_path11.default.resolve(options.output);
|
|
11420
|
+
const record = import_node_path11.default.resolve(qualityRecordPath(output));
|
|
10850
11421
|
const palette = normalizeRefinementPalette(options.palette);
|
|
10851
11422
|
const finalOutputs = options.sources.map((source, index) => qualityFrameOutputPath(output, source.role, index));
|
|
10852
11423
|
if (new Set(finalOutputs).size !== finalOutputs.length) {
|
|
10853
11424
|
throw new Error("Frame-set roles resolve to duplicate output filenames.");
|
|
10854
11425
|
}
|
|
10855
|
-
const sourceSet = new Set(options.sources.map((source) =>
|
|
11426
|
+
const sourceSet = new Set(options.sources.map((source) => import_node_path11.default.resolve(source.path)));
|
|
10856
11427
|
if (finalOutputs.some((file) => sourceSet.has(file))) {
|
|
10857
11428
|
throw new Error("Frame-set refinement sources and outputs must be different files.");
|
|
10858
11429
|
}
|
|
10859
|
-
const temp = await (0,
|
|
11430
|
+
const temp = await (0, import_promises7.mkdtemp)(import_node_path11.default.join((0, import_node_os3.tmpdir)(), "pixelkiln-refine-frames-"));
|
|
10860
11431
|
try {
|
|
10861
11432
|
const frames = [];
|
|
10862
11433
|
const outputs = [];
|
|
@@ -10864,8 +11435,8 @@ async function refineFrameSet(options) {
|
|
|
10864
11435
|
const sourcePaths = [];
|
|
10865
11436
|
let firstOptions = null;
|
|
10866
11437
|
for (const [index, sourceEntry] of options.sources.entries()) {
|
|
10867
|
-
const source =
|
|
10868
|
-
const temporaryOutput =
|
|
11438
|
+
const source = import_node_path11.default.resolve(sourceEntry.path);
|
|
11439
|
+
const temporaryOutput = import_node_path11.default.join(temp, `${String(index).padStart(2, "0")}.png`);
|
|
10869
11440
|
const refined = await refineAsset({
|
|
10870
11441
|
source,
|
|
10871
11442
|
output: temporaryOutput,
|
|
@@ -10897,7 +11468,7 @@ async function refineFrameSet(options) {
|
|
|
10897
11468
|
});
|
|
10898
11469
|
outputs.push({
|
|
10899
11470
|
path: finalOutputs[index],
|
|
10900
|
-
data: await (0,
|
|
11471
|
+
data: await (0, import_promises7.readFile)(temporaryOutput)
|
|
10901
11472
|
});
|
|
10902
11473
|
}
|
|
10903
11474
|
const first = frames[0];
|
|
@@ -10940,23 +11511,23 @@ async function refineFrameSet(options) {
|
|
|
10940
11511
|
);
|
|
10941
11512
|
return {
|
|
10942
11513
|
sources: sourcePaths,
|
|
10943
|
-
outputs: outputs.map((item) =>
|
|
11514
|
+
outputs: outputs.map((item) => import_node_path11.default.resolve(item.path)),
|
|
10944
11515
|
record,
|
|
10945
11516
|
frames,
|
|
10946
11517
|
palette,
|
|
10947
11518
|
audit
|
|
10948
11519
|
};
|
|
10949
11520
|
} finally {
|
|
10950
|
-
await (0,
|
|
11521
|
+
await (0, import_promises7.rm)(temp, { recursive: true, force: true });
|
|
10951
11522
|
}
|
|
10952
11523
|
}
|
|
10953
11524
|
async function checkQualityRecord(recordPath) {
|
|
10954
|
-
const record =
|
|
11525
|
+
const record = import_node_path11.default.resolve(recordPath);
|
|
10955
11526
|
const manifest = await readArtifactBundleManifest(record);
|
|
10956
11527
|
const options = parseRecordOptions(manifest, record);
|
|
10957
11528
|
const verification = await verifyArtifactBundle(record);
|
|
10958
|
-
const sources = manifest.sources.map((source2) =>
|
|
10959
|
-
const outputs = manifest.outputs.map((output2) =>
|
|
11529
|
+
const sources = manifest.sources.map((source2) => import_node_path11.default.resolve(import_node_path11.default.dirname(record), source2.path));
|
|
11530
|
+
const outputs = manifest.outputs.map((output2) => import_node_path11.default.resolve(import_node_path11.default.dirname(record), output2.path));
|
|
10960
11531
|
const source = sources[0] ?? "";
|
|
10961
11532
|
const output = outputs[0] ?? "";
|
|
10962
11533
|
const expectedCount = options.frameSet?.count ?? 1;
|
|
@@ -10989,7 +11560,7 @@ async function checkQualityRecord(recordPath) {
|
|
|
10989
11560
|
async function approveQualityRecord(recordPath, approval) {
|
|
10990
11561
|
const reviewer = approval.reviewer.trim();
|
|
10991
11562
|
if (!reviewer) throw new Error("A human reviewer name is required.");
|
|
10992
|
-
const record =
|
|
11563
|
+
const record = import_node_path11.default.resolve(recordPath);
|
|
10993
11564
|
const manifest = await readArtifactBundleManifest(record);
|
|
10994
11565
|
const options = parseRecordOptions(manifest, record);
|
|
10995
11566
|
const verification = await verifyArtifactBundle(record);
|
|
@@ -11010,14 +11581,14 @@ async function approveQualityRecord(recordPath, approval) {
|
|
|
11010
11581
|
`A refinement record must contain exactly ${expectedCount} source(s) and output(s).`
|
|
11011
11582
|
);
|
|
11012
11583
|
}
|
|
11013
|
-
const root =
|
|
11584
|
+
const root = import_node_path11.default.dirname(record);
|
|
11014
11585
|
const outputs = await Promise.all(manifest.outputs.map(async (entry) => {
|
|
11015
|
-
const outputPath =
|
|
11016
|
-
return { path: outputPath, data: await (0,
|
|
11586
|
+
const outputPath = import_node_path11.default.resolve(root, entry.path);
|
|
11587
|
+
return { path: outputPath, data: await (0, import_promises7.readFile)(outputPath) };
|
|
11017
11588
|
}));
|
|
11018
11589
|
const sources = manifest.sources.map((source) => ({
|
|
11019
11590
|
...source,
|
|
11020
|
-
path:
|
|
11591
|
+
path: import_node_path11.default.resolve(root, source.path)
|
|
11021
11592
|
}));
|
|
11022
11593
|
const approved = {
|
|
11023
11594
|
...options,
|
|
@@ -11049,8 +11620,8 @@ async function qualitySource(spec, lock) {
|
|
|
11049
11620
|
if (spec.generator === "frames") {
|
|
11050
11621
|
return { items: null, reason: "frame-set quality requires ordered downloaded provider outputs" };
|
|
11051
11622
|
}
|
|
11052
|
-
const source =
|
|
11053
|
-
return (0,
|
|
11623
|
+
const source = import_node_path12.default.resolve(spec.root, spec.source);
|
|
11624
|
+
return (0, import_node_fs9.existsSync)(source) ? { items: [{ role: "source", path: source }] } : { items: null, reason: `declared source is missing: ${spec.source}` };
|
|
11054
11625
|
}
|
|
11055
11626
|
const entry = lock.entries[lockKey(spec.styleId, spec.assetId)];
|
|
11056
11627
|
if (!entry) return { items: null, reason: "raw provider output is not in the lockfile" };
|
|
@@ -11079,7 +11650,7 @@ async function qualitySource(spec, lock) {
|
|
|
11079
11650
|
}
|
|
11080
11651
|
if (role) roles.add(role);
|
|
11081
11652
|
const source = currentEntryOutputPath(entry, spec, index);
|
|
11082
|
-
if (!(0,
|
|
11653
|
+
if (!(0, import_node_fs9.existsSync)(source)) return { items: null, reason: `raw provider output is missing: ${source}` };
|
|
11083
11654
|
if (await sha256File(source) !== output.sha256) {
|
|
11084
11655
|
return { items: null, reason: "raw provider output was modified after download" };
|
|
11085
11656
|
}
|
|
@@ -11098,7 +11669,7 @@ async function inspectQualityProfile(spec, lock) {
|
|
|
11098
11669
|
}
|
|
11099
11670
|
const sourcePaths = source.items.map((item) => item.path);
|
|
11100
11671
|
const expectedOutputs = spec.generator === "frames" ? source.items.map((item, index) => qualityFrameOutputPath(output, item.role, index)) : [output];
|
|
11101
|
-
if (!(0,
|
|
11672
|
+
if (!(0, import_node_fs9.existsSync)(record)) {
|
|
11102
11673
|
return {
|
|
11103
11674
|
key,
|
|
11104
11675
|
state: "needs-refinement",
|
|
@@ -11116,7 +11687,7 @@ async function inspectQualityProfile(spec, lock) {
|
|
|
11116
11687
|
const expectedRevision = spec.quality.fixerRevision ?? PIXEL_ART_FIXER_REVISION;
|
|
11117
11688
|
const expectedTransparency = spec.quality.minTransparency ?? null;
|
|
11118
11689
|
const expectedFrameSet = spec.generator === "frames" ? { fps: spec.quality.fps ?? 12, count: source.items.length, roles: source.items.map((item) => item.role) } : null;
|
|
11119
|
-
const contractChanged = JSON.stringify(check.sources.map((item) =>
|
|
11690
|
+
const contractChanged = JSON.stringify(check.sources.map((item) => import_node_path12.default.resolve(item))) !== JSON.stringify(sourcePaths.map((item) => import_node_path12.default.resolve(item))) || JSON.stringify(check.outputs.map((item) => import_node_path12.default.resolve(item))) !== JSON.stringify(expectedOutputs.map((item) => import_node_path12.default.resolve(item))) || JSON.stringify(check.options.palette.colors) !== JSON.stringify(expectedPalette) || check.options.nativeGrid.revision !== expectedRevision || check.options.audit.thresholds.minGridConfidence !== spec.quality.minGridConfidence || check.options.audit.thresholds.minTransparency !== expectedTransparency || (expectedFrameSet ? !check.options.frameSet || check.options.frameSet.fps !== expectedFrameSet.fps || check.options.frameSet.count !== expectedFrameSet.count || JSON.stringify(check.options.frameSet.roles) !== JSON.stringify(expectedFrameSet.roles) : check.options.frameSet !== void 0);
|
|
11120
11691
|
if (contractChanged) {
|
|
11121
11692
|
return {
|
|
11122
11693
|
key,
|
|
@@ -11279,8 +11850,8 @@ async function requireApprovedQualitySources(specs, lock, assetIds, outputRoles)
|
|
|
11279
11850
|
}
|
|
11280
11851
|
|
|
11281
11852
|
// src/pipeline/revision.ts
|
|
11282
|
-
var
|
|
11283
|
-
var
|
|
11853
|
+
var import_node_fs10 = require("fs");
|
|
11854
|
+
var import_node_path13 = __toESM(require("path"), 1);
|
|
11284
11855
|
async function inspectRevisionReadiness(spec, lock) {
|
|
11285
11856
|
if (spec.mirror) return inspectMirror(spec, lock, /* @__PURE__ */ new Set());
|
|
11286
11857
|
if (spec.character?.kind === "outfit") return inspectOutfit(spec, lock, /* @__PURE__ */ new Set());
|
|
@@ -11288,6 +11859,7 @@ async function inspectRevisionReadiness(spec, lock) {
|
|
|
11288
11859
|
if (spec.character?.styleAnchor) return inspectStyleAnchor(spec, lock, /* @__PURE__ */ new Set());
|
|
11289
11860
|
if (spec.objectPro?.parentSpec) return inspectObjectProParent(spec, lock, /* @__PURE__ */ new Set());
|
|
11290
11861
|
if (spec.scene) return inspectScene(spec);
|
|
11862
|
+
if (spec.vfx?.sprite && "file" in spec.vfx.sprite) return inspectVfxSprite(spec);
|
|
11291
11863
|
if (!spec.revision) return null;
|
|
11292
11864
|
return inspectRevision(spec, lock, /* @__PURE__ */ new Set());
|
|
11293
11865
|
}
|
|
@@ -11296,7 +11868,7 @@ async function inspectStyleAnchor(spec, lock, seen) {
|
|
|
11296
11868
|
const label = `style anchor ${spec.styleId}/${anchor.assetId}`;
|
|
11297
11869
|
const dependency = await inspectParent(anchor.spec, lock, seen);
|
|
11298
11870
|
if (!dependency.ready) return { ready: false, reason: `${label} is not ready: ${dependency.reason}` };
|
|
11299
|
-
if (!anchor.sha256 || !(0,
|
|
11871
|
+
if (!anchor.sha256 || !(0, import_node_fs10.existsSync)(anchor.file)) return { ready: false, reason: `${label} has no generated south-facing file yet` };
|
|
11300
11872
|
if (await sha256File(anchor.file) !== anchor.sha256) {
|
|
11301
11873
|
return { ready: false, reason: `${label} changed after the manifest was resolved` };
|
|
11302
11874
|
}
|
|
@@ -11310,11 +11882,17 @@ async function inspectScene(spec) {
|
|
|
11310
11882
|
const files = [{ label: "scene image", file: scene.file, sha256: scene.sha256 }];
|
|
11311
11883
|
if (scene.placement.type === "mask") files.push({ label: "scene mask", file: scene.placement.file, sha256: scene.placement.sha256 });
|
|
11312
11884
|
for (const { label, file, sha256: sha2562 } of files) {
|
|
11313
|
-
if (!sha2562 || !(0,
|
|
11885
|
+
if (!sha2562 || !(0, import_node_fs10.existsSync)(file)) return { ready: false, reason: `${label} does not exist yet: ${file}` };
|
|
11314
11886
|
if (await sha256File(file) !== sha2562) return { ready: false, reason: `${label} changed after the manifest was resolved` };
|
|
11315
11887
|
}
|
|
11316
11888
|
return { ready: true, reason: "scene is current" };
|
|
11317
11889
|
}
|
|
11890
|
+
async function inspectVfxSprite(spec) {
|
|
11891
|
+
const sprite = spec.vfx.sprite;
|
|
11892
|
+
if (!sprite.sha256 || !(0, import_node_fs10.existsSync)(sprite.file)) return { ready: false, reason: `vfx sprite does not exist yet: ${sprite.file}` };
|
|
11893
|
+
if (await sha256File(sprite.file) !== sprite.sha256) return { ready: false, reason: "vfx sprite changed after the manifest was resolved" };
|
|
11894
|
+
return { ready: true, reason: "vfx sprite is current" };
|
|
11895
|
+
}
|
|
11318
11896
|
async function inspectMirror(spec, lock, seen) {
|
|
11319
11897
|
const source = spec.mirror.sourceSpec;
|
|
11320
11898
|
const label = `mirror source ${spec.styleId}/${spec.mirror.sourceAssetId}`;
|
|
@@ -11327,7 +11905,7 @@ async function inspectOutfit(spec, lock, seen) {
|
|
|
11327
11905
|
const label = `outfit source ${spec.styleId}/${character.parentAssetId}`;
|
|
11328
11906
|
const dependency = await inspectParent(character.parentSpec, lock, seen);
|
|
11329
11907
|
if (!dependency.ready) return { ready: false, reason: `${label} is not ready: ${dependency.reason}` };
|
|
11330
|
-
if (!(0,
|
|
11908
|
+
if (!(0, import_node_fs10.existsSync)(outfit.reference.path)) {
|
|
11331
11909
|
return { ready: false, reason: `outfit reference is missing: ${outfit.reference.path}` };
|
|
11332
11910
|
}
|
|
11333
11911
|
if (await sha256File(outfit.reference.path) !== outfit.reference.sha256) {
|
|
@@ -11341,7 +11919,7 @@ async function inspectCharacterParent(spec, lock, seen) {
|
|
|
11341
11919
|
const label = `${character.kind} parent ${spec.styleId}/${character.parentAssetId}`;
|
|
11342
11920
|
const dependency = await inspectParent(parentSpec, lock, seen);
|
|
11343
11921
|
if (!dependency.ready) return { ready: false, reason: `${label} is not ready: ${dependency.reason}` };
|
|
11344
|
-
if (!character.parentSha256 || !character.parentFile || !(0,
|
|
11922
|
+
if (!character.parentSha256 || !character.parentFile || !(0, import_node_fs10.existsSync)(character.parentFile)) {
|
|
11345
11923
|
return { ready: false, reason: `${label} has no generated south-facing file yet` };
|
|
11346
11924
|
}
|
|
11347
11925
|
if (await sha256File(character.parentFile) !== character.parentSha256) {
|
|
@@ -11359,7 +11937,7 @@ async function inspectObjectProParent(spec, lock, seen) {
|
|
|
11359
11937
|
const label = `${object.kind} parent ${spec.styleId}/${object.parentAssetId}`;
|
|
11360
11938
|
const dependency = await inspectParent(parentSpec, lock, seen);
|
|
11361
11939
|
if (!dependency.ready) return { ready: false, reason: `${label} is not ready: ${dependency.reason}` };
|
|
11362
|
-
if (!object.parentSha256 || !object.parentFile || !(0,
|
|
11940
|
+
if (!object.parentSha256 || !object.parentFile || !(0, import_node_fs10.existsSync)(object.parentFile)) {
|
|
11363
11941
|
return { ready: false, reason: `${label} has no generated south-facing file yet` };
|
|
11364
11942
|
}
|
|
11365
11943
|
if (await sha256File(object.parentFile) !== object.parentSha256) {
|
|
@@ -11385,7 +11963,7 @@ async function inspectRevision(spec, lock, seen) {
|
|
|
11385
11963
|
const members = await inspectSourceMembers(spec, lock);
|
|
11386
11964
|
if (!members.ready) return members;
|
|
11387
11965
|
} else {
|
|
11388
|
-
if (!revision.sourceSha256 || !(0,
|
|
11966
|
+
if (!revision.sourceSha256 || !(0, import_node_fs10.existsSync)(revision.sourceFile)) {
|
|
11389
11967
|
return { ready: false, reason: `revision source is missing: ${revision.sourceFile}` };
|
|
11390
11968
|
}
|
|
11391
11969
|
if (await sha256File(revision.sourceFile) !== revision.sourceSha256) {
|
|
@@ -11393,7 +11971,7 @@ async function inspectRevision(spec, lock, seen) {
|
|
|
11393
11971
|
}
|
|
11394
11972
|
}
|
|
11395
11973
|
if (revision.maskFile) {
|
|
11396
|
-
if (!revision.maskSha256 || !(0,
|
|
11974
|
+
if (!revision.maskSha256 || !(0, import_node_fs10.existsSync)(revision.maskFile)) {
|
|
11397
11975
|
return { ready: false, reason: `revision mask is missing: ${revision.maskFile}` };
|
|
11398
11976
|
}
|
|
11399
11977
|
if (await sha256File(revision.maskFile) !== revision.maskSha256) {
|
|
@@ -11401,7 +11979,7 @@ async function inspectRevision(spec, lock, seen) {
|
|
|
11401
11979
|
}
|
|
11402
11980
|
}
|
|
11403
11981
|
if (revision.lastFrameFile) {
|
|
11404
|
-
if (!revision.lastFrameSha256 || !(0,
|
|
11982
|
+
if (!revision.lastFrameSha256 || !(0, import_node_fs10.existsSync)(revision.lastFrameFile)) {
|
|
11405
11983
|
return { ready: false, reason: `revision last frame is missing: ${revision.lastFrameFile}` };
|
|
11406
11984
|
}
|
|
11407
11985
|
if (await sha256File(revision.lastFrameFile) !== revision.lastFrameSha256) {
|
|
@@ -11414,7 +11992,7 @@ async function inspectSourceMembers(spec, lock) {
|
|
|
11414
11992
|
const revision = spec.revision;
|
|
11415
11993
|
const members = revision.sourceMembers;
|
|
11416
11994
|
for (const member of members) {
|
|
11417
|
-
if (!(0,
|
|
11995
|
+
if (!(0, import_node_fs10.existsSync)(member.file)) return { ready: false, reason: `revision source member is missing: ${member.file}` };
|
|
11418
11996
|
if (await sha256File(member.file) !== member.sha256) {
|
|
11419
11997
|
return { ready: false, reason: `revision source member changed after the manifest was resolved: ${member.file}` };
|
|
11420
11998
|
}
|
|
@@ -11423,8 +12001,8 @@ async function inspectSourceMembers(spec, lock) {
|
|
|
11423
12001
|
if (parent.source) return { ready: true, reason: "declared source set is present" };
|
|
11424
12002
|
const entry = lock.entries[lockKey(parent.styleId, parent.assetId)];
|
|
11425
12003
|
if (!entry) return { ready: false, reason: "revision source is not in the lockfile" };
|
|
11426
|
-
const recorded = entry.outputs.map((_, index) =>
|
|
11427
|
-
const found = members.map((member) =>
|
|
12004
|
+
const recorded = entry.outputs.map((_, index) => import_node_path13.default.resolve(currentEntryOutputPath(entry, parent, index))).sort();
|
|
12005
|
+
const found = members.map((member) => import_node_path13.default.resolve(member.file)).sort();
|
|
11428
12006
|
if (recorded.length !== found.length || recorded.some((file, index) => file !== found[index])) {
|
|
11429
12007
|
return {
|
|
11430
12008
|
ready: false,
|
|
@@ -11473,8 +12051,8 @@ async function inspectParent(spec, lock, seen) {
|
|
|
11473
12051
|
};
|
|
11474
12052
|
}
|
|
11475
12053
|
if (spec.source) {
|
|
11476
|
-
const source =
|
|
11477
|
-
return (0,
|
|
12054
|
+
const source = import_node_path13.default.resolve(spec.root, spec.source);
|
|
12055
|
+
return (0, import_node_fs10.existsSync)(source) ? { ready: true, reason: "declared source is present" } : { ready: false, reason: `declared source is missing: ${spec.source}` };
|
|
11478
12056
|
}
|
|
11479
12057
|
const entry = lock.entries[key];
|
|
11480
12058
|
if (!entry) return { ready: false, reason: "parent is not in the lockfile" };
|
|
@@ -11488,7 +12066,7 @@ async function inspectParent(spec, lock, seen) {
|
|
|
11488
12066
|
for (let index = 0; index < entry.outputs.length; index++) {
|
|
11489
12067
|
const output = entry.outputs[index];
|
|
11490
12068
|
const file = currentEntryOutputPath(entry, spec, index);
|
|
11491
|
-
if (!(0,
|
|
12069
|
+
if (!(0, import_node_fs10.existsSync)(file)) return { ready: false, reason: `parent output is missing: ${file}` };
|
|
11492
12070
|
if (await sha256File(file) !== output.sha256) {
|
|
11493
12071
|
return { ready: false, reason: `parent output was modified: ${file}` };
|
|
11494
12072
|
}
|
|
@@ -11551,8 +12129,8 @@ async function buildPlan(specs, lock, opts = {}) {
|
|
|
11551
12129
|
} else if (opts.force) {
|
|
11552
12130
|
state = "missing";
|
|
11553
12131
|
reason = "--force";
|
|
11554
|
-
} else if (!entry && spec.source && spec.generator !== "frames") {
|
|
11555
|
-
if ((0,
|
|
12132
|
+
} else if (!entry && spec.source && spec.generator !== "frames" && spec.generator !== "vfx") {
|
|
12133
|
+
if ((0, import_node_fs11.existsSync)(import_node_path14.default.resolve(spec.root, spec.source))) {
|
|
11556
12134
|
state = "ok";
|
|
11557
12135
|
reason = `placed from ${spec.source}; not generated`;
|
|
11558
12136
|
} else {
|
|
@@ -11560,7 +12138,7 @@ async function buildPlan(specs, lock, opts = {}) {
|
|
|
11560
12138
|
reason = `declared source is missing: ${spec.source}`;
|
|
11561
12139
|
}
|
|
11562
12140
|
} else if (!entry) {
|
|
11563
|
-
if ((0,
|
|
12141
|
+
if ((0, import_node_fs11.existsSync)(spec.outFile)) {
|
|
11564
12142
|
state = "untracked";
|
|
11565
12143
|
reason = "file on disk, no provenance recorded";
|
|
11566
12144
|
} else {
|
|
@@ -11573,7 +12151,7 @@ async function buildPlan(specs, lock, opts = {}) {
|
|
|
11573
12151
|
} else if (spec.mirror && !mirrorCurrent(entry, lock.entries[lockKey(spec.styleId, spec.mirror.sourceAssetId)])) {
|
|
11574
12152
|
state = "stale";
|
|
11575
12153
|
reason = `mirror source ${spec.mirror.sourceAssetId} changed; flip it again (no generation cost)`;
|
|
11576
|
-
} else if (spec.mirror && entry.status === "downloaded" && entry.outputs.some((_output, index) => !(0,
|
|
12154
|
+
} else if (spec.mirror && entry.status === "downloaded" && entry.outputs.some((_output, index) => !(0, import_node_fs11.existsSync)(currentEntryOutputPath(entry, spec, index)))) {
|
|
11577
12155
|
state = "stale";
|
|
11578
12156
|
reason = "mirror output missing on disk; flip it again (no generation cost)";
|
|
11579
12157
|
} else if (spec.character?.kind === "outfit" && entry.outfit?.sourceSha256 !== mirrorSourceHash(lock.entries[lockKey(spec.styleId, spec.character.parentAssetId)] ?? { outputs: [] })) {
|
|
@@ -11601,9 +12179,9 @@ async function buildPlan(specs, lock, opts = {}) {
|
|
|
11601
12179
|
} else if (entry.status !== "downloaded") {
|
|
11602
12180
|
state = "in-flight";
|
|
11603
12181
|
reason = `awaiting ${entry.status}`;
|
|
11604
|
-
} else if (entry.outputs.length === 0 || entry.outputs.some((_output, index) => !(0,
|
|
12182
|
+
} else if (entry.outputs.length === 0 || entry.outputs.some((_output, index) => !(0, import_node_fs11.existsSync)(currentEntryOutputPath(entry, spec, index)))) {
|
|
11605
12183
|
state = "orphaned";
|
|
11606
|
-
reason = entry.outputs.length === 0 ? "no recorded output files" : `missing on disk: ${entry.outputs.filter((_output, index) => !(0,
|
|
12184
|
+
reason = entry.outputs.length === 0 ? "no recorded output files" : `missing on disk: ${entry.outputs.filter((_output, index) => !(0, import_node_fs11.existsSync)(currentEntryOutputPath(entry, spec, index))).map((o) => o.role ?? "asset").join(", ")}`;
|
|
11607
12185
|
} else if (await anyOutputModified(entry, spec)) {
|
|
11608
12186
|
state = "orphaned";
|
|
11609
12187
|
reason = "output modified since download";
|
|
@@ -11694,13 +12272,13 @@ function summarize(plan) {
|
|
|
11694
12272
|
|
|
11695
12273
|
// src/project.ts
|
|
11696
12274
|
async function openProject(manifestPath = "pixelkiln.manifest.json", opts = {}) {
|
|
11697
|
-
const absolute =
|
|
11698
|
-
const root =
|
|
12275
|
+
const absolute = import_node_path15.default.resolve(manifestPath);
|
|
12276
|
+
const root = import_node_path15.default.dirname(absolute);
|
|
11699
12277
|
if (opts.env !== false) {
|
|
11700
12278
|
loadEnvFiles(root);
|
|
11701
|
-
if (
|
|
12279
|
+
if (import_node_path15.default.resolve(process.cwd()) !== root) loadEnvFiles(process.cwd());
|
|
11702
12280
|
}
|
|
11703
|
-
const lockPath =
|
|
12281
|
+
const lockPath = import_node_path15.default.resolve(opts.lockPath ?? import_node_path15.default.join(root, "pixelkiln.lock.json"));
|
|
11704
12282
|
const loaded = await loadManifest(absolute);
|
|
11705
12283
|
const specs = await resolveSpecs(loaded, { styles: opts.styles ?? [], assets: opts.assets ?? [] });
|
|
11706
12284
|
const lock = await loadLock(lockPath);
|
|
@@ -11767,9 +12345,9 @@ function shouldPersistSourceUrl(value) {
|
|
|
11767
12345
|
}
|
|
11768
12346
|
|
|
11769
12347
|
// src/recipes.ts
|
|
11770
|
-
var
|
|
11771
|
-
var
|
|
11772
|
-
var
|
|
12348
|
+
var import_node_path16 = __toESM(require("path"), 1);
|
|
12349
|
+
var import_node_fs12 = require("fs");
|
|
12350
|
+
var import_promises8 = require("fs/promises");
|
|
11773
12351
|
var import_node_url = require("url");
|
|
11774
12352
|
var import_zod4 = require("zod");
|
|
11775
12353
|
var SHA256_RE = /^[0-9a-f]{64}$/;
|
|
@@ -11778,7 +12356,7 @@ var VERSION_RE = /^\d+\.\d+\.\d+$/;
|
|
|
11778
12356
|
var RECIPE_FILE = "pixelkiln.recipe.json";
|
|
11779
12357
|
var RECIPE_DIR_TOKEN = "{{recipeDir}}";
|
|
11780
12358
|
var PortablePathSchema = import_zod4.z.string().min(1).refine((value) => {
|
|
11781
|
-
if (value.includes("\\") ||
|
|
12359
|
+
if (value.includes("\\") || import_node_path16.default.posix.isAbsolute(value)) return false;
|
|
11782
12360
|
const parts = value.split("/");
|
|
11783
12361
|
return !parts.includes("") && !parts.includes(".") && !parts.includes("..");
|
|
11784
12362
|
}, "expected a portable relative path without '.', '..', or backslashes");
|
|
@@ -11954,10 +12532,10 @@ function message3(error) {
|
|
|
11954
12532
|
return error instanceof Error ? error.message : String(error);
|
|
11955
12533
|
}
|
|
11956
12534
|
async function readRecipeFile(recipePath, bundled) {
|
|
11957
|
-
const absolute =
|
|
12535
|
+
const absolute = import_node_path16.default.resolve(recipePath);
|
|
11958
12536
|
let raw;
|
|
11959
12537
|
try {
|
|
11960
|
-
raw = JSON.parse(await (0,
|
|
12538
|
+
raw = JSON.parse(await (0, import_promises8.readFile)(absolute, "utf8"));
|
|
11961
12539
|
} catch (error) {
|
|
11962
12540
|
throw new Error(`Could not read recipe ${absolute}: ${message3(error)}`, { cause: error });
|
|
11963
12541
|
}
|
|
@@ -11968,13 +12546,13 @@ async function readRecipeFile(recipePath, bundled) {
|
|
|
11968
12546
|
` + parsed.error.issues.map((issue) => ` ${issue.path.join(".") || "$"}: ${issue.message}`).join("\n")
|
|
11969
12547
|
);
|
|
11970
12548
|
}
|
|
11971
|
-
return { recipe: parsed.data, authored: raw, path: absolute, dir:
|
|
12549
|
+
return { recipe: parsed.data, authored: raw, path: absolute, dir: import_node_path16.default.dirname(absolute), bundled };
|
|
11972
12550
|
}
|
|
11973
12551
|
async function walkRecipeFiles(root) {
|
|
11974
|
-
if (!(0,
|
|
12552
|
+
if (!(0, import_node_fs12.existsSync)(root)) return [];
|
|
11975
12553
|
const out = [];
|
|
11976
|
-
for (const entry of await (0,
|
|
11977
|
-
const resolved =
|
|
12554
|
+
for (const entry of await (0, import_promises8.readdir)(root, { withFileTypes: true })) {
|
|
12555
|
+
const resolved = import_node_path16.default.join(root, entry.name);
|
|
11978
12556
|
if (entry.isDirectory()) out.push(...await walkRecipeFiles(resolved));
|
|
11979
12557
|
else if (entry.isFile() && entry.name === RECIPE_FILE) out.push(resolved);
|
|
11980
12558
|
}
|
|
@@ -11985,7 +12563,7 @@ function bundledRecipeRoot() {
|
|
|
11985
12563
|
}
|
|
11986
12564
|
async function listBundledRecipes(root = bundledRecipeRoot()) {
|
|
11987
12565
|
const recipes = await Promise.all(
|
|
11988
|
-
(await walkRecipeFiles(
|
|
12566
|
+
(await walkRecipeFiles(import_node_path16.default.resolve(root))).map((file) => readRecipeFile(file, true))
|
|
11989
12567
|
);
|
|
11990
12568
|
return recipes.sort(
|
|
11991
12569
|
(a, b) => a.recipe.id.localeCompare(b.recipe.id) || compareVersions(b.recipe.version, a.recipe.version)
|
|
@@ -12008,9 +12586,9 @@ function parseSelector(selector) {
|
|
|
12008
12586
|
return { id: match[1], version: match[2] };
|
|
12009
12587
|
}
|
|
12010
12588
|
async function resolveRecipe(target, bundledRoot = bundledRecipeRoot()) {
|
|
12011
|
-
const local =
|
|
12012
|
-
if ((0,
|
|
12013
|
-
const recipePath =
|
|
12589
|
+
const local = import_node_path16.default.resolve(target);
|
|
12590
|
+
if ((0, import_node_fs12.existsSync)(local)) {
|
|
12591
|
+
const recipePath = import_node_path16.default.basename(local) === RECIPE_FILE ? local : import_node_path16.default.join(local, RECIPE_FILE);
|
|
12014
12592
|
return readRecipeFile(recipePath, false);
|
|
12015
12593
|
}
|
|
12016
12594
|
if (target.startsWith(".") || target.startsWith("/") || target.includes("\\")) {
|
|
@@ -12024,8 +12602,8 @@ async function resolveRecipe(target, bundledRoot = bundledRecipeRoot()) {
|
|
|
12024
12602
|
return matches[0];
|
|
12025
12603
|
}
|
|
12026
12604
|
async function verifyFile(root, file) {
|
|
12027
|
-
const absolute =
|
|
12028
|
-
if (!(0,
|
|
12605
|
+
const absolute = import_node_path16.default.resolve(root, ...file.path.split("/"));
|
|
12606
|
+
if (!(0, import_node_fs12.existsSync)(absolute)) {
|
|
12029
12607
|
return { path: file.path, expectedSha256: file.sha256, actualSha256: null, status: "missing" };
|
|
12030
12608
|
}
|
|
12031
12609
|
const actualSha256 = await sha256File(absolute);
|
|
@@ -12040,7 +12618,7 @@ async function verifyRecipe(target, options = {}) {
|
|
|
12040
12618
|
const loaded = await resolveRecipe(target, options.bundledRoot);
|
|
12041
12619
|
const actualIntegrity = recipeDigest(loaded.authored);
|
|
12042
12620
|
const files = await Promise.all(loaded.recipe.files.map((file) => verifyFile(loaded.dir, file)));
|
|
12043
|
-
const modelRoot = options.modelRoot ?
|
|
12621
|
+
const modelRoot = options.modelRoot ? import_node_path16.default.resolve(options.modelRoot) : null;
|
|
12044
12622
|
const models = modelRoot ? await Promise.all(loaded.recipe.models.map(async (model) => ({
|
|
12045
12623
|
...await verifyFile(modelRoot, model),
|
|
12046
12624
|
source: model.source,
|
|
@@ -12074,7 +12652,7 @@ async function verifyRecipe(target, options = {}) {
|
|
|
12074
12652
|
};
|
|
12075
12653
|
}
|
|
12076
12654
|
function renderStyle(style, destination, cwd) {
|
|
12077
|
-
const portableDir =
|
|
12655
|
+
const portableDir = import_node_path16.default.relative(cwd, destination).split(import_node_path16.default.sep).join("/") || ".";
|
|
12078
12656
|
const replace = (value) => {
|
|
12079
12657
|
if (typeof value === "string") return value.replaceAll(RECIPE_DIR_TOKEN, portableDir);
|
|
12080
12658
|
if (Array.isArray(value)) return value.map(replace);
|
|
@@ -12086,25 +12664,25 @@ function renderStyle(style, destination, cwd) {
|
|
|
12086
12664
|
return replace(style);
|
|
12087
12665
|
}
|
|
12088
12666
|
async function installRecipe(target, options = {}) {
|
|
12089
|
-
const cwd =
|
|
12667
|
+
const cwd = import_node_path16.default.resolve(options.cwd ?? process.cwd());
|
|
12090
12668
|
const loaded = await resolveRecipe(target, options.bundledRoot);
|
|
12091
12669
|
const verification = await verifyRecipe(loaded.path);
|
|
12092
12670
|
if (!verification.ok) {
|
|
12093
12671
|
throw new Error(`Recipe ${loaded.recipe.id}@${loaded.recipe.version} failed integrity verification.`);
|
|
12094
12672
|
}
|
|
12095
|
-
const destination = options.out ?
|
|
12673
|
+
const destination = options.out ? import_node_path16.default.resolve(cwd, options.out) : import_node_path16.default.join(cwd, "pixelkiln-recipes", ...loaded.recipe.id.split("/"), loaded.recipe.version);
|
|
12096
12674
|
const sources = [
|
|
12097
12675
|
{ path: RECIPE_FILE, source: loaded.path },
|
|
12098
|
-
...loaded.recipe.files.map((file) => ({ path: file.path, source:
|
|
12676
|
+
...loaded.recipe.files.map((file) => ({ path: file.path, source: import_node_path16.default.join(loaded.dir, ...file.path.split("/")) }))
|
|
12099
12677
|
];
|
|
12100
12678
|
const files = await Promise.all(sources.map(async (file) => ({
|
|
12101
|
-
path:
|
|
12102
|
-
data: await (0,
|
|
12679
|
+
path: import_node_path16.default.join(destination, ...file.path.split("/")),
|
|
12680
|
+
data: await (0, import_promises8.readFile)(file.source)
|
|
12103
12681
|
})));
|
|
12104
12682
|
if (!options.force) {
|
|
12105
12683
|
for (const file of files) {
|
|
12106
|
-
if (!(0,
|
|
12107
|
-
const current = await (0,
|
|
12684
|
+
if (!(0, import_node_fs12.existsSync)(file.path)) continue;
|
|
12685
|
+
const current = await (0, import_promises8.readFile)(file.path);
|
|
12108
12686
|
if (!current.equals(file.data)) {
|
|
12109
12687
|
throw new OverwriteRefusedError(
|
|
12110
12688
|
`Recipe destination has local changes: ${file.path}. Choose another --out or pass --force to replace declared recipe files.`,
|
|
@@ -12128,9 +12706,9 @@ async function installRecipe(target, options = {}) {
|
|
|
12128
12706
|
}
|
|
12129
12707
|
|
|
12130
12708
|
// src/pipeline/audit.ts
|
|
12131
|
-
var
|
|
12132
|
-
var
|
|
12133
|
-
var
|
|
12709
|
+
var import_promises9 = require("fs/promises");
|
|
12710
|
+
var import_node_fs13 = require("fs");
|
|
12711
|
+
var import_node_path17 = __toESM(require("path"), 1);
|
|
12134
12712
|
function paletteDistance(asset, reference) {
|
|
12135
12713
|
if (!asset.length || !reference.length) return 0;
|
|
12136
12714
|
let total = 0;
|
|
@@ -12166,12 +12744,12 @@ async function auditStyle(loaded, specs, styleId, lock) {
|
|
|
12166
12744
|
const unreadable = [];
|
|
12167
12745
|
for (const spec of mine) {
|
|
12168
12746
|
for (const output of resolveSpecOutputs(spec, lock, loaded.root)) {
|
|
12169
|
-
if (!(0,
|
|
12747
|
+
if (!(0, import_node_fs13.existsSync)(output.absolutePath)) {
|
|
12170
12748
|
missing.push(output.id);
|
|
12171
12749
|
continue;
|
|
12172
12750
|
}
|
|
12173
12751
|
try {
|
|
12174
|
-
const png = decodePng(await (0,
|
|
12752
|
+
const png = decodePng(await (0, import_promises9.readFile)(output.absolutePath));
|
|
12175
12753
|
const palette = extractPalette(png, 12);
|
|
12176
12754
|
assets.push({
|
|
12177
12755
|
assetId: spec.assetId,
|
|
@@ -12195,10 +12773,10 @@ async function auditStyle(loaded, specs, styleId, lock) {
|
|
|
12195
12773
|
let referenceFromStyleImages = false;
|
|
12196
12774
|
const refPalettes = [];
|
|
12197
12775
|
for (const img of style.styleImages) {
|
|
12198
|
-
const abs =
|
|
12199
|
-
if (!(0,
|
|
12776
|
+
const abs = import_node_path17.default.resolve(loaded.root, img.path);
|
|
12777
|
+
if (!(0, import_node_fs13.existsSync)(abs)) continue;
|
|
12200
12778
|
try {
|
|
12201
|
-
refPalettes.push(extractPalette(decodePng(await (0,
|
|
12779
|
+
refPalettes.push(extractPalette(decodePng(await (0, import_promises9.readFile)(abs)), 12));
|
|
12202
12780
|
} catch {
|
|
12203
12781
|
}
|
|
12204
12782
|
}
|
|
@@ -12274,20 +12852,20 @@ function hex(c) {
|
|
|
12274
12852
|
}
|
|
12275
12853
|
|
|
12276
12854
|
// src/pixellab-utils.ts
|
|
12277
|
-
var
|
|
12278
|
-
var
|
|
12279
|
-
var
|
|
12855
|
+
var import_node_fs14 = require("fs");
|
|
12856
|
+
var import_promises10 = require("fs/promises");
|
|
12857
|
+
var import_node_path18 = __toESM(require("path"), 1);
|
|
12280
12858
|
var UNZOOM_MIN_SIDE = 256;
|
|
12281
12859
|
var UNZOOM_MAX_AREA = 2048 * 2048;
|
|
12282
12860
|
function defaultUnzoomOut(input) {
|
|
12283
|
-
const ext =
|
|
12861
|
+
const ext = import_node_path18.default.extname(input);
|
|
12284
12862
|
return `${ext ? input.slice(0, -ext.length) : input}.unzoomed.png`;
|
|
12285
12863
|
}
|
|
12286
12864
|
async function unzoomFile(client2, input, opts = {}) {
|
|
12287
|
-
const out =
|
|
12288
|
-
if (
|
|
12289
|
-
if ((0,
|
|
12290
|
-
const bytes = await (0,
|
|
12865
|
+
const out = import_node_path18.default.resolve(opts.out ?? defaultUnzoomOut(input));
|
|
12866
|
+
if (import_node_path18.default.resolve(input) === out) throw new Error("unzoom will not overwrite its own input; pass a different --out");
|
|
12867
|
+
if ((0, import_node_fs14.existsSync)(out) && !opts.force) throw new Error(`${out} already exists; pass --force to replace it`);
|
|
12868
|
+
const bytes = await (0, import_promises10.readFile)(input);
|
|
12291
12869
|
const metadata = imageMetadata(bytes);
|
|
12292
12870
|
if (!metadata) throw new Error(`${input} is not a readable PNG or JPEG`);
|
|
12293
12871
|
const { width, height, format } = metadata;
|
|
@@ -12300,22 +12878,22 @@ async function unzoomFile(client2, input, opts = {}) {
|
|
|
12300
12878
|
throw new Error(`${input} is ${width}x${height}; PixelLab's unzoom takes at most 2048x2048 worth of pixels`);
|
|
12301
12879
|
}
|
|
12302
12880
|
const res = await client2.unzoom({ image: { base64: bytes.toString("base64"), format }, quantize: opts.quantize });
|
|
12303
|
-
await (0,
|
|
12304
|
-
await (0,
|
|
12881
|
+
await (0, import_promises10.mkdir)(import_node_path18.default.dirname(out), { recursive: true });
|
|
12882
|
+
await (0, import_promises10.writeFile)(out, res.png);
|
|
12305
12883
|
return { out, original: res.originalSize, unzoomed: res.unzoomedSize, zoomFactor: res.zoomFactor, usage: res.usage };
|
|
12306
12884
|
}
|
|
12307
12885
|
var FONT_WEIGHTS = ["Bold", "Regular"];
|
|
12308
12886
|
var FONT_GLYPH_SIZES = [8, 16, 32, 64];
|
|
12309
12887
|
var FONT_COST_GENERATIONS = 15;
|
|
12310
12888
|
function fontOutputPaths(out) {
|
|
12311
|
-
const base =
|
|
12889
|
+
const base = import_node_path18.default.resolve(out.replace(/\.(ttf|png)$/i, ""));
|
|
12312
12890
|
return { ttf: `${base}.ttf`, atlas: `${base}.png` };
|
|
12313
12891
|
}
|
|
12314
12892
|
async function generateFont(client2, opts) {
|
|
12315
12893
|
const { ttf, atlas } = fontOutputPaths(opts.out);
|
|
12316
12894
|
if (!opts.force) {
|
|
12317
12895
|
for (const file of [ttf, atlas]) {
|
|
12318
|
-
if ((0,
|
|
12896
|
+
if ((0, import_node_fs14.existsSync)(file)) throw new Error(`${file} already exists; pass --force to replace it`);
|
|
12319
12897
|
}
|
|
12320
12898
|
}
|
|
12321
12899
|
const sleep3 = opts.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
@@ -12342,9 +12920,9 @@ async function generateFont(client2, opts) {
|
|
|
12342
12920
|
client2.download(job.download_ttf_url),
|
|
12343
12921
|
client2.download(job.download_atlas_url)
|
|
12344
12922
|
]);
|
|
12345
|
-
await (0,
|
|
12346
|
-
await (0,
|
|
12347
|
-
await (0,
|
|
12923
|
+
await (0, import_promises10.mkdir)(import_node_path18.default.dirname(ttf), { recursive: true });
|
|
12924
|
+
await (0, import_promises10.writeFile)(ttf, ttfBytes);
|
|
12925
|
+
await (0, import_promises10.writeFile)(atlas, atlasBytes);
|
|
12348
12926
|
return { jobId, ttf, atlas, glyphPx: job.glyph_px ?? null, usage: job.usage };
|
|
12349
12927
|
}
|
|
12350
12928
|
if (Date.now() - started > timeout) {
|
|
@@ -12358,14 +12936,14 @@ async function generateFont(client2, opts) {
|
|
|
12358
12936
|
var PIXELATE_COST_GENERATIONS = 6;
|
|
12359
12937
|
var PIXELATE_MAX_SIDE = 2048;
|
|
12360
12938
|
function defaultPixelateOut(input) {
|
|
12361
|
-
const ext =
|
|
12939
|
+
const ext = import_node_path18.default.extname(input);
|
|
12362
12940
|
return `${ext ? input.slice(0, -ext.length) : input}.pixel.png`;
|
|
12363
12941
|
}
|
|
12364
12942
|
async function pixelateFile(client2, input, opts = {}) {
|
|
12365
|
-
const out =
|
|
12366
|
-
if (
|
|
12367
|
-
if ((0,
|
|
12368
|
-
const bytes = await (0,
|
|
12943
|
+
const out = import_node_path18.default.resolve(opts.out ?? defaultPixelateOut(input));
|
|
12944
|
+
if (import_node_path18.default.resolve(input) === out) throw new Error("pixelate will not overwrite its own input; pass a different --out");
|
|
12945
|
+
if ((0, import_node_fs14.existsSync)(out) && !opts.force) throw new Error(`${out} already exists; pass --force to replace it`);
|
|
12946
|
+
const bytes = await (0, import_promises10.readFile)(input);
|
|
12369
12947
|
const metadata = imageMetadata(bytes);
|
|
12370
12948
|
if (!metadata) throw new Error(`${input} is not a readable PNG or JPEG`);
|
|
12371
12949
|
const { width, height, format } = metadata;
|
|
@@ -12391,8 +12969,8 @@ async function pixelateFile(client2, input, opts = {}) {
|
|
|
12391
12969
|
}
|
|
12392
12970
|
const png = Buffer.from(first.base64, "base64");
|
|
12393
12971
|
const size = imageMetadata(png);
|
|
12394
|
-
await (0,
|
|
12395
|
-
await (0,
|
|
12972
|
+
await (0, import_promises10.mkdir)(import_node_path18.default.dirname(out), { recursive: true });
|
|
12973
|
+
await (0, import_promises10.writeFile)(out, png);
|
|
12396
12974
|
return {
|
|
12397
12975
|
jobId,
|
|
12398
12976
|
out,
|
|
@@ -12412,15 +12990,15 @@ async function pixelateFile(client2, input, opts = {}) {
|
|
|
12412
12990
|
}
|
|
12413
12991
|
|
|
12414
12992
|
// src/pipeline/quality-regression.ts
|
|
12415
|
-
var
|
|
12416
|
-
var
|
|
12417
|
-
var
|
|
12993
|
+
var import_node_path19 = __toESM(require("path"), 1);
|
|
12994
|
+
var import_node_fs15 = require("fs");
|
|
12995
|
+
var import_promises11 = require("fs/promises");
|
|
12418
12996
|
var import_zod5 = require("zod");
|
|
12419
12997
|
var SHA256_RE2 = /^[0-9a-f]{64}$/;
|
|
12420
12998
|
var HEX_RE = /^#[0-9a-f]{6}$/;
|
|
12421
12999
|
var MAX_REDMEAN_DISTANCE = 765;
|
|
12422
13000
|
var QualityPathSchema = import_zod5.z.string().min(1).refine(
|
|
12423
|
-
(value) => !
|
|
13001
|
+
(value) => !import_node_path19.default.posix.isAbsolute(value) && !value.includes("\\"),
|
|
12424
13002
|
"expected a portable relative path"
|
|
12425
13003
|
);
|
|
12426
13004
|
var DEFAULT_QUALITY_TOLERANCES = {
|
|
@@ -12567,10 +13145,10 @@ function measureDecodedImageQuality(png, digest2) {
|
|
|
12567
13145
|
};
|
|
12568
13146
|
}
|
|
12569
13147
|
async function measureImageQuality(file) {
|
|
12570
|
-
const absolute =
|
|
13148
|
+
const absolute = import_node_path19.default.resolve(file);
|
|
12571
13149
|
let bytes;
|
|
12572
13150
|
try {
|
|
12573
|
-
bytes = await (0,
|
|
13151
|
+
bytes = await (0, import_promises11.readFile)(absolute);
|
|
12574
13152
|
} catch (error) {
|
|
12575
13153
|
throw new Error(`Cannot read quality image ${absolute}: ${message4(error)}`, { cause: error });
|
|
12576
13154
|
}
|
|
@@ -12589,11 +13167,11 @@ function resolveQualityInputs(raw, inputsFilePath) {
|
|
|
12589
13167
|
}
|
|
12590
13168
|
const ids = parsed.data.map((entry) => entry.id);
|
|
12591
13169
|
if (new Set(ids).size !== ids.length) throw new Error("--inputs contains a duplicate quality case id.");
|
|
12592
|
-
const root =
|
|
13170
|
+
const root = import_node_path19.default.dirname(import_node_path19.default.resolve(inputsFilePath));
|
|
12593
13171
|
return parsed.data.map((entry) => ({
|
|
12594
13172
|
id: entry.id,
|
|
12595
|
-
file:
|
|
12596
|
-
...entry.record ? { record:
|
|
13173
|
+
file: import_node_path19.default.resolve(root, entry.path),
|
|
13174
|
+
...entry.record ? { record: import_node_path19.default.resolve(root, entry.record) } : {},
|
|
12597
13175
|
tolerances: QualityToleranceSchema.parse({
|
|
12598
13176
|
...DEFAULT_QUALITY_TOLERANCES,
|
|
12599
13177
|
...entry.tolerances
|
|
@@ -12601,7 +13179,7 @@ function resolveQualityInputs(raw, inputsFilePath) {
|
|
|
12601
13179
|
}));
|
|
12602
13180
|
}
|
|
12603
13181
|
function portableRelative2(from, to) {
|
|
12604
|
-
return
|
|
13182
|
+
return import_node_path19.default.relative(from, import_node_path19.default.resolve(to)).split(import_node_path19.default.sep).join("/") || ".";
|
|
12605
13183
|
}
|
|
12606
13184
|
async function verifyRecordForImage(recordPath, imagePath) {
|
|
12607
13185
|
const verification = await verifyArtifactBundle(recordPath);
|
|
@@ -12616,20 +13194,20 @@ async function verifyRecordForImage(recordPath, imagePath) {
|
|
|
12616
13194
|
if (record.kind !== "refine" || options === null || typeof options !== "object" || options.schema !== "pixelkiln-quality") {
|
|
12617
13195
|
throw new Error(`Quality record ${recordPath} is not a PixelKiln refinement record.`);
|
|
12618
13196
|
}
|
|
12619
|
-
const outputs = record.outputs.map((output) =>
|
|
12620
|
-
if (!outputs.includes(
|
|
13197
|
+
const outputs = record.outputs.map((output) => import_node_path19.default.resolve(import_node_path19.default.dirname(recordPath), output.path));
|
|
13198
|
+
if (!outputs.includes(import_node_path19.default.resolve(imagePath))) {
|
|
12621
13199
|
throw new Error(`Quality record ${recordPath} does not own ${imagePath}.`);
|
|
12622
13200
|
}
|
|
12623
13201
|
}
|
|
12624
13202
|
async function snapshotQualityBaseline(inputs, baselinePath, options = {}) {
|
|
12625
13203
|
if (!inputs.length) throw new Error("Cannot snapshot an empty quality baseline.");
|
|
12626
|
-
const absolute =
|
|
13204
|
+
const absolute = import_node_path19.default.resolve(baselinePath);
|
|
12627
13205
|
for (const input of inputs) {
|
|
12628
|
-
if (
|
|
13206
|
+
if (import_node_path19.default.resolve(input.file) === absolute || input.record && import_node_path19.default.resolve(input.record) === absolute) {
|
|
12629
13207
|
throw new Error(`Quality baseline output would overwrite an input: ${absolute}.`);
|
|
12630
13208
|
}
|
|
12631
13209
|
}
|
|
12632
|
-
const root =
|
|
13210
|
+
const root = import_node_path19.default.dirname(absolute);
|
|
12633
13211
|
const cases = [];
|
|
12634
13212
|
for (const input of [...inputs].sort((a, b) => a.id.localeCompare(b.id))) {
|
|
12635
13213
|
const expected = await measureImageQuality(input.file);
|
|
@@ -12656,8 +13234,8 @@ async function snapshotQualityBaseline(inputs, baselinePath, options = {}) {
|
|
|
12656
13234
|
cases
|
|
12657
13235
|
});
|
|
12658
13236
|
const data = Buffer.from(JSON.stringify(baseline, null, 2) + "\n");
|
|
12659
|
-
if ((0,
|
|
12660
|
-
const current = await (0,
|
|
13237
|
+
if ((0, import_node_fs15.existsSync)(absolute) && !options.force) {
|
|
13238
|
+
const current = await (0, import_promises11.readFile)(absolute);
|
|
12661
13239
|
if (!current.equals(data)) {
|
|
12662
13240
|
throw new OverwriteRefusedError(
|
|
12663
13241
|
`Quality baseline already exists with different content: ${absolute}. Pass --force to replace it.`,
|
|
@@ -12669,10 +13247,10 @@ async function snapshotQualityBaseline(inputs, baselinePath, options = {}) {
|
|
|
12669
13247
|
return { baseline, path: absolute, changed: result.changed.length > 0 };
|
|
12670
13248
|
}
|
|
12671
13249
|
async function readQualityBaseline(baselinePath) {
|
|
12672
|
-
const absolute =
|
|
13250
|
+
const absolute = import_node_path19.default.resolve(baselinePath);
|
|
12673
13251
|
let raw;
|
|
12674
13252
|
try {
|
|
12675
|
-
raw = JSON.parse(await (0,
|
|
13253
|
+
raw = JSON.parse(await (0, import_promises11.readFile)(absolute, "utf8"));
|
|
12676
13254
|
} catch (error) {
|
|
12677
13255
|
throw new Error(`Could not read quality baseline ${absolute}: ${message4(error)}`, { cause: error });
|
|
12678
13256
|
}
|
|
@@ -12742,12 +13320,12 @@ function regressionViolations(expected, actual, tolerances) {
|
|
|
12742
13320
|
return { violations, warnings };
|
|
12743
13321
|
}
|
|
12744
13322
|
async function checkQualityBaseline(baselinePath) {
|
|
12745
|
-
const absolute =
|
|
13323
|
+
const absolute = import_node_path19.default.resolve(baselinePath);
|
|
12746
13324
|
const baseline = await readQualityBaseline(absolute);
|
|
12747
|
-
const root =
|
|
13325
|
+
const root = import_node_path19.default.dirname(absolute);
|
|
12748
13326
|
const cases = [];
|
|
12749
13327
|
for (const entry of baseline.cases) {
|
|
12750
|
-
const file =
|
|
13328
|
+
const file = import_node_path19.default.resolve(root, entry.file);
|
|
12751
13329
|
let actual = null;
|
|
12752
13330
|
const violations = [];
|
|
12753
13331
|
const warnings = [];
|
|
@@ -12760,7 +13338,7 @@ async function checkQualityBaseline(baselinePath) {
|
|
|
12760
13338
|
violations.push(message4(error));
|
|
12761
13339
|
}
|
|
12762
13340
|
if (entry.record) {
|
|
12763
|
-
const recordPath =
|
|
13341
|
+
const recordPath = import_node_path19.default.resolve(root, entry.record.path);
|
|
12764
13342
|
try {
|
|
12765
13343
|
const recordHash = await sha256File(recordPath);
|
|
12766
13344
|
if (recordHash !== entry.record.sha256) violations.push("quality record hash changed");
|
|
@@ -12796,14 +13374,14 @@ async function checkQualityBaseline(baselinePath) {
|
|
|
12796
13374
|
}
|
|
12797
13375
|
|
|
12798
13376
|
// src/pipeline/cache-health.ts
|
|
12799
|
-
var
|
|
12800
|
-
var
|
|
12801
|
-
var
|
|
13377
|
+
var import_node_fs19 = require("fs");
|
|
13378
|
+
var import_promises14 = require("fs/promises");
|
|
13379
|
+
var import_node_path23 = __toESM(require("path"), 1);
|
|
12802
13380
|
|
|
12803
13381
|
// src/cache.ts
|
|
12804
|
-
var
|
|
12805
|
-
var
|
|
12806
|
-
var
|
|
13382
|
+
var import_promises12 = require("fs/promises");
|
|
13383
|
+
var import_node_fs16 = require("fs");
|
|
13384
|
+
var import_node_path20 = __toESM(require("path"), 1);
|
|
12807
13385
|
var import_zod6 = require("zod");
|
|
12808
13386
|
var HashCacheSchema = import_zod6.z.object({
|
|
12809
13387
|
version: import_zod6.z.literal(1),
|
|
@@ -12817,10 +13395,10 @@ function isSha256Hash(value) {
|
|
|
12817
13395
|
function parseCache(value) {
|
|
12818
13396
|
return HashCacheSchema.parse(value);
|
|
12819
13397
|
}
|
|
12820
|
-
async function loadCache(
|
|
12821
|
-
if (!(0,
|
|
13398
|
+
async function loadCache(path39) {
|
|
13399
|
+
if (!(0, import_node_fs16.existsSync)(path39)) return { version: 1, hashes: {} };
|
|
12822
13400
|
try {
|
|
12823
|
-
const parsed = HashCacheSchema.safeParse(JSON.parse(await (0,
|
|
13401
|
+
const parsed = HashCacheSchema.safeParse(JSON.parse(await (0, import_promises12.readFile)(path39, "utf8")));
|
|
12824
13402
|
if (!parsed.success) return { version: 1, hashes: {} };
|
|
12825
13403
|
return {
|
|
12826
13404
|
version: 1,
|
|
@@ -12832,20 +13410,20 @@ async function loadCache(path38) {
|
|
|
12832
13410
|
return { version: 1, hashes: {} };
|
|
12833
13411
|
}
|
|
12834
13412
|
}
|
|
12835
|
-
async function saveCache(
|
|
13413
|
+
async function saveCache(path39, cache) {
|
|
12836
13414
|
const sorted = {};
|
|
12837
13415
|
for (const key of Object.keys(cache.hashes).sort()) {
|
|
12838
13416
|
const hash = cache.hashes[key];
|
|
12839
13417
|
if (!isSha256Hash(hash)) throw new Error(`Refusing to cache invalid SHA-256 for ${key}`);
|
|
12840
13418
|
sorted[key] = hash;
|
|
12841
13419
|
}
|
|
12842
|
-
await (0,
|
|
12843
|
-
const tmp = `${
|
|
13420
|
+
await (0, import_promises12.mkdir)(import_node_path20.default.dirname(import_node_path20.default.resolve(path39)), { recursive: true });
|
|
13421
|
+
const tmp = `${path39}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
12844
13422
|
try {
|
|
12845
|
-
await (0,
|
|
12846
|
-
await (0,
|
|
13423
|
+
await (0, import_promises12.writeFile)(tmp, JSON.stringify({ version: 1, hashes: sorted }, null, 2) + "\n");
|
|
13424
|
+
await (0, import_promises12.rename)(tmp, path39);
|
|
12847
13425
|
} finally {
|
|
12848
|
-
await (0,
|
|
13426
|
+
await (0, import_promises12.rm)(tmp, { force: true });
|
|
12849
13427
|
}
|
|
12850
13428
|
}
|
|
12851
13429
|
function pruneCache(cache, liveIds, namespace = "") {
|
|
@@ -12867,20 +13445,20 @@ function cachePathFor(lockPath) {
|
|
|
12867
13445
|
}
|
|
12868
13446
|
|
|
12869
13447
|
// src/pipeline/history.ts
|
|
12870
|
-
var
|
|
12871
|
-
var
|
|
13448
|
+
var import_node_fs18 = require("fs");
|
|
13449
|
+
var import_node_path22 = __toESM(require("path"), 1);
|
|
12872
13450
|
|
|
12873
13451
|
// src/pipeline/fetch.ts
|
|
12874
|
-
var
|
|
12875
|
-
var
|
|
12876
|
-
var
|
|
13452
|
+
var import_node_fs17 = require("fs");
|
|
13453
|
+
var import_promises13 = require("fs/promises");
|
|
13454
|
+
var import_node_path21 = __toESM(require("path"), 1);
|
|
12877
13455
|
async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
12878
13456
|
const log = opts.onProgress ?? (() => {
|
|
12879
13457
|
});
|
|
12880
13458
|
const result = { downloaded: 0, skipped: 0, failed: 0, ...opts.refresh ? { unchanged: 0 } : {} };
|
|
12881
13459
|
const specByKey = new Map(specs.map((s) => [lockKey(s.styleId, s.assetId), s]));
|
|
12882
13460
|
normalizeLockOutputPaths(lock, specs);
|
|
12883
|
-
const cacheDir = opts.cacheDir === false ? null :
|
|
13461
|
+
const cacheDir = opts.cacheDir === false ? null : import_node_path21.default.resolve(opts.cacheDir ?? import_node_path21.default.join(import_node_path21.default.dirname(lockPath), ".pixelkiln", "cache"));
|
|
12884
13462
|
const pending = Object.entries(lock.entries).filter(([key, e]) => {
|
|
12885
13463
|
if (e.provider !== provider.id || !specByKey.has(key)) return false;
|
|
12886
13464
|
if (specByKey.get(key)?.mirror) return false;
|
|
@@ -12891,15 +13469,15 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
12891
13469
|
if (spec && e.specHash === spec.specHash && e.outputs.length && !postprocessCurrent(e, spec)) {
|
|
12892
13470
|
return Boolean(opts.force) || e.outputs.every((output) => {
|
|
12893
13471
|
const file = resolveOutputPath(output.path, spec.root);
|
|
12894
|
-
return (0,
|
|
13472
|
+
return (0, import_node_fs17.existsSync)(file) && sha256((0, import_node_fs17.readFileSync)(file)) === output.sha256;
|
|
12895
13473
|
});
|
|
12896
13474
|
}
|
|
12897
13475
|
if (!opts.repair) return false;
|
|
12898
13476
|
if (e.outputs.length === 0 || !spec) return true;
|
|
12899
13477
|
return e.outputs.some((output) => {
|
|
12900
13478
|
const file = resolveOutputPath(output.path, spec.root);
|
|
12901
|
-
if (!(0,
|
|
12902
|
-
const current = sha256((0,
|
|
13479
|
+
if (!(0, import_node_fs17.existsSync)(file)) return true;
|
|
13480
|
+
const current = sha256((0, import_node_fs17.readFileSync)(file));
|
|
12903
13481
|
if (current === output.sha256) return false;
|
|
12904
13482
|
return Boolean(opts.force) || (e.supersededOutputs ?? []).some((old) => old.sha256 === current);
|
|
12905
13483
|
});
|
|
@@ -12982,21 +13560,21 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
12982
13560
|
if (!source.url) throw new Error(`no durable source URL to refresh ${source.role ?? "asset"} from`);
|
|
12983
13561
|
fresh = await provider.download(source.url);
|
|
12984
13562
|
if (recorded && sha256(fresh) === recordedRaw && !reapply) {
|
|
12985
|
-
log(` current ${
|
|
13563
|
+
log(` current ${import_node_path21.default.relative(process.cwd(), target)}`);
|
|
12986
13564
|
outputs.push({ ...recorded, path: portableOutputPath(target, spec.root) });
|
|
12987
13565
|
continue;
|
|
12988
13566
|
}
|
|
12989
13567
|
}
|
|
12990
13568
|
let raw = null;
|
|
12991
13569
|
let final = null;
|
|
12992
|
-
if ((0,
|
|
13570
|
+
if ((0, import_node_fs17.existsSync)(target)) {
|
|
12993
13571
|
const currentHash = await sha256File(target);
|
|
12994
13572
|
if (recorded && currentHash === recorded.sha256) {
|
|
12995
13573
|
if (!fresh && !reapply) {
|
|
12996
13574
|
if (cacheDir) {
|
|
12997
13575
|
await cacheMedia(
|
|
12998
13576
|
cacheDir,
|
|
12999
|
-
await (0,
|
|
13577
|
+
await (0, import_promises13.readFile)(target),
|
|
13000
13578
|
recorded.mediaType ?? MediaType.PNG,
|
|
13001
13579
|
recorded.sha256
|
|
13002
13580
|
);
|
|
@@ -13005,10 +13583,10 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
13005
13583
|
continue;
|
|
13006
13584
|
}
|
|
13007
13585
|
if (reapply && !fresh) {
|
|
13008
|
-
if (!recorded.raw) raw = await (0,
|
|
13009
|
-
log(` reapply ${
|
|
13586
|
+
if (!recorded.raw) raw = await (0, import_promises13.readFile)(target);
|
|
13587
|
+
log(` reapply ${import_node_path21.default.relative(process.cwd(), target)}`);
|
|
13010
13588
|
} else {
|
|
13011
|
-
log(` changed ${
|
|
13589
|
+
log(` changed ${import_node_path21.default.relative(process.cwd(), target)} (upstream)`);
|
|
13012
13590
|
}
|
|
13013
13591
|
} else {
|
|
13014
13592
|
const superseded = (entry.supersededOutputs ?? []).find(
|
|
@@ -13029,14 +13607,14 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
13029
13607
|
}
|
|
13030
13608
|
if (outgoing && cacheDir) {
|
|
13031
13609
|
try {
|
|
13032
|
-
const oldBytes = await (0,
|
|
13610
|
+
const oldBytes = await (0, import_promises13.readFile)(target);
|
|
13033
13611
|
const oldMediaType = detectMediaType(oldBytes);
|
|
13034
13612
|
if (oldMediaType) await cacheMedia(cacheDir, oldBytes, oldMediaType, currentHash);
|
|
13035
13613
|
} catch {
|
|
13036
13614
|
}
|
|
13037
13615
|
}
|
|
13038
13616
|
log(
|
|
13039
|
-
` replace ${
|
|
13617
|
+
` replace ${import_node_path21.default.relative(process.cwd(), target)}` + (opts.force ? " (--force)" : " (previous tracked generation)")
|
|
13040
13618
|
);
|
|
13041
13619
|
}
|
|
13042
13620
|
}
|
|
@@ -13045,7 +13623,7 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
13045
13623
|
} else if (!raw && recorded && cacheDir) {
|
|
13046
13624
|
if (!reapply) final = await readCachedMedia(cacheDir, recorded.sha256, expectedMediaType);
|
|
13047
13625
|
if (!final && recordedRaw) raw = await readCachedMedia(cacheDir, recordedRaw, expectedMediaType);
|
|
13048
|
-
if (final || raw) log(` cached ${
|
|
13626
|
+
if (final || raw) log(` cached ${import_node_path21.default.relative(process.cwd(), target)}`);
|
|
13049
13627
|
}
|
|
13050
13628
|
if (!final && !raw) {
|
|
13051
13629
|
if (!source.url) {
|
|
@@ -13072,13 +13650,13 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
13072
13650
|
buf = processed.bytes;
|
|
13073
13651
|
if (processed.changed) {
|
|
13074
13652
|
rawHash = sha256(raw);
|
|
13075
|
-
log(` snapped ${
|
|
13653
|
+
log(` snapped ${import_node_path21.default.relative(process.cwd(), target)} to ${wanted.palette.colors.length} colours`);
|
|
13076
13654
|
}
|
|
13077
13655
|
}
|
|
13078
13656
|
if (cacheDir) await cacheMedia(cacheDir, buf, mediaType);
|
|
13079
|
-
await (0,
|
|
13657
|
+
await (0, import_promises13.mkdir)(import_node_path21.default.dirname(target), { recursive: true });
|
|
13080
13658
|
const tmp = `${target}.pixelkiln.tmp`;
|
|
13081
|
-
await (0,
|
|
13659
|
+
await (0, import_promises13.writeFile)(tmp, buf);
|
|
13082
13660
|
outputs.push({
|
|
13083
13661
|
path: portableOutputPath(target, spec.root),
|
|
13084
13662
|
sha256: sha256(buf),
|
|
@@ -13088,9 +13666,9 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
13088
13666
|
});
|
|
13089
13667
|
upsert(lock, key, { outputs: mergeOutputs(entry.outputs, outputs) });
|
|
13090
13668
|
await saveLock(lockPath, lock);
|
|
13091
|
-
await (0,
|
|
13669
|
+
await (0, import_promises13.rename)(tmp, target);
|
|
13092
13670
|
wrote = true;
|
|
13093
|
-
log(` wrote ${
|
|
13671
|
+
log(` wrote ${import_node_path21.default.relative(process.cwd(), target)}`);
|
|
13094
13672
|
}
|
|
13095
13673
|
if (opts.refresh && !wrote) {
|
|
13096
13674
|
if (rotated) {
|
|
@@ -13134,10 +13712,10 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
13134
13712
|
return result;
|
|
13135
13713
|
}
|
|
13136
13714
|
async function readCachedMedia(cacheDir, hash, mediaType) {
|
|
13137
|
-
const file =
|
|
13138
|
-
if (!(0,
|
|
13715
|
+
const file = import_node_path21.default.join(cacheDir, cacheFileName(hash, mediaType));
|
|
13716
|
+
if (!(0, import_node_fs17.existsSync)(file)) return null;
|
|
13139
13717
|
try {
|
|
13140
|
-
const buf = await (0,
|
|
13718
|
+
const buf = await (0, import_promises13.readFile)(file);
|
|
13141
13719
|
if (sha256(buf) !== hash) return null;
|
|
13142
13720
|
validateMedia(buf, mediaType);
|
|
13143
13721
|
return buf;
|
|
@@ -13148,17 +13726,17 @@ async function readCachedMedia(cacheDir, hash, mediaType) {
|
|
|
13148
13726
|
async function cacheMedia(cacheDir, buf, mediaType, knownHash) {
|
|
13149
13727
|
const hash = knownHash ?? sha256(buf);
|
|
13150
13728
|
validateMedia(buf, mediaType);
|
|
13151
|
-
const file =
|
|
13729
|
+
const file = import_node_path21.default.join(cacheDir, cacheFileName(hash, mediaType));
|
|
13152
13730
|
if (await readCachedMedia(cacheDir, hash, mediaType)) return;
|
|
13153
|
-
await (0,
|
|
13731
|
+
await (0, import_promises13.mkdir)(cacheDir, { recursive: true });
|
|
13154
13732
|
const tmp = `${file}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
13155
|
-
await (0,
|
|
13733
|
+
await (0, import_promises13.writeFile)(tmp, buf);
|
|
13156
13734
|
try {
|
|
13157
|
-
await (0,
|
|
13735
|
+
await (0, import_promises13.rename)(tmp, file);
|
|
13158
13736
|
} catch (err) {
|
|
13159
|
-
if (!(0,
|
|
13737
|
+
if (!(0, import_node_fs17.existsSync)(file)) throw err;
|
|
13160
13738
|
} finally {
|
|
13161
|
-
await (0,
|
|
13739
|
+
await (0, import_promises13.rm)(tmp, { force: true });
|
|
13162
13740
|
}
|
|
13163
13741
|
}
|
|
13164
13742
|
function mergeOutputs(previous, current) {
|
|
@@ -13176,7 +13754,7 @@ async function pushTags(provider, specs, lock, opts = {}) {
|
|
|
13176
13754
|
for (const [key, entry] of Object.entries(lock.entries)) {
|
|
13177
13755
|
const spec = specByKey.get(key);
|
|
13178
13756
|
if (!spec || entry.provider !== provider.id || !entry.objectId) continue;
|
|
13179
|
-
if (entry.generator === "pixflux") continue;
|
|
13757
|
+
if (entry.generator === "pixflux" || entry.generator === "vfx") continue;
|
|
13180
13758
|
if (!provider.setTags) return tagged;
|
|
13181
13759
|
try {
|
|
13182
13760
|
await provider.setTags(entry.objectId, spec.tags, entry.generator);
|
|
@@ -13275,10 +13853,10 @@ async function revertGeneration(provider, spec, lock, lockPath, opts) {
|
|
|
13275
13853
|
}
|
|
13276
13854
|
for (let i = 0; i < entry.outputs.length; i++) {
|
|
13277
13855
|
const file = currentEntryOutputPath(entry, spec, i);
|
|
13278
|
-
if (!(0,
|
|
13856
|
+
if (!(0, import_node_fs18.existsSync)(file)) continue;
|
|
13279
13857
|
if (await sha256File(file) !== entry.outputs[i].sha256 && !opts.force) {
|
|
13280
13858
|
throw new OverwriteRefusedError(
|
|
13281
|
-
`${
|
|
13859
|
+
`${import_node_path22.default.relative(process.cwd(), file)} was changed after download; keep it with pixelkiln edit, or pass --force to replace it`,
|
|
13282
13860
|
[file]
|
|
13283
13861
|
);
|
|
13284
13862
|
}
|
|
@@ -13330,11 +13908,11 @@ async function revertGeneration(provider, spec, lock, lockPath, opts) {
|
|
|
13330
13908
|
|
|
13331
13909
|
// src/pipeline/cache-health.ts
|
|
13332
13910
|
async function inspectCaches(lock, lockPath, options = {}) {
|
|
13333
|
-
if (options.prune && !(0,
|
|
13334
|
-
throw new Error(`Refusing to prune without an existing lockfile at ${
|
|
13911
|
+
if (options.prune && !(0, import_node_fs19.existsSync)(lockPath)) {
|
|
13912
|
+
throw new Error(`Refusing to prune without an existing lockfile at ${import_node_path23.default.resolve(lockPath)}`);
|
|
13335
13913
|
}
|
|
13336
|
-
const contentDir =
|
|
13337
|
-
const remotePath =
|
|
13914
|
+
const contentDir = import_node_path23.default.resolve(import_node_path23.default.dirname(lockPath), ".pixelkiln", "cache");
|
|
13915
|
+
const remotePath = import_node_path23.default.resolve(cachePathFor(lockPath));
|
|
13338
13916
|
const referenced = referencedHashes(lock);
|
|
13339
13917
|
let content = await inspectContentCache(contentDir, referenced);
|
|
13340
13918
|
let remoteHashes = await inspectRemoteHashCache(remotePath);
|
|
@@ -13346,7 +13924,7 @@ async function inspectCaches(lock, lockPath, options = {}) {
|
|
|
13346
13924
|
]);
|
|
13347
13925
|
for (const name of names) {
|
|
13348
13926
|
try {
|
|
13349
|
-
await (0,
|
|
13927
|
+
await (0, import_promises14.rm)(import_node_path23.default.join(contentDir, name), { force: true });
|
|
13350
13928
|
removed.contentFiles++;
|
|
13351
13929
|
} catch {
|
|
13352
13930
|
}
|
|
@@ -13355,7 +13933,7 @@ async function inspectCaches(lock, lockPath, options = {}) {
|
|
|
13355
13933
|
await saveCache(remotePath, { version: 1, hashes: {} });
|
|
13356
13934
|
removed.resetRemoteHashCache = true;
|
|
13357
13935
|
} else if (remoteHashes.invalidIds.length) {
|
|
13358
|
-
const cache = parseCache(JSON.parse(await (0,
|
|
13936
|
+
const cache = parseCache(JSON.parse(await (0, import_promises14.readFile)(remotePath, "utf8")));
|
|
13359
13937
|
for (const id of remoteHashes.invalidIds) delete cache.hashes[id];
|
|
13360
13938
|
removed.remoteHashEntries = remoteHashes.invalidIds.length;
|
|
13361
13939
|
await saveCache(remotePath, cache);
|
|
@@ -13373,7 +13951,7 @@ async function inspectCaches(lock, lockPath, options = {}) {
|
|
|
13373
13951
|
async function inspectContentCache(contentDir, referenced) {
|
|
13374
13952
|
const report = {
|
|
13375
13953
|
path: contentDir,
|
|
13376
|
-
exists: (0,
|
|
13954
|
+
exists: (0, import_node_fs19.existsSync)(contentDir),
|
|
13377
13955
|
files: 0,
|
|
13378
13956
|
bytes: 0,
|
|
13379
13957
|
valid: 0,
|
|
@@ -13387,19 +13965,19 @@ async function inspectContentCache(contentDir, referenced) {
|
|
|
13387
13965
|
return report;
|
|
13388
13966
|
}
|
|
13389
13967
|
const present = /* @__PURE__ */ new Set();
|
|
13390
|
-
const entries = await (0,
|
|
13968
|
+
const entries = await (0, import_promises14.readdir)(contentDir, { withFileTypes: true });
|
|
13391
13969
|
for (const entry of entries) {
|
|
13392
13970
|
if (!entry.isFile()) {
|
|
13393
13971
|
report.invalid.push({ name: entry.name, reason: "not a regular file" });
|
|
13394
13972
|
continue;
|
|
13395
13973
|
}
|
|
13396
13974
|
report.files++;
|
|
13397
|
-
const file =
|
|
13975
|
+
const file = import_node_path23.default.join(contentDir, entry.name);
|
|
13398
13976
|
const mediaType = mediaTypeFromExtension(entry.name);
|
|
13399
13977
|
const expected = mediaType ? entry.name.slice(0, -4) : "";
|
|
13400
13978
|
let bytes;
|
|
13401
13979
|
try {
|
|
13402
|
-
bytes = await (0,
|
|
13980
|
+
bytes = await (0, import_promises14.readFile)(file);
|
|
13403
13981
|
report.bytes += bytes.length;
|
|
13404
13982
|
} catch (err) {
|
|
13405
13983
|
report.invalid.push({
|
|
@@ -13437,7 +14015,7 @@ async function inspectContentCache(contentDir, referenced) {
|
|
|
13437
14015
|
async function inspectRemoteHashCache(remotePath) {
|
|
13438
14016
|
const report = {
|
|
13439
14017
|
path: remotePath,
|
|
13440
|
-
exists: (0,
|
|
14018
|
+
exists: (0, import_node_fs19.existsSync)(remotePath),
|
|
13441
14019
|
entries: 0,
|
|
13442
14020
|
valid: 0,
|
|
13443
14021
|
invalidIds: [],
|
|
@@ -13446,7 +14024,7 @@ async function inspectRemoteHashCache(remotePath) {
|
|
|
13446
14024
|
if (!report.exists) return report;
|
|
13447
14025
|
let cache;
|
|
13448
14026
|
try {
|
|
13449
|
-
cache = parseCache(JSON.parse(await (0,
|
|
14027
|
+
cache = parseCache(JSON.parse(await (0, import_promises14.readFile)(remotePath, "utf8")));
|
|
13450
14028
|
} catch (err) {
|
|
13451
14029
|
report.error = err instanceof Error ? err.message : String(err);
|
|
13452
14030
|
return report;
|
|
@@ -13461,20 +14039,20 @@ async function inspectRemoteHashCache(remotePath) {
|
|
|
13461
14039
|
}
|
|
13462
14040
|
|
|
13463
14041
|
// src/pipeline/pack.ts
|
|
13464
|
-
var
|
|
13465
|
-
var
|
|
14042
|
+
var import_node_fs20 = require("fs");
|
|
14043
|
+
var import_node_path24 = __toESM(require("path"), 1);
|
|
13466
14044
|
var DEFAULT_FRAME_SET_FPS = 12;
|
|
13467
14045
|
function resolvePackInputs(raw, inputsFilePath) {
|
|
13468
14046
|
if (!Array.isArray(raw) || !raw.length) {
|
|
13469
14047
|
throw new Error("--inputs must be a non-empty JSON array of { id, path }");
|
|
13470
14048
|
}
|
|
13471
|
-
const baseDir =
|
|
14049
|
+
const baseDir = import_node_path24.default.dirname(import_node_path24.default.resolve(inputsFilePath));
|
|
13472
14050
|
return raw.map((entry, i) => {
|
|
13473
14051
|
const e = entry;
|
|
13474
14052
|
if (typeof e.id !== "string" || typeof e.path !== "string") {
|
|
13475
14053
|
throw new Error(`--inputs[${i}] needs string "id" and "path"`);
|
|
13476
14054
|
}
|
|
13477
|
-
return { id: e.id, path:
|
|
14055
|
+
return { id: e.id, path: import_node_path24.default.resolve(baseDir, e.path) };
|
|
13478
14056
|
});
|
|
13479
14057
|
}
|
|
13480
14058
|
function packSprites(inputs, options = {}) {
|
|
@@ -13495,7 +14073,7 @@ function packSprites(inputs, options = {}) {
|
|
|
13495
14073
|
for (const input of ordered) {
|
|
13496
14074
|
let sourceHash = null;
|
|
13497
14075
|
try {
|
|
13498
|
-
const bytes = (0,
|
|
14076
|
+
const bytes = (0, import_node_fs20.readFileSync)(input.path);
|
|
13499
14077
|
sourceHash = sha256(bytes);
|
|
13500
14078
|
const png = decodePng(bytes);
|
|
13501
14079
|
sprites.push({ id: input.id, width: png.width, height: png.height, pixels: png.pixels });
|
|
@@ -13558,7 +14136,7 @@ function packStyle(lock, styleId, manifestDir, options = {}) {
|
|
|
13558
14136
|
}
|
|
13559
14137
|
const inputs2 = Object.entries(options.sourceOverrides).map(([id, source]) => ({
|
|
13560
14138
|
id,
|
|
13561
|
-
path:
|
|
14139
|
+
path: import_node_path24.default.resolve(source)
|
|
13562
14140
|
}));
|
|
13563
14141
|
if (!inputs2.length) throw new Error(`No approved quality outputs for style "${styleId}".`);
|
|
13564
14142
|
const packed2 = packSprites(inputs2, options);
|
|
@@ -13582,7 +14160,7 @@ function packStyle(lock, styleId, manifestDir, options = {}) {
|
|
|
13582
14160
|
let outputs = resolveEntryOutputs(entry, id, manifestDir);
|
|
13583
14161
|
const source = sources[id];
|
|
13584
14162
|
if (source && outputs.length <= 1) {
|
|
13585
|
-
inputs.push({ id, path:
|
|
14163
|
+
inputs.push({ id, path: import_node_path24.default.resolve(manifestDir, source) });
|
|
13586
14164
|
continue;
|
|
13587
14165
|
}
|
|
13588
14166
|
if (source && sourceIsStem(source, entry, manifestDir)) {
|
|
@@ -13611,12 +14189,13 @@ function packStyle(lock, styleId, manifestDir, options = {}) {
|
|
|
13611
14189
|
id,
|
|
13612
14190
|
kind: isFrames ? "frames" : "members",
|
|
13613
14191
|
...fps ? { fps } : {},
|
|
14192
|
+
...isFrames && frameSetLoop(entry) === false ? { loop: false } : {},
|
|
13614
14193
|
frames: selected.map((output) => output.id)
|
|
13615
14194
|
});
|
|
13616
14195
|
}
|
|
13617
14196
|
}
|
|
13618
14197
|
for (const [id, source] of Object.entries(sources)) {
|
|
13619
|
-
if (!locked.has(id)) inputs.push({ id, path:
|
|
14198
|
+
if (!locked.has(id)) inputs.push({ id, path: import_node_path24.default.resolve(manifestDir, source) });
|
|
13620
14199
|
}
|
|
13621
14200
|
if (!inputs.length) {
|
|
13622
14201
|
throw new Error(`No readable sprites for style "${styleId}"; ${noOutput.length} skipped.`);
|
|
@@ -13653,7 +14232,7 @@ function mountSprites(placements, options) {
|
|
|
13653
14232
|
for (const p of placements) {
|
|
13654
14233
|
let sourceHash = null;
|
|
13655
14234
|
try {
|
|
13656
|
-
const bytes = (0,
|
|
14235
|
+
const bytes = (0, import_node_fs20.readFileSync)(p.path);
|
|
13657
14236
|
sourceHash = sha256(bytes);
|
|
13658
14237
|
const png = decodePng(bytes);
|
|
13659
14238
|
if (png.width > cellWidth || png.height > cellHeight) {
|
|
@@ -13724,7 +14303,7 @@ function mountStyle(lock, styleId, manifestDir, mount, cells, sources = {}, outp
|
|
|
13724
14303
|
const source = sources[id];
|
|
13725
14304
|
const entry = lock.entries[`${styleId}/${id}`];
|
|
13726
14305
|
if (source && !(entry && sourceIsStem(source, entry, manifestDir))) {
|
|
13727
|
-
placements.push({ id, path:
|
|
14306
|
+
placements.push({ id, path: import_node_path24.default.resolve(manifestDir, source), cell });
|
|
13728
14307
|
continue;
|
|
13729
14308
|
}
|
|
13730
14309
|
if (source && entry) {
|
|
@@ -13757,7 +14336,7 @@ function mountStyle(lock, styleId, manifestDir, mount, cells, sources = {}, outp
|
|
|
13757
14336
|
`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}`
|
|
13758
14337
|
);
|
|
13759
14338
|
}
|
|
13760
|
-
const basePng = mount.base ? (0,
|
|
14339
|
+
const basePng = mount.base ? (0, import_node_fs20.readFileSync)(import_node_path24.default.resolve(manifestDir, mount.base)) : void 0;
|
|
13761
14340
|
const mounted = mountSprites(placements, {
|
|
13762
14341
|
cellWidth: mount.cellWidth,
|
|
13763
14342
|
cellHeight: mount.cellHeight,
|
|
@@ -13767,7 +14346,7 @@ function mountStyle(lock, styleId, manifestDir, mount, cells, sources = {}, outp
|
|
|
13767
14346
|
...mounted,
|
|
13768
14347
|
sources: mount.base ? [{
|
|
13769
14348
|
id: "$base",
|
|
13770
|
-
path:
|
|
14349
|
+
path: import_node_path24.default.resolve(manifestDir, mount.base),
|
|
13771
14350
|
sha256: sha256(basePng),
|
|
13772
14351
|
included: true
|
|
13773
14352
|
}, ...mounted.sources] : mounted.sources,
|
|
@@ -14149,7 +14728,7 @@ function renderGodotSpriteFrames(atlas, opts) {
|
|
|
14149
14728
|
);
|
|
14150
14729
|
};
|
|
14151
14730
|
for (const set of atlas.sets ?? []) {
|
|
14152
|
-
if (set.kind === "frames") animation(set.id, set.frames, set.fps ?? GODOT_DEFAULT_SPEED, true);
|
|
14731
|
+
if (set.kind === "frames") animation(set.id, set.frames, set.fps ?? GODOT_DEFAULT_SPEED, set.loop ?? true);
|
|
14153
14732
|
else for (const id of set.frames) animation(id, [id], GODOT_DEFAULT_SPEED, false);
|
|
14154
14733
|
}
|
|
14155
14734
|
for (const frame of atlas.frames) {
|
|
@@ -14404,6 +14983,8 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
|
|
|
14404
14983
|
const anchor = spec.character?.styleAnchor;
|
|
14405
14984
|
const anchorEntry = anchor ? lock.entries[lockKey(anchor.spec.styleId, anchor.spec.assetId)] : void 0;
|
|
14406
14985
|
const replacedMetadata = previousEntry?.providerMetadata?.[provider.id];
|
|
14986
|
+
const lastGeneration = previousEntry?.status === "downloaded" ? previousEntry : previousEntry?.history?.[0];
|
|
14987
|
+
const lastGenerationMetadata = lastGeneration?.providerMetadata?.[provider.id];
|
|
14407
14988
|
const parentOutputs = spec.character?.kind === "outfit" && parentEntry ? parentEntry.outputs.map((output, index) => ({
|
|
14408
14989
|
path: currentEntryOutputPath(parentEntry, spec.character.parentSpec, index),
|
|
14409
14990
|
role: output.role
|
|
@@ -14416,6 +14997,7 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
|
|
|
14416
14997
|
...anchorEntry?.objectId ? { styleObjectId: anchorEntry.objectId } : {},
|
|
14417
14998
|
...parentOutputs ? { parentOutputs } : {},
|
|
14418
14999
|
...replacedMetadata ? { replacedMetadata } : {},
|
|
15000
|
+
...lastGenerationMetadata ? { lastGenerationMetadata } : {},
|
|
14419
15001
|
...previousMetadata ? { previousMetadata } : {},
|
|
14420
15002
|
checkpoint: async (checkpoint) => {
|
|
14421
15003
|
if (!checkpoint.jobId) throw new Error("Provider checkpoint returned an empty job id");
|
|
@@ -14578,9 +15160,9 @@ async function poll(provider, lock, lockPath, opts = {}) {
|
|
|
14578
15160
|
}
|
|
14579
15161
|
|
|
14580
15162
|
// src/pipeline/doctor.ts
|
|
14581
|
-
var
|
|
14582
|
-
var
|
|
14583
|
-
var
|
|
15163
|
+
var import_node_fs21 = require("fs");
|
|
15164
|
+
var import_promises15 = require("fs/promises");
|
|
15165
|
+
var import_node_path25 = __toESM(require("path"), 1);
|
|
14584
15166
|
async function doctor(loaded, specs, lock, lockPath, opts = {}) {
|
|
14585
15167
|
const checks = [];
|
|
14586
15168
|
const add = (id, level, message6) => checks.push({ id, level, message: message6 });
|
|
@@ -14591,14 +15173,14 @@ async function doctor(loaded, specs, lock, lockPath, opts = {}) {
|
|
|
14591
15173
|
);
|
|
14592
15174
|
add("lock", "ok", `${Object.keys(lock.entries).length} valid v2 lock entr(ies)`);
|
|
14593
15175
|
const dirs = [...new Set(specs.flatMap((spec) => [
|
|
14594
|
-
|
|
14595
|
-
...spec.quality ? [
|
|
15176
|
+
import_node_path25.default.dirname(spec.outFile),
|
|
15177
|
+
...spec.quality ? [import_node_path25.default.dirname(spec.quality.outFile)] : []
|
|
14596
15178
|
]))];
|
|
14597
15179
|
const unwritable = [];
|
|
14598
15180
|
for (const dir of dirs) {
|
|
14599
15181
|
const ancestor = await nearestExistingDirectory(dir);
|
|
14600
15182
|
try {
|
|
14601
|
-
await (0,
|
|
15183
|
+
await (0, import_promises15.access)(ancestor, import_node_fs21.constants.W_OK);
|
|
14602
15184
|
} catch {
|
|
14603
15185
|
unwritable.push(dir);
|
|
14604
15186
|
}
|
|
@@ -14615,7 +15197,7 @@ async function doctor(loaded, specs, lock, lockPath, opts = {}) {
|
|
|
14615
15197
|
for (let index = 0; index < entry.outputs.length; index++) {
|
|
14616
15198
|
const output = entry.outputs[index];
|
|
14617
15199
|
const spec = specByKey.get(key);
|
|
14618
|
-
const absolute = spec ? currentEntryOutputPath(entry, spec, index) : resolveOutputPath(output.path,
|
|
15200
|
+
const absolute = spec ? currentEntryOutputPath(entry, spec, index) : resolveOutputPath(output.path, import_node_path25.default.dirname(lockPath));
|
|
14619
15201
|
const owner = outputOwners.get(absolute);
|
|
14620
15202
|
if (owner && owner !== key) duplicateOutputs.push(`${output.path} (${owner}, ${key})`);
|
|
14621
15203
|
outputOwners.set(absolute, key);
|
|
@@ -14630,7 +15212,7 @@ async function doctor(loaded, specs, lock, lockPath, opts = {}) {
|
|
|
14630
15212
|
} else {
|
|
14631
15213
|
add("lock-outputs", "ok", `${outputOwners.size} recorded output path(s) are uniquely owned`);
|
|
14632
15214
|
}
|
|
14633
|
-
const cacheDir =
|
|
15215
|
+
const cacheDir = import_node_path25.default.join(import_node_path25.default.dirname(lockPath), ".pixelkiln", "cache");
|
|
14634
15216
|
const stranded = [];
|
|
14635
15217
|
const invalidState = [];
|
|
14636
15218
|
const staleMapUrls = [];
|
|
@@ -14646,11 +15228,11 @@ async function doctor(loaded, specs, lock, lockPath, opts = {}) {
|
|
|
14646
15228
|
const hasSource = Boolean(entry.sourceUrl || entry.sourceUrls?.length);
|
|
14647
15229
|
let hasCache = false;
|
|
14648
15230
|
for (const output of entry.outputs) {
|
|
14649
|
-
const cached =
|
|
15231
|
+
const cached = import_node_path25.default.join(
|
|
14650
15232
|
cacheDir,
|
|
14651
15233
|
cacheFileName(output.sha256, output.mediaType ?? MediaType.PNG)
|
|
14652
15234
|
);
|
|
14653
|
-
if ((0,
|
|
15235
|
+
if ((0, import_node_fs21.existsSync)(cached) && await sha256File(cached) === output.sha256) {
|
|
14654
15236
|
hasCache = true;
|
|
14655
15237
|
break;
|
|
14656
15238
|
}
|
|
@@ -14743,19 +15325,19 @@ async function nearestExistingDirectory(start) {
|
|
|
14743
15325
|
let current = start;
|
|
14744
15326
|
for (; ; ) {
|
|
14745
15327
|
try {
|
|
14746
|
-
if ((await (0,
|
|
15328
|
+
if ((await (0, import_promises15.stat)(current)).isDirectory()) return current;
|
|
14747
15329
|
} catch {
|
|
14748
15330
|
}
|
|
14749
|
-
const parent =
|
|
15331
|
+
const parent = import_node_path25.default.dirname(current);
|
|
14750
15332
|
if (parent === current) return current;
|
|
14751
15333
|
current = parent;
|
|
14752
15334
|
}
|
|
14753
15335
|
}
|
|
14754
15336
|
|
|
14755
15337
|
// src/pipeline/adopt.ts
|
|
14756
|
-
var
|
|
14757
|
-
var
|
|
14758
|
-
var
|
|
15338
|
+
var import_node_fs22 = require("fs");
|
|
15339
|
+
var import_promises16 = require("fs/promises");
|
|
15340
|
+
var import_node_path26 = __toESM(require("path"), 1);
|
|
14759
15341
|
async function adopt(provider, specs, lock, lockPath, opts = {}) {
|
|
14760
15342
|
const log = opts.onProgress ?? (() => {
|
|
14761
15343
|
});
|
|
@@ -14766,11 +15348,11 @@ async function adopt(provider, specs, lock, lockPath, opts = {}) {
|
|
|
14766
15348
|
const localByHash = /* @__PURE__ */ new Map();
|
|
14767
15349
|
const unmatchedLocal = [];
|
|
14768
15350
|
for (const spec of specs) {
|
|
14769
|
-
if (!(0,
|
|
15351
|
+
if (!(0, import_node_fs22.existsSync)(spec.outFile)) {
|
|
14770
15352
|
unmatchedLocal.push(`${spec.styleId}/${spec.assetId} (no file at ${spec.outFile})`);
|
|
14771
15353
|
continue;
|
|
14772
15354
|
}
|
|
14773
|
-
const bytes = await (0,
|
|
15355
|
+
const bytes = await (0, import_promises16.readFile)(spec.outFile);
|
|
14774
15356
|
try {
|
|
14775
15357
|
decodePng(bytes);
|
|
14776
15358
|
} catch (err) {
|
|
@@ -14888,13 +15470,13 @@ async function tagAdopted(provider, specs, lock, opts = {}) {
|
|
|
14888
15470
|
}
|
|
14889
15471
|
|
|
14890
15472
|
// src/pick/server.ts
|
|
14891
|
-
var
|
|
14892
|
-
var
|
|
15473
|
+
var import_node_fs24 = require("fs");
|
|
15474
|
+
var import_promises19 = require("fs/promises");
|
|
14893
15475
|
|
|
14894
15476
|
// src/pipeline/init.ts
|
|
14895
|
-
var
|
|
14896
|
-
var
|
|
14897
|
-
var
|
|
15477
|
+
var import_promises17 = require("fs/promises");
|
|
15478
|
+
var import_node_fs23 = require("fs");
|
|
15479
|
+
var import_node_path27 = __toESM(require("path"), 1);
|
|
14898
15480
|
function pngSize(buf) {
|
|
14899
15481
|
if (buf.length < 24) return null;
|
|
14900
15482
|
const isPng = buf.readUInt32BE(0) === 2303741511 && buf.readUInt32BE(4) === 218765834;
|
|
@@ -14907,8 +15489,8 @@ function slugify(value) {
|
|
|
14907
15489
|
}
|
|
14908
15490
|
async function walk(dir, exclude) {
|
|
14909
15491
|
const out = [];
|
|
14910
|
-
for (const entry of await (0,
|
|
14911
|
-
const full =
|
|
15492
|
+
for (const entry of await (0, import_promises17.readdir)(dir, { withFileTypes: true })) {
|
|
15493
|
+
const full = import_node_path27.default.join(dir, entry.name);
|
|
14912
15494
|
if (exclude.some((x) => full.includes(x))) continue;
|
|
14913
15495
|
if (entry.isDirectory()) out.push(...await walk(full, exclude));
|
|
14914
15496
|
else if (entry.name.toLowerCase().endsWith(".png")) out.push(full);
|
|
@@ -14922,14 +15504,14 @@ async function scanAssets(root, opts = {}) {
|
|
|
14922
15504
|
const skipped = [];
|
|
14923
15505
|
const seen = /* @__PURE__ */ new Map();
|
|
14924
15506
|
for (const file of files.sort()) {
|
|
14925
|
-
const size = pngSize(await (0,
|
|
15507
|
+
const size = pngSize(await (0, import_promises17.readFile)(file));
|
|
14926
15508
|
if (!size) {
|
|
14927
15509
|
skipped.push(`${file} (not a readable PNG)`);
|
|
14928
15510
|
continue;
|
|
14929
15511
|
}
|
|
14930
|
-
const rel =
|
|
14931
|
-
const category =
|
|
14932
|
-
let id = slugify(
|
|
15512
|
+
const rel = import_node_path27.default.relative(root, file);
|
|
15513
|
+
const category = import_node_path27.default.dirname(rel) === "." ? "" : import_node_path27.default.dirname(rel);
|
|
15514
|
+
let id = slugify(import_node_path27.default.basename(rel));
|
|
14933
15515
|
if (seen.has(id)) {
|
|
14934
15516
|
const n = seen.get(id) + 1;
|
|
14935
15517
|
seen.set(id, n);
|
|
@@ -15393,7 +15975,7 @@ document.getElementById('submit').onclick = async () => {
|
|
|
15393
15975
|
|
|
15394
15976
|
// src/pick/review-server.ts
|
|
15395
15977
|
var import_node_http = require("http");
|
|
15396
|
-
var
|
|
15978
|
+
var import_promises18 = require("fs/promises");
|
|
15397
15979
|
function serveReviewPage(opts) {
|
|
15398
15980
|
const log = opts.onProgress ?? (() => {
|
|
15399
15981
|
});
|
|
@@ -15402,7 +15984,7 @@ function serveReviewPage(opts) {
|
|
|
15402
15984
|
if (req.method === "GET" && req.url && opts.assets?.has(req.url)) {
|
|
15403
15985
|
try {
|
|
15404
15986
|
const asset = opts.assets.get(req.url);
|
|
15405
|
-
const bytes = await (0,
|
|
15987
|
+
const bytes = await (0, import_promises18.readFile)(asset.path);
|
|
15406
15988
|
res.writeHead(200, {
|
|
15407
15989
|
"Content-Type": asset.contentType,
|
|
15408
15990
|
"Cache-Control": "no-store",
|
|
@@ -15478,13 +16060,13 @@ async function currentArt(entry, spec) {
|
|
|
15478
16060
|
const previous = primaryOutput({ ...entry, outputs: entry.supersededOutputs });
|
|
15479
16061
|
if (!previous) return null;
|
|
15480
16062
|
const file = resolveOutputPath(previous.path, spec.root);
|
|
15481
|
-
if (!(0,
|
|
16063
|
+
if (!(0, import_node_fs24.existsSync)(file)) return null;
|
|
15482
16064
|
const contentType = previous.mediaType ?? mediaTypeFromExtension(file);
|
|
15483
16065
|
if (!contentType) return null;
|
|
15484
16066
|
let width = entry.width;
|
|
15485
16067
|
let height = entry.height;
|
|
15486
16068
|
if (contentType === "image/png") {
|
|
15487
|
-
const size = pngSize(await (0,
|
|
16069
|
+
const size = pngSize(await (0, import_promises19.readFile)(file));
|
|
15488
16070
|
if (size) ({ width, height } = size);
|
|
15489
16071
|
}
|
|
15490
16072
|
return { path: file, contentType, width, height };
|
|
@@ -15492,7 +16074,7 @@ async function currentArt(entry, spec) {
|
|
|
15492
16074
|
async function cachedFrame(url) {
|
|
15493
16075
|
if (!url.startsWith("file://")) return null;
|
|
15494
16076
|
const file = url.slice("file://".length);
|
|
15495
|
-
const contentType = mediaTypeFromExtension(file) ?? detectMediaType(await (0,
|
|
16077
|
+
const contentType = mediaTypeFromExtension(file) ?? detectMediaType(await (0, import_promises19.readFile)(file).catch(() => Buffer.alloc(0))) ?? "application/octet-stream";
|
|
15496
16078
|
return { path: file, contentType };
|
|
15497
16079
|
}
|
|
15498
16080
|
async function prepareReview(provider, lock, opts = {}) {
|
|
@@ -15674,14 +16256,14 @@ async function runPicker(provider, lock, lockPath, opts = {}) {
|
|
|
15674
16256
|
|
|
15675
16257
|
// src/gallery/snapshot.ts
|
|
15676
16258
|
var import_node_crypto7 = require("crypto");
|
|
15677
|
-
var
|
|
15678
|
-
var
|
|
15679
|
-
var
|
|
16259
|
+
var import_node_fs30 = require("fs");
|
|
16260
|
+
var import_promises27 = require("fs/promises");
|
|
16261
|
+
var import_node_path35 = __toESM(require("path"), 1);
|
|
15680
16262
|
|
|
15681
16263
|
// src/workspace.ts
|
|
15682
|
-
var
|
|
15683
|
-
var
|
|
15684
|
-
var
|
|
16264
|
+
var import_promises20 = require("fs/promises");
|
|
16265
|
+
var import_node_fs25 = require("fs");
|
|
16266
|
+
var import_node_path28 = __toESM(require("path"), 1);
|
|
15685
16267
|
var import_zod7 = require("zod");
|
|
15686
16268
|
var WorkspaceProjectSchema = import_zod7.z.object({
|
|
15687
16269
|
id: import_zod7.z.string().min(1),
|
|
@@ -15706,10 +16288,10 @@ ${parsed.error.issues.slice(0, 5).map((i) => ` ${i.path.join(".")}: ${i.message
|
|
|
15706
16288
|
);
|
|
15707
16289
|
}
|
|
15708
16290
|
async function loadWorkspace(workspacePath) {
|
|
15709
|
-
if (!(0,
|
|
16291
|
+
if (!(0, import_node_fs25.existsSync)(workspacePath)) return { version: 1, projects: [] };
|
|
15710
16292
|
let raw;
|
|
15711
16293
|
try {
|
|
15712
|
-
raw = JSON.parse(await (0,
|
|
16294
|
+
raw = JSON.parse(await (0, import_promises20.readFile)(workspacePath, "utf8"));
|
|
15713
16295
|
} catch (err) {
|
|
15714
16296
|
throw new ProjectError(
|
|
15715
16297
|
`Workspace catalog at ${workspacePath} is malformed:
|
|
@@ -15723,22 +16305,22 @@ async function saveWorkspace(workspacePath, ws) {
|
|
|
15723
16305
|
version: 1,
|
|
15724
16306
|
projects: [...ws.projects].sort((a, b) => a.id.localeCompare(b.id))
|
|
15725
16307
|
};
|
|
15726
|
-
await (0,
|
|
16308
|
+
await (0, import_promises20.mkdir)(import_node_path28.default.dirname(import_node_path28.default.resolve(workspacePath)), { recursive: true });
|
|
15727
16309
|
const tmp = `${workspacePath}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
15728
16310
|
try {
|
|
15729
|
-
await (0,
|
|
15730
|
-
await (0,
|
|
16311
|
+
await (0, import_promises20.writeFile)(tmp, JSON.stringify(sorted, null, 2) + "\n");
|
|
16312
|
+
await (0, import_promises20.rename)(tmp, workspacePath);
|
|
15731
16313
|
} finally {
|
|
15732
|
-
await (0,
|
|
16314
|
+
await (0, import_promises20.rm)(tmp, { force: true });
|
|
15733
16315
|
}
|
|
15734
16316
|
}
|
|
15735
16317
|
function toPortablePath(dir, absolute) {
|
|
15736
|
-
return
|
|
16318
|
+
return import_node_path28.default.relative(dir, absolute).split(import_node_path28.default.sep).join("/");
|
|
15737
16319
|
}
|
|
15738
16320
|
function resolveProject(dir, project) {
|
|
15739
16321
|
return {
|
|
15740
|
-
manifestPath:
|
|
15741
|
-
lockPath:
|
|
16322
|
+
manifestPath: import_node_path28.default.resolve(dir, project.manifest.split("/").join(import_node_path28.default.sep)),
|
|
16323
|
+
lockPath: import_node_path28.default.resolve(dir, project.lock.split("/").join(import_node_path28.default.sep))
|
|
15742
16324
|
};
|
|
15743
16325
|
}
|
|
15744
16326
|
function validateWorkspace(ws, dir) {
|
|
@@ -15751,21 +16333,21 @@ function validateWorkspace(ws, dir) {
|
|
|
15751
16333
|
const { manifestPath, lockPath } = resolveProject(dir, project);
|
|
15752
16334
|
lockOwners.set(lockPath, [...lockOwners.get(lockPath) ?? [], project.id]);
|
|
15753
16335
|
manifestOwners.set(manifestPath, [...manifestOwners.get(manifestPath) ?? [], project.id]);
|
|
15754
|
-
if (
|
|
16336
|
+
if (import_node_path28.default.isAbsolute(project.manifest) || import_node_path28.default.isAbsolute(project.lock)) {
|
|
15755
16337
|
diagnostics.push({
|
|
15756
16338
|
id: "absolute-path",
|
|
15757
16339
|
level: "warning",
|
|
15758
16340
|
message: `project "${project.id}" stores an absolute path; the catalog will not resolve correctly if this tree is cloned or moved elsewhere`
|
|
15759
16341
|
});
|
|
15760
16342
|
}
|
|
15761
|
-
if (!(0,
|
|
16343
|
+
if (!(0, import_node_fs25.existsSync)(manifestPath)) {
|
|
15762
16344
|
diagnostics.push({
|
|
15763
16345
|
id: "missing-manifest",
|
|
15764
16346
|
level: "error",
|
|
15765
16347
|
message: `project "${project.id}" manifest not found: ${manifestPath}`
|
|
15766
16348
|
});
|
|
15767
16349
|
}
|
|
15768
|
-
if (!(0,
|
|
16350
|
+
if (!(0, import_node_fs25.existsSync)(lockPath)) {
|
|
15769
16351
|
diagnostics.push({
|
|
15770
16352
|
id: "missing-lock",
|
|
15771
16353
|
level: "error",
|
|
@@ -15812,16 +16394,16 @@ function validateWorkspace(ws, dir) {
|
|
|
15812
16394
|
}
|
|
15813
16395
|
|
|
15814
16396
|
// src/gallery/edit.ts
|
|
15815
|
-
var
|
|
15816
|
-
var
|
|
16397
|
+
var import_promises26 = require("fs/promises");
|
|
16398
|
+
var import_node_path34 = __toESM(require("path"), 1);
|
|
15817
16399
|
var import_zod13 = require("zod");
|
|
15818
16400
|
|
|
15819
16401
|
// src/manifest-edit.ts
|
|
15820
|
-
var
|
|
15821
|
-
var
|
|
16402
|
+
var import_promises21 = require("fs/promises");
|
|
16403
|
+
var import_node_path29 = __toESM(require("path"), 1);
|
|
15822
16404
|
var import_zod8 = require("zod");
|
|
15823
16405
|
var HexSha = import_zod8.z.string().regex(/^[0-9a-f]{64}$/);
|
|
15824
|
-
var ProjectFileSchema = import_zod8.z.string().min(1).refine((file) => !
|
|
16406
|
+
var ProjectFileSchema = import_zod8.z.string().min(1).refine((file) => !import_node_path29.default.isAbsolute(file) && !file.split(/[\\/]/).includes(".."), "must be a path inside the project");
|
|
15825
16407
|
var AssetPatchSchema = import_zod8.z.object({
|
|
15826
16408
|
prompt: import_zod8.z.string().optional(),
|
|
15827
16409
|
/** Per-style override for the style being viewed; null removes it. */
|
|
@@ -16240,8 +16822,8 @@ function applyEdit(raw, edit) {
|
|
|
16240
16822
|
}
|
|
16241
16823
|
}
|
|
16242
16824
|
async function withCandidate(manifestPath, edit, use, unchanged) {
|
|
16243
|
-
const absolute =
|
|
16244
|
-
const original = await (0,
|
|
16825
|
+
const absolute = import_node_path29.default.resolve(manifestPath);
|
|
16826
|
+
const original = await (0, import_promises21.readFile)(absolute, "utf8");
|
|
16245
16827
|
if (sha256(original) !== edit.expectedSha256) throw new ManifestDriftError(absolute);
|
|
16246
16828
|
let raw;
|
|
16247
16829
|
try {
|
|
@@ -16254,12 +16836,12 @@ async function withCandidate(manifestPath, edit, use, unchanged) {
|
|
|
16254
16836
|
applyEdit(raw, edit);
|
|
16255
16837
|
const next = serializeLike(original, raw);
|
|
16256
16838
|
if (next === original) return unchanged(absolute, original);
|
|
16257
|
-
const tmp =
|
|
16258
|
-
|
|
16259
|
-
`.${
|
|
16839
|
+
const tmp = import_node_path29.default.join(
|
|
16840
|
+
import_node_path29.default.dirname(absolute),
|
|
16841
|
+
`.${import_node_path29.default.basename(absolute)}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`
|
|
16260
16842
|
);
|
|
16261
16843
|
try {
|
|
16262
|
-
await (0,
|
|
16844
|
+
await (0, import_promises21.writeFile)(tmp, next);
|
|
16263
16845
|
let specs;
|
|
16264
16846
|
try {
|
|
16265
16847
|
specs = await resolveSpecs(await loadManifest(tmp));
|
|
@@ -16269,7 +16851,7 @@ async function withCandidate(manifestPath, edit, use, unchanged) {
|
|
|
16269
16851
|
}
|
|
16270
16852
|
return await use({ absolute, original, next, tmp, specs });
|
|
16271
16853
|
} finally {
|
|
16272
|
-
await (0,
|
|
16854
|
+
await (0, import_promises21.rm)(tmp, { force: true });
|
|
16273
16855
|
}
|
|
16274
16856
|
}
|
|
16275
16857
|
async function applyManifestEdit(manifestPath, edit) {
|
|
@@ -16277,7 +16859,7 @@ async function applyManifestEdit(manifestPath, edit) {
|
|
|
16277
16859
|
manifestPath,
|
|
16278
16860
|
edit,
|
|
16279
16861
|
async ({ absolute, next, tmp }) => {
|
|
16280
|
-
await (0,
|
|
16862
|
+
await (0, import_promises21.rename)(tmp, absolute);
|
|
16281
16863
|
return { manifestPath: absolute, sha256: sha256(next), changed: true };
|
|
16282
16864
|
},
|
|
16283
16865
|
(absolute, original) => ({ manifestPath: absolute, sha256: sha256(original), changed: false })
|
|
@@ -16285,18 +16867,18 @@ async function applyManifestEdit(manifestPath, edit) {
|
|
|
16285
16867
|
}
|
|
16286
16868
|
|
|
16287
16869
|
// src/pipeline/hand-edit.ts
|
|
16288
|
-
var
|
|
16289
|
-
var
|
|
16290
|
-
var
|
|
16870
|
+
var import_node_fs26 = require("fs");
|
|
16871
|
+
var import_promises22 = require("fs/promises");
|
|
16872
|
+
var import_node_path30 = __toESM(require("path"), 1);
|
|
16291
16873
|
var import_zod9 = require("zod");
|
|
16292
16874
|
var HAND_EDIT_DIR = "edits";
|
|
16293
16875
|
function handEditPath(loaded, spec) {
|
|
16294
16876
|
const style = loaded.manifest.styles[spec.styleId];
|
|
16295
|
-
const outDir =
|
|
16296
|
-
const relative =
|
|
16297
|
-
const inside = relative && !relative.startsWith("..") && !
|
|
16298
|
-
const file = inside ? relative :
|
|
16299
|
-
return
|
|
16877
|
+
const outDir = import_node_path30.default.resolve(loaded.root, style?.outDir ?? ".");
|
|
16878
|
+
const relative = import_node_path30.default.relative(outDir, spec.outFile);
|
|
16879
|
+
const inside = relative && !relative.startsWith("..") && !import_node_path30.default.isAbsolute(relative);
|
|
16880
|
+
const file = inside ? relative : import_node_path30.default.basename(spec.outFile);
|
|
16881
|
+
return import_node_path30.default.join(outDir, HAND_EDIT_DIR, file.replace(/\.gif$/i, ".png"));
|
|
16300
16882
|
}
|
|
16301
16883
|
function handEditBases(spec, lock) {
|
|
16302
16884
|
const entry = lock.entries[lockKey(spec.styleId, spec.assetId)];
|
|
@@ -16309,10 +16891,10 @@ function handEditBases(spec, lock) {
|
|
|
16309
16891
|
}));
|
|
16310
16892
|
if (members.some((member) => member.mediaType && member.mediaType !== "image/png")) return null;
|
|
16311
16893
|
if (entry.outputs.length === 1 && !primaryOutput(entry)) return null;
|
|
16312
|
-
if (!members.every((member) => (0,
|
|
16894
|
+
if (!members.every((member) => (0, import_node_fs26.existsSync)(member.path))) return null;
|
|
16313
16895
|
return members.map(({ role, path: file }) => ({ role, path: file }));
|
|
16314
16896
|
}
|
|
16315
|
-
return (0,
|
|
16897
|
+
return (0, import_node_fs26.existsSync)(spec.outFile) ? [{ role: null, path: spec.outFile }] : null;
|
|
16316
16898
|
}
|
|
16317
16899
|
function handEditBase(spec, lock) {
|
|
16318
16900
|
const bases = handEditBases(spec, lock);
|
|
@@ -16342,19 +16924,19 @@ async function startHandEdit(loaded, lock, spec, opts = {}) {
|
|
|
16342
16924
|
`${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`
|
|
16343
16925
|
);
|
|
16344
16926
|
}
|
|
16345
|
-
const existing = spec.source ?
|
|
16927
|
+
const existing = spec.source ? import_node_path30.default.resolve(loaded.root, spec.source) : null;
|
|
16346
16928
|
const editPath = existing ?? handEditPath(loaded, spec);
|
|
16347
16929
|
const members = handEditMembers(loaded, spec, lock, editPath);
|
|
16348
16930
|
let created = false;
|
|
16349
16931
|
for (const member of members) {
|
|
16350
|
-
if ((0,
|
|
16932
|
+
if ((0, import_node_fs26.existsSync)(member.path)) continue;
|
|
16351
16933
|
if (!member.base) {
|
|
16352
16934
|
throw new ManifestEditError(
|
|
16353
16935
|
`${spec.styleId}/${spec.assetId} has no generated PNG on disk to start from; generate or restore it first`
|
|
16354
16936
|
);
|
|
16355
16937
|
}
|
|
16356
|
-
await (0,
|
|
16357
|
-
await (0,
|
|
16938
|
+
await (0, import_promises22.mkdir)(import_node_path30.default.dirname(member.path), { recursive: true });
|
|
16939
|
+
await (0, import_promises22.copyFile)(member.base, member.path);
|
|
16358
16940
|
created = true;
|
|
16359
16941
|
}
|
|
16360
16942
|
const source = portableOutputPath(editPath, loaded.root);
|
|
@@ -16432,7 +17014,7 @@ function handEditProjectPath(editPath) {
|
|
|
16432
17014
|
async function readHandEditCompanion(editPath) {
|
|
16433
17015
|
let text;
|
|
16434
17016
|
try {
|
|
16435
|
-
text = await (0,
|
|
17017
|
+
text = await (0, import_promises22.readFile)(handEditCompanionPath(editPath), "utf8");
|
|
16436
17018
|
} catch {
|
|
16437
17019
|
return null;
|
|
16438
17020
|
}
|
|
@@ -16475,7 +17057,7 @@ async function saveHandEdit(loaded, lock, spec, input) {
|
|
|
16475
17057
|
}
|
|
16476
17058
|
});
|
|
16477
17059
|
const bases = handEditBases(spec, lock);
|
|
16478
|
-
const expectedSize = bases?.[0] ? decodePng(await (0,
|
|
17060
|
+
const expectedSize = bases?.[0] ? decodePng(await (0, import_promises22.readFile)(bases[0].path)) : { width: spec.width, height: spec.height };
|
|
16479
17061
|
for (const [index, image] of decoded.entries()) {
|
|
16480
17062
|
if (image.width !== expectedSize.width || image.height !== expectedSize.height) {
|
|
16481
17063
|
const which = memberSet ? `member ${incoming[index].role ?? index}` : "the edit";
|
|
@@ -16491,7 +17073,7 @@ async function saveHandEdit(loaded, lock, spec, input) {
|
|
|
16491
17073
|
editor: input.editor,
|
|
16492
17074
|
protocol: input.protocol,
|
|
16493
17075
|
savedAt: (input.now ?? /* @__PURE__ */ new Date()).toISOString(),
|
|
16494
|
-
project: projectPath ?
|
|
17076
|
+
project: projectPath ? import_node_path30.default.basename(projectPath) : null,
|
|
16495
17077
|
outputs: incoming.map((frame, index) => ({
|
|
16496
17078
|
role: frame.role,
|
|
16497
17079
|
basedOn: basedOnHash(entry, index),
|
|
@@ -16521,10 +17103,10 @@ function basedOnHash(entry, index) {
|
|
|
16521
17103
|
async function replaceFile(file, bytes) {
|
|
16522
17104
|
const tmp = `${file}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
16523
17105
|
try {
|
|
16524
|
-
await (0,
|
|
16525
|
-
await (0,
|
|
17106
|
+
await (0, import_promises22.writeFile)(tmp, bytes);
|
|
17107
|
+
await (0, import_promises22.rename)(tmp, file);
|
|
16526
17108
|
} finally {
|
|
16527
|
-
await (0,
|
|
17109
|
+
await (0, import_promises22.rm)(tmp, { force: true });
|
|
16528
17110
|
}
|
|
16529
17111
|
}
|
|
16530
17112
|
function openInEditor(file, editor = process.env.PIXELKILN_EDITOR) {
|
|
@@ -16533,9 +17115,9 @@ function openInEditor(file, editor = process.env.PIXELKILN_EDITOR) {
|
|
|
16533
17115
|
}
|
|
16534
17116
|
|
|
16535
17117
|
// src/gallery/upload.ts
|
|
16536
|
-
var
|
|
16537
|
-
var
|
|
16538
|
-
var
|
|
17118
|
+
var import_node_fs27 = require("fs");
|
|
17119
|
+
var import_promises23 = require("fs/promises");
|
|
17120
|
+
var import_node_path31 = __toESM(require("path"), 1);
|
|
16539
17121
|
var import_zod10 = require("zod");
|
|
16540
17122
|
var MAX_UPLOAD_BYTES = 8 * 1024 * 1024;
|
|
16541
17123
|
var UploadImageSchema = import_zod10.z.object({
|
|
@@ -16548,12 +17130,12 @@ var UploadImageSchema = import_zod10.z.object({
|
|
|
16548
17130
|
overwrite: import_zod10.z.boolean().optional()
|
|
16549
17131
|
}).strict();
|
|
16550
17132
|
async function saveProjectImage(root, request) {
|
|
16551
|
-
const file =
|
|
16552
|
-
const relative =
|
|
16553
|
-
if (relative.startsWith("..") ||
|
|
17133
|
+
const file = import_node_path31.default.resolve(root, request.path);
|
|
17134
|
+
const relative = import_node_path31.default.relative(root, file);
|
|
17135
|
+
if (relative.startsWith("..") || import_node_path31.default.isAbsolute(relative)) {
|
|
16554
17136
|
throw new ManifestEditError("an upload must stay inside the project");
|
|
16555
17137
|
}
|
|
16556
|
-
const extension =
|
|
17138
|
+
const extension = import_node_path31.default.extname(file).toLowerCase();
|
|
16557
17139
|
const expected = extension === ".png" ? "png" : extension === ".jpg" || extension === ".jpeg" ? "jpeg" : null;
|
|
16558
17140
|
if (!expected) throw new ManifestEditError("an upload must be a .png, .jpg, or .jpeg path");
|
|
16559
17141
|
const bytes = Buffer.from(request.base64, "base64");
|
|
@@ -16565,18 +17147,18 @@ async function saveProjectImage(root, request) {
|
|
|
16565
17147
|
if (metadata.format !== expected) {
|
|
16566
17148
|
throw new ManifestEditError(`the upload is a ${metadata.format.toUpperCase()}; name it ${metadata.format === "png" ? ".png" : ".jpg"}`);
|
|
16567
17149
|
}
|
|
16568
|
-
if ((0,
|
|
16569
|
-
throw new ManifestEditError(`${relative.split(
|
|
17150
|
+
if ((0, import_node_fs27.existsSync)(file) && !request.overwrite) {
|
|
17151
|
+
throw new ManifestEditError(`${relative.split(import_node_path31.default.sep).join("/")} already exists; choose another name, or replace it`);
|
|
16570
17152
|
}
|
|
16571
|
-
await (0,
|
|
16572
|
-
await (0,
|
|
16573
|
-
return relative.split(
|
|
17153
|
+
await (0, import_promises23.mkdir)(import_node_path31.default.dirname(file), { recursive: true });
|
|
17154
|
+
await (0, import_promises23.writeFile)(file, bytes);
|
|
17155
|
+
return relative.split(import_node_path31.default.sep).join("/");
|
|
16574
17156
|
}
|
|
16575
17157
|
|
|
16576
17158
|
// src/gallery/image-revision.ts
|
|
16577
|
-
var
|
|
16578
|
-
var
|
|
16579
|
-
var
|
|
17159
|
+
var import_node_fs28 = require("fs");
|
|
17160
|
+
var import_promises24 = require("fs/promises");
|
|
17161
|
+
var import_node_path32 = __toESM(require("path"), 1);
|
|
16580
17162
|
var import_zod11 = require("zod");
|
|
16581
17163
|
var InpaintRevisionRequestSchema = import_zod11.z.object({
|
|
16582
17164
|
action: import_zod11.z.literal("create-inpaint-revision"),
|
|
@@ -16595,15 +17177,15 @@ var InpaintRevisionRequestSchema = import_zod11.z.object({
|
|
|
16595
17177
|
overwrite: import_zod11.z.boolean().optional()
|
|
16596
17178
|
}).strict();
|
|
16597
17179
|
async function createInpaintRevision(manifestPath, request) {
|
|
16598
|
-
const root =
|
|
16599
|
-
if (
|
|
17180
|
+
const root = import_node_path32.default.dirname(import_node_path32.default.resolve(manifestPath));
|
|
17181
|
+
if (import_node_path32.default.extname(request.maskPath).toLowerCase() !== ".png") {
|
|
16600
17182
|
throw new ManifestEditError("the mask must be a .png path inside the project");
|
|
16601
17183
|
}
|
|
16602
17184
|
const bytes = Buffer.from(request.maskBase64, "base64");
|
|
16603
17185
|
const metadata = imageMetadata(bytes);
|
|
16604
17186
|
if (!metadata || metadata.format !== "png") throw new ManifestEditError("the mask is not a readable PNG");
|
|
16605
|
-
const file =
|
|
16606
|
-
const previous = (0,
|
|
17187
|
+
const file = import_node_path32.default.resolve(root, request.maskPath);
|
|
17188
|
+
const previous = (0, import_node_fs28.existsSync)(file) ? await (0, import_promises24.readFile)(file) : null;
|
|
16607
17189
|
const mask = await saveProjectImage(root, {
|
|
16608
17190
|
action: "upload-image",
|
|
16609
17191
|
path: request.maskPath,
|
|
@@ -16622,17 +17204,17 @@ async function createInpaintRevision(manifestPath, request) {
|
|
|
16622
17204
|
}
|
|
16623
17205
|
});
|
|
16624
17206
|
} catch (error) {
|
|
16625
|
-
if (previous) await (0,
|
|
16626
|
-
else await (0,
|
|
17207
|
+
if (previous) await (0, import_promises24.writeFile)(file, previous);
|
|
17208
|
+
else await (0, import_promises24.rm)(file, { force: true });
|
|
16627
17209
|
throw error;
|
|
16628
17210
|
}
|
|
16629
17211
|
return mask;
|
|
16630
17212
|
}
|
|
16631
17213
|
|
|
16632
17214
|
// src/gallery/skeleton.ts
|
|
16633
|
-
var
|
|
16634
|
-
var
|
|
16635
|
-
var
|
|
17215
|
+
var import_node_fs29 = require("fs");
|
|
17216
|
+
var import_promises25 = require("fs/promises");
|
|
17217
|
+
var import_node_path33 = __toESM(require("path"), 1);
|
|
16636
17218
|
var import_zod12 = require("zod");
|
|
16637
17219
|
var SkeletonEstimateRequestSchema = import_zod12.z.object({
|
|
16638
17220
|
project: import_zod12.z.string().min(1).optional(),
|
|
@@ -16670,21 +17252,21 @@ var SkeletonKeypointsUpdateSchema = import_zod12.z.object({
|
|
|
16670
17252
|
set: SkeletonSetSchema
|
|
16671
17253
|
}).strict();
|
|
16672
17254
|
async function createSkeletonAnimation(manifestPath, request) {
|
|
16673
|
-
const root =
|
|
16674
|
-
const file =
|
|
16675
|
-
const relative =
|
|
16676
|
-
if (!request.keypointsFile.endsWith(".json") || relative.startsWith("..") ||
|
|
17255
|
+
const root = import_node_path33.default.dirname(import_node_path33.default.resolve(manifestPath));
|
|
17256
|
+
const file = import_node_path33.default.resolve(root, request.keypointsFile);
|
|
17257
|
+
const relative = import_node_path33.default.relative(root, file);
|
|
17258
|
+
if (!request.keypointsFile.endsWith(".json") || relative.startsWith("..") || import_node_path33.default.isAbsolute(relative)) {
|
|
16677
17259
|
throw new ManifestEditError("the keypoints file must be a .json path inside the project");
|
|
16678
17260
|
}
|
|
16679
|
-
const existed = (0,
|
|
17261
|
+
const existed = (0, import_node_fs29.existsSync)(file);
|
|
16680
17262
|
let previous = null;
|
|
16681
17263
|
if (request.set) {
|
|
16682
17264
|
if (existed && !request.overwrite) {
|
|
16683
17265
|
throw new ManifestEditError(`${request.keypointsFile} already exists; choose another path, or replace it`);
|
|
16684
17266
|
}
|
|
16685
|
-
if (existed) previous = await (0,
|
|
16686
|
-
await (0,
|
|
16687
|
-
await (0,
|
|
17267
|
+
if (existed) previous = await (0, import_promises25.readFile)(file);
|
|
17268
|
+
await (0, import_promises25.mkdir)(import_node_path33.default.dirname(file), { recursive: true });
|
|
17269
|
+
await (0, import_promises25.writeFile)(file, JSON.stringify(request.set, null, 2) + "\n");
|
|
16688
17270
|
} else if (!existed) {
|
|
16689
17271
|
throw new ManifestEditError(`${request.keypointsFile} does not exist; paste poses or estimate them from the sprite`);
|
|
16690
17272
|
}
|
|
@@ -16699,7 +17281,7 @@ async function createSkeletonAnimation(manifestPath, request) {
|
|
|
16699
17281
|
revision: {
|
|
16700
17282
|
mode: "animate-skeleton",
|
|
16701
17283
|
from: request.from,
|
|
16702
|
-
keypointsFile: relative.split(
|
|
17284
|
+
keypointsFile: relative.split(import_node_path33.default.sep).join("/"),
|
|
16703
17285
|
direction: request.direction,
|
|
16704
17286
|
...request.description ? { description: request.description } : {},
|
|
16705
17287
|
...request.skeletonTemplate ? { skeletonTemplate: request.skeletonTemplate } : {}
|
|
@@ -16708,8 +17290,8 @@ async function createSkeletonAnimation(manifestPath, request) {
|
|
|
16708
17290
|
});
|
|
16709
17291
|
} catch (error) {
|
|
16710
17292
|
if (request.set) {
|
|
16711
|
-
if (previous) await (0,
|
|
16712
|
-
else await (0,
|
|
17293
|
+
if (previous) await (0, import_promises25.writeFile)(file, previous);
|
|
17294
|
+
else await (0, import_promises25.rm)(file, { force: true });
|
|
16713
17295
|
}
|
|
16714
17296
|
throw error;
|
|
16715
17297
|
}
|
|
@@ -16720,19 +17302,19 @@ async function updateSkeletonKeypoints(ctx, request) {
|
|
|
16720
17302
|
if (!spec) throw new ManifestEditError(`"${key}" is not declared by the manifest`);
|
|
16721
17303
|
const file = spec.revision?.mode === "animate-skeleton" ? spec.revision.keypointsFile : void 0;
|
|
16722
17304
|
if (!file) throw new ManifestEditError(`${key} is not an animate-skeleton revision`);
|
|
16723
|
-
const relative =
|
|
16724
|
-
if (relative.startsWith("..") ||
|
|
17305
|
+
const relative = import_node_path33.default.relative(ctx.loaded.root, file);
|
|
17306
|
+
if (relative.startsWith("..") || import_node_path33.default.isAbsolute(relative)) {
|
|
16725
17307
|
throw new ManifestEditError(`${key}'s keypoints file is outside the project; edit it by hand`);
|
|
16726
17308
|
}
|
|
16727
|
-
const current = (0,
|
|
17309
|
+
const current = (0, import_node_fs29.existsSync)(file) ? sha256(await (0, import_promises25.readFile)(file)) : null;
|
|
16728
17310
|
if (current !== request.expectedSha256) {
|
|
16729
17311
|
throw Object.assign(
|
|
16730
|
-
new Error(`${relative.split(
|
|
17312
|
+
new Error(`${relative.split(import_node_path33.default.sep).join("/")} changed on disk since this page loaded it; press Refresh and edit again`),
|
|
16731
17313
|
{ status: 409 }
|
|
16732
17314
|
);
|
|
16733
17315
|
}
|
|
16734
|
-
await (0,
|
|
16735
|
-
return relative.split(
|
|
17316
|
+
await (0, import_promises25.writeFile)(file, JSON.stringify(request.set, null, 2) + "\n");
|
|
17317
|
+
return relative.split(import_node_path33.default.sep).join("/");
|
|
16736
17318
|
}
|
|
16737
17319
|
|
|
16738
17320
|
// src/gallery/edit.ts
|
|
@@ -16786,7 +17368,7 @@ function createGalleryEditHandler(opts) {
|
|
|
16786
17368
|
const history = /* @__PURE__ */ new Map();
|
|
16787
17369
|
const manifestOf = async (project) => {
|
|
16788
17370
|
try {
|
|
16789
|
-
return
|
|
17371
|
+
return import_node_path34.default.resolve(await opts.manifestFor(typeof project === "string" ? project : void 0));
|
|
16790
17372
|
} catch {
|
|
16791
17373
|
return null;
|
|
16792
17374
|
}
|
|
@@ -16796,7 +17378,7 @@ function createGalleryEditHandler(opts) {
|
|
|
16796
17378
|
if (undo.success) {
|
|
16797
17379
|
const manifestPath2 = await manifestOf(undo.data.project);
|
|
16798
17380
|
if (!manifestPath2) throw new ManifestEditError("no manifest to undo an edit in");
|
|
16799
|
-
const current = await (0,
|
|
17381
|
+
const current = await (0, import_promises26.readFile)(manifestPath2, "utf8");
|
|
16800
17382
|
if (sha256(current) !== undo.data.expectedSha256) throw new ManifestDriftError(manifestPath2);
|
|
16801
17383
|
const stack = history.get(manifestPath2) ?? [];
|
|
16802
17384
|
const last = stack[stack.length - 1];
|
|
@@ -16806,17 +17388,17 @@ function createGalleryEditHandler(opts) {
|
|
|
16806
17388
|
throw Object.assign(new Error("the manifest changed outside the gallery since its last edit; undo would discard that change"), { status: 409 });
|
|
16807
17389
|
}
|
|
16808
17390
|
const tmp = manifestPath2 + ".pixelkiln-undo";
|
|
16809
|
-
await (0,
|
|
16810
|
-
await (0,
|
|
17391
|
+
await (0, import_promises26.writeFile)(tmp, last.before);
|
|
17392
|
+
await (0, import_promises26.rename)(tmp, manifestPath2);
|
|
16811
17393
|
stack.pop();
|
|
16812
|
-
log(` manifest edit undone in ${
|
|
17394
|
+
log(` manifest edit undone in ${import_node_path34.default.relative(process.cwd(), manifestPath2) || manifestPath2}`);
|
|
16813
17395
|
return opts.reload();
|
|
16814
17396
|
}
|
|
16815
17397
|
const manifestPath = await manifestOf(body?.project);
|
|
16816
|
-
const before = manifestPath ? await (0,
|
|
17398
|
+
const before = manifestPath ? await (0, import_promises26.readFile)(manifestPath, "utf8").catch(() => null) : null;
|
|
16817
17399
|
const build = await apply(body);
|
|
16818
17400
|
if (manifestPath && before !== null) {
|
|
16819
|
-
const after = await (0,
|
|
17401
|
+
const after = await (0, import_promises26.readFile)(manifestPath, "utf8").catch(() => null);
|
|
16820
17402
|
if (after !== null && after !== before) {
|
|
16821
17403
|
const stack = history.get(manifestPath) ?? [];
|
|
16822
17404
|
stack.push({ before, afterSha: sha256(after) });
|
|
@@ -16871,7 +17453,7 @@ function createEditApplier(opts, log) {
|
|
|
16871
17453
|
} catch (error) {
|
|
16872
17454
|
throw new ManifestEditError(error instanceof Error ? error.message : String(error));
|
|
16873
17455
|
}
|
|
16874
|
-
const written = await saveProjectImage(
|
|
17456
|
+
const written = await saveProjectImage(import_node_path34.default.dirname(import_node_path34.default.resolve(manifestPath2)), upload.data);
|
|
16875
17457
|
log(` image added: ${written}`);
|
|
16876
17458
|
return opts.reload();
|
|
16877
17459
|
}
|
|
@@ -16938,7 +17520,7 @@ function createEditApplier(opts, log) {
|
|
|
16938
17520
|
}
|
|
16939
17521
|
const result = await applyManifestEdit(manifestPath, parsed.data);
|
|
16940
17522
|
if (result.changed) {
|
|
16941
|
-
const relative =
|
|
17523
|
+
const relative = import_node_path34.default.relative(process.cwd(), result.manifestPath);
|
|
16942
17524
|
const shown = !relative || relative.startsWith("..") ? result.manifestPath : relative;
|
|
16943
17525
|
log(` manifest edited: ${describeEdit(parsed.data)} in ${shown}`);
|
|
16944
17526
|
}
|
|
@@ -16966,15 +17548,20 @@ function describeEdit(edit) {
|
|
|
16966
17548
|
// src/gallery/snapshot.ts
|
|
16967
17549
|
var PROJECT_FILE_TYPE = "application/octet-stream";
|
|
16968
17550
|
function galleryMediaId(absolutePath) {
|
|
16969
|
-
return (0, import_node_crypto7.createHash)("sha256").update(
|
|
17551
|
+
return (0, import_node_crypto7.createHash)("sha256").update(import_node_path35.default.resolve(absolutePath)).digest("hex").slice(0, 24);
|
|
16970
17552
|
}
|
|
16971
17553
|
function galleryMediaRoute(id) {
|
|
16972
17554
|
return `/media/${id}`;
|
|
16973
17555
|
}
|
|
17556
|
+
function renderedSize(entry) {
|
|
17557
|
+
const vfx = entry?.providerMetadata?.[entry.provider]?.vfx;
|
|
17558
|
+
return typeof vfx?.width === "number" && typeof vfx.height === "number" ? { width: vfx.width, height: vfx.height } : null;
|
|
17559
|
+
}
|
|
16974
17560
|
function isDeclared(loaded, styleId, assetId) {
|
|
16975
17561
|
const asset = loaded.manifest.assets[assetId];
|
|
16976
|
-
|
|
16977
|
-
|
|
17562
|
+
const style = loaded.manifest.styles[styleId];
|
|
17563
|
+
if (!asset || !style) return false;
|
|
17564
|
+
return assetInStyle(asset, styleId, style);
|
|
16978
17565
|
}
|
|
16979
17566
|
function matchesFilter(filter, styleId, assetId) {
|
|
16980
17567
|
if (filter?.styles?.length && !filter.styles.includes(styleId)) return false;
|
|
@@ -16985,9 +17572,9 @@ function describeHistory(media, entry, cacheDir) {
|
|
|
16985
17572
|
return (entry.history ?? []).map((generation, i) => {
|
|
16986
17573
|
const outputs = generation.outputs.map((output) => {
|
|
16987
17574
|
const mediaType = output.mediaType ?? MediaType.PNG;
|
|
16988
|
-
const file =
|
|
17575
|
+
const file = import_node_path35.default.join(cacheDir, cacheFileName(output.sha256, mediaType));
|
|
16989
17576
|
let url = null;
|
|
16990
|
-
if ((0,
|
|
17577
|
+
if ((0, import_node_fs30.existsSync)(file)) {
|
|
16991
17578
|
const id = galleryMediaId(file);
|
|
16992
17579
|
media.set(id, { path: file, contentType: mediaType });
|
|
16993
17580
|
url = `${galleryMediaRoute(id)}?v=${output.sha256.slice(0, 24)}`;
|
|
@@ -17019,7 +17606,7 @@ function describeHistory(media, entry, cacheDir) {
|
|
|
17019
17606
|
var MAX_COMPARED_BYTES = 8 * 1024 * 1024;
|
|
17020
17607
|
async function samePixels(a, b) {
|
|
17021
17608
|
try {
|
|
17022
|
-
const [left, right] = await Promise.all([(0,
|
|
17609
|
+
const [left, right] = await Promise.all([(0, import_promises27.readFile)(a), (0, import_promises27.readFile)(b)]);
|
|
17023
17610
|
if (left.length > MAX_COMPARED_BYTES || right.length > MAX_COMPARED_BYTES) return false;
|
|
17024
17611
|
const x = decodePng(left);
|
|
17025
17612
|
const y = decodePng(right);
|
|
@@ -17085,7 +17672,7 @@ function describeCharacter(spec, entry) {
|
|
|
17085
17672
|
}
|
|
17086
17673
|
async function fileInfo(absolutePath) {
|
|
17087
17674
|
try {
|
|
17088
|
-
const info = await (0,
|
|
17675
|
+
const info = await (0, import_promises27.stat)(absolutePath);
|
|
17089
17676
|
return info.isFile() ? { bytes: info.size, modifiedAt: info.mtime.toISOString() } : null;
|
|
17090
17677
|
} catch {
|
|
17091
17678
|
return null;
|
|
@@ -17117,10 +17704,10 @@ async function describeOutput(media, root, absolutePath, recorded = {}) {
|
|
|
17117
17704
|
async function describeQuality(media, root, inspection) {
|
|
17118
17705
|
const outputPaths = inspection.outputs ?? [inspection.output];
|
|
17119
17706
|
const outputs = await Promise.all(
|
|
17120
|
-
outputPaths.map((file) => describeOutput(media, root,
|
|
17707
|
+
outputPaths.map((file) => describeOutput(media, root, import_node_path35.default.resolve(root, file)))
|
|
17121
17708
|
);
|
|
17122
|
-
const recordPath =
|
|
17123
|
-
const recordExists = (0,
|
|
17709
|
+
const recordPath = import_node_path35.default.resolve(root, inspection.record);
|
|
17710
|
+
const recordExists = (0, import_node_fs30.existsSync)(recordPath);
|
|
17124
17711
|
let options = null;
|
|
17125
17712
|
let check = null;
|
|
17126
17713
|
if (recordExists) {
|
|
@@ -17139,7 +17726,7 @@ async function describeQuality(media, root, inspection) {
|
|
|
17139
17726
|
return {
|
|
17140
17727
|
state: inspection.state,
|
|
17141
17728
|
reason: inspection.reason,
|
|
17142
|
-
output: portableOutputPath(
|
|
17729
|
+
output: portableOutputPath(import_node_path35.default.resolve(root, inspection.output), root),
|
|
17143
17730
|
record: portableOutputPath(recordPath, root),
|
|
17144
17731
|
recordExists,
|
|
17145
17732
|
outputs,
|
|
@@ -17177,9 +17764,9 @@ async function buildGallerySnapshot(opts) {
|
|
|
17177
17764
|
const media = /* @__PURE__ */ new Map();
|
|
17178
17765
|
const plan = await buildPlan(specs, lock);
|
|
17179
17766
|
const items = [];
|
|
17180
|
-
const manifestText = await (0,
|
|
17767
|
+
const manifestText = await (0, import_promises27.readFile)(loaded.path, "utf8");
|
|
17181
17768
|
const manifestSha256 = sha256(manifestText);
|
|
17182
|
-
const cacheDir =
|
|
17769
|
+
const cacheDir = import_node_path35.default.resolve(import_node_path35.default.dirname(opts.lockPath), ".pixelkiln", "cache");
|
|
17183
17770
|
let rawStyles = {};
|
|
17184
17771
|
try {
|
|
17185
17772
|
const raw = JSON.parse(manifestText);
|
|
@@ -17203,12 +17790,12 @@ async function buildGallerySnapshot(opts) {
|
|
|
17203
17790
|
)
|
|
17204
17791
|
);
|
|
17205
17792
|
if (spec.source) {
|
|
17206
|
-
const editPath =
|
|
17793
|
+
const editPath = import_node_path35.default.resolve(root, spec.source);
|
|
17207
17794
|
const memberCount = sourceIsStem(spec.source, entry, root) ? entry.outputs.length : 1;
|
|
17208
17795
|
const editShas = await Promise.all(
|
|
17209
17796
|
Array.from({ length: memberCount }, async (_, index) => {
|
|
17210
17797
|
const file = sourceOutputPath(spec.source, entry, index, root);
|
|
17211
|
-
return { file, sha256: (0,
|
|
17798
|
+
return { file, sha256: (0, import_node_fs30.existsSync)(file) ? await sha256File(file) : null };
|
|
17212
17799
|
})
|
|
17213
17800
|
);
|
|
17214
17801
|
edits = await Promise.all(editShas.map(({ file, sha256: editSha }) => describeOutput(media, root, file, { sha256: editSha })));
|
|
@@ -17216,7 +17803,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
17216
17803
|
const companion = await readHandEditCompanion(editPath);
|
|
17217
17804
|
if (companion) {
|
|
17218
17805
|
const projectPath = handEditProjectPath(editPath);
|
|
17219
|
-
const project = companion.project && (0,
|
|
17806
|
+
const project = companion.project && (0, import_node_fs30.existsSync)(projectPath) ? await fileInfo(projectPath) : null;
|
|
17220
17807
|
let projectUrl = null;
|
|
17221
17808
|
if (project) {
|
|
17222
17809
|
const id = galleryMediaId(projectPath);
|
|
@@ -17252,8 +17839,8 @@ async function buildGallerySnapshot(opts) {
|
|
|
17252
17839
|
editStatus = edits.some((member) => !member.exists) ? "missing" : editChanged.every((changed) => !changed) ? "same" : regenerated ? "regenerated-since" : "edited";
|
|
17253
17840
|
}
|
|
17254
17841
|
} else if (spec.source) {
|
|
17255
|
-
outputs = [await describeOutput(media, root,
|
|
17256
|
-
} else if (!entry && (0,
|
|
17842
|
+
outputs = [await describeOutput(media, root, import_node_path35.default.resolve(root, spec.source))];
|
|
17843
|
+
} else if (!entry && (0, import_node_fs30.existsSync)(spec.outFile)) {
|
|
17257
17844
|
outputs = [await describeOutput(media, root, spec.outFile)];
|
|
17258
17845
|
} else {
|
|
17259
17846
|
outputs = [];
|
|
@@ -17274,8 +17861,8 @@ async function buildGallerySnapshot(opts) {
|
|
|
17274
17861
|
generator: entry?.generator ?? spec.generator,
|
|
17275
17862
|
prompt: entry?.prompt ?? spec.prompt,
|
|
17276
17863
|
currentPrompt: entry && entry.prompt !== spec.prompt ? spec.prompt : null,
|
|
17277
|
-
width: entry?.width ?? spec.width,
|
|
17278
|
-
height: entry?.height ?? spec.height,
|
|
17864
|
+
width: renderedSize(entry)?.width ?? entry?.width ?? spec.width,
|
|
17865
|
+
height: renderedSize(entry)?.height ?? entry?.height ?? spec.height,
|
|
17279
17866
|
tileFeature: entry?.tileFeature ?? spec.tileFeature ?? null,
|
|
17280
17867
|
fps: quality?.frameSet?.fps ?? metadataFps(entry) ?? spec.quality?.fps ?? null,
|
|
17281
17868
|
cost: entry?.cost ?? 0,
|
|
@@ -17456,7 +18043,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
17456
18043
|
id: loaded.manifest.name,
|
|
17457
18044
|
name: loaded.manifest.name,
|
|
17458
18045
|
manifest: loaded.path,
|
|
17459
|
-
lock:
|
|
18046
|
+
lock: import_node_path35.default.resolve(opts.lockPath),
|
|
17460
18047
|
root,
|
|
17461
18048
|
provider: loaded.manifest.provider,
|
|
17462
18049
|
account: null,
|
|
@@ -17482,7 +18069,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
17482
18069
|
return { snapshot, media };
|
|
17483
18070
|
}
|
|
17484
18071
|
async function buildWorkspaceGallerySnapshot(opts) {
|
|
17485
|
-
const dir =
|
|
18072
|
+
const dir = import_node_path35.default.dirname(import_node_path35.default.resolve(opts.workspacePath));
|
|
17486
18073
|
const media = /* @__PURE__ */ new Map();
|
|
17487
18074
|
const projects = [];
|
|
17488
18075
|
const items = [];
|
|
@@ -17525,11 +18112,11 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
17525
18112
|
id: project.id,
|
|
17526
18113
|
name: loaded.manifest.name,
|
|
17527
18114
|
manifest: manifestPath,
|
|
17528
|
-
lock:
|
|
18115
|
+
lock: import_node_path35.default.resolve(lockPath),
|
|
17529
18116
|
root: loaded.root,
|
|
17530
18117
|
provider: loaded.manifest.provider,
|
|
17531
18118
|
account: project.account ?? null,
|
|
17532
|
-
manifestSha256: manifestSha256 ?? sha256(await (0,
|
|
18119
|
+
manifestSha256: manifestSha256 ?? sha256(await (0, import_promises27.readFile)(manifestPath, "utf8")),
|
|
17533
18120
|
historyLimit: historyLimit(loaded.manifest),
|
|
17534
18121
|
entries: Object.keys(lock.entries).length,
|
|
17535
18122
|
items: projectItems.length,
|
|
@@ -17541,8 +18128,8 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
17541
18128
|
id: project.id,
|
|
17542
18129
|
name: project.id,
|
|
17543
18130
|
manifest: manifestPath,
|
|
17544
|
-
lock:
|
|
17545
|
-
root:
|
|
18131
|
+
lock: import_node_path35.default.resolve(lockPath),
|
|
18132
|
+
root: import_node_path35.default.dirname(manifestPath),
|
|
17546
18133
|
provider: project.provider,
|
|
17547
18134
|
account: project.account ?? null,
|
|
17548
18135
|
manifestSha256: null,
|
|
@@ -17569,7 +18156,7 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
17569
18156
|
version: 1,
|
|
17570
18157
|
generatedAt: (opts.now ?? (() => /* @__PURE__ */ new Date()))().toISOString(),
|
|
17571
18158
|
project: null,
|
|
17572
|
-
workspace: { path:
|
|
18159
|
+
workspace: { path: import_node_path35.default.resolve(opts.workspacePath), dir, projects },
|
|
17573
18160
|
filter,
|
|
17574
18161
|
totals: {
|
|
17575
18162
|
entries: projects.reduce((sum, project) => sum + project.entries, 0),
|
|
@@ -17586,7 +18173,7 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
17586
18173
|
}
|
|
17587
18174
|
|
|
17588
18175
|
// src/gallery/page.ts
|
|
17589
|
-
var
|
|
18176
|
+
var import_node_fs31 = require("fs");
|
|
17590
18177
|
|
|
17591
18178
|
// src/gallery/client-bundle.ts
|
|
17592
18179
|
var import_node_module = require("module");
|
|
@@ -17626,8 +18213,8 @@ var client;
|
|
|
17626
18213
|
function clientAssets() {
|
|
17627
18214
|
const prebuilt = new URL("gallery.js", CLIENT_DIR);
|
|
17628
18215
|
client ??= {
|
|
17629
|
-
css: (0,
|
|
17630
|
-
js: (0,
|
|
18216
|
+
css: (0, import_node_fs31.readFileSync)(new URL("gallery.css", CLIENT_DIR), "utf8"),
|
|
18217
|
+
js: (0, import_node_fs31.existsSync)(prebuilt) ? (0, import_node_fs31.readFileSync)(prebuilt, "utf8") : bundleGalleryClient()
|
|
17631
18218
|
};
|
|
17632
18219
|
return client;
|
|
17633
18220
|
}
|
|
@@ -17770,10 +18357,10 @@ var EDITOR_RELEASE_BASE = "https://github.com/gfargo/pixelkiln/releases/download
|
|
|
17770
18357
|
|
|
17771
18358
|
// src/editor/install.ts
|
|
17772
18359
|
var import_node_crypto8 = require("crypto");
|
|
17773
|
-
var
|
|
17774
|
-
var
|
|
18360
|
+
var import_node_fs32 = require("fs");
|
|
18361
|
+
var import_promises28 = require("fs/promises");
|
|
17775
18362
|
var import_node_os4 = __toESM(require("os"), 1);
|
|
17776
|
-
var
|
|
18363
|
+
var import_node_path36 = __toESM(require("path"), 1);
|
|
17777
18364
|
var EditorInstallError = class extends Error {
|
|
17778
18365
|
constructor(message6, file) {
|
|
17779
18366
|
super(message6);
|
|
@@ -17784,15 +18371,15 @@ var EditorInstallError = class extends Error {
|
|
|
17784
18371
|
};
|
|
17785
18372
|
function editorToolsRoot() {
|
|
17786
18373
|
const override = process.env.PIXELKILN_TOOLS_DIR?.trim();
|
|
17787
|
-
if (override) return
|
|
17788
|
-
if (process.platform === "darwin") return
|
|
18374
|
+
if (override) return import_node_path36.default.resolve(override);
|
|
18375
|
+
if (process.platform === "darwin") return import_node_path36.default.join(import_node_os4.default.homedir(), "Library", "Caches", "pixelkiln", "tools");
|
|
17789
18376
|
if (process.platform === "win32") {
|
|
17790
|
-
return
|
|
18377
|
+
return import_node_path36.default.join(process.env.LOCALAPPDATA ?? import_node_path36.default.join(import_node_os4.default.homedir(), "AppData", "Local"), "pixelkiln", "tools");
|
|
17791
18378
|
}
|
|
17792
|
-
return
|
|
18379
|
+
return import_node_path36.default.join(process.env.XDG_CACHE_HOME ?? import_node_path36.default.join(import_node_os4.default.homedir(), ".cache"), "pixelkiln", "tools");
|
|
17793
18380
|
}
|
|
17794
18381
|
function editorDir(pin = EDITOR_PIN, root = editorToolsRoot()) {
|
|
17795
|
-
return
|
|
18382
|
+
return import_node_path36.default.join(root, "pixelorama", pin.release ?? "unpublished");
|
|
17796
18383
|
}
|
|
17797
18384
|
function editorBaseUrl(pin = EDITOR_PIN) {
|
|
17798
18385
|
const override = process.env.PIXELKILN_EDITOR_URL?.trim();
|
|
@@ -17802,7 +18389,7 @@ function editorBaseUrl(pin = EDITOR_PIN) {
|
|
|
17802
18389
|
}
|
|
17803
18390
|
async function sha256Of(file) {
|
|
17804
18391
|
try {
|
|
17805
|
-
return (0, import_node_crypto8.createHash)("sha256").update(await (0,
|
|
18392
|
+
return (0, import_node_crypto8.createHash)("sha256").update(await (0, import_promises28.readFile)(file)).digest("hex");
|
|
17806
18393
|
} catch {
|
|
17807
18394
|
return null;
|
|
17808
18395
|
}
|
|
@@ -17815,8 +18402,8 @@ async function editorStatus(opts = {}) {
|
|
|
17815
18402
|
let totalBytes = 0;
|
|
17816
18403
|
for (const [name, expected] of Object.entries(pin.files)) {
|
|
17817
18404
|
totalBytes += expected.bytes;
|
|
17818
|
-
const file =
|
|
17819
|
-
const info = (0,
|
|
18405
|
+
const file = import_node_path36.default.join(dir, name);
|
|
18406
|
+
const info = (0, import_node_fs32.existsSync)(file) ? await (0, import_promises28.stat)(file).catch(() => null) : null;
|
|
17820
18407
|
if (info && info.size === expected.bytes && await sha256Of(file) === expected.sha256) {
|
|
17821
18408
|
installedBytes += expected.bytes;
|
|
17822
18409
|
} else {
|
|
@@ -17845,7 +18432,7 @@ async function installEditor(opts = {}) {
|
|
|
17845
18432
|
);
|
|
17846
18433
|
}
|
|
17847
18434
|
if (!baseUrl) throw new EditorInstallError("no download location for the editor build");
|
|
17848
|
-
await (0,
|
|
18435
|
+
await (0, import_promises28.mkdir)(dir, { recursive: true });
|
|
17849
18436
|
const downloaded = [];
|
|
17850
18437
|
const skipped = Object.keys(pin.files).filter((name) => !before.missing.includes(name));
|
|
17851
18438
|
let fetchedBytes = before.installedBytes;
|
|
@@ -17873,13 +18460,13 @@ async function installEditor(opts = {}) {
|
|
|
17873
18460
|
if (digest2 !== expected.sha256) {
|
|
17874
18461
|
throw new EditorInstallError(`${name}: sha256 ${digest2.slice(0, 12)}\u2026 does not match the pinned ${expected.sha256.slice(0, 12)}\u2026`, name);
|
|
17875
18462
|
}
|
|
17876
|
-
const final =
|
|
18463
|
+
const final = import_node_path36.default.join(dir, name);
|
|
17877
18464
|
const tmp = `${final}.${process.pid}.part`;
|
|
17878
18465
|
try {
|
|
17879
|
-
await (0,
|
|
17880
|
-
await (0,
|
|
18466
|
+
await (0, import_promises28.writeFile)(tmp, bytes);
|
|
18467
|
+
await (0, import_promises28.rename)(tmp, final);
|
|
17881
18468
|
} finally {
|
|
17882
|
-
await (0,
|
|
18469
|
+
await (0, import_promises28.rm)(tmp, { force: true });
|
|
17883
18470
|
}
|
|
17884
18471
|
fetchedBytes += bytes.length;
|
|
17885
18472
|
downloaded.push(name);
|
|
@@ -17919,12 +18506,12 @@ var EDITOR_CONTENT_TYPES = {
|
|
|
17919
18506
|
".json": "application/json; charset=utf-8"
|
|
17920
18507
|
};
|
|
17921
18508
|
function editorContentType(name) {
|
|
17922
|
-
return EDITOR_CONTENT_TYPES[
|
|
18509
|
+
return EDITOR_CONTENT_TYPES[import_node_path36.default.extname(name).toLowerCase()] ?? null;
|
|
17923
18510
|
}
|
|
17924
18511
|
|
|
17925
18512
|
// src/gallery/editor.ts
|
|
17926
|
-
var
|
|
17927
|
-
var
|
|
18513
|
+
var import_promises29 = require("fs/promises");
|
|
18514
|
+
var import_node_path37 = __toESM(require("path"), 1);
|
|
17928
18515
|
function editorRoute(release, name = "index.html") {
|
|
17929
18516
|
return `/editor/${encodeURIComponent(release)}/${encodeURIComponent(name)}`;
|
|
17930
18517
|
}
|
|
@@ -17997,7 +18584,7 @@ function createGalleryEditorHandlers(opts = {}) {
|
|
|
17997
18584
|
if (!status.installed) return null;
|
|
17998
18585
|
let bytes;
|
|
17999
18586
|
try {
|
|
18000
|
-
bytes = await (0,
|
|
18587
|
+
bytes = await (0, import_promises29.readFile)(import_node_path37.default.join(dir, name));
|
|
18001
18588
|
} catch {
|
|
18002
18589
|
verified = null;
|
|
18003
18590
|
return null;
|
|
@@ -18014,7 +18601,7 @@ function createGalleryEditorHandlers(opts = {}) {
|
|
|
18014
18601
|
// src/gallery/server.ts
|
|
18015
18602
|
var import_node_crypto9 = require("crypto");
|
|
18016
18603
|
var import_node_http2 = require("http");
|
|
18017
|
-
var
|
|
18604
|
+
var import_promises30 = require("fs/promises");
|
|
18018
18605
|
var MAX_EDIT_BYTES = 48 * 1024 * 1024;
|
|
18019
18606
|
var EDITOR_CSP = [
|
|
18020
18607
|
"default-src 'self'",
|
|
@@ -18195,7 +18782,7 @@ async function serveGallery(opts) {
|
|
|
18195
18782
|
const asset = reviewAssets.get(jobId)?.get(url2.pathname);
|
|
18196
18783
|
if (!asset) return fail(404, "no such review asset");
|
|
18197
18784
|
try {
|
|
18198
|
-
const bytes = await (0,
|
|
18785
|
+
const bytes = await (0, import_promises30.readFile)(asset.path);
|
|
18199
18786
|
res.writeHead(200, {
|
|
18200
18787
|
"Content-Type": asset.contentType,
|
|
18201
18788
|
"Cache-Control": "no-store",
|
|
@@ -18278,7 +18865,7 @@ async function serveGallery(opts) {
|
|
|
18278
18865
|
if (!asset) return fail(404, "no such gallery media");
|
|
18279
18866
|
let bytes;
|
|
18280
18867
|
try {
|
|
18281
|
-
bytes = await (0,
|
|
18868
|
+
bytes = await (0, import_promises30.readFile)(asset.path);
|
|
18282
18869
|
} catch {
|
|
18283
18870
|
return fail(404, "gallery media is no longer on disk; refresh the page");
|
|
18284
18871
|
}
|
|
@@ -18688,19 +19275,19 @@ function createGenerateHandlers(opts) {
|
|
|
18688
19275
|
}
|
|
18689
19276
|
|
|
18690
19277
|
// src/pipeline/salvage.ts
|
|
18691
|
-
var
|
|
18692
|
-
var
|
|
18693
|
-
var
|
|
19278
|
+
var import_promises31 = require("fs/promises");
|
|
19279
|
+
var import_node_fs33 = require("fs");
|
|
19280
|
+
var import_node_path38 = __toESM(require("path"), 1);
|
|
18694
19281
|
function providerClaimId(provider, objectId) {
|
|
18695
19282
|
return `${encodeURIComponent(provider)}:${encodeURIComponent(objectId)}`;
|
|
18696
19283
|
}
|
|
18697
19284
|
async function loadClaims(lockPaths, opts = {}) {
|
|
18698
19285
|
const claimed = /* @__PURE__ */ new Set();
|
|
18699
19286
|
for (const p of lockPaths) {
|
|
18700
|
-
if (!(0,
|
|
19287
|
+
if (!(0, import_node_fs33.existsSync)(p)) throw new Error(`Claim lockfile not found: ${p}`);
|
|
18701
19288
|
let parsed;
|
|
18702
19289
|
try {
|
|
18703
|
-
parsed = parseLock(JSON.parse(await (0,
|
|
19290
|
+
parsed = parseLock(JSON.parse(await (0, import_promises31.readFile)(p, "utf8")));
|
|
18704
19291
|
} catch {
|
|
18705
19292
|
throw new Error(`Claim lockfile is malformed: ${p}`);
|
|
18706
19293
|
}
|
|
@@ -18804,20 +19391,20 @@ function matchOrphanStyle(prompt, manifest) {
|
|
|
18804
19391
|
return matchStyleByPattern(prompt, manifest);
|
|
18805
19392
|
}
|
|
18806
19393
|
async function loadSiblingManifests(ownManifestPath, workspaceManifestPaths, claimPaths) {
|
|
18807
|
-
const own =
|
|
19394
|
+
const own = import_node_path38.default.resolve(ownManifestPath);
|
|
18808
19395
|
const siblingManifestPaths = [
|
|
18809
19396
|
.../* @__PURE__ */ new Set([
|
|
18810
19397
|
...workspaceManifestPaths,
|
|
18811
|
-
...claimPaths.map((c) =>
|
|
19398
|
+
...claimPaths.map((c) => import_node_path38.default.join(import_node_path38.default.dirname(import_node_path38.default.resolve(c)), "pixelkiln.manifest.json"))
|
|
18812
19399
|
])
|
|
18813
19400
|
];
|
|
18814
19401
|
const siblings = [];
|
|
18815
19402
|
for (const siblingManifestPath of siblingManifestPaths) {
|
|
18816
|
-
if (
|
|
18817
|
-
if (!(0,
|
|
19403
|
+
if (import_node_path38.default.resolve(siblingManifestPath) === own) continue;
|
|
19404
|
+
if (!(0, import_node_fs33.existsSync)(siblingManifestPath)) continue;
|
|
18818
19405
|
try {
|
|
18819
19406
|
const { manifest } = await loadManifest(siblingManifestPath);
|
|
18820
|
-
siblings.push({ label:
|
|
19407
|
+
siblings.push({ label: import_node_path38.default.basename(import_node_path38.default.dirname(siblingManifestPath)), manifest });
|
|
18821
19408
|
} catch {
|
|
18822
19409
|
}
|
|
18823
19410
|
}
|
|
@@ -18937,8 +19524,8 @@ async function applyTags(provider, decisions, existing, opts = {}) {
|
|
|
18937
19524
|
}
|
|
18938
19525
|
|
|
18939
19526
|
// src/pick/salvage-server.ts
|
|
18940
|
-
var
|
|
18941
|
-
var
|
|
19527
|
+
var import_promises32 = require("fs/promises");
|
|
19528
|
+
var import_node_path39 = __toESM(require("path"), 1);
|
|
18942
19529
|
|
|
18943
19530
|
// src/pick/salvage-sheet.ts
|
|
18944
19531
|
var escapeHtml3 = (s) => s.replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
@@ -19104,7 +19691,7 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
19104
19691
|
});
|
|
19105
19692
|
const html = renderSalvageSheet(orphans, {
|
|
19106
19693
|
styleId: ctx.styleId,
|
|
19107
|
-
importDir:
|
|
19694
|
+
importDir: import_node_path39.default.relative(process.cwd(), ctx.importDir) || "."
|
|
19108
19695
|
});
|
|
19109
19696
|
const byId = new Map(orphans.map((o) => [o.id, o]));
|
|
19110
19697
|
const existingTags = new Map(orphans.map((o) => [o.id, o.tags]));
|
|
@@ -19160,10 +19747,10 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
19160
19747
|
if (!buf.subarray(0, 8).equals(PNG_SIGNATURE2)) throw new Error("not a PNG");
|
|
19161
19748
|
decodePng(buf);
|
|
19162
19749
|
const assetId = idFromPrompt(orphan.prompt, taken);
|
|
19163
|
-
const rel =
|
|
19164
|
-
const outFile =
|
|
19165
|
-
await (0,
|
|
19166
|
-
await (0,
|
|
19750
|
+
const rel = import_node_path39.default.join("_salvaged", `${assetId}.png`);
|
|
19751
|
+
const outFile = import_node_path39.default.resolve(ctx.importDir, rel);
|
|
19752
|
+
await (0, import_promises32.mkdir)(import_node_path39.default.dirname(outFile), { recursive: true });
|
|
19753
|
+
await (0, import_promises32.writeFile)(outFile, buf);
|
|
19167
19754
|
ctx.manifest.assets[assetId] = {
|
|
19168
19755
|
prompt: orphan.prompt,
|
|
19169
19756
|
promptByStyle: {},
|
|
@@ -19195,7 +19782,7 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
19195
19782
|
sourceUrl: durableSource,
|
|
19196
19783
|
sourceUrls: durableSource ? [{ url: durableSource }] : [],
|
|
19197
19784
|
outputs: [{
|
|
19198
|
-
path: portableOutputPath(outFile,
|
|
19785
|
+
path: portableOutputPath(outFile, import_node_path39.default.dirname(ctx.manifestPath)),
|
|
19199
19786
|
sha256: sha256(buf)
|
|
19200
19787
|
}],
|
|
19201
19788
|
submittedAt: orphan.createdAt,
|
|
@@ -19219,9 +19806,9 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
19219
19806
|
}
|
|
19220
19807
|
}
|
|
19221
19808
|
await applyTags(provider, decisions, existingTags, { onProgress: log, characters: characterMembers });
|
|
19222
|
-
const raw = JSON.parse(await (0,
|
|
19809
|
+
const raw = JSON.parse(await (0, import_promises32.readFile)(ctx.manifestPath, "utf8"));
|
|
19223
19810
|
for (const id of importedAssetIds) raw.assets[id] = ctx.manifest.assets[id];
|
|
19224
|
-
await (0,
|
|
19811
|
+
await (0, import_promises32.writeFile)(ctx.manifestPath, JSON.stringify(raw, null, 2) + "\n");
|
|
19225
19812
|
await saveLock(ctx.lockPath, ctx.lock);
|
|
19226
19813
|
if (characterAssetIds.length) result.characterAssetIds = characterAssetIds;
|
|
19227
19814
|
return result;
|
|
@@ -19332,6 +19919,7 @@ async function workspaceStatus(ws, dir) {
|
|
|
19332
19919
|
0 && (module.exports = {
|
|
19333
19920
|
AssetBatchSchema,
|
|
19334
19921
|
AssetSchema,
|
|
19922
|
+
AssetVfxSchema,
|
|
19335
19923
|
BudgetError,
|
|
19336
19924
|
CHARACTER_DIRECTIONS_4,
|
|
19337
19925
|
CHARACTER_DIRECTIONS_8,
|
|
@@ -19388,12 +19976,14 @@ async function workspaceStatus(ws, dir) {
|
|
|
19388
19976
|
OverwriteRefusedError,
|
|
19389
19977
|
PIXELATE_COST_GENERATIONS,
|
|
19390
19978
|
PIXELATE_MAX_SIDE,
|
|
19979
|
+
PIXELLAB_MCP_URL,
|
|
19391
19980
|
PIXEL_ART_FIXER_REVISION,
|
|
19392
19981
|
PIXEL_ART_FIXER_URL,
|
|
19393
19982
|
PRO_TIER_PRICES,
|
|
19394
19983
|
PixelKilnError,
|
|
19395
19984
|
PixelLabClient,
|
|
19396
19985
|
PixelLabError,
|
|
19986
|
+
PixelLabMcpClient,
|
|
19397
19987
|
PixelLabProvider,
|
|
19398
19988
|
PostprocessSchema,
|
|
19399
19989
|
ProjectError,
|
|
@@ -19416,12 +20006,15 @@ async function workspaceStatus(ws, dir) {
|
|
|
19416
20006
|
UiPieceSchema,
|
|
19417
20007
|
UnsupportedCapabilityError,
|
|
19418
20008
|
UsageError,
|
|
20009
|
+
VFX_TOOL,
|
|
20010
|
+
VfxPartSchema,
|
|
19419
20011
|
WorkspaceProjectSchema,
|
|
19420
20012
|
WorkspaceSchema,
|
|
19421
20013
|
adopt,
|
|
19422
20014
|
applyManifestEdit,
|
|
19423
20015
|
applyTags,
|
|
19424
20016
|
approveQualityRecord,
|
|
20017
|
+
assetInStyle,
|
|
19425
20018
|
auditStyle,
|
|
19426
20019
|
availableProviders,
|
|
19427
20020
|
backoffMs,
|
|
@@ -19448,6 +20041,7 @@ async function workspaceStatus(ws, dir) {
|
|
|
19448
20041
|
defaultOpenCommand,
|
|
19449
20042
|
defaultPixelateOut,
|
|
19450
20043
|
defaultUnzoomOut,
|
|
20044
|
+
describeVfx,
|
|
19451
20045
|
detachHandEdit,
|
|
19452
20046
|
detectMediaType,
|
|
19453
20047
|
doctor,
|
|
@@ -19468,6 +20062,7 @@ async function workspaceStatus(ws, dir) {
|
|
|
19468
20062
|
fontOutputPaths,
|
|
19469
20063
|
formatCost,
|
|
19470
20064
|
frameSetFps,
|
|
20065
|
+
frameSetLoop,
|
|
19471
20066
|
galleryContentSecurityPolicy,
|
|
19472
20067
|
galleryMediaId,
|
|
19473
20068
|
galleryMediaRoute,
|
|
@@ -19490,9 +20085,11 @@ async function workspaceStatus(ws, dir) {
|
|
|
19490
20085
|
inspectRevisionReadiness,
|
|
19491
20086
|
installEditor,
|
|
19492
20087
|
installRecipe,
|
|
20088
|
+
isBusyReply,
|
|
19493
20089
|
isFrameSetEntry,
|
|
19494
20090
|
isMemberSetEntry,
|
|
19495
20091
|
isSensitiveSourceUrl,
|
|
20092
|
+
isVfxReference,
|
|
19496
20093
|
isometricTileCost,
|
|
19497
20094
|
listBundledRecipes,
|
|
19498
20095
|
loadClaims,
|
|
@@ -19501,7 +20098,9 @@ async function workspaceStatus(ws, dir) {
|
|
|
19501
20098
|
loadSiblingManifests,
|
|
19502
20099
|
loadWorkspace,
|
|
19503
20100
|
lockKey,
|
|
20101
|
+
makeVfx,
|
|
19504
20102
|
matchOrphanStyle,
|
|
20103
|
+
mcpClientFromEnv,
|
|
19505
20104
|
measureBalanceChange,
|
|
19506
20105
|
measureDecodedImageQuality,
|
|
19507
20106
|
measureImageQuality,
|
|
@@ -19525,8 +20124,12 @@ async function workspaceStatus(ws, dir) {
|
|
|
19525
20124
|
paletteDistance,
|
|
19526
20125
|
parentAssetId,
|
|
19527
20126
|
parseLock,
|
|
20127
|
+
parseMcpBody,
|
|
19528
20128
|
parseProFlashQuote,
|
|
19529
20129
|
parseTerrainDescriptions,
|
|
20130
|
+
parseVfxExport,
|
|
20131
|
+
parseVfxRecipe,
|
|
20132
|
+
parseVfxStep,
|
|
19530
20133
|
parseWorkspace,
|
|
19531
20134
|
pickHistory,
|
|
19532
20135
|
pixelateFile,
|
|
@@ -19546,6 +20149,8 @@ async function workspaceStatus(ws, dir) {
|
|
|
19546
20149
|
readArtifactBundleManifest,
|
|
19547
20150
|
readHandEditCompanion,
|
|
19548
20151
|
readQualityBaseline,
|
|
20152
|
+
readSavedVfxRecipe,
|
|
20153
|
+
readVfxRecipe,
|
|
19549
20154
|
recipeDigest,
|
|
19550
20155
|
referencedHashes,
|
|
19551
20156
|
refineAsset,
|
|
@@ -19576,6 +20181,7 @@ async function workspaceStatus(ws, dir) {
|
|
|
19576
20181
|
resolveSpecs,
|
|
19577
20182
|
resolveStyleImages,
|
|
19578
20183
|
resolveStyleOutputs,
|
|
20184
|
+
resolveVfxSprite,
|
|
19579
20185
|
resumeActions,
|
|
19580
20186
|
resumeCommandForStatus,
|
|
19581
20187
|
retireGeneration,
|
|
@@ -19583,6 +20189,7 @@ async function workspaceStatus(ws, dir) {
|
|
|
19583
20189
|
revertGeneration,
|
|
19584
20190
|
runPicker,
|
|
19585
20191
|
runSalvage,
|
|
20192
|
+
runVfxWorkbench,
|
|
19586
20193
|
saveHandEdit,
|
|
19587
20194
|
saveLock,
|
|
19588
20195
|
saveWorkspace,
|
|
@@ -19593,6 +20200,7 @@ async function workspaceStatus(ws, dir) {
|
|
|
19593
20200
|
sha256File,
|
|
19594
20201
|
shouldPersistSourceUrl,
|
|
19595
20202
|
shouldRetry,
|
|
20203
|
+
sliceVfxSheet,
|
|
19596
20204
|
slugify,
|
|
19597
20205
|
snapshotQualityBaseline,
|
|
19598
20206
|
sourceIsStem,
|
|
@@ -19617,6 +20225,8 @@ async function workspaceStatus(ws, dir) {
|
|
|
19617
20225
|
validateWorkspace,
|
|
19618
20226
|
verifyArtifactBundle,
|
|
19619
20227
|
verifyRecipe,
|
|
20228
|
+
vfxOutputPaths,
|
|
20229
|
+
vfxRecipeSha256,
|
|
19620
20230
|
withArtifactManifest,
|
|
19621
20231
|
workspaceClaims,
|
|
19622
20232
|
workspaceStatus,
|