pixelkiln 0.86.3 → 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 +12 -11
- package/README.md +7 -7
- package/dist/cli.js +1382 -602
- package/dist/cli.js.map +1 -1
- package/dist/client/gallery.js +4 -4
- package/dist/index.cjs +1361 -654
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +897 -124
- package/dist/index.d.ts +897 -124
- package/dist/index.js +1259 -581
- package/dist/index.js.map +1 -1
- package/docs/CHARACTERS.md +2 -2
- package/docs/CLI.md +102 -7
- package/docs/ENDPOINTS.md +142 -11
- package/docs/GENERATORS.md +84 -14
- package/docs/MANIFEST.md +4 -3
- package/docs/PIXELLAB.md +7 -6
- package/docs/REVISIONS.md +2 -2
- package/package.json +1 -1
- package/schema/manifest.schema.json +47 -2
- package/schema/recipe.schema.json +2 -1
- package/skills/pixelkiln/SKILL.md +11 -3
- package/skills/pixelkiln/references/pixellab-roadmap.md +31 -4
- package/skills/pixelkiln/references/pixellab.md +26 -13
- package/skills/pixelkiln/references/revisions.md +2 -2
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,
|
|
@@ -86,11 +87,16 @@ __export(index_exports, {
|
|
|
86
87
|
MapSceneSchema: () => MapSceneSchema,
|
|
87
88
|
MediaType: () => MediaType,
|
|
88
89
|
OverwriteRefusedError: () => OverwriteRefusedError,
|
|
90
|
+
PIXELATE_COST_GENERATIONS: () => PIXELATE_COST_GENERATIONS,
|
|
91
|
+
PIXELATE_MAX_SIDE: () => PIXELATE_MAX_SIDE,
|
|
92
|
+
PIXELLAB_MCP_URL: () => PIXELLAB_MCP_URL,
|
|
89
93
|
PIXEL_ART_FIXER_REVISION: () => PIXEL_ART_FIXER_REVISION,
|
|
90
94
|
PIXEL_ART_FIXER_URL: () => PIXEL_ART_FIXER_URL,
|
|
95
|
+
PRO_TIER_PRICES: () => PRO_TIER_PRICES,
|
|
91
96
|
PixelKilnError: () => PixelKilnError,
|
|
92
97
|
PixelLabClient: () => PixelLabClient,
|
|
93
98
|
PixelLabError: () => PixelLabError,
|
|
99
|
+
PixelLabMcpClient: () => PixelLabMcpClient,
|
|
94
100
|
PixelLabProvider: () => PixelLabProvider,
|
|
95
101
|
PostprocessSchema: () => PostprocessSchema,
|
|
96
102
|
ProjectError: () => ProjectError,
|
|
@@ -113,12 +119,15 @@ __export(index_exports, {
|
|
|
113
119
|
UiPieceSchema: () => UiPieceSchema,
|
|
114
120
|
UnsupportedCapabilityError: () => UnsupportedCapabilityError,
|
|
115
121
|
UsageError: () => UsageError,
|
|
122
|
+
VFX_TOOL: () => VFX_TOOL,
|
|
123
|
+
VfxPartSchema: () => VfxPartSchema,
|
|
116
124
|
WorkspaceProjectSchema: () => WorkspaceProjectSchema,
|
|
117
125
|
WorkspaceSchema: () => WorkspaceSchema,
|
|
118
126
|
adopt: () => adopt,
|
|
119
127
|
applyManifestEdit: () => applyManifestEdit,
|
|
120
128
|
applyTags: () => applyTags,
|
|
121
129
|
approveQualityRecord: () => approveQualityRecord,
|
|
130
|
+
assetInStyle: () => assetInStyle,
|
|
122
131
|
auditStyle: () => auditStyle,
|
|
123
132
|
availableProviders: () => availableProviders,
|
|
124
133
|
backoffMs: () => backoffMs,
|
|
@@ -143,7 +152,9 @@ __export(index_exports, {
|
|
|
143
152
|
currentEntryOutputPath: () => currentEntryOutputPath,
|
|
144
153
|
currentOutputPath: () => currentOutputPath,
|
|
145
154
|
defaultOpenCommand: () => defaultOpenCommand,
|
|
155
|
+
defaultPixelateOut: () => defaultPixelateOut,
|
|
146
156
|
defaultUnzoomOut: () => defaultUnzoomOut,
|
|
157
|
+
describeVfx: () => describeVfx,
|
|
147
158
|
detachHandEdit: () => detachHandEdit,
|
|
148
159
|
detectMediaType: () => detectMediaType,
|
|
149
160
|
doctor: () => doctor,
|
|
@@ -164,6 +175,7 @@ __export(index_exports, {
|
|
|
164
175
|
fontOutputPaths: () => fontOutputPaths,
|
|
165
176
|
formatCost: () => formatCost,
|
|
166
177
|
frameSetFps: () => frameSetFps,
|
|
178
|
+
frameSetLoop: () => frameSetLoop,
|
|
167
179
|
galleryContentSecurityPolicy: () => galleryContentSecurityPolicy,
|
|
168
180
|
galleryMediaId: () => galleryMediaId,
|
|
169
181
|
galleryMediaRoute: () => galleryMediaRoute,
|
|
@@ -186,9 +198,11 @@ __export(index_exports, {
|
|
|
186
198
|
inspectRevisionReadiness: () => inspectRevisionReadiness,
|
|
187
199
|
installEditor: () => installEditor,
|
|
188
200
|
installRecipe: () => installRecipe,
|
|
201
|
+
isBusyReply: () => isBusyReply,
|
|
189
202
|
isFrameSetEntry: () => isFrameSetEntry,
|
|
190
203
|
isMemberSetEntry: () => isMemberSetEntry,
|
|
191
204
|
isSensitiveSourceUrl: () => isSensitiveSourceUrl,
|
|
205
|
+
isVfxReference: () => isVfxReference,
|
|
192
206
|
isometricTileCost: () => isometricTileCost,
|
|
193
207
|
listBundledRecipes: () => listBundledRecipes,
|
|
194
208
|
loadClaims: () => loadClaims,
|
|
@@ -197,7 +211,9 @@ __export(index_exports, {
|
|
|
197
211
|
loadSiblingManifests: () => loadSiblingManifests,
|
|
198
212
|
loadWorkspace: () => loadWorkspace,
|
|
199
213
|
lockKey: () => lockKey,
|
|
214
|
+
makeVfx: () => makeVfx,
|
|
200
215
|
matchOrphanStyle: () => matchOrphanStyle,
|
|
216
|
+
mcpClientFromEnv: () => mcpClientFromEnv,
|
|
201
217
|
measureBalanceChange: () => measureBalanceChange,
|
|
202
218
|
measureDecodedImageQuality: () => measureDecodedImageQuality,
|
|
203
219
|
measureImageQuality: () => measureImageQuality,
|
|
@@ -221,15 +237,21 @@ __export(index_exports, {
|
|
|
221
237
|
paletteDistance: () => paletteDistance,
|
|
222
238
|
parentAssetId: () => parentAssetId,
|
|
223
239
|
parseLock: () => parseLock,
|
|
240
|
+
parseMcpBody: () => parseMcpBody,
|
|
224
241
|
parseProFlashQuote: () => parseProFlashQuote,
|
|
225
242
|
parseTerrainDescriptions: () => parseTerrainDescriptions,
|
|
243
|
+
parseVfxExport: () => parseVfxExport,
|
|
244
|
+
parseVfxRecipe: () => parseVfxRecipe,
|
|
245
|
+
parseVfxStep: () => parseVfxStep,
|
|
226
246
|
parseWorkspace: () => parseWorkspace,
|
|
227
247
|
pickHistory: () => pickHistory,
|
|
248
|
+
pixelateFile: () => pixelateFile,
|
|
228
249
|
pngSize: () => pngSize,
|
|
229
250
|
poll: () => poll,
|
|
230
251
|
portableOutputPath: () => portableOutputPath,
|
|
231
252
|
prepareReview: () => prepareReview,
|
|
232
253
|
primaryOutput: () => primaryOutput,
|
|
254
|
+
proTierCost: () => proTierCost,
|
|
233
255
|
proportionsPayload: () => proportionsPayload,
|
|
234
256
|
providerCredentialEnvs: () => providerCredentialEnvs,
|
|
235
257
|
providerFactory: () => providerFactory,
|
|
@@ -240,6 +262,8 @@ __export(index_exports, {
|
|
|
240
262
|
readArtifactBundleManifest: () => readArtifactBundleManifest,
|
|
241
263
|
readHandEditCompanion: () => readHandEditCompanion,
|
|
242
264
|
readQualityBaseline: () => readQualityBaseline,
|
|
265
|
+
readSavedVfxRecipe: () => readSavedVfxRecipe,
|
|
266
|
+
readVfxRecipe: () => readVfxRecipe,
|
|
243
267
|
recipeDigest: () => recipeDigest,
|
|
244
268
|
referencedHashes: () => referencedHashes,
|
|
245
269
|
refineAsset: () => refineAsset,
|
|
@@ -270,6 +294,7 @@ __export(index_exports, {
|
|
|
270
294
|
resolveSpecs: () => resolveSpecs,
|
|
271
295
|
resolveStyleImages: () => resolveStyleImages,
|
|
272
296
|
resolveStyleOutputs: () => resolveStyleOutputs,
|
|
297
|
+
resolveVfxSprite: () => resolveVfxSprite,
|
|
273
298
|
resumeActions: () => resumeActions,
|
|
274
299
|
resumeCommandForStatus: () => resumeCommandForStatus,
|
|
275
300
|
retireGeneration: () => retireGeneration,
|
|
@@ -277,6 +302,7 @@ __export(index_exports, {
|
|
|
277
302
|
revertGeneration: () => revertGeneration,
|
|
278
303
|
runPicker: () => runPicker,
|
|
279
304
|
runSalvage: () => runSalvage,
|
|
305
|
+
runVfxWorkbench: () => runVfxWorkbench,
|
|
280
306
|
saveHandEdit: () => saveHandEdit,
|
|
281
307
|
saveLock: () => saveLock,
|
|
282
308
|
saveWorkspace: () => saveWorkspace,
|
|
@@ -287,6 +313,7 @@ __export(index_exports, {
|
|
|
287
313
|
sha256File: () => sha256File,
|
|
288
314
|
shouldPersistSourceUrl: () => shouldPersistSourceUrl,
|
|
289
315
|
shouldRetry: () => shouldRetry,
|
|
316
|
+
sliceVfxSheet: () => sliceVfxSheet,
|
|
290
317
|
slugify: () => slugify,
|
|
291
318
|
snapshotQualityBaseline: () => snapshotQualityBaseline,
|
|
292
319
|
sourceIsStem: () => sourceIsStem,
|
|
@@ -311,6 +338,8 @@ __export(index_exports, {
|
|
|
311
338
|
validateWorkspace: () => validateWorkspace,
|
|
312
339
|
verifyArtifactBundle: () => verifyArtifactBundle,
|
|
313
340
|
verifyRecipe: () => verifyRecipe,
|
|
341
|
+
vfxOutputPaths: () => vfxOutputPaths,
|
|
342
|
+
vfxRecipeSha256: () => vfxRecipeSha256,
|
|
314
343
|
withArtifactManifest: () => withArtifactManifest,
|
|
315
344
|
workspaceClaims: () => workspaceClaims,
|
|
316
345
|
workspaceStatus: () => workspaceStatus,
|
|
@@ -326,7 +355,7 @@ var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
|
|
|
326
355
|
// src/types.ts
|
|
327
356
|
var import_zod = require("zod");
|
|
328
357
|
var MediaTypeSchema = import_zod.z.enum(["image/png", "image/gif"]);
|
|
329
|
-
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"]);
|
|
330
359
|
var GridConfidenceSchema = import_zod.z.enum(["low", "medium", "high"]);
|
|
331
360
|
var RevisionModeSchema = import_zod.z.enum([
|
|
332
361
|
"image-to-image",
|
|
@@ -359,18 +388,26 @@ function candidateCount(size) {
|
|
|
359
388
|
return 1;
|
|
360
389
|
}
|
|
361
390
|
function generationCost(width, height, generator = "map") {
|
|
391
|
+
if (generator === "vfx") return 0;
|
|
362
392
|
if (generator === "map" || generator === "pixflux") return 1;
|
|
363
|
-
if (generator === "imagePro") return
|
|
393
|
+
if (generator === "imagePro") return proTierCost(width, height);
|
|
364
394
|
const px = width * height;
|
|
365
|
-
if (px <= 1024) return
|
|
366
|
-
if (px <= 2048) return
|
|
367
|
-
return
|
|
395
|
+
if (px <= 1024) return PRO_TIER_PRICES[0];
|
|
396
|
+
if (px <= 2048) return PRO_TIER_PRICES[1];
|
|
397
|
+
return PRO_TIER_PRICES[2];
|
|
398
|
+
}
|
|
399
|
+
var PRO_TIER_PRICES = [10, 15, 25];
|
|
400
|
+
function proTierCost(width, height) {
|
|
401
|
+
const px = width * height;
|
|
402
|
+
if (px <= 65536) return PRO_TIER_PRICES[0];
|
|
403
|
+
if (px <= 102400) return PRO_TIER_PRICES[1];
|
|
404
|
+
return PRO_TIER_PRICES[2];
|
|
368
405
|
}
|
|
369
406
|
function tilesCost(tileSize, variations) {
|
|
370
407
|
const px = tileSize * tileSize * Math.max(1, variations);
|
|
371
|
-
if (px <= 1024) return
|
|
372
|
-
if (px <= 2048) return
|
|
373
|
-
return
|
|
408
|
+
if (px <= 1024) return PRO_TIER_PRICES[0];
|
|
409
|
+
if (px <= 2048) return PRO_TIER_PRICES[1];
|
|
410
|
+
return PRO_TIER_PRICES[2];
|
|
374
411
|
}
|
|
375
412
|
function isometricTileCost() {
|
|
376
413
|
return 1;
|
|
@@ -536,6 +573,29 @@ var MapSceneSchema = import_zod.z.object({
|
|
|
536
573
|
image: import_zod.z.string().min(1),
|
|
537
574
|
placement: MapScenePlacementSchema.default({ oval: 0.3 })
|
|
538
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
|
+
});
|
|
539
599
|
var CharacterOutfitSchema = import_zod.z.object({
|
|
540
600
|
/** The loop (a character animation asset) to re-clothe, in the same style. */
|
|
541
601
|
of: import_zod.z.string().min(1),
|
|
@@ -886,7 +946,7 @@ var MEMBER_SET_REVISION_MODES = ["reduce-colors", "correct-pixelart", "edit-anim
|
|
|
886
946
|
var StyleObjectSchema = import_zod.z.object({
|
|
887
947
|
/** Generation backend for this style. Omit to inherit the manifest default. */
|
|
888
948
|
provider: import_zod.z.string().min(1).optional(),
|
|
889
|
-
// `map` is the default because it is
|
|
949
|
+
// `map` is the default because it is 10-25x cheaper and correct for any
|
|
890
950
|
// asset that is not going to be rotated or animated.
|
|
891
951
|
generator: GeneratorSchema.default("map"),
|
|
892
952
|
/** Square edge length. The active provider owns its exact limit. */
|
|
@@ -1169,7 +1229,7 @@ var StyleSchema = StyleObjectSchema.refine((s) => !(s.tileFeature && s.styleImag
|
|
|
1169
1229
|
message: "tileFeature and styleImages cannot be combined; a connectable set derives its own tile geometry, so remove one or the other",
|
|
1170
1230
|
path: ["tileFeature"]
|
|
1171
1231
|
}).superRefine((style, ctx) => {
|
|
1172
|
-
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")) {
|
|
1173
1233
|
ctx.addIssue({
|
|
1174
1234
|
code: import_zod.z.ZodIssueCode.custom,
|
|
1175
1235
|
message: "quality profiles currently support single-image generators only",
|
|
@@ -1299,6 +1359,8 @@ var AssetSchema = import_zod.z.object({
|
|
|
1299
1359
|
outfit: CharacterOutfitSchema.optional(),
|
|
1300
1360
|
/** `map` styles only: draw this object in the style of a scene image. See `MapSceneSchema`. */
|
|
1301
1361
|
scene: MapSceneSchema.optional(),
|
|
1362
|
+
/** `vfx` styles only: the effect to render. See `AssetVfxSchema`. */
|
|
1363
|
+
vfx: AssetVfxSchema.optional(),
|
|
1302
1364
|
/** `character` styles, `standard` humanoid bases: this character's proportions, over the style's. */
|
|
1303
1365
|
proportions: CharacterProportionsSchema.optional(),
|
|
1304
1366
|
/**
|
|
@@ -1375,7 +1437,7 @@ var AssetSchema = import_zod.z.object({
|
|
|
1375
1437
|
path: ["revision"]
|
|
1376
1438
|
});
|
|
1377
1439
|
}
|
|
1378
|
-
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);
|
|
1379
1441
|
if (shapes.length > 1) {
|
|
1380
1442
|
context.addIssue({
|
|
1381
1443
|
code: import_zod.z.ZodIssueCode.custom,
|
|
@@ -1390,7 +1452,7 @@ var AssetSchema = import_zod.z.object({
|
|
|
1390
1452
|
path: ["mirror"]
|
|
1391
1453
|
});
|
|
1392
1454
|
}
|
|
1393
|
-
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) {
|
|
1394
1456
|
context.addIssue({
|
|
1395
1457
|
code: import_zod.z.ZodIssueCode.custom,
|
|
1396
1458
|
message: "Required",
|
|
@@ -2150,9 +2212,9 @@ var PixelLabClient = class {
|
|
|
2150
2212
|
apiKey;
|
|
2151
2213
|
http;
|
|
2152
2214
|
/** Retries transport failures, 429, and 5xx; see http.ts for the policy. */
|
|
2153
|
-
async request(
|
|
2215
|
+
async request(path39, init) {
|
|
2154
2216
|
const auth = this.apiKey.startsWith("Bearer ") ? this.apiKey : `Bearer ${this.apiKey}`;
|
|
2155
|
-
const res = await this.http(`${BASE}${
|
|
2217
|
+
const res = await this.http(`${BASE}${path39}`, {
|
|
2156
2218
|
...init,
|
|
2157
2219
|
headers: {
|
|
2158
2220
|
Authorization: auth,
|
|
@@ -2162,13 +2224,13 @@ var PixelLabClient = class {
|
|
|
2162
2224
|
});
|
|
2163
2225
|
const text = await res.text();
|
|
2164
2226
|
if (!res.ok) {
|
|
2165
|
-
throw new PixelLabError(`${init?.method ?? "GET"} ${
|
|
2227
|
+
throw new PixelLabError(`${init?.method ?? "GET"} ${path39} \u2192 ${res.status}`, res.status, text);
|
|
2166
2228
|
}
|
|
2167
2229
|
if (!text) return {};
|
|
2168
2230
|
try {
|
|
2169
2231
|
return JSON.parse(text);
|
|
2170
2232
|
} catch {
|
|
2171
|
-
throw new Error(`${init?.method ?? "GET"} ${
|
|
2233
|
+
throw new Error(`${init?.method ?? "GET"} ${path39} returned invalid JSON`);
|
|
2172
2234
|
}
|
|
2173
2235
|
}
|
|
2174
2236
|
async balance() {
|
|
@@ -2573,10 +2635,10 @@ var PixelLabClient = class {
|
|
|
2573
2635
|
const palette = args.paletteSwatchBase64 ? { color_image: { type: "base64", base64: args.paletteSwatchBase64, format: "png" }, force_colors: true } : {};
|
|
2574
2636
|
const encode = (image) => ({ type: "base64", base64: image.base64, format: image.format });
|
|
2575
2637
|
const south = args.reference?.south;
|
|
2576
|
-
let
|
|
2638
|
+
let path39;
|
|
2577
2639
|
let body;
|
|
2578
2640
|
if (args.mode === "v3") {
|
|
2579
|
-
|
|
2641
|
+
path39 = "/create-character-v3";
|
|
2580
2642
|
body = {
|
|
2581
2643
|
description: args.description,
|
|
2582
2644
|
image_size: imageSize,
|
|
@@ -2590,7 +2652,7 @@ var PixelLabClient = class {
|
|
|
2590
2652
|
...args.enhancePrompt ? { enhance_prompt: true } : {}
|
|
2591
2653
|
};
|
|
2592
2654
|
} else if (args.mode === "pro-flash") {
|
|
2593
|
-
|
|
2655
|
+
path39 = "/create-character-pro-flash";
|
|
2594
2656
|
const traits = args.styleTraits;
|
|
2595
2657
|
body = {
|
|
2596
2658
|
description: args.description,
|
|
@@ -2614,7 +2676,7 @@ var PixelLabClient = class {
|
|
|
2614
2676
|
} : {}
|
|
2615
2677
|
};
|
|
2616
2678
|
} else if (args.mode === "pro") {
|
|
2617
|
-
|
|
2679
|
+
path39 = "/create-character-pro";
|
|
2618
2680
|
body = {
|
|
2619
2681
|
description: args.description,
|
|
2620
2682
|
image_size: imageSize,
|
|
@@ -2628,7 +2690,7 @@ var PixelLabClient = class {
|
|
|
2628
2690
|
...!south && args.styleCharacterId ? { style_character_id: args.styleCharacterId } : {}
|
|
2629
2691
|
};
|
|
2630
2692
|
} else {
|
|
2631
|
-
|
|
2693
|
+
path39 = args.directions === 4 ? "/create-character-with-4-directions" : "/create-character-with-8-directions";
|
|
2632
2694
|
body = {
|
|
2633
2695
|
description: args.description,
|
|
2634
2696
|
image_size: imageSize,
|
|
@@ -2645,8 +2707,8 @@ var PixelLabClient = class {
|
|
|
2645
2707
|
...palette
|
|
2646
2708
|
};
|
|
2647
2709
|
}
|
|
2648
|
-
const raw = await this.request(
|
|
2649
|
-
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));
|
|
2650
2712
|
}
|
|
2651
2713
|
/** A text edit of an existing character, applied to every direction and stored as a sibling. */
|
|
2652
2714
|
async createCharacterState(args) {
|
|
@@ -2709,7 +2771,8 @@ var PixelLabClient = class {
|
|
|
2709
2771
|
* A full-body sprite in, a bust portrait out (or the reverse, unused
|
|
2710
2772
|
* today). Job-based like a state or `1dir`, not the couple-generations
|
|
2711
2773
|
* "conversion" the name suggests: the smallest `resultSize` (16px) billed
|
|
2712
|
-
* 20 generations live (docs/ENDPOINTS.md, "Characters, measured")
|
|
2774
|
+
* 20 generations live (docs/ENDPOINTS.md, "Characters, measured"), the
|
|
2775
|
+
* Pro floor before PixelLab's October 2026 cut put it at 10.
|
|
2713
2776
|
*/
|
|
2714
2777
|
async createPortraitCharacterPro(args) {
|
|
2715
2778
|
const raw = await this.request("/portrait-character-pro", {
|
|
@@ -2748,7 +2811,8 @@ var PixelLabClient = class {
|
|
|
2748
2811
|
* everything else here, but the completed job returns the result frames
|
|
2749
2812
|
* inline as base64 (`last_response.quantized_images`), not as storage
|
|
2750
2813
|
* URLs — the one PixelLab character tool that works this way. A live
|
|
2751
|
-
* 2-frame, 92x92 job billed 20 generations (docs/ENDPOINTS.md)
|
|
2814
|
+
* 2-frame, 92x92 job billed 20 generations (docs/ENDPOINTS.md), before
|
|
2815
|
+
* PixelLab's October 2026 Pro cut halved that floor to 10.
|
|
2752
2816
|
*/
|
|
2753
2817
|
async transferOutfitV2(args) {
|
|
2754
2818
|
const image = (img) => ({
|
|
@@ -2798,7 +2862,9 @@ var PixelLabClient = class {
|
|
|
2798
2862
|
* far higher than this tiering assumes; 352x352 (123904px²), 384x384
|
|
2799
2863
|
* (147456px²), and 512x512 (262144px², the tiering ceiling) all billed
|
|
2800
2864
|
* 40. Both breakpoints are now tightly bracketed: 20->25 in
|
|
2801
|
-
* (65536px², 82944px²], 25->40 in (102400px², 123904px²].
|
|
2865
|
+
* (65536px², 82944px²], 25->40 in (102400px², 123904px²]. Those prices
|
|
2866
|
+
* predate PixelLab's October 2026 Pro cut to 10/15/25; `proTierCost`
|
|
2867
|
+
* keeps the breakpoints with the new prices.
|
|
2802
2868
|
* `editImagesV2` below returns the same fields under `images`, plural
|
|
2803
2869
|
* and array-wrapped, not `image` — do not assume the two endpoints share
|
|
2804
2870
|
* one response shape. See docs/ENDPOINTS.md and docs/REVISIONS.md for
|
|
@@ -2851,8 +2917,9 @@ var PixelLabClient = class {
|
|
|
2851
2917
|
);
|
|
2852
2918
|
}
|
|
2853
2919
|
/**
|
|
2854
|
-
* PixelLab's Pro image tier, `/generate-image-v2`:
|
|
2855
|
-
*
|
|
2920
|
+
* PixelLab's Pro image tier, `/generate-image-v2`: 10 to 25 generations by
|
|
2921
|
+
* canvas since PixelLab's October 2026 Pro cut, a flat 40 before
|
|
2922
|
+
* (docs/ENDPOINTS.md, "Single-image generators, measured"), for real style
|
|
2856
2923
|
* transfer and non-square canvases up to 792x688, where `pixflux` is
|
|
2857
2924
|
* limited to 400x400 and no style reference. Like a revision, this hands
|
|
2858
2925
|
* back a plain background job with no resource of its own; unlike a
|
|
@@ -2938,6 +3005,24 @@ var PixelLabClient = class {
|
|
|
2938
3005
|
"inpaint-image-pro-flash"
|
|
2939
3006
|
);
|
|
2940
3007
|
}
|
|
3008
|
+
/**
|
|
3009
|
+
* `/image-to-pixelart-pro-flash`: a photo, painting, or render in, pixel
|
|
3010
|
+
* art out, with no size to pass: PixelLab detects the native pixel scale
|
|
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
|
|
3013
|
+
* in October 2026. Sources over 2048px on the longer side are scaled down
|
|
3014
|
+
* upstream. The finished job carries the image at `last_response.images[0]`.
|
|
3015
|
+
*/
|
|
3016
|
+
async imageToPixelartProFlash(args) {
|
|
3017
|
+
const body = { image: args.image };
|
|
3018
|
+
if (args.description) body.description = args.description;
|
|
3019
|
+
if (args.seed != null) body.seed = args.seed;
|
|
3020
|
+
return validateResponse(
|
|
3021
|
+
ProFlashImageSubmitSchema,
|
|
3022
|
+
await this.request("/image-to-pixelart-pro-flash", { method: "POST", body: JSON.stringify(body) }),
|
|
3023
|
+
"image-to-pixelart-pro-flash"
|
|
3024
|
+
);
|
|
3025
|
+
}
|
|
2941
3026
|
/**
|
|
2942
3027
|
* `/generate-ui-v2` ("Generate UI (Pro)"): one UI element (a button, a
|
|
2943
3028
|
* health bar, an inventory slot, a dialogue box) from a description,
|
|
@@ -3848,10 +3933,10 @@ function imageMetadata(buf) {
|
|
|
3848
3933
|
}
|
|
3849
3934
|
|
|
3850
3935
|
// src/providers/pixellab.ts
|
|
3851
|
-
var
|
|
3936
|
+
var import_node_fs3 = require("fs");
|
|
3852
3937
|
var import_node_crypto2 = require("crypto");
|
|
3853
3938
|
var import_node_os = __toESM(require("os"), 1);
|
|
3854
|
-
var
|
|
3939
|
+
var import_node_path2 = __toESM(require("path"), 1);
|
|
3855
3940
|
|
|
3856
3941
|
// src/hash.ts
|
|
3857
3942
|
var import_node_crypto = require("crypto");
|
|
@@ -3859,16 +3944,17 @@ var import_node_fs = require("fs");
|
|
|
3859
3944
|
function sha256(data) {
|
|
3860
3945
|
return (0, import_node_crypto.createHash)("sha256").update(data).digest("hex");
|
|
3861
3946
|
}
|
|
3862
|
-
async function sha256File(
|
|
3947
|
+
async function sha256File(path39) {
|
|
3863
3948
|
return new Promise((resolve, reject) => {
|
|
3864
3949
|
const hash = (0, import_node_crypto.createHash)("sha256");
|
|
3865
|
-
const stream = (0, import_node_fs.createReadStream)(
|
|
3950
|
+
const stream = (0, import_node_fs.createReadStream)(path39);
|
|
3866
3951
|
stream.on("data", (chunk2) => hash.update(chunk2));
|
|
3867
3952
|
stream.on("error", reject);
|
|
3868
3953
|
stream.on("end", () => resolve(hash.digest("hex")));
|
|
3869
3954
|
});
|
|
3870
3955
|
}
|
|
3871
3956
|
function specHash(spec, styleImageHashes, providerOptionIdentity = spec.providerOptions, providerInputIdentity = spec.providerInputs ?? {}) {
|
|
3957
|
+
const effect = Boolean(spec.vfx);
|
|
3872
3958
|
return sha256(
|
|
3873
3959
|
JSON.stringify({
|
|
3874
3960
|
// Preserve every existing PixelLab hash while making a provider switch
|
|
@@ -3877,15 +3963,15 @@ function specHash(spec, styleImageHashes, providerOptionIdentity = spec.provider
|
|
|
3877
3963
|
providerOptions: providerOptionIdentity && (typeof providerOptionIdentity !== "object" || Object.keys(providerOptionIdentity).length > 0) ? providerOptionIdentity : void 0,
|
|
3878
3964
|
providerInputs: providerInputIdentity && (typeof providerInputIdentity !== "object" || Object.keys(providerInputIdentity).length > 0) ? providerInputIdentity : void 0,
|
|
3879
3965
|
generator: spec.generator,
|
|
3880
|
-
prompt: spec.prompt,
|
|
3881
|
-
width: spec.width,
|
|
3882
|
-
height: spec.height,
|
|
3883
|
-
view: spec.view,
|
|
3884
|
-
outline: spec.outline ?? null,
|
|
3885
|
-
shading: spec.shading ?? null,
|
|
3886
|
-
detail: spec.detail ?? null,
|
|
3887
|
-
seed: spec.seed ?? null,
|
|
3888
|
-
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,
|
|
3889
3975
|
// `noBackground` only reaches the wire for pixflux and uiAsset; the
|
|
3890
3976
|
// tile fields are undefined for every other generator. `tileSize` is
|
|
3891
3977
|
// intentionally absent; width/height are derived from it, so it is
|
|
@@ -3960,6 +4046,15 @@ function specHash(spec, styleImageHashes, providerOptionIdentity = spec.provider
|
|
|
3960
4046
|
styleTraits: spec.objectPro.styleTraits,
|
|
3961
4047
|
reference: spec.objectPro.reference?.sha256
|
|
3962
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,
|
|
3963
4058
|
// A mirror's bytes come from its source's recorded outputs; the plan
|
|
3964
4059
|
// compares those directly, so only the choice of source is identity.
|
|
3965
4060
|
mirror: spec.mirror ? { of: spec.mirror.sourceAssetId } : void 0,
|
|
@@ -3997,6 +4092,337 @@ function specHash(spec, styleImageHashes, providerOptionIdentity = spec.provider
|
|
|
3997
4092
|
);
|
|
3998
4093
|
}
|
|
3999
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
|
+
|
|
4000
4426
|
// src/providers/pixellab.ts
|
|
4001
4427
|
function pixelLabObjectUrl(generator, objectId) {
|
|
4002
4428
|
if (!objectId) return null;
|
|
@@ -4011,6 +4437,7 @@ function pixelLabObjectUrl(generator, objectId) {
|
|
|
4011
4437
|
const characterId = objectId.split("#")[0];
|
|
4012
4438
|
return characterId ? `https://www.pixellab.ai/create-character/${encodeURIComponent(characterId)}` : null;
|
|
4013
4439
|
}
|
|
4440
|
+
if (generator === "vfx") return `https://pixellab.ai/create-vfx?effect=${encodeURIComponent(objectId)}`;
|
|
4014
4441
|
return null;
|
|
4015
4442
|
}
|
|
4016
4443
|
function billedFromUsage(usage) {
|
|
@@ -4034,7 +4461,7 @@ function characterCost(spec) {
|
|
|
4034
4461
|
}
|
|
4035
4462
|
if (character.kind === "state" || character.kind === "portrait") return generationCost(spec.width, spec.height, "1dir");
|
|
4036
4463
|
if (character.kind === "outfit") {
|
|
4037
|
-
return
|
|
4464
|
+
return PRO_TIER_PRICES[0];
|
|
4038
4465
|
}
|
|
4039
4466
|
if (character.mode === "standard") return 1;
|
|
4040
4467
|
if (character.mode === "v3") {
|
|
@@ -4127,13 +4554,20 @@ function decodeObjectAnimationJob(jobId) {
|
|
|
4127
4554
|
return null;
|
|
4128
4555
|
}
|
|
4129
4556
|
var PixelLabProvider = class _PixelLabProvider {
|
|
4130
|
-
|
|
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) {
|
|
4131
4563
|
this.client = client2;
|
|
4564
|
+
this.mcp = mcp;
|
|
4132
4565
|
}
|
|
4133
4566
|
client;
|
|
4567
|
+
mcp;
|
|
4134
4568
|
id = "pixellab";
|
|
4135
4569
|
static fromEnv() {
|
|
4136
|
-
return new _PixelLabProvider(clientFromEnv());
|
|
4570
|
+
return new _PixelLabProvider(clientFromEnv(), mcpClientFromEnv());
|
|
4137
4571
|
}
|
|
4138
4572
|
/** Public storage URLs and the local content cache do not require API auth. */
|
|
4139
4573
|
static forDownloads() {
|
|
@@ -4144,7 +4578,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
4144
4578
|
return new _PixelLabProvider(new PixelLabClient("download-only"));
|
|
4145
4579
|
}
|
|
4146
4580
|
supports(generator) {
|
|
4147
|
-
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";
|
|
4148
4582
|
}
|
|
4149
4583
|
/** `/map-objects` style matching: `background_image` plus `inpainting`. */
|
|
4150
4584
|
supportsMapScene = true;
|
|
@@ -4186,11 +4620,12 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
4186
4620
|
* is a file read.
|
|
4187
4621
|
*/
|
|
4188
4622
|
static cacheDir(namespace = "pixflux") {
|
|
4189
|
-
const dir =
|
|
4190
|
-
(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 });
|
|
4191
4625
|
return dir;
|
|
4192
4626
|
}
|
|
4193
4627
|
estimate(spec) {
|
|
4628
|
+
if (spec.vfx) return { unit: "generations", amount: 0, candidates: 1 };
|
|
4194
4629
|
if (spec.batch?.role === "member") {
|
|
4195
4630
|
return { unit: "generations", amount: 0, candidates: 1 };
|
|
4196
4631
|
}
|
|
@@ -4227,7 +4662,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
4227
4662
|
if (spec.revision) {
|
|
4228
4663
|
const width = spec.revision.sourceWidth ?? spec.width;
|
|
4229
4664
|
const height = spec.revision.sourceHeight ?? spec.height;
|
|
4230
|
-
return { unit: "generations", amount:
|
|
4665
|
+
return { unit: "generations", amount: proTierCost(width, height), candidates: 1 };
|
|
4231
4666
|
}
|
|
4232
4667
|
if (spec.generator === "tiles" || spec.generator === "terrain" || spec.generator === "isometricTile") {
|
|
4233
4668
|
return { unit: "generations", amount: spec.cost, candidates: spec.candidates };
|
|
@@ -4248,6 +4683,9 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
4248
4683
|
};
|
|
4249
4684
|
}
|
|
4250
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
|
+
}
|
|
4251
4689
|
const members = spec.revision?.sourceMembers;
|
|
4252
4690
|
if (spec.revision && members && !MEMBER_SET_REVISION_MODES.includes(spec.revision.mode)) {
|
|
4253
4691
|
throw new Error(
|
|
@@ -4532,7 +4970,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
4532
4970
|
}
|
|
4533
4971
|
}
|
|
4534
4972
|
if (endFrame) {
|
|
4535
|
-
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);
|
|
4536
4974
|
if (start && (start.width !== endFrame.width || start.height !== endFrame.height)) {
|
|
4537
4975
|
throw new Error(
|
|
4538
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`
|
|
@@ -4690,7 +5128,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
4690
5128
|
}
|
|
4691
5129
|
}
|
|
4692
5130
|
if (endFrame) {
|
|
4693
|
-
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);
|
|
4694
5132
|
if (start && (start.width !== endFrame.width || start.height !== endFrame.height)) {
|
|
4695
5133
|
throw new Error(
|
|
4696
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`
|
|
@@ -4745,13 +5183,13 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
4745
5183
|
);
|
|
4746
5184
|
}
|
|
4747
5185
|
const frames = parentOutputs.slice(0, 16).map((output) => {
|
|
4748
|
-
const bytes = (0,
|
|
5186
|
+
const bytes = (0, import_node_fs3.readFileSync)(output.path);
|
|
4749
5187
|
const dims = imageMetadata(bytes);
|
|
4750
5188
|
if (!dims) throw new Error(`${spec.styleId}/${spec.assetId}: outfit source frame is not a readable PNG: ${output.path}`);
|
|
4751
5189
|
return { base64: bytes.toString("base64"), format: "png", width: dims.width, height: dims.height };
|
|
4752
5190
|
});
|
|
4753
5191
|
const outfit = character.outfit;
|
|
4754
|
-
const referenceBytes = (0,
|
|
5192
|
+
const referenceBytes = (0, import_node_fs3.readFileSync)(outfit.reference.path);
|
|
4755
5193
|
if (sha256(referenceBytes) !== outfit.reference.sha256) {
|
|
4756
5194
|
throw new Error(`${spec.styleId}/${spec.assetId}: outfit reference changed after the manifest was resolved: ${outfit.reference.path}`);
|
|
4757
5195
|
}
|
|
@@ -4794,7 +5232,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
4794
5232
|
}
|
|
4795
5233
|
if (character.kind === "portrait") {
|
|
4796
5234
|
const parentFile = character.parentFile;
|
|
4797
|
-
const bytes = (0,
|
|
5235
|
+
const bytes = (0, import_node_fs3.readFileSync)(parentFile);
|
|
4798
5236
|
if (character.parentSha256 && sha256(bytes) !== character.parentSha256) {
|
|
4799
5237
|
throw new Error(`${spec.styleId}/${spec.assetId}: portrait source changed after the manifest was resolved: ${parentFile}`);
|
|
4800
5238
|
}
|
|
@@ -5000,8 +5438,8 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5000
5438
|
const sources = images.map((image, index) => {
|
|
5001
5439
|
if (!image.base64) throw new Error(`outfit transfer frame ${index} has no image data`);
|
|
5002
5440
|
const role = meta?.frameRoles?.[index] ?? void 0;
|
|
5003
|
-
const file =
|
|
5004
|
-
(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"));
|
|
5005
5443
|
return { url: `file://${file}`, ...role ? { role } : {} };
|
|
5006
5444
|
});
|
|
5007
5445
|
return {
|
|
@@ -5152,6 +5590,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5152
5590
|
async submit(spec, styleImages, context) {
|
|
5153
5591
|
this.validate(spec, styleImages);
|
|
5154
5592
|
if (spec.revision) return this.submitRevision(spec);
|
|
5593
|
+
if (spec.vfx) return this.submitVfx(spec, context);
|
|
5155
5594
|
if (spec.generator === "character") return this.submitCharacter(spec, styleImages, context);
|
|
5156
5595
|
if (spec.generator === "objectPro") return this.submitObjectPro(spec, styleImages, context);
|
|
5157
5596
|
if (spec.generator === "uiAsset") {
|
|
@@ -5205,7 +5644,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5205
5644
|
seed: spec.seed
|
|
5206
5645
|
});
|
|
5207
5646
|
const jobId = (0, import_node_crypto2.randomUUID)();
|
|
5208
|
-
(0,
|
|
5647
|
+
(0, import_node_fs3.writeFileSync)(import_node_path2.default.join(_PixelLabProvider.cacheDir(), `${jobId}.png`), png);
|
|
5209
5648
|
return { jobId };
|
|
5210
5649
|
}
|
|
5211
5650
|
if (spec.generator === "tiles") {
|
|
@@ -5325,7 +5764,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5325
5764
|
}
|
|
5326
5765
|
const sourceFiles = revision.sourceMembers ?? [{ file: revision.sourceFile, sha256: revision.sourceSha256, format: revision.sourceFormat }];
|
|
5327
5766
|
const images = sourceFiles.map((member) => {
|
|
5328
|
-
const bytes = (0,
|
|
5767
|
+
const bytes = (0, import_node_fs3.readFileSync)(member.file);
|
|
5329
5768
|
if (sha256(bytes) !== member.sha256) {
|
|
5330
5769
|
throw new Error(`${spec.styleId}/${spec.assetId}: revision source changed after the manifest was resolved`);
|
|
5331
5770
|
}
|
|
@@ -5340,7 +5779,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5340
5779
|
if (!revision.maskFile || !revision.maskSha256 || !revision.maskFormat) {
|
|
5341
5780
|
throw new Error(`${spec.styleId}/${spec.assetId}: revision mask is not ready`);
|
|
5342
5781
|
}
|
|
5343
|
-
const maskBytes = (0,
|
|
5782
|
+
const maskBytes = (0, import_node_fs3.readFileSync)(revision.maskFile);
|
|
5344
5783
|
if (sha256(maskBytes) !== revision.maskSha256) {
|
|
5345
5784
|
throw new Error(`${spec.styleId}/${spec.assetId}: revision mask changed after the manifest was resolved`);
|
|
5346
5785
|
}
|
|
@@ -5365,7 +5804,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5365
5804
|
if (!revision.maskFile || !revision.maskSha256 || !revision.maskFormat) {
|
|
5366
5805
|
throw new Error(`${spec.styleId}/${spec.assetId}: revision mask is not ready`);
|
|
5367
5806
|
}
|
|
5368
|
-
const maskBytes = (0,
|
|
5807
|
+
const maskBytes = (0, import_node_fs3.readFileSync)(revision.maskFile);
|
|
5369
5808
|
if (sha256(maskBytes) !== revision.maskSha256) {
|
|
5370
5809
|
throw new Error(`${spec.styleId}/${spec.assetId}: revision mask changed after the manifest was resolved`);
|
|
5371
5810
|
}
|
|
@@ -5386,7 +5825,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5386
5825
|
if (!revision.paletteImageSha256 || !revision.paletteImageFormat) {
|
|
5387
5826
|
throw new Error(`${spec.styleId}/${spec.assetId}: revision palette image is not ready`);
|
|
5388
5827
|
}
|
|
5389
|
-
const paletteBytes = (0,
|
|
5828
|
+
const paletteBytes = (0, import_node_fs3.readFileSync)(revision.paletteImageFile);
|
|
5390
5829
|
if (sha256(paletteBytes) !== revision.paletteImageSha256) {
|
|
5391
5830
|
throw new Error(`${spec.styleId}/${spec.assetId}: revision palette image changed after the manifest was resolved`);
|
|
5392
5831
|
}
|
|
@@ -5477,7 +5916,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5477
5916
|
if (!revision.keypointsFile || !revision.keypointsSha256 || !revision.skeleton) {
|
|
5478
5917
|
throw new Error(`${spec.styleId}/${spec.assetId}: revision keypoints are not ready`);
|
|
5479
5918
|
}
|
|
5480
|
-
const keypointsBytes = (0,
|
|
5919
|
+
const keypointsBytes = (0, import_node_fs3.readFileSync)(revision.keypointsFile);
|
|
5481
5920
|
if (sha256(keypointsBytes) !== revision.keypointsSha256) {
|
|
5482
5921
|
throw new Error(`${spec.styleId}/${spec.assetId}: revision keypoints changed after the manifest was resolved`);
|
|
5483
5922
|
}
|
|
@@ -5519,7 +5958,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5519
5958
|
if (!revision.lastFrameSha256 || !revision.lastFrameFormat) {
|
|
5520
5959
|
throw new Error(`${spec.styleId}/${spec.assetId}: revision last frame is not ready`);
|
|
5521
5960
|
}
|
|
5522
|
-
const bytes = (0,
|
|
5961
|
+
const bytes = (0, import_node_fs3.readFileSync)(revision.lastFrameFile);
|
|
5523
5962
|
if (sha256(bytes) !== revision.lastFrameSha256) {
|
|
5524
5963
|
throw new Error(`${spec.styleId}/${spec.assetId}: revision last frame changed after the manifest was resolved`);
|
|
5525
5964
|
}
|
|
@@ -5534,11 +5973,11 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5534
5973
|
cacheCleanupResult(pngs, usage, roles, fps) {
|
|
5535
5974
|
const jobId = (0, import_node_crypto2.randomUUID)();
|
|
5536
5975
|
if (!roles) {
|
|
5537
|
-
(0,
|
|
5976
|
+
(0, import_node_fs3.writeFileSync)(import_node_path2.default.join(_PixelLabProvider.cacheDir(), `${jobId}.png`), pngs[0]);
|
|
5538
5977
|
return { jobId, metadata: { revisionUsage: usage } };
|
|
5539
5978
|
}
|
|
5540
5979
|
for (const [index, png] of pngs.entries()) {
|
|
5541
|
-
(0,
|
|
5980
|
+
(0, import_node_fs3.writeFileSync)(import_node_path2.default.join(_PixelLabProvider.cacheDir(), `${jobId}-${index}.png`), png);
|
|
5542
5981
|
}
|
|
5543
5982
|
return { jobId, metadata: { revisionUsage: usage, revisionMembers: roles, ...fps != null ? { revisionFps: fps } : {} } };
|
|
5544
5983
|
}
|
|
@@ -5566,10 +6005,11 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5566
6005
|
return this.pollAnimateRevision(jobId, context);
|
|
5567
6006
|
}
|
|
5568
6007
|
if (context?.spec?.revision) return this.pollRevision(jobId);
|
|
6008
|
+
if (generator === "vfx") return this.pollVfx(jobId, context);
|
|
5569
6009
|
if (generator === "imageProFlash") return this.pollRevision(jobId);
|
|
5570
6010
|
if (generator === "pixflux") {
|
|
5571
|
-
const file =
|
|
5572
|
-
if ((0,
|
|
6011
|
+
const file = import_node_path2.default.join(_PixelLabProvider.cacheDir(), `${jobId}.png`);
|
|
6012
|
+
if ((0, import_node_fs3.existsSync)(file)) {
|
|
5573
6013
|
const sourceUrl = `file://${file}`;
|
|
5574
6014
|
return { status: "ready", objectId: jobId, sourceUrl, sources: [{ url: sourceUrl }] };
|
|
5575
6015
|
}
|
|
@@ -5612,6 +6052,120 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5612
6052
|
* background job to ask afterwards the way `billedForJob` does for async
|
|
5613
6053
|
* work.
|
|
5614
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
|
+
}
|
|
5615
6169
|
async pollCachedRevision(jobId, context) {
|
|
5616
6170
|
const usage = context?.metadata?.revisionUsage;
|
|
5617
6171
|
const roles = context?.metadata?.revisionMembers;
|
|
@@ -5621,10 +6175,10 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5621
6175
|
};
|
|
5622
6176
|
if (Array.isArray(roles) && roles.length) {
|
|
5623
6177
|
const sources = roles.map((role, index) => ({
|
|
5624
|
-
url: `file://${
|
|
6178
|
+
url: `file://${import_node_path2.default.join(_PixelLabProvider.cacheDir(), `${jobId}-${index}.png`)}`,
|
|
5625
6179
|
role: String(role)
|
|
5626
6180
|
}));
|
|
5627
|
-
if (sources.some((source) => !(0,
|
|
6181
|
+
if (sources.some((source) => !(0, import_node_fs3.existsSync)(source.url.slice("file://".length)))) return gone;
|
|
5628
6182
|
const fps = context?.metadata?.revisionFps;
|
|
5629
6183
|
const frames = sources.every((source) => source.role.startsWith("frame-"));
|
|
5630
6184
|
return {
|
|
@@ -5636,8 +6190,8 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5636
6190
|
billed: billedFromUsage(usage)
|
|
5637
6191
|
};
|
|
5638
6192
|
}
|
|
5639
|
-
const file =
|
|
5640
|
-
if (!(0,
|
|
6193
|
+
const file = import_node_path2.default.join(_PixelLabProvider.cacheDir(), `${jobId}.png`);
|
|
6194
|
+
if (!(0, import_node_fs3.existsSync)(file)) return gone;
|
|
5641
6195
|
const sourceUrl = `file://${file}`;
|
|
5642
6196
|
return { status: "ready", objectId: jobId, sourceUrl, sources: [{ url: sourceUrl }], billed: billedFromUsage(usage) };
|
|
5643
6197
|
}
|
|
@@ -5693,8 +6247,8 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5693
6247
|
}
|
|
5694
6248
|
const base64 = extractBase64(item);
|
|
5695
6249
|
if (base64) {
|
|
5696
|
-
const file =
|
|
5697
|
-
(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"));
|
|
5698
6252
|
urls.push(`file://${file}`);
|
|
5699
6253
|
continue;
|
|
5700
6254
|
}
|
|
@@ -5744,8 +6298,8 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5744
6298
|
const candidate = done[key];
|
|
5745
6299
|
const base64 = extractBase64(candidate);
|
|
5746
6300
|
if (base64) {
|
|
5747
|
-
const file =
|
|
5748
|
-
(0,
|
|
6301
|
+
const file = import_node_path2.default.join(_PixelLabProvider.cacheDir(), `${jobId}.png`);
|
|
6302
|
+
(0, import_node_fs3.writeFileSync)(file, Buffer.from(base64, "base64"));
|
|
5749
6303
|
const sourceUrl = `file://${file}`;
|
|
5750
6304
|
return { status: "ready", objectId: jobId, sourceUrl, sources: [{ url: sourceUrl }], billed };
|
|
5751
6305
|
}
|
|
@@ -5753,8 +6307,8 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5753
6307
|
if (Array.isArray(done.images) && done.images.length) {
|
|
5754
6308
|
const base64 = extractBase64(done.images[0]);
|
|
5755
6309
|
if (base64) {
|
|
5756
|
-
const file =
|
|
5757
|
-
(0,
|
|
6310
|
+
const file = import_node_path2.default.join(_PixelLabProvider.cacheDir(), `${jobId}.png`);
|
|
6311
|
+
(0, import_node_fs3.writeFileSync)(file, Buffer.from(base64, "base64"));
|
|
5758
6312
|
const sourceUrl = `file://${file}`;
|
|
5759
6313
|
return { status: "ready", objectId: jobId, sourceUrl, sources: [{ url: sourceUrl }], billed };
|
|
5760
6314
|
}
|
|
@@ -5787,8 +6341,8 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5787
6341
|
for (const [index, candidate] of done.images.entries()) {
|
|
5788
6342
|
const base64 = extractBase64(candidate);
|
|
5789
6343
|
if (!base64) continue;
|
|
5790
|
-
const file =
|
|
5791
|
-
(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"));
|
|
5792
6346
|
urls.push(`file://${file}`);
|
|
5793
6347
|
}
|
|
5794
6348
|
if (urls.length) return { status: "review", candidateUrls: urls, billed };
|
|
@@ -5817,8 +6371,8 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5817
6371
|
for (const [index, candidate] of images.entries()) {
|
|
5818
6372
|
const base64 = extractBase64(candidate);
|
|
5819
6373
|
if (!base64) continue;
|
|
5820
|
-
const file =
|
|
5821
|
-
(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"));
|
|
5822
6376
|
urls.push(`file://${file}`);
|
|
5823
6377
|
}
|
|
5824
6378
|
if (urls.length === 1) return { status: "ready", objectId: jobId, sourceUrl: urls[0], sources: [{ url: urls[0] }], billed };
|
|
@@ -5915,8 +6469,8 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5915
6469
|
for (const [index, tile] of tileset.tiles.entries()) {
|
|
5916
6470
|
const slug2 = tile.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "tile";
|
|
5917
6471
|
const role = `tile-${String(index).padStart(2, "0")}-${slug2}`;
|
|
5918
|
-
const file =
|
|
5919
|
-
(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"));
|
|
5920
6474
|
sources.push({ url: `file://${file}`, role });
|
|
5921
6475
|
terrainTiles.push({ role, corners: tile.corners, pattern4x4: tile.pattern_4x4 });
|
|
5922
6476
|
}
|
|
@@ -5945,8 +6499,8 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5945
6499
|
const { image, usage } = await this.client.getIsometricTile(tileId);
|
|
5946
6500
|
const backgroundJobId = context?.metadata?.backgroundJobId;
|
|
5947
6501
|
const billed = billedFromUsage(usage) ?? await this.billedForJob(backgroundJobId);
|
|
5948
|
-
const file =
|
|
5949
|
-
(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"));
|
|
5950
6504
|
return {
|
|
5951
6505
|
status: "ready",
|
|
5952
6506
|
objectId: tileId,
|
|
@@ -5989,8 +6543,8 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5989
6543
|
return { objectId: `${jobId}#${index}`, sourceUrl: url };
|
|
5990
6544
|
}
|
|
5991
6545
|
if (generator === "imagePro" || generator === "uiElement") {
|
|
5992
|
-
const file =
|
|
5993
|
-
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}`);
|
|
5994
6548
|
return { objectId: `${jobId}#${index}`, sourceUrl: `file://${file}` };
|
|
5995
6549
|
}
|
|
5996
6550
|
const promoted = await this.client.selectFrames(jobId, [index], commonTag);
|
|
@@ -6002,7 +6556,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
6002
6556
|
return { objectId, sourceUrl: firstUrl(obj?.rotation_urls) ?? obj?.preview_url ?? null };
|
|
6003
6557
|
}
|
|
6004
6558
|
async download(url) {
|
|
6005
|
-
if (url.startsWith("file://")) return (0,
|
|
6559
|
+
if (url.startsWith("file://")) return (0, import_node_fs3.readFileSync)(url.slice("file://".length));
|
|
6006
6560
|
return this.client.download(url);
|
|
6007
6561
|
}
|
|
6008
6562
|
async balance() {
|
|
@@ -6124,7 +6678,7 @@ function extractBase64(value) {
|
|
|
6124
6678
|
function readReference(spec, reference) {
|
|
6125
6679
|
const images = {};
|
|
6126
6680
|
for (const [direction, image] of Object.entries(reference)) {
|
|
6127
|
-
const bytes = (0,
|
|
6681
|
+
const bytes = (0, import_node_fs3.readFileSync)(image.path);
|
|
6128
6682
|
if (sha256(bytes) !== image.sha256) {
|
|
6129
6683
|
throw new Error(`${spec.styleId}/${spec.assetId}: reference (${direction}) changed after the manifest was resolved: ${image.path}`);
|
|
6130
6684
|
}
|
|
@@ -6141,7 +6695,7 @@ function requireStyleObjectId(spec, context) {
|
|
|
6141
6695
|
return context.styleObjectId;
|
|
6142
6696
|
}
|
|
6143
6697
|
function readPose(spec, what, image) {
|
|
6144
|
-
const bytes = (0,
|
|
6698
|
+
const bytes = (0, import_node_fs3.readFileSync)(image.path);
|
|
6145
6699
|
if (sha256(bytes) !== image.sha256) {
|
|
6146
6700
|
throw new Error(`${spec.styleId}/${spec.assetId}: ${what} changed after the manifest was resolved: ${image.path}`);
|
|
6147
6701
|
}
|
|
@@ -6206,7 +6760,7 @@ function findObjectAnimation(object, name, direction, animationId) {
|
|
|
6206
6760
|
function mapScenePayload(spec) {
|
|
6207
6761
|
const scene = spec.scene;
|
|
6208
6762
|
const read = (file, hash, label) => {
|
|
6209
|
-
const bytes = (0,
|
|
6763
|
+
const bytes = (0, import_node_fs3.readFileSync)(file);
|
|
6210
6764
|
if (!hash || sha256(bytes) !== hash) {
|
|
6211
6765
|
throw new Error(`${spec.styleId}/${spec.assetId}: ${label} changed after the manifest was resolved`);
|
|
6212
6766
|
}
|
|
@@ -6267,9 +6821,9 @@ var RetroDiffusionClient = class {
|
|
|
6267
6821
|
if (!response.ok) throw new ProviderError("retrodiffusion", `Retro Diffusion download failed (${response.status})`, { status: response.status });
|
|
6268
6822
|
return Buffer.from(await response.arrayBuffer());
|
|
6269
6823
|
}
|
|
6270
|
-
async call(
|
|
6824
|
+
async call(path39, init = {}) {
|
|
6271
6825
|
if (!this.token) throw new Error("RD_API_KEY is not set");
|
|
6272
|
-
const response = await this.request(`${this.baseUrl}${
|
|
6826
|
+
const response = await this.request(`${this.baseUrl}${path39}`, {
|
|
6273
6827
|
...init,
|
|
6274
6828
|
headers: {
|
|
6275
6829
|
"Content-Type": "application/json",
|
|
@@ -6579,9 +7133,9 @@ function retroError(value) {
|
|
|
6579
7133
|
|
|
6580
7134
|
// src/providers/scenario.ts
|
|
6581
7135
|
var import_node_crypto3 = require("crypto");
|
|
6582
|
-
var
|
|
7136
|
+
var import_node_fs4 = require("fs");
|
|
6583
7137
|
var import_node_os2 = __toESM(require("os"), 1);
|
|
6584
|
-
var
|
|
7138
|
+
var import_node_path3 = __toESM(require("path"), 1);
|
|
6585
7139
|
var DEFAULT_BASE_URL2 = "https://api.cloud.scenario.com/v1";
|
|
6586
7140
|
var SOURCE_PROTOCOL2 = "scenario:";
|
|
6587
7141
|
var SUBMIT_RETRIES = 8;
|
|
@@ -6964,7 +7518,7 @@ var ScenarioProvider = class _ScenarioProvider {
|
|
|
6964
7518
|
}
|
|
6965
7519
|
};
|
|
6966
7520
|
function uploadCachePath() {
|
|
6967
|
-
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");
|
|
6968
7522
|
}
|
|
6969
7523
|
function uploadCacheKey(baseUrl, projectId, digest2) {
|
|
6970
7524
|
return (0, import_node_crypto3.createHash)("sha256").update(`${baseUrl}
|
|
@@ -6973,7 +7527,7 @@ ${digest2}`).digest("hex");
|
|
|
6973
7527
|
}
|
|
6974
7528
|
function readUploadCache() {
|
|
6975
7529
|
try {
|
|
6976
|
-
const value = JSON.parse((0,
|
|
7530
|
+
const value = JSON.parse((0, import_node_fs4.readFileSync)(uploadCachePath(), "utf8"));
|
|
6977
7531
|
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
6978
7532
|
} catch {
|
|
6979
7533
|
return {};
|
|
@@ -6982,10 +7536,10 @@ function readUploadCache() {
|
|
|
6982
7536
|
function writeUploadCache(key, assetId) {
|
|
6983
7537
|
try {
|
|
6984
7538
|
const file = uploadCachePath();
|
|
6985
|
-
(0,
|
|
7539
|
+
(0, import_node_fs4.mkdirSync)(import_node_path3.default.dirname(file), { recursive: true });
|
|
6986
7540
|
const tmp = `${file}.${process.pid}.tmp`;
|
|
6987
|
-
(0,
|
|
6988
|
-
(0,
|
|
7541
|
+
(0, import_node_fs4.writeFileSync)(tmp, JSON.stringify({ ...readUploadCache(), [key]: assetId }));
|
|
7542
|
+
(0, import_node_fs4.renameSync)(tmp, file);
|
|
6989
7543
|
} catch {
|
|
6990
7544
|
}
|
|
6991
7545
|
}
|
|
@@ -7012,7 +7566,7 @@ function readRevisionSource(spec) {
|
|
|
7012
7566
|
if (!revision.sourceFile || !revision.sourceSha256 || !revision.sourceFormat) {
|
|
7013
7567
|
throw new Error(`${spec.styleId}/${spec.assetId}: revision source is not ready`);
|
|
7014
7568
|
}
|
|
7015
|
-
const bytes = (0,
|
|
7569
|
+
const bytes = (0, import_node_fs4.readFileSync)(revision.sourceFile);
|
|
7016
7570
|
if (sha256(bytes) !== revision.sourceSha256) {
|
|
7017
7571
|
throw new Error(`${spec.styleId}/${spec.assetId}: revision source changed after the manifest was resolved`);
|
|
7018
7572
|
}
|
|
@@ -7182,8 +7736,8 @@ function redactScenarioError(value, secrets) {
|
|
|
7182
7736
|
|
|
7183
7737
|
// src/providers/comfyui.ts
|
|
7184
7738
|
var import_node_crypto4 = require("crypto");
|
|
7185
|
-
var
|
|
7186
|
-
var
|
|
7739
|
+
var import_promises2 = require("fs/promises");
|
|
7740
|
+
var import_node_path4 = __toESM(require("path"), 1);
|
|
7187
7741
|
var DEFAULT_BASE_URL3 = "http://127.0.0.1:8188";
|
|
7188
7742
|
var BUILTIN_BINDINGS = /* @__PURE__ */ new Set([
|
|
7189
7743
|
"prompt",
|
|
@@ -7320,10 +7874,10 @@ var ComfyUIProvider = class _ComfyUIProvider {
|
|
|
7320
7874
|
}
|
|
7321
7875
|
async resolveOptions(value, context) {
|
|
7322
7876
|
const options = parseOptions(value);
|
|
7323
|
-
const workflowPath =
|
|
7877
|
+
const workflowPath = import_node_path4.default.resolve(context.root, options.workflowFile);
|
|
7324
7878
|
let raw;
|
|
7325
7879
|
try {
|
|
7326
|
-
raw = await (0,
|
|
7880
|
+
raw = await (0, import_promises2.readFile)(workflowPath, "utf8");
|
|
7327
7881
|
} catch (err) {
|
|
7328
7882
|
throw new Error(
|
|
7329
7883
|
`ComfyUI workflow for style "${context.styleId}" could not be read at ${workflowPath}: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -7375,10 +7929,10 @@ var ComfyUIProvider = class _ComfyUIProvider {
|
|
|
7375
7929
|
if (typeof item !== "string") {
|
|
7376
7930
|
throw new Error(`ComfyUI ${label} must be a manifest-relative PNG or JPEG path`);
|
|
7377
7931
|
}
|
|
7378
|
-
const file =
|
|
7932
|
+
const file = import_node_path4.default.resolve(context.root, item);
|
|
7379
7933
|
let bytes;
|
|
7380
7934
|
try {
|
|
7381
|
-
bytes = await (0,
|
|
7935
|
+
bytes = await (0, import_promises2.readFile)(file);
|
|
7382
7936
|
} catch (error) {
|
|
7383
7937
|
throw new Error(
|
|
7384
7938
|
`ComfyUI ${label} could not be read at ${file}: ${error instanceof Error ? error.message : String(error)}`
|
|
@@ -7535,7 +8089,7 @@ var ComfyUIProvider = class _ComfyUIProvider {
|
|
|
7535
8089
|
setBinding(workflow2, binding, input);
|
|
7536
8090
|
return;
|
|
7537
8091
|
}
|
|
7538
|
-
const bytes = await (0,
|
|
8092
|
+
const bytes = await (0, import_promises2.readFile)(input.path);
|
|
7539
8093
|
if (sha256(bytes) !== input.sha256) {
|
|
7540
8094
|
throw new Error(`ComfyUI provider input "${name}" changed before upload`);
|
|
7541
8095
|
}
|
|
@@ -7602,7 +8156,7 @@ var ComfyUIProvider = class _ComfyUIProvider {
|
|
|
7602
8156
|
if (!spec.revision.sourceSha256 || !spec.revision.sourceFormat) {
|
|
7603
8157
|
throw new Error("ComfyUI revision source is not ready");
|
|
7604
8158
|
}
|
|
7605
|
-
const sourceBytes = await (0,
|
|
8159
|
+
const sourceBytes = await (0, import_promises2.readFile)(spec.revision.sourceFile);
|
|
7606
8160
|
if (sha256(sourceBytes) !== spec.revision.sourceSha256) {
|
|
7607
8161
|
throw new Error("ComfyUI revision source changed before upload");
|
|
7608
8162
|
}
|
|
@@ -7616,7 +8170,7 @@ var ComfyUIProvider = class _ComfyUIProvider {
|
|
|
7616
8170
|
if (!spec.revision.maskSha256 || !spec.revision.maskFormat) {
|
|
7617
8171
|
throw new Error("ComfyUI revision mask is not ready");
|
|
7618
8172
|
}
|
|
7619
|
-
const maskBytes = await (0,
|
|
8173
|
+
const maskBytes = await (0, import_promises2.readFile)(spec.revision.maskFile);
|
|
7620
8174
|
if (sha256(maskBytes) !== spec.revision.maskSha256) {
|
|
7621
8175
|
throw new Error("ComfyUI revision mask changed before upload");
|
|
7622
8176
|
}
|
|
@@ -8336,9 +8890,9 @@ registerProvider({
|
|
|
8336
8890
|
});
|
|
8337
8891
|
|
|
8338
8892
|
// src/manifest.ts
|
|
8339
|
-
var
|
|
8340
|
-
var
|
|
8341
|
-
var
|
|
8893
|
+
var import_promises4 = require("fs/promises");
|
|
8894
|
+
var import_node_fs7 = require("fs");
|
|
8895
|
+
var import_node_path7 = __toESM(require("path"), 1);
|
|
8342
8896
|
|
|
8343
8897
|
// src/loop-directions.ts
|
|
8344
8898
|
var SINGLE_OUTPUT_FIELDS = ["file", "cell", "remoteId", "source", "sourceByStyle", "outputRole"];
|
|
@@ -8426,17 +8980,17 @@ function isObject2(value) {
|
|
|
8426
8980
|
}
|
|
8427
8981
|
|
|
8428
8982
|
// src/outputs.ts
|
|
8429
|
-
var
|
|
8430
|
-
var
|
|
8983
|
+
var import_node_fs6 = require("fs");
|
|
8984
|
+
var import_node_path6 = __toESM(require("path"), 1);
|
|
8431
8985
|
|
|
8432
8986
|
// src/lock.ts
|
|
8433
|
-
var
|
|
8434
|
-
var
|
|
8435
|
-
var
|
|
8987
|
+
var import_promises3 = require("fs/promises");
|
|
8988
|
+
var import_node_fs5 = require("fs");
|
|
8989
|
+
var import_node_path5 = __toESM(require("path"), 1);
|
|
8436
8990
|
async function loadLock(lockPath) {
|
|
8437
|
-
if (!(0,
|
|
8991
|
+
if (!(0, import_node_fs5.existsSync)(lockPath)) return { version: 2, entries: {} };
|
|
8438
8992
|
try {
|
|
8439
|
-
return parseLock(JSON.parse(await (0,
|
|
8993
|
+
return parseLock(JSON.parse(await (0, import_promises3.readFile)(lockPath, "utf8")));
|
|
8440
8994
|
} catch (err) {
|
|
8441
8995
|
throw new ProjectError(
|
|
8442
8996
|
`Lockfile at ${lockPath} is malformed:
|
|
@@ -8449,7 +9003,7 @@ var saveQueues = /* @__PURE__ */ new Map();
|
|
|
8449
9003
|
var dirtyPatches = /* @__PURE__ */ new WeakMap();
|
|
8450
9004
|
var dirtyDeletes = /* @__PURE__ */ new WeakMap();
|
|
8451
9005
|
function saveLock(lockPath, lock) {
|
|
8452
|
-
const queueKey =
|
|
9006
|
+
const queueKey = import_node_path5.default.resolve(lockPath);
|
|
8453
9007
|
const previous = saveQueues.get(queueKey) ?? Promise.resolve();
|
|
8454
9008
|
const next = previous.catch(() => {
|
|
8455
9009
|
}).then(() => writeLockNow(lockPath, lock));
|
|
@@ -8461,7 +9015,7 @@ function saveLock(lockPath, lock) {
|
|
|
8461
9015
|
return next;
|
|
8462
9016
|
}
|
|
8463
9017
|
async function writeLockNow(lockPath, lock) {
|
|
8464
|
-
await (0,
|
|
9018
|
+
await (0, import_promises3.mkdir)(import_node_path5.default.dirname(import_node_path5.default.resolve(lockPath)), { recursive: true });
|
|
8465
9019
|
const release = await acquireFileLock(lockPath);
|
|
8466
9020
|
try {
|
|
8467
9021
|
await writeLockWhileHeld(lockPath, lock);
|
|
@@ -8471,9 +9025,9 @@ async function writeLockNow(lockPath, lock) {
|
|
|
8471
9025
|
}
|
|
8472
9026
|
async function writeLockWhileHeld(file, lock) {
|
|
8473
9027
|
let disk = { version: 2, entries: {} };
|
|
8474
|
-
if ((0,
|
|
9028
|
+
if ((0, import_node_fs5.existsSync)(file)) {
|
|
8475
9029
|
try {
|
|
8476
|
-
disk = parseLock(JSON.parse(await (0,
|
|
9030
|
+
disk = parseLock(JSON.parse(await (0, import_promises3.readFile)(file, "utf8")));
|
|
8477
9031
|
} catch (err) {
|
|
8478
9032
|
throw new ProjectError(
|
|
8479
9033
|
`Refusing to overwrite malformed lockfile at ${file}: ${err instanceof Error ? err.message : String(err)}`,
|
|
@@ -8502,10 +9056,10 @@ async function writeLockWhileHeld(file, lock) {
|
|
|
8502
9056
|
}
|
|
8503
9057
|
const tmp = `${file}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
8504
9058
|
try {
|
|
8505
|
-
await (0,
|
|
8506
|
-
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);
|
|
8507
9061
|
} finally {
|
|
8508
|
-
await (0,
|
|
9062
|
+
await (0, import_promises3.rm)(tmp, { force: true });
|
|
8509
9063
|
}
|
|
8510
9064
|
const currentDirty = dirtyPatches.get(lock);
|
|
8511
9065
|
if (currentDirty) {
|
|
@@ -8568,18 +9122,18 @@ async function acquireFileLock(file) {
|
|
|
8568
9122
|
const deadline = Date.now() + FILE_LOCK_TIMEOUT_MS;
|
|
8569
9123
|
for (; ; ) {
|
|
8570
9124
|
try {
|
|
8571
|
-
await (0,
|
|
9125
|
+
await (0, import_promises3.mkdir)(lockDir);
|
|
8572
9126
|
return async () => {
|
|
8573
|
-
await (0,
|
|
9127
|
+
await (0, import_promises3.rmdir)(lockDir).catch(() => {
|
|
8574
9128
|
});
|
|
8575
9129
|
};
|
|
8576
9130
|
} catch (err) {
|
|
8577
9131
|
const code = err.code;
|
|
8578
9132
|
if (code !== "EEXIST") throw err;
|
|
8579
9133
|
try {
|
|
8580
|
-
const age = Date.now() - (await (0,
|
|
9134
|
+
const age = Date.now() - (await (0, import_promises3.stat)(lockDir)).mtimeMs;
|
|
8581
9135
|
if (age > STALE_FILE_LOCK_MS) {
|
|
8582
|
-
await (0,
|
|
9136
|
+
await (0, import_promises3.rmdir)(lockDir);
|
|
8583
9137
|
continue;
|
|
8584
9138
|
}
|
|
8585
9139
|
} catch (lockErr) {
|
|
@@ -8606,22 +9160,22 @@ function totalSpend(lock, unit = "generations") {
|
|
|
8606
9160
|
|
|
8607
9161
|
// src/outputs.ts
|
|
8608
9162
|
function portableOutputPath(file, manifestDir) {
|
|
8609
|
-
const root =
|
|
8610
|
-
const absolute =
|
|
8611
|
-
const relative =
|
|
8612
|
-
const value =
|
|
8613
|
-
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("/");
|
|
8614
9168
|
}
|
|
8615
9169
|
function resolveOutputPath(recordedPath, manifestDir) {
|
|
8616
|
-
if (
|
|
8617
|
-
if (
|
|
8618
|
-
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));
|
|
8619
9173
|
}
|
|
8620
9174
|
function expectedOutputPath(spec, role, index, total, mediaType) {
|
|
8621
9175
|
return memberPath(spec.outFile, role, index, total, mediaType);
|
|
8622
9176
|
}
|
|
8623
9177
|
function memberPath(file, role, index, total, mediaType) {
|
|
8624
|
-
const originalExt =
|
|
9178
|
+
const originalExt = import_node_path6.default.extname(file);
|
|
8625
9179
|
const ext = mediaType ? mediaExtension(mediaType) : originalExt || ".png";
|
|
8626
9180
|
const stem = originalExt ? file.slice(0, -originalExt.length) : file;
|
|
8627
9181
|
if (total === 1) return `${stem}${ext}`;
|
|
@@ -8635,10 +9189,10 @@ function isFrameSetEntry(entry) {
|
|
|
8635
9189
|
return entry.generator === "frames" && isMemberSetEntry(entry);
|
|
8636
9190
|
}
|
|
8637
9191
|
function sourceIsStem(source, entry, manifestDir) {
|
|
8638
|
-
return isMemberSetEntry(entry) && !(0,
|
|
9192
|
+
return isMemberSetEntry(entry) && !(0, import_node_fs6.existsSync)(import_node_path6.default.resolve(manifestDir, source));
|
|
8639
9193
|
}
|
|
8640
9194
|
function sourceOutputPath(source, entry, index, manifestDir) {
|
|
8641
|
-
const absolute =
|
|
9195
|
+
const absolute = import_node_path6.default.resolve(manifestDir, source);
|
|
8642
9196
|
if (!sourceIsStem(source, entry, manifestDir)) return absolute;
|
|
8643
9197
|
const output = entry.outputs[index];
|
|
8644
9198
|
return memberPath(absolute, output.role, index, entry.outputs.length, output.mediaType);
|
|
@@ -8725,7 +9279,7 @@ function resolveSpecOutputs(spec, lock, manifestDir) {
|
|
|
8725
9279
|
id: spec.assetId,
|
|
8726
9280
|
index: 0,
|
|
8727
9281
|
path: spec.outFile,
|
|
8728
|
-
absolutePath:
|
|
9282
|
+
absolutePath: import_node_path6.default.resolve(spec.outFile),
|
|
8729
9283
|
sha256: ""
|
|
8730
9284
|
}];
|
|
8731
9285
|
}
|
|
@@ -8738,12 +9292,17 @@ function frameSetFps(entry) {
|
|
|
8738
9292
|
}
|
|
8739
9293
|
return null;
|
|
8740
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
|
+
}
|
|
8741
9300
|
|
|
8742
9301
|
// src/manifest.ts
|
|
8743
9302
|
async function loadManifest(manifestPath) {
|
|
8744
|
-
const abs =
|
|
8745
|
-
if (!(0,
|
|
8746
|
-
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")));
|
|
8747
9306
|
if (expansion.issues.length) {
|
|
8748
9307
|
throw new ProjectError(`Manifest at ${abs} is invalid:
|
|
8749
9308
|
${expansion.issues.map((i) => ` ${i}`).join("\n")}`);
|
|
@@ -8798,7 +9357,7 @@ ${issues}`);
|
|
|
8798
9357
|
if (fromStyle) {
|
|
8799
9358
|
if (!styleIds.has(fromStyle)) {
|
|
8800
9359
|
unknownReferences.push(`assets.${assetId}.revision.fromStyle: unknown style "${fromStyle}"`);
|
|
8801
|
-
} else if (parent && parent
|
|
9360
|
+
} else if (parent && !assetInStyle(parent, fromStyle, parsed.data.styles[fromStyle])) {
|
|
8802
9361
|
unknownReferences.push(
|
|
8803
9362
|
`assets.${assetId}.revision.fromStyle: "${asset.revision.from}" is not in style "${fromStyle}"`
|
|
8804
9363
|
);
|
|
@@ -8845,7 +9404,11 @@ ${issues}`);
|
|
|
8845
9404
|
${unknownReferences.map((i) => ` ${i}`).join("\n")}`);
|
|
8846
9405
|
}
|
|
8847
9406
|
const loopFamilies = Object.keys(expansion.families).length ? { loopFamilies: expansion.families } : {};
|
|
8848
|
-
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);
|
|
8849
9412
|
}
|
|
8850
9413
|
function parentAssetId(asset) {
|
|
8851
9414
|
return asset?.revision?.from ?? asset?.state?.of ?? asset?.animation?.of ?? asset?.portrait?.of ?? asset?.outfit?.of;
|
|
@@ -8949,8 +9512,8 @@ async function resolveSpecs(loaded, filter) {
|
|
|
8949
9512
|
const needed = /* @__PURE__ */ new Set();
|
|
8950
9513
|
for (const assetId of resolutionAssetIds) {
|
|
8951
9514
|
const asset = manifest.assets[assetId];
|
|
8952
|
-
if (asset
|
|
8953
|
-
const fromStyle = asset
|
|
9515
|
+
if (!asset || !assetInStyle(asset, styleId, manifest.styles[styleId])) continue;
|
|
9516
|
+
const fromStyle = asset.revision?.fromStyle;
|
|
8954
9517
|
if (fromStyle && fromStyle !== styleId) needed.add(fromStyle);
|
|
8955
9518
|
}
|
|
8956
9519
|
styleDependencies.set(styleId, needed);
|
|
@@ -8979,11 +9542,11 @@ async function resolveSpecs(loaded, filter) {
|
|
|
8979
9542
|
const styleFinalizers = /* @__PURE__ */ new Map();
|
|
8980
9543
|
const styleImageCache = /* @__PURE__ */ new Map();
|
|
8981
9544
|
async function loadStyleImage(rel, what = "Style image") {
|
|
8982
|
-
const abs =
|
|
9545
|
+
const abs = import_node_path7.default.resolve(root, rel);
|
|
8983
9546
|
let hit = styleImageCache.get(abs);
|
|
8984
9547
|
if (!hit) {
|
|
8985
|
-
if (!(0,
|
|
8986
|
-
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);
|
|
8987
9550
|
const metadata = imageMetadata(buf);
|
|
8988
9551
|
if (!metadata) throw new Error(`${what} is not a readable PNG or JPEG: ${abs}`);
|
|
8989
9552
|
if (metadata.width < 1 || metadata.height < 1) {
|
|
@@ -9009,14 +9572,14 @@ async function resolveSpecs(loaded, filter) {
|
|
|
9009
9572
|
styleImageDimensions.push(loadedImage);
|
|
9010
9573
|
}
|
|
9011
9574
|
const resolvedImages = style.styleImages.map((image) => {
|
|
9012
|
-
const hit = styleImageCache.get(
|
|
9575
|
+
const hit = styleImageCache.get(import_node_path7.default.resolve(root, image.path));
|
|
9013
9576
|
return { base64: hit.base64, width: hit.width, height: hit.height, format: hit.format };
|
|
9014
9577
|
});
|
|
9015
9578
|
const styleSpecs = /* @__PURE__ */ new Map();
|
|
9016
9579
|
const providerInputIdentities = /* @__PURE__ */ new Map();
|
|
9017
9580
|
for (const [assetId, asset] of Object.entries(manifest.assets)) {
|
|
9018
9581
|
if (!resolutionAssetIds.has(assetId)) continue;
|
|
9019
|
-
if (asset
|
|
9582
|
+
if (!assetInStyle(asset, styleId, style)) continue;
|
|
9020
9583
|
const generator = style.generator;
|
|
9021
9584
|
if (!activeProvider.supports(generator)) {
|
|
9022
9585
|
throw new Error(`Provider "${activeProvider.id}" does not support generator "${generator}"`);
|
|
@@ -9036,6 +9599,15 @@ async function resolveSpecs(loaded, filter) {
|
|
|
9036
9599
|
const providerInputs = inputResolution.inputs;
|
|
9037
9600
|
const providerInputIdentity = inputResolution.identity ?? providerInputs;
|
|
9038
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;
|
|
9039
9611
|
let width;
|
|
9040
9612
|
let height;
|
|
9041
9613
|
let size;
|
|
@@ -9063,6 +9635,11 @@ async function resolveSpecs(loaded, filter) {
|
|
|
9063
9635
|
size = asset.size ?? style.size ?? 64;
|
|
9064
9636
|
width = size;
|
|
9065
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);
|
|
9066
9643
|
} else if (generator === "uiAsset" || generator === "uiElement") {
|
|
9067
9644
|
width = asset.width ?? style.size ?? 256;
|
|
9068
9645
|
height = asset.height ?? style.size ?? 256;
|
|
@@ -9105,14 +9682,14 @@ async function resolveSpecs(loaded, filter) {
|
|
|
9105
9682
|
terrainUpperDescription = wrap(parsed.upper);
|
|
9106
9683
|
terrainTransitionDescription = parsed.transition ? wrap(parsed.transition) : void 0;
|
|
9107
9684
|
prompt = subject.trim();
|
|
9108
|
-
} else if ((generator === "character" || generator === "objectPro") && characterKind !== "base") {
|
|
9685
|
+
} else if ((generator === "character" || generator === "objectPro") && characterKind !== "base" || vfx) {
|
|
9109
9686
|
prompt = subject.trim();
|
|
9110
9687
|
} else {
|
|
9111
9688
|
prompt = [style.promptPrefix, subject, style.promptSuffix].map((p) => p.trim()).filter(Boolean).join(", ");
|
|
9112
9689
|
}
|
|
9113
|
-
const relFile = asset.file ??
|
|
9114
|
-
const outFile =
|
|
9115
|
-
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;
|
|
9116
9693
|
const tileSize = generator === "tiles" ? size : style.tileSize ?? 32;
|
|
9117
9694
|
const tileVariations = tileFeatureOutputCount(generator === "tiles" ? style.tileFeature : void 0) ?? tileVariationCount(countNumberedDescriptions(prompt));
|
|
9118
9695
|
const terrainTiles = generator === "terrain" ? terrainTileCount(style.terrainTransitionSize) : 0;
|
|
@@ -9183,6 +9760,7 @@ async function resolveSpecs(loaded, filter) {
|
|
|
9183
9760
|
// A portrait needs a character style (refused above), so an
|
|
9184
9761
|
// objectPro asset is only ever a base, state, or animation.
|
|
9185
9762
|
...generator === "objectPro" && !asset.revision && characterKind !== "portrait" ? { objectPro: await resolveObjectProShape(assetId, asset, style, characterKind, { root, load: loadStyleImage }) } : {},
|
|
9763
|
+
...vfx ? { vfx } : {},
|
|
9186
9764
|
cost: generator === "tiles" ? tilesCost(tileSize, tileVariations) : generator === "terrain" ? tilesCost(size, terrainTiles) : generator === "isometricTile" ? isometricTileCost() : generationCost(width, height, generator),
|
|
9187
9765
|
costUnit: "generations",
|
|
9188
9766
|
candidates: generator === "tiles" ? tileVariations : generator === "terrain" ? terrainTiles : generator === "1dir" || generator === "imagePro" ? candidateCount(size) : 1
|
|
@@ -9207,7 +9785,7 @@ async function resolveSpecs(loaded, filter) {
|
|
|
9207
9785
|
minGridConfidence: style.quality.minGridConfidence,
|
|
9208
9786
|
...style.quality.minTransparency == null ? {} : { minTransparency: style.quality.minTransparency },
|
|
9209
9787
|
...style.quality.fixerRevision ? { fixerRevision: style.quality.fixerRevision } : {},
|
|
9210
|
-
...style.quality.fixerPython ? { fixerPython:
|
|
9788
|
+
...style.quality.fixerPython ? { fixerPython: import_node_path7.default.resolve(root, style.quality.fixerPython) } : {},
|
|
9211
9789
|
...style.quality.fps == null ? {} : { fps: style.quality.fps }
|
|
9212
9790
|
}
|
|
9213
9791
|
} : {},
|
|
@@ -9290,7 +9868,7 @@ async function resolveSpecs(loaded, filter) {
|
|
|
9290
9868
|
const file = memberPath(anchorSpec.outFile, "south", 0, anchorSpec.character.directions, MediaType.PNG);
|
|
9291
9869
|
resolved.character = {
|
|
9292
9870
|
...resolved.character,
|
|
9293
|
-
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 }
|
|
9294
9872
|
};
|
|
9295
9873
|
}
|
|
9296
9874
|
const characterParent = asset.state?.of ?? asset.animation?.of ?? asset.portrait?.of;
|
|
@@ -9303,7 +9881,7 @@ async function resolveSpecs(loaded, filter) {
|
|
|
9303
9881
|
parentAssetId: characterParent,
|
|
9304
9882
|
parentSpec,
|
|
9305
9883
|
parentFile,
|
|
9306
|
-
parentSha256: (0,
|
|
9884
|
+
parentSha256: (0, import_node_fs7.existsSync)(parentFile) ? await sha256File(parentFile) : null,
|
|
9307
9885
|
// A state and its animations keep the engine and rotations of the base.
|
|
9308
9886
|
mode: resolved.character.kind === "base" ? resolved.character.mode : parentSpec.character?.mode ?? resolved.character.mode,
|
|
9309
9887
|
directions: resolved.character.kind === "base" ? resolved.character.directions : parentSpec.character?.directions ?? resolved.character.directions
|
|
@@ -9321,7 +9899,7 @@ async function resolveSpecs(loaded, filter) {
|
|
|
9321
9899
|
parentAssetId: characterParent,
|
|
9322
9900
|
parentSpec,
|
|
9323
9901
|
parentFile,
|
|
9324
|
-
parentSha256: (0,
|
|
9902
|
+
parentSha256: (0, import_node_fs7.existsSync)(parentFile) ? await sha256File(parentFile) : null,
|
|
9325
9903
|
// A state and its animations keep the base's own rotation count.
|
|
9326
9904
|
directions: resolved.objectPro.kind === "base" ? resolved.objectPro.directions : parentSpec.objectPro.directions
|
|
9327
9905
|
};
|
|
@@ -9343,7 +9921,7 @@ async function resolveSpecs(loaded, filter) {
|
|
|
9343
9921
|
animation: sourceSpec.character.animation,
|
|
9344
9922
|
outfit: {
|
|
9345
9923
|
reference: {
|
|
9346
|
-
path:
|
|
9924
|
+
path: import_node_path7.default.resolve(root, asset.outfit.reference),
|
|
9347
9925
|
sha256: referenceImage.hash,
|
|
9348
9926
|
width: referenceImage.width,
|
|
9349
9927
|
height: referenceImage.height,
|
|
@@ -9366,23 +9944,23 @@ async function resolveSpecs(loaded, filter) {
|
|
|
9366
9944
|
}
|
|
9367
9945
|
const sourceStyleId = asset.revision.fromStyle && asset.revision.fromStyle !== styleId ? asset.revision.fromStyle : void 0;
|
|
9368
9946
|
const sourceSpec = sourceStyleId ? await styleFinalizers.get(sourceStyleId)(asset.revision.from) : await finalize(asset.revision.from);
|
|
9369
|
-
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);
|
|
9370
9948
|
const sourceMembers = await revisionSourceMembers(sourceStem, `revision source ${asset.revision.from}`);
|
|
9371
9949
|
const sourceFile = sourceMembers?.[0]?.file ?? sourceStem;
|
|
9372
9950
|
const sourceImage = sourceMembers ? { ...sourceMembers[0], hash: sha256(JSON.stringify(sourceMembers.map((member) => member.sha256))) } : await optionalRevisionImage(sourceFile, "revision source");
|
|
9373
9951
|
const sourceFps = sourceSpec.character?.animation?.fps ?? sourceSpec.objectPro?.animation?.fps ?? sourceSpec.revision?.fps ?? sourceSpec.revision?.sourceFps;
|
|
9374
|
-
const maskFile = asset.revision.mask ?
|
|
9952
|
+
const maskFile = asset.revision.mask ? import_node_path7.default.resolve(root, asset.revision.mask) : void 0;
|
|
9375
9953
|
const maskImage = maskFile ? await optionalRevisionImage(maskFile, "revision mask", "png") : null;
|
|
9376
9954
|
if (sourceImage && maskImage && (sourceImage.width !== maskImage.width || sourceImage.height !== maskImage.height)) {
|
|
9377
9955
|
throw new Error(
|
|
9378
9956
|
`Revision mask for ${styleId}/${assetId} is ${maskImage.width}x${maskImage.height}; source ${asset.revision.from} is ${sourceImage.width}x${sourceImage.height}`
|
|
9379
9957
|
);
|
|
9380
9958
|
}
|
|
9381
|
-
const paletteImageFile = asset.revision.paletteImage ?
|
|
9959
|
+
const paletteImageFile = asset.revision.paletteImage ? import_node_path7.default.resolve(root, asset.revision.paletteImage) : void 0;
|
|
9382
9960
|
const paletteImage = paletteImageFile ? await optionalRevisionImage(paletteImageFile, "revision palette image", "png") : null;
|
|
9383
|
-
const lastFrameFile = asset.revision.lastFrame ?
|
|
9961
|
+
const lastFrameFile = asset.revision.lastFrame ? import_node_path7.default.resolve(root, asset.revision.lastFrame) : void 0;
|
|
9384
9962
|
const lastFrame = lastFrameFile ? await optionalRevisionImage(lastFrameFile, "revision last frame") : null;
|
|
9385
|
-
const keypointsFile = asset.revision.keypointsFile ?
|
|
9963
|
+
const keypointsFile = asset.revision.keypointsFile ? import_node_path7.default.resolve(root, asset.revision.keypointsFile) : void 0;
|
|
9386
9964
|
const keypoints = keypointsFile ? await optionalSkeletonSet(keypointsFile) : null;
|
|
9387
9965
|
resolved.revision = {
|
|
9388
9966
|
mode: asset.revision.mode,
|
|
@@ -9552,7 +10130,7 @@ async function resolveSpecs(loaded, filter) {
|
|
|
9552
10130
|
return specs;
|
|
9553
10131
|
}
|
|
9554
10132
|
function pngPath(file) {
|
|
9555
|
-
const extension =
|
|
10133
|
+
const extension = import_node_path7.default.extname(file);
|
|
9556
10134
|
return extension ? `${file.slice(0, -extension.length)}.png` : `${file}.png`;
|
|
9557
10135
|
}
|
|
9558
10136
|
async function resolveMapScene(root, styleId, assetId, generator, scene, provider) {
|
|
@@ -9562,12 +10140,12 @@ async function resolveMapScene(root, styleId, assetId, generator, scene, provide
|
|
|
9562
10140
|
if (!provider.supportsMapScene) {
|
|
9563
10141
|
throw new Error(`assets.${assetId}.scene: provider "${provider.id}" cannot draw into a scene; PixelLab's /map-objects can`);
|
|
9564
10142
|
}
|
|
9565
|
-
const file =
|
|
10143
|
+
const file = import_node_path7.default.resolve(root, scene.image);
|
|
9566
10144
|
const image = await optionalRevisionImage(file, "scene image", "png");
|
|
9567
10145
|
const base = { file, sha256: image?.hash ?? null, width: image?.width ?? null, height: image?.height ?? null };
|
|
9568
10146
|
const placement = scene.placement;
|
|
9569
10147
|
if ("mask" in placement) {
|
|
9570
|
-
const maskFile =
|
|
10148
|
+
const maskFile = import_node_path7.default.resolve(root, placement.mask);
|
|
9571
10149
|
const mask = await optionalRevisionImage(maskFile, "scene mask", "png");
|
|
9572
10150
|
if (image && mask && (image.width !== mask.width || image.height !== mask.height)) {
|
|
9573
10151
|
throw new Error(
|
|
@@ -9581,9 +10159,38 @@ async function resolveMapScene(root, styleId, assetId, generator, scene, provide
|
|
|
9581
10159
|
}
|
|
9582
10160
|
return "oval" in placement ? { ...base, placement: { type: "oval", fraction: placement.oval } } : { ...base, placement: { type: "rectangle", fraction: placement.rectangle } };
|
|
9583
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
|
+
}
|
|
9584
10191
|
async function optionalRevisionImage(file, label, requiredFormat) {
|
|
9585
|
-
if (!(0,
|
|
9586
|
-
const bytes = await (0,
|
|
10192
|
+
if (!(0, import_node_fs7.existsSync)(file)) return null;
|
|
10193
|
+
const bytes = await (0, import_promises4.readFile)(file);
|
|
9587
10194
|
const metadata = imageMetadata(bytes);
|
|
9588
10195
|
if (!metadata) throw new Error(`${label} is not a readable PNG or JPEG: ${file}`);
|
|
9589
10196
|
if (requiredFormat && metadata.format !== requiredFormat) {
|
|
@@ -9592,8 +10199,8 @@ async function optionalRevisionImage(file, label, requiredFormat) {
|
|
|
9592
10199
|
return { hash: sha256(bytes), ...metadata };
|
|
9593
10200
|
}
|
|
9594
10201
|
async function optionalSkeletonSet(file) {
|
|
9595
|
-
if (!(0,
|
|
9596
|
-
const bytes = await (0,
|
|
10202
|
+
if (!(0, import_node_fs7.existsSync)(file)) return null;
|
|
10203
|
+
const bytes = await (0, import_promises4.readFile)(file);
|
|
9597
10204
|
let json;
|
|
9598
10205
|
try {
|
|
9599
10206
|
json = JSON.parse(bytes.toString("utf8"));
|
|
@@ -9603,17 +10210,17 @@ async function optionalSkeletonSet(file) {
|
|
|
9603
10210
|
return { hash: sha256(bytes), skeleton: parseSkeletonSet(json, file) };
|
|
9604
10211
|
}
|
|
9605
10212
|
async function revisionSourceMembers(stem, label) {
|
|
9606
|
-
if ((0,
|
|
10213
|
+
if ((0, import_node_fs7.existsSync)(stem)) return null;
|
|
9607
10214
|
const at = (role) => memberPath(stem, role, 0, 2, MediaType.PNG);
|
|
9608
10215
|
let roles = [];
|
|
9609
10216
|
for (const set of [CHARACTER_DIRECTIONS_8, CHARACTER_DIRECTIONS_4]) {
|
|
9610
|
-
if (set.every((direction) => (0,
|
|
10217
|
+
if (set.every((direction) => (0, import_node_fs7.existsSync)(at(direction)))) {
|
|
9611
10218
|
roles = [...set];
|
|
9612
10219
|
break;
|
|
9613
10220
|
}
|
|
9614
10221
|
}
|
|
9615
10222
|
if (!roles.length) {
|
|
9616
|
-
for (let index = 0; (0,
|
|
10223
|
+
for (let index = 0; (0, import_node_fs7.existsSync)(at(`frame-${String(index).padStart(2, "0")}`)); index++) {
|
|
9617
10224
|
roles.push(`frame-${String(index).padStart(2, "0")}`);
|
|
9618
10225
|
}
|
|
9619
10226
|
}
|
|
@@ -9639,8 +10246,8 @@ async function resolveStyleImages(loaded, styleId) {
|
|
|
9639
10246
|
if (!style) throw new Error(`Unknown style "${styleId}"`);
|
|
9640
10247
|
const out = [];
|
|
9641
10248
|
for (const img of style.styleImages) {
|
|
9642
|
-
const file =
|
|
9643
|
-
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);
|
|
9644
10251
|
const metadata = imageMetadata(buf);
|
|
9645
10252
|
if (!metadata) throw new Error(`Style image is not a readable PNG or JPEG: ${file}`);
|
|
9646
10253
|
if (metadata.width < 1 || metadata.height < 1) {
|
|
@@ -9670,7 +10277,7 @@ async function resolveCharacterShape(asset, style, kind, files) {
|
|
|
9670
10277
|
};
|
|
9671
10278
|
if (asset.concept) {
|
|
9672
10279
|
const image = await files.load(asset.concept, "Concept image");
|
|
9673
|
-
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 };
|
|
9674
10281
|
}
|
|
9675
10282
|
if (asset.reference) {
|
|
9676
10283
|
const byDirection = typeof asset.reference === "string" ? { south: asset.reference } : asset.reference;
|
|
@@ -9678,7 +10285,7 @@ async function resolveCharacterShape(asset, style, kind, files) {
|
|
|
9678
10285
|
for (const [direction, rel] of Object.entries(byDirection)) {
|
|
9679
10286
|
const image = await files.load(rel, `Reference sprite (${direction})`);
|
|
9680
10287
|
shape.reference[direction] = {
|
|
9681
|
-
path:
|
|
10288
|
+
path: import_node_path7.default.resolve(files.root, rel),
|
|
9682
10289
|
sha256: image.hash,
|
|
9683
10290
|
width: image.width,
|
|
9684
10291
|
height: image.height,
|
|
@@ -9699,7 +10306,7 @@ async function resolveCharacterShape(asset, style, kind, files) {
|
|
|
9699
10306
|
const animationMode = asset.animation.mode ?? (asset.animation.template ? "template" : "v3");
|
|
9700
10307
|
const pose = async (rel, what) => {
|
|
9701
10308
|
const image = await files.load(rel, what);
|
|
9702
|
-
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 };
|
|
9703
10310
|
};
|
|
9704
10311
|
shape.animation = {
|
|
9705
10312
|
mode: animationMode,
|
|
@@ -9730,7 +10337,7 @@ async function resolveObjectProShape(assetId, asset, style, kind, files) {
|
|
|
9730
10337
|
const south = typeof asset.reference === "string" ? asset.reference : asset.reference.south;
|
|
9731
10338
|
if (south) {
|
|
9732
10339
|
const image = await files.load(south, "Reference sprite");
|
|
9733
|
-
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 };
|
|
9734
10341
|
}
|
|
9735
10342
|
}
|
|
9736
10343
|
if (asset.state) {
|
|
@@ -9758,7 +10365,7 @@ async function resolveObjectProShape(assetId, asset, style, kind, files) {
|
|
|
9758
10365
|
}
|
|
9759
10366
|
const pose = async (rel, what) => {
|
|
9760
10367
|
const image = await files.load(rel, what);
|
|
9761
|
-
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 };
|
|
9762
10369
|
};
|
|
9763
10370
|
shape.animation = {
|
|
9764
10371
|
mode: asset.animation.mode ?? "v3",
|
|
@@ -9778,11 +10385,11 @@ function characterDirections(directions) {
|
|
|
9778
10385
|
}
|
|
9779
10386
|
|
|
9780
10387
|
// src/project.ts
|
|
9781
|
-
var
|
|
10388
|
+
var import_node_path15 = __toESM(require("path"), 1);
|
|
9782
10389
|
|
|
9783
10390
|
// src/env.ts
|
|
9784
|
-
var
|
|
9785
|
-
var
|
|
10391
|
+
var import_node_fs8 = require("fs");
|
|
10392
|
+
var import_node_path8 = __toESM(require("path"), 1);
|
|
9786
10393
|
function loadEnvFiles(dir) {
|
|
9787
10394
|
const loaded = [];
|
|
9788
10395
|
for (const [file, vars] of envFiles(dir)) {
|
|
@@ -9796,10 +10403,10 @@ function loadEnvFiles(dir) {
|
|
|
9796
10403
|
function envFiles(dir) {
|
|
9797
10404
|
const out = [];
|
|
9798
10405
|
for (const name of [".env.local", ".env"]) {
|
|
9799
|
-
const file =
|
|
9800
|
-
if (!(0,
|
|
10406
|
+
const file = import_node_path8.default.join(dir, name);
|
|
10407
|
+
if (!(0, import_node_fs8.existsSync)(file)) continue;
|
|
9801
10408
|
try {
|
|
9802
|
-
out.push([file, parseEnv((0,
|
|
10409
|
+
out.push([file, parseEnv((0, import_node_fs8.readFileSync)(file, "utf8"))]);
|
|
9803
10410
|
} catch {
|
|
9804
10411
|
}
|
|
9805
10412
|
}
|
|
@@ -9825,12 +10432,12 @@ function parseEnv(contents) {
|
|
|
9825
10432
|
}
|
|
9826
10433
|
|
|
9827
10434
|
// src/pipeline/plan.ts
|
|
9828
|
-
var
|
|
9829
|
-
var
|
|
10435
|
+
var import_node_fs11 = require("fs");
|
|
10436
|
+
var import_node_path14 = __toESM(require("path"), 1);
|
|
9830
10437
|
|
|
9831
10438
|
// src/pipeline/mirror.ts
|
|
9832
|
-
var
|
|
9833
|
-
var
|
|
10439
|
+
var import_promises5 = require("fs/promises");
|
|
10440
|
+
var import_node_path9 = __toESM(require("path"), 1);
|
|
9834
10441
|
|
|
9835
10442
|
// src/palette.ts
|
|
9836
10443
|
function normalizePalette(values) {
|
|
@@ -9967,12 +10574,12 @@ async function deriveMirror(spec, lock, lockPath, opts = {}) {
|
|
|
9967
10574
|
const outputs = [];
|
|
9968
10575
|
const written = [];
|
|
9969
10576
|
for (const [index, member] of members.entries()) {
|
|
9970
|
-
const bytes = flipHorizontal(await (0,
|
|
10577
|
+
const bytes = flipHorizontal(await (0, import_promises5.readFile)(member.file));
|
|
9971
10578
|
const target = expectedOutputPath(spec, member.role, index, members.length, MediaType.PNG);
|
|
9972
|
-
await (0,
|
|
10579
|
+
await (0, import_promises5.mkdir)(import_node_path9.default.dirname(target), { recursive: true });
|
|
9973
10580
|
const tmp = `${target}.pixelkiln.tmp`;
|
|
9974
|
-
await (0,
|
|
9975
|
-
await (0,
|
|
10581
|
+
await (0, import_promises5.writeFile)(tmp, bytes);
|
|
10582
|
+
await (0, import_promises5.rename)(tmp, target);
|
|
9976
10583
|
written.push(target);
|
|
9977
10584
|
outputs.push({
|
|
9978
10585
|
path: portableOutputPath(target, spec.root),
|
|
@@ -10031,20 +10638,20 @@ async function deriveMirror(spec, lock, lockPath, opts = {}) {
|
|
|
10031
10638
|
}
|
|
10032
10639
|
|
|
10033
10640
|
// src/pipeline/quality-profile.ts
|
|
10034
|
-
var
|
|
10035
|
-
var
|
|
10641
|
+
var import_node_fs9 = require("fs");
|
|
10642
|
+
var import_node_path12 = __toESM(require("path"), 1);
|
|
10036
10643
|
|
|
10037
10644
|
// src/pipeline/refine.ts
|
|
10038
|
-
var
|
|
10645
|
+
var import_node_path11 = __toESM(require("path"), 1);
|
|
10039
10646
|
var import_node_child_process2 = require("child_process");
|
|
10040
|
-
var
|
|
10647
|
+
var import_promises7 = require("fs/promises");
|
|
10041
10648
|
var import_node_os3 = require("os");
|
|
10042
10649
|
var import_node_util = require("util");
|
|
10043
10650
|
|
|
10044
10651
|
// src/artifacts.ts
|
|
10045
|
-
var
|
|
10652
|
+
var import_node_path10 = __toESM(require("path"), 1);
|
|
10046
10653
|
var import_node_crypto6 = require("crypto");
|
|
10047
|
-
var
|
|
10654
|
+
var import_promises6 = require("fs/promises");
|
|
10048
10655
|
var activeTransactions = /* @__PURE__ */ new Set();
|
|
10049
10656
|
function message(error) {
|
|
10050
10657
|
return error instanceof Error ? error.message : String(error);
|
|
@@ -10053,7 +10660,7 @@ function digest(data) {
|
|
|
10053
10660
|
return (0, import_node_crypto6.createHash)("sha256").update(data).digest("hex");
|
|
10054
10661
|
}
|
|
10055
10662
|
function portableRelative(from, to) {
|
|
10056
|
-
return
|
|
10663
|
+
return import_node_path10.default.relative(from, import_node_path10.default.resolve(to)).split(import_node_path10.default.sep).join("/") || ".";
|
|
10057
10664
|
}
|
|
10058
10665
|
function canonical2(value) {
|
|
10059
10666
|
if (value === null || typeof value === "string" || typeof value === "boolean") return value;
|
|
@@ -10103,12 +10710,12 @@ function parseArtifactManifest(absolute, data) {
|
|
|
10103
10710
|
return raw;
|
|
10104
10711
|
}
|
|
10105
10712
|
async function readArtifactBundleManifest(manifestPath) {
|
|
10106
|
-
const absolute =
|
|
10107
|
-
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"));
|
|
10108
10715
|
}
|
|
10109
10716
|
async function readOptional(file) {
|
|
10110
10717
|
try {
|
|
10111
|
-
return await (0,
|
|
10718
|
+
return await (0, import_promises6.readFile)(file);
|
|
10112
10719
|
} catch (error) {
|
|
10113
10720
|
if (isCode(error, "ENOENT")) return null;
|
|
10114
10721
|
throw error;
|
|
@@ -10127,8 +10734,8 @@ function processIsAlive(pid) {
|
|
|
10127
10734
|
}
|
|
10128
10735
|
}
|
|
10129
10736
|
function validTemporaryPath(candidate, destination, type) {
|
|
10130
|
-
return
|
|
10131
|
-
`.${
|
|
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}-`
|
|
10132
10739
|
);
|
|
10133
10740
|
}
|
|
10134
10741
|
function parseTransaction(journal, data) {
|
|
@@ -10144,14 +10751,14 @@ function parseTransaction(journal, data) {
|
|
|
10144
10751
|
return raw;
|
|
10145
10752
|
}
|
|
10146
10753
|
async function removeTransactionFiles(journal) {
|
|
10147
|
-
await (0,
|
|
10148
|
-
await (0,
|
|
10754
|
+
await (0, import_promises6.rm)(journal, { force: true });
|
|
10755
|
+
await (0, import_promises6.rm)(transactionMarker(journal), { force: true });
|
|
10149
10756
|
}
|
|
10150
10757
|
async function recoverArtifactTransaction(recoveryFile, allowedDestinations) {
|
|
10151
|
-
const journal =
|
|
10758
|
+
const journal = import_node_path10.default.resolve(recoveryFile);
|
|
10152
10759
|
const bytes = await readOptional(journal);
|
|
10153
10760
|
if (!bytes) {
|
|
10154
|
-
await (0,
|
|
10761
|
+
await (0, import_promises6.rm)(transactionMarker(journal), { force: true });
|
|
10155
10762
|
return;
|
|
10156
10763
|
}
|
|
10157
10764
|
if (activeTransactions.has(journal)) {
|
|
@@ -10164,7 +10771,7 @@ async function recoverArtifactTransaction(recoveryFile, allowedDestinations) {
|
|
|
10164
10771
|
);
|
|
10165
10772
|
}
|
|
10166
10773
|
for (const entry of transaction.entries) {
|
|
10167
|
-
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")) {
|
|
10168
10775
|
throw new Error(
|
|
10169
10776
|
`Refusing unsafe artifact recovery from ${journal}; its destinations or temporary paths do not match the current bundle.`
|
|
10170
10777
|
);
|
|
@@ -10190,7 +10797,7 @@ async function recoverArtifactTransaction(recoveryFile, allowedDestinations) {
|
|
|
10190
10797
|
}
|
|
10191
10798
|
await remove2(entry.destination, errors);
|
|
10192
10799
|
try {
|
|
10193
|
-
await (0,
|
|
10800
|
+
await (0, import_promises6.rename)(entry.backup, entry.destination);
|
|
10194
10801
|
} catch (error) {
|
|
10195
10802
|
errors.push(
|
|
10196
10803
|
`could not restore ${entry.destination}; previous file remains at ${entry.backup}: ${message(error)}`
|
|
@@ -10210,7 +10817,7 @@ async function recoverArtifactTransaction(recoveryFile, allowedDestinations) {
|
|
|
10210
10817
|
await removeTransactionFiles(journal);
|
|
10211
10818
|
}
|
|
10212
10819
|
function createArtifactBundleManifest(manifestPath, outputs, provenance) {
|
|
10213
|
-
const root =
|
|
10820
|
+
const root = import_node_path10.default.dirname(import_node_path10.default.resolve(manifestPath));
|
|
10214
10821
|
const sources = provenance.sources.map((source) => ({
|
|
10215
10822
|
...source,
|
|
10216
10823
|
path: portableRelative(root, source.path)
|
|
@@ -10240,11 +10847,11 @@ function withArtifactManifest(manifestPath, outputs, provenance) {
|
|
|
10240
10847
|
];
|
|
10241
10848
|
}
|
|
10242
10849
|
async function writeManagedArtifactBundle(manifestPath, outputs, provenance, options = {}) {
|
|
10243
|
-
const absoluteManifest =
|
|
10850
|
+
const absoluteManifest = import_node_path10.default.resolve(manifestPath);
|
|
10244
10851
|
const recoveryFile = `${absoluteManifest}.transaction`;
|
|
10245
10852
|
const allowedDestinations = /* @__PURE__ */ new Set([
|
|
10246
10853
|
absoluteManifest,
|
|
10247
|
-
...outputs.map((output) =>
|
|
10854
|
+
...outputs.map((output) => import_node_path10.default.resolve(output.path))
|
|
10248
10855
|
]);
|
|
10249
10856
|
await recoverArtifactTransaction(recoveryFile, allowedDestinations);
|
|
10250
10857
|
const existingManifestBytes = await readOptional(absoluteManifest);
|
|
@@ -10262,13 +10869,13 @@ async function writeManagedArtifactBundle(manifestPath, outputs, provenance, opt
|
|
|
10262
10869
|
}
|
|
10263
10870
|
}
|
|
10264
10871
|
if (!options.force) {
|
|
10265
|
-
const root =
|
|
10872
|
+
const root = import_node_path10.default.dirname(absoluteManifest);
|
|
10266
10873
|
const recorded = new Map(
|
|
10267
|
-
previous?.outputs.map((output) => [
|
|
10874
|
+
previous?.outputs.map((output) => [import_node_path10.default.resolve(root, output.path), output.sha256]) ?? []
|
|
10268
10875
|
);
|
|
10269
10876
|
const conflicts = [];
|
|
10270
10877
|
for (const output of outputs) {
|
|
10271
|
-
const destination =
|
|
10878
|
+
const destination = import_node_path10.default.resolve(output.path);
|
|
10272
10879
|
const current = await readOptional(destination);
|
|
10273
10880
|
if (!current || digest(current) === digest(output.data)) continue;
|
|
10274
10881
|
const expected = recorded.get(destination);
|
|
@@ -10290,15 +10897,15 @@ Pass { force: true } (CLI: --force) to take ownership and replace the complete b
|
|
|
10290
10897
|
);
|
|
10291
10898
|
}
|
|
10292
10899
|
async function verifyArtifactBundle(manifestPath) {
|
|
10293
|
-
const absolute =
|
|
10900
|
+
const absolute = import_node_path10.default.resolve(manifestPath);
|
|
10294
10901
|
const raw = await readArtifactBundleManifest(absolute);
|
|
10295
|
-
const root =
|
|
10902
|
+
const root = import_node_path10.default.dirname(absolute);
|
|
10296
10903
|
const changedSources = [];
|
|
10297
10904
|
const changedOutputs = [];
|
|
10298
10905
|
for (const source of raw.sources) {
|
|
10299
10906
|
let actual = null;
|
|
10300
10907
|
try {
|
|
10301
|
-
actual = digest(await (0,
|
|
10908
|
+
actual = digest(await (0, import_promises6.readFile)(import_node_path10.default.resolve(root, source.path)));
|
|
10302
10909
|
} catch (error) {
|
|
10303
10910
|
if (!isCode(error, "ENOENT")) throw error;
|
|
10304
10911
|
}
|
|
@@ -10306,7 +10913,7 @@ async function verifyArtifactBundle(manifestPath) {
|
|
|
10306
10913
|
}
|
|
10307
10914
|
for (const output of raw.outputs) {
|
|
10308
10915
|
try {
|
|
10309
|
-
if (digest(await (0,
|
|
10916
|
+
if (digest(await (0, import_promises6.readFile)(import_node_path10.default.resolve(root, output.path))) !== output.sha256) {
|
|
10310
10917
|
changedOutputs.push(output.path);
|
|
10311
10918
|
}
|
|
10312
10919
|
} catch (error) {
|
|
@@ -10332,7 +10939,7 @@ function isCode(error, code) {
|
|
|
10332
10939
|
}
|
|
10333
10940
|
async function remove2(file, errors) {
|
|
10334
10941
|
try {
|
|
10335
|
-
await (0,
|
|
10942
|
+
await (0, import_promises6.rm)(file, { force: true });
|
|
10336
10943
|
} catch (error) {
|
|
10337
10944
|
errors?.push(`could not remove ${file}: ${message(error)}`);
|
|
10338
10945
|
}
|
|
@@ -10345,7 +10952,7 @@ async function rollback(prepared) {
|
|
|
10345
10952
|
for (const artifact of [...prepared].reverse()) {
|
|
10346
10953
|
if (!artifact.backup || !artifact.backedUp) continue;
|
|
10347
10954
|
try {
|
|
10348
|
-
await (0,
|
|
10955
|
+
await (0, import_promises6.rename)(artifact.backup, artifact.destination);
|
|
10349
10956
|
artifact.backedUp = false;
|
|
10350
10957
|
} catch (error) {
|
|
10351
10958
|
errors.push(
|
|
@@ -10362,7 +10969,7 @@ async function writeArtifactBundle(files, options = {}) {
|
|
|
10362
10969
|
if (!files.length) throw new Error("An artifact bundle must contain at least one file.");
|
|
10363
10970
|
const normalized = files.map((file) => {
|
|
10364
10971
|
if (!file.path.trim()) throw new Error("Artifact paths cannot be empty.");
|
|
10365
|
-
return { destination:
|
|
10972
|
+
return { destination: import_node_path10.default.resolve(file.path), data: Buffer.from(file.data) };
|
|
10366
10973
|
});
|
|
10367
10974
|
const destinations = /* @__PURE__ */ new Set();
|
|
10368
10975
|
for (const artifact of normalized) {
|
|
@@ -10378,7 +10985,7 @@ async function writeArtifactBundle(files, options = {}) {
|
|
|
10378
10985
|
const unchanged = [];
|
|
10379
10986
|
for (const artifact of normalized) {
|
|
10380
10987
|
try {
|
|
10381
|
-
const current = await (0,
|
|
10988
|
+
const current = await (0, import_promises6.readFile)(artifact.destination);
|
|
10382
10989
|
if (current.equals(artifact.data)) {
|
|
10383
10990
|
unchanged.push(artifact.destination);
|
|
10384
10991
|
} else {
|
|
@@ -10392,21 +10999,21 @@ async function writeArtifactBundle(files, options = {}) {
|
|
|
10392
10999
|
if (!changed.length) return { changed: [], unchanged };
|
|
10393
11000
|
const token = (0, import_node_crypto6.randomUUID)();
|
|
10394
11001
|
for (const [index, artifact] of changed.entries()) {
|
|
10395
|
-
const basename =
|
|
10396
|
-
artifact.stage =
|
|
10397
|
-
|
|
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),
|
|
10398
11005
|
`.${basename}.pixelkiln-stage-${token}-${index}`
|
|
10399
11006
|
);
|
|
10400
11007
|
if (artifact.existed) {
|
|
10401
|
-
artifact.backup =
|
|
10402
|
-
|
|
11008
|
+
artifact.backup = import_node_path10.default.join(
|
|
11009
|
+
import_node_path10.default.dirname(artifact.destination),
|
|
10403
11010
|
`.${basename}.pixelkiln-backup-${token}-${index}`
|
|
10404
11011
|
);
|
|
10405
11012
|
}
|
|
10406
11013
|
}
|
|
10407
|
-
const journal = options.recoveryFile ?
|
|
11014
|
+
const journal = options.recoveryFile ? import_node_path10.default.resolve(options.recoveryFile) : null;
|
|
10408
11015
|
if (journal) {
|
|
10409
|
-
await (0,
|
|
11016
|
+
await (0, import_promises6.mkdir)(import_node_path10.default.dirname(journal), { recursive: true });
|
|
10410
11017
|
const transaction = {
|
|
10411
11018
|
format: "pixelkiln-artifact-transaction",
|
|
10412
11019
|
version: 1,
|
|
@@ -10420,7 +11027,7 @@ async function writeArtifactBundle(files, options = {}) {
|
|
|
10420
11027
|
}))
|
|
10421
11028
|
};
|
|
10422
11029
|
try {
|
|
10423
|
-
await (0,
|
|
11030
|
+
await (0, import_promises6.writeFile)(journal, JSON.stringify(transaction, null, 2) + "\n", { flag: "wx" });
|
|
10424
11031
|
activeTransactions.add(journal);
|
|
10425
11032
|
} catch (error) {
|
|
10426
11033
|
throw new Error(
|
|
@@ -10431,9 +11038,9 @@ async function writeArtifactBundle(files, options = {}) {
|
|
|
10431
11038
|
}
|
|
10432
11039
|
try {
|
|
10433
11040
|
for (const [index, artifact] of changed.entries()) {
|
|
10434
|
-
await (0,
|
|
11041
|
+
await (0, import_promises6.mkdir)(import_node_path10.default.dirname(artifact.destination), { recursive: true });
|
|
10435
11042
|
await options.beforeStage?.(artifact.destination, index);
|
|
10436
|
-
await (0,
|
|
11043
|
+
await (0, import_promises6.writeFile)(artifact.stage, artifact.data, { flag: "wx" });
|
|
10437
11044
|
}
|
|
10438
11045
|
} catch (error) {
|
|
10439
11046
|
const cleanupErrors2 = await rollback(changed);
|
|
@@ -10446,17 +11053,17 @@ async function writeArtifactBundle(files, options = {}) {
|
|
|
10446
11053
|
try {
|
|
10447
11054
|
for (const artifact of changed) {
|
|
10448
11055
|
if (!artifact.existed) continue;
|
|
10449
|
-
await (0,
|
|
11056
|
+
await (0, import_promises6.rename)(artifact.destination, artifact.backup);
|
|
10450
11057
|
artifact.backedUp = true;
|
|
10451
11058
|
}
|
|
10452
11059
|
for (const [index, artifact] of changed.entries()) {
|
|
10453
11060
|
await options.beforePromote?.(artifact.destination, index);
|
|
10454
|
-
await (0,
|
|
11061
|
+
await (0, import_promises6.rename)(artifact.stage, artifact.destination);
|
|
10455
11062
|
artifact.stage = void 0;
|
|
10456
11063
|
artifact.promoted = true;
|
|
10457
11064
|
}
|
|
10458
11065
|
if (journal) {
|
|
10459
|
-
await (0,
|
|
11066
|
+
await (0, import_promises6.writeFile)(transactionMarker(journal), `${token}
|
|
10460
11067
|
`, { flag: "wx" });
|
|
10461
11068
|
durableCommit = true;
|
|
10462
11069
|
await options.afterCommit?.();
|
|
@@ -10697,16 +11304,16 @@ function sameFrameGrid(first, next) {
|
|
|
10697
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;
|
|
10698
11305
|
}
|
|
10699
11306
|
async function refineAsset(options) {
|
|
10700
|
-
const source =
|
|
10701
|
-
const output =
|
|
11307
|
+
const source = import_node_path11.default.resolve(options.source);
|
|
11308
|
+
const output = import_node_path11.default.resolve(options.output);
|
|
10702
11309
|
if (source === output) throw new Error("Refinement source and output must be different files.");
|
|
10703
|
-
const record =
|
|
11310
|
+
const record = import_node_path11.default.resolve(qualityRecordPath(output));
|
|
10704
11311
|
const palette = normalizePalette(options.palette).map((color) => color.hex);
|
|
10705
11312
|
const minGridConfidence = requireConfidence(options.minGridConfidence ?? "high");
|
|
10706
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.");
|
|
10707
11314
|
let sourcePng;
|
|
10708
11315
|
try {
|
|
10709
|
-
sourcePng = decodePng(await (0,
|
|
11316
|
+
sourcePng = decodePng(await (0, import_promises7.readFile)(source));
|
|
10710
11317
|
} catch (error) {
|
|
10711
11318
|
throw new Error(`Cannot read refinement source ${source}: ${message2(error)}`, { cause: error });
|
|
10712
11319
|
}
|
|
@@ -10723,8 +11330,8 @@ async function refineAsset(options) {
|
|
|
10723
11330
|
);
|
|
10724
11331
|
}
|
|
10725
11332
|
}
|
|
10726
|
-
const temp = await (0,
|
|
10727
|
-
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");
|
|
10728
11335
|
const fixerCommand = compatibilityCommand ?? options.fixerPython ?? process.env.PIXELKILN_PIXEL_FIXER_PYTHON ?? "python3";
|
|
10729
11336
|
const args = compatibilityCommand ? [...options.fixerArgsPrefix ?? [], source, "--extract", recovered] : ["-c", PYTHON_FIXER_BRIDGE, source, recovered];
|
|
10730
11337
|
try {
|
|
@@ -10745,7 +11352,7 @@ async function refineAsset(options) {
|
|
|
10745
11352
|
);
|
|
10746
11353
|
}
|
|
10747
11354
|
const detection = parseDetection(firstJsonObject(stdout));
|
|
10748
|
-
const native = decodePng(await (0,
|
|
11355
|
+
const native = decodePng(await (0, import_promises7.readFile)(recovered));
|
|
10749
11356
|
const quantized = quantizeToPalette(native, palette);
|
|
10750
11357
|
const png = encodeRgbaPng(quantized.width, quantized.height, quantized.pixels);
|
|
10751
11358
|
const audit = auditRefinement(
|
|
@@ -10795,7 +11402,7 @@ async function refineAsset(options) {
|
|
|
10795
11402
|
);
|
|
10796
11403
|
return { source, output, record, detection, palette, audit };
|
|
10797
11404
|
} finally {
|
|
10798
|
-
await (0,
|
|
11405
|
+
await (0, import_promises7.rm)(temp, { recursive: true, force: true });
|
|
10799
11406
|
}
|
|
10800
11407
|
}
|
|
10801
11408
|
async function refineFrameSet(options) {
|
|
@@ -10809,18 +11416,18 @@ async function refineFrameSet(options) {
|
|
|
10809
11416
|
if (roles.some((role) => !role.trim()) || new Set(roles).size !== roles.length) {
|
|
10810
11417
|
throw new Error("Frame-set roles must be non-empty and unique.");
|
|
10811
11418
|
}
|
|
10812
|
-
const output =
|
|
10813
|
-
const record =
|
|
11419
|
+
const output = import_node_path11.default.resolve(options.output);
|
|
11420
|
+
const record = import_node_path11.default.resolve(qualityRecordPath(output));
|
|
10814
11421
|
const palette = normalizeRefinementPalette(options.palette);
|
|
10815
11422
|
const finalOutputs = options.sources.map((source, index) => qualityFrameOutputPath(output, source.role, index));
|
|
10816
11423
|
if (new Set(finalOutputs).size !== finalOutputs.length) {
|
|
10817
11424
|
throw new Error("Frame-set roles resolve to duplicate output filenames.");
|
|
10818
11425
|
}
|
|
10819
|
-
const sourceSet = new Set(options.sources.map((source) =>
|
|
11426
|
+
const sourceSet = new Set(options.sources.map((source) => import_node_path11.default.resolve(source.path)));
|
|
10820
11427
|
if (finalOutputs.some((file) => sourceSet.has(file))) {
|
|
10821
11428
|
throw new Error("Frame-set refinement sources and outputs must be different files.");
|
|
10822
11429
|
}
|
|
10823
|
-
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-"));
|
|
10824
11431
|
try {
|
|
10825
11432
|
const frames = [];
|
|
10826
11433
|
const outputs = [];
|
|
@@ -10828,8 +11435,8 @@ async function refineFrameSet(options) {
|
|
|
10828
11435
|
const sourcePaths = [];
|
|
10829
11436
|
let firstOptions = null;
|
|
10830
11437
|
for (const [index, sourceEntry] of options.sources.entries()) {
|
|
10831
|
-
const source =
|
|
10832
|
-
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`);
|
|
10833
11440
|
const refined = await refineAsset({
|
|
10834
11441
|
source,
|
|
10835
11442
|
output: temporaryOutput,
|
|
@@ -10861,7 +11468,7 @@ async function refineFrameSet(options) {
|
|
|
10861
11468
|
});
|
|
10862
11469
|
outputs.push({
|
|
10863
11470
|
path: finalOutputs[index],
|
|
10864
|
-
data: await (0,
|
|
11471
|
+
data: await (0, import_promises7.readFile)(temporaryOutput)
|
|
10865
11472
|
});
|
|
10866
11473
|
}
|
|
10867
11474
|
const first = frames[0];
|
|
@@ -10904,23 +11511,23 @@ async function refineFrameSet(options) {
|
|
|
10904
11511
|
);
|
|
10905
11512
|
return {
|
|
10906
11513
|
sources: sourcePaths,
|
|
10907
|
-
outputs: outputs.map((item) =>
|
|
11514
|
+
outputs: outputs.map((item) => import_node_path11.default.resolve(item.path)),
|
|
10908
11515
|
record,
|
|
10909
11516
|
frames,
|
|
10910
11517
|
palette,
|
|
10911
11518
|
audit
|
|
10912
11519
|
};
|
|
10913
11520
|
} finally {
|
|
10914
|
-
await (0,
|
|
11521
|
+
await (0, import_promises7.rm)(temp, { recursive: true, force: true });
|
|
10915
11522
|
}
|
|
10916
11523
|
}
|
|
10917
11524
|
async function checkQualityRecord(recordPath) {
|
|
10918
|
-
const record =
|
|
11525
|
+
const record = import_node_path11.default.resolve(recordPath);
|
|
10919
11526
|
const manifest = await readArtifactBundleManifest(record);
|
|
10920
11527
|
const options = parseRecordOptions(manifest, record);
|
|
10921
11528
|
const verification = await verifyArtifactBundle(record);
|
|
10922
|
-
const sources = manifest.sources.map((source2) =>
|
|
10923
|
-
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));
|
|
10924
11531
|
const source = sources[0] ?? "";
|
|
10925
11532
|
const output = outputs[0] ?? "";
|
|
10926
11533
|
const expectedCount = options.frameSet?.count ?? 1;
|
|
@@ -10953,7 +11560,7 @@ async function checkQualityRecord(recordPath) {
|
|
|
10953
11560
|
async function approveQualityRecord(recordPath, approval) {
|
|
10954
11561
|
const reviewer = approval.reviewer.trim();
|
|
10955
11562
|
if (!reviewer) throw new Error("A human reviewer name is required.");
|
|
10956
|
-
const record =
|
|
11563
|
+
const record = import_node_path11.default.resolve(recordPath);
|
|
10957
11564
|
const manifest = await readArtifactBundleManifest(record);
|
|
10958
11565
|
const options = parseRecordOptions(manifest, record);
|
|
10959
11566
|
const verification = await verifyArtifactBundle(record);
|
|
@@ -10974,14 +11581,14 @@ async function approveQualityRecord(recordPath, approval) {
|
|
|
10974
11581
|
`A refinement record must contain exactly ${expectedCount} source(s) and output(s).`
|
|
10975
11582
|
);
|
|
10976
11583
|
}
|
|
10977
|
-
const root =
|
|
11584
|
+
const root = import_node_path11.default.dirname(record);
|
|
10978
11585
|
const outputs = await Promise.all(manifest.outputs.map(async (entry) => {
|
|
10979
|
-
const outputPath =
|
|
10980
|
-
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) };
|
|
10981
11588
|
}));
|
|
10982
11589
|
const sources = manifest.sources.map((source) => ({
|
|
10983
11590
|
...source,
|
|
10984
|
-
path:
|
|
11591
|
+
path: import_node_path11.default.resolve(root, source.path)
|
|
10985
11592
|
}));
|
|
10986
11593
|
const approved = {
|
|
10987
11594
|
...options,
|
|
@@ -11013,8 +11620,8 @@ async function qualitySource(spec, lock) {
|
|
|
11013
11620
|
if (spec.generator === "frames") {
|
|
11014
11621
|
return { items: null, reason: "frame-set quality requires ordered downloaded provider outputs" };
|
|
11015
11622
|
}
|
|
11016
|
-
const source =
|
|
11017
|
-
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}` };
|
|
11018
11625
|
}
|
|
11019
11626
|
const entry = lock.entries[lockKey(spec.styleId, spec.assetId)];
|
|
11020
11627
|
if (!entry) return { items: null, reason: "raw provider output is not in the lockfile" };
|
|
@@ -11043,7 +11650,7 @@ async function qualitySource(spec, lock) {
|
|
|
11043
11650
|
}
|
|
11044
11651
|
if (role) roles.add(role);
|
|
11045
11652
|
const source = currentEntryOutputPath(entry, spec, index);
|
|
11046
|
-
if (!(0,
|
|
11653
|
+
if (!(0, import_node_fs9.existsSync)(source)) return { items: null, reason: `raw provider output is missing: ${source}` };
|
|
11047
11654
|
if (await sha256File(source) !== output.sha256) {
|
|
11048
11655
|
return { items: null, reason: "raw provider output was modified after download" };
|
|
11049
11656
|
}
|
|
@@ -11062,7 +11669,7 @@ async function inspectQualityProfile(spec, lock) {
|
|
|
11062
11669
|
}
|
|
11063
11670
|
const sourcePaths = source.items.map((item) => item.path);
|
|
11064
11671
|
const expectedOutputs = spec.generator === "frames" ? source.items.map((item, index) => qualityFrameOutputPath(output, item.role, index)) : [output];
|
|
11065
|
-
if (!(0,
|
|
11672
|
+
if (!(0, import_node_fs9.existsSync)(record)) {
|
|
11066
11673
|
return {
|
|
11067
11674
|
key,
|
|
11068
11675
|
state: "needs-refinement",
|
|
@@ -11080,7 +11687,7 @@ async function inspectQualityProfile(spec, lock) {
|
|
|
11080
11687
|
const expectedRevision = spec.quality.fixerRevision ?? PIXEL_ART_FIXER_REVISION;
|
|
11081
11688
|
const expectedTransparency = spec.quality.minTransparency ?? null;
|
|
11082
11689
|
const expectedFrameSet = spec.generator === "frames" ? { fps: spec.quality.fps ?? 12, count: source.items.length, roles: source.items.map((item) => item.role) } : null;
|
|
11083
|
-
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);
|
|
11084
11691
|
if (contractChanged) {
|
|
11085
11692
|
return {
|
|
11086
11693
|
key,
|
|
@@ -11243,8 +11850,8 @@ async function requireApprovedQualitySources(specs, lock, assetIds, outputRoles)
|
|
|
11243
11850
|
}
|
|
11244
11851
|
|
|
11245
11852
|
// src/pipeline/revision.ts
|
|
11246
|
-
var
|
|
11247
|
-
var
|
|
11853
|
+
var import_node_fs10 = require("fs");
|
|
11854
|
+
var import_node_path13 = __toESM(require("path"), 1);
|
|
11248
11855
|
async function inspectRevisionReadiness(spec, lock) {
|
|
11249
11856
|
if (spec.mirror) return inspectMirror(spec, lock, /* @__PURE__ */ new Set());
|
|
11250
11857
|
if (spec.character?.kind === "outfit") return inspectOutfit(spec, lock, /* @__PURE__ */ new Set());
|
|
@@ -11252,6 +11859,7 @@ async function inspectRevisionReadiness(spec, lock) {
|
|
|
11252
11859
|
if (spec.character?.styleAnchor) return inspectStyleAnchor(spec, lock, /* @__PURE__ */ new Set());
|
|
11253
11860
|
if (spec.objectPro?.parentSpec) return inspectObjectProParent(spec, lock, /* @__PURE__ */ new Set());
|
|
11254
11861
|
if (spec.scene) return inspectScene(spec);
|
|
11862
|
+
if (spec.vfx?.sprite && "file" in spec.vfx.sprite) return inspectVfxSprite(spec);
|
|
11255
11863
|
if (!spec.revision) return null;
|
|
11256
11864
|
return inspectRevision(spec, lock, /* @__PURE__ */ new Set());
|
|
11257
11865
|
}
|
|
@@ -11260,7 +11868,7 @@ async function inspectStyleAnchor(spec, lock, seen) {
|
|
|
11260
11868
|
const label = `style anchor ${spec.styleId}/${anchor.assetId}`;
|
|
11261
11869
|
const dependency = await inspectParent(anchor.spec, lock, seen);
|
|
11262
11870
|
if (!dependency.ready) return { ready: false, reason: `${label} is not ready: ${dependency.reason}` };
|
|
11263
|
-
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` };
|
|
11264
11872
|
if (await sha256File(anchor.file) !== anchor.sha256) {
|
|
11265
11873
|
return { ready: false, reason: `${label} changed after the manifest was resolved` };
|
|
11266
11874
|
}
|
|
@@ -11274,11 +11882,17 @@ async function inspectScene(spec) {
|
|
|
11274
11882
|
const files = [{ label: "scene image", file: scene.file, sha256: scene.sha256 }];
|
|
11275
11883
|
if (scene.placement.type === "mask") files.push({ label: "scene mask", file: scene.placement.file, sha256: scene.placement.sha256 });
|
|
11276
11884
|
for (const { label, file, sha256: sha2562 } of files) {
|
|
11277
|
-
if (!sha2562 || !(0,
|
|
11885
|
+
if (!sha2562 || !(0, import_node_fs10.existsSync)(file)) return { ready: false, reason: `${label} does not exist yet: ${file}` };
|
|
11278
11886
|
if (await sha256File(file) !== sha2562) return { ready: false, reason: `${label} changed after the manifest was resolved` };
|
|
11279
11887
|
}
|
|
11280
11888
|
return { ready: true, reason: "scene is current" };
|
|
11281
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
|
+
}
|
|
11282
11896
|
async function inspectMirror(spec, lock, seen) {
|
|
11283
11897
|
const source = spec.mirror.sourceSpec;
|
|
11284
11898
|
const label = `mirror source ${spec.styleId}/${spec.mirror.sourceAssetId}`;
|
|
@@ -11291,7 +11905,7 @@ async function inspectOutfit(spec, lock, seen) {
|
|
|
11291
11905
|
const label = `outfit source ${spec.styleId}/${character.parentAssetId}`;
|
|
11292
11906
|
const dependency = await inspectParent(character.parentSpec, lock, seen);
|
|
11293
11907
|
if (!dependency.ready) return { ready: false, reason: `${label} is not ready: ${dependency.reason}` };
|
|
11294
|
-
if (!(0,
|
|
11908
|
+
if (!(0, import_node_fs10.existsSync)(outfit.reference.path)) {
|
|
11295
11909
|
return { ready: false, reason: `outfit reference is missing: ${outfit.reference.path}` };
|
|
11296
11910
|
}
|
|
11297
11911
|
if (await sha256File(outfit.reference.path) !== outfit.reference.sha256) {
|
|
@@ -11305,7 +11919,7 @@ async function inspectCharacterParent(spec, lock, seen) {
|
|
|
11305
11919
|
const label = `${character.kind} parent ${spec.styleId}/${character.parentAssetId}`;
|
|
11306
11920
|
const dependency = await inspectParent(parentSpec, lock, seen);
|
|
11307
11921
|
if (!dependency.ready) return { ready: false, reason: `${label} is not ready: ${dependency.reason}` };
|
|
11308
|
-
if (!character.parentSha256 || !character.parentFile || !(0,
|
|
11922
|
+
if (!character.parentSha256 || !character.parentFile || !(0, import_node_fs10.existsSync)(character.parentFile)) {
|
|
11309
11923
|
return { ready: false, reason: `${label} has no generated south-facing file yet` };
|
|
11310
11924
|
}
|
|
11311
11925
|
if (await sha256File(character.parentFile) !== character.parentSha256) {
|
|
@@ -11323,7 +11937,7 @@ async function inspectObjectProParent(spec, lock, seen) {
|
|
|
11323
11937
|
const label = `${object.kind} parent ${spec.styleId}/${object.parentAssetId}`;
|
|
11324
11938
|
const dependency = await inspectParent(parentSpec, lock, seen);
|
|
11325
11939
|
if (!dependency.ready) return { ready: false, reason: `${label} is not ready: ${dependency.reason}` };
|
|
11326
|
-
if (!object.parentSha256 || !object.parentFile || !(0,
|
|
11940
|
+
if (!object.parentSha256 || !object.parentFile || !(0, import_node_fs10.existsSync)(object.parentFile)) {
|
|
11327
11941
|
return { ready: false, reason: `${label} has no generated south-facing file yet` };
|
|
11328
11942
|
}
|
|
11329
11943
|
if (await sha256File(object.parentFile) !== object.parentSha256) {
|
|
@@ -11349,7 +11963,7 @@ async function inspectRevision(spec, lock, seen) {
|
|
|
11349
11963
|
const members = await inspectSourceMembers(spec, lock);
|
|
11350
11964
|
if (!members.ready) return members;
|
|
11351
11965
|
} else {
|
|
11352
|
-
if (!revision.sourceSha256 || !(0,
|
|
11966
|
+
if (!revision.sourceSha256 || !(0, import_node_fs10.existsSync)(revision.sourceFile)) {
|
|
11353
11967
|
return { ready: false, reason: `revision source is missing: ${revision.sourceFile}` };
|
|
11354
11968
|
}
|
|
11355
11969
|
if (await sha256File(revision.sourceFile) !== revision.sourceSha256) {
|
|
@@ -11357,7 +11971,7 @@ async function inspectRevision(spec, lock, seen) {
|
|
|
11357
11971
|
}
|
|
11358
11972
|
}
|
|
11359
11973
|
if (revision.maskFile) {
|
|
11360
|
-
if (!revision.maskSha256 || !(0,
|
|
11974
|
+
if (!revision.maskSha256 || !(0, import_node_fs10.existsSync)(revision.maskFile)) {
|
|
11361
11975
|
return { ready: false, reason: `revision mask is missing: ${revision.maskFile}` };
|
|
11362
11976
|
}
|
|
11363
11977
|
if (await sha256File(revision.maskFile) !== revision.maskSha256) {
|
|
@@ -11365,7 +11979,7 @@ async function inspectRevision(spec, lock, seen) {
|
|
|
11365
11979
|
}
|
|
11366
11980
|
}
|
|
11367
11981
|
if (revision.lastFrameFile) {
|
|
11368
|
-
if (!revision.lastFrameSha256 || !(0,
|
|
11982
|
+
if (!revision.lastFrameSha256 || !(0, import_node_fs10.existsSync)(revision.lastFrameFile)) {
|
|
11369
11983
|
return { ready: false, reason: `revision last frame is missing: ${revision.lastFrameFile}` };
|
|
11370
11984
|
}
|
|
11371
11985
|
if (await sha256File(revision.lastFrameFile) !== revision.lastFrameSha256) {
|
|
@@ -11378,7 +11992,7 @@ async function inspectSourceMembers(spec, lock) {
|
|
|
11378
11992
|
const revision = spec.revision;
|
|
11379
11993
|
const members = revision.sourceMembers;
|
|
11380
11994
|
for (const member of members) {
|
|
11381
|
-
if (!(0,
|
|
11995
|
+
if (!(0, import_node_fs10.existsSync)(member.file)) return { ready: false, reason: `revision source member is missing: ${member.file}` };
|
|
11382
11996
|
if (await sha256File(member.file) !== member.sha256) {
|
|
11383
11997
|
return { ready: false, reason: `revision source member changed after the manifest was resolved: ${member.file}` };
|
|
11384
11998
|
}
|
|
@@ -11387,8 +12001,8 @@ async function inspectSourceMembers(spec, lock) {
|
|
|
11387
12001
|
if (parent.source) return { ready: true, reason: "declared source set is present" };
|
|
11388
12002
|
const entry = lock.entries[lockKey(parent.styleId, parent.assetId)];
|
|
11389
12003
|
if (!entry) return { ready: false, reason: "revision source is not in the lockfile" };
|
|
11390
|
-
const recorded = entry.outputs.map((_, index) =>
|
|
11391
|
-
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();
|
|
11392
12006
|
if (recorded.length !== found.length || recorded.some((file, index) => file !== found[index])) {
|
|
11393
12007
|
return {
|
|
11394
12008
|
ready: false,
|
|
@@ -11437,8 +12051,8 @@ async function inspectParent(spec, lock, seen) {
|
|
|
11437
12051
|
};
|
|
11438
12052
|
}
|
|
11439
12053
|
if (spec.source) {
|
|
11440
|
-
const source =
|
|
11441
|
-
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}` };
|
|
11442
12056
|
}
|
|
11443
12057
|
const entry = lock.entries[key];
|
|
11444
12058
|
if (!entry) return { ready: false, reason: "parent is not in the lockfile" };
|
|
@@ -11452,7 +12066,7 @@ async function inspectParent(spec, lock, seen) {
|
|
|
11452
12066
|
for (let index = 0; index < entry.outputs.length; index++) {
|
|
11453
12067
|
const output = entry.outputs[index];
|
|
11454
12068
|
const file = currentEntryOutputPath(entry, spec, index);
|
|
11455
|
-
if (!(0,
|
|
12069
|
+
if (!(0, import_node_fs10.existsSync)(file)) return { ready: false, reason: `parent output is missing: ${file}` };
|
|
11456
12070
|
if (await sha256File(file) !== output.sha256) {
|
|
11457
12071
|
return { ready: false, reason: `parent output was modified: ${file}` };
|
|
11458
12072
|
}
|
|
@@ -11515,8 +12129,8 @@ async function buildPlan(specs, lock, opts = {}) {
|
|
|
11515
12129
|
} else if (opts.force) {
|
|
11516
12130
|
state = "missing";
|
|
11517
12131
|
reason = "--force";
|
|
11518
|
-
} else if (!entry && spec.source && spec.generator !== "frames") {
|
|
11519
|
-
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))) {
|
|
11520
12134
|
state = "ok";
|
|
11521
12135
|
reason = `placed from ${spec.source}; not generated`;
|
|
11522
12136
|
} else {
|
|
@@ -11524,7 +12138,7 @@ async function buildPlan(specs, lock, opts = {}) {
|
|
|
11524
12138
|
reason = `declared source is missing: ${spec.source}`;
|
|
11525
12139
|
}
|
|
11526
12140
|
} else if (!entry) {
|
|
11527
|
-
if ((0,
|
|
12141
|
+
if ((0, import_node_fs11.existsSync)(spec.outFile)) {
|
|
11528
12142
|
state = "untracked";
|
|
11529
12143
|
reason = "file on disk, no provenance recorded";
|
|
11530
12144
|
} else {
|
|
@@ -11537,7 +12151,7 @@ async function buildPlan(specs, lock, opts = {}) {
|
|
|
11537
12151
|
} else if (spec.mirror && !mirrorCurrent(entry, lock.entries[lockKey(spec.styleId, spec.mirror.sourceAssetId)])) {
|
|
11538
12152
|
state = "stale";
|
|
11539
12153
|
reason = `mirror source ${spec.mirror.sourceAssetId} changed; flip it again (no generation cost)`;
|
|
11540
|
-
} 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)))) {
|
|
11541
12155
|
state = "stale";
|
|
11542
12156
|
reason = "mirror output missing on disk; flip it again (no generation cost)";
|
|
11543
12157
|
} else if (spec.character?.kind === "outfit" && entry.outfit?.sourceSha256 !== mirrorSourceHash(lock.entries[lockKey(spec.styleId, spec.character.parentAssetId)] ?? { outputs: [] })) {
|
|
@@ -11565,9 +12179,9 @@ async function buildPlan(specs, lock, opts = {}) {
|
|
|
11565
12179
|
} else if (entry.status !== "downloaded") {
|
|
11566
12180
|
state = "in-flight";
|
|
11567
12181
|
reason = `awaiting ${entry.status}`;
|
|
11568
|
-
} 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)))) {
|
|
11569
12183
|
state = "orphaned";
|
|
11570
|
-
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(", ")}`;
|
|
11571
12185
|
} else if (await anyOutputModified(entry, spec)) {
|
|
11572
12186
|
state = "orphaned";
|
|
11573
12187
|
reason = "output modified since download";
|
|
@@ -11658,13 +12272,13 @@ function summarize(plan) {
|
|
|
11658
12272
|
|
|
11659
12273
|
// src/project.ts
|
|
11660
12274
|
async function openProject(manifestPath = "pixelkiln.manifest.json", opts = {}) {
|
|
11661
|
-
const absolute =
|
|
11662
|
-
const root =
|
|
12275
|
+
const absolute = import_node_path15.default.resolve(manifestPath);
|
|
12276
|
+
const root = import_node_path15.default.dirname(absolute);
|
|
11663
12277
|
if (opts.env !== false) {
|
|
11664
12278
|
loadEnvFiles(root);
|
|
11665
|
-
if (
|
|
12279
|
+
if (import_node_path15.default.resolve(process.cwd()) !== root) loadEnvFiles(process.cwd());
|
|
11666
12280
|
}
|
|
11667
|
-
const lockPath =
|
|
12281
|
+
const lockPath = import_node_path15.default.resolve(opts.lockPath ?? import_node_path15.default.join(root, "pixelkiln.lock.json"));
|
|
11668
12282
|
const loaded = await loadManifest(absolute);
|
|
11669
12283
|
const specs = await resolveSpecs(loaded, { styles: opts.styles ?? [], assets: opts.assets ?? [] });
|
|
11670
12284
|
const lock = await loadLock(lockPath);
|
|
@@ -11731,9 +12345,9 @@ function shouldPersistSourceUrl(value) {
|
|
|
11731
12345
|
}
|
|
11732
12346
|
|
|
11733
12347
|
// src/recipes.ts
|
|
11734
|
-
var
|
|
11735
|
-
var
|
|
11736
|
-
var
|
|
12348
|
+
var import_node_path16 = __toESM(require("path"), 1);
|
|
12349
|
+
var import_node_fs12 = require("fs");
|
|
12350
|
+
var import_promises8 = require("fs/promises");
|
|
11737
12351
|
var import_node_url = require("url");
|
|
11738
12352
|
var import_zod4 = require("zod");
|
|
11739
12353
|
var SHA256_RE = /^[0-9a-f]{64}$/;
|
|
@@ -11742,7 +12356,7 @@ var VERSION_RE = /^\d+\.\d+\.\d+$/;
|
|
|
11742
12356
|
var RECIPE_FILE = "pixelkiln.recipe.json";
|
|
11743
12357
|
var RECIPE_DIR_TOKEN = "{{recipeDir}}";
|
|
11744
12358
|
var PortablePathSchema = import_zod4.z.string().min(1).refine((value) => {
|
|
11745
|
-
if (value.includes("\\") ||
|
|
12359
|
+
if (value.includes("\\") || import_node_path16.default.posix.isAbsolute(value)) return false;
|
|
11746
12360
|
const parts = value.split("/");
|
|
11747
12361
|
return !parts.includes("") && !parts.includes(".") && !parts.includes("..");
|
|
11748
12362
|
}, "expected a portable relative path without '.', '..', or backslashes");
|
|
@@ -11918,10 +12532,10 @@ function message3(error) {
|
|
|
11918
12532
|
return error instanceof Error ? error.message : String(error);
|
|
11919
12533
|
}
|
|
11920
12534
|
async function readRecipeFile(recipePath, bundled) {
|
|
11921
|
-
const absolute =
|
|
12535
|
+
const absolute = import_node_path16.default.resolve(recipePath);
|
|
11922
12536
|
let raw;
|
|
11923
12537
|
try {
|
|
11924
|
-
raw = JSON.parse(await (0,
|
|
12538
|
+
raw = JSON.parse(await (0, import_promises8.readFile)(absolute, "utf8"));
|
|
11925
12539
|
} catch (error) {
|
|
11926
12540
|
throw new Error(`Could not read recipe ${absolute}: ${message3(error)}`, { cause: error });
|
|
11927
12541
|
}
|
|
@@ -11932,13 +12546,13 @@ async function readRecipeFile(recipePath, bundled) {
|
|
|
11932
12546
|
` + parsed.error.issues.map((issue) => ` ${issue.path.join(".") || "$"}: ${issue.message}`).join("\n")
|
|
11933
12547
|
);
|
|
11934
12548
|
}
|
|
11935
|
-
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 };
|
|
11936
12550
|
}
|
|
11937
12551
|
async function walkRecipeFiles(root) {
|
|
11938
|
-
if (!(0,
|
|
12552
|
+
if (!(0, import_node_fs12.existsSync)(root)) return [];
|
|
11939
12553
|
const out = [];
|
|
11940
|
-
for (const entry of await (0,
|
|
11941
|
-
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);
|
|
11942
12556
|
if (entry.isDirectory()) out.push(...await walkRecipeFiles(resolved));
|
|
11943
12557
|
else if (entry.isFile() && entry.name === RECIPE_FILE) out.push(resolved);
|
|
11944
12558
|
}
|
|
@@ -11949,7 +12563,7 @@ function bundledRecipeRoot() {
|
|
|
11949
12563
|
}
|
|
11950
12564
|
async function listBundledRecipes(root = bundledRecipeRoot()) {
|
|
11951
12565
|
const recipes = await Promise.all(
|
|
11952
|
-
(await walkRecipeFiles(
|
|
12566
|
+
(await walkRecipeFiles(import_node_path16.default.resolve(root))).map((file) => readRecipeFile(file, true))
|
|
11953
12567
|
);
|
|
11954
12568
|
return recipes.sort(
|
|
11955
12569
|
(a, b) => a.recipe.id.localeCompare(b.recipe.id) || compareVersions(b.recipe.version, a.recipe.version)
|
|
@@ -11972,9 +12586,9 @@ function parseSelector(selector) {
|
|
|
11972
12586
|
return { id: match[1], version: match[2] };
|
|
11973
12587
|
}
|
|
11974
12588
|
async function resolveRecipe(target, bundledRoot = bundledRecipeRoot()) {
|
|
11975
|
-
const local =
|
|
11976
|
-
if ((0,
|
|
11977
|
-
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);
|
|
11978
12592
|
return readRecipeFile(recipePath, false);
|
|
11979
12593
|
}
|
|
11980
12594
|
if (target.startsWith(".") || target.startsWith("/") || target.includes("\\")) {
|
|
@@ -11988,8 +12602,8 @@ async function resolveRecipe(target, bundledRoot = bundledRecipeRoot()) {
|
|
|
11988
12602
|
return matches[0];
|
|
11989
12603
|
}
|
|
11990
12604
|
async function verifyFile(root, file) {
|
|
11991
|
-
const absolute =
|
|
11992
|
-
if (!(0,
|
|
12605
|
+
const absolute = import_node_path16.default.resolve(root, ...file.path.split("/"));
|
|
12606
|
+
if (!(0, import_node_fs12.existsSync)(absolute)) {
|
|
11993
12607
|
return { path: file.path, expectedSha256: file.sha256, actualSha256: null, status: "missing" };
|
|
11994
12608
|
}
|
|
11995
12609
|
const actualSha256 = await sha256File(absolute);
|
|
@@ -12004,7 +12618,7 @@ async function verifyRecipe(target, options = {}) {
|
|
|
12004
12618
|
const loaded = await resolveRecipe(target, options.bundledRoot);
|
|
12005
12619
|
const actualIntegrity = recipeDigest(loaded.authored);
|
|
12006
12620
|
const files = await Promise.all(loaded.recipe.files.map((file) => verifyFile(loaded.dir, file)));
|
|
12007
|
-
const modelRoot = options.modelRoot ?
|
|
12621
|
+
const modelRoot = options.modelRoot ? import_node_path16.default.resolve(options.modelRoot) : null;
|
|
12008
12622
|
const models = modelRoot ? await Promise.all(loaded.recipe.models.map(async (model) => ({
|
|
12009
12623
|
...await verifyFile(modelRoot, model),
|
|
12010
12624
|
source: model.source,
|
|
@@ -12038,7 +12652,7 @@ async function verifyRecipe(target, options = {}) {
|
|
|
12038
12652
|
};
|
|
12039
12653
|
}
|
|
12040
12654
|
function renderStyle(style, destination, cwd) {
|
|
12041
|
-
const portableDir =
|
|
12655
|
+
const portableDir = import_node_path16.default.relative(cwd, destination).split(import_node_path16.default.sep).join("/") || ".";
|
|
12042
12656
|
const replace = (value) => {
|
|
12043
12657
|
if (typeof value === "string") return value.replaceAll(RECIPE_DIR_TOKEN, portableDir);
|
|
12044
12658
|
if (Array.isArray(value)) return value.map(replace);
|
|
@@ -12050,25 +12664,25 @@ function renderStyle(style, destination, cwd) {
|
|
|
12050
12664
|
return replace(style);
|
|
12051
12665
|
}
|
|
12052
12666
|
async function installRecipe(target, options = {}) {
|
|
12053
|
-
const cwd =
|
|
12667
|
+
const cwd = import_node_path16.default.resolve(options.cwd ?? process.cwd());
|
|
12054
12668
|
const loaded = await resolveRecipe(target, options.bundledRoot);
|
|
12055
12669
|
const verification = await verifyRecipe(loaded.path);
|
|
12056
12670
|
if (!verification.ok) {
|
|
12057
12671
|
throw new Error(`Recipe ${loaded.recipe.id}@${loaded.recipe.version} failed integrity verification.`);
|
|
12058
12672
|
}
|
|
12059
|
-
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);
|
|
12060
12674
|
const sources = [
|
|
12061
12675
|
{ path: RECIPE_FILE, source: loaded.path },
|
|
12062
|
-
...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("/")) }))
|
|
12063
12677
|
];
|
|
12064
12678
|
const files = await Promise.all(sources.map(async (file) => ({
|
|
12065
|
-
path:
|
|
12066
|
-
data: await (0,
|
|
12679
|
+
path: import_node_path16.default.join(destination, ...file.path.split("/")),
|
|
12680
|
+
data: await (0, import_promises8.readFile)(file.source)
|
|
12067
12681
|
})));
|
|
12068
12682
|
if (!options.force) {
|
|
12069
12683
|
for (const file of files) {
|
|
12070
|
-
if (!(0,
|
|
12071
|
-
const current = await (0,
|
|
12684
|
+
if (!(0, import_node_fs12.existsSync)(file.path)) continue;
|
|
12685
|
+
const current = await (0, import_promises8.readFile)(file.path);
|
|
12072
12686
|
if (!current.equals(file.data)) {
|
|
12073
12687
|
throw new OverwriteRefusedError(
|
|
12074
12688
|
`Recipe destination has local changes: ${file.path}. Choose another --out or pass --force to replace declared recipe files.`,
|
|
@@ -12092,9 +12706,9 @@ async function installRecipe(target, options = {}) {
|
|
|
12092
12706
|
}
|
|
12093
12707
|
|
|
12094
12708
|
// src/pipeline/audit.ts
|
|
12095
|
-
var
|
|
12096
|
-
var
|
|
12097
|
-
var
|
|
12709
|
+
var import_promises9 = require("fs/promises");
|
|
12710
|
+
var import_node_fs13 = require("fs");
|
|
12711
|
+
var import_node_path17 = __toESM(require("path"), 1);
|
|
12098
12712
|
function paletteDistance(asset, reference) {
|
|
12099
12713
|
if (!asset.length || !reference.length) return 0;
|
|
12100
12714
|
let total = 0;
|
|
@@ -12130,12 +12744,12 @@ async function auditStyle(loaded, specs, styleId, lock) {
|
|
|
12130
12744
|
const unreadable = [];
|
|
12131
12745
|
for (const spec of mine) {
|
|
12132
12746
|
for (const output of resolveSpecOutputs(spec, lock, loaded.root)) {
|
|
12133
|
-
if (!(0,
|
|
12747
|
+
if (!(0, import_node_fs13.existsSync)(output.absolutePath)) {
|
|
12134
12748
|
missing.push(output.id);
|
|
12135
12749
|
continue;
|
|
12136
12750
|
}
|
|
12137
12751
|
try {
|
|
12138
|
-
const png = decodePng(await (0,
|
|
12752
|
+
const png = decodePng(await (0, import_promises9.readFile)(output.absolutePath));
|
|
12139
12753
|
const palette = extractPalette(png, 12);
|
|
12140
12754
|
assets.push({
|
|
12141
12755
|
assetId: spec.assetId,
|
|
@@ -12159,10 +12773,10 @@ async function auditStyle(loaded, specs, styleId, lock) {
|
|
|
12159
12773
|
let referenceFromStyleImages = false;
|
|
12160
12774
|
const refPalettes = [];
|
|
12161
12775
|
for (const img of style.styleImages) {
|
|
12162
|
-
const abs =
|
|
12163
|
-
if (!(0,
|
|
12776
|
+
const abs = import_node_path17.default.resolve(loaded.root, img.path);
|
|
12777
|
+
if (!(0, import_node_fs13.existsSync)(abs)) continue;
|
|
12164
12778
|
try {
|
|
12165
|
-
refPalettes.push(extractPalette(decodePng(await (0,
|
|
12779
|
+
refPalettes.push(extractPalette(decodePng(await (0, import_promises9.readFile)(abs)), 12));
|
|
12166
12780
|
} catch {
|
|
12167
12781
|
}
|
|
12168
12782
|
}
|
|
@@ -12238,20 +12852,20 @@ function hex(c) {
|
|
|
12238
12852
|
}
|
|
12239
12853
|
|
|
12240
12854
|
// src/pixellab-utils.ts
|
|
12241
|
-
var
|
|
12242
|
-
var
|
|
12243
|
-
var
|
|
12855
|
+
var import_node_fs14 = require("fs");
|
|
12856
|
+
var import_promises10 = require("fs/promises");
|
|
12857
|
+
var import_node_path18 = __toESM(require("path"), 1);
|
|
12244
12858
|
var UNZOOM_MIN_SIDE = 256;
|
|
12245
12859
|
var UNZOOM_MAX_AREA = 2048 * 2048;
|
|
12246
12860
|
function defaultUnzoomOut(input) {
|
|
12247
|
-
const ext =
|
|
12861
|
+
const ext = import_node_path18.default.extname(input);
|
|
12248
12862
|
return `${ext ? input.slice(0, -ext.length) : input}.unzoomed.png`;
|
|
12249
12863
|
}
|
|
12250
12864
|
async function unzoomFile(client2, input, opts = {}) {
|
|
12251
|
-
const out =
|
|
12252
|
-
if (
|
|
12253
|
-
if ((0,
|
|
12254
|
-
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);
|
|
12255
12869
|
const metadata = imageMetadata(bytes);
|
|
12256
12870
|
if (!metadata) throw new Error(`${input} is not a readable PNG or JPEG`);
|
|
12257
12871
|
const { width, height, format } = metadata;
|
|
@@ -12264,22 +12878,22 @@ async function unzoomFile(client2, input, opts = {}) {
|
|
|
12264
12878
|
throw new Error(`${input} is ${width}x${height}; PixelLab's unzoom takes at most 2048x2048 worth of pixels`);
|
|
12265
12879
|
}
|
|
12266
12880
|
const res = await client2.unzoom({ image: { base64: bytes.toString("base64"), format }, quantize: opts.quantize });
|
|
12267
|
-
await (0,
|
|
12268
|
-
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);
|
|
12269
12883
|
return { out, original: res.originalSize, unzoomed: res.unzoomedSize, zoomFactor: res.zoomFactor, usage: res.usage };
|
|
12270
12884
|
}
|
|
12271
12885
|
var FONT_WEIGHTS = ["Bold", "Regular"];
|
|
12272
12886
|
var FONT_GLYPH_SIZES = [8, 16, 32, 64];
|
|
12273
|
-
var FONT_COST_GENERATIONS =
|
|
12887
|
+
var FONT_COST_GENERATIONS = 15;
|
|
12274
12888
|
function fontOutputPaths(out) {
|
|
12275
|
-
const base =
|
|
12889
|
+
const base = import_node_path18.default.resolve(out.replace(/\.(ttf|png)$/i, ""));
|
|
12276
12890
|
return { ttf: `${base}.ttf`, atlas: `${base}.png` };
|
|
12277
12891
|
}
|
|
12278
12892
|
async function generateFont(client2, opts) {
|
|
12279
12893
|
const { ttf, atlas } = fontOutputPaths(opts.out);
|
|
12280
12894
|
if (!opts.force) {
|
|
12281
12895
|
for (const file of [ttf, atlas]) {
|
|
12282
|
-
if ((0,
|
|
12896
|
+
if ((0, import_node_fs14.existsSync)(file)) throw new Error(`${file} already exists; pass --force to replace it`);
|
|
12283
12897
|
}
|
|
12284
12898
|
}
|
|
12285
12899
|
const sleep3 = opts.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
@@ -12306,9 +12920,9 @@ async function generateFont(client2, opts) {
|
|
|
12306
12920
|
client2.download(job.download_ttf_url),
|
|
12307
12921
|
client2.download(job.download_atlas_url)
|
|
12308
12922
|
]);
|
|
12309
|
-
await (0,
|
|
12310
|
-
await (0,
|
|
12311
|
-
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);
|
|
12312
12926
|
return { jobId, ttf, atlas, glyphPx: job.glyph_px ?? null, usage: job.usage };
|
|
12313
12927
|
}
|
|
12314
12928
|
if (Date.now() - started > timeout) {
|
|
@@ -12319,17 +12933,72 @@ async function generateFont(client2, opts) {
|
|
|
12319
12933
|
await sleep3(interval);
|
|
12320
12934
|
}
|
|
12321
12935
|
}
|
|
12936
|
+
var PIXELATE_COST_GENERATIONS = 6;
|
|
12937
|
+
var PIXELATE_MAX_SIDE = 2048;
|
|
12938
|
+
function defaultPixelateOut(input) {
|
|
12939
|
+
const ext = import_node_path18.default.extname(input);
|
|
12940
|
+
return `${ext ? input.slice(0, -ext.length) : input}.pixel.png`;
|
|
12941
|
+
}
|
|
12942
|
+
async function pixelateFile(client2, input, opts = {}) {
|
|
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);
|
|
12947
|
+
const metadata = imageMetadata(bytes);
|
|
12948
|
+
if (!metadata) throw new Error(`${input} is not a readable PNG or JPEG`);
|
|
12949
|
+
const { width, height, format } = metadata;
|
|
12950
|
+
const sleep3 = opts.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
12951
|
+
const interval = opts.intervalMs ?? 5e3;
|
|
12952
|
+
const timeout = opts.timeoutMs ?? 10 * 6e4;
|
|
12953
|
+
const submitted = await client2.imageToPixelartProFlash({
|
|
12954
|
+
image: { base64: bytes.toString("base64"), format },
|
|
12955
|
+
description: opts.description,
|
|
12956
|
+
seed: opts.seed
|
|
12957
|
+
});
|
|
12958
|
+
const jobId = submitted.background_job_id;
|
|
12959
|
+
const started = Date.now();
|
|
12960
|
+
for (; ; ) {
|
|
12961
|
+
const job = await client2.getBackgroundJob(jobId);
|
|
12962
|
+
opts.onProgress?.(job.status);
|
|
12963
|
+
if (job.status === "failed") throw new Error(`PixelLab pixelate job ${jobId} failed upstream`);
|
|
12964
|
+
if (job.status === "completed") {
|
|
12965
|
+
const images = job.last_response?.images;
|
|
12966
|
+
const first = Array.isArray(images) ? images[0] : void 0;
|
|
12967
|
+
if (!first || typeof first.base64 !== "string" || !first.base64) {
|
|
12968
|
+
throw new Error(`PixelLab pixelate job ${jobId} completed without an image at last_response.images[0]`);
|
|
12969
|
+
}
|
|
12970
|
+
const png = Buffer.from(first.base64, "base64");
|
|
12971
|
+
const size = imageMetadata(png);
|
|
12972
|
+
await (0, import_promises10.mkdir)(import_node_path18.default.dirname(out), { recursive: true });
|
|
12973
|
+
await (0, import_promises10.writeFile)(out, png);
|
|
12974
|
+
return {
|
|
12975
|
+
jobId,
|
|
12976
|
+
out,
|
|
12977
|
+
original: { width, height },
|
|
12978
|
+
pixelated: { width: size?.width ?? Number(first.width), height: size?.height ?? Number(first.height) },
|
|
12979
|
+
imageId: submitted.image_id,
|
|
12980
|
+
usage: job.usage ?? null
|
|
12981
|
+
};
|
|
12982
|
+
}
|
|
12983
|
+
if (Date.now() - started > timeout) {
|
|
12984
|
+
throw new Error(
|
|
12985
|
+
`PixelLab pixelate job ${jobId} is still ${job.status} after ${Math.round(timeout / 1e3)} s; it keeps running upstream, but this command has stopped waiting`
|
|
12986
|
+
);
|
|
12987
|
+
}
|
|
12988
|
+
await sleep3(interval);
|
|
12989
|
+
}
|
|
12990
|
+
}
|
|
12322
12991
|
|
|
12323
12992
|
// src/pipeline/quality-regression.ts
|
|
12324
|
-
var
|
|
12325
|
-
var
|
|
12326
|
-
var
|
|
12993
|
+
var import_node_path19 = __toESM(require("path"), 1);
|
|
12994
|
+
var import_node_fs15 = require("fs");
|
|
12995
|
+
var import_promises11 = require("fs/promises");
|
|
12327
12996
|
var import_zod5 = require("zod");
|
|
12328
12997
|
var SHA256_RE2 = /^[0-9a-f]{64}$/;
|
|
12329
12998
|
var HEX_RE = /^#[0-9a-f]{6}$/;
|
|
12330
12999
|
var MAX_REDMEAN_DISTANCE = 765;
|
|
12331
13000
|
var QualityPathSchema = import_zod5.z.string().min(1).refine(
|
|
12332
|
-
(value) => !
|
|
13001
|
+
(value) => !import_node_path19.default.posix.isAbsolute(value) && !value.includes("\\"),
|
|
12333
13002
|
"expected a portable relative path"
|
|
12334
13003
|
);
|
|
12335
13004
|
var DEFAULT_QUALITY_TOLERANCES = {
|
|
@@ -12476,10 +13145,10 @@ function measureDecodedImageQuality(png, digest2) {
|
|
|
12476
13145
|
};
|
|
12477
13146
|
}
|
|
12478
13147
|
async function measureImageQuality(file) {
|
|
12479
|
-
const absolute =
|
|
13148
|
+
const absolute = import_node_path19.default.resolve(file);
|
|
12480
13149
|
let bytes;
|
|
12481
13150
|
try {
|
|
12482
|
-
bytes = await (0,
|
|
13151
|
+
bytes = await (0, import_promises11.readFile)(absolute);
|
|
12483
13152
|
} catch (error) {
|
|
12484
13153
|
throw new Error(`Cannot read quality image ${absolute}: ${message4(error)}`, { cause: error });
|
|
12485
13154
|
}
|
|
@@ -12498,11 +13167,11 @@ function resolveQualityInputs(raw, inputsFilePath) {
|
|
|
12498
13167
|
}
|
|
12499
13168
|
const ids = parsed.data.map((entry) => entry.id);
|
|
12500
13169
|
if (new Set(ids).size !== ids.length) throw new Error("--inputs contains a duplicate quality case id.");
|
|
12501
|
-
const root =
|
|
13170
|
+
const root = import_node_path19.default.dirname(import_node_path19.default.resolve(inputsFilePath));
|
|
12502
13171
|
return parsed.data.map((entry) => ({
|
|
12503
13172
|
id: entry.id,
|
|
12504
|
-
file:
|
|
12505
|
-
...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) } : {},
|
|
12506
13175
|
tolerances: QualityToleranceSchema.parse({
|
|
12507
13176
|
...DEFAULT_QUALITY_TOLERANCES,
|
|
12508
13177
|
...entry.tolerances
|
|
@@ -12510,7 +13179,7 @@ function resolveQualityInputs(raw, inputsFilePath) {
|
|
|
12510
13179
|
}));
|
|
12511
13180
|
}
|
|
12512
13181
|
function portableRelative2(from, to) {
|
|
12513
|
-
return
|
|
13182
|
+
return import_node_path19.default.relative(from, import_node_path19.default.resolve(to)).split(import_node_path19.default.sep).join("/") || ".";
|
|
12514
13183
|
}
|
|
12515
13184
|
async function verifyRecordForImage(recordPath, imagePath) {
|
|
12516
13185
|
const verification = await verifyArtifactBundle(recordPath);
|
|
@@ -12525,20 +13194,20 @@ async function verifyRecordForImage(recordPath, imagePath) {
|
|
|
12525
13194
|
if (record.kind !== "refine" || options === null || typeof options !== "object" || options.schema !== "pixelkiln-quality") {
|
|
12526
13195
|
throw new Error(`Quality record ${recordPath} is not a PixelKiln refinement record.`);
|
|
12527
13196
|
}
|
|
12528
|
-
const outputs = record.outputs.map((output) =>
|
|
12529
|
-
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))) {
|
|
12530
13199
|
throw new Error(`Quality record ${recordPath} does not own ${imagePath}.`);
|
|
12531
13200
|
}
|
|
12532
13201
|
}
|
|
12533
13202
|
async function snapshotQualityBaseline(inputs, baselinePath, options = {}) {
|
|
12534
13203
|
if (!inputs.length) throw new Error("Cannot snapshot an empty quality baseline.");
|
|
12535
|
-
const absolute =
|
|
13204
|
+
const absolute = import_node_path19.default.resolve(baselinePath);
|
|
12536
13205
|
for (const input of inputs) {
|
|
12537
|
-
if (
|
|
13206
|
+
if (import_node_path19.default.resolve(input.file) === absolute || input.record && import_node_path19.default.resolve(input.record) === absolute) {
|
|
12538
13207
|
throw new Error(`Quality baseline output would overwrite an input: ${absolute}.`);
|
|
12539
13208
|
}
|
|
12540
13209
|
}
|
|
12541
|
-
const root =
|
|
13210
|
+
const root = import_node_path19.default.dirname(absolute);
|
|
12542
13211
|
const cases = [];
|
|
12543
13212
|
for (const input of [...inputs].sort((a, b) => a.id.localeCompare(b.id))) {
|
|
12544
13213
|
const expected = await measureImageQuality(input.file);
|
|
@@ -12565,8 +13234,8 @@ async function snapshotQualityBaseline(inputs, baselinePath, options = {}) {
|
|
|
12565
13234
|
cases
|
|
12566
13235
|
});
|
|
12567
13236
|
const data = Buffer.from(JSON.stringify(baseline, null, 2) + "\n");
|
|
12568
|
-
if ((0,
|
|
12569
|
-
const current = await (0,
|
|
13237
|
+
if ((0, import_node_fs15.existsSync)(absolute) && !options.force) {
|
|
13238
|
+
const current = await (0, import_promises11.readFile)(absolute);
|
|
12570
13239
|
if (!current.equals(data)) {
|
|
12571
13240
|
throw new OverwriteRefusedError(
|
|
12572
13241
|
`Quality baseline already exists with different content: ${absolute}. Pass --force to replace it.`,
|
|
@@ -12578,10 +13247,10 @@ async function snapshotQualityBaseline(inputs, baselinePath, options = {}) {
|
|
|
12578
13247
|
return { baseline, path: absolute, changed: result.changed.length > 0 };
|
|
12579
13248
|
}
|
|
12580
13249
|
async function readQualityBaseline(baselinePath) {
|
|
12581
|
-
const absolute =
|
|
13250
|
+
const absolute = import_node_path19.default.resolve(baselinePath);
|
|
12582
13251
|
let raw;
|
|
12583
13252
|
try {
|
|
12584
|
-
raw = JSON.parse(await (0,
|
|
13253
|
+
raw = JSON.parse(await (0, import_promises11.readFile)(absolute, "utf8"));
|
|
12585
13254
|
} catch (error) {
|
|
12586
13255
|
throw new Error(`Could not read quality baseline ${absolute}: ${message4(error)}`, { cause: error });
|
|
12587
13256
|
}
|
|
@@ -12651,12 +13320,12 @@ function regressionViolations(expected, actual, tolerances) {
|
|
|
12651
13320
|
return { violations, warnings };
|
|
12652
13321
|
}
|
|
12653
13322
|
async function checkQualityBaseline(baselinePath) {
|
|
12654
|
-
const absolute =
|
|
13323
|
+
const absolute = import_node_path19.default.resolve(baselinePath);
|
|
12655
13324
|
const baseline = await readQualityBaseline(absolute);
|
|
12656
|
-
const root =
|
|
13325
|
+
const root = import_node_path19.default.dirname(absolute);
|
|
12657
13326
|
const cases = [];
|
|
12658
13327
|
for (const entry of baseline.cases) {
|
|
12659
|
-
const file =
|
|
13328
|
+
const file = import_node_path19.default.resolve(root, entry.file);
|
|
12660
13329
|
let actual = null;
|
|
12661
13330
|
const violations = [];
|
|
12662
13331
|
const warnings = [];
|
|
@@ -12669,7 +13338,7 @@ async function checkQualityBaseline(baselinePath) {
|
|
|
12669
13338
|
violations.push(message4(error));
|
|
12670
13339
|
}
|
|
12671
13340
|
if (entry.record) {
|
|
12672
|
-
const recordPath =
|
|
13341
|
+
const recordPath = import_node_path19.default.resolve(root, entry.record.path);
|
|
12673
13342
|
try {
|
|
12674
13343
|
const recordHash = await sha256File(recordPath);
|
|
12675
13344
|
if (recordHash !== entry.record.sha256) violations.push("quality record hash changed");
|
|
@@ -12705,14 +13374,14 @@ async function checkQualityBaseline(baselinePath) {
|
|
|
12705
13374
|
}
|
|
12706
13375
|
|
|
12707
13376
|
// src/pipeline/cache-health.ts
|
|
12708
|
-
var
|
|
12709
|
-
var
|
|
12710
|
-
var
|
|
13377
|
+
var import_node_fs19 = require("fs");
|
|
13378
|
+
var import_promises14 = require("fs/promises");
|
|
13379
|
+
var import_node_path23 = __toESM(require("path"), 1);
|
|
12711
13380
|
|
|
12712
13381
|
// src/cache.ts
|
|
12713
|
-
var
|
|
12714
|
-
var
|
|
12715
|
-
var
|
|
13382
|
+
var import_promises12 = require("fs/promises");
|
|
13383
|
+
var import_node_fs16 = require("fs");
|
|
13384
|
+
var import_node_path20 = __toESM(require("path"), 1);
|
|
12716
13385
|
var import_zod6 = require("zod");
|
|
12717
13386
|
var HashCacheSchema = import_zod6.z.object({
|
|
12718
13387
|
version: import_zod6.z.literal(1),
|
|
@@ -12726,10 +13395,10 @@ function isSha256Hash(value) {
|
|
|
12726
13395
|
function parseCache(value) {
|
|
12727
13396
|
return HashCacheSchema.parse(value);
|
|
12728
13397
|
}
|
|
12729
|
-
async function loadCache(
|
|
12730
|
-
if (!(0,
|
|
13398
|
+
async function loadCache(path39) {
|
|
13399
|
+
if (!(0, import_node_fs16.existsSync)(path39)) return { version: 1, hashes: {} };
|
|
12731
13400
|
try {
|
|
12732
|
-
const parsed = HashCacheSchema.safeParse(JSON.parse(await (0,
|
|
13401
|
+
const parsed = HashCacheSchema.safeParse(JSON.parse(await (0, import_promises12.readFile)(path39, "utf8")));
|
|
12733
13402
|
if (!parsed.success) return { version: 1, hashes: {} };
|
|
12734
13403
|
return {
|
|
12735
13404
|
version: 1,
|
|
@@ -12741,20 +13410,20 @@ async function loadCache(path38) {
|
|
|
12741
13410
|
return { version: 1, hashes: {} };
|
|
12742
13411
|
}
|
|
12743
13412
|
}
|
|
12744
|
-
async function saveCache(
|
|
13413
|
+
async function saveCache(path39, cache) {
|
|
12745
13414
|
const sorted = {};
|
|
12746
13415
|
for (const key of Object.keys(cache.hashes).sort()) {
|
|
12747
13416
|
const hash = cache.hashes[key];
|
|
12748
13417
|
if (!isSha256Hash(hash)) throw new Error(`Refusing to cache invalid SHA-256 for ${key}`);
|
|
12749
13418
|
sorted[key] = hash;
|
|
12750
13419
|
}
|
|
12751
|
-
await (0,
|
|
12752
|
-
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`;
|
|
12753
13422
|
try {
|
|
12754
|
-
await (0,
|
|
12755
|
-
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);
|
|
12756
13425
|
} finally {
|
|
12757
|
-
await (0,
|
|
13426
|
+
await (0, import_promises12.rm)(tmp, { force: true });
|
|
12758
13427
|
}
|
|
12759
13428
|
}
|
|
12760
13429
|
function pruneCache(cache, liveIds, namespace = "") {
|
|
@@ -12776,20 +13445,20 @@ function cachePathFor(lockPath) {
|
|
|
12776
13445
|
}
|
|
12777
13446
|
|
|
12778
13447
|
// src/pipeline/history.ts
|
|
12779
|
-
var
|
|
12780
|
-
var
|
|
13448
|
+
var import_node_fs18 = require("fs");
|
|
13449
|
+
var import_node_path22 = __toESM(require("path"), 1);
|
|
12781
13450
|
|
|
12782
13451
|
// src/pipeline/fetch.ts
|
|
12783
|
-
var
|
|
12784
|
-
var
|
|
12785
|
-
var
|
|
13452
|
+
var import_node_fs17 = require("fs");
|
|
13453
|
+
var import_promises13 = require("fs/promises");
|
|
13454
|
+
var import_node_path21 = __toESM(require("path"), 1);
|
|
12786
13455
|
async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
12787
13456
|
const log = opts.onProgress ?? (() => {
|
|
12788
13457
|
});
|
|
12789
13458
|
const result = { downloaded: 0, skipped: 0, failed: 0, ...opts.refresh ? { unchanged: 0 } : {} };
|
|
12790
13459
|
const specByKey = new Map(specs.map((s) => [lockKey(s.styleId, s.assetId), s]));
|
|
12791
13460
|
normalizeLockOutputPaths(lock, specs);
|
|
12792
|
-
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"));
|
|
12793
13462
|
const pending = Object.entries(lock.entries).filter(([key, e]) => {
|
|
12794
13463
|
if (e.provider !== provider.id || !specByKey.has(key)) return false;
|
|
12795
13464
|
if (specByKey.get(key)?.mirror) return false;
|
|
@@ -12800,15 +13469,15 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
12800
13469
|
if (spec && e.specHash === spec.specHash && e.outputs.length && !postprocessCurrent(e, spec)) {
|
|
12801
13470
|
return Boolean(opts.force) || e.outputs.every((output) => {
|
|
12802
13471
|
const file = resolveOutputPath(output.path, spec.root);
|
|
12803
|
-
return (0,
|
|
13472
|
+
return (0, import_node_fs17.existsSync)(file) && sha256((0, import_node_fs17.readFileSync)(file)) === output.sha256;
|
|
12804
13473
|
});
|
|
12805
13474
|
}
|
|
12806
13475
|
if (!opts.repair) return false;
|
|
12807
13476
|
if (e.outputs.length === 0 || !spec) return true;
|
|
12808
13477
|
return e.outputs.some((output) => {
|
|
12809
13478
|
const file = resolveOutputPath(output.path, spec.root);
|
|
12810
|
-
if (!(0,
|
|
12811
|
-
const current = sha256((0,
|
|
13479
|
+
if (!(0, import_node_fs17.existsSync)(file)) return true;
|
|
13480
|
+
const current = sha256((0, import_node_fs17.readFileSync)(file));
|
|
12812
13481
|
if (current === output.sha256) return false;
|
|
12813
13482
|
return Boolean(opts.force) || (e.supersededOutputs ?? []).some((old) => old.sha256 === current);
|
|
12814
13483
|
});
|
|
@@ -12891,21 +13560,21 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
12891
13560
|
if (!source.url) throw new Error(`no durable source URL to refresh ${source.role ?? "asset"} from`);
|
|
12892
13561
|
fresh = await provider.download(source.url);
|
|
12893
13562
|
if (recorded && sha256(fresh) === recordedRaw && !reapply) {
|
|
12894
|
-
log(` current ${
|
|
13563
|
+
log(` current ${import_node_path21.default.relative(process.cwd(), target)}`);
|
|
12895
13564
|
outputs.push({ ...recorded, path: portableOutputPath(target, spec.root) });
|
|
12896
13565
|
continue;
|
|
12897
13566
|
}
|
|
12898
13567
|
}
|
|
12899
13568
|
let raw = null;
|
|
12900
13569
|
let final = null;
|
|
12901
|
-
if ((0,
|
|
13570
|
+
if ((0, import_node_fs17.existsSync)(target)) {
|
|
12902
13571
|
const currentHash = await sha256File(target);
|
|
12903
13572
|
if (recorded && currentHash === recorded.sha256) {
|
|
12904
13573
|
if (!fresh && !reapply) {
|
|
12905
13574
|
if (cacheDir) {
|
|
12906
13575
|
await cacheMedia(
|
|
12907
13576
|
cacheDir,
|
|
12908
|
-
await (0,
|
|
13577
|
+
await (0, import_promises13.readFile)(target),
|
|
12909
13578
|
recorded.mediaType ?? MediaType.PNG,
|
|
12910
13579
|
recorded.sha256
|
|
12911
13580
|
);
|
|
@@ -12914,10 +13583,10 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
12914
13583
|
continue;
|
|
12915
13584
|
}
|
|
12916
13585
|
if (reapply && !fresh) {
|
|
12917
|
-
if (!recorded.raw) raw = await (0,
|
|
12918
|
-
log(` reapply ${
|
|
13586
|
+
if (!recorded.raw) raw = await (0, import_promises13.readFile)(target);
|
|
13587
|
+
log(` reapply ${import_node_path21.default.relative(process.cwd(), target)}`);
|
|
12919
13588
|
} else {
|
|
12920
|
-
log(` changed ${
|
|
13589
|
+
log(` changed ${import_node_path21.default.relative(process.cwd(), target)} (upstream)`);
|
|
12921
13590
|
}
|
|
12922
13591
|
} else {
|
|
12923
13592
|
const superseded = (entry.supersededOutputs ?? []).find(
|
|
@@ -12938,14 +13607,14 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
12938
13607
|
}
|
|
12939
13608
|
if (outgoing && cacheDir) {
|
|
12940
13609
|
try {
|
|
12941
|
-
const oldBytes = await (0,
|
|
13610
|
+
const oldBytes = await (0, import_promises13.readFile)(target);
|
|
12942
13611
|
const oldMediaType = detectMediaType(oldBytes);
|
|
12943
13612
|
if (oldMediaType) await cacheMedia(cacheDir, oldBytes, oldMediaType, currentHash);
|
|
12944
13613
|
} catch {
|
|
12945
13614
|
}
|
|
12946
13615
|
}
|
|
12947
13616
|
log(
|
|
12948
|
-
` replace ${
|
|
13617
|
+
` replace ${import_node_path21.default.relative(process.cwd(), target)}` + (opts.force ? " (--force)" : " (previous tracked generation)")
|
|
12949
13618
|
);
|
|
12950
13619
|
}
|
|
12951
13620
|
}
|
|
@@ -12954,7 +13623,7 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
12954
13623
|
} else if (!raw && recorded && cacheDir) {
|
|
12955
13624
|
if (!reapply) final = await readCachedMedia(cacheDir, recorded.sha256, expectedMediaType);
|
|
12956
13625
|
if (!final && recordedRaw) raw = await readCachedMedia(cacheDir, recordedRaw, expectedMediaType);
|
|
12957
|
-
if (final || raw) log(` cached ${
|
|
13626
|
+
if (final || raw) log(` cached ${import_node_path21.default.relative(process.cwd(), target)}`);
|
|
12958
13627
|
}
|
|
12959
13628
|
if (!final && !raw) {
|
|
12960
13629
|
if (!source.url) {
|
|
@@ -12981,13 +13650,13 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
12981
13650
|
buf = processed.bytes;
|
|
12982
13651
|
if (processed.changed) {
|
|
12983
13652
|
rawHash = sha256(raw);
|
|
12984
|
-
log(` snapped ${
|
|
13653
|
+
log(` snapped ${import_node_path21.default.relative(process.cwd(), target)} to ${wanted.palette.colors.length} colours`);
|
|
12985
13654
|
}
|
|
12986
13655
|
}
|
|
12987
13656
|
if (cacheDir) await cacheMedia(cacheDir, buf, mediaType);
|
|
12988
|
-
await (0,
|
|
13657
|
+
await (0, import_promises13.mkdir)(import_node_path21.default.dirname(target), { recursive: true });
|
|
12989
13658
|
const tmp = `${target}.pixelkiln.tmp`;
|
|
12990
|
-
await (0,
|
|
13659
|
+
await (0, import_promises13.writeFile)(tmp, buf);
|
|
12991
13660
|
outputs.push({
|
|
12992
13661
|
path: portableOutputPath(target, spec.root),
|
|
12993
13662
|
sha256: sha256(buf),
|
|
@@ -12997,9 +13666,9 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
12997
13666
|
});
|
|
12998
13667
|
upsert(lock, key, { outputs: mergeOutputs(entry.outputs, outputs) });
|
|
12999
13668
|
await saveLock(lockPath, lock);
|
|
13000
|
-
await (0,
|
|
13669
|
+
await (0, import_promises13.rename)(tmp, target);
|
|
13001
13670
|
wrote = true;
|
|
13002
|
-
log(` wrote ${
|
|
13671
|
+
log(` wrote ${import_node_path21.default.relative(process.cwd(), target)}`);
|
|
13003
13672
|
}
|
|
13004
13673
|
if (opts.refresh && !wrote) {
|
|
13005
13674
|
if (rotated) {
|
|
@@ -13043,10 +13712,10 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
13043
13712
|
return result;
|
|
13044
13713
|
}
|
|
13045
13714
|
async function readCachedMedia(cacheDir, hash, mediaType) {
|
|
13046
|
-
const file =
|
|
13047
|
-
if (!(0,
|
|
13715
|
+
const file = import_node_path21.default.join(cacheDir, cacheFileName(hash, mediaType));
|
|
13716
|
+
if (!(0, import_node_fs17.existsSync)(file)) return null;
|
|
13048
13717
|
try {
|
|
13049
|
-
const buf = await (0,
|
|
13718
|
+
const buf = await (0, import_promises13.readFile)(file);
|
|
13050
13719
|
if (sha256(buf) !== hash) return null;
|
|
13051
13720
|
validateMedia(buf, mediaType);
|
|
13052
13721
|
return buf;
|
|
@@ -13057,17 +13726,17 @@ async function readCachedMedia(cacheDir, hash, mediaType) {
|
|
|
13057
13726
|
async function cacheMedia(cacheDir, buf, mediaType, knownHash) {
|
|
13058
13727
|
const hash = knownHash ?? sha256(buf);
|
|
13059
13728
|
validateMedia(buf, mediaType);
|
|
13060
|
-
const file =
|
|
13729
|
+
const file = import_node_path21.default.join(cacheDir, cacheFileName(hash, mediaType));
|
|
13061
13730
|
if (await readCachedMedia(cacheDir, hash, mediaType)) return;
|
|
13062
|
-
await (0,
|
|
13731
|
+
await (0, import_promises13.mkdir)(cacheDir, { recursive: true });
|
|
13063
13732
|
const tmp = `${file}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
13064
|
-
await (0,
|
|
13733
|
+
await (0, import_promises13.writeFile)(tmp, buf);
|
|
13065
13734
|
try {
|
|
13066
|
-
await (0,
|
|
13735
|
+
await (0, import_promises13.rename)(tmp, file);
|
|
13067
13736
|
} catch (err) {
|
|
13068
|
-
if (!(0,
|
|
13737
|
+
if (!(0, import_node_fs17.existsSync)(file)) throw err;
|
|
13069
13738
|
} finally {
|
|
13070
|
-
await (0,
|
|
13739
|
+
await (0, import_promises13.rm)(tmp, { force: true });
|
|
13071
13740
|
}
|
|
13072
13741
|
}
|
|
13073
13742
|
function mergeOutputs(previous, current) {
|
|
@@ -13085,7 +13754,7 @@ async function pushTags(provider, specs, lock, opts = {}) {
|
|
|
13085
13754
|
for (const [key, entry] of Object.entries(lock.entries)) {
|
|
13086
13755
|
const spec = specByKey.get(key);
|
|
13087
13756
|
if (!spec || entry.provider !== provider.id || !entry.objectId) continue;
|
|
13088
|
-
if (entry.generator === "pixflux") continue;
|
|
13757
|
+
if (entry.generator === "pixflux" || entry.generator === "vfx") continue;
|
|
13089
13758
|
if (!provider.setTags) return tagged;
|
|
13090
13759
|
try {
|
|
13091
13760
|
await provider.setTags(entry.objectId, spec.tags, entry.generator);
|
|
@@ -13184,10 +13853,10 @@ async function revertGeneration(provider, spec, lock, lockPath, opts) {
|
|
|
13184
13853
|
}
|
|
13185
13854
|
for (let i = 0; i < entry.outputs.length; i++) {
|
|
13186
13855
|
const file = currentEntryOutputPath(entry, spec, i);
|
|
13187
|
-
if (!(0,
|
|
13856
|
+
if (!(0, import_node_fs18.existsSync)(file)) continue;
|
|
13188
13857
|
if (await sha256File(file) !== entry.outputs[i].sha256 && !opts.force) {
|
|
13189
13858
|
throw new OverwriteRefusedError(
|
|
13190
|
-
`${
|
|
13859
|
+
`${import_node_path22.default.relative(process.cwd(), file)} was changed after download; keep it with pixelkiln edit, or pass --force to replace it`,
|
|
13191
13860
|
[file]
|
|
13192
13861
|
);
|
|
13193
13862
|
}
|
|
@@ -13239,11 +13908,11 @@ async function revertGeneration(provider, spec, lock, lockPath, opts) {
|
|
|
13239
13908
|
|
|
13240
13909
|
// src/pipeline/cache-health.ts
|
|
13241
13910
|
async function inspectCaches(lock, lockPath, options = {}) {
|
|
13242
|
-
if (options.prune && !(0,
|
|
13243
|
-
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)}`);
|
|
13244
13913
|
}
|
|
13245
|
-
const contentDir =
|
|
13246
|
-
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));
|
|
13247
13916
|
const referenced = referencedHashes(lock);
|
|
13248
13917
|
let content = await inspectContentCache(contentDir, referenced);
|
|
13249
13918
|
let remoteHashes = await inspectRemoteHashCache(remotePath);
|
|
@@ -13255,7 +13924,7 @@ async function inspectCaches(lock, lockPath, options = {}) {
|
|
|
13255
13924
|
]);
|
|
13256
13925
|
for (const name of names) {
|
|
13257
13926
|
try {
|
|
13258
|
-
await (0,
|
|
13927
|
+
await (0, import_promises14.rm)(import_node_path23.default.join(contentDir, name), { force: true });
|
|
13259
13928
|
removed.contentFiles++;
|
|
13260
13929
|
} catch {
|
|
13261
13930
|
}
|
|
@@ -13264,7 +13933,7 @@ async function inspectCaches(lock, lockPath, options = {}) {
|
|
|
13264
13933
|
await saveCache(remotePath, { version: 1, hashes: {} });
|
|
13265
13934
|
removed.resetRemoteHashCache = true;
|
|
13266
13935
|
} else if (remoteHashes.invalidIds.length) {
|
|
13267
|
-
const cache = parseCache(JSON.parse(await (0,
|
|
13936
|
+
const cache = parseCache(JSON.parse(await (0, import_promises14.readFile)(remotePath, "utf8")));
|
|
13268
13937
|
for (const id of remoteHashes.invalidIds) delete cache.hashes[id];
|
|
13269
13938
|
removed.remoteHashEntries = remoteHashes.invalidIds.length;
|
|
13270
13939
|
await saveCache(remotePath, cache);
|
|
@@ -13282,7 +13951,7 @@ async function inspectCaches(lock, lockPath, options = {}) {
|
|
|
13282
13951
|
async function inspectContentCache(contentDir, referenced) {
|
|
13283
13952
|
const report = {
|
|
13284
13953
|
path: contentDir,
|
|
13285
|
-
exists: (0,
|
|
13954
|
+
exists: (0, import_node_fs19.existsSync)(contentDir),
|
|
13286
13955
|
files: 0,
|
|
13287
13956
|
bytes: 0,
|
|
13288
13957
|
valid: 0,
|
|
@@ -13296,19 +13965,19 @@ async function inspectContentCache(contentDir, referenced) {
|
|
|
13296
13965
|
return report;
|
|
13297
13966
|
}
|
|
13298
13967
|
const present = /* @__PURE__ */ new Set();
|
|
13299
|
-
const entries = await (0,
|
|
13968
|
+
const entries = await (0, import_promises14.readdir)(contentDir, { withFileTypes: true });
|
|
13300
13969
|
for (const entry of entries) {
|
|
13301
13970
|
if (!entry.isFile()) {
|
|
13302
13971
|
report.invalid.push({ name: entry.name, reason: "not a regular file" });
|
|
13303
13972
|
continue;
|
|
13304
13973
|
}
|
|
13305
13974
|
report.files++;
|
|
13306
|
-
const file =
|
|
13975
|
+
const file = import_node_path23.default.join(contentDir, entry.name);
|
|
13307
13976
|
const mediaType = mediaTypeFromExtension(entry.name);
|
|
13308
13977
|
const expected = mediaType ? entry.name.slice(0, -4) : "";
|
|
13309
13978
|
let bytes;
|
|
13310
13979
|
try {
|
|
13311
|
-
bytes = await (0,
|
|
13980
|
+
bytes = await (0, import_promises14.readFile)(file);
|
|
13312
13981
|
report.bytes += bytes.length;
|
|
13313
13982
|
} catch (err) {
|
|
13314
13983
|
report.invalid.push({
|
|
@@ -13346,7 +14015,7 @@ async function inspectContentCache(contentDir, referenced) {
|
|
|
13346
14015
|
async function inspectRemoteHashCache(remotePath) {
|
|
13347
14016
|
const report = {
|
|
13348
14017
|
path: remotePath,
|
|
13349
|
-
exists: (0,
|
|
14018
|
+
exists: (0, import_node_fs19.existsSync)(remotePath),
|
|
13350
14019
|
entries: 0,
|
|
13351
14020
|
valid: 0,
|
|
13352
14021
|
invalidIds: [],
|
|
@@ -13355,7 +14024,7 @@ async function inspectRemoteHashCache(remotePath) {
|
|
|
13355
14024
|
if (!report.exists) return report;
|
|
13356
14025
|
let cache;
|
|
13357
14026
|
try {
|
|
13358
|
-
cache = parseCache(JSON.parse(await (0,
|
|
14027
|
+
cache = parseCache(JSON.parse(await (0, import_promises14.readFile)(remotePath, "utf8")));
|
|
13359
14028
|
} catch (err) {
|
|
13360
14029
|
report.error = err instanceof Error ? err.message : String(err);
|
|
13361
14030
|
return report;
|
|
@@ -13370,20 +14039,20 @@ async function inspectRemoteHashCache(remotePath) {
|
|
|
13370
14039
|
}
|
|
13371
14040
|
|
|
13372
14041
|
// src/pipeline/pack.ts
|
|
13373
|
-
var
|
|
13374
|
-
var
|
|
14042
|
+
var import_node_fs20 = require("fs");
|
|
14043
|
+
var import_node_path24 = __toESM(require("path"), 1);
|
|
13375
14044
|
var DEFAULT_FRAME_SET_FPS = 12;
|
|
13376
14045
|
function resolvePackInputs(raw, inputsFilePath) {
|
|
13377
14046
|
if (!Array.isArray(raw) || !raw.length) {
|
|
13378
14047
|
throw new Error("--inputs must be a non-empty JSON array of { id, path }");
|
|
13379
14048
|
}
|
|
13380
|
-
const baseDir =
|
|
14049
|
+
const baseDir = import_node_path24.default.dirname(import_node_path24.default.resolve(inputsFilePath));
|
|
13381
14050
|
return raw.map((entry, i) => {
|
|
13382
14051
|
const e = entry;
|
|
13383
14052
|
if (typeof e.id !== "string" || typeof e.path !== "string") {
|
|
13384
14053
|
throw new Error(`--inputs[${i}] needs string "id" and "path"`);
|
|
13385
14054
|
}
|
|
13386
|
-
return { id: e.id, path:
|
|
14055
|
+
return { id: e.id, path: import_node_path24.default.resolve(baseDir, e.path) };
|
|
13387
14056
|
});
|
|
13388
14057
|
}
|
|
13389
14058
|
function packSprites(inputs, options = {}) {
|
|
@@ -13404,7 +14073,7 @@ function packSprites(inputs, options = {}) {
|
|
|
13404
14073
|
for (const input of ordered) {
|
|
13405
14074
|
let sourceHash = null;
|
|
13406
14075
|
try {
|
|
13407
|
-
const bytes = (0,
|
|
14076
|
+
const bytes = (0, import_node_fs20.readFileSync)(input.path);
|
|
13408
14077
|
sourceHash = sha256(bytes);
|
|
13409
14078
|
const png = decodePng(bytes);
|
|
13410
14079
|
sprites.push({ id: input.id, width: png.width, height: png.height, pixels: png.pixels });
|
|
@@ -13467,7 +14136,7 @@ function packStyle(lock, styleId, manifestDir, options = {}) {
|
|
|
13467
14136
|
}
|
|
13468
14137
|
const inputs2 = Object.entries(options.sourceOverrides).map(([id, source]) => ({
|
|
13469
14138
|
id,
|
|
13470
|
-
path:
|
|
14139
|
+
path: import_node_path24.default.resolve(source)
|
|
13471
14140
|
}));
|
|
13472
14141
|
if (!inputs2.length) throw new Error(`No approved quality outputs for style "${styleId}".`);
|
|
13473
14142
|
const packed2 = packSprites(inputs2, options);
|
|
@@ -13491,7 +14160,7 @@ function packStyle(lock, styleId, manifestDir, options = {}) {
|
|
|
13491
14160
|
let outputs = resolveEntryOutputs(entry, id, manifestDir);
|
|
13492
14161
|
const source = sources[id];
|
|
13493
14162
|
if (source && outputs.length <= 1) {
|
|
13494
|
-
inputs.push({ id, path:
|
|
14163
|
+
inputs.push({ id, path: import_node_path24.default.resolve(manifestDir, source) });
|
|
13495
14164
|
continue;
|
|
13496
14165
|
}
|
|
13497
14166
|
if (source && sourceIsStem(source, entry, manifestDir)) {
|
|
@@ -13520,12 +14189,13 @@ function packStyle(lock, styleId, manifestDir, options = {}) {
|
|
|
13520
14189
|
id,
|
|
13521
14190
|
kind: isFrames ? "frames" : "members",
|
|
13522
14191
|
...fps ? { fps } : {},
|
|
14192
|
+
...isFrames && frameSetLoop(entry) === false ? { loop: false } : {},
|
|
13523
14193
|
frames: selected.map((output) => output.id)
|
|
13524
14194
|
});
|
|
13525
14195
|
}
|
|
13526
14196
|
}
|
|
13527
14197
|
for (const [id, source] of Object.entries(sources)) {
|
|
13528
|
-
if (!locked.has(id)) inputs.push({ id, path:
|
|
14198
|
+
if (!locked.has(id)) inputs.push({ id, path: import_node_path24.default.resolve(manifestDir, source) });
|
|
13529
14199
|
}
|
|
13530
14200
|
if (!inputs.length) {
|
|
13531
14201
|
throw new Error(`No readable sprites for style "${styleId}"; ${noOutput.length} skipped.`);
|
|
@@ -13562,7 +14232,7 @@ function mountSprites(placements, options) {
|
|
|
13562
14232
|
for (const p of placements) {
|
|
13563
14233
|
let sourceHash = null;
|
|
13564
14234
|
try {
|
|
13565
|
-
const bytes = (0,
|
|
14235
|
+
const bytes = (0, import_node_fs20.readFileSync)(p.path);
|
|
13566
14236
|
sourceHash = sha256(bytes);
|
|
13567
14237
|
const png = decodePng(bytes);
|
|
13568
14238
|
if (png.width > cellWidth || png.height > cellHeight) {
|
|
@@ -13633,7 +14303,7 @@ function mountStyle(lock, styleId, manifestDir, mount, cells, sources = {}, outp
|
|
|
13633
14303
|
const source = sources[id];
|
|
13634
14304
|
const entry = lock.entries[`${styleId}/${id}`];
|
|
13635
14305
|
if (source && !(entry && sourceIsStem(source, entry, manifestDir))) {
|
|
13636
|
-
placements.push({ id, path:
|
|
14306
|
+
placements.push({ id, path: import_node_path24.default.resolve(manifestDir, source), cell });
|
|
13637
14307
|
continue;
|
|
13638
14308
|
}
|
|
13639
14309
|
if (source && entry) {
|
|
@@ -13666,7 +14336,7 @@ function mountStyle(lock, styleId, manifestDir, mount, cells, sources = {}, outp
|
|
|
13666
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}`
|
|
13667
14337
|
);
|
|
13668
14338
|
}
|
|
13669
|
-
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;
|
|
13670
14340
|
const mounted = mountSprites(placements, {
|
|
13671
14341
|
cellWidth: mount.cellWidth,
|
|
13672
14342
|
cellHeight: mount.cellHeight,
|
|
@@ -13676,7 +14346,7 @@ function mountStyle(lock, styleId, manifestDir, mount, cells, sources = {}, outp
|
|
|
13676
14346
|
...mounted,
|
|
13677
14347
|
sources: mount.base ? [{
|
|
13678
14348
|
id: "$base",
|
|
13679
|
-
path:
|
|
14349
|
+
path: import_node_path24.default.resolve(manifestDir, mount.base),
|
|
13680
14350
|
sha256: sha256(basePng),
|
|
13681
14351
|
included: true
|
|
13682
14352
|
}, ...mounted.sources] : mounted.sources,
|
|
@@ -14058,7 +14728,7 @@ function renderGodotSpriteFrames(atlas, opts) {
|
|
|
14058
14728
|
);
|
|
14059
14729
|
};
|
|
14060
14730
|
for (const set of atlas.sets ?? []) {
|
|
14061
|
-
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);
|
|
14062
14732
|
else for (const id of set.frames) animation(id, [id], GODOT_DEFAULT_SPEED, false);
|
|
14063
14733
|
}
|
|
14064
14734
|
for (const frame of atlas.frames) {
|
|
@@ -14313,6 +14983,8 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
|
|
|
14313
14983
|
const anchor = spec.character?.styleAnchor;
|
|
14314
14984
|
const anchorEntry = anchor ? lock.entries[lockKey(anchor.spec.styleId, anchor.spec.assetId)] : void 0;
|
|
14315
14985
|
const replacedMetadata = previousEntry?.providerMetadata?.[provider.id];
|
|
14986
|
+
const lastGeneration = previousEntry?.status === "downloaded" ? previousEntry : previousEntry?.history?.[0];
|
|
14987
|
+
const lastGenerationMetadata = lastGeneration?.providerMetadata?.[provider.id];
|
|
14316
14988
|
const parentOutputs = spec.character?.kind === "outfit" && parentEntry ? parentEntry.outputs.map((output, index) => ({
|
|
14317
14989
|
path: currentEntryOutputPath(parentEntry, spec.character.parentSpec, index),
|
|
14318
14990
|
role: output.role
|
|
@@ -14325,6 +14997,7 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
|
|
|
14325
14997
|
...anchorEntry?.objectId ? { styleObjectId: anchorEntry.objectId } : {},
|
|
14326
14998
|
...parentOutputs ? { parentOutputs } : {},
|
|
14327
14999
|
...replacedMetadata ? { replacedMetadata } : {},
|
|
15000
|
+
...lastGenerationMetadata ? { lastGenerationMetadata } : {},
|
|
14328
15001
|
...previousMetadata ? { previousMetadata } : {},
|
|
14329
15002
|
checkpoint: async (checkpoint) => {
|
|
14330
15003
|
if (!checkpoint.jobId) throw new Error("Provider checkpoint returned an empty job id");
|
|
@@ -14487,9 +15160,9 @@ async function poll(provider, lock, lockPath, opts = {}) {
|
|
|
14487
15160
|
}
|
|
14488
15161
|
|
|
14489
15162
|
// src/pipeline/doctor.ts
|
|
14490
|
-
var
|
|
14491
|
-
var
|
|
14492
|
-
var
|
|
15163
|
+
var import_node_fs21 = require("fs");
|
|
15164
|
+
var import_promises15 = require("fs/promises");
|
|
15165
|
+
var import_node_path25 = __toESM(require("path"), 1);
|
|
14493
15166
|
async function doctor(loaded, specs, lock, lockPath, opts = {}) {
|
|
14494
15167
|
const checks = [];
|
|
14495
15168
|
const add = (id, level, message6) => checks.push({ id, level, message: message6 });
|
|
@@ -14500,14 +15173,14 @@ async function doctor(loaded, specs, lock, lockPath, opts = {}) {
|
|
|
14500
15173
|
);
|
|
14501
15174
|
add("lock", "ok", `${Object.keys(lock.entries).length} valid v2 lock entr(ies)`);
|
|
14502
15175
|
const dirs = [...new Set(specs.flatMap((spec) => [
|
|
14503
|
-
|
|
14504
|
-
...spec.quality ? [
|
|
15176
|
+
import_node_path25.default.dirname(spec.outFile),
|
|
15177
|
+
...spec.quality ? [import_node_path25.default.dirname(spec.quality.outFile)] : []
|
|
14505
15178
|
]))];
|
|
14506
15179
|
const unwritable = [];
|
|
14507
15180
|
for (const dir of dirs) {
|
|
14508
15181
|
const ancestor = await nearestExistingDirectory(dir);
|
|
14509
15182
|
try {
|
|
14510
|
-
await (0,
|
|
15183
|
+
await (0, import_promises15.access)(ancestor, import_node_fs21.constants.W_OK);
|
|
14511
15184
|
} catch {
|
|
14512
15185
|
unwritable.push(dir);
|
|
14513
15186
|
}
|
|
@@ -14524,7 +15197,7 @@ async function doctor(loaded, specs, lock, lockPath, opts = {}) {
|
|
|
14524
15197
|
for (let index = 0; index < entry.outputs.length; index++) {
|
|
14525
15198
|
const output = entry.outputs[index];
|
|
14526
15199
|
const spec = specByKey.get(key);
|
|
14527
|
-
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));
|
|
14528
15201
|
const owner = outputOwners.get(absolute);
|
|
14529
15202
|
if (owner && owner !== key) duplicateOutputs.push(`${output.path} (${owner}, ${key})`);
|
|
14530
15203
|
outputOwners.set(absolute, key);
|
|
@@ -14539,7 +15212,7 @@ async function doctor(loaded, specs, lock, lockPath, opts = {}) {
|
|
|
14539
15212
|
} else {
|
|
14540
15213
|
add("lock-outputs", "ok", `${outputOwners.size} recorded output path(s) are uniquely owned`);
|
|
14541
15214
|
}
|
|
14542
|
-
const cacheDir =
|
|
15215
|
+
const cacheDir = import_node_path25.default.join(import_node_path25.default.dirname(lockPath), ".pixelkiln", "cache");
|
|
14543
15216
|
const stranded = [];
|
|
14544
15217
|
const invalidState = [];
|
|
14545
15218
|
const staleMapUrls = [];
|
|
@@ -14555,11 +15228,11 @@ async function doctor(loaded, specs, lock, lockPath, opts = {}) {
|
|
|
14555
15228
|
const hasSource = Boolean(entry.sourceUrl || entry.sourceUrls?.length);
|
|
14556
15229
|
let hasCache = false;
|
|
14557
15230
|
for (const output of entry.outputs) {
|
|
14558
|
-
const cached =
|
|
15231
|
+
const cached = import_node_path25.default.join(
|
|
14559
15232
|
cacheDir,
|
|
14560
15233
|
cacheFileName(output.sha256, output.mediaType ?? MediaType.PNG)
|
|
14561
15234
|
);
|
|
14562
|
-
if ((0,
|
|
15235
|
+
if ((0, import_node_fs21.existsSync)(cached) && await sha256File(cached) === output.sha256) {
|
|
14563
15236
|
hasCache = true;
|
|
14564
15237
|
break;
|
|
14565
15238
|
}
|
|
@@ -14652,19 +15325,19 @@ async function nearestExistingDirectory(start) {
|
|
|
14652
15325
|
let current = start;
|
|
14653
15326
|
for (; ; ) {
|
|
14654
15327
|
try {
|
|
14655
|
-
if ((await (0,
|
|
15328
|
+
if ((await (0, import_promises15.stat)(current)).isDirectory()) return current;
|
|
14656
15329
|
} catch {
|
|
14657
15330
|
}
|
|
14658
|
-
const parent =
|
|
15331
|
+
const parent = import_node_path25.default.dirname(current);
|
|
14659
15332
|
if (parent === current) return current;
|
|
14660
15333
|
current = parent;
|
|
14661
15334
|
}
|
|
14662
15335
|
}
|
|
14663
15336
|
|
|
14664
15337
|
// src/pipeline/adopt.ts
|
|
14665
|
-
var
|
|
14666
|
-
var
|
|
14667
|
-
var
|
|
15338
|
+
var import_node_fs22 = require("fs");
|
|
15339
|
+
var import_promises16 = require("fs/promises");
|
|
15340
|
+
var import_node_path26 = __toESM(require("path"), 1);
|
|
14668
15341
|
async function adopt(provider, specs, lock, lockPath, opts = {}) {
|
|
14669
15342
|
const log = opts.onProgress ?? (() => {
|
|
14670
15343
|
});
|
|
@@ -14675,11 +15348,11 @@ async function adopt(provider, specs, lock, lockPath, opts = {}) {
|
|
|
14675
15348
|
const localByHash = /* @__PURE__ */ new Map();
|
|
14676
15349
|
const unmatchedLocal = [];
|
|
14677
15350
|
for (const spec of specs) {
|
|
14678
|
-
if (!(0,
|
|
15351
|
+
if (!(0, import_node_fs22.existsSync)(spec.outFile)) {
|
|
14679
15352
|
unmatchedLocal.push(`${spec.styleId}/${spec.assetId} (no file at ${spec.outFile})`);
|
|
14680
15353
|
continue;
|
|
14681
15354
|
}
|
|
14682
|
-
const bytes = await (0,
|
|
15355
|
+
const bytes = await (0, import_promises16.readFile)(spec.outFile);
|
|
14683
15356
|
try {
|
|
14684
15357
|
decodePng(bytes);
|
|
14685
15358
|
} catch (err) {
|
|
@@ -14797,13 +15470,13 @@ async function tagAdopted(provider, specs, lock, opts = {}) {
|
|
|
14797
15470
|
}
|
|
14798
15471
|
|
|
14799
15472
|
// src/pick/server.ts
|
|
14800
|
-
var
|
|
14801
|
-
var
|
|
15473
|
+
var import_node_fs24 = require("fs");
|
|
15474
|
+
var import_promises19 = require("fs/promises");
|
|
14802
15475
|
|
|
14803
15476
|
// src/pipeline/init.ts
|
|
14804
|
-
var
|
|
14805
|
-
var
|
|
14806
|
-
var
|
|
15477
|
+
var import_promises17 = require("fs/promises");
|
|
15478
|
+
var import_node_fs23 = require("fs");
|
|
15479
|
+
var import_node_path27 = __toESM(require("path"), 1);
|
|
14807
15480
|
function pngSize(buf) {
|
|
14808
15481
|
if (buf.length < 24) return null;
|
|
14809
15482
|
const isPng = buf.readUInt32BE(0) === 2303741511 && buf.readUInt32BE(4) === 218765834;
|
|
@@ -14816,8 +15489,8 @@ function slugify(value) {
|
|
|
14816
15489
|
}
|
|
14817
15490
|
async function walk(dir, exclude) {
|
|
14818
15491
|
const out = [];
|
|
14819
|
-
for (const entry of await (0,
|
|
14820
|
-
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);
|
|
14821
15494
|
if (exclude.some((x) => full.includes(x))) continue;
|
|
14822
15495
|
if (entry.isDirectory()) out.push(...await walk(full, exclude));
|
|
14823
15496
|
else if (entry.name.toLowerCase().endsWith(".png")) out.push(full);
|
|
@@ -14831,14 +15504,14 @@ async function scanAssets(root, opts = {}) {
|
|
|
14831
15504
|
const skipped = [];
|
|
14832
15505
|
const seen = /* @__PURE__ */ new Map();
|
|
14833
15506
|
for (const file of files.sort()) {
|
|
14834
|
-
const size = pngSize(await (0,
|
|
15507
|
+
const size = pngSize(await (0, import_promises17.readFile)(file));
|
|
14835
15508
|
if (!size) {
|
|
14836
15509
|
skipped.push(`${file} (not a readable PNG)`);
|
|
14837
15510
|
continue;
|
|
14838
15511
|
}
|
|
14839
|
-
const rel =
|
|
14840
|
-
const category =
|
|
14841
|
-
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));
|
|
14842
15515
|
if (seen.has(id)) {
|
|
14843
15516
|
const n = seen.get(id) + 1;
|
|
14844
15517
|
seen.set(id, n);
|
|
@@ -15302,7 +15975,7 @@ document.getElementById('submit').onclick = async () => {
|
|
|
15302
15975
|
|
|
15303
15976
|
// src/pick/review-server.ts
|
|
15304
15977
|
var import_node_http = require("http");
|
|
15305
|
-
var
|
|
15978
|
+
var import_promises18 = require("fs/promises");
|
|
15306
15979
|
function serveReviewPage(opts) {
|
|
15307
15980
|
const log = opts.onProgress ?? (() => {
|
|
15308
15981
|
});
|
|
@@ -15311,7 +15984,7 @@ function serveReviewPage(opts) {
|
|
|
15311
15984
|
if (req.method === "GET" && req.url && opts.assets?.has(req.url)) {
|
|
15312
15985
|
try {
|
|
15313
15986
|
const asset = opts.assets.get(req.url);
|
|
15314
|
-
const bytes = await (0,
|
|
15987
|
+
const bytes = await (0, import_promises18.readFile)(asset.path);
|
|
15315
15988
|
res.writeHead(200, {
|
|
15316
15989
|
"Content-Type": asset.contentType,
|
|
15317
15990
|
"Cache-Control": "no-store",
|
|
@@ -15387,13 +16060,13 @@ async function currentArt(entry, spec) {
|
|
|
15387
16060
|
const previous = primaryOutput({ ...entry, outputs: entry.supersededOutputs });
|
|
15388
16061
|
if (!previous) return null;
|
|
15389
16062
|
const file = resolveOutputPath(previous.path, spec.root);
|
|
15390
|
-
if (!(0,
|
|
16063
|
+
if (!(0, import_node_fs24.existsSync)(file)) return null;
|
|
15391
16064
|
const contentType = previous.mediaType ?? mediaTypeFromExtension(file);
|
|
15392
16065
|
if (!contentType) return null;
|
|
15393
16066
|
let width = entry.width;
|
|
15394
16067
|
let height = entry.height;
|
|
15395
16068
|
if (contentType === "image/png") {
|
|
15396
|
-
const size = pngSize(await (0,
|
|
16069
|
+
const size = pngSize(await (0, import_promises19.readFile)(file));
|
|
15397
16070
|
if (size) ({ width, height } = size);
|
|
15398
16071
|
}
|
|
15399
16072
|
return { path: file, contentType, width, height };
|
|
@@ -15401,7 +16074,7 @@ async function currentArt(entry, spec) {
|
|
|
15401
16074
|
async function cachedFrame(url) {
|
|
15402
16075
|
if (!url.startsWith("file://")) return null;
|
|
15403
16076
|
const file = url.slice("file://".length);
|
|
15404
|
-
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";
|
|
15405
16078
|
return { path: file, contentType };
|
|
15406
16079
|
}
|
|
15407
16080
|
async function prepareReview(provider, lock, opts = {}) {
|
|
@@ -15583,14 +16256,14 @@ async function runPicker(provider, lock, lockPath, opts = {}) {
|
|
|
15583
16256
|
|
|
15584
16257
|
// src/gallery/snapshot.ts
|
|
15585
16258
|
var import_node_crypto7 = require("crypto");
|
|
15586
|
-
var
|
|
15587
|
-
var
|
|
15588
|
-
var
|
|
16259
|
+
var import_node_fs30 = require("fs");
|
|
16260
|
+
var import_promises27 = require("fs/promises");
|
|
16261
|
+
var import_node_path35 = __toESM(require("path"), 1);
|
|
15589
16262
|
|
|
15590
16263
|
// src/workspace.ts
|
|
15591
|
-
var
|
|
15592
|
-
var
|
|
15593
|
-
var
|
|
16264
|
+
var import_promises20 = require("fs/promises");
|
|
16265
|
+
var import_node_fs25 = require("fs");
|
|
16266
|
+
var import_node_path28 = __toESM(require("path"), 1);
|
|
15594
16267
|
var import_zod7 = require("zod");
|
|
15595
16268
|
var WorkspaceProjectSchema = import_zod7.z.object({
|
|
15596
16269
|
id: import_zod7.z.string().min(1),
|
|
@@ -15615,10 +16288,10 @@ ${parsed.error.issues.slice(0, 5).map((i) => ` ${i.path.join(".")}: ${i.message
|
|
|
15615
16288
|
);
|
|
15616
16289
|
}
|
|
15617
16290
|
async function loadWorkspace(workspacePath) {
|
|
15618
|
-
if (!(0,
|
|
16291
|
+
if (!(0, import_node_fs25.existsSync)(workspacePath)) return { version: 1, projects: [] };
|
|
15619
16292
|
let raw;
|
|
15620
16293
|
try {
|
|
15621
|
-
raw = JSON.parse(await (0,
|
|
16294
|
+
raw = JSON.parse(await (0, import_promises20.readFile)(workspacePath, "utf8"));
|
|
15622
16295
|
} catch (err) {
|
|
15623
16296
|
throw new ProjectError(
|
|
15624
16297
|
`Workspace catalog at ${workspacePath} is malformed:
|
|
@@ -15632,22 +16305,22 @@ async function saveWorkspace(workspacePath, ws) {
|
|
|
15632
16305
|
version: 1,
|
|
15633
16306
|
projects: [...ws.projects].sort((a, b) => a.id.localeCompare(b.id))
|
|
15634
16307
|
};
|
|
15635
|
-
await (0,
|
|
16308
|
+
await (0, import_promises20.mkdir)(import_node_path28.default.dirname(import_node_path28.default.resolve(workspacePath)), { recursive: true });
|
|
15636
16309
|
const tmp = `${workspacePath}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
15637
16310
|
try {
|
|
15638
|
-
await (0,
|
|
15639
|
-
await (0,
|
|
16311
|
+
await (0, import_promises20.writeFile)(tmp, JSON.stringify(sorted, null, 2) + "\n");
|
|
16312
|
+
await (0, import_promises20.rename)(tmp, workspacePath);
|
|
15640
16313
|
} finally {
|
|
15641
|
-
await (0,
|
|
16314
|
+
await (0, import_promises20.rm)(tmp, { force: true });
|
|
15642
16315
|
}
|
|
15643
16316
|
}
|
|
15644
16317
|
function toPortablePath(dir, absolute) {
|
|
15645
|
-
return
|
|
16318
|
+
return import_node_path28.default.relative(dir, absolute).split(import_node_path28.default.sep).join("/");
|
|
15646
16319
|
}
|
|
15647
16320
|
function resolveProject(dir, project) {
|
|
15648
16321
|
return {
|
|
15649
|
-
manifestPath:
|
|
15650
|
-
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))
|
|
15651
16324
|
};
|
|
15652
16325
|
}
|
|
15653
16326
|
function validateWorkspace(ws, dir) {
|
|
@@ -15660,21 +16333,21 @@ function validateWorkspace(ws, dir) {
|
|
|
15660
16333
|
const { manifestPath, lockPath } = resolveProject(dir, project);
|
|
15661
16334
|
lockOwners.set(lockPath, [...lockOwners.get(lockPath) ?? [], project.id]);
|
|
15662
16335
|
manifestOwners.set(manifestPath, [...manifestOwners.get(manifestPath) ?? [], project.id]);
|
|
15663
|
-
if (
|
|
16336
|
+
if (import_node_path28.default.isAbsolute(project.manifest) || import_node_path28.default.isAbsolute(project.lock)) {
|
|
15664
16337
|
diagnostics.push({
|
|
15665
16338
|
id: "absolute-path",
|
|
15666
16339
|
level: "warning",
|
|
15667
16340
|
message: `project "${project.id}" stores an absolute path; the catalog will not resolve correctly if this tree is cloned or moved elsewhere`
|
|
15668
16341
|
});
|
|
15669
16342
|
}
|
|
15670
|
-
if (!(0,
|
|
16343
|
+
if (!(0, import_node_fs25.existsSync)(manifestPath)) {
|
|
15671
16344
|
diagnostics.push({
|
|
15672
16345
|
id: "missing-manifest",
|
|
15673
16346
|
level: "error",
|
|
15674
16347
|
message: `project "${project.id}" manifest not found: ${manifestPath}`
|
|
15675
16348
|
});
|
|
15676
16349
|
}
|
|
15677
|
-
if (!(0,
|
|
16350
|
+
if (!(0, import_node_fs25.existsSync)(lockPath)) {
|
|
15678
16351
|
diagnostics.push({
|
|
15679
16352
|
id: "missing-lock",
|
|
15680
16353
|
level: "error",
|
|
@@ -15721,16 +16394,16 @@ function validateWorkspace(ws, dir) {
|
|
|
15721
16394
|
}
|
|
15722
16395
|
|
|
15723
16396
|
// src/gallery/edit.ts
|
|
15724
|
-
var
|
|
15725
|
-
var
|
|
16397
|
+
var import_promises26 = require("fs/promises");
|
|
16398
|
+
var import_node_path34 = __toESM(require("path"), 1);
|
|
15726
16399
|
var import_zod13 = require("zod");
|
|
15727
16400
|
|
|
15728
16401
|
// src/manifest-edit.ts
|
|
15729
|
-
var
|
|
15730
|
-
var
|
|
16402
|
+
var import_promises21 = require("fs/promises");
|
|
16403
|
+
var import_node_path29 = __toESM(require("path"), 1);
|
|
15731
16404
|
var import_zod8 = require("zod");
|
|
15732
16405
|
var HexSha = import_zod8.z.string().regex(/^[0-9a-f]{64}$/);
|
|
15733
|
-
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");
|
|
15734
16407
|
var AssetPatchSchema = import_zod8.z.object({
|
|
15735
16408
|
prompt: import_zod8.z.string().optional(),
|
|
15736
16409
|
/** Per-style override for the style being viewed; null removes it. */
|
|
@@ -16149,8 +16822,8 @@ function applyEdit(raw, edit) {
|
|
|
16149
16822
|
}
|
|
16150
16823
|
}
|
|
16151
16824
|
async function withCandidate(manifestPath, edit, use, unchanged) {
|
|
16152
|
-
const absolute =
|
|
16153
|
-
const original = await (0,
|
|
16825
|
+
const absolute = import_node_path29.default.resolve(manifestPath);
|
|
16826
|
+
const original = await (0, import_promises21.readFile)(absolute, "utf8");
|
|
16154
16827
|
if (sha256(original) !== edit.expectedSha256) throw new ManifestDriftError(absolute);
|
|
16155
16828
|
let raw;
|
|
16156
16829
|
try {
|
|
@@ -16163,12 +16836,12 @@ async function withCandidate(manifestPath, edit, use, unchanged) {
|
|
|
16163
16836
|
applyEdit(raw, edit);
|
|
16164
16837
|
const next = serializeLike(original, raw);
|
|
16165
16838
|
if (next === original) return unchanged(absolute, original);
|
|
16166
|
-
const tmp =
|
|
16167
|
-
|
|
16168
|
-
`.${
|
|
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`
|
|
16169
16842
|
);
|
|
16170
16843
|
try {
|
|
16171
|
-
await (0,
|
|
16844
|
+
await (0, import_promises21.writeFile)(tmp, next);
|
|
16172
16845
|
let specs;
|
|
16173
16846
|
try {
|
|
16174
16847
|
specs = await resolveSpecs(await loadManifest(tmp));
|
|
@@ -16178,7 +16851,7 @@ async function withCandidate(manifestPath, edit, use, unchanged) {
|
|
|
16178
16851
|
}
|
|
16179
16852
|
return await use({ absolute, original, next, tmp, specs });
|
|
16180
16853
|
} finally {
|
|
16181
|
-
await (0,
|
|
16854
|
+
await (0, import_promises21.rm)(tmp, { force: true });
|
|
16182
16855
|
}
|
|
16183
16856
|
}
|
|
16184
16857
|
async function applyManifestEdit(manifestPath, edit) {
|
|
@@ -16186,7 +16859,7 @@ async function applyManifestEdit(manifestPath, edit) {
|
|
|
16186
16859
|
manifestPath,
|
|
16187
16860
|
edit,
|
|
16188
16861
|
async ({ absolute, next, tmp }) => {
|
|
16189
|
-
await (0,
|
|
16862
|
+
await (0, import_promises21.rename)(tmp, absolute);
|
|
16190
16863
|
return { manifestPath: absolute, sha256: sha256(next), changed: true };
|
|
16191
16864
|
},
|
|
16192
16865
|
(absolute, original) => ({ manifestPath: absolute, sha256: sha256(original), changed: false })
|
|
@@ -16194,18 +16867,18 @@ async function applyManifestEdit(manifestPath, edit) {
|
|
|
16194
16867
|
}
|
|
16195
16868
|
|
|
16196
16869
|
// src/pipeline/hand-edit.ts
|
|
16197
|
-
var
|
|
16198
|
-
var
|
|
16199
|
-
var
|
|
16870
|
+
var import_node_fs26 = require("fs");
|
|
16871
|
+
var import_promises22 = require("fs/promises");
|
|
16872
|
+
var import_node_path30 = __toESM(require("path"), 1);
|
|
16200
16873
|
var import_zod9 = require("zod");
|
|
16201
16874
|
var HAND_EDIT_DIR = "edits";
|
|
16202
16875
|
function handEditPath(loaded, spec) {
|
|
16203
16876
|
const style = loaded.manifest.styles[spec.styleId];
|
|
16204
|
-
const outDir =
|
|
16205
|
-
const relative =
|
|
16206
|
-
const inside = relative && !relative.startsWith("..") && !
|
|
16207
|
-
const file = inside ? relative :
|
|
16208
|
-
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"));
|
|
16209
16882
|
}
|
|
16210
16883
|
function handEditBases(spec, lock) {
|
|
16211
16884
|
const entry = lock.entries[lockKey(spec.styleId, spec.assetId)];
|
|
@@ -16218,10 +16891,10 @@ function handEditBases(spec, lock) {
|
|
|
16218
16891
|
}));
|
|
16219
16892
|
if (members.some((member) => member.mediaType && member.mediaType !== "image/png")) return null;
|
|
16220
16893
|
if (entry.outputs.length === 1 && !primaryOutput(entry)) return null;
|
|
16221
|
-
if (!members.every((member) => (0,
|
|
16894
|
+
if (!members.every((member) => (0, import_node_fs26.existsSync)(member.path))) return null;
|
|
16222
16895
|
return members.map(({ role, path: file }) => ({ role, path: file }));
|
|
16223
16896
|
}
|
|
16224
|
-
return (0,
|
|
16897
|
+
return (0, import_node_fs26.existsSync)(spec.outFile) ? [{ role: null, path: spec.outFile }] : null;
|
|
16225
16898
|
}
|
|
16226
16899
|
function handEditBase(spec, lock) {
|
|
16227
16900
|
const bases = handEditBases(spec, lock);
|
|
@@ -16251,19 +16924,19 @@ async function startHandEdit(loaded, lock, spec, opts = {}) {
|
|
|
16251
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`
|
|
16252
16925
|
);
|
|
16253
16926
|
}
|
|
16254
|
-
const existing = spec.source ?
|
|
16927
|
+
const existing = spec.source ? import_node_path30.default.resolve(loaded.root, spec.source) : null;
|
|
16255
16928
|
const editPath = existing ?? handEditPath(loaded, spec);
|
|
16256
16929
|
const members = handEditMembers(loaded, spec, lock, editPath);
|
|
16257
16930
|
let created = false;
|
|
16258
16931
|
for (const member of members) {
|
|
16259
|
-
if ((0,
|
|
16932
|
+
if ((0, import_node_fs26.existsSync)(member.path)) continue;
|
|
16260
16933
|
if (!member.base) {
|
|
16261
16934
|
throw new ManifestEditError(
|
|
16262
16935
|
`${spec.styleId}/${spec.assetId} has no generated PNG on disk to start from; generate or restore it first`
|
|
16263
16936
|
);
|
|
16264
16937
|
}
|
|
16265
|
-
await (0,
|
|
16266
|
-
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);
|
|
16267
16940
|
created = true;
|
|
16268
16941
|
}
|
|
16269
16942
|
const source = portableOutputPath(editPath, loaded.root);
|
|
@@ -16341,7 +17014,7 @@ function handEditProjectPath(editPath) {
|
|
|
16341
17014
|
async function readHandEditCompanion(editPath) {
|
|
16342
17015
|
let text;
|
|
16343
17016
|
try {
|
|
16344
|
-
text = await (0,
|
|
17017
|
+
text = await (0, import_promises22.readFile)(handEditCompanionPath(editPath), "utf8");
|
|
16345
17018
|
} catch {
|
|
16346
17019
|
return null;
|
|
16347
17020
|
}
|
|
@@ -16384,7 +17057,7 @@ async function saveHandEdit(loaded, lock, spec, input) {
|
|
|
16384
17057
|
}
|
|
16385
17058
|
});
|
|
16386
17059
|
const bases = handEditBases(spec, lock);
|
|
16387
|
-
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 };
|
|
16388
17061
|
for (const [index, image] of decoded.entries()) {
|
|
16389
17062
|
if (image.width !== expectedSize.width || image.height !== expectedSize.height) {
|
|
16390
17063
|
const which = memberSet ? `member ${incoming[index].role ?? index}` : "the edit";
|
|
@@ -16400,7 +17073,7 @@ async function saveHandEdit(loaded, lock, spec, input) {
|
|
|
16400
17073
|
editor: input.editor,
|
|
16401
17074
|
protocol: input.protocol,
|
|
16402
17075
|
savedAt: (input.now ?? /* @__PURE__ */ new Date()).toISOString(),
|
|
16403
|
-
project: projectPath ?
|
|
17076
|
+
project: projectPath ? import_node_path30.default.basename(projectPath) : null,
|
|
16404
17077
|
outputs: incoming.map((frame, index) => ({
|
|
16405
17078
|
role: frame.role,
|
|
16406
17079
|
basedOn: basedOnHash(entry, index),
|
|
@@ -16430,10 +17103,10 @@ function basedOnHash(entry, index) {
|
|
|
16430
17103
|
async function replaceFile(file, bytes) {
|
|
16431
17104
|
const tmp = `${file}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
16432
17105
|
try {
|
|
16433
|
-
await (0,
|
|
16434
|
-
await (0,
|
|
17106
|
+
await (0, import_promises22.writeFile)(tmp, bytes);
|
|
17107
|
+
await (0, import_promises22.rename)(tmp, file);
|
|
16435
17108
|
} finally {
|
|
16436
|
-
await (0,
|
|
17109
|
+
await (0, import_promises22.rm)(tmp, { force: true });
|
|
16437
17110
|
}
|
|
16438
17111
|
}
|
|
16439
17112
|
function openInEditor(file, editor = process.env.PIXELKILN_EDITOR) {
|
|
@@ -16442,9 +17115,9 @@ function openInEditor(file, editor = process.env.PIXELKILN_EDITOR) {
|
|
|
16442
17115
|
}
|
|
16443
17116
|
|
|
16444
17117
|
// src/gallery/upload.ts
|
|
16445
|
-
var
|
|
16446
|
-
var
|
|
16447
|
-
var
|
|
17118
|
+
var import_node_fs27 = require("fs");
|
|
17119
|
+
var import_promises23 = require("fs/promises");
|
|
17120
|
+
var import_node_path31 = __toESM(require("path"), 1);
|
|
16448
17121
|
var import_zod10 = require("zod");
|
|
16449
17122
|
var MAX_UPLOAD_BYTES = 8 * 1024 * 1024;
|
|
16450
17123
|
var UploadImageSchema = import_zod10.z.object({
|
|
@@ -16457,12 +17130,12 @@ var UploadImageSchema = import_zod10.z.object({
|
|
|
16457
17130
|
overwrite: import_zod10.z.boolean().optional()
|
|
16458
17131
|
}).strict();
|
|
16459
17132
|
async function saveProjectImage(root, request) {
|
|
16460
|
-
const file =
|
|
16461
|
-
const relative =
|
|
16462
|
-
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)) {
|
|
16463
17136
|
throw new ManifestEditError("an upload must stay inside the project");
|
|
16464
17137
|
}
|
|
16465
|
-
const extension =
|
|
17138
|
+
const extension = import_node_path31.default.extname(file).toLowerCase();
|
|
16466
17139
|
const expected = extension === ".png" ? "png" : extension === ".jpg" || extension === ".jpeg" ? "jpeg" : null;
|
|
16467
17140
|
if (!expected) throw new ManifestEditError("an upload must be a .png, .jpg, or .jpeg path");
|
|
16468
17141
|
const bytes = Buffer.from(request.base64, "base64");
|
|
@@ -16474,18 +17147,18 @@ async function saveProjectImage(root, request) {
|
|
|
16474
17147
|
if (metadata.format !== expected) {
|
|
16475
17148
|
throw new ManifestEditError(`the upload is a ${metadata.format.toUpperCase()}; name it ${metadata.format === "png" ? ".png" : ".jpg"}`);
|
|
16476
17149
|
}
|
|
16477
|
-
if ((0,
|
|
16478
|
-
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`);
|
|
16479
17152
|
}
|
|
16480
|
-
await (0,
|
|
16481
|
-
await (0,
|
|
16482
|
-
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("/");
|
|
16483
17156
|
}
|
|
16484
17157
|
|
|
16485
17158
|
// src/gallery/image-revision.ts
|
|
16486
|
-
var
|
|
16487
|
-
var
|
|
16488
|
-
var
|
|
17159
|
+
var import_node_fs28 = require("fs");
|
|
17160
|
+
var import_promises24 = require("fs/promises");
|
|
17161
|
+
var import_node_path32 = __toESM(require("path"), 1);
|
|
16489
17162
|
var import_zod11 = require("zod");
|
|
16490
17163
|
var InpaintRevisionRequestSchema = import_zod11.z.object({
|
|
16491
17164
|
action: import_zod11.z.literal("create-inpaint-revision"),
|
|
@@ -16504,15 +17177,15 @@ var InpaintRevisionRequestSchema = import_zod11.z.object({
|
|
|
16504
17177
|
overwrite: import_zod11.z.boolean().optional()
|
|
16505
17178
|
}).strict();
|
|
16506
17179
|
async function createInpaintRevision(manifestPath, request) {
|
|
16507
|
-
const root =
|
|
16508
|
-
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") {
|
|
16509
17182
|
throw new ManifestEditError("the mask must be a .png path inside the project");
|
|
16510
17183
|
}
|
|
16511
17184
|
const bytes = Buffer.from(request.maskBase64, "base64");
|
|
16512
17185
|
const metadata = imageMetadata(bytes);
|
|
16513
17186
|
if (!metadata || metadata.format !== "png") throw new ManifestEditError("the mask is not a readable PNG");
|
|
16514
|
-
const file =
|
|
16515
|
-
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;
|
|
16516
17189
|
const mask = await saveProjectImage(root, {
|
|
16517
17190
|
action: "upload-image",
|
|
16518
17191
|
path: request.maskPath,
|
|
@@ -16531,17 +17204,17 @@ async function createInpaintRevision(manifestPath, request) {
|
|
|
16531
17204
|
}
|
|
16532
17205
|
});
|
|
16533
17206
|
} catch (error) {
|
|
16534
|
-
if (previous) await (0,
|
|
16535
|
-
else await (0,
|
|
17207
|
+
if (previous) await (0, import_promises24.writeFile)(file, previous);
|
|
17208
|
+
else await (0, import_promises24.rm)(file, { force: true });
|
|
16536
17209
|
throw error;
|
|
16537
17210
|
}
|
|
16538
17211
|
return mask;
|
|
16539
17212
|
}
|
|
16540
17213
|
|
|
16541
17214
|
// src/gallery/skeleton.ts
|
|
16542
|
-
var
|
|
16543
|
-
var
|
|
16544
|
-
var
|
|
17215
|
+
var import_node_fs29 = require("fs");
|
|
17216
|
+
var import_promises25 = require("fs/promises");
|
|
17217
|
+
var import_node_path33 = __toESM(require("path"), 1);
|
|
16545
17218
|
var import_zod12 = require("zod");
|
|
16546
17219
|
var SkeletonEstimateRequestSchema = import_zod12.z.object({
|
|
16547
17220
|
project: import_zod12.z.string().min(1).optional(),
|
|
@@ -16579,21 +17252,21 @@ var SkeletonKeypointsUpdateSchema = import_zod12.z.object({
|
|
|
16579
17252
|
set: SkeletonSetSchema
|
|
16580
17253
|
}).strict();
|
|
16581
17254
|
async function createSkeletonAnimation(manifestPath, request) {
|
|
16582
|
-
const root =
|
|
16583
|
-
const file =
|
|
16584
|
-
const relative =
|
|
16585
|
-
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)) {
|
|
16586
17259
|
throw new ManifestEditError("the keypoints file must be a .json path inside the project");
|
|
16587
17260
|
}
|
|
16588
|
-
const existed = (0,
|
|
17261
|
+
const existed = (0, import_node_fs29.existsSync)(file);
|
|
16589
17262
|
let previous = null;
|
|
16590
17263
|
if (request.set) {
|
|
16591
17264
|
if (existed && !request.overwrite) {
|
|
16592
17265
|
throw new ManifestEditError(`${request.keypointsFile} already exists; choose another path, or replace it`);
|
|
16593
17266
|
}
|
|
16594
|
-
if (existed) previous = await (0,
|
|
16595
|
-
await (0,
|
|
16596
|
-
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");
|
|
16597
17270
|
} else if (!existed) {
|
|
16598
17271
|
throw new ManifestEditError(`${request.keypointsFile} does not exist; paste poses or estimate them from the sprite`);
|
|
16599
17272
|
}
|
|
@@ -16608,7 +17281,7 @@ async function createSkeletonAnimation(manifestPath, request) {
|
|
|
16608
17281
|
revision: {
|
|
16609
17282
|
mode: "animate-skeleton",
|
|
16610
17283
|
from: request.from,
|
|
16611
|
-
keypointsFile: relative.split(
|
|
17284
|
+
keypointsFile: relative.split(import_node_path33.default.sep).join("/"),
|
|
16612
17285
|
direction: request.direction,
|
|
16613
17286
|
...request.description ? { description: request.description } : {},
|
|
16614
17287
|
...request.skeletonTemplate ? { skeletonTemplate: request.skeletonTemplate } : {}
|
|
@@ -16617,8 +17290,8 @@ async function createSkeletonAnimation(manifestPath, request) {
|
|
|
16617
17290
|
});
|
|
16618
17291
|
} catch (error) {
|
|
16619
17292
|
if (request.set) {
|
|
16620
|
-
if (previous) await (0,
|
|
16621
|
-
else await (0,
|
|
17293
|
+
if (previous) await (0, import_promises25.writeFile)(file, previous);
|
|
17294
|
+
else await (0, import_promises25.rm)(file, { force: true });
|
|
16622
17295
|
}
|
|
16623
17296
|
throw error;
|
|
16624
17297
|
}
|
|
@@ -16629,19 +17302,19 @@ async function updateSkeletonKeypoints(ctx, request) {
|
|
|
16629
17302
|
if (!spec) throw new ManifestEditError(`"${key}" is not declared by the manifest`);
|
|
16630
17303
|
const file = spec.revision?.mode === "animate-skeleton" ? spec.revision.keypointsFile : void 0;
|
|
16631
17304
|
if (!file) throw new ManifestEditError(`${key} is not an animate-skeleton revision`);
|
|
16632
|
-
const relative =
|
|
16633
|
-
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)) {
|
|
16634
17307
|
throw new ManifestEditError(`${key}'s keypoints file is outside the project; edit it by hand`);
|
|
16635
17308
|
}
|
|
16636
|
-
const current = (0,
|
|
17309
|
+
const current = (0, import_node_fs29.existsSync)(file) ? sha256(await (0, import_promises25.readFile)(file)) : null;
|
|
16637
17310
|
if (current !== request.expectedSha256) {
|
|
16638
17311
|
throw Object.assign(
|
|
16639
|
-
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`),
|
|
16640
17313
|
{ status: 409 }
|
|
16641
17314
|
);
|
|
16642
17315
|
}
|
|
16643
|
-
await (0,
|
|
16644
|
-
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("/");
|
|
16645
17318
|
}
|
|
16646
17319
|
|
|
16647
17320
|
// src/gallery/edit.ts
|
|
@@ -16695,7 +17368,7 @@ function createGalleryEditHandler(opts) {
|
|
|
16695
17368
|
const history = /* @__PURE__ */ new Map();
|
|
16696
17369
|
const manifestOf = async (project) => {
|
|
16697
17370
|
try {
|
|
16698
|
-
return
|
|
17371
|
+
return import_node_path34.default.resolve(await opts.manifestFor(typeof project === "string" ? project : void 0));
|
|
16699
17372
|
} catch {
|
|
16700
17373
|
return null;
|
|
16701
17374
|
}
|
|
@@ -16705,7 +17378,7 @@ function createGalleryEditHandler(opts) {
|
|
|
16705
17378
|
if (undo.success) {
|
|
16706
17379
|
const manifestPath2 = await manifestOf(undo.data.project);
|
|
16707
17380
|
if (!manifestPath2) throw new ManifestEditError("no manifest to undo an edit in");
|
|
16708
|
-
const current = await (0,
|
|
17381
|
+
const current = await (0, import_promises26.readFile)(manifestPath2, "utf8");
|
|
16709
17382
|
if (sha256(current) !== undo.data.expectedSha256) throw new ManifestDriftError(manifestPath2);
|
|
16710
17383
|
const stack = history.get(manifestPath2) ?? [];
|
|
16711
17384
|
const last = stack[stack.length - 1];
|
|
@@ -16715,17 +17388,17 @@ function createGalleryEditHandler(opts) {
|
|
|
16715
17388
|
throw Object.assign(new Error("the manifest changed outside the gallery since its last edit; undo would discard that change"), { status: 409 });
|
|
16716
17389
|
}
|
|
16717
17390
|
const tmp = manifestPath2 + ".pixelkiln-undo";
|
|
16718
|
-
await (0,
|
|
16719
|
-
await (0,
|
|
17391
|
+
await (0, import_promises26.writeFile)(tmp, last.before);
|
|
17392
|
+
await (0, import_promises26.rename)(tmp, manifestPath2);
|
|
16720
17393
|
stack.pop();
|
|
16721
|
-
log(` manifest edit undone in ${
|
|
17394
|
+
log(` manifest edit undone in ${import_node_path34.default.relative(process.cwd(), manifestPath2) || manifestPath2}`);
|
|
16722
17395
|
return opts.reload();
|
|
16723
17396
|
}
|
|
16724
17397
|
const manifestPath = await manifestOf(body?.project);
|
|
16725
|
-
const before = manifestPath ? await (0,
|
|
17398
|
+
const before = manifestPath ? await (0, import_promises26.readFile)(manifestPath, "utf8").catch(() => null) : null;
|
|
16726
17399
|
const build = await apply(body);
|
|
16727
17400
|
if (manifestPath && before !== null) {
|
|
16728
|
-
const after = await (0,
|
|
17401
|
+
const after = await (0, import_promises26.readFile)(manifestPath, "utf8").catch(() => null);
|
|
16729
17402
|
if (after !== null && after !== before) {
|
|
16730
17403
|
const stack = history.get(manifestPath) ?? [];
|
|
16731
17404
|
stack.push({ before, afterSha: sha256(after) });
|
|
@@ -16780,7 +17453,7 @@ function createEditApplier(opts, log) {
|
|
|
16780
17453
|
} catch (error) {
|
|
16781
17454
|
throw new ManifestEditError(error instanceof Error ? error.message : String(error));
|
|
16782
17455
|
}
|
|
16783
|
-
const written = await saveProjectImage(
|
|
17456
|
+
const written = await saveProjectImage(import_node_path34.default.dirname(import_node_path34.default.resolve(manifestPath2)), upload.data);
|
|
16784
17457
|
log(` image added: ${written}`);
|
|
16785
17458
|
return opts.reload();
|
|
16786
17459
|
}
|
|
@@ -16847,7 +17520,7 @@ function createEditApplier(opts, log) {
|
|
|
16847
17520
|
}
|
|
16848
17521
|
const result = await applyManifestEdit(manifestPath, parsed.data);
|
|
16849
17522
|
if (result.changed) {
|
|
16850
|
-
const relative =
|
|
17523
|
+
const relative = import_node_path34.default.relative(process.cwd(), result.manifestPath);
|
|
16851
17524
|
const shown = !relative || relative.startsWith("..") ? result.manifestPath : relative;
|
|
16852
17525
|
log(` manifest edited: ${describeEdit(parsed.data)} in ${shown}`);
|
|
16853
17526
|
}
|
|
@@ -16875,15 +17548,20 @@ function describeEdit(edit) {
|
|
|
16875
17548
|
// src/gallery/snapshot.ts
|
|
16876
17549
|
var PROJECT_FILE_TYPE = "application/octet-stream";
|
|
16877
17550
|
function galleryMediaId(absolutePath) {
|
|
16878
|
-
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);
|
|
16879
17552
|
}
|
|
16880
17553
|
function galleryMediaRoute(id) {
|
|
16881
17554
|
return `/media/${id}`;
|
|
16882
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
|
+
}
|
|
16883
17560
|
function isDeclared(loaded, styleId, assetId) {
|
|
16884
17561
|
const asset = loaded.manifest.assets[assetId];
|
|
16885
|
-
|
|
16886
|
-
|
|
17562
|
+
const style = loaded.manifest.styles[styleId];
|
|
17563
|
+
if (!asset || !style) return false;
|
|
17564
|
+
return assetInStyle(asset, styleId, style);
|
|
16887
17565
|
}
|
|
16888
17566
|
function matchesFilter(filter, styleId, assetId) {
|
|
16889
17567
|
if (filter?.styles?.length && !filter.styles.includes(styleId)) return false;
|
|
@@ -16894,9 +17572,9 @@ function describeHistory(media, entry, cacheDir) {
|
|
|
16894
17572
|
return (entry.history ?? []).map((generation, i) => {
|
|
16895
17573
|
const outputs = generation.outputs.map((output) => {
|
|
16896
17574
|
const mediaType = output.mediaType ?? MediaType.PNG;
|
|
16897
|
-
const file =
|
|
17575
|
+
const file = import_node_path35.default.join(cacheDir, cacheFileName(output.sha256, mediaType));
|
|
16898
17576
|
let url = null;
|
|
16899
|
-
if ((0,
|
|
17577
|
+
if ((0, import_node_fs30.existsSync)(file)) {
|
|
16900
17578
|
const id = galleryMediaId(file);
|
|
16901
17579
|
media.set(id, { path: file, contentType: mediaType });
|
|
16902
17580
|
url = `${galleryMediaRoute(id)}?v=${output.sha256.slice(0, 24)}`;
|
|
@@ -16928,7 +17606,7 @@ function describeHistory(media, entry, cacheDir) {
|
|
|
16928
17606
|
var MAX_COMPARED_BYTES = 8 * 1024 * 1024;
|
|
16929
17607
|
async function samePixels(a, b) {
|
|
16930
17608
|
try {
|
|
16931
|
-
const [left, right] = await Promise.all([(0,
|
|
17609
|
+
const [left, right] = await Promise.all([(0, import_promises27.readFile)(a), (0, import_promises27.readFile)(b)]);
|
|
16932
17610
|
if (left.length > MAX_COMPARED_BYTES || right.length > MAX_COMPARED_BYTES) return false;
|
|
16933
17611
|
const x = decodePng(left);
|
|
16934
17612
|
const y = decodePng(right);
|
|
@@ -16994,7 +17672,7 @@ function describeCharacter(spec, entry) {
|
|
|
16994
17672
|
}
|
|
16995
17673
|
async function fileInfo(absolutePath) {
|
|
16996
17674
|
try {
|
|
16997
|
-
const info = await (0,
|
|
17675
|
+
const info = await (0, import_promises27.stat)(absolutePath);
|
|
16998
17676
|
return info.isFile() ? { bytes: info.size, modifiedAt: info.mtime.toISOString() } : null;
|
|
16999
17677
|
} catch {
|
|
17000
17678
|
return null;
|
|
@@ -17026,10 +17704,10 @@ async function describeOutput(media, root, absolutePath, recorded = {}) {
|
|
|
17026
17704
|
async function describeQuality(media, root, inspection) {
|
|
17027
17705
|
const outputPaths = inspection.outputs ?? [inspection.output];
|
|
17028
17706
|
const outputs = await Promise.all(
|
|
17029
|
-
outputPaths.map((file) => describeOutput(media, root,
|
|
17707
|
+
outputPaths.map((file) => describeOutput(media, root, import_node_path35.default.resolve(root, file)))
|
|
17030
17708
|
);
|
|
17031
|
-
const recordPath =
|
|
17032
|
-
const recordExists = (0,
|
|
17709
|
+
const recordPath = import_node_path35.default.resolve(root, inspection.record);
|
|
17710
|
+
const recordExists = (0, import_node_fs30.existsSync)(recordPath);
|
|
17033
17711
|
let options = null;
|
|
17034
17712
|
let check = null;
|
|
17035
17713
|
if (recordExists) {
|
|
@@ -17048,7 +17726,7 @@ async function describeQuality(media, root, inspection) {
|
|
|
17048
17726
|
return {
|
|
17049
17727
|
state: inspection.state,
|
|
17050
17728
|
reason: inspection.reason,
|
|
17051
|
-
output: portableOutputPath(
|
|
17729
|
+
output: portableOutputPath(import_node_path35.default.resolve(root, inspection.output), root),
|
|
17052
17730
|
record: portableOutputPath(recordPath, root),
|
|
17053
17731
|
recordExists,
|
|
17054
17732
|
outputs,
|
|
@@ -17086,9 +17764,9 @@ async function buildGallerySnapshot(opts) {
|
|
|
17086
17764
|
const media = /* @__PURE__ */ new Map();
|
|
17087
17765
|
const plan = await buildPlan(specs, lock);
|
|
17088
17766
|
const items = [];
|
|
17089
|
-
const manifestText = await (0,
|
|
17767
|
+
const manifestText = await (0, import_promises27.readFile)(loaded.path, "utf8");
|
|
17090
17768
|
const manifestSha256 = sha256(manifestText);
|
|
17091
|
-
const cacheDir =
|
|
17769
|
+
const cacheDir = import_node_path35.default.resolve(import_node_path35.default.dirname(opts.lockPath), ".pixelkiln", "cache");
|
|
17092
17770
|
let rawStyles = {};
|
|
17093
17771
|
try {
|
|
17094
17772
|
const raw = JSON.parse(manifestText);
|
|
@@ -17112,12 +17790,12 @@ async function buildGallerySnapshot(opts) {
|
|
|
17112
17790
|
)
|
|
17113
17791
|
);
|
|
17114
17792
|
if (spec.source) {
|
|
17115
|
-
const editPath =
|
|
17793
|
+
const editPath = import_node_path35.default.resolve(root, spec.source);
|
|
17116
17794
|
const memberCount = sourceIsStem(spec.source, entry, root) ? entry.outputs.length : 1;
|
|
17117
17795
|
const editShas = await Promise.all(
|
|
17118
17796
|
Array.from({ length: memberCount }, async (_, index) => {
|
|
17119
17797
|
const file = sourceOutputPath(spec.source, entry, index, root);
|
|
17120
|
-
return { file, sha256: (0,
|
|
17798
|
+
return { file, sha256: (0, import_node_fs30.existsSync)(file) ? await sha256File(file) : null };
|
|
17121
17799
|
})
|
|
17122
17800
|
);
|
|
17123
17801
|
edits = await Promise.all(editShas.map(({ file, sha256: editSha }) => describeOutput(media, root, file, { sha256: editSha })));
|
|
@@ -17125,7 +17803,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
17125
17803
|
const companion = await readHandEditCompanion(editPath);
|
|
17126
17804
|
if (companion) {
|
|
17127
17805
|
const projectPath = handEditProjectPath(editPath);
|
|
17128
|
-
const project = companion.project && (0,
|
|
17806
|
+
const project = companion.project && (0, import_node_fs30.existsSync)(projectPath) ? await fileInfo(projectPath) : null;
|
|
17129
17807
|
let projectUrl = null;
|
|
17130
17808
|
if (project) {
|
|
17131
17809
|
const id = galleryMediaId(projectPath);
|
|
@@ -17161,8 +17839,8 @@ async function buildGallerySnapshot(opts) {
|
|
|
17161
17839
|
editStatus = edits.some((member) => !member.exists) ? "missing" : editChanged.every((changed) => !changed) ? "same" : regenerated ? "regenerated-since" : "edited";
|
|
17162
17840
|
}
|
|
17163
17841
|
} else if (spec.source) {
|
|
17164
|
-
outputs = [await describeOutput(media, root,
|
|
17165
|
-
} 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)) {
|
|
17166
17844
|
outputs = [await describeOutput(media, root, spec.outFile)];
|
|
17167
17845
|
} else {
|
|
17168
17846
|
outputs = [];
|
|
@@ -17183,8 +17861,8 @@ async function buildGallerySnapshot(opts) {
|
|
|
17183
17861
|
generator: entry?.generator ?? spec.generator,
|
|
17184
17862
|
prompt: entry?.prompt ?? spec.prompt,
|
|
17185
17863
|
currentPrompt: entry && entry.prompt !== spec.prompt ? spec.prompt : null,
|
|
17186
|
-
width: entry?.width ?? spec.width,
|
|
17187
|
-
height: entry?.height ?? spec.height,
|
|
17864
|
+
width: renderedSize(entry)?.width ?? entry?.width ?? spec.width,
|
|
17865
|
+
height: renderedSize(entry)?.height ?? entry?.height ?? spec.height,
|
|
17188
17866
|
tileFeature: entry?.tileFeature ?? spec.tileFeature ?? null,
|
|
17189
17867
|
fps: quality?.frameSet?.fps ?? metadataFps(entry) ?? spec.quality?.fps ?? null,
|
|
17190
17868
|
cost: entry?.cost ?? 0,
|
|
@@ -17365,7 +18043,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
17365
18043
|
id: loaded.manifest.name,
|
|
17366
18044
|
name: loaded.manifest.name,
|
|
17367
18045
|
manifest: loaded.path,
|
|
17368
|
-
lock:
|
|
18046
|
+
lock: import_node_path35.default.resolve(opts.lockPath),
|
|
17369
18047
|
root,
|
|
17370
18048
|
provider: loaded.manifest.provider,
|
|
17371
18049
|
account: null,
|
|
@@ -17391,7 +18069,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
17391
18069
|
return { snapshot, media };
|
|
17392
18070
|
}
|
|
17393
18071
|
async function buildWorkspaceGallerySnapshot(opts) {
|
|
17394
|
-
const dir =
|
|
18072
|
+
const dir = import_node_path35.default.dirname(import_node_path35.default.resolve(opts.workspacePath));
|
|
17395
18073
|
const media = /* @__PURE__ */ new Map();
|
|
17396
18074
|
const projects = [];
|
|
17397
18075
|
const items = [];
|
|
@@ -17434,11 +18112,11 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
17434
18112
|
id: project.id,
|
|
17435
18113
|
name: loaded.manifest.name,
|
|
17436
18114
|
manifest: manifestPath,
|
|
17437
|
-
lock:
|
|
18115
|
+
lock: import_node_path35.default.resolve(lockPath),
|
|
17438
18116
|
root: loaded.root,
|
|
17439
18117
|
provider: loaded.manifest.provider,
|
|
17440
18118
|
account: project.account ?? null,
|
|
17441
|
-
manifestSha256: manifestSha256 ?? sha256(await (0,
|
|
18119
|
+
manifestSha256: manifestSha256 ?? sha256(await (0, import_promises27.readFile)(manifestPath, "utf8")),
|
|
17442
18120
|
historyLimit: historyLimit(loaded.manifest),
|
|
17443
18121
|
entries: Object.keys(lock.entries).length,
|
|
17444
18122
|
items: projectItems.length,
|
|
@@ -17450,8 +18128,8 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
17450
18128
|
id: project.id,
|
|
17451
18129
|
name: project.id,
|
|
17452
18130
|
manifest: manifestPath,
|
|
17453
|
-
lock:
|
|
17454
|
-
root:
|
|
18131
|
+
lock: import_node_path35.default.resolve(lockPath),
|
|
18132
|
+
root: import_node_path35.default.dirname(manifestPath),
|
|
17455
18133
|
provider: project.provider,
|
|
17456
18134
|
account: project.account ?? null,
|
|
17457
18135
|
manifestSha256: null,
|
|
@@ -17478,7 +18156,7 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
17478
18156
|
version: 1,
|
|
17479
18157
|
generatedAt: (opts.now ?? (() => /* @__PURE__ */ new Date()))().toISOString(),
|
|
17480
18158
|
project: null,
|
|
17481
|
-
workspace: { path:
|
|
18159
|
+
workspace: { path: import_node_path35.default.resolve(opts.workspacePath), dir, projects },
|
|
17482
18160
|
filter,
|
|
17483
18161
|
totals: {
|
|
17484
18162
|
entries: projects.reduce((sum, project) => sum + project.entries, 0),
|
|
@@ -17495,7 +18173,7 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
17495
18173
|
}
|
|
17496
18174
|
|
|
17497
18175
|
// src/gallery/page.ts
|
|
17498
|
-
var
|
|
18176
|
+
var import_node_fs31 = require("fs");
|
|
17499
18177
|
|
|
17500
18178
|
// src/gallery/client-bundle.ts
|
|
17501
18179
|
var import_node_module = require("module");
|
|
@@ -17535,8 +18213,8 @@ var client;
|
|
|
17535
18213
|
function clientAssets() {
|
|
17536
18214
|
const prebuilt = new URL("gallery.js", CLIENT_DIR);
|
|
17537
18215
|
client ??= {
|
|
17538
|
-
css: (0,
|
|
17539
|
-
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()
|
|
17540
18218
|
};
|
|
17541
18219
|
return client;
|
|
17542
18220
|
}
|
|
@@ -17679,10 +18357,10 @@ var EDITOR_RELEASE_BASE = "https://github.com/gfargo/pixelkiln/releases/download
|
|
|
17679
18357
|
|
|
17680
18358
|
// src/editor/install.ts
|
|
17681
18359
|
var import_node_crypto8 = require("crypto");
|
|
17682
|
-
var
|
|
17683
|
-
var
|
|
18360
|
+
var import_node_fs32 = require("fs");
|
|
18361
|
+
var import_promises28 = require("fs/promises");
|
|
17684
18362
|
var import_node_os4 = __toESM(require("os"), 1);
|
|
17685
|
-
var
|
|
18363
|
+
var import_node_path36 = __toESM(require("path"), 1);
|
|
17686
18364
|
var EditorInstallError = class extends Error {
|
|
17687
18365
|
constructor(message6, file) {
|
|
17688
18366
|
super(message6);
|
|
@@ -17693,15 +18371,15 @@ var EditorInstallError = class extends Error {
|
|
|
17693
18371
|
};
|
|
17694
18372
|
function editorToolsRoot() {
|
|
17695
18373
|
const override = process.env.PIXELKILN_TOOLS_DIR?.trim();
|
|
17696
|
-
if (override) return
|
|
17697
|
-
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");
|
|
17698
18376
|
if (process.platform === "win32") {
|
|
17699
|
-
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");
|
|
17700
18378
|
}
|
|
17701
|
-
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");
|
|
17702
18380
|
}
|
|
17703
18381
|
function editorDir(pin = EDITOR_PIN, root = editorToolsRoot()) {
|
|
17704
|
-
return
|
|
18382
|
+
return import_node_path36.default.join(root, "pixelorama", pin.release ?? "unpublished");
|
|
17705
18383
|
}
|
|
17706
18384
|
function editorBaseUrl(pin = EDITOR_PIN) {
|
|
17707
18385
|
const override = process.env.PIXELKILN_EDITOR_URL?.trim();
|
|
@@ -17711,7 +18389,7 @@ function editorBaseUrl(pin = EDITOR_PIN) {
|
|
|
17711
18389
|
}
|
|
17712
18390
|
async function sha256Of(file) {
|
|
17713
18391
|
try {
|
|
17714
|
-
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");
|
|
17715
18393
|
} catch {
|
|
17716
18394
|
return null;
|
|
17717
18395
|
}
|
|
@@ -17724,8 +18402,8 @@ async function editorStatus(opts = {}) {
|
|
|
17724
18402
|
let totalBytes = 0;
|
|
17725
18403
|
for (const [name, expected] of Object.entries(pin.files)) {
|
|
17726
18404
|
totalBytes += expected.bytes;
|
|
17727
|
-
const file =
|
|
17728
|
-
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;
|
|
17729
18407
|
if (info && info.size === expected.bytes && await sha256Of(file) === expected.sha256) {
|
|
17730
18408
|
installedBytes += expected.bytes;
|
|
17731
18409
|
} else {
|
|
@@ -17754,7 +18432,7 @@ async function installEditor(opts = {}) {
|
|
|
17754
18432
|
);
|
|
17755
18433
|
}
|
|
17756
18434
|
if (!baseUrl) throw new EditorInstallError("no download location for the editor build");
|
|
17757
|
-
await (0,
|
|
18435
|
+
await (0, import_promises28.mkdir)(dir, { recursive: true });
|
|
17758
18436
|
const downloaded = [];
|
|
17759
18437
|
const skipped = Object.keys(pin.files).filter((name) => !before.missing.includes(name));
|
|
17760
18438
|
let fetchedBytes = before.installedBytes;
|
|
@@ -17782,13 +18460,13 @@ async function installEditor(opts = {}) {
|
|
|
17782
18460
|
if (digest2 !== expected.sha256) {
|
|
17783
18461
|
throw new EditorInstallError(`${name}: sha256 ${digest2.slice(0, 12)}\u2026 does not match the pinned ${expected.sha256.slice(0, 12)}\u2026`, name);
|
|
17784
18462
|
}
|
|
17785
|
-
const final =
|
|
18463
|
+
const final = import_node_path36.default.join(dir, name);
|
|
17786
18464
|
const tmp = `${final}.${process.pid}.part`;
|
|
17787
18465
|
try {
|
|
17788
|
-
await (0,
|
|
17789
|
-
await (0,
|
|
18466
|
+
await (0, import_promises28.writeFile)(tmp, bytes);
|
|
18467
|
+
await (0, import_promises28.rename)(tmp, final);
|
|
17790
18468
|
} finally {
|
|
17791
|
-
await (0,
|
|
18469
|
+
await (0, import_promises28.rm)(tmp, { force: true });
|
|
17792
18470
|
}
|
|
17793
18471
|
fetchedBytes += bytes.length;
|
|
17794
18472
|
downloaded.push(name);
|
|
@@ -17828,12 +18506,12 @@ var EDITOR_CONTENT_TYPES = {
|
|
|
17828
18506
|
".json": "application/json; charset=utf-8"
|
|
17829
18507
|
};
|
|
17830
18508
|
function editorContentType(name) {
|
|
17831
|
-
return EDITOR_CONTENT_TYPES[
|
|
18509
|
+
return EDITOR_CONTENT_TYPES[import_node_path36.default.extname(name).toLowerCase()] ?? null;
|
|
17832
18510
|
}
|
|
17833
18511
|
|
|
17834
18512
|
// src/gallery/editor.ts
|
|
17835
|
-
var
|
|
17836
|
-
var
|
|
18513
|
+
var import_promises29 = require("fs/promises");
|
|
18514
|
+
var import_node_path37 = __toESM(require("path"), 1);
|
|
17837
18515
|
function editorRoute(release, name = "index.html") {
|
|
17838
18516
|
return `/editor/${encodeURIComponent(release)}/${encodeURIComponent(name)}`;
|
|
17839
18517
|
}
|
|
@@ -17906,7 +18584,7 @@ function createGalleryEditorHandlers(opts = {}) {
|
|
|
17906
18584
|
if (!status.installed) return null;
|
|
17907
18585
|
let bytes;
|
|
17908
18586
|
try {
|
|
17909
|
-
bytes = await (0,
|
|
18587
|
+
bytes = await (0, import_promises29.readFile)(import_node_path37.default.join(dir, name));
|
|
17910
18588
|
} catch {
|
|
17911
18589
|
verified = null;
|
|
17912
18590
|
return null;
|
|
@@ -17923,7 +18601,7 @@ function createGalleryEditorHandlers(opts = {}) {
|
|
|
17923
18601
|
// src/gallery/server.ts
|
|
17924
18602
|
var import_node_crypto9 = require("crypto");
|
|
17925
18603
|
var import_node_http2 = require("http");
|
|
17926
|
-
var
|
|
18604
|
+
var import_promises30 = require("fs/promises");
|
|
17927
18605
|
var MAX_EDIT_BYTES = 48 * 1024 * 1024;
|
|
17928
18606
|
var EDITOR_CSP = [
|
|
17929
18607
|
"default-src 'self'",
|
|
@@ -18104,7 +18782,7 @@ async function serveGallery(opts) {
|
|
|
18104
18782
|
const asset = reviewAssets.get(jobId)?.get(url2.pathname);
|
|
18105
18783
|
if (!asset) return fail(404, "no such review asset");
|
|
18106
18784
|
try {
|
|
18107
|
-
const bytes = await (0,
|
|
18785
|
+
const bytes = await (0, import_promises30.readFile)(asset.path);
|
|
18108
18786
|
res.writeHead(200, {
|
|
18109
18787
|
"Content-Type": asset.contentType,
|
|
18110
18788
|
"Cache-Control": "no-store",
|
|
@@ -18187,7 +18865,7 @@ async function serveGallery(opts) {
|
|
|
18187
18865
|
if (!asset) return fail(404, "no such gallery media");
|
|
18188
18866
|
let bytes;
|
|
18189
18867
|
try {
|
|
18190
|
-
bytes = await (0,
|
|
18868
|
+
bytes = await (0, import_promises30.readFile)(asset.path);
|
|
18191
18869
|
} catch {
|
|
18192
18870
|
return fail(404, "gallery media is no longer on disk; refresh the page");
|
|
18193
18871
|
}
|
|
@@ -18597,19 +19275,19 @@ function createGenerateHandlers(opts) {
|
|
|
18597
19275
|
}
|
|
18598
19276
|
|
|
18599
19277
|
// src/pipeline/salvage.ts
|
|
18600
|
-
var
|
|
18601
|
-
var
|
|
18602
|
-
var
|
|
19278
|
+
var import_promises31 = require("fs/promises");
|
|
19279
|
+
var import_node_fs33 = require("fs");
|
|
19280
|
+
var import_node_path38 = __toESM(require("path"), 1);
|
|
18603
19281
|
function providerClaimId(provider, objectId) {
|
|
18604
19282
|
return `${encodeURIComponent(provider)}:${encodeURIComponent(objectId)}`;
|
|
18605
19283
|
}
|
|
18606
19284
|
async function loadClaims(lockPaths, opts = {}) {
|
|
18607
19285
|
const claimed = /* @__PURE__ */ new Set();
|
|
18608
19286
|
for (const p of lockPaths) {
|
|
18609
|
-
if (!(0,
|
|
19287
|
+
if (!(0, import_node_fs33.existsSync)(p)) throw new Error(`Claim lockfile not found: ${p}`);
|
|
18610
19288
|
let parsed;
|
|
18611
19289
|
try {
|
|
18612
|
-
parsed = parseLock(JSON.parse(await (0,
|
|
19290
|
+
parsed = parseLock(JSON.parse(await (0, import_promises31.readFile)(p, "utf8")));
|
|
18613
19291
|
} catch {
|
|
18614
19292
|
throw new Error(`Claim lockfile is malformed: ${p}`);
|
|
18615
19293
|
}
|
|
@@ -18713,20 +19391,20 @@ function matchOrphanStyle(prompt, manifest) {
|
|
|
18713
19391
|
return matchStyleByPattern(prompt, manifest);
|
|
18714
19392
|
}
|
|
18715
19393
|
async function loadSiblingManifests(ownManifestPath, workspaceManifestPaths, claimPaths) {
|
|
18716
|
-
const own =
|
|
19394
|
+
const own = import_node_path38.default.resolve(ownManifestPath);
|
|
18717
19395
|
const siblingManifestPaths = [
|
|
18718
19396
|
.../* @__PURE__ */ new Set([
|
|
18719
19397
|
...workspaceManifestPaths,
|
|
18720
|
-
...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"))
|
|
18721
19399
|
])
|
|
18722
19400
|
];
|
|
18723
19401
|
const siblings = [];
|
|
18724
19402
|
for (const siblingManifestPath of siblingManifestPaths) {
|
|
18725
|
-
if (
|
|
18726
|
-
if (!(0,
|
|
19403
|
+
if (import_node_path38.default.resolve(siblingManifestPath) === own) continue;
|
|
19404
|
+
if (!(0, import_node_fs33.existsSync)(siblingManifestPath)) continue;
|
|
18727
19405
|
try {
|
|
18728
19406
|
const { manifest } = await loadManifest(siblingManifestPath);
|
|
18729
|
-
siblings.push({ label:
|
|
19407
|
+
siblings.push({ label: import_node_path38.default.basename(import_node_path38.default.dirname(siblingManifestPath)), manifest });
|
|
18730
19408
|
} catch {
|
|
18731
19409
|
}
|
|
18732
19410
|
}
|
|
@@ -18846,8 +19524,8 @@ async function applyTags(provider, decisions, existing, opts = {}) {
|
|
|
18846
19524
|
}
|
|
18847
19525
|
|
|
18848
19526
|
// src/pick/salvage-server.ts
|
|
18849
|
-
var
|
|
18850
|
-
var
|
|
19527
|
+
var import_promises32 = require("fs/promises");
|
|
19528
|
+
var import_node_path39 = __toESM(require("path"), 1);
|
|
18851
19529
|
|
|
18852
19530
|
// src/pick/salvage-sheet.ts
|
|
18853
19531
|
var escapeHtml3 = (s) => s.replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
@@ -19013,7 +19691,7 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
19013
19691
|
});
|
|
19014
19692
|
const html = renderSalvageSheet(orphans, {
|
|
19015
19693
|
styleId: ctx.styleId,
|
|
19016
|
-
importDir:
|
|
19694
|
+
importDir: import_node_path39.default.relative(process.cwd(), ctx.importDir) || "."
|
|
19017
19695
|
});
|
|
19018
19696
|
const byId = new Map(orphans.map((o) => [o.id, o]));
|
|
19019
19697
|
const existingTags = new Map(orphans.map((o) => [o.id, o.tags]));
|
|
@@ -19069,10 +19747,10 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
19069
19747
|
if (!buf.subarray(0, 8).equals(PNG_SIGNATURE2)) throw new Error("not a PNG");
|
|
19070
19748
|
decodePng(buf);
|
|
19071
19749
|
const assetId = idFromPrompt(orphan.prompt, taken);
|
|
19072
|
-
const rel =
|
|
19073
|
-
const outFile =
|
|
19074
|
-
await (0,
|
|
19075
|
-
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);
|
|
19076
19754
|
ctx.manifest.assets[assetId] = {
|
|
19077
19755
|
prompt: orphan.prompt,
|
|
19078
19756
|
promptByStyle: {},
|
|
@@ -19104,7 +19782,7 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
19104
19782
|
sourceUrl: durableSource,
|
|
19105
19783
|
sourceUrls: durableSource ? [{ url: durableSource }] : [],
|
|
19106
19784
|
outputs: [{
|
|
19107
|
-
path: portableOutputPath(outFile,
|
|
19785
|
+
path: portableOutputPath(outFile, import_node_path39.default.dirname(ctx.manifestPath)),
|
|
19108
19786
|
sha256: sha256(buf)
|
|
19109
19787
|
}],
|
|
19110
19788
|
submittedAt: orphan.createdAt,
|
|
@@ -19128,9 +19806,9 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
19128
19806
|
}
|
|
19129
19807
|
}
|
|
19130
19808
|
await applyTags(provider, decisions, existingTags, { onProgress: log, characters: characterMembers });
|
|
19131
|
-
const raw = JSON.parse(await (0,
|
|
19809
|
+
const raw = JSON.parse(await (0, import_promises32.readFile)(ctx.manifestPath, "utf8"));
|
|
19132
19810
|
for (const id of importedAssetIds) raw.assets[id] = ctx.manifest.assets[id];
|
|
19133
|
-
await (0,
|
|
19811
|
+
await (0, import_promises32.writeFile)(ctx.manifestPath, JSON.stringify(raw, null, 2) + "\n");
|
|
19134
19812
|
await saveLock(ctx.lockPath, ctx.lock);
|
|
19135
19813
|
if (characterAssetIds.length) result.characterAssetIds = characterAssetIds;
|
|
19136
19814
|
return result;
|
|
@@ -19241,6 +19919,7 @@ async function workspaceStatus(ws, dir) {
|
|
|
19241
19919
|
0 && (module.exports = {
|
|
19242
19920
|
AssetBatchSchema,
|
|
19243
19921
|
AssetSchema,
|
|
19922
|
+
AssetVfxSchema,
|
|
19244
19923
|
BudgetError,
|
|
19245
19924
|
CHARACTER_DIRECTIONS_4,
|
|
19246
19925
|
CHARACTER_DIRECTIONS_8,
|
|
@@ -19295,11 +19974,16 @@ async function workspaceStatus(ws, dir) {
|
|
|
19295
19974
|
MapSceneSchema,
|
|
19296
19975
|
MediaType,
|
|
19297
19976
|
OverwriteRefusedError,
|
|
19977
|
+
PIXELATE_COST_GENERATIONS,
|
|
19978
|
+
PIXELATE_MAX_SIDE,
|
|
19979
|
+
PIXELLAB_MCP_URL,
|
|
19298
19980
|
PIXEL_ART_FIXER_REVISION,
|
|
19299
19981
|
PIXEL_ART_FIXER_URL,
|
|
19982
|
+
PRO_TIER_PRICES,
|
|
19300
19983
|
PixelKilnError,
|
|
19301
19984
|
PixelLabClient,
|
|
19302
19985
|
PixelLabError,
|
|
19986
|
+
PixelLabMcpClient,
|
|
19303
19987
|
PixelLabProvider,
|
|
19304
19988
|
PostprocessSchema,
|
|
19305
19989
|
ProjectError,
|
|
@@ -19322,12 +20006,15 @@ async function workspaceStatus(ws, dir) {
|
|
|
19322
20006
|
UiPieceSchema,
|
|
19323
20007
|
UnsupportedCapabilityError,
|
|
19324
20008
|
UsageError,
|
|
20009
|
+
VFX_TOOL,
|
|
20010
|
+
VfxPartSchema,
|
|
19325
20011
|
WorkspaceProjectSchema,
|
|
19326
20012
|
WorkspaceSchema,
|
|
19327
20013
|
adopt,
|
|
19328
20014
|
applyManifestEdit,
|
|
19329
20015
|
applyTags,
|
|
19330
20016
|
approveQualityRecord,
|
|
20017
|
+
assetInStyle,
|
|
19331
20018
|
auditStyle,
|
|
19332
20019
|
availableProviders,
|
|
19333
20020
|
backoffMs,
|
|
@@ -19352,7 +20039,9 @@ async function workspaceStatus(ws, dir) {
|
|
|
19352
20039
|
currentEntryOutputPath,
|
|
19353
20040
|
currentOutputPath,
|
|
19354
20041
|
defaultOpenCommand,
|
|
20042
|
+
defaultPixelateOut,
|
|
19355
20043
|
defaultUnzoomOut,
|
|
20044
|
+
describeVfx,
|
|
19356
20045
|
detachHandEdit,
|
|
19357
20046
|
detectMediaType,
|
|
19358
20047
|
doctor,
|
|
@@ -19373,6 +20062,7 @@ async function workspaceStatus(ws, dir) {
|
|
|
19373
20062
|
fontOutputPaths,
|
|
19374
20063
|
formatCost,
|
|
19375
20064
|
frameSetFps,
|
|
20065
|
+
frameSetLoop,
|
|
19376
20066
|
galleryContentSecurityPolicy,
|
|
19377
20067
|
galleryMediaId,
|
|
19378
20068
|
galleryMediaRoute,
|
|
@@ -19395,9 +20085,11 @@ async function workspaceStatus(ws, dir) {
|
|
|
19395
20085
|
inspectRevisionReadiness,
|
|
19396
20086
|
installEditor,
|
|
19397
20087
|
installRecipe,
|
|
20088
|
+
isBusyReply,
|
|
19398
20089
|
isFrameSetEntry,
|
|
19399
20090
|
isMemberSetEntry,
|
|
19400
20091
|
isSensitiveSourceUrl,
|
|
20092
|
+
isVfxReference,
|
|
19401
20093
|
isometricTileCost,
|
|
19402
20094
|
listBundledRecipes,
|
|
19403
20095
|
loadClaims,
|
|
@@ -19406,7 +20098,9 @@ async function workspaceStatus(ws, dir) {
|
|
|
19406
20098
|
loadSiblingManifests,
|
|
19407
20099
|
loadWorkspace,
|
|
19408
20100
|
lockKey,
|
|
20101
|
+
makeVfx,
|
|
19409
20102
|
matchOrphanStyle,
|
|
20103
|
+
mcpClientFromEnv,
|
|
19410
20104
|
measureBalanceChange,
|
|
19411
20105
|
measureDecodedImageQuality,
|
|
19412
20106
|
measureImageQuality,
|
|
@@ -19430,15 +20124,21 @@ async function workspaceStatus(ws, dir) {
|
|
|
19430
20124
|
paletteDistance,
|
|
19431
20125
|
parentAssetId,
|
|
19432
20126
|
parseLock,
|
|
20127
|
+
parseMcpBody,
|
|
19433
20128
|
parseProFlashQuote,
|
|
19434
20129
|
parseTerrainDescriptions,
|
|
20130
|
+
parseVfxExport,
|
|
20131
|
+
parseVfxRecipe,
|
|
20132
|
+
parseVfxStep,
|
|
19435
20133
|
parseWorkspace,
|
|
19436
20134
|
pickHistory,
|
|
20135
|
+
pixelateFile,
|
|
19437
20136
|
pngSize,
|
|
19438
20137
|
poll,
|
|
19439
20138
|
portableOutputPath,
|
|
19440
20139
|
prepareReview,
|
|
19441
20140
|
primaryOutput,
|
|
20141
|
+
proTierCost,
|
|
19442
20142
|
proportionsPayload,
|
|
19443
20143
|
providerCredentialEnvs,
|
|
19444
20144
|
providerFactory,
|
|
@@ -19449,6 +20149,8 @@ async function workspaceStatus(ws, dir) {
|
|
|
19449
20149
|
readArtifactBundleManifest,
|
|
19450
20150
|
readHandEditCompanion,
|
|
19451
20151
|
readQualityBaseline,
|
|
20152
|
+
readSavedVfxRecipe,
|
|
20153
|
+
readVfxRecipe,
|
|
19452
20154
|
recipeDigest,
|
|
19453
20155
|
referencedHashes,
|
|
19454
20156
|
refineAsset,
|
|
@@ -19479,6 +20181,7 @@ async function workspaceStatus(ws, dir) {
|
|
|
19479
20181
|
resolveSpecs,
|
|
19480
20182
|
resolveStyleImages,
|
|
19481
20183
|
resolveStyleOutputs,
|
|
20184
|
+
resolveVfxSprite,
|
|
19482
20185
|
resumeActions,
|
|
19483
20186
|
resumeCommandForStatus,
|
|
19484
20187
|
retireGeneration,
|
|
@@ -19486,6 +20189,7 @@ async function workspaceStatus(ws, dir) {
|
|
|
19486
20189
|
revertGeneration,
|
|
19487
20190
|
runPicker,
|
|
19488
20191
|
runSalvage,
|
|
20192
|
+
runVfxWorkbench,
|
|
19489
20193
|
saveHandEdit,
|
|
19490
20194
|
saveLock,
|
|
19491
20195
|
saveWorkspace,
|
|
@@ -19496,6 +20200,7 @@ async function workspaceStatus(ws, dir) {
|
|
|
19496
20200
|
sha256File,
|
|
19497
20201
|
shouldPersistSourceUrl,
|
|
19498
20202
|
shouldRetry,
|
|
20203
|
+
sliceVfxSheet,
|
|
19499
20204
|
slugify,
|
|
19500
20205
|
snapshotQualityBaseline,
|
|
19501
20206
|
sourceIsStem,
|
|
@@ -19520,6 +20225,8 @@ async function workspaceStatus(ws, dir) {
|
|
|
19520
20225
|
validateWorkspace,
|
|
19521
20226
|
verifyArtifactBundle,
|
|
19522
20227
|
verifyRecipe,
|
|
20228
|
+
vfxOutputPaths,
|
|
20229
|
+
vfxRecipeSha256,
|
|
19523
20230
|
withArtifactManifest,
|
|
19524
20231
|
workspaceClaims,
|
|
19525
20232
|
workspaceStatus,
|