pixelkiln 0.84.0 → 0.86.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/PROVIDERS.md +3 -3
- package/dist/cli.js +528 -353
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +615 -440
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +107 -4
- package/dist/index.d.ts +107 -4
- package/dist/index.js +510 -335
- package/dist/index.js.map +1 -1
- package/docs/MANIFEST.md +2 -1
- package/docs/REVISIONS.md +43 -11
- package/docs/SCENARIO.md +203 -7
- package/package.json +1 -1
- package/schema/manifest.schema.json +4 -0
- package/skills/pixelkiln/references/revisions.md +4 -2
- package/skills/pixelkiln/references/scenario.md +8 -4
package/dist/index.js
CHANGED
|
@@ -313,8 +313,16 @@ var RevisionDitheringSchema = z.enum(["none", "2x2", "4x4", "8x8"]);
|
|
|
313
313
|
var RevisionSchema = z.object({
|
|
314
314
|
/** What kind of controlled change the provider workflow performs. */
|
|
315
315
|
mode: RevisionModeSchema,
|
|
316
|
-
/** Parent asset id in
|
|
316
|
+
/** Parent asset id. It lives in this asset's own style unless `fromStyle` names another. */
|
|
317
317
|
from: z.string().min(1),
|
|
318
|
+
/**
|
|
319
|
+
* The style the parent lives in, when it is not this asset's own. The
|
|
320
|
+
* parent is generated by that style's provider and model, and this
|
|
321
|
+
* revision starts from its output file, so a chain (generate with one
|
|
322
|
+
* model, clean up with another) is declared instead of restated by hand.
|
|
323
|
+
* Styles may depend on one another but not in a cycle.
|
|
324
|
+
*/
|
|
325
|
+
fromStyle: z.string().min(1).optional(),
|
|
318
326
|
/** Manifest-relative black/white PNG. Required only for masked inpainting. */
|
|
319
327
|
mask: z.string().min(1).optional(),
|
|
320
328
|
/** Provider-neutral edit strength. The active adapter must bind it explicitly. */
|
|
@@ -1175,6 +1183,7 @@ var LockEntrySchema = z.object({
|
|
|
1175
1183
|
revision: z.object({
|
|
1176
1184
|
mode: RevisionModeSchema,
|
|
1177
1185
|
sourceAssetId: z.string().min(1),
|
|
1186
|
+
sourceStyleId: z.string().min(1).optional(),
|
|
1178
1187
|
sourceSha256: z.string().regex(/^[0-9a-f]{64}$/),
|
|
1179
1188
|
maskSha256: z.string().regex(/^[0-9a-f]{64}$/).optional(),
|
|
1180
1189
|
strength: z.number().min(0).max(1).optional(),
|
|
@@ -1816,9 +1825,9 @@ var PixelLabClient = class {
|
|
|
1816
1825
|
apiKey;
|
|
1817
1826
|
http;
|
|
1818
1827
|
/** Retries transport failures, 429, and 5xx; see http.ts for the policy. */
|
|
1819
|
-
async request(
|
|
1828
|
+
async request(path38, init) {
|
|
1820
1829
|
const auth = this.apiKey.startsWith("Bearer ") ? this.apiKey : `Bearer ${this.apiKey}`;
|
|
1821
|
-
const res = await this.http(`${BASE}${
|
|
1830
|
+
const res = await this.http(`${BASE}${path38}`, {
|
|
1822
1831
|
...init,
|
|
1823
1832
|
headers: {
|
|
1824
1833
|
Authorization: auth,
|
|
@@ -1828,13 +1837,13 @@ var PixelLabClient = class {
|
|
|
1828
1837
|
});
|
|
1829
1838
|
const text = await res.text();
|
|
1830
1839
|
if (!res.ok) {
|
|
1831
|
-
throw new PixelLabError(`${init?.method ?? "GET"} ${
|
|
1840
|
+
throw new PixelLabError(`${init?.method ?? "GET"} ${path38} \u2192 ${res.status}`, res.status, text);
|
|
1832
1841
|
}
|
|
1833
1842
|
if (!text) return {};
|
|
1834
1843
|
try {
|
|
1835
1844
|
return JSON.parse(text);
|
|
1836
1845
|
} catch {
|
|
1837
|
-
throw new Error(`${init?.method ?? "GET"} ${
|
|
1846
|
+
throw new Error(`${init?.method ?? "GET"} ${path38} returned invalid JSON`);
|
|
1838
1847
|
}
|
|
1839
1848
|
}
|
|
1840
1849
|
async balance() {
|
|
@@ -2239,10 +2248,10 @@ var PixelLabClient = class {
|
|
|
2239
2248
|
const palette = args.paletteSwatchBase64 ? { color_image: { type: "base64", base64: args.paletteSwatchBase64, format: "png" }, force_colors: true } : {};
|
|
2240
2249
|
const encode = (image) => ({ type: "base64", base64: image.base64, format: image.format });
|
|
2241
2250
|
const south = args.reference?.south;
|
|
2242
|
-
let
|
|
2251
|
+
let path38;
|
|
2243
2252
|
let body;
|
|
2244
2253
|
if (args.mode === "v3") {
|
|
2245
|
-
|
|
2254
|
+
path38 = "/create-character-v3";
|
|
2246
2255
|
body = {
|
|
2247
2256
|
description: args.description,
|
|
2248
2257
|
image_size: imageSize,
|
|
@@ -2256,7 +2265,7 @@ var PixelLabClient = class {
|
|
|
2256
2265
|
...args.enhancePrompt ? { enhance_prompt: true } : {}
|
|
2257
2266
|
};
|
|
2258
2267
|
} else if (args.mode === "pro-flash") {
|
|
2259
|
-
|
|
2268
|
+
path38 = "/create-character-pro-flash";
|
|
2260
2269
|
const traits = args.styleTraits;
|
|
2261
2270
|
body = {
|
|
2262
2271
|
description: args.description,
|
|
@@ -2280,7 +2289,7 @@ var PixelLabClient = class {
|
|
|
2280
2289
|
} : {}
|
|
2281
2290
|
};
|
|
2282
2291
|
} else if (args.mode === "pro") {
|
|
2283
|
-
|
|
2292
|
+
path38 = "/create-character-pro";
|
|
2284
2293
|
body = {
|
|
2285
2294
|
description: args.description,
|
|
2286
2295
|
image_size: imageSize,
|
|
@@ -2294,7 +2303,7 @@ var PixelLabClient = class {
|
|
|
2294
2303
|
...!south && args.styleCharacterId ? { style_character_id: args.styleCharacterId } : {}
|
|
2295
2304
|
};
|
|
2296
2305
|
} else {
|
|
2297
|
-
|
|
2306
|
+
path38 = args.directions === 4 ? "/create-character-with-4-directions" : "/create-character-with-8-directions";
|
|
2298
2307
|
body = {
|
|
2299
2308
|
description: args.description,
|
|
2300
2309
|
image_size: imageSize,
|
|
@@ -2311,8 +2320,8 @@ var PixelLabClient = class {
|
|
|
2311
2320
|
...palette
|
|
2312
2321
|
};
|
|
2313
2322
|
}
|
|
2314
|
-
const raw = await this.request(
|
|
2315
|
-
return validateResponse(CharacterSubmitSchema, raw,
|
|
2323
|
+
const raw = await this.request(path38, { method: "POST", body: JSON.stringify(body) });
|
|
2324
|
+
return validateResponse(CharacterSubmitSchema, raw, path38.slice(1));
|
|
2316
2325
|
}
|
|
2317
2326
|
/** A text edit of an existing character, applied to every direction and stored as a sibling. */
|
|
2318
2327
|
async createCharacterState(args) {
|
|
@@ -3525,10 +3534,10 @@ import { createReadStream } from "fs";
|
|
|
3525
3534
|
function sha256(data) {
|
|
3526
3535
|
return createHash("sha256").update(data).digest("hex");
|
|
3527
3536
|
}
|
|
3528
|
-
async function sha256File(
|
|
3537
|
+
async function sha256File(path38) {
|
|
3529
3538
|
return new Promise((resolve, reject) => {
|
|
3530
3539
|
const hash = createHash("sha256");
|
|
3531
|
-
const stream = createReadStream(
|
|
3540
|
+
const stream = createReadStream(path38);
|
|
3532
3541
|
stream.on("data", (chunk2) => hash.update(chunk2));
|
|
3533
3542
|
stream.on("error", reject);
|
|
3534
3543
|
stream.on("end", () => resolve(hash.digest("hex")));
|
|
@@ -3637,6 +3646,7 @@ function specHash(spec, styleImageHashes, providerOptionIdentity = spec.provider
|
|
|
3637
3646
|
revision: spec.revision ? {
|
|
3638
3647
|
mode: spec.revision.mode,
|
|
3639
3648
|
from: spec.revision.sourceAssetId,
|
|
3649
|
+
fromStyle: spec.revision.sourceStyleId,
|
|
3640
3650
|
sourceSha256: spec.revision.sourceSha256,
|
|
3641
3651
|
maskSha256: spec.revision.maskSha256,
|
|
3642
3652
|
strength: spec.revision.strength,
|
|
@@ -5932,9 +5942,9 @@ var RetroDiffusionClient = class {
|
|
|
5932
5942
|
if (!response.ok) throw new ProviderError("retrodiffusion", `Retro Diffusion download failed (${response.status})`, { status: response.status });
|
|
5933
5943
|
return Buffer.from(await response.arrayBuffer());
|
|
5934
5944
|
}
|
|
5935
|
-
async call(
|
|
5945
|
+
async call(path38, init = {}) {
|
|
5936
5946
|
if (!this.token) throw new Error("RD_API_KEY is not set");
|
|
5937
|
-
const response = await this.request(`${this.baseUrl}${
|
|
5947
|
+
const response = await this.request(`${this.baseUrl}${path38}`, {
|
|
5938
5948
|
...init,
|
|
5939
5949
|
headers: {
|
|
5940
5950
|
"Content-Type": "application/json",
|
|
@@ -6243,8 +6253,13 @@ function retroError(value) {
|
|
|
6243
6253
|
}
|
|
6244
6254
|
|
|
6245
6255
|
// src/providers/scenario.ts
|
|
6256
|
+
import { createHash as createHash2 } from "crypto";
|
|
6257
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, renameSync, writeFileSync as writeFileSync2 } from "fs";
|
|
6258
|
+
import os2 from "os";
|
|
6259
|
+
import path2 from "path";
|
|
6246
6260
|
var DEFAULT_BASE_URL2 = "https://api.cloud.scenario.com/v1";
|
|
6247
6261
|
var SOURCE_PROTOCOL2 = "scenario:";
|
|
6262
|
+
var SUBMIT_RETRIES = 8;
|
|
6248
6263
|
var PROTECTED_PARAMETERS = /* @__PURE__ */ new Set([
|
|
6249
6264
|
"dryRun",
|
|
6250
6265
|
"height",
|
|
@@ -6291,7 +6306,11 @@ var ScenarioClient = class {
|
|
|
6291
6306
|
async submit(modelId, body, projectId) {
|
|
6292
6307
|
const response = await this.call(
|
|
6293
6308
|
`/generate/custom/${encodeURIComponent(modelId)}`,
|
|
6294
|
-
|
|
6309
|
+
// A 429 here means this account's concurrent-job limit refused the
|
|
6310
|
+
// request, so nothing was accepted or billed; wait it out instead of
|
|
6311
|
+
// failing the asset. The default four retries give up after about half
|
|
6312
|
+
// a minute, before a running job has finished.
|
|
6313
|
+
{ method: "POST", body: JSON.stringify(body), retries: SUBMIT_RETRIES },
|
|
6295
6314
|
{ projectId }
|
|
6296
6315
|
);
|
|
6297
6316
|
const job = response?.job;
|
|
@@ -6299,6 +6318,29 @@ var ScenarioClient = class {
|
|
|
6299
6318
|
if (!jobId) throw new Error("Scenario did not return an async job id");
|
|
6300
6319
|
return jobId;
|
|
6301
6320
|
}
|
|
6321
|
+
/**
|
|
6322
|
+
* Uploads one image (POST /assets) and returns its asset id, which a model's
|
|
6323
|
+
* reference input takes. Free, and idempotent per image: the id is kept by
|
|
6324
|
+
* content hash so a rerun does not upload the same bytes again.
|
|
6325
|
+
*/
|
|
6326
|
+
async uploadImage(image, projectId) {
|
|
6327
|
+
const bytes = Buffer.from(image.base64, "base64");
|
|
6328
|
+
const digest2 = sha256(bytes);
|
|
6329
|
+
const cached = readUploadCache()[uploadCacheKey(this.baseUrl, projectId, digest2)];
|
|
6330
|
+
if (cached) return cached;
|
|
6331
|
+
const response = await this.call(
|
|
6332
|
+
"/assets",
|
|
6333
|
+
{
|
|
6334
|
+
method: "POST",
|
|
6335
|
+
body: JSON.stringify({ image: image.base64, name: `pixelkiln-${digest2.slice(0, 12)}.${image.format === "png" ? "png" : "jpg"}` })
|
|
6336
|
+
},
|
|
6337
|
+
{ projectId }
|
|
6338
|
+
);
|
|
6339
|
+
const id = response?.asset?.id;
|
|
6340
|
+
if (typeof id !== "string" || !id) throw new Error("Scenario did not return an uploaded asset id");
|
|
6341
|
+
writeUploadCache(uploadCacheKey(this.baseUrl, projectId, digest2), id);
|
|
6342
|
+
return id;
|
|
6343
|
+
}
|
|
6302
6344
|
async job(id, projectId) {
|
|
6303
6345
|
const response = await this.call(`/jobs/${encodeURIComponent(id)}`, {}, { projectId });
|
|
6304
6346
|
const raw = response?.job;
|
|
@@ -6425,6 +6467,14 @@ var ScenarioProvider = class _ScenarioProvider {
|
|
|
6425
6467
|
supports(generator) {
|
|
6426
6468
|
return generator === "map";
|
|
6427
6469
|
}
|
|
6470
|
+
/**
|
|
6471
|
+
* An edit is a generation with the parent as a reference image, which the
|
|
6472
|
+
* editing models (GPT Image, Gemini, FLUX) take. Masked inpainting and the
|
|
6473
|
+
* PixelLab-specific modes have no common Scenario input.
|
|
6474
|
+
*/
|
|
6475
|
+
supportsRevision(mode2) {
|
|
6476
|
+
return mode2 === "image-to-image";
|
|
6477
|
+
}
|
|
6428
6478
|
estimate(spec) {
|
|
6429
6479
|
const options = scenarioOptions(spec);
|
|
6430
6480
|
return {
|
|
@@ -6445,11 +6495,31 @@ var ScenarioProvider = class _ScenarioProvider {
|
|
|
6445
6495
|
if (!Number.isInteger(count) || count < 1 || count > 4) {
|
|
6446
6496
|
throw new Error("Scenario numOutputs must be a whole number from 1 to 4");
|
|
6447
6497
|
}
|
|
6448
|
-
if (spec.width
|
|
6449
|
-
throw new Error("Scenario width and height must be
|
|
6498
|
+
if (!Number.isInteger(spec.width) || spec.width < 16 || spec.width > 4096 || !Number.isInteger(spec.height) || spec.height < 16 || spec.height > 4096) {
|
|
6499
|
+
throw new Error("Scenario width and height must be whole numbers from 16 to 4096");
|
|
6450
6500
|
}
|
|
6451
|
-
|
|
6452
|
-
|
|
6501
|
+
const revision = spec.revision;
|
|
6502
|
+
if (revision) {
|
|
6503
|
+
if (revision.sourceMembers?.length) {
|
|
6504
|
+
throw new Error(
|
|
6505
|
+
`Scenario edits one image; ${revision.sourceAssetId} is a ${revision.sourceMembers.length}-member set`
|
|
6506
|
+
);
|
|
6507
|
+
}
|
|
6508
|
+
if (revision.strength != null) {
|
|
6509
|
+
throw new Error("Scenario image-to-image takes no strength; use the model's own parameters");
|
|
6510
|
+
}
|
|
6511
|
+
}
|
|
6512
|
+
const parameter = referenceParameter(options);
|
|
6513
|
+
if ((styleImages.length || revision) && Object.hasOwn(options.parameters ?? {}, parameter)) {
|
|
6514
|
+
throw new Error(
|
|
6515
|
+
`Scenario parameters.${parameter} is the reference-image input and is filled from styleImages and revisions`
|
|
6516
|
+
);
|
|
6517
|
+
}
|
|
6518
|
+
if (styleImages.length + (revision ? 1 : 0) > 1 && options.referenceArray === false) {
|
|
6519
|
+
throw new Error(`Scenario ${parameter} takes one image; use one style image or a revision, not both`);
|
|
6520
|
+
}
|
|
6521
|
+
if (options.referenceParameter != null && (typeof options.referenceParameter !== "string" || !options.referenceParameter.trim())) {
|
|
6522
|
+
throw new Error("Scenario referenceParameter must be a non-empty string");
|
|
6453
6523
|
}
|
|
6454
6524
|
if (options.projectId != null && (typeof options.projectId !== "string" || !options.projectId.trim())) {
|
|
6455
6525
|
throw new Error("Scenario projectId must be a non-empty string");
|
|
@@ -6462,7 +6532,12 @@ var ScenarioProvider = class _ScenarioProvider {
|
|
|
6462
6532
|
async submit(spec, styleImages) {
|
|
6463
6533
|
this.validate(spec, styleImages);
|
|
6464
6534
|
const options = scenarioOptions(spec);
|
|
6465
|
-
const
|
|
6535
|
+
const references = [];
|
|
6536
|
+
if (spec.revision) references.push(readRevisionSource(spec));
|
|
6537
|
+
for (const image of styleImages) references.push({ base64: image.base64, format: image.format });
|
|
6538
|
+
const referenceAssetIds = [];
|
|
6539
|
+
for (const image of references) referenceAssetIds.push(await this.client.uploadImage(image, options.projectId));
|
|
6540
|
+
const body = requestBody(spec, options, referenceAssetIds);
|
|
6466
6541
|
const quote = await this.client.quote(options.modelId, body, options.projectId);
|
|
6467
6542
|
if (quote.total > options.maxComputeUnits + Number.EPSILON) {
|
|
6468
6543
|
throw new Error(
|
|
@@ -6479,7 +6554,8 @@ var ScenarioProvider = class _ScenarioProvider {
|
|
|
6479
6554
|
quotedComputeUnits: quote.total,
|
|
6480
6555
|
creativeUnitsCost: quote.creativeUnitsCost,
|
|
6481
6556
|
costDetails: quote.costDetails,
|
|
6482
|
-
ipDetectionCost: quote.ipDetectionCost
|
|
6557
|
+
ipDetectionCost: quote.ipDetectionCost,
|
|
6558
|
+
...referenceAssetIds.length ? { referenceAssetIds } : {}
|
|
6483
6559
|
}
|
|
6484
6560
|
};
|
|
6485
6561
|
}
|
|
@@ -6501,7 +6577,13 @@ var ScenarioProvider = class _ScenarioProvider {
|
|
|
6501
6577
|
}
|
|
6502
6578
|
const assetIds = scenarioAssetIds(job);
|
|
6503
6579
|
if (!assetIds.length) return { status: "failed", error: "Scenario job returned no asset IDs" };
|
|
6504
|
-
|
|
6580
|
+
let assets;
|
|
6581
|
+
try {
|
|
6582
|
+
assets = await this.assets(assetIds, reference.projectId);
|
|
6583
|
+
} catch (error) {
|
|
6584
|
+
if (error instanceof UnsupportedScenarioOutput) return { status: "failed", error: error.message };
|
|
6585
|
+
throw error;
|
|
6586
|
+
}
|
|
6505
6587
|
if (assets.length > 1) {
|
|
6506
6588
|
return {
|
|
6507
6589
|
status: "review",
|
|
@@ -6556,6 +6638,32 @@ var ScenarioProvider = class _ScenarioProvider {
|
|
|
6556
6638
|
return assets.sort((left, right) => left.outputIndex - right.outputIndex || (jobOrder.get(left.id) ?? Number.MAX_SAFE_INTEGER) - (jobOrder.get(right.id) ?? Number.MAX_SAFE_INTEGER) || left.id.localeCompare(right.id));
|
|
6557
6639
|
}
|
|
6558
6640
|
};
|
|
6641
|
+
function uploadCachePath() {
|
|
6642
|
+
return process.env.SCENARIO_UPLOAD_CACHE ?? path2.join(os2.tmpdir(), "pixelkiln-scenario", "uploads.json");
|
|
6643
|
+
}
|
|
6644
|
+
function uploadCacheKey(baseUrl, projectId, digest2) {
|
|
6645
|
+
return createHash2("sha256").update(`${baseUrl}
|
|
6646
|
+
${projectId ?? ""}
|
|
6647
|
+
${digest2}`).digest("hex");
|
|
6648
|
+
}
|
|
6649
|
+
function readUploadCache() {
|
|
6650
|
+
try {
|
|
6651
|
+
const value = JSON.parse(readFileSync2(uploadCachePath(), "utf8"));
|
|
6652
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
6653
|
+
} catch {
|
|
6654
|
+
return {};
|
|
6655
|
+
}
|
|
6656
|
+
}
|
|
6657
|
+
function writeUploadCache(key, assetId) {
|
|
6658
|
+
try {
|
|
6659
|
+
const file = uploadCachePath();
|
|
6660
|
+
mkdirSync2(path2.dirname(file), { recursive: true });
|
|
6661
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
6662
|
+
writeFileSync2(tmp, JSON.stringify({ ...readUploadCache(), [key]: assetId }));
|
|
6663
|
+
renameSync(tmp, file);
|
|
6664
|
+
} catch {
|
|
6665
|
+
}
|
|
6666
|
+
}
|
|
6559
6667
|
function scenarioOptions(spec) {
|
|
6560
6668
|
return spec.providerOptions;
|
|
6561
6669
|
}
|
|
@@ -6571,9 +6679,25 @@ function scenarioBaseUrl(value) {
|
|
|
6571
6679
|
}
|
|
6572
6680
|
return url.href.replace(/\/$/, "");
|
|
6573
6681
|
}
|
|
6574
|
-
function
|
|
6682
|
+
function referenceParameter(options) {
|
|
6683
|
+
return options.referenceParameter?.trim() || "referenceImages";
|
|
6684
|
+
}
|
|
6685
|
+
function readRevisionSource(spec) {
|
|
6686
|
+
const revision = spec.revision;
|
|
6687
|
+
if (!revision.sourceFile || !revision.sourceSha256 || !revision.sourceFormat) {
|
|
6688
|
+
throw new Error(`${spec.styleId}/${spec.assetId}: revision source is not ready`);
|
|
6689
|
+
}
|
|
6690
|
+
const bytes = readFileSync2(revision.sourceFile);
|
|
6691
|
+
if (sha256(bytes) !== revision.sourceSha256) {
|
|
6692
|
+
throw new Error(`${spec.styleId}/${spec.assetId}: revision source changed after the manifest was resolved`);
|
|
6693
|
+
}
|
|
6694
|
+
return { base64: bytes.toString("base64"), format: revision.sourceFormat };
|
|
6695
|
+
}
|
|
6696
|
+
function requestBody(spec, options, referenceAssetIds = []) {
|
|
6697
|
+
const parameter = referenceParameter(options);
|
|
6575
6698
|
return {
|
|
6576
6699
|
...options.parameters ?? {},
|
|
6700
|
+
...referenceAssetIds.length ? { [parameter]: options.referenceArray === false ? referenceAssetIds[0] : referenceAssetIds } : {},
|
|
6577
6701
|
prompt: spec.prompt,
|
|
6578
6702
|
width: spec.width,
|
|
6579
6703
|
height: spec.height,
|
|
@@ -6663,9 +6787,11 @@ function scenarioAssetIds(job) {
|
|
|
6663
6787
|
function assetDownloadUrl(asset) {
|
|
6664
6788
|
return asset.originalFileUrl || asset.url;
|
|
6665
6789
|
}
|
|
6790
|
+
var UnsupportedScenarioOutput = class extends Error {
|
|
6791
|
+
};
|
|
6666
6792
|
function assertPngAsset(asset) {
|
|
6667
6793
|
if (asset.originalMimeType && asset.originalMimeType !== MediaType.PNG) {
|
|
6668
|
-
throw new
|
|
6794
|
+
throw new UnsupportedScenarioOutput(
|
|
6669
6795
|
`Scenario asset ${asset.id} is ${asset.originalMimeType}; PixelKiln's Scenario MVP requires PNG output`
|
|
6670
6796
|
);
|
|
6671
6797
|
}
|
|
@@ -6732,7 +6858,7 @@ function redactScenarioError(value, secrets) {
|
|
|
6732
6858
|
// src/providers/comfyui.ts
|
|
6733
6859
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
6734
6860
|
import { readFile } from "fs/promises";
|
|
6735
|
-
import
|
|
6861
|
+
import path3 from "path";
|
|
6736
6862
|
var DEFAULT_BASE_URL3 = "http://127.0.0.1:8188";
|
|
6737
6863
|
var BUILTIN_BINDINGS = /* @__PURE__ */ new Set([
|
|
6738
6864
|
"prompt",
|
|
@@ -6869,7 +6995,7 @@ var ComfyUIProvider = class _ComfyUIProvider {
|
|
|
6869
6995
|
}
|
|
6870
6996
|
async resolveOptions(value, context) {
|
|
6871
6997
|
const options = parseOptions(value);
|
|
6872
|
-
const workflowPath =
|
|
6998
|
+
const workflowPath = path3.resolve(context.root, options.workflowFile);
|
|
6873
6999
|
let raw;
|
|
6874
7000
|
try {
|
|
6875
7001
|
raw = await readFile(workflowPath, "utf8");
|
|
@@ -6924,7 +7050,7 @@ var ComfyUIProvider = class _ComfyUIProvider {
|
|
|
6924
7050
|
if (typeof item !== "string") {
|
|
6925
7051
|
throw new Error(`ComfyUI ${label} must be a manifest-relative PNG or JPEG path`);
|
|
6926
7052
|
}
|
|
6927
|
-
const file =
|
|
7053
|
+
const file = path3.resolve(context.root, item);
|
|
6928
7054
|
let bytes;
|
|
6929
7055
|
try {
|
|
6930
7056
|
bytes = await readFile(file);
|
|
@@ -7636,7 +7762,7 @@ function canonical(value) {
|
|
|
7636
7762
|
}
|
|
7637
7763
|
|
|
7638
7764
|
// src/providers/fake.ts
|
|
7639
|
-
import { createHash as
|
|
7765
|
+
import { createHash as createHash3 } from "crypto";
|
|
7640
7766
|
var FAKE_PNG = Buffer.from(
|
|
7641
7767
|
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==",
|
|
7642
7768
|
"base64"
|
|
@@ -7738,7 +7864,7 @@ var FakeProvider = class {
|
|
|
7738
7864
|
}
|
|
7739
7865
|
async download(url) {
|
|
7740
7866
|
if (!url.startsWith("fake://")) throw new Error(`fake provider cannot download ${url}`);
|
|
7741
|
-
const salt =
|
|
7867
|
+
const salt = createHash3("sha256").update(url).digest();
|
|
7742
7868
|
return Buffer.concat([FAKE_PNG, salt.subarray(0, 4)]);
|
|
7743
7869
|
}
|
|
7744
7870
|
async balance() {
|
|
@@ -7887,7 +8013,7 @@ registerProvider({
|
|
|
7887
8013
|
// src/manifest.ts
|
|
7888
8014
|
import { readFile as readFile3 } from "fs/promises";
|
|
7889
8015
|
import { existsSync as existsSync4 } from "fs";
|
|
7890
|
-
import
|
|
8016
|
+
import path6 from "path";
|
|
7891
8017
|
|
|
7892
8018
|
// src/loop-directions.ts
|
|
7893
8019
|
var SINGLE_OUTPUT_FIELDS = ["file", "cell", "remoteId", "source", "sourceByStyle", "outputRole"];
|
|
@@ -7976,12 +8102,12 @@ function isObject2(value) {
|
|
|
7976
8102
|
|
|
7977
8103
|
// src/outputs.ts
|
|
7978
8104
|
import { existsSync as existsSync3 } from "fs";
|
|
7979
|
-
import
|
|
8105
|
+
import path5 from "path";
|
|
7980
8106
|
|
|
7981
8107
|
// src/lock.ts
|
|
7982
8108
|
import { readFile as readFile2, writeFile, rename, mkdir, rmdir, rm, stat } from "fs/promises";
|
|
7983
8109
|
import { existsSync as existsSync2 } from "fs";
|
|
7984
|
-
import
|
|
8110
|
+
import path4 from "path";
|
|
7985
8111
|
async function loadLock(lockPath) {
|
|
7986
8112
|
if (!existsSync2(lockPath)) return { version: 2, entries: {} };
|
|
7987
8113
|
try {
|
|
@@ -7998,7 +8124,7 @@ var saveQueues = /* @__PURE__ */ new Map();
|
|
|
7998
8124
|
var dirtyPatches = /* @__PURE__ */ new WeakMap();
|
|
7999
8125
|
var dirtyDeletes = /* @__PURE__ */ new WeakMap();
|
|
8000
8126
|
function saveLock(lockPath, lock) {
|
|
8001
|
-
const queueKey =
|
|
8127
|
+
const queueKey = path4.resolve(lockPath);
|
|
8002
8128
|
const previous = saveQueues.get(queueKey) ?? Promise.resolve();
|
|
8003
8129
|
const next = previous.catch(() => {
|
|
8004
8130
|
}).then(() => writeLockNow(lockPath, lock));
|
|
@@ -8010,7 +8136,7 @@ function saveLock(lockPath, lock) {
|
|
|
8010
8136
|
return next;
|
|
8011
8137
|
}
|
|
8012
8138
|
async function writeLockNow(lockPath, lock) {
|
|
8013
|
-
await mkdir(
|
|
8139
|
+
await mkdir(path4.dirname(path4.resolve(lockPath)), { recursive: true });
|
|
8014
8140
|
const release = await acquireFileLock(lockPath);
|
|
8015
8141
|
try {
|
|
8016
8142
|
await writeLockWhileHeld(lockPath, lock);
|
|
@@ -8155,22 +8281,22 @@ function totalSpend(lock, unit = "generations") {
|
|
|
8155
8281
|
|
|
8156
8282
|
// src/outputs.ts
|
|
8157
8283
|
function portableOutputPath(file, manifestDir) {
|
|
8158
|
-
const root =
|
|
8159
|
-
const absolute =
|
|
8160
|
-
const relative =
|
|
8161
|
-
const value =
|
|
8162
|
-
return value.split(
|
|
8284
|
+
const root = path5.resolve(manifestDir);
|
|
8285
|
+
const absolute = path5.isAbsolute(file) ? path5.normalize(file) : path5.resolve(root, file);
|
|
8286
|
+
const relative = path5.relative(root, absolute);
|
|
8287
|
+
const value = path5.isAbsolute(relative) ? absolute : relative;
|
|
8288
|
+
return value.split(path5.sep).join("/");
|
|
8163
8289
|
}
|
|
8164
8290
|
function resolveOutputPath(recordedPath, manifestDir) {
|
|
8165
|
-
if (
|
|
8166
|
-
if (
|
|
8167
|
-
return
|
|
8291
|
+
if (path5.isAbsolute(recordedPath)) return path5.normalize(recordedPath);
|
|
8292
|
+
if (path5.win32.isAbsolute(recordedPath)) return recordedPath;
|
|
8293
|
+
return path5.resolve(manifestDir, recordedPath.split(/[\\/]/).join(path5.sep));
|
|
8168
8294
|
}
|
|
8169
8295
|
function expectedOutputPath(spec, role, index, total, mediaType) {
|
|
8170
8296
|
return memberPath(spec.outFile, role, index, total, mediaType);
|
|
8171
8297
|
}
|
|
8172
8298
|
function memberPath(file, role, index, total, mediaType) {
|
|
8173
|
-
const originalExt =
|
|
8299
|
+
const originalExt = path5.extname(file);
|
|
8174
8300
|
const ext = mediaType ? mediaExtension(mediaType) : originalExt || ".png";
|
|
8175
8301
|
const stem = originalExt ? file.slice(0, -originalExt.length) : file;
|
|
8176
8302
|
if (total === 1) return `${stem}${ext}`;
|
|
@@ -8184,10 +8310,10 @@ function isFrameSetEntry(entry) {
|
|
|
8184
8310
|
return entry.generator === "frames" && isMemberSetEntry(entry);
|
|
8185
8311
|
}
|
|
8186
8312
|
function sourceIsStem(source, entry, manifestDir) {
|
|
8187
|
-
return isMemberSetEntry(entry) && !existsSync3(
|
|
8313
|
+
return isMemberSetEntry(entry) && !existsSync3(path5.resolve(manifestDir, source));
|
|
8188
8314
|
}
|
|
8189
8315
|
function sourceOutputPath(source, entry, index, manifestDir) {
|
|
8190
|
-
const absolute =
|
|
8316
|
+
const absolute = path5.resolve(manifestDir, source);
|
|
8191
8317
|
if (!sourceIsStem(source, entry, manifestDir)) return absolute;
|
|
8192
8318
|
const output = entry.outputs[index];
|
|
8193
8319
|
return memberPath(absolute, output.role, index, entry.outputs.length, output.mediaType);
|
|
@@ -8274,7 +8400,7 @@ function resolveSpecOutputs(spec, lock, manifestDir) {
|
|
|
8274
8400
|
id: spec.assetId,
|
|
8275
8401
|
index: 0,
|
|
8276
8402
|
path: spec.outFile,
|
|
8277
|
-
absolutePath:
|
|
8403
|
+
absolutePath: path5.resolve(spec.outFile),
|
|
8278
8404
|
sha256: ""
|
|
8279
8405
|
}];
|
|
8280
8406
|
}
|
|
@@ -8290,7 +8416,7 @@ function frameSetFps(entry) {
|
|
|
8290
8416
|
|
|
8291
8417
|
// src/manifest.ts
|
|
8292
8418
|
async function loadManifest(manifestPath) {
|
|
8293
|
-
const abs =
|
|
8419
|
+
const abs = path6.resolve(manifestPath);
|
|
8294
8420
|
if (!existsSync4(abs)) throw new ProjectError(`No manifest at ${abs}`);
|
|
8295
8421
|
const expansion = expandLoopDirections(JSON.parse(await readFile3(abs, "utf8")));
|
|
8296
8422
|
if (expansion.issues.length) {
|
|
@@ -8335,13 +8461,24 @@ ${issues}`);
|
|
|
8335
8461
|
}
|
|
8336
8462
|
}
|
|
8337
8463
|
if (asset.revision) {
|
|
8338
|
-
|
|
8464
|
+
const parent = parsed.data.assets[asset.revision.from];
|
|
8465
|
+
if (!parent) {
|
|
8339
8466
|
unknownReferences.push(
|
|
8340
8467
|
`assets.${assetId}.revision.from: unknown asset "${asset.revision.from}"`
|
|
8341
8468
|
);
|
|
8342
8469
|
} else if (asset.revision.from === assetId) {
|
|
8343
8470
|
unknownReferences.push(`assets.${assetId}.revision.from: an asset cannot revise itself`);
|
|
8344
8471
|
}
|
|
8472
|
+
const fromStyle = asset.revision.fromStyle;
|
|
8473
|
+
if (fromStyle) {
|
|
8474
|
+
if (!styleIds.has(fromStyle)) {
|
|
8475
|
+
unknownReferences.push(`assets.${assetId}.revision.fromStyle: unknown style "${fromStyle}"`);
|
|
8476
|
+
} else if (parent && parent.styles.length && !parent.styles.includes(fromStyle)) {
|
|
8477
|
+
unknownReferences.push(
|
|
8478
|
+
`assets.${assetId}.revision.fromStyle: "${asset.revision.from}" is not in style "${fromStyle}"`
|
|
8479
|
+
);
|
|
8480
|
+
}
|
|
8481
|
+
}
|
|
8345
8482
|
}
|
|
8346
8483
|
for (const shape of ["state", "animation"]) {
|
|
8347
8484
|
const of = asset[shape]?.of;
|
|
@@ -8383,7 +8520,7 @@ ${issues}`);
|
|
|
8383
8520
|
${unknownReferences.map((i) => ` ${i}`).join("\n")}`);
|
|
8384
8521
|
}
|
|
8385
8522
|
const loopFamilies = Object.keys(expansion.families).length ? { loopFamilies: expansion.families } : {};
|
|
8386
|
-
return { manifest: parsed.data, root:
|
|
8523
|
+
return { manifest: parsed.data, root: path6.dirname(abs), path: abs, ...loopFamilies };
|
|
8387
8524
|
}
|
|
8388
8525
|
function parentAssetId(asset) {
|
|
8389
8526
|
return asset?.revision?.from ?? asset?.state?.of ?? asset?.animation?.of ?? asset?.portrait?.of ?? asset?.outfit?.of;
|
|
@@ -8457,7 +8594,7 @@ async function resolveSpecs(loaded, filter) {
|
|
|
8457
8594
|
return provider;
|
|
8458
8595
|
};
|
|
8459
8596
|
const specs = [];
|
|
8460
|
-
const
|
|
8597
|
+
const requestedStyleIds = Object.keys(manifest.styles).filter(
|
|
8461
8598
|
(id) => !filter?.styles?.length || filter.styles.includes(id)
|
|
8462
8599
|
);
|
|
8463
8600
|
for (const unknownStyle of filter?.styles ?? []) {
|
|
@@ -8482,9 +8619,42 @@ async function resolveSpecs(loaded, filter) {
|
|
|
8482
8619
|
current = parentAssetId(manifest.assets[current]);
|
|
8483
8620
|
}
|
|
8484
8621
|
}
|
|
8622
|
+
const styleDependencies = /* @__PURE__ */ new Map();
|
|
8623
|
+
for (const styleId of Object.keys(manifest.styles)) {
|
|
8624
|
+
const needed = /* @__PURE__ */ new Set();
|
|
8625
|
+
for (const assetId of resolutionAssetIds) {
|
|
8626
|
+
const asset = manifest.assets[assetId];
|
|
8627
|
+
if (asset?.styles.length && !asset.styles.includes(styleId)) continue;
|
|
8628
|
+
const fromStyle = asset?.revision?.fromStyle;
|
|
8629
|
+
if (fromStyle && fromStyle !== styleId) needed.add(fromStyle);
|
|
8630
|
+
}
|
|
8631
|
+
styleDependencies.set(styleId, needed);
|
|
8632
|
+
}
|
|
8633
|
+
const styleIds = [];
|
|
8634
|
+
const emitStyles = new Set(requestedStyleIds);
|
|
8635
|
+
{
|
|
8636
|
+
const placed = /* @__PURE__ */ new Set();
|
|
8637
|
+
const visiting = [];
|
|
8638
|
+
const place = (styleId) => {
|
|
8639
|
+
if (placed.has(styleId)) return;
|
|
8640
|
+
const cycleStart = visiting.indexOf(styleId);
|
|
8641
|
+
if (cycleStart >= 0) {
|
|
8642
|
+
throw new Error(
|
|
8643
|
+
`Styles revise each other in a cycle: ${[...visiting.slice(cycleStart), styleId].join(" -> ")}. revision.fromStyle must form a chain, not a loop.`
|
|
8644
|
+
);
|
|
8645
|
+
}
|
|
8646
|
+
visiting.push(styleId);
|
|
8647
|
+
for (const dependency of styleDependencies.get(styleId) ?? []) place(dependency);
|
|
8648
|
+
visiting.pop();
|
|
8649
|
+
placed.add(styleId);
|
|
8650
|
+
styleIds.push(styleId);
|
|
8651
|
+
};
|
|
8652
|
+
for (const styleId of requestedStyleIds) place(styleId);
|
|
8653
|
+
}
|
|
8654
|
+
const styleFinalizers = /* @__PURE__ */ new Map();
|
|
8485
8655
|
const styleImageCache = /* @__PURE__ */ new Map();
|
|
8486
8656
|
async function loadStyleImage(rel, what = "Style image") {
|
|
8487
|
-
const abs =
|
|
8657
|
+
const abs = path6.resolve(root, rel);
|
|
8488
8658
|
let hit = styleImageCache.get(abs);
|
|
8489
8659
|
if (!hit) {
|
|
8490
8660
|
if (!existsSync4(abs)) throw new Error(`${what} not found: ${abs}`);
|
|
@@ -8514,7 +8684,7 @@ async function resolveSpecs(loaded, filter) {
|
|
|
8514
8684
|
styleImageDimensions.push(loadedImage);
|
|
8515
8685
|
}
|
|
8516
8686
|
const resolvedImages = style.styleImages.map((image) => {
|
|
8517
|
-
const hit = styleImageCache.get(
|
|
8687
|
+
const hit = styleImageCache.get(path6.resolve(root, image.path));
|
|
8518
8688
|
return { base64: hit.base64, width: hit.width, height: hit.height, format: hit.format };
|
|
8519
8689
|
});
|
|
8520
8690
|
const styleSpecs = /* @__PURE__ */ new Map();
|
|
@@ -8615,9 +8785,9 @@ async function resolveSpecs(loaded, filter) {
|
|
|
8615
8785
|
} else {
|
|
8616
8786
|
prompt = [style.promptPrefix, subject, style.promptSuffix].map((p) => p.trim()).filter(Boolean).join(", ");
|
|
8617
8787
|
}
|
|
8618
|
-
const relFile = asset.file ??
|
|
8619
|
-
const outFile =
|
|
8620
|
-
const qualityOutFile = style.quality ?
|
|
8788
|
+
const relFile = asset.file ?? path6.join(asset.category ?? "", `${assetId}.png`);
|
|
8789
|
+
const outFile = path6.resolve(root, style.outDir, relFile);
|
|
8790
|
+
const qualityOutFile = style.quality ? path6.resolve(root, style.quality.outDir, pngPath(relFile)) : void 0;
|
|
8621
8791
|
const tileSize = generator === "tiles" ? size : style.tileSize ?? 32;
|
|
8622
8792
|
const tileVariations = tileFeatureOutputCount(generator === "tiles" ? style.tileFeature : void 0) ?? tileVariationCount(countNumberedDescriptions(prompt));
|
|
8623
8793
|
const terrainTiles = generator === "terrain" ? terrainTileCount(style.terrainTransitionSize) : 0;
|
|
@@ -8712,7 +8882,7 @@ async function resolveSpecs(loaded, filter) {
|
|
|
8712
8882
|
minGridConfidence: style.quality.minGridConfidence,
|
|
8713
8883
|
...style.quality.minTransparency == null ? {} : { minTransparency: style.quality.minTransparency },
|
|
8714
8884
|
...style.quality.fixerRevision ? { fixerRevision: style.quality.fixerRevision } : {},
|
|
8715
|
-
...style.quality.fixerPython ? { fixerPython:
|
|
8885
|
+
...style.quality.fixerPython ? { fixerPython: path6.resolve(root, style.quality.fixerPython) } : {},
|
|
8716
8886
|
...style.quality.fps == null ? {} : { fps: style.quality.fps }
|
|
8717
8887
|
}
|
|
8718
8888
|
} : {},
|
|
@@ -8848,7 +9018,7 @@ async function resolveSpecs(loaded, filter) {
|
|
|
8848
9018
|
animation: sourceSpec.character.animation,
|
|
8849
9019
|
outfit: {
|
|
8850
9020
|
reference: {
|
|
8851
|
-
path:
|
|
9021
|
+
path: path6.resolve(root, asset.outfit.reference),
|
|
8852
9022
|
sha256: referenceImage.hash,
|
|
8853
9023
|
width: referenceImage.width,
|
|
8854
9024
|
height: referenceImage.height,
|
|
@@ -8869,28 +9039,30 @@ async function resolveSpecs(loaded, filter) {
|
|
|
8869
9039
|
`Provider "${activeProvider.id}" does not support ${asset.revision.mode} revisions`
|
|
8870
9040
|
);
|
|
8871
9041
|
}
|
|
8872
|
-
const
|
|
8873
|
-
const
|
|
9042
|
+
const sourceStyleId = asset.revision.fromStyle && asset.revision.fromStyle !== styleId ? asset.revision.fromStyle : void 0;
|
|
9043
|
+
const sourceSpec = sourceStyleId ? await styleFinalizers.get(sourceStyleId)(asset.revision.from) : await finalize(asset.revision.from);
|
|
9044
|
+
const sourceStem = sourceSpec.quality?.outFile ?? (sourceSpec.source ? path6.resolve(root, sourceSpec.source) : sourceSpec.outFile);
|
|
8874
9045
|
const sourceMembers = await revisionSourceMembers(sourceStem, `revision source ${asset.revision.from}`);
|
|
8875
9046
|
const sourceFile = sourceMembers?.[0]?.file ?? sourceStem;
|
|
8876
9047
|
const sourceImage = sourceMembers ? { ...sourceMembers[0], hash: sha256(JSON.stringify(sourceMembers.map((member) => member.sha256))) } : await optionalRevisionImage(sourceFile, "revision source");
|
|
8877
9048
|
const sourceFps = sourceSpec.character?.animation?.fps ?? sourceSpec.objectPro?.animation?.fps ?? sourceSpec.revision?.fps ?? sourceSpec.revision?.sourceFps;
|
|
8878
|
-
const maskFile = asset.revision.mask ?
|
|
9049
|
+
const maskFile = asset.revision.mask ? path6.resolve(root, asset.revision.mask) : void 0;
|
|
8879
9050
|
const maskImage = maskFile ? await optionalRevisionImage(maskFile, "revision mask", "png") : null;
|
|
8880
9051
|
if (sourceImage && maskImage && (sourceImage.width !== maskImage.width || sourceImage.height !== maskImage.height)) {
|
|
8881
9052
|
throw new Error(
|
|
8882
9053
|
`Revision mask for ${styleId}/${assetId} is ${maskImage.width}x${maskImage.height}; source ${asset.revision.from} is ${sourceImage.width}x${sourceImage.height}`
|
|
8883
9054
|
);
|
|
8884
9055
|
}
|
|
8885
|
-
const paletteImageFile = asset.revision.paletteImage ?
|
|
9056
|
+
const paletteImageFile = asset.revision.paletteImage ? path6.resolve(root, asset.revision.paletteImage) : void 0;
|
|
8886
9057
|
const paletteImage = paletteImageFile ? await optionalRevisionImage(paletteImageFile, "revision palette image", "png") : null;
|
|
8887
|
-
const lastFrameFile = asset.revision.lastFrame ?
|
|
9058
|
+
const lastFrameFile = asset.revision.lastFrame ? path6.resolve(root, asset.revision.lastFrame) : void 0;
|
|
8888
9059
|
const lastFrame = lastFrameFile ? await optionalRevisionImage(lastFrameFile, "revision last frame") : null;
|
|
8889
|
-
const keypointsFile = asset.revision.keypointsFile ?
|
|
9060
|
+
const keypointsFile = asset.revision.keypointsFile ? path6.resolve(root, asset.revision.keypointsFile) : void 0;
|
|
8890
9061
|
const keypoints = keypointsFile ? await optionalSkeletonSet(keypointsFile) : null;
|
|
8891
9062
|
resolved.revision = {
|
|
8892
9063
|
mode: asset.revision.mode,
|
|
8893
9064
|
sourceAssetId: asset.revision.from,
|
|
9065
|
+
...sourceStyleId ? { sourceStyleId } : {},
|
|
8894
9066
|
sourceFile,
|
|
8895
9067
|
sourceSha256: sourceImage?.hash ?? null,
|
|
8896
9068
|
sourceWidth: sourceImage?.width ?? null,
|
|
@@ -9012,6 +9184,8 @@ async function resolveSpecs(loaded, filter) {
|
|
|
9012
9184
|
finalized.add(assetId);
|
|
9013
9185
|
return resolved;
|
|
9014
9186
|
};
|
|
9187
|
+
styleFinalizers.set(styleId, finalize);
|
|
9188
|
+
if (!emitStyles.has(styleId)) continue;
|
|
9015
9189
|
for (const assetId of Object.keys(manifest.assets)) {
|
|
9016
9190
|
if (!requestedAssetIds.has(assetId) || !styleSpecs.has(assetId)) continue;
|
|
9017
9191
|
specs.push(await finalize(assetId));
|
|
@@ -9053,7 +9227,7 @@ async function resolveSpecs(loaded, filter) {
|
|
|
9053
9227
|
return specs;
|
|
9054
9228
|
}
|
|
9055
9229
|
function pngPath(file) {
|
|
9056
|
-
const extension =
|
|
9230
|
+
const extension = path6.extname(file);
|
|
9057
9231
|
return extension ? `${file.slice(0, -extension.length)}.png` : `${file}.png`;
|
|
9058
9232
|
}
|
|
9059
9233
|
async function resolveMapScene(root, styleId, assetId, generator, scene, provider) {
|
|
@@ -9063,12 +9237,12 @@ async function resolveMapScene(root, styleId, assetId, generator, scene, provide
|
|
|
9063
9237
|
if (!provider.supportsMapScene) {
|
|
9064
9238
|
throw new Error(`assets.${assetId}.scene: provider "${provider.id}" cannot draw into a scene; PixelLab's /map-objects can`);
|
|
9065
9239
|
}
|
|
9066
|
-
const file =
|
|
9240
|
+
const file = path6.resolve(root, scene.image);
|
|
9067
9241
|
const image = await optionalRevisionImage(file, "scene image", "png");
|
|
9068
9242
|
const base = { file, sha256: image?.hash ?? null, width: image?.width ?? null, height: image?.height ?? null };
|
|
9069
9243
|
const placement = scene.placement;
|
|
9070
9244
|
if ("mask" in placement) {
|
|
9071
|
-
const maskFile =
|
|
9245
|
+
const maskFile = path6.resolve(root, placement.mask);
|
|
9072
9246
|
const mask = await optionalRevisionImage(maskFile, "scene mask", "png");
|
|
9073
9247
|
if (image && mask && (image.width !== mask.width || image.height !== mask.height)) {
|
|
9074
9248
|
throw new Error(
|
|
@@ -9140,7 +9314,7 @@ async function resolveStyleImages(loaded, styleId) {
|
|
|
9140
9314
|
if (!style) throw new Error(`Unknown style "${styleId}"`);
|
|
9141
9315
|
const out = [];
|
|
9142
9316
|
for (const img of style.styleImages) {
|
|
9143
|
-
const file =
|
|
9317
|
+
const file = path6.resolve(loaded.root, img.path);
|
|
9144
9318
|
const buf = await readFile3(file);
|
|
9145
9319
|
const metadata = imageMetadata(buf);
|
|
9146
9320
|
if (!metadata) throw new Error(`Style image is not a readable PNG or JPEG: ${file}`);
|
|
@@ -9171,7 +9345,7 @@ async function resolveCharacterShape(asset, style, kind, files) {
|
|
|
9171
9345
|
};
|
|
9172
9346
|
if (asset.concept) {
|
|
9173
9347
|
const image = await files.load(asset.concept, "Concept image");
|
|
9174
|
-
shape.concept = { path:
|
|
9348
|
+
shape.concept = { path: path6.resolve(files.root, asset.concept), sha256: image.hash, width: image.width, height: image.height, format: image.format };
|
|
9175
9349
|
}
|
|
9176
9350
|
if (asset.reference) {
|
|
9177
9351
|
const byDirection = typeof asset.reference === "string" ? { south: asset.reference } : asset.reference;
|
|
@@ -9179,7 +9353,7 @@ async function resolveCharacterShape(asset, style, kind, files) {
|
|
|
9179
9353
|
for (const [direction, rel] of Object.entries(byDirection)) {
|
|
9180
9354
|
const image = await files.load(rel, `Reference sprite (${direction})`);
|
|
9181
9355
|
shape.reference[direction] = {
|
|
9182
|
-
path:
|
|
9356
|
+
path: path6.resolve(files.root, rel),
|
|
9183
9357
|
sha256: image.hash,
|
|
9184
9358
|
width: image.width,
|
|
9185
9359
|
height: image.height,
|
|
@@ -9200,7 +9374,7 @@ async function resolveCharacterShape(asset, style, kind, files) {
|
|
|
9200
9374
|
const animationMode = asset.animation.mode ?? (asset.animation.template ? "template" : "v3");
|
|
9201
9375
|
const pose = async (rel, what) => {
|
|
9202
9376
|
const image = await files.load(rel, what);
|
|
9203
|
-
return { path:
|
|
9377
|
+
return { path: path6.resolve(files.root, rel), sha256: image.hash, width: image.width, height: image.height, format: image.format };
|
|
9204
9378
|
};
|
|
9205
9379
|
shape.animation = {
|
|
9206
9380
|
mode: animationMode,
|
|
@@ -9231,7 +9405,7 @@ async function resolveObjectProShape(assetId, asset, style, kind, files) {
|
|
|
9231
9405
|
const south = typeof asset.reference === "string" ? asset.reference : asset.reference.south;
|
|
9232
9406
|
if (south) {
|
|
9233
9407
|
const image = await files.load(south, "Reference sprite");
|
|
9234
|
-
shape.reference = { path:
|
|
9408
|
+
shape.reference = { path: path6.resolve(files.root, south), sha256: image.hash, width: image.width, height: image.height, format: image.format };
|
|
9235
9409
|
}
|
|
9236
9410
|
}
|
|
9237
9411
|
if (asset.state) {
|
|
@@ -9259,7 +9433,7 @@ async function resolveObjectProShape(assetId, asset, style, kind, files) {
|
|
|
9259
9433
|
}
|
|
9260
9434
|
const pose = async (rel, what) => {
|
|
9261
9435
|
const image = await files.load(rel, what);
|
|
9262
|
-
return { path:
|
|
9436
|
+
return { path: path6.resolve(files.root, rel), sha256: image.hash, width: image.width, height: image.height, format: image.format };
|
|
9263
9437
|
};
|
|
9264
9438
|
shape.animation = {
|
|
9265
9439
|
mode: asset.animation.mode ?? "v3",
|
|
@@ -9279,11 +9453,11 @@ function characterDirections(directions) {
|
|
|
9279
9453
|
}
|
|
9280
9454
|
|
|
9281
9455
|
// src/project.ts
|
|
9282
|
-
import
|
|
9456
|
+
import path14 from "path";
|
|
9283
9457
|
|
|
9284
9458
|
// src/env.ts
|
|
9285
|
-
import { readFileSync as
|
|
9286
|
-
import
|
|
9459
|
+
import { readFileSync as readFileSync3, existsSync as existsSync5 } from "fs";
|
|
9460
|
+
import path7 from "path";
|
|
9287
9461
|
function loadEnvFiles(dir) {
|
|
9288
9462
|
const loaded = [];
|
|
9289
9463
|
for (const [file, vars] of envFiles(dir)) {
|
|
@@ -9297,10 +9471,10 @@ function loadEnvFiles(dir) {
|
|
|
9297
9471
|
function envFiles(dir) {
|
|
9298
9472
|
const out = [];
|
|
9299
9473
|
for (const name of [".env.local", ".env"]) {
|
|
9300
|
-
const file =
|
|
9474
|
+
const file = path7.join(dir, name);
|
|
9301
9475
|
if (!existsSync5(file)) continue;
|
|
9302
9476
|
try {
|
|
9303
|
-
out.push([file, parseEnv(
|
|
9477
|
+
out.push([file, parseEnv(readFileSync3(file, "utf8"))]);
|
|
9304
9478
|
} catch {
|
|
9305
9479
|
}
|
|
9306
9480
|
}
|
|
@@ -9327,11 +9501,11 @@ function parseEnv(contents) {
|
|
|
9327
9501
|
|
|
9328
9502
|
// src/pipeline/plan.ts
|
|
9329
9503
|
import { existsSync as existsSync8 } from "fs";
|
|
9330
|
-
import
|
|
9504
|
+
import path13 from "path";
|
|
9331
9505
|
|
|
9332
9506
|
// src/pipeline/mirror.ts
|
|
9333
9507
|
import { mkdir as mkdir2, readFile as readFile4, rename as rename2, writeFile as writeFile2 } from "fs/promises";
|
|
9334
|
-
import
|
|
9508
|
+
import path8 from "path";
|
|
9335
9509
|
|
|
9336
9510
|
// src/palette.ts
|
|
9337
9511
|
function normalizePalette(values) {
|
|
@@ -9470,7 +9644,7 @@ async function deriveMirror(spec, lock, lockPath, opts = {}) {
|
|
|
9470
9644
|
for (const [index, member] of members.entries()) {
|
|
9471
9645
|
const bytes = flipHorizontal(await readFile4(member.file));
|
|
9472
9646
|
const target = expectedOutputPath(spec, member.role, index, members.length, MediaType.PNG);
|
|
9473
|
-
await mkdir2(
|
|
9647
|
+
await mkdir2(path8.dirname(target), { recursive: true });
|
|
9474
9648
|
const tmp = `${target}.pixelkiln.tmp`;
|
|
9475
9649
|
await writeFile2(tmp, bytes);
|
|
9476
9650
|
await rename2(tmp, target);
|
|
@@ -9533,28 +9707,28 @@ async function deriveMirror(spec, lock, lockPath, opts = {}) {
|
|
|
9533
9707
|
|
|
9534
9708
|
// src/pipeline/quality-profile.ts
|
|
9535
9709
|
import { existsSync as existsSync6 } from "fs";
|
|
9536
|
-
import
|
|
9710
|
+
import path11 from "path";
|
|
9537
9711
|
|
|
9538
9712
|
// src/pipeline/refine.ts
|
|
9539
|
-
import
|
|
9713
|
+
import path10 from "path";
|
|
9540
9714
|
import { execFile } from "child_process";
|
|
9541
9715
|
import { mkdtemp, readFile as readFile6, rm as rm3 } from "fs/promises";
|
|
9542
9716
|
import { tmpdir } from "os";
|
|
9543
9717
|
import { promisify } from "util";
|
|
9544
9718
|
|
|
9545
9719
|
// src/artifacts.ts
|
|
9546
|
-
import
|
|
9547
|
-
import { createHash as
|
|
9720
|
+
import path9 from "path";
|
|
9721
|
+
import { createHash as createHash4, randomUUID as randomUUID3 } from "crypto";
|
|
9548
9722
|
import { mkdir as mkdir3, readFile as readFile5, rename as rename3, rm as rm2, writeFile as writeFile3 } from "fs/promises";
|
|
9549
9723
|
var activeTransactions = /* @__PURE__ */ new Set();
|
|
9550
9724
|
function message(error) {
|
|
9551
9725
|
return error instanceof Error ? error.message : String(error);
|
|
9552
9726
|
}
|
|
9553
9727
|
function digest(data) {
|
|
9554
|
-
return
|
|
9728
|
+
return createHash4("sha256").update(data).digest("hex");
|
|
9555
9729
|
}
|
|
9556
9730
|
function portableRelative(from, to) {
|
|
9557
|
-
return
|
|
9731
|
+
return path9.relative(from, path9.resolve(to)).split(path9.sep).join("/") || ".";
|
|
9558
9732
|
}
|
|
9559
9733
|
function canonical2(value) {
|
|
9560
9734
|
if (value === null || typeof value === "string" || typeof value === "boolean") return value;
|
|
@@ -9604,7 +9778,7 @@ function parseArtifactManifest(absolute, data) {
|
|
|
9604
9778
|
return raw;
|
|
9605
9779
|
}
|
|
9606
9780
|
async function readArtifactBundleManifest(manifestPath) {
|
|
9607
|
-
const absolute =
|
|
9781
|
+
const absolute = path9.resolve(manifestPath);
|
|
9608
9782
|
return parseArtifactManifest(absolute, await readFile5(absolute, "utf8"));
|
|
9609
9783
|
}
|
|
9610
9784
|
async function readOptional(file) {
|
|
@@ -9628,8 +9802,8 @@ function processIsAlive(pid) {
|
|
|
9628
9802
|
}
|
|
9629
9803
|
}
|
|
9630
9804
|
function validTemporaryPath(candidate, destination, type) {
|
|
9631
|
-
return
|
|
9632
|
-
`.${
|
|
9805
|
+
return path9.dirname(candidate) === path9.dirname(destination) && path9.basename(candidate).startsWith(
|
|
9806
|
+
`.${path9.basename(destination)}.pixelkiln-${type}-`
|
|
9633
9807
|
);
|
|
9634
9808
|
}
|
|
9635
9809
|
function parseTransaction(journal, data) {
|
|
@@ -9649,7 +9823,7 @@ async function removeTransactionFiles(journal) {
|
|
|
9649
9823
|
await rm2(transactionMarker(journal), { force: true });
|
|
9650
9824
|
}
|
|
9651
9825
|
async function recoverArtifactTransaction(recoveryFile, allowedDestinations) {
|
|
9652
|
-
const journal =
|
|
9826
|
+
const journal = path9.resolve(recoveryFile);
|
|
9653
9827
|
const bytes = await readOptional(journal);
|
|
9654
9828
|
if (!bytes) {
|
|
9655
9829
|
await rm2(transactionMarker(journal), { force: true });
|
|
@@ -9665,7 +9839,7 @@ async function recoverArtifactTransaction(recoveryFile, allowedDestinations) {
|
|
|
9665
9839
|
);
|
|
9666
9840
|
}
|
|
9667
9841
|
for (const entry of transaction.entries) {
|
|
9668
|
-
if (typeof entry.destination !== "string" || typeof entry.stage !== "string" || typeof entry.sha256 !== "string" || entry.backup !== void 0 && typeof entry.backup !== "string" || !allowedDestinations.has(
|
|
9842
|
+
if (typeof entry.destination !== "string" || typeof entry.stage !== "string" || typeof entry.sha256 !== "string" || entry.backup !== void 0 && typeof entry.backup !== "string" || !allowedDestinations.has(path9.resolve(entry.destination)) || !validTemporaryPath(entry.stage, entry.destination, "stage") || entry.backup !== void 0 && !validTemporaryPath(entry.backup, entry.destination, "backup")) {
|
|
9669
9843
|
throw new Error(
|
|
9670
9844
|
`Refusing unsafe artifact recovery from ${journal}; its destinations or temporary paths do not match the current bundle.`
|
|
9671
9845
|
);
|
|
@@ -9711,7 +9885,7 @@ async function recoverArtifactTransaction(recoveryFile, allowedDestinations) {
|
|
|
9711
9885
|
await removeTransactionFiles(journal);
|
|
9712
9886
|
}
|
|
9713
9887
|
function createArtifactBundleManifest(manifestPath, outputs, provenance) {
|
|
9714
|
-
const root =
|
|
9888
|
+
const root = path9.dirname(path9.resolve(manifestPath));
|
|
9715
9889
|
const sources = provenance.sources.map((source) => ({
|
|
9716
9890
|
...source,
|
|
9717
9891
|
path: portableRelative(root, source.path)
|
|
@@ -9741,11 +9915,11 @@ function withArtifactManifest(manifestPath, outputs, provenance) {
|
|
|
9741
9915
|
];
|
|
9742
9916
|
}
|
|
9743
9917
|
async function writeManagedArtifactBundle(manifestPath, outputs, provenance, options = {}) {
|
|
9744
|
-
const absoluteManifest =
|
|
9918
|
+
const absoluteManifest = path9.resolve(manifestPath);
|
|
9745
9919
|
const recoveryFile = `${absoluteManifest}.transaction`;
|
|
9746
9920
|
const allowedDestinations = /* @__PURE__ */ new Set([
|
|
9747
9921
|
absoluteManifest,
|
|
9748
|
-
...outputs.map((output) =>
|
|
9922
|
+
...outputs.map((output) => path9.resolve(output.path))
|
|
9749
9923
|
]);
|
|
9750
9924
|
await recoverArtifactTransaction(recoveryFile, allowedDestinations);
|
|
9751
9925
|
const existingManifestBytes = await readOptional(absoluteManifest);
|
|
@@ -9763,13 +9937,13 @@ async function writeManagedArtifactBundle(manifestPath, outputs, provenance, opt
|
|
|
9763
9937
|
}
|
|
9764
9938
|
}
|
|
9765
9939
|
if (!options.force) {
|
|
9766
|
-
const root =
|
|
9940
|
+
const root = path9.dirname(absoluteManifest);
|
|
9767
9941
|
const recorded = new Map(
|
|
9768
|
-
previous?.outputs.map((output) => [
|
|
9942
|
+
previous?.outputs.map((output) => [path9.resolve(root, output.path), output.sha256]) ?? []
|
|
9769
9943
|
);
|
|
9770
9944
|
const conflicts = [];
|
|
9771
9945
|
for (const output of outputs) {
|
|
9772
|
-
const destination =
|
|
9946
|
+
const destination = path9.resolve(output.path);
|
|
9773
9947
|
const current = await readOptional(destination);
|
|
9774
9948
|
if (!current || digest(current) === digest(output.data)) continue;
|
|
9775
9949
|
const expected = recorded.get(destination);
|
|
@@ -9791,15 +9965,15 @@ Pass { force: true } (CLI: --force) to take ownership and replace the complete b
|
|
|
9791
9965
|
);
|
|
9792
9966
|
}
|
|
9793
9967
|
async function verifyArtifactBundle(manifestPath) {
|
|
9794
|
-
const absolute =
|
|
9968
|
+
const absolute = path9.resolve(manifestPath);
|
|
9795
9969
|
const raw = await readArtifactBundleManifest(absolute);
|
|
9796
|
-
const root =
|
|
9970
|
+
const root = path9.dirname(absolute);
|
|
9797
9971
|
const changedSources = [];
|
|
9798
9972
|
const changedOutputs = [];
|
|
9799
9973
|
for (const source of raw.sources) {
|
|
9800
9974
|
let actual = null;
|
|
9801
9975
|
try {
|
|
9802
|
-
actual = digest(await readFile5(
|
|
9976
|
+
actual = digest(await readFile5(path9.resolve(root, source.path)));
|
|
9803
9977
|
} catch (error) {
|
|
9804
9978
|
if (!isCode(error, "ENOENT")) throw error;
|
|
9805
9979
|
}
|
|
@@ -9807,7 +9981,7 @@ async function verifyArtifactBundle(manifestPath) {
|
|
|
9807
9981
|
}
|
|
9808
9982
|
for (const output of raw.outputs) {
|
|
9809
9983
|
try {
|
|
9810
|
-
if (digest(await readFile5(
|
|
9984
|
+
if (digest(await readFile5(path9.resolve(root, output.path))) !== output.sha256) {
|
|
9811
9985
|
changedOutputs.push(output.path);
|
|
9812
9986
|
}
|
|
9813
9987
|
} catch (error) {
|
|
@@ -9863,7 +10037,7 @@ async function writeArtifactBundle(files, options = {}) {
|
|
|
9863
10037
|
if (!files.length) throw new Error("An artifact bundle must contain at least one file.");
|
|
9864
10038
|
const normalized = files.map((file) => {
|
|
9865
10039
|
if (!file.path.trim()) throw new Error("Artifact paths cannot be empty.");
|
|
9866
|
-
return { destination:
|
|
10040
|
+
return { destination: path9.resolve(file.path), data: Buffer.from(file.data) };
|
|
9867
10041
|
});
|
|
9868
10042
|
const destinations = /* @__PURE__ */ new Set();
|
|
9869
10043
|
for (const artifact of normalized) {
|
|
@@ -9893,21 +10067,21 @@ async function writeArtifactBundle(files, options = {}) {
|
|
|
9893
10067
|
if (!changed.length) return { changed: [], unchanged };
|
|
9894
10068
|
const token = randomUUID3();
|
|
9895
10069
|
for (const [index, artifact] of changed.entries()) {
|
|
9896
|
-
const basename =
|
|
9897
|
-
artifact.stage =
|
|
9898
|
-
|
|
10070
|
+
const basename = path9.basename(artifact.destination);
|
|
10071
|
+
artifact.stage = path9.join(
|
|
10072
|
+
path9.dirname(artifact.destination),
|
|
9899
10073
|
`.${basename}.pixelkiln-stage-${token}-${index}`
|
|
9900
10074
|
);
|
|
9901
10075
|
if (artifact.existed) {
|
|
9902
|
-
artifact.backup =
|
|
9903
|
-
|
|
10076
|
+
artifact.backup = path9.join(
|
|
10077
|
+
path9.dirname(artifact.destination),
|
|
9904
10078
|
`.${basename}.pixelkiln-backup-${token}-${index}`
|
|
9905
10079
|
);
|
|
9906
10080
|
}
|
|
9907
10081
|
}
|
|
9908
|
-
const journal = options.recoveryFile ?
|
|
10082
|
+
const journal = options.recoveryFile ? path9.resolve(options.recoveryFile) : null;
|
|
9909
10083
|
if (journal) {
|
|
9910
|
-
await mkdir3(
|
|
10084
|
+
await mkdir3(path9.dirname(journal), { recursive: true });
|
|
9911
10085
|
const transaction = {
|
|
9912
10086
|
format: "pixelkiln-artifact-transaction",
|
|
9913
10087
|
version: 1,
|
|
@@ -9932,7 +10106,7 @@ async function writeArtifactBundle(files, options = {}) {
|
|
|
9932
10106
|
}
|
|
9933
10107
|
try {
|
|
9934
10108
|
for (const [index, artifact] of changed.entries()) {
|
|
9935
|
-
await mkdir3(
|
|
10109
|
+
await mkdir3(path9.dirname(artifact.destination), { recursive: true });
|
|
9936
10110
|
await options.beforeStage?.(artifact.destination, index);
|
|
9937
10111
|
await writeFile3(artifact.stage, artifact.data, { flag: "wx" });
|
|
9938
10112
|
}
|
|
@@ -10198,10 +10372,10 @@ function sameFrameGrid(first, next) {
|
|
|
10198
10372
|
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;
|
|
10199
10373
|
}
|
|
10200
10374
|
async function refineAsset(options) {
|
|
10201
|
-
const source =
|
|
10202
|
-
const output =
|
|
10375
|
+
const source = path10.resolve(options.source);
|
|
10376
|
+
const output = path10.resolve(options.output);
|
|
10203
10377
|
if (source === output) throw new Error("Refinement source and output must be different files.");
|
|
10204
|
-
const record =
|
|
10378
|
+
const record = path10.resolve(qualityRecordPath(output));
|
|
10205
10379
|
const palette = normalizePalette(options.palette).map((color) => color.hex);
|
|
10206
10380
|
const minGridConfidence = requireConfidence(options.minGridConfidence ?? "high");
|
|
10207
10381
|
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.");
|
|
@@ -10224,8 +10398,8 @@ async function refineAsset(options) {
|
|
|
10224
10398
|
);
|
|
10225
10399
|
}
|
|
10226
10400
|
}
|
|
10227
|
-
const temp = await mkdtemp(
|
|
10228
|
-
const recovered =
|
|
10401
|
+
const temp = await mkdtemp(path10.join(tmpdir(), "pixelkiln-refine-"));
|
|
10402
|
+
const recovered = path10.join(temp, "native.png");
|
|
10229
10403
|
const fixerCommand = compatibilityCommand ?? options.fixerPython ?? process.env.PIXELKILN_PIXEL_FIXER_PYTHON ?? "python3";
|
|
10230
10404
|
const args = compatibilityCommand ? [...options.fixerArgsPrefix ?? [], source, "--extract", recovered] : ["-c", PYTHON_FIXER_BRIDGE, source, recovered];
|
|
10231
10405
|
try {
|
|
@@ -10310,18 +10484,18 @@ async function refineFrameSet(options) {
|
|
|
10310
10484
|
if (roles.some((role) => !role.trim()) || new Set(roles).size !== roles.length) {
|
|
10311
10485
|
throw new Error("Frame-set roles must be non-empty and unique.");
|
|
10312
10486
|
}
|
|
10313
|
-
const output =
|
|
10314
|
-
const record =
|
|
10487
|
+
const output = path10.resolve(options.output);
|
|
10488
|
+
const record = path10.resolve(qualityRecordPath(output));
|
|
10315
10489
|
const palette = normalizeRefinementPalette(options.palette);
|
|
10316
10490
|
const finalOutputs = options.sources.map((source, index) => qualityFrameOutputPath(output, source.role, index));
|
|
10317
10491
|
if (new Set(finalOutputs).size !== finalOutputs.length) {
|
|
10318
10492
|
throw new Error("Frame-set roles resolve to duplicate output filenames.");
|
|
10319
10493
|
}
|
|
10320
|
-
const sourceSet = new Set(options.sources.map((source) =>
|
|
10494
|
+
const sourceSet = new Set(options.sources.map((source) => path10.resolve(source.path)));
|
|
10321
10495
|
if (finalOutputs.some((file) => sourceSet.has(file))) {
|
|
10322
10496
|
throw new Error("Frame-set refinement sources and outputs must be different files.");
|
|
10323
10497
|
}
|
|
10324
|
-
const temp = await mkdtemp(
|
|
10498
|
+
const temp = await mkdtemp(path10.join(tmpdir(), "pixelkiln-refine-frames-"));
|
|
10325
10499
|
try {
|
|
10326
10500
|
const frames = [];
|
|
10327
10501
|
const outputs = [];
|
|
@@ -10329,8 +10503,8 @@ async function refineFrameSet(options) {
|
|
|
10329
10503
|
const sourcePaths = [];
|
|
10330
10504
|
let firstOptions = null;
|
|
10331
10505
|
for (const [index, sourceEntry] of options.sources.entries()) {
|
|
10332
|
-
const source =
|
|
10333
|
-
const temporaryOutput =
|
|
10506
|
+
const source = path10.resolve(sourceEntry.path);
|
|
10507
|
+
const temporaryOutput = path10.join(temp, `${String(index).padStart(2, "0")}.png`);
|
|
10334
10508
|
const refined = await refineAsset({
|
|
10335
10509
|
source,
|
|
10336
10510
|
output: temporaryOutput,
|
|
@@ -10405,7 +10579,7 @@ async function refineFrameSet(options) {
|
|
|
10405
10579
|
);
|
|
10406
10580
|
return {
|
|
10407
10581
|
sources: sourcePaths,
|
|
10408
|
-
outputs: outputs.map((item) =>
|
|
10582
|
+
outputs: outputs.map((item) => path10.resolve(item.path)),
|
|
10409
10583
|
record,
|
|
10410
10584
|
frames,
|
|
10411
10585
|
palette,
|
|
@@ -10416,12 +10590,12 @@ async function refineFrameSet(options) {
|
|
|
10416
10590
|
}
|
|
10417
10591
|
}
|
|
10418
10592
|
async function checkQualityRecord(recordPath) {
|
|
10419
|
-
const record =
|
|
10593
|
+
const record = path10.resolve(recordPath);
|
|
10420
10594
|
const manifest = await readArtifactBundleManifest(record);
|
|
10421
10595
|
const options = parseRecordOptions(manifest, record);
|
|
10422
10596
|
const verification = await verifyArtifactBundle(record);
|
|
10423
|
-
const sources = manifest.sources.map((source2) =>
|
|
10424
|
-
const outputs = manifest.outputs.map((output2) =>
|
|
10597
|
+
const sources = manifest.sources.map((source2) => path10.resolve(path10.dirname(record), source2.path));
|
|
10598
|
+
const outputs = manifest.outputs.map((output2) => path10.resolve(path10.dirname(record), output2.path));
|
|
10425
10599
|
const source = sources[0] ?? "";
|
|
10426
10600
|
const output = outputs[0] ?? "";
|
|
10427
10601
|
const expectedCount = options.frameSet?.count ?? 1;
|
|
@@ -10454,7 +10628,7 @@ async function checkQualityRecord(recordPath) {
|
|
|
10454
10628
|
async function approveQualityRecord(recordPath, approval) {
|
|
10455
10629
|
const reviewer = approval.reviewer.trim();
|
|
10456
10630
|
if (!reviewer) throw new Error("A human reviewer name is required.");
|
|
10457
|
-
const record =
|
|
10631
|
+
const record = path10.resolve(recordPath);
|
|
10458
10632
|
const manifest = await readArtifactBundleManifest(record);
|
|
10459
10633
|
const options = parseRecordOptions(manifest, record);
|
|
10460
10634
|
const verification = await verifyArtifactBundle(record);
|
|
@@ -10475,14 +10649,14 @@ async function approveQualityRecord(recordPath, approval) {
|
|
|
10475
10649
|
`A refinement record must contain exactly ${expectedCount} source(s) and output(s).`
|
|
10476
10650
|
);
|
|
10477
10651
|
}
|
|
10478
|
-
const root =
|
|
10652
|
+
const root = path10.dirname(record);
|
|
10479
10653
|
const outputs = await Promise.all(manifest.outputs.map(async (entry) => {
|
|
10480
|
-
const outputPath =
|
|
10654
|
+
const outputPath = path10.resolve(root, entry.path);
|
|
10481
10655
|
return { path: outputPath, data: await readFile6(outputPath) };
|
|
10482
10656
|
}));
|
|
10483
10657
|
const sources = manifest.sources.map((source) => ({
|
|
10484
10658
|
...source,
|
|
10485
|
-
path:
|
|
10659
|
+
path: path10.resolve(root, source.path)
|
|
10486
10660
|
}));
|
|
10487
10661
|
const approved = {
|
|
10488
10662
|
...options,
|
|
@@ -10514,7 +10688,7 @@ async function qualitySource(spec, lock) {
|
|
|
10514
10688
|
if (spec.generator === "frames") {
|
|
10515
10689
|
return { items: null, reason: "frame-set quality requires ordered downloaded provider outputs" };
|
|
10516
10690
|
}
|
|
10517
|
-
const source =
|
|
10691
|
+
const source = path11.resolve(spec.root, spec.source);
|
|
10518
10692
|
return existsSync6(source) ? { items: [{ role: "source", path: source }] } : { items: null, reason: `declared source is missing: ${spec.source}` };
|
|
10519
10693
|
}
|
|
10520
10694
|
const entry = lock.entries[lockKey(spec.styleId, spec.assetId)];
|
|
@@ -10581,7 +10755,7 @@ async function inspectQualityProfile(spec, lock) {
|
|
|
10581
10755
|
const expectedRevision = spec.quality.fixerRevision ?? PIXEL_ART_FIXER_REVISION;
|
|
10582
10756
|
const expectedTransparency = spec.quality.minTransparency ?? null;
|
|
10583
10757
|
const expectedFrameSet = spec.generator === "frames" ? { fps: spec.quality.fps ?? 12, count: source.items.length, roles: source.items.map((item) => item.role) } : null;
|
|
10584
|
-
const contractChanged = JSON.stringify(check.sources.map((item) =>
|
|
10758
|
+
const contractChanged = JSON.stringify(check.sources.map((item) => path11.resolve(item))) !== JSON.stringify(sourcePaths.map((item) => path11.resolve(item))) || JSON.stringify(check.outputs.map((item) => path11.resolve(item))) !== JSON.stringify(expectedOutputs.map((item) => path11.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);
|
|
10585
10759
|
if (contractChanged) {
|
|
10586
10760
|
return {
|
|
10587
10761
|
key,
|
|
@@ -10745,7 +10919,7 @@ async function requireApprovedQualitySources(specs, lock, assetIds, outputRoles)
|
|
|
10745
10919
|
|
|
10746
10920
|
// src/pipeline/revision.ts
|
|
10747
10921
|
import { existsSync as existsSync7 } from "fs";
|
|
10748
|
-
import
|
|
10922
|
+
import path12 from "path";
|
|
10749
10923
|
async function inspectRevisionReadiness(spec, lock) {
|
|
10750
10924
|
if (spec.mirror) return inspectMirror(spec, lock, /* @__PURE__ */ new Set());
|
|
10751
10925
|
if (spec.character?.kind === "outfit") return inspectOutfit(spec, lock, /* @__PURE__ */ new Set());
|
|
@@ -10843,7 +11017,7 @@ async function inspectRevision(spec, lock, seen) {
|
|
|
10843
11017
|
if (!dependency.ready) {
|
|
10844
11018
|
return {
|
|
10845
11019
|
ready: false,
|
|
10846
|
-
reason: `revision source ${spec.styleId}/${revision.sourceAssetId} is not ready: ${dependency.reason}`
|
|
11020
|
+
reason: `revision source ${revision.sourceStyleId ?? spec.styleId}/${revision.sourceAssetId} is not ready: ${dependency.reason}`
|
|
10847
11021
|
};
|
|
10848
11022
|
}
|
|
10849
11023
|
if (revision.sourceMembers) {
|
|
@@ -10888,8 +11062,8 @@ async function inspectSourceMembers(spec, lock) {
|
|
|
10888
11062
|
if (parent.source) return { ready: true, reason: "declared source set is present" };
|
|
10889
11063
|
const entry = lock.entries[lockKey(parent.styleId, parent.assetId)];
|
|
10890
11064
|
if (!entry) return { ready: false, reason: "revision source is not in the lockfile" };
|
|
10891
|
-
const recorded = entry.outputs.map((_, index) =>
|
|
10892
|
-
const found = members.map((member) =>
|
|
11065
|
+
const recorded = entry.outputs.map((_, index) => path12.resolve(currentEntryOutputPath(entry, parent, index))).sort();
|
|
11066
|
+
const found = members.map((member) => path12.resolve(member.file)).sort();
|
|
10893
11067
|
if (recorded.length !== found.length || recorded.some((file, index) => file !== found[index])) {
|
|
10894
11068
|
return {
|
|
10895
11069
|
ready: false,
|
|
@@ -10938,7 +11112,7 @@ async function inspectParent(spec, lock, seen) {
|
|
|
10938
11112
|
};
|
|
10939
11113
|
}
|
|
10940
11114
|
if (spec.source) {
|
|
10941
|
-
const source =
|
|
11115
|
+
const source = path12.resolve(spec.root, spec.source);
|
|
10942
11116
|
return existsSync7(source) ? { ready: true, reason: "declared source is present" } : { ready: false, reason: `declared source is missing: ${spec.source}` };
|
|
10943
11117
|
}
|
|
10944
11118
|
const entry = lock.entries[key];
|
|
@@ -11017,7 +11191,7 @@ async function buildPlan(specs, lock, opts = {}) {
|
|
|
11017
11191
|
state = "missing";
|
|
11018
11192
|
reason = "--force";
|
|
11019
11193
|
} else if (!entry && spec.source && spec.generator !== "frames") {
|
|
11020
|
-
if (existsSync8(
|
|
11194
|
+
if (existsSync8(path13.resolve(spec.root, spec.source))) {
|
|
11021
11195
|
state = "ok";
|
|
11022
11196
|
reason = `placed from ${spec.source}; not generated`;
|
|
11023
11197
|
} else {
|
|
@@ -11159,13 +11333,13 @@ function summarize(plan) {
|
|
|
11159
11333
|
|
|
11160
11334
|
// src/project.ts
|
|
11161
11335
|
async function openProject(manifestPath = "pixelkiln.manifest.json", opts = {}) {
|
|
11162
|
-
const absolute =
|
|
11163
|
-
const root =
|
|
11336
|
+
const absolute = path14.resolve(manifestPath);
|
|
11337
|
+
const root = path14.dirname(absolute);
|
|
11164
11338
|
if (opts.env !== false) {
|
|
11165
11339
|
loadEnvFiles(root);
|
|
11166
|
-
if (
|
|
11340
|
+
if (path14.resolve(process.cwd()) !== root) loadEnvFiles(process.cwd());
|
|
11167
11341
|
}
|
|
11168
|
-
const lockPath =
|
|
11342
|
+
const lockPath = path14.resolve(opts.lockPath ?? path14.join(root, "pixelkiln.lock.json"));
|
|
11169
11343
|
const loaded = await loadManifest(absolute);
|
|
11170
11344
|
const specs = await resolveSpecs(loaded, { styles: opts.styles ?? [], assets: opts.assets ?? [] });
|
|
11171
11345
|
const lock = await loadLock(lockPath);
|
|
@@ -11232,7 +11406,7 @@ function shouldPersistSourceUrl(value) {
|
|
|
11232
11406
|
}
|
|
11233
11407
|
|
|
11234
11408
|
// src/recipes.ts
|
|
11235
|
-
import
|
|
11409
|
+
import path15 from "path";
|
|
11236
11410
|
import { existsSync as existsSync9 } from "fs";
|
|
11237
11411
|
import { readdir, readFile as readFile7 } from "fs/promises";
|
|
11238
11412
|
import { fileURLToPath } from "url";
|
|
@@ -11243,7 +11417,7 @@ var VERSION_RE = /^\d+\.\d+\.\d+$/;
|
|
|
11243
11417
|
var RECIPE_FILE = "pixelkiln.recipe.json";
|
|
11244
11418
|
var RECIPE_DIR_TOKEN = "{{recipeDir}}";
|
|
11245
11419
|
var PortablePathSchema = z4.string().min(1).refine((value) => {
|
|
11246
|
-
if (value.includes("\\") ||
|
|
11420
|
+
if (value.includes("\\") || path15.posix.isAbsolute(value)) return false;
|
|
11247
11421
|
const parts = value.split("/");
|
|
11248
11422
|
return !parts.includes("") && !parts.includes(".") && !parts.includes("..");
|
|
11249
11423
|
}, "expected a portable relative path without '.', '..', or backslashes");
|
|
@@ -11419,7 +11593,7 @@ function message3(error) {
|
|
|
11419
11593
|
return error instanceof Error ? error.message : String(error);
|
|
11420
11594
|
}
|
|
11421
11595
|
async function readRecipeFile(recipePath, bundled) {
|
|
11422
|
-
const absolute =
|
|
11596
|
+
const absolute = path15.resolve(recipePath);
|
|
11423
11597
|
let raw;
|
|
11424
11598
|
try {
|
|
11425
11599
|
raw = JSON.parse(await readFile7(absolute, "utf8"));
|
|
@@ -11433,13 +11607,13 @@ async function readRecipeFile(recipePath, bundled) {
|
|
|
11433
11607
|
` + parsed.error.issues.map((issue) => ` ${issue.path.join(".") || "$"}: ${issue.message}`).join("\n")
|
|
11434
11608
|
);
|
|
11435
11609
|
}
|
|
11436
|
-
return { recipe: parsed.data, authored: raw, path: absolute, dir:
|
|
11610
|
+
return { recipe: parsed.data, authored: raw, path: absolute, dir: path15.dirname(absolute), bundled };
|
|
11437
11611
|
}
|
|
11438
11612
|
async function walkRecipeFiles(root) {
|
|
11439
11613
|
if (!existsSync9(root)) return [];
|
|
11440
11614
|
const out = [];
|
|
11441
11615
|
for (const entry of await readdir(root, { withFileTypes: true })) {
|
|
11442
|
-
const resolved =
|
|
11616
|
+
const resolved = path15.join(root, entry.name);
|
|
11443
11617
|
if (entry.isDirectory()) out.push(...await walkRecipeFiles(resolved));
|
|
11444
11618
|
else if (entry.isFile() && entry.name === RECIPE_FILE) out.push(resolved);
|
|
11445
11619
|
}
|
|
@@ -11450,7 +11624,7 @@ function bundledRecipeRoot() {
|
|
|
11450
11624
|
}
|
|
11451
11625
|
async function listBundledRecipes(root = bundledRecipeRoot()) {
|
|
11452
11626
|
const recipes = await Promise.all(
|
|
11453
|
-
(await walkRecipeFiles(
|
|
11627
|
+
(await walkRecipeFiles(path15.resolve(root))).map((file) => readRecipeFile(file, true))
|
|
11454
11628
|
);
|
|
11455
11629
|
return recipes.sort(
|
|
11456
11630
|
(a, b) => a.recipe.id.localeCompare(b.recipe.id) || compareVersions(b.recipe.version, a.recipe.version)
|
|
@@ -11473,9 +11647,9 @@ function parseSelector(selector) {
|
|
|
11473
11647
|
return { id: match[1], version: match[2] };
|
|
11474
11648
|
}
|
|
11475
11649
|
async function resolveRecipe(target, bundledRoot = bundledRecipeRoot()) {
|
|
11476
|
-
const local =
|
|
11650
|
+
const local = path15.resolve(target);
|
|
11477
11651
|
if (existsSync9(local)) {
|
|
11478
|
-
const recipePath =
|
|
11652
|
+
const recipePath = path15.basename(local) === RECIPE_FILE ? local : path15.join(local, RECIPE_FILE);
|
|
11479
11653
|
return readRecipeFile(recipePath, false);
|
|
11480
11654
|
}
|
|
11481
11655
|
if (target.startsWith(".") || target.startsWith("/") || target.includes("\\")) {
|
|
@@ -11489,7 +11663,7 @@ async function resolveRecipe(target, bundledRoot = bundledRecipeRoot()) {
|
|
|
11489
11663
|
return matches[0];
|
|
11490
11664
|
}
|
|
11491
11665
|
async function verifyFile(root, file) {
|
|
11492
|
-
const absolute =
|
|
11666
|
+
const absolute = path15.resolve(root, ...file.path.split("/"));
|
|
11493
11667
|
if (!existsSync9(absolute)) {
|
|
11494
11668
|
return { path: file.path, expectedSha256: file.sha256, actualSha256: null, status: "missing" };
|
|
11495
11669
|
}
|
|
@@ -11505,7 +11679,7 @@ async function verifyRecipe(target, options = {}) {
|
|
|
11505
11679
|
const loaded = await resolveRecipe(target, options.bundledRoot);
|
|
11506
11680
|
const actualIntegrity = recipeDigest(loaded.authored);
|
|
11507
11681
|
const files = await Promise.all(loaded.recipe.files.map((file) => verifyFile(loaded.dir, file)));
|
|
11508
|
-
const modelRoot = options.modelRoot ?
|
|
11682
|
+
const modelRoot = options.modelRoot ? path15.resolve(options.modelRoot) : null;
|
|
11509
11683
|
const models = modelRoot ? await Promise.all(loaded.recipe.models.map(async (model) => ({
|
|
11510
11684
|
...await verifyFile(modelRoot, model),
|
|
11511
11685
|
source: model.source,
|
|
@@ -11539,7 +11713,7 @@ async function verifyRecipe(target, options = {}) {
|
|
|
11539
11713
|
};
|
|
11540
11714
|
}
|
|
11541
11715
|
function renderStyle(style, destination, cwd) {
|
|
11542
|
-
const portableDir =
|
|
11716
|
+
const portableDir = path15.relative(cwd, destination).split(path15.sep).join("/") || ".";
|
|
11543
11717
|
const replace = (value) => {
|
|
11544
11718
|
if (typeof value === "string") return value.replaceAll(RECIPE_DIR_TOKEN, portableDir);
|
|
11545
11719
|
if (Array.isArray(value)) return value.map(replace);
|
|
@@ -11551,19 +11725,19 @@ function renderStyle(style, destination, cwd) {
|
|
|
11551
11725
|
return replace(style);
|
|
11552
11726
|
}
|
|
11553
11727
|
async function installRecipe(target, options = {}) {
|
|
11554
|
-
const cwd =
|
|
11728
|
+
const cwd = path15.resolve(options.cwd ?? process.cwd());
|
|
11555
11729
|
const loaded = await resolveRecipe(target, options.bundledRoot);
|
|
11556
11730
|
const verification = await verifyRecipe(loaded.path);
|
|
11557
11731
|
if (!verification.ok) {
|
|
11558
11732
|
throw new Error(`Recipe ${loaded.recipe.id}@${loaded.recipe.version} failed integrity verification.`);
|
|
11559
11733
|
}
|
|
11560
|
-
const destination = options.out ?
|
|
11734
|
+
const destination = options.out ? path15.resolve(cwd, options.out) : path15.join(cwd, "pixelkiln-recipes", ...loaded.recipe.id.split("/"), loaded.recipe.version);
|
|
11561
11735
|
const sources = [
|
|
11562
11736
|
{ path: RECIPE_FILE, source: loaded.path },
|
|
11563
|
-
...loaded.recipe.files.map((file) => ({ path: file.path, source:
|
|
11737
|
+
...loaded.recipe.files.map((file) => ({ path: file.path, source: path15.join(loaded.dir, ...file.path.split("/")) }))
|
|
11564
11738
|
];
|
|
11565
11739
|
const files = await Promise.all(sources.map(async (file) => ({
|
|
11566
|
-
path:
|
|
11740
|
+
path: path15.join(destination, ...file.path.split("/")),
|
|
11567
11741
|
data: await readFile7(file.source)
|
|
11568
11742
|
})));
|
|
11569
11743
|
if (!options.force) {
|
|
@@ -11595,7 +11769,7 @@ async function installRecipe(target, options = {}) {
|
|
|
11595
11769
|
// src/pipeline/audit.ts
|
|
11596
11770
|
import { readFile as readFile8 } from "fs/promises";
|
|
11597
11771
|
import { existsSync as existsSync10 } from "fs";
|
|
11598
|
-
import
|
|
11772
|
+
import path16 from "path";
|
|
11599
11773
|
function paletteDistance(asset, reference) {
|
|
11600
11774
|
if (!asset.length || !reference.length) return 0;
|
|
11601
11775
|
let total = 0;
|
|
@@ -11660,7 +11834,7 @@ async function auditStyle(loaded, specs, styleId, lock) {
|
|
|
11660
11834
|
let referenceFromStyleImages = false;
|
|
11661
11835
|
const refPalettes = [];
|
|
11662
11836
|
for (const img of style.styleImages) {
|
|
11663
|
-
const abs =
|
|
11837
|
+
const abs = path16.resolve(loaded.root, img.path);
|
|
11664
11838
|
if (!existsSync10(abs)) continue;
|
|
11665
11839
|
try {
|
|
11666
11840
|
refPalettes.push(extractPalette(decodePng(await readFile8(abs)), 12));
|
|
@@ -11741,16 +11915,16 @@ function hex(c) {
|
|
|
11741
11915
|
// src/pixellab-utils.ts
|
|
11742
11916
|
import { existsSync as existsSync11 } from "fs";
|
|
11743
11917
|
import { mkdir as mkdir4, readFile as readFile9, writeFile as writeFile4 } from "fs/promises";
|
|
11744
|
-
import
|
|
11918
|
+
import path17 from "path";
|
|
11745
11919
|
var UNZOOM_MIN_SIDE = 256;
|
|
11746
11920
|
var UNZOOM_MAX_AREA = 2048 * 2048;
|
|
11747
11921
|
function defaultUnzoomOut(input) {
|
|
11748
|
-
const ext =
|
|
11922
|
+
const ext = path17.extname(input);
|
|
11749
11923
|
return `${ext ? input.slice(0, -ext.length) : input}.unzoomed.png`;
|
|
11750
11924
|
}
|
|
11751
11925
|
async function unzoomFile(client2, input, opts = {}) {
|
|
11752
|
-
const out =
|
|
11753
|
-
if (
|
|
11926
|
+
const out = path17.resolve(opts.out ?? defaultUnzoomOut(input));
|
|
11927
|
+
if (path17.resolve(input) === out) throw new Error("unzoom will not overwrite its own input; pass a different --out");
|
|
11754
11928
|
if (existsSync11(out) && !opts.force) throw new Error(`${out} already exists; pass --force to replace it`);
|
|
11755
11929
|
const bytes = await readFile9(input);
|
|
11756
11930
|
const metadata = imageMetadata(bytes);
|
|
@@ -11765,7 +11939,7 @@ async function unzoomFile(client2, input, opts = {}) {
|
|
|
11765
11939
|
throw new Error(`${input} is ${width}x${height}; PixelLab's unzoom takes at most 2048x2048 worth of pixels`);
|
|
11766
11940
|
}
|
|
11767
11941
|
const res = await client2.unzoom({ image: { base64: bytes.toString("base64"), format }, quantize: opts.quantize });
|
|
11768
|
-
await mkdir4(
|
|
11942
|
+
await mkdir4(path17.dirname(out), { recursive: true });
|
|
11769
11943
|
await writeFile4(out, res.png);
|
|
11770
11944
|
return { out, original: res.originalSize, unzoomed: res.unzoomedSize, zoomFactor: res.zoomFactor, usage: res.usage };
|
|
11771
11945
|
}
|
|
@@ -11773,7 +11947,7 @@ var FONT_WEIGHTS = ["Bold", "Regular"];
|
|
|
11773
11947
|
var FONT_GLYPH_SIZES = [8, 16, 32, 64];
|
|
11774
11948
|
var FONT_COST_GENERATIONS = 25;
|
|
11775
11949
|
function fontOutputPaths(out) {
|
|
11776
|
-
const base =
|
|
11950
|
+
const base = path17.resolve(out.replace(/\.(ttf|png)$/i, ""));
|
|
11777
11951
|
return { ttf: `${base}.ttf`, atlas: `${base}.png` };
|
|
11778
11952
|
}
|
|
11779
11953
|
async function generateFont(client2, opts) {
|
|
@@ -11807,7 +11981,7 @@ async function generateFont(client2, opts) {
|
|
|
11807
11981
|
client2.download(job.download_ttf_url),
|
|
11808
11982
|
client2.download(job.download_atlas_url)
|
|
11809
11983
|
]);
|
|
11810
|
-
await mkdir4(
|
|
11984
|
+
await mkdir4(path17.dirname(ttf), { recursive: true });
|
|
11811
11985
|
await writeFile4(ttf, ttfBytes);
|
|
11812
11986
|
await writeFile4(atlas, atlasBytes);
|
|
11813
11987
|
return { jobId, ttf, atlas, glyphPx: job.glyph_px ?? null, usage: job.usage };
|
|
@@ -11822,7 +11996,7 @@ async function generateFont(client2, opts) {
|
|
|
11822
11996
|
}
|
|
11823
11997
|
|
|
11824
11998
|
// src/pipeline/quality-regression.ts
|
|
11825
|
-
import
|
|
11999
|
+
import path18 from "path";
|
|
11826
12000
|
import { existsSync as existsSync12 } from "fs";
|
|
11827
12001
|
import { readFile as readFile10 } from "fs/promises";
|
|
11828
12002
|
import { z as z5 } from "zod";
|
|
@@ -11830,7 +12004,7 @@ var SHA256_RE2 = /^[0-9a-f]{64}$/;
|
|
|
11830
12004
|
var HEX_RE = /^#[0-9a-f]{6}$/;
|
|
11831
12005
|
var MAX_REDMEAN_DISTANCE = 765;
|
|
11832
12006
|
var QualityPathSchema = z5.string().min(1).refine(
|
|
11833
|
-
(value) => !
|
|
12007
|
+
(value) => !path18.posix.isAbsolute(value) && !value.includes("\\"),
|
|
11834
12008
|
"expected a portable relative path"
|
|
11835
12009
|
);
|
|
11836
12010
|
var DEFAULT_QUALITY_TOLERANCES = {
|
|
@@ -11977,7 +12151,7 @@ function measureDecodedImageQuality(png, digest2) {
|
|
|
11977
12151
|
};
|
|
11978
12152
|
}
|
|
11979
12153
|
async function measureImageQuality(file) {
|
|
11980
|
-
const absolute =
|
|
12154
|
+
const absolute = path18.resolve(file);
|
|
11981
12155
|
let bytes;
|
|
11982
12156
|
try {
|
|
11983
12157
|
bytes = await readFile10(absolute);
|
|
@@ -11999,11 +12173,11 @@ function resolveQualityInputs(raw, inputsFilePath) {
|
|
|
11999
12173
|
}
|
|
12000
12174
|
const ids = parsed.data.map((entry) => entry.id);
|
|
12001
12175
|
if (new Set(ids).size !== ids.length) throw new Error("--inputs contains a duplicate quality case id.");
|
|
12002
|
-
const root =
|
|
12176
|
+
const root = path18.dirname(path18.resolve(inputsFilePath));
|
|
12003
12177
|
return parsed.data.map((entry) => ({
|
|
12004
12178
|
id: entry.id,
|
|
12005
|
-
file:
|
|
12006
|
-
...entry.record ? { record:
|
|
12179
|
+
file: path18.resolve(root, entry.path),
|
|
12180
|
+
...entry.record ? { record: path18.resolve(root, entry.record) } : {},
|
|
12007
12181
|
tolerances: QualityToleranceSchema.parse({
|
|
12008
12182
|
...DEFAULT_QUALITY_TOLERANCES,
|
|
12009
12183
|
...entry.tolerances
|
|
@@ -12011,7 +12185,7 @@ function resolveQualityInputs(raw, inputsFilePath) {
|
|
|
12011
12185
|
}));
|
|
12012
12186
|
}
|
|
12013
12187
|
function portableRelative2(from, to) {
|
|
12014
|
-
return
|
|
12188
|
+
return path18.relative(from, path18.resolve(to)).split(path18.sep).join("/") || ".";
|
|
12015
12189
|
}
|
|
12016
12190
|
async function verifyRecordForImage(recordPath, imagePath) {
|
|
12017
12191
|
const verification = await verifyArtifactBundle(recordPath);
|
|
@@ -12026,20 +12200,20 @@ async function verifyRecordForImage(recordPath, imagePath) {
|
|
|
12026
12200
|
if (record.kind !== "refine" || options === null || typeof options !== "object" || options.schema !== "pixelkiln-quality") {
|
|
12027
12201
|
throw new Error(`Quality record ${recordPath} is not a PixelKiln refinement record.`);
|
|
12028
12202
|
}
|
|
12029
|
-
const outputs = record.outputs.map((output) =>
|
|
12030
|
-
if (!outputs.includes(
|
|
12203
|
+
const outputs = record.outputs.map((output) => path18.resolve(path18.dirname(recordPath), output.path));
|
|
12204
|
+
if (!outputs.includes(path18.resolve(imagePath))) {
|
|
12031
12205
|
throw new Error(`Quality record ${recordPath} does not own ${imagePath}.`);
|
|
12032
12206
|
}
|
|
12033
12207
|
}
|
|
12034
12208
|
async function snapshotQualityBaseline(inputs, baselinePath, options = {}) {
|
|
12035
12209
|
if (!inputs.length) throw new Error("Cannot snapshot an empty quality baseline.");
|
|
12036
|
-
const absolute =
|
|
12210
|
+
const absolute = path18.resolve(baselinePath);
|
|
12037
12211
|
for (const input of inputs) {
|
|
12038
|
-
if (
|
|
12212
|
+
if (path18.resolve(input.file) === absolute || input.record && path18.resolve(input.record) === absolute) {
|
|
12039
12213
|
throw new Error(`Quality baseline output would overwrite an input: ${absolute}.`);
|
|
12040
12214
|
}
|
|
12041
12215
|
}
|
|
12042
|
-
const root =
|
|
12216
|
+
const root = path18.dirname(absolute);
|
|
12043
12217
|
const cases = [];
|
|
12044
12218
|
for (const input of [...inputs].sort((a, b) => a.id.localeCompare(b.id))) {
|
|
12045
12219
|
const expected = await measureImageQuality(input.file);
|
|
@@ -12079,7 +12253,7 @@ async function snapshotQualityBaseline(inputs, baselinePath, options = {}) {
|
|
|
12079
12253
|
return { baseline, path: absolute, changed: result.changed.length > 0 };
|
|
12080
12254
|
}
|
|
12081
12255
|
async function readQualityBaseline(baselinePath) {
|
|
12082
|
-
const absolute =
|
|
12256
|
+
const absolute = path18.resolve(baselinePath);
|
|
12083
12257
|
let raw;
|
|
12084
12258
|
try {
|
|
12085
12259
|
raw = JSON.parse(await readFile10(absolute, "utf8"));
|
|
@@ -12152,12 +12326,12 @@ function regressionViolations(expected, actual, tolerances) {
|
|
|
12152
12326
|
return { violations, warnings };
|
|
12153
12327
|
}
|
|
12154
12328
|
async function checkQualityBaseline(baselinePath) {
|
|
12155
|
-
const absolute =
|
|
12329
|
+
const absolute = path18.resolve(baselinePath);
|
|
12156
12330
|
const baseline = await readQualityBaseline(absolute);
|
|
12157
|
-
const root =
|
|
12331
|
+
const root = path18.dirname(absolute);
|
|
12158
12332
|
const cases = [];
|
|
12159
12333
|
for (const entry of baseline.cases) {
|
|
12160
|
-
const file =
|
|
12334
|
+
const file = path18.resolve(root, entry.file);
|
|
12161
12335
|
let actual = null;
|
|
12162
12336
|
const violations = [];
|
|
12163
12337
|
const warnings = [];
|
|
@@ -12170,7 +12344,7 @@ async function checkQualityBaseline(baselinePath) {
|
|
|
12170
12344
|
violations.push(message4(error));
|
|
12171
12345
|
}
|
|
12172
12346
|
if (entry.record) {
|
|
12173
|
-
const recordPath =
|
|
12347
|
+
const recordPath = path18.resolve(root, entry.record.path);
|
|
12174
12348
|
try {
|
|
12175
12349
|
const recordHash = await sha256File(recordPath);
|
|
12176
12350
|
if (recordHash !== entry.record.sha256) violations.push("quality record hash changed");
|
|
@@ -12208,7 +12382,7 @@ async function checkQualityBaseline(baselinePath) {
|
|
|
12208
12382
|
// src/pipeline/cache-health.ts
|
|
12209
12383
|
import { existsSync as existsSync16 } from "fs";
|
|
12210
12384
|
import { readFile as readFile13, readdir as readdir2, rm as rm6 } from "fs/promises";
|
|
12211
|
-
import
|
|
12385
|
+
import path21 from "path";
|
|
12212
12386
|
|
|
12213
12387
|
// src/cache.ts
|
|
12214
12388
|
import { mkdir as mkdir5, readFile as readFile11, rename as rename4, rm as rm4, writeFile as writeFile5 } from "fs/promises";
|
|
@@ -12227,10 +12401,10 @@ function isSha256Hash(value) {
|
|
|
12227
12401
|
function parseCache(value) {
|
|
12228
12402
|
return HashCacheSchema.parse(value);
|
|
12229
12403
|
}
|
|
12230
|
-
async function loadCache(
|
|
12231
|
-
if (!existsSync13(
|
|
12404
|
+
async function loadCache(path38) {
|
|
12405
|
+
if (!existsSync13(path38)) return { version: 1, hashes: {} };
|
|
12232
12406
|
try {
|
|
12233
|
-
const parsed = HashCacheSchema.safeParse(JSON.parse(await readFile11(
|
|
12407
|
+
const parsed = HashCacheSchema.safeParse(JSON.parse(await readFile11(path38, "utf8")));
|
|
12234
12408
|
if (!parsed.success) return { version: 1, hashes: {} };
|
|
12235
12409
|
return {
|
|
12236
12410
|
version: 1,
|
|
@@ -12242,18 +12416,18 @@ async function loadCache(path37) {
|
|
|
12242
12416
|
return { version: 1, hashes: {} };
|
|
12243
12417
|
}
|
|
12244
12418
|
}
|
|
12245
|
-
async function saveCache(
|
|
12419
|
+
async function saveCache(path38, cache) {
|
|
12246
12420
|
const sorted = {};
|
|
12247
12421
|
for (const key of Object.keys(cache.hashes).sort()) {
|
|
12248
12422
|
const hash = cache.hashes[key];
|
|
12249
12423
|
if (!isSha256Hash(hash)) throw new Error(`Refusing to cache invalid SHA-256 for ${key}`);
|
|
12250
12424
|
sorted[key] = hash;
|
|
12251
12425
|
}
|
|
12252
|
-
await mkdir5(pathModule.dirname(pathModule.resolve(
|
|
12253
|
-
const tmp = `${
|
|
12426
|
+
await mkdir5(pathModule.dirname(pathModule.resolve(path38)), { recursive: true });
|
|
12427
|
+
const tmp = `${path38}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
12254
12428
|
try {
|
|
12255
12429
|
await writeFile5(tmp, JSON.stringify({ version: 1, hashes: sorted }, null, 2) + "\n");
|
|
12256
|
-
await rename4(tmp,
|
|
12430
|
+
await rename4(tmp, path38);
|
|
12257
12431
|
} finally {
|
|
12258
12432
|
await rm4(tmp, { force: true });
|
|
12259
12433
|
}
|
|
@@ -12278,19 +12452,19 @@ function cachePathFor(lockPath) {
|
|
|
12278
12452
|
|
|
12279
12453
|
// src/pipeline/history.ts
|
|
12280
12454
|
import { existsSync as existsSync15 } from "fs";
|
|
12281
|
-
import
|
|
12455
|
+
import path20 from "path";
|
|
12282
12456
|
|
|
12283
12457
|
// src/pipeline/fetch.ts
|
|
12284
|
-
import { existsSync as existsSync14, readFileSync as
|
|
12458
|
+
import { existsSync as existsSync14, readFileSync as readFileSync4 } from "fs";
|
|
12285
12459
|
import { mkdir as mkdir6, readFile as readFile12, rename as rename5, rm as rm5, writeFile as writeFile6 } from "fs/promises";
|
|
12286
|
-
import
|
|
12460
|
+
import path19 from "path";
|
|
12287
12461
|
async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
12288
12462
|
const log = opts.onProgress ?? (() => {
|
|
12289
12463
|
});
|
|
12290
12464
|
const result = { downloaded: 0, skipped: 0, failed: 0, ...opts.refresh ? { unchanged: 0 } : {} };
|
|
12291
12465
|
const specByKey = new Map(specs.map((s) => [lockKey(s.styleId, s.assetId), s]));
|
|
12292
12466
|
normalizeLockOutputPaths(lock, specs);
|
|
12293
|
-
const cacheDir = opts.cacheDir === false ? null :
|
|
12467
|
+
const cacheDir = opts.cacheDir === false ? null : path19.resolve(opts.cacheDir ?? path19.join(path19.dirname(lockPath), ".pixelkiln", "cache"));
|
|
12294
12468
|
const pending = Object.entries(lock.entries).filter(([key, e]) => {
|
|
12295
12469
|
if (e.provider !== provider.id || !specByKey.has(key)) return false;
|
|
12296
12470
|
if (specByKey.get(key)?.mirror) return false;
|
|
@@ -12301,7 +12475,7 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
12301
12475
|
if (spec && e.specHash === spec.specHash && e.outputs.length && !postprocessCurrent(e, spec)) {
|
|
12302
12476
|
return Boolean(opts.force) || e.outputs.every((output) => {
|
|
12303
12477
|
const file = resolveOutputPath(output.path, spec.root);
|
|
12304
|
-
return existsSync14(file) && sha256(
|
|
12478
|
+
return existsSync14(file) && sha256(readFileSync4(file)) === output.sha256;
|
|
12305
12479
|
});
|
|
12306
12480
|
}
|
|
12307
12481
|
if (!opts.repair) return false;
|
|
@@ -12309,7 +12483,7 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
12309
12483
|
return e.outputs.some((output) => {
|
|
12310
12484
|
const file = resolveOutputPath(output.path, spec.root);
|
|
12311
12485
|
if (!existsSync14(file)) return true;
|
|
12312
|
-
const current = sha256(
|
|
12486
|
+
const current = sha256(readFileSync4(file));
|
|
12313
12487
|
if (current === output.sha256) return false;
|
|
12314
12488
|
return Boolean(opts.force) || (e.supersededOutputs ?? []).some((old) => old.sha256 === current);
|
|
12315
12489
|
});
|
|
@@ -12392,7 +12566,7 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
12392
12566
|
if (!source.url) throw new Error(`no durable source URL to refresh ${source.role ?? "asset"} from`);
|
|
12393
12567
|
fresh = await provider.download(source.url);
|
|
12394
12568
|
if (recorded && sha256(fresh) === recordedRaw && !reapply) {
|
|
12395
|
-
log(` current ${
|
|
12569
|
+
log(` current ${path19.relative(process.cwd(), target)}`);
|
|
12396
12570
|
outputs.push({ ...recorded, path: portableOutputPath(target, spec.root) });
|
|
12397
12571
|
continue;
|
|
12398
12572
|
}
|
|
@@ -12416,9 +12590,9 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
12416
12590
|
}
|
|
12417
12591
|
if (reapply && !fresh) {
|
|
12418
12592
|
if (!recorded.raw) raw = await readFile12(target);
|
|
12419
|
-
log(` reapply ${
|
|
12593
|
+
log(` reapply ${path19.relative(process.cwd(), target)}`);
|
|
12420
12594
|
} else {
|
|
12421
|
-
log(` changed ${
|
|
12595
|
+
log(` changed ${path19.relative(process.cwd(), target)} (upstream)`);
|
|
12422
12596
|
}
|
|
12423
12597
|
} else {
|
|
12424
12598
|
const superseded = (entry.supersededOutputs ?? []).find(
|
|
@@ -12446,7 +12620,7 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
12446
12620
|
}
|
|
12447
12621
|
}
|
|
12448
12622
|
log(
|
|
12449
|
-
` replace ${
|
|
12623
|
+
` replace ${path19.relative(process.cwd(), target)}` + (opts.force ? " (--force)" : " (previous tracked generation)")
|
|
12450
12624
|
);
|
|
12451
12625
|
}
|
|
12452
12626
|
}
|
|
@@ -12455,7 +12629,7 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
12455
12629
|
} else if (!raw && recorded && cacheDir) {
|
|
12456
12630
|
if (!reapply) final = await readCachedMedia(cacheDir, recorded.sha256, expectedMediaType);
|
|
12457
12631
|
if (!final && recordedRaw) raw = await readCachedMedia(cacheDir, recordedRaw, expectedMediaType);
|
|
12458
|
-
if (final || raw) log(` cached ${
|
|
12632
|
+
if (final || raw) log(` cached ${path19.relative(process.cwd(), target)}`);
|
|
12459
12633
|
}
|
|
12460
12634
|
if (!final && !raw) {
|
|
12461
12635
|
if (!source.url) {
|
|
@@ -12482,11 +12656,11 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
12482
12656
|
buf = processed.bytes;
|
|
12483
12657
|
if (processed.changed) {
|
|
12484
12658
|
rawHash = sha256(raw);
|
|
12485
|
-
log(` snapped ${
|
|
12659
|
+
log(` snapped ${path19.relative(process.cwd(), target)} to ${wanted.palette.colors.length} colours`);
|
|
12486
12660
|
}
|
|
12487
12661
|
}
|
|
12488
12662
|
if (cacheDir) await cacheMedia(cacheDir, buf, mediaType);
|
|
12489
|
-
await mkdir6(
|
|
12663
|
+
await mkdir6(path19.dirname(target), { recursive: true });
|
|
12490
12664
|
const tmp = `${target}.pixelkiln.tmp`;
|
|
12491
12665
|
await writeFile6(tmp, buf);
|
|
12492
12666
|
outputs.push({
|
|
@@ -12500,7 +12674,7 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
12500
12674
|
await saveLock(lockPath, lock);
|
|
12501
12675
|
await rename5(tmp, target);
|
|
12502
12676
|
wrote = true;
|
|
12503
|
-
log(` wrote ${
|
|
12677
|
+
log(` wrote ${path19.relative(process.cwd(), target)}`);
|
|
12504
12678
|
}
|
|
12505
12679
|
if (opts.refresh && !wrote) {
|
|
12506
12680
|
if (rotated) {
|
|
@@ -12544,7 +12718,7 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
12544
12718
|
return result;
|
|
12545
12719
|
}
|
|
12546
12720
|
async function readCachedMedia(cacheDir, hash, mediaType) {
|
|
12547
|
-
const file =
|
|
12721
|
+
const file = path19.join(cacheDir, cacheFileName(hash, mediaType));
|
|
12548
12722
|
if (!existsSync14(file)) return null;
|
|
12549
12723
|
try {
|
|
12550
12724
|
const buf = await readFile12(file);
|
|
@@ -12558,7 +12732,7 @@ async function readCachedMedia(cacheDir, hash, mediaType) {
|
|
|
12558
12732
|
async function cacheMedia(cacheDir, buf, mediaType, knownHash) {
|
|
12559
12733
|
const hash = knownHash ?? sha256(buf);
|
|
12560
12734
|
validateMedia(buf, mediaType);
|
|
12561
|
-
const file =
|
|
12735
|
+
const file = path19.join(cacheDir, cacheFileName(hash, mediaType));
|
|
12562
12736
|
if (await readCachedMedia(cacheDir, hash, mediaType)) return;
|
|
12563
12737
|
await mkdir6(cacheDir, { recursive: true });
|
|
12564
12738
|
const tmp = `${file}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
@@ -12688,7 +12862,7 @@ async function revertGeneration(provider, spec, lock, lockPath, opts) {
|
|
|
12688
12862
|
if (!existsSync15(file)) continue;
|
|
12689
12863
|
if (await sha256File(file) !== entry.outputs[i].sha256 && !opts.force) {
|
|
12690
12864
|
throw new OverwriteRefusedError(
|
|
12691
|
-
`${
|
|
12865
|
+
`${path20.relative(process.cwd(), file)} was changed after download; keep it with pixelkiln edit, or pass --force to replace it`,
|
|
12692
12866
|
[file]
|
|
12693
12867
|
);
|
|
12694
12868
|
}
|
|
@@ -12741,10 +12915,10 @@ async function revertGeneration(provider, spec, lock, lockPath, opts) {
|
|
|
12741
12915
|
// src/pipeline/cache-health.ts
|
|
12742
12916
|
async function inspectCaches(lock, lockPath, options = {}) {
|
|
12743
12917
|
if (options.prune && !existsSync16(lockPath)) {
|
|
12744
|
-
throw new Error(`Refusing to prune without an existing lockfile at ${
|
|
12918
|
+
throw new Error(`Refusing to prune without an existing lockfile at ${path21.resolve(lockPath)}`);
|
|
12745
12919
|
}
|
|
12746
|
-
const contentDir =
|
|
12747
|
-
const remotePath =
|
|
12920
|
+
const contentDir = path21.resolve(path21.dirname(lockPath), ".pixelkiln", "cache");
|
|
12921
|
+
const remotePath = path21.resolve(cachePathFor(lockPath));
|
|
12748
12922
|
const referenced = referencedHashes(lock);
|
|
12749
12923
|
let content = await inspectContentCache(contentDir, referenced);
|
|
12750
12924
|
let remoteHashes = await inspectRemoteHashCache(remotePath);
|
|
@@ -12756,7 +12930,7 @@ async function inspectCaches(lock, lockPath, options = {}) {
|
|
|
12756
12930
|
]);
|
|
12757
12931
|
for (const name of names) {
|
|
12758
12932
|
try {
|
|
12759
|
-
await rm6(
|
|
12933
|
+
await rm6(path21.join(contentDir, name), { force: true });
|
|
12760
12934
|
removed.contentFiles++;
|
|
12761
12935
|
} catch {
|
|
12762
12936
|
}
|
|
@@ -12804,7 +12978,7 @@ async function inspectContentCache(contentDir, referenced) {
|
|
|
12804
12978
|
continue;
|
|
12805
12979
|
}
|
|
12806
12980
|
report.files++;
|
|
12807
|
-
const file =
|
|
12981
|
+
const file = path21.join(contentDir, entry.name);
|
|
12808
12982
|
const mediaType = mediaTypeFromExtension(entry.name);
|
|
12809
12983
|
const expected = mediaType ? entry.name.slice(0, -4) : "";
|
|
12810
12984
|
let bytes;
|
|
@@ -12871,20 +13045,20 @@ async function inspectRemoteHashCache(remotePath) {
|
|
|
12871
13045
|
}
|
|
12872
13046
|
|
|
12873
13047
|
// src/pipeline/pack.ts
|
|
12874
|
-
import { readFileSync as
|
|
12875
|
-
import
|
|
13048
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
13049
|
+
import path22 from "path";
|
|
12876
13050
|
var DEFAULT_FRAME_SET_FPS = 12;
|
|
12877
13051
|
function resolvePackInputs(raw, inputsFilePath) {
|
|
12878
13052
|
if (!Array.isArray(raw) || !raw.length) {
|
|
12879
13053
|
throw new Error("--inputs must be a non-empty JSON array of { id, path }");
|
|
12880
13054
|
}
|
|
12881
|
-
const baseDir =
|
|
13055
|
+
const baseDir = path22.dirname(path22.resolve(inputsFilePath));
|
|
12882
13056
|
return raw.map((entry, i) => {
|
|
12883
13057
|
const e = entry;
|
|
12884
13058
|
if (typeof e.id !== "string" || typeof e.path !== "string") {
|
|
12885
13059
|
throw new Error(`--inputs[${i}] needs string "id" and "path"`);
|
|
12886
13060
|
}
|
|
12887
|
-
return { id: e.id, path:
|
|
13061
|
+
return { id: e.id, path: path22.resolve(baseDir, e.path) };
|
|
12888
13062
|
});
|
|
12889
13063
|
}
|
|
12890
13064
|
function packSprites(inputs, options = {}) {
|
|
@@ -12905,7 +13079,7 @@ function packSprites(inputs, options = {}) {
|
|
|
12905
13079
|
for (const input of ordered) {
|
|
12906
13080
|
let sourceHash = null;
|
|
12907
13081
|
try {
|
|
12908
|
-
const bytes =
|
|
13082
|
+
const bytes = readFileSync5(input.path);
|
|
12909
13083
|
sourceHash = sha256(bytes);
|
|
12910
13084
|
const png = decodePng(bytes);
|
|
12911
13085
|
sprites.push({ id: input.id, width: png.width, height: png.height, pixels: png.pixels });
|
|
@@ -12968,7 +13142,7 @@ function packStyle(lock, styleId, manifestDir, options = {}) {
|
|
|
12968
13142
|
}
|
|
12969
13143
|
const inputs2 = Object.entries(options.sourceOverrides).map(([id, source]) => ({
|
|
12970
13144
|
id,
|
|
12971
|
-
path:
|
|
13145
|
+
path: path22.resolve(source)
|
|
12972
13146
|
}));
|
|
12973
13147
|
if (!inputs2.length) throw new Error(`No approved quality outputs for style "${styleId}".`);
|
|
12974
13148
|
const packed2 = packSprites(inputs2, options);
|
|
@@ -12992,7 +13166,7 @@ function packStyle(lock, styleId, manifestDir, options = {}) {
|
|
|
12992
13166
|
let outputs = resolveEntryOutputs(entry, id, manifestDir);
|
|
12993
13167
|
const source = sources[id];
|
|
12994
13168
|
if (source && outputs.length <= 1) {
|
|
12995
|
-
inputs.push({ id, path:
|
|
13169
|
+
inputs.push({ id, path: path22.resolve(manifestDir, source) });
|
|
12996
13170
|
continue;
|
|
12997
13171
|
}
|
|
12998
13172
|
if (source && sourceIsStem(source, entry, manifestDir)) {
|
|
@@ -13026,7 +13200,7 @@ function packStyle(lock, styleId, manifestDir, options = {}) {
|
|
|
13026
13200
|
}
|
|
13027
13201
|
}
|
|
13028
13202
|
for (const [id, source] of Object.entries(sources)) {
|
|
13029
|
-
if (!locked.has(id)) inputs.push({ id, path:
|
|
13203
|
+
if (!locked.has(id)) inputs.push({ id, path: path22.resolve(manifestDir, source) });
|
|
13030
13204
|
}
|
|
13031
13205
|
if (!inputs.length) {
|
|
13032
13206
|
throw new Error(`No readable sprites for style "${styleId}"; ${noOutput.length} skipped.`);
|
|
@@ -13063,7 +13237,7 @@ function mountSprites(placements, options) {
|
|
|
13063
13237
|
for (const p of placements) {
|
|
13064
13238
|
let sourceHash = null;
|
|
13065
13239
|
try {
|
|
13066
|
-
const bytes =
|
|
13240
|
+
const bytes = readFileSync5(p.path);
|
|
13067
13241
|
sourceHash = sha256(bytes);
|
|
13068
13242
|
const png = decodePng(bytes);
|
|
13069
13243
|
if (png.width > cellWidth || png.height > cellHeight) {
|
|
@@ -13134,7 +13308,7 @@ function mountStyle(lock, styleId, manifestDir, mount, cells, sources = {}, outp
|
|
|
13134
13308
|
const source = sources[id];
|
|
13135
13309
|
const entry = lock.entries[`${styleId}/${id}`];
|
|
13136
13310
|
if (source && !(entry && sourceIsStem(source, entry, manifestDir))) {
|
|
13137
|
-
placements.push({ id, path:
|
|
13311
|
+
placements.push({ id, path: path22.resolve(manifestDir, source), cell });
|
|
13138
13312
|
continue;
|
|
13139
13313
|
}
|
|
13140
13314
|
if (source && entry) {
|
|
@@ -13167,7 +13341,7 @@ function mountStyle(lock, styleId, manifestDir, mount, cells, sources = {}, outp
|
|
|
13167
13341
|
`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}`
|
|
13168
13342
|
);
|
|
13169
13343
|
}
|
|
13170
|
-
const basePng = mount.base ?
|
|
13344
|
+
const basePng = mount.base ? readFileSync5(path22.resolve(manifestDir, mount.base)) : void 0;
|
|
13171
13345
|
const mounted = mountSprites(placements, {
|
|
13172
13346
|
cellWidth: mount.cellWidth,
|
|
13173
13347
|
cellHeight: mount.cellHeight,
|
|
@@ -13177,7 +13351,7 @@ function mountStyle(lock, styleId, manifestDir, mount, cells, sources = {}, outp
|
|
|
13177
13351
|
...mounted,
|
|
13178
13352
|
sources: mount.base ? [{
|
|
13179
13353
|
id: "$base",
|
|
13180
|
-
path:
|
|
13354
|
+
path: path22.resolve(manifestDir, mount.base),
|
|
13181
13355
|
sha256: sha256(basePng),
|
|
13182
13356
|
included: true
|
|
13183
13357
|
}, ...mounted.sources] : mounted.sources,
|
|
@@ -13754,6 +13928,7 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
|
|
|
13754
13928
|
revision: spec.revision ? {
|
|
13755
13929
|
mode: spec.revision.mode,
|
|
13756
13930
|
sourceAssetId: spec.revision.sourceAssetId,
|
|
13931
|
+
...spec.revision.sourceStyleId ? { sourceStyleId: spec.revision.sourceStyleId } : {},
|
|
13757
13932
|
sourceSha256: spec.revision.sourceSha256,
|
|
13758
13933
|
...spec.revision.maskSha256 ? { maskSha256: spec.revision.maskSha256 } : {},
|
|
13759
13934
|
...spec.revision.strength == null ? {} : { strength: spec.revision.strength },
|
|
@@ -13989,7 +14164,7 @@ async function poll(provider, lock, lockPath, opts = {}) {
|
|
|
13989
14164
|
// src/pipeline/doctor.ts
|
|
13990
14165
|
import { constants, existsSync as existsSync17 } from "fs";
|
|
13991
14166
|
import { access, stat as stat2 } from "fs/promises";
|
|
13992
|
-
import
|
|
14167
|
+
import path23 from "path";
|
|
13993
14168
|
async function doctor(loaded, specs, lock, lockPath, opts = {}) {
|
|
13994
14169
|
const checks = [];
|
|
13995
14170
|
const add = (id, level, message6) => checks.push({ id, level, message: message6 });
|
|
@@ -14000,8 +14175,8 @@ async function doctor(loaded, specs, lock, lockPath, opts = {}) {
|
|
|
14000
14175
|
);
|
|
14001
14176
|
add("lock", "ok", `${Object.keys(lock.entries).length} valid v2 lock entr(ies)`);
|
|
14002
14177
|
const dirs = [...new Set(specs.flatMap((spec) => [
|
|
14003
|
-
|
|
14004
|
-
...spec.quality ? [
|
|
14178
|
+
path23.dirname(spec.outFile),
|
|
14179
|
+
...spec.quality ? [path23.dirname(spec.quality.outFile)] : []
|
|
14005
14180
|
]))];
|
|
14006
14181
|
const unwritable = [];
|
|
14007
14182
|
for (const dir of dirs) {
|
|
@@ -14024,7 +14199,7 @@ async function doctor(loaded, specs, lock, lockPath, opts = {}) {
|
|
|
14024
14199
|
for (let index = 0; index < entry.outputs.length; index++) {
|
|
14025
14200
|
const output = entry.outputs[index];
|
|
14026
14201
|
const spec = specByKey.get(key);
|
|
14027
|
-
const absolute = spec ? currentEntryOutputPath(entry, spec, index) : resolveOutputPath(output.path,
|
|
14202
|
+
const absolute = spec ? currentEntryOutputPath(entry, spec, index) : resolveOutputPath(output.path, path23.dirname(lockPath));
|
|
14028
14203
|
const owner = outputOwners.get(absolute);
|
|
14029
14204
|
if (owner && owner !== key) duplicateOutputs.push(`${output.path} (${owner}, ${key})`);
|
|
14030
14205
|
outputOwners.set(absolute, key);
|
|
@@ -14039,7 +14214,7 @@ async function doctor(loaded, specs, lock, lockPath, opts = {}) {
|
|
|
14039
14214
|
} else {
|
|
14040
14215
|
add("lock-outputs", "ok", `${outputOwners.size} recorded output path(s) are uniquely owned`);
|
|
14041
14216
|
}
|
|
14042
|
-
const cacheDir =
|
|
14217
|
+
const cacheDir = path23.join(path23.dirname(lockPath), ".pixelkiln", "cache");
|
|
14043
14218
|
const stranded = [];
|
|
14044
14219
|
const invalidState = [];
|
|
14045
14220
|
const staleMapUrls = [];
|
|
@@ -14055,7 +14230,7 @@ async function doctor(loaded, specs, lock, lockPath, opts = {}) {
|
|
|
14055
14230
|
const hasSource = Boolean(entry.sourceUrl || entry.sourceUrls?.length);
|
|
14056
14231
|
let hasCache = false;
|
|
14057
14232
|
for (const output of entry.outputs) {
|
|
14058
|
-
const cached =
|
|
14233
|
+
const cached = path23.join(
|
|
14059
14234
|
cacheDir,
|
|
14060
14235
|
cacheFileName(output.sha256, output.mediaType ?? MediaType.PNG)
|
|
14061
14236
|
);
|
|
@@ -14155,7 +14330,7 @@ async function nearestExistingDirectory(start) {
|
|
|
14155
14330
|
if ((await stat2(current)).isDirectory()) return current;
|
|
14156
14331
|
} catch {
|
|
14157
14332
|
}
|
|
14158
|
-
const parent =
|
|
14333
|
+
const parent = path23.dirname(current);
|
|
14159
14334
|
if (parent === current) return current;
|
|
14160
14335
|
current = parent;
|
|
14161
14336
|
}
|
|
@@ -14164,7 +14339,7 @@ async function nearestExistingDirectory(start) {
|
|
|
14164
14339
|
// src/pipeline/adopt.ts
|
|
14165
14340
|
import { existsSync as existsSync18 } from "fs";
|
|
14166
14341
|
import { readFile as readFile14, writeFile as writeFile7 } from "fs/promises";
|
|
14167
|
-
import
|
|
14342
|
+
import path24 from "path";
|
|
14168
14343
|
async function adopt(provider, specs, lock, lockPath, opts = {}) {
|
|
14169
14344
|
const log = opts.onProgress ?? (() => {
|
|
14170
14345
|
});
|
|
@@ -14303,7 +14478,7 @@ import { readFile as readFile17 } from "fs/promises";
|
|
|
14303
14478
|
// src/pipeline/init.ts
|
|
14304
14479
|
import { readdir as readdir3, readFile as readFile15, writeFile as writeFile8 } from "fs/promises";
|
|
14305
14480
|
import { existsSync as existsSync19 } from "fs";
|
|
14306
|
-
import
|
|
14481
|
+
import path25 from "path";
|
|
14307
14482
|
function pngSize(buf) {
|
|
14308
14483
|
if (buf.length < 24) return null;
|
|
14309
14484
|
const isPng = buf.readUInt32BE(0) === 2303741511 && buf.readUInt32BE(4) === 218765834;
|
|
@@ -14317,7 +14492,7 @@ function slugify(value) {
|
|
|
14317
14492
|
async function walk(dir, exclude) {
|
|
14318
14493
|
const out = [];
|
|
14319
14494
|
for (const entry of await readdir3(dir, { withFileTypes: true })) {
|
|
14320
|
-
const full =
|
|
14495
|
+
const full = path25.join(dir, entry.name);
|
|
14321
14496
|
if (exclude.some((x) => full.includes(x))) continue;
|
|
14322
14497
|
if (entry.isDirectory()) out.push(...await walk(full, exclude));
|
|
14323
14498
|
else if (entry.name.toLowerCase().endsWith(".png")) out.push(full);
|
|
@@ -14336,9 +14511,9 @@ async function scanAssets(root, opts = {}) {
|
|
|
14336
14511
|
skipped.push(`${file} (not a readable PNG)`);
|
|
14337
14512
|
continue;
|
|
14338
14513
|
}
|
|
14339
|
-
const rel =
|
|
14340
|
-
const category =
|
|
14341
|
-
let id = slugify(
|
|
14514
|
+
const rel = path25.relative(root, file);
|
|
14515
|
+
const category = path25.dirname(rel) === "." ? "" : path25.dirname(rel);
|
|
14516
|
+
let id = slugify(path25.basename(rel));
|
|
14342
14517
|
if (seen.has(id)) {
|
|
14343
14518
|
const n = seen.get(id) + 1;
|
|
14344
14519
|
seen.set(id, n);
|
|
@@ -15061,15 +15236,15 @@ async function runPicker(provider, lock, lockPath, opts = {}) {
|
|
|
15061
15236
|
}
|
|
15062
15237
|
|
|
15063
15238
|
// src/gallery/snapshot.ts
|
|
15064
|
-
import { createHash as
|
|
15239
|
+
import { createHash as createHash5 } from "crypto";
|
|
15065
15240
|
import { existsSync as existsSync26 } from "fs";
|
|
15066
15241
|
import { readFile as readFile24, stat as stat3 } from "fs/promises";
|
|
15067
|
-
import
|
|
15242
|
+
import path33 from "path";
|
|
15068
15243
|
|
|
15069
15244
|
// src/workspace.ts
|
|
15070
15245
|
import { mkdir as mkdir7, readFile as readFile18, rename as rename6, rm as rm7, writeFile as writeFile9 } from "fs/promises";
|
|
15071
15246
|
import { existsSync as existsSync21 } from "fs";
|
|
15072
|
-
import
|
|
15247
|
+
import path26 from "path";
|
|
15073
15248
|
import { z as z7 } from "zod";
|
|
15074
15249
|
var WorkspaceProjectSchema = z7.object({
|
|
15075
15250
|
id: z7.string().min(1),
|
|
@@ -15111,7 +15286,7 @@ async function saveWorkspace(workspacePath, ws) {
|
|
|
15111
15286
|
version: 1,
|
|
15112
15287
|
projects: [...ws.projects].sort((a, b) => a.id.localeCompare(b.id))
|
|
15113
15288
|
};
|
|
15114
|
-
await mkdir7(
|
|
15289
|
+
await mkdir7(path26.dirname(path26.resolve(workspacePath)), { recursive: true });
|
|
15115
15290
|
const tmp = `${workspacePath}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
15116
15291
|
try {
|
|
15117
15292
|
await writeFile9(tmp, JSON.stringify(sorted, null, 2) + "\n");
|
|
@@ -15121,12 +15296,12 @@ async function saveWorkspace(workspacePath, ws) {
|
|
|
15121
15296
|
}
|
|
15122
15297
|
}
|
|
15123
15298
|
function toPortablePath(dir, absolute) {
|
|
15124
|
-
return
|
|
15299
|
+
return path26.relative(dir, absolute).split(path26.sep).join("/");
|
|
15125
15300
|
}
|
|
15126
15301
|
function resolveProject(dir, project) {
|
|
15127
15302
|
return {
|
|
15128
|
-
manifestPath:
|
|
15129
|
-
lockPath:
|
|
15303
|
+
manifestPath: path26.resolve(dir, project.manifest.split("/").join(path26.sep)),
|
|
15304
|
+
lockPath: path26.resolve(dir, project.lock.split("/").join(path26.sep))
|
|
15130
15305
|
};
|
|
15131
15306
|
}
|
|
15132
15307
|
function validateWorkspace(ws, dir) {
|
|
@@ -15139,7 +15314,7 @@ function validateWorkspace(ws, dir) {
|
|
|
15139
15314
|
const { manifestPath, lockPath } = resolveProject(dir, project);
|
|
15140
15315
|
lockOwners.set(lockPath, [...lockOwners.get(lockPath) ?? [], project.id]);
|
|
15141
15316
|
manifestOwners.set(manifestPath, [...manifestOwners.get(manifestPath) ?? [], project.id]);
|
|
15142
|
-
if (
|
|
15317
|
+
if (path26.isAbsolute(project.manifest) || path26.isAbsolute(project.lock)) {
|
|
15143
15318
|
diagnostics.push({
|
|
15144
15319
|
id: "absolute-path",
|
|
15145
15320
|
level: "warning",
|
|
@@ -15201,15 +15376,15 @@ function validateWorkspace(ws, dir) {
|
|
|
15201
15376
|
|
|
15202
15377
|
// src/gallery/edit.ts
|
|
15203
15378
|
import { readFile as readFile23, rename as rename9, writeFile as writeFile15 } from "fs/promises";
|
|
15204
|
-
import
|
|
15379
|
+
import path32 from "path";
|
|
15205
15380
|
import { z as z13 } from "zod";
|
|
15206
15381
|
|
|
15207
15382
|
// src/manifest-edit.ts
|
|
15208
15383
|
import { readFile as readFile19, rename as rename7, rm as rm8, writeFile as writeFile10 } from "fs/promises";
|
|
15209
|
-
import
|
|
15384
|
+
import path27 from "path";
|
|
15210
15385
|
import { z as z8 } from "zod";
|
|
15211
15386
|
var HexSha = z8.string().regex(/^[0-9a-f]{64}$/);
|
|
15212
|
-
var ProjectFileSchema = z8.string().min(1).refine((file) => !
|
|
15387
|
+
var ProjectFileSchema = z8.string().min(1).refine((file) => !path27.isAbsolute(file) && !file.split(/[\\/]/).includes(".."), "must be a path inside the project");
|
|
15213
15388
|
var AssetPatchSchema = z8.object({
|
|
15214
15389
|
prompt: z8.string().optional(),
|
|
15215
15390
|
/** Per-style override for the style being viewed; null removes it. */
|
|
@@ -15628,7 +15803,7 @@ function applyEdit(raw, edit) {
|
|
|
15628
15803
|
}
|
|
15629
15804
|
}
|
|
15630
15805
|
async function withCandidate(manifestPath, edit, use, unchanged) {
|
|
15631
|
-
const absolute =
|
|
15806
|
+
const absolute = path27.resolve(manifestPath);
|
|
15632
15807
|
const original = await readFile19(absolute, "utf8");
|
|
15633
15808
|
if (sha256(original) !== edit.expectedSha256) throw new ManifestDriftError(absolute);
|
|
15634
15809
|
let raw;
|
|
@@ -15642,9 +15817,9 @@ async function withCandidate(manifestPath, edit, use, unchanged) {
|
|
|
15642
15817
|
applyEdit(raw, edit);
|
|
15643
15818
|
const next = serializeLike(original, raw);
|
|
15644
15819
|
if (next === original) return unchanged(absolute, original);
|
|
15645
|
-
const tmp =
|
|
15646
|
-
|
|
15647
|
-
`.${
|
|
15820
|
+
const tmp = path27.join(
|
|
15821
|
+
path27.dirname(absolute),
|
|
15822
|
+
`.${path27.basename(absolute)}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`
|
|
15648
15823
|
);
|
|
15649
15824
|
try {
|
|
15650
15825
|
await writeFile10(tmp, next);
|
|
@@ -15675,16 +15850,16 @@ async function applyManifestEdit(manifestPath, edit) {
|
|
|
15675
15850
|
// src/pipeline/hand-edit.ts
|
|
15676
15851
|
import { existsSync as existsSync22 } from "fs";
|
|
15677
15852
|
import { copyFile, mkdir as mkdir8, readFile as readFile20, rename as rename8, rm as rm9, writeFile as writeFile11 } from "fs/promises";
|
|
15678
|
-
import
|
|
15853
|
+
import path28 from "path";
|
|
15679
15854
|
import { z as z9 } from "zod";
|
|
15680
15855
|
var HAND_EDIT_DIR = "edits";
|
|
15681
15856
|
function handEditPath(loaded, spec) {
|
|
15682
15857
|
const style = loaded.manifest.styles[spec.styleId];
|
|
15683
|
-
const outDir =
|
|
15684
|
-
const relative =
|
|
15685
|
-
const inside = relative && !relative.startsWith("..") && !
|
|
15686
|
-
const file = inside ? relative :
|
|
15687
|
-
return
|
|
15858
|
+
const outDir = path28.resolve(loaded.root, style?.outDir ?? ".");
|
|
15859
|
+
const relative = path28.relative(outDir, spec.outFile);
|
|
15860
|
+
const inside = relative && !relative.startsWith("..") && !path28.isAbsolute(relative);
|
|
15861
|
+
const file = inside ? relative : path28.basename(spec.outFile);
|
|
15862
|
+
return path28.join(outDir, HAND_EDIT_DIR, file.replace(/\.gif$/i, ".png"));
|
|
15688
15863
|
}
|
|
15689
15864
|
function handEditBases(spec, lock) {
|
|
15690
15865
|
const entry = lock.entries[lockKey(spec.styleId, spec.assetId)];
|
|
@@ -15730,7 +15905,7 @@ async function startHandEdit(loaded, lock, spec, opts = {}) {
|
|
|
15730
15905
|
`${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`
|
|
15731
15906
|
);
|
|
15732
15907
|
}
|
|
15733
|
-
const existing = spec.source ?
|
|
15908
|
+
const existing = spec.source ? path28.resolve(loaded.root, spec.source) : null;
|
|
15734
15909
|
const editPath = existing ?? handEditPath(loaded, spec);
|
|
15735
15910
|
const members = handEditMembers(loaded, spec, lock, editPath);
|
|
15736
15911
|
let created = false;
|
|
@@ -15741,7 +15916,7 @@ async function startHandEdit(loaded, lock, spec, opts = {}) {
|
|
|
15741
15916
|
`${spec.styleId}/${spec.assetId} has no generated PNG on disk to start from; generate or restore it first`
|
|
15742
15917
|
);
|
|
15743
15918
|
}
|
|
15744
|
-
await mkdir8(
|
|
15919
|
+
await mkdir8(path28.dirname(member.path), { recursive: true });
|
|
15745
15920
|
await copyFile(member.base, member.path);
|
|
15746
15921
|
created = true;
|
|
15747
15922
|
}
|
|
@@ -15879,7 +16054,7 @@ async function saveHandEdit(loaded, lock, spec, input) {
|
|
|
15879
16054
|
editor: input.editor,
|
|
15880
16055
|
protocol: input.protocol,
|
|
15881
16056
|
savedAt: (input.now ?? /* @__PURE__ */ new Date()).toISOString(),
|
|
15882
|
-
project: projectPath ?
|
|
16057
|
+
project: projectPath ? path28.basename(projectPath) : null,
|
|
15883
16058
|
outputs: incoming.map((frame, index) => ({
|
|
15884
16059
|
role: frame.role,
|
|
15885
16060
|
basedOn: basedOnHash(entry, index),
|
|
@@ -15923,7 +16098,7 @@ function openInEditor(file, editor = process.env.PIXELKILN_EDITOR) {
|
|
|
15923
16098
|
// src/gallery/upload.ts
|
|
15924
16099
|
import { existsSync as existsSync23 } from "fs";
|
|
15925
16100
|
import { mkdir as mkdir9, writeFile as writeFile12 } from "fs/promises";
|
|
15926
|
-
import
|
|
16101
|
+
import path29 from "path";
|
|
15927
16102
|
import { z as z10 } from "zod";
|
|
15928
16103
|
var MAX_UPLOAD_BYTES = 8 * 1024 * 1024;
|
|
15929
16104
|
var UploadImageSchema = z10.object({
|
|
@@ -15936,12 +16111,12 @@ var UploadImageSchema = z10.object({
|
|
|
15936
16111
|
overwrite: z10.boolean().optional()
|
|
15937
16112
|
}).strict();
|
|
15938
16113
|
async function saveProjectImage(root, request) {
|
|
15939
|
-
const file =
|
|
15940
|
-
const relative =
|
|
15941
|
-
if (relative.startsWith("..") ||
|
|
16114
|
+
const file = path29.resolve(root, request.path);
|
|
16115
|
+
const relative = path29.relative(root, file);
|
|
16116
|
+
if (relative.startsWith("..") || path29.isAbsolute(relative)) {
|
|
15942
16117
|
throw new ManifestEditError("an upload must stay inside the project");
|
|
15943
16118
|
}
|
|
15944
|
-
const extension =
|
|
16119
|
+
const extension = path29.extname(file).toLowerCase();
|
|
15945
16120
|
const expected = extension === ".png" ? "png" : extension === ".jpg" || extension === ".jpeg" ? "jpeg" : null;
|
|
15946
16121
|
if (!expected) throw new ManifestEditError("an upload must be a .png, .jpg, or .jpeg path");
|
|
15947
16122
|
const bytes = Buffer.from(request.base64, "base64");
|
|
@@ -15954,17 +16129,17 @@ async function saveProjectImage(root, request) {
|
|
|
15954
16129
|
throw new ManifestEditError(`the upload is a ${metadata.format.toUpperCase()}; name it ${metadata.format === "png" ? ".png" : ".jpg"}`);
|
|
15955
16130
|
}
|
|
15956
16131
|
if (existsSync23(file) && !request.overwrite) {
|
|
15957
|
-
throw new ManifestEditError(`${relative.split(
|
|
16132
|
+
throw new ManifestEditError(`${relative.split(path29.sep).join("/")} already exists; choose another name, or replace it`);
|
|
15958
16133
|
}
|
|
15959
|
-
await mkdir9(
|
|
16134
|
+
await mkdir9(path29.dirname(file), { recursive: true });
|
|
15960
16135
|
await writeFile12(file, bytes);
|
|
15961
|
-
return relative.split(
|
|
16136
|
+
return relative.split(path29.sep).join("/");
|
|
15962
16137
|
}
|
|
15963
16138
|
|
|
15964
16139
|
// src/gallery/image-revision.ts
|
|
15965
16140
|
import { existsSync as existsSync24 } from "fs";
|
|
15966
16141
|
import { readFile as readFile21, rm as rm10, writeFile as writeFile13 } from "fs/promises";
|
|
15967
|
-
import
|
|
16142
|
+
import path30 from "path";
|
|
15968
16143
|
import { z as z11 } from "zod";
|
|
15969
16144
|
var InpaintRevisionRequestSchema = z11.object({
|
|
15970
16145
|
action: z11.literal("create-inpaint-revision"),
|
|
@@ -15983,14 +16158,14 @@ var InpaintRevisionRequestSchema = z11.object({
|
|
|
15983
16158
|
overwrite: z11.boolean().optional()
|
|
15984
16159
|
}).strict();
|
|
15985
16160
|
async function createInpaintRevision(manifestPath, request) {
|
|
15986
|
-
const root =
|
|
15987
|
-
if (
|
|
16161
|
+
const root = path30.dirname(path30.resolve(manifestPath));
|
|
16162
|
+
if (path30.extname(request.maskPath).toLowerCase() !== ".png") {
|
|
15988
16163
|
throw new ManifestEditError("the mask must be a .png path inside the project");
|
|
15989
16164
|
}
|
|
15990
16165
|
const bytes = Buffer.from(request.maskBase64, "base64");
|
|
15991
16166
|
const metadata = imageMetadata(bytes);
|
|
15992
16167
|
if (!metadata || metadata.format !== "png") throw new ManifestEditError("the mask is not a readable PNG");
|
|
15993
|
-
const file =
|
|
16168
|
+
const file = path30.resolve(root, request.maskPath);
|
|
15994
16169
|
const previous = existsSync24(file) ? await readFile21(file) : null;
|
|
15995
16170
|
const mask = await saveProjectImage(root, {
|
|
15996
16171
|
action: "upload-image",
|
|
@@ -16020,7 +16195,7 @@ async function createInpaintRevision(manifestPath, request) {
|
|
|
16020
16195
|
// src/gallery/skeleton.ts
|
|
16021
16196
|
import { existsSync as existsSync25 } from "fs";
|
|
16022
16197
|
import { mkdir as mkdir10, readFile as readFile22, rm as rm11, writeFile as writeFile14 } from "fs/promises";
|
|
16023
|
-
import
|
|
16198
|
+
import path31 from "path";
|
|
16024
16199
|
import { z as z12 } from "zod";
|
|
16025
16200
|
var SkeletonEstimateRequestSchema = z12.object({
|
|
16026
16201
|
project: z12.string().min(1).optional(),
|
|
@@ -16058,10 +16233,10 @@ var SkeletonKeypointsUpdateSchema = z12.object({
|
|
|
16058
16233
|
set: SkeletonSetSchema
|
|
16059
16234
|
}).strict();
|
|
16060
16235
|
async function createSkeletonAnimation(manifestPath, request) {
|
|
16061
|
-
const root =
|
|
16062
|
-
const file =
|
|
16063
|
-
const relative =
|
|
16064
|
-
if (!request.keypointsFile.endsWith(".json") || relative.startsWith("..") ||
|
|
16236
|
+
const root = path31.dirname(path31.resolve(manifestPath));
|
|
16237
|
+
const file = path31.resolve(root, request.keypointsFile);
|
|
16238
|
+
const relative = path31.relative(root, file);
|
|
16239
|
+
if (!request.keypointsFile.endsWith(".json") || relative.startsWith("..") || path31.isAbsolute(relative)) {
|
|
16065
16240
|
throw new ManifestEditError("the keypoints file must be a .json path inside the project");
|
|
16066
16241
|
}
|
|
16067
16242
|
const existed = existsSync25(file);
|
|
@@ -16071,7 +16246,7 @@ async function createSkeletonAnimation(manifestPath, request) {
|
|
|
16071
16246
|
throw new ManifestEditError(`${request.keypointsFile} already exists; choose another path, or replace it`);
|
|
16072
16247
|
}
|
|
16073
16248
|
if (existed) previous = await readFile22(file);
|
|
16074
|
-
await mkdir10(
|
|
16249
|
+
await mkdir10(path31.dirname(file), { recursive: true });
|
|
16075
16250
|
await writeFile14(file, JSON.stringify(request.set, null, 2) + "\n");
|
|
16076
16251
|
} else if (!existed) {
|
|
16077
16252
|
throw new ManifestEditError(`${request.keypointsFile} does not exist; paste poses or estimate them from the sprite`);
|
|
@@ -16087,7 +16262,7 @@ async function createSkeletonAnimation(manifestPath, request) {
|
|
|
16087
16262
|
revision: {
|
|
16088
16263
|
mode: "animate-skeleton",
|
|
16089
16264
|
from: request.from,
|
|
16090
|
-
keypointsFile: relative.split(
|
|
16265
|
+
keypointsFile: relative.split(path31.sep).join("/"),
|
|
16091
16266
|
direction: request.direction,
|
|
16092
16267
|
...request.description ? { description: request.description } : {},
|
|
16093
16268
|
...request.skeletonTemplate ? { skeletonTemplate: request.skeletonTemplate } : {}
|
|
@@ -16108,19 +16283,19 @@ async function updateSkeletonKeypoints(ctx, request) {
|
|
|
16108
16283
|
if (!spec) throw new ManifestEditError(`"${key}" is not declared by the manifest`);
|
|
16109
16284
|
const file = spec.revision?.mode === "animate-skeleton" ? spec.revision.keypointsFile : void 0;
|
|
16110
16285
|
if (!file) throw new ManifestEditError(`${key} is not an animate-skeleton revision`);
|
|
16111
|
-
const relative =
|
|
16112
|
-
if (relative.startsWith("..") ||
|
|
16286
|
+
const relative = path31.relative(ctx.loaded.root, file);
|
|
16287
|
+
if (relative.startsWith("..") || path31.isAbsolute(relative)) {
|
|
16113
16288
|
throw new ManifestEditError(`${key}'s keypoints file is outside the project; edit it by hand`);
|
|
16114
16289
|
}
|
|
16115
16290
|
const current = existsSync25(file) ? sha256(await readFile22(file)) : null;
|
|
16116
16291
|
if (current !== request.expectedSha256) {
|
|
16117
16292
|
throw Object.assign(
|
|
16118
|
-
new Error(`${relative.split(
|
|
16293
|
+
new Error(`${relative.split(path31.sep).join("/")} changed on disk since this page loaded it; press Refresh and edit again`),
|
|
16119
16294
|
{ status: 409 }
|
|
16120
16295
|
);
|
|
16121
16296
|
}
|
|
16122
16297
|
await writeFile14(file, JSON.stringify(request.set, null, 2) + "\n");
|
|
16123
|
-
return relative.split(
|
|
16298
|
+
return relative.split(path31.sep).join("/");
|
|
16124
16299
|
}
|
|
16125
16300
|
|
|
16126
16301
|
// src/gallery/edit.ts
|
|
@@ -16174,7 +16349,7 @@ function createGalleryEditHandler(opts) {
|
|
|
16174
16349
|
const history = /* @__PURE__ */ new Map();
|
|
16175
16350
|
const manifestOf = async (project) => {
|
|
16176
16351
|
try {
|
|
16177
|
-
return
|
|
16352
|
+
return path32.resolve(await opts.manifestFor(typeof project === "string" ? project : void 0));
|
|
16178
16353
|
} catch {
|
|
16179
16354
|
return null;
|
|
16180
16355
|
}
|
|
@@ -16197,7 +16372,7 @@ function createGalleryEditHandler(opts) {
|
|
|
16197
16372
|
await writeFile15(tmp, last.before);
|
|
16198
16373
|
await rename9(tmp, manifestPath2);
|
|
16199
16374
|
stack.pop();
|
|
16200
|
-
log(` manifest edit undone in ${
|
|
16375
|
+
log(` manifest edit undone in ${path32.relative(process.cwd(), manifestPath2) || manifestPath2}`);
|
|
16201
16376
|
return opts.reload();
|
|
16202
16377
|
}
|
|
16203
16378
|
const manifestPath = await manifestOf(body?.project);
|
|
@@ -16259,7 +16434,7 @@ function createEditApplier(opts, log) {
|
|
|
16259
16434
|
} catch (error) {
|
|
16260
16435
|
throw new ManifestEditError(error instanceof Error ? error.message : String(error));
|
|
16261
16436
|
}
|
|
16262
|
-
const written = await saveProjectImage(
|
|
16437
|
+
const written = await saveProjectImage(path32.dirname(path32.resolve(manifestPath2)), upload.data);
|
|
16263
16438
|
log(` image added: ${written}`);
|
|
16264
16439
|
return opts.reload();
|
|
16265
16440
|
}
|
|
@@ -16326,7 +16501,7 @@ function createEditApplier(opts, log) {
|
|
|
16326
16501
|
}
|
|
16327
16502
|
const result = await applyManifestEdit(manifestPath, parsed.data);
|
|
16328
16503
|
if (result.changed) {
|
|
16329
|
-
const relative =
|
|
16504
|
+
const relative = path32.relative(process.cwd(), result.manifestPath);
|
|
16330
16505
|
const shown = !relative || relative.startsWith("..") ? result.manifestPath : relative;
|
|
16331
16506
|
log(` manifest edited: ${describeEdit(parsed.data)} in ${shown}`);
|
|
16332
16507
|
}
|
|
@@ -16354,7 +16529,7 @@ function describeEdit(edit) {
|
|
|
16354
16529
|
// src/gallery/snapshot.ts
|
|
16355
16530
|
var PROJECT_FILE_TYPE = "application/octet-stream";
|
|
16356
16531
|
function galleryMediaId(absolutePath) {
|
|
16357
|
-
return
|
|
16532
|
+
return createHash5("sha256").update(path33.resolve(absolutePath)).digest("hex").slice(0, 24);
|
|
16358
16533
|
}
|
|
16359
16534
|
function galleryMediaRoute(id) {
|
|
16360
16535
|
return `/media/${id}`;
|
|
@@ -16373,7 +16548,7 @@ function describeHistory(media, entry, cacheDir) {
|
|
|
16373
16548
|
return (entry.history ?? []).map((generation, i) => {
|
|
16374
16549
|
const outputs = generation.outputs.map((output) => {
|
|
16375
16550
|
const mediaType = output.mediaType ?? MediaType.PNG;
|
|
16376
|
-
const file =
|
|
16551
|
+
const file = path33.join(cacheDir, cacheFileName(output.sha256, mediaType));
|
|
16377
16552
|
let url = null;
|
|
16378
16553
|
if (existsSync26(file)) {
|
|
16379
16554
|
const id = galleryMediaId(file);
|
|
@@ -16505,9 +16680,9 @@ async function describeOutput(media, root, absolutePath, recorded = {}) {
|
|
|
16505
16680
|
async function describeQuality(media, root, inspection) {
|
|
16506
16681
|
const outputPaths = inspection.outputs ?? [inspection.output];
|
|
16507
16682
|
const outputs = await Promise.all(
|
|
16508
|
-
outputPaths.map((file) => describeOutput(media, root,
|
|
16683
|
+
outputPaths.map((file) => describeOutput(media, root, path33.resolve(root, file)))
|
|
16509
16684
|
);
|
|
16510
|
-
const recordPath =
|
|
16685
|
+
const recordPath = path33.resolve(root, inspection.record);
|
|
16511
16686
|
const recordExists = existsSync26(recordPath);
|
|
16512
16687
|
let options = null;
|
|
16513
16688
|
let check = null;
|
|
@@ -16527,7 +16702,7 @@ async function describeQuality(media, root, inspection) {
|
|
|
16527
16702
|
return {
|
|
16528
16703
|
state: inspection.state,
|
|
16529
16704
|
reason: inspection.reason,
|
|
16530
|
-
output: portableOutputPath(
|
|
16705
|
+
output: portableOutputPath(path33.resolve(root, inspection.output), root),
|
|
16531
16706
|
record: portableOutputPath(recordPath, root),
|
|
16532
16707
|
recordExists,
|
|
16533
16708
|
outputs,
|
|
@@ -16567,7 +16742,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
16567
16742
|
const items = [];
|
|
16568
16743
|
const manifestText = await readFile24(loaded.path, "utf8");
|
|
16569
16744
|
const manifestSha256 = sha256(manifestText);
|
|
16570
|
-
const cacheDir =
|
|
16745
|
+
const cacheDir = path33.resolve(path33.dirname(opts.lockPath), ".pixelkiln", "cache");
|
|
16571
16746
|
let rawStyles = {};
|
|
16572
16747
|
try {
|
|
16573
16748
|
const raw = JSON.parse(manifestText);
|
|
@@ -16591,7 +16766,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
16591
16766
|
)
|
|
16592
16767
|
);
|
|
16593
16768
|
if (spec.source) {
|
|
16594
|
-
const editPath =
|
|
16769
|
+
const editPath = path33.resolve(root, spec.source);
|
|
16595
16770
|
const memberCount = sourceIsStem(spec.source, entry, root) ? entry.outputs.length : 1;
|
|
16596
16771
|
const editShas = await Promise.all(
|
|
16597
16772
|
Array.from({ length: memberCount }, async (_, index) => {
|
|
@@ -16640,7 +16815,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
16640
16815
|
editStatus = edits.some((member) => !member.exists) ? "missing" : editChanged.every((changed) => !changed) ? "same" : regenerated ? "regenerated-since" : "edited";
|
|
16641
16816
|
}
|
|
16642
16817
|
} else if (spec.source) {
|
|
16643
|
-
outputs = [await describeOutput(media, root,
|
|
16818
|
+
outputs = [await describeOutput(media, root, path33.resolve(root, spec.source))];
|
|
16644
16819
|
} else if (!entry && existsSync26(spec.outFile)) {
|
|
16645
16820
|
outputs = [await describeOutput(media, root, spec.outFile)];
|
|
16646
16821
|
} else {
|
|
@@ -16681,7 +16856,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
16681
16856
|
recordedSpecHash: entry?.specHash ?? null,
|
|
16682
16857
|
currentSpecHash: spec.specHash,
|
|
16683
16858
|
revision: entry?.revision ?? null,
|
|
16684
|
-
revisionParentKey: spec.revision ? lockKey(spec.styleId, spec.revision.sourceAssetId) : entry?.revision ? lockKey(spec.styleId, entry.revision.sourceAssetId) : null,
|
|
16859
|
+
revisionParentKey: spec.revision ? lockKey(spec.revision.sourceStyleId ?? spec.styleId, spec.revision.sourceAssetId) : entry?.revision ? lockKey(entry.revision.sourceStyleId ?? spec.styleId, entry.revision.sourceAssetId) : null,
|
|
16685
16860
|
mirrorOfKey: spec.mirror ? lockKey(spec.styleId, spec.mirror.sourceAssetId) : entry?.mirror ? lockKey(spec.styleId, entry.mirror.sourceAssetId) : null,
|
|
16686
16861
|
skeleton: spec.revision?.mode === "animate-skeleton" && spec.revision.keypointsFile ? {
|
|
16687
16862
|
keypointsFile: portableOutputPath(spec.revision.keypointsFile, root),
|
|
@@ -16751,7 +16926,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
16751
16926
|
recordedSpecHash: entry.specHash,
|
|
16752
16927
|
currentSpecHash: null,
|
|
16753
16928
|
revision: entry.revision,
|
|
16754
|
-
revisionParentKey: entry.revision ? lockKey(entry.styleId, entry.revision.sourceAssetId) : null,
|
|
16929
|
+
revisionParentKey: entry.revision ? lockKey(entry.revision.sourceStyleId ?? entry.styleId, entry.revision.sourceAssetId) : null,
|
|
16755
16930
|
mirrorOfKey: entry.mirror ? lockKey(entry.styleId, entry.mirror.sourceAssetId) : null,
|
|
16756
16931
|
skeleton: null,
|
|
16757
16932
|
character: describeCharacter(void 0, entry),
|
|
@@ -16844,7 +17019,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
16844
17019
|
id: loaded.manifest.name,
|
|
16845
17020
|
name: loaded.manifest.name,
|
|
16846
17021
|
manifest: loaded.path,
|
|
16847
|
-
lock:
|
|
17022
|
+
lock: path33.resolve(opts.lockPath),
|
|
16848
17023
|
root,
|
|
16849
17024
|
provider: loaded.manifest.provider,
|
|
16850
17025
|
account: null,
|
|
@@ -16870,7 +17045,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
16870
17045
|
return { snapshot, media };
|
|
16871
17046
|
}
|
|
16872
17047
|
async function buildWorkspaceGallerySnapshot(opts) {
|
|
16873
|
-
const dir =
|
|
17048
|
+
const dir = path33.dirname(path33.resolve(opts.workspacePath));
|
|
16874
17049
|
const media = /* @__PURE__ */ new Map();
|
|
16875
17050
|
const projects = [];
|
|
16876
17051
|
const items = [];
|
|
@@ -16913,7 +17088,7 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
16913
17088
|
id: project.id,
|
|
16914
17089
|
name: loaded.manifest.name,
|
|
16915
17090
|
manifest: manifestPath,
|
|
16916
|
-
lock:
|
|
17091
|
+
lock: path33.resolve(lockPath),
|
|
16917
17092
|
root: loaded.root,
|
|
16918
17093
|
provider: loaded.manifest.provider,
|
|
16919
17094
|
account: project.account ?? null,
|
|
@@ -16929,8 +17104,8 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
16929
17104
|
id: project.id,
|
|
16930
17105
|
name: project.id,
|
|
16931
17106
|
manifest: manifestPath,
|
|
16932
|
-
lock:
|
|
16933
|
-
root:
|
|
17107
|
+
lock: path33.resolve(lockPath),
|
|
17108
|
+
root: path33.dirname(manifestPath),
|
|
16934
17109
|
provider: project.provider,
|
|
16935
17110
|
account: project.account ?? null,
|
|
16936
17111
|
manifestSha256: null,
|
|
@@ -16957,7 +17132,7 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
16957
17132
|
version: 1,
|
|
16958
17133
|
generatedAt: (opts.now ?? (() => /* @__PURE__ */ new Date()))().toISOString(),
|
|
16959
17134
|
project: null,
|
|
16960
|
-
workspace: { path:
|
|
17135
|
+
workspace: { path: path33.resolve(opts.workspacePath), dir, projects },
|
|
16961
17136
|
filter,
|
|
16962
17137
|
totals: {
|
|
16963
17138
|
entries: projects.reduce((sum, project) => sum + project.entries, 0),
|
|
@@ -16974,7 +17149,7 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
16974
17149
|
}
|
|
16975
17150
|
|
|
16976
17151
|
// src/gallery/page.ts
|
|
16977
|
-
import { existsSync as existsSync27, readFileSync as
|
|
17152
|
+
import { existsSync as existsSync27, readFileSync as readFileSync6 } from "fs";
|
|
16978
17153
|
|
|
16979
17154
|
// src/gallery/client-bundle.ts
|
|
16980
17155
|
import { createRequire } from "module";
|
|
@@ -17014,8 +17189,8 @@ var client;
|
|
|
17014
17189
|
function clientAssets() {
|
|
17015
17190
|
const prebuilt = new URL("gallery.js", CLIENT_DIR);
|
|
17016
17191
|
client ??= {
|
|
17017
|
-
css:
|
|
17018
|
-
js: existsSync27(prebuilt) ?
|
|
17192
|
+
css: readFileSync6(new URL("gallery.css", CLIENT_DIR), "utf8"),
|
|
17193
|
+
js: existsSync27(prebuilt) ? readFileSync6(prebuilt, "utf8") : bundleGalleryClient()
|
|
17019
17194
|
};
|
|
17020
17195
|
return client;
|
|
17021
17196
|
}
|
|
@@ -17157,11 +17332,11 @@ var EDITOR_PIN = pin_default;
|
|
|
17157
17332
|
var EDITOR_RELEASE_BASE = "https://github.com/gfargo/pixelkiln/releases/download";
|
|
17158
17333
|
|
|
17159
17334
|
// src/editor/install.ts
|
|
17160
|
-
import { createHash as
|
|
17335
|
+
import { createHash as createHash6 } from "crypto";
|
|
17161
17336
|
import { existsSync as existsSync28 } from "fs";
|
|
17162
17337
|
import { mkdir as mkdir11, readFile as readFile25, rename as rename10, rm as rm12, stat as stat4, writeFile as writeFile16 } from "fs/promises";
|
|
17163
|
-
import
|
|
17164
|
-
import
|
|
17338
|
+
import os3 from "os";
|
|
17339
|
+
import path34 from "path";
|
|
17165
17340
|
var EditorInstallError = class extends Error {
|
|
17166
17341
|
constructor(message6, file) {
|
|
17167
17342
|
super(message6);
|
|
@@ -17172,15 +17347,15 @@ var EditorInstallError = class extends Error {
|
|
|
17172
17347
|
};
|
|
17173
17348
|
function editorToolsRoot() {
|
|
17174
17349
|
const override = process.env.PIXELKILN_TOOLS_DIR?.trim();
|
|
17175
|
-
if (override) return
|
|
17176
|
-
if (process.platform === "darwin") return
|
|
17350
|
+
if (override) return path34.resolve(override);
|
|
17351
|
+
if (process.platform === "darwin") return path34.join(os3.homedir(), "Library", "Caches", "pixelkiln", "tools");
|
|
17177
17352
|
if (process.platform === "win32") {
|
|
17178
|
-
return
|
|
17353
|
+
return path34.join(process.env.LOCALAPPDATA ?? path34.join(os3.homedir(), "AppData", "Local"), "pixelkiln", "tools");
|
|
17179
17354
|
}
|
|
17180
|
-
return
|
|
17355
|
+
return path34.join(process.env.XDG_CACHE_HOME ?? path34.join(os3.homedir(), ".cache"), "pixelkiln", "tools");
|
|
17181
17356
|
}
|
|
17182
17357
|
function editorDir(pin = EDITOR_PIN, root = editorToolsRoot()) {
|
|
17183
|
-
return
|
|
17358
|
+
return path34.join(root, "pixelorama", pin.release ?? "unpublished");
|
|
17184
17359
|
}
|
|
17185
17360
|
function editorBaseUrl(pin = EDITOR_PIN) {
|
|
17186
17361
|
const override = process.env.PIXELKILN_EDITOR_URL?.trim();
|
|
@@ -17190,7 +17365,7 @@ function editorBaseUrl(pin = EDITOR_PIN) {
|
|
|
17190
17365
|
}
|
|
17191
17366
|
async function sha256Of(file) {
|
|
17192
17367
|
try {
|
|
17193
|
-
return
|
|
17368
|
+
return createHash6("sha256").update(await readFile25(file)).digest("hex");
|
|
17194
17369
|
} catch {
|
|
17195
17370
|
return null;
|
|
17196
17371
|
}
|
|
@@ -17203,7 +17378,7 @@ async function editorStatus(opts = {}) {
|
|
|
17203
17378
|
let totalBytes = 0;
|
|
17204
17379
|
for (const [name, expected] of Object.entries(pin.files)) {
|
|
17205
17380
|
totalBytes += expected.bytes;
|
|
17206
|
-
const file =
|
|
17381
|
+
const file = path34.join(dir, name);
|
|
17207
17382
|
const info = existsSync28(file) ? await stat4(file).catch(() => null) : null;
|
|
17208
17383
|
if (info && info.size === expected.bytes && await sha256Of(file) === expected.sha256) {
|
|
17209
17384
|
installedBytes += expected.bytes;
|
|
@@ -17257,11 +17432,11 @@ async function installEditor(opts = {}) {
|
|
|
17257
17432
|
if (bytes.length !== expected.bytes) {
|
|
17258
17433
|
throw new EditorInstallError(`${name}: expected ${expected.bytes} bytes, got ${bytes.length}`, name);
|
|
17259
17434
|
}
|
|
17260
|
-
const digest2 =
|
|
17435
|
+
const digest2 = createHash6("sha256").update(bytes).digest("hex");
|
|
17261
17436
|
if (digest2 !== expected.sha256) {
|
|
17262
17437
|
throw new EditorInstallError(`${name}: sha256 ${digest2.slice(0, 12)}\u2026 does not match the pinned ${expected.sha256.slice(0, 12)}\u2026`, name);
|
|
17263
17438
|
}
|
|
17264
|
-
const final =
|
|
17439
|
+
const final = path34.join(dir, name);
|
|
17265
17440
|
const tmp = `${final}.${process.pid}.part`;
|
|
17266
17441
|
try {
|
|
17267
17442
|
await writeFile16(tmp, bytes);
|
|
@@ -17307,12 +17482,12 @@ var EDITOR_CONTENT_TYPES = {
|
|
|
17307
17482
|
".json": "application/json; charset=utf-8"
|
|
17308
17483
|
};
|
|
17309
17484
|
function editorContentType(name) {
|
|
17310
|
-
return EDITOR_CONTENT_TYPES[
|
|
17485
|
+
return EDITOR_CONTENT_TYPES[path34.extname(name).toLowerCase()] ?? null;
|
|
17311
17486
|
}
|
|
17312
17487
|
|
|
17313
17488
|
// src/gallery/editor.ts
|
|
17314
17489
|
import { readFile as readFile26 } from "fs/promises";
|
|
17315
|
-
import
|
|
17490
|
+
import path35 from "path";
|
|
17316
17491
|
function editorRoute(release, name = "index.html") {
|
|
17317
17492
|
return `/editor/${encodeURIComponent(release)}/${encodeURIComponent(name)}`;
|
|
17318
17493
|
}
|
|
@@ -17385,7 +17560,7 @@ function createGalleryEditorHandlers(opts = {}) {
|
|
|
17385
17560
|
if (!status.installed) return null;
|
|
17386
17561
|
let bytes;
|
|
17387
17562
|
try {
|
|
17388
|
-
bytes = await readFile26(
|
|
17563
|
+
bytes = await readFile26(path35.join(dir, name));
|
|
17389
17564
|
} catch {
|
|
17390
17565
|
verified = null;
|
|
17391
17566
|
return null;
|
|
@@ -18078,7 +18253,7 @@ function createGenerateHandlers(opts) {
|
|
|
18078
18253
|
// src/pipeline/salvage.ts
|
|
18079
18254
|
import { readFile as readFile28 } from "fs/promises";
|
|
18080
18255
|
import { existsSync as existsSync29 } from "fs";
|
|
18081
|
-
import
|
|
18256
|
+
import path36 from "path";
|
|
18082
18257
|
function providerClaimId(provider, objectId) {
|
|
18083
18258
|
return `${encodeURIComponent(provider)}:${encodeURIComponent(objectId)}`;
|
|
18084
18259
|
}
|
|
@@ -18192,20 +18367,20 @@ function matchOrphanStyle(prompt, manifest) {
|
|
|
18192
18367
|
return matchStyleByPattern(prompt, manifest);
|
|
18193
18368
|
}
|
|
18194
18369
|
async function loadSiblingManifests(ownManifestPath, workspaceManifestPaths, claimPaths) {
|
|
18195
|
-
const own =
|
|
18370
|
+
const own = path36.resolve(ownManifestPath);
|
|
18196
18371
|
const siblingManifestPaths = [
|
|
18197
18372
|
.../* @__PURE__ */ new Set([
|
|
18198
18373
|
...workspaceManifestPaths,
|
|
18199
|
-
...claimPaths.map((c) =>
|
|
18374
|
+
...claimPaths.map((c) => path36.join(path36.dirname(path36.resolve(c)), "pixelkiln.manifest.json"))
|
|
18200
18375
|
])
|
|
18201
18376
|
];
|
|
18202
18377
|
const siblings = [];
|
|
18203
18378
|
for (const siblingManifestPath of siblingManifestPaths) {
|
|
18204
|
-
if (
|
|
18379
|
+
if (path36.resolve(siblingManifestPath) === own) continue;
|
|
18205
18380
|
if (!existsSync29(siblingManifestPath)) continue;
|
|
18206
18381
|
try {
|
|
18207
18382
|
const { manifest } = await loadManifest(siblingManifestPath);
|
|
18208
|
-
siblings.push({ label:
|
|
18383
|
+
siblings.push({ label: path36.basename(path36.dirname(siblingManifestPath)), manifest });
|
|
18209
18384
|
} catch {
|
|
18210
18385
|
}
|
|
18211
18386
|
}
|
|
@@ -18326,7 +18501,7 @@ async function applyTags(provider, decisions, existing, opts = {}) {
|
|
|
18326
18501
|
|
|
18327
18502
|
// src/pick/salvage-server.ts
|
|
18328
18503
|
import { mkdir as mkdir12, writeFile as writeFile17, readFile as readFile29 } from "fs/promises";
|
|
18329
|
-
import
|
|
18504
|
+
import path37 from "path";
|
|
18330
18505
|
|
|
18331
18506
|
// src/pick/salvage-sheet.ts
|
|
18332
18507
|
var escapeHtml3 = (s) => s.replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
@@ -18492,7 +18667,7 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
18492
18667
|
});
|
|
18493
18668
|
const html = renderSalvageSheet(orphans, {
|
|
18494
18669
|
styleId: ctx.styleId,
|
|
18495
|
-
importDir:
|
|
18670
|
+
importDir: path37.relative(process.cwd(), ctx.importDir) || "."
|
|
18496
18671
|
});
|
|
18497
18672
|
const byId = new Map(orphans.map((o) => [o.id, o]));
|
|
18498
18673
|
const existingTags = new Map(orphans.map((o) => [o.id, o.tags]));
|
|
@@ -18548,9 +18723,9 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
18548
18723
|
if (!buf.subarray(0, 8).equals(PNG_SIGNATURE2)) throw new Error("not a PNG");
|
|
18549
18724
|
decodePng(buf);
|
|
18550
18725
|
const assetId = idFromPrompt(orphan.prompt, taken);
|
|
18551
|
-
const rel =
|
|
18552
|
-
const outFile =
|
|
18553
|
-
await mkdir12(
|
|
18726
|
+
const rel = path37.join("_salvaged", `${assetId}.png`);
|
|
18727
|
+
const outFile = path37.resolve(ctx.importDir, rel);
|
|
18728
|
+
await mkdir12(path37.dirname(outFile), { recursive: true });
|
|
18554
18729
|
await writeFile17(outFile, buf);
|
|
18555
18730
|
ctx.manifest.assets[assetId] = {
|
|
18556
18731
|
prompt: orphan.prompt,
|
|
@@ -18583,7 +18758,7 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
18583
18758
|
sourceUrl: durableSource,
|
|
18584
18759
|
sourceUrls: durableSource ? [{ url: durableSource }] : [],
|
|
18585
18760
|
outputs: [{
|
|
18586
|
-
path: portableOutputPath(outFile,
|
|
18761
|
+
path: portableOutputPath(outFile, path37.dirname(ctx.manifestPath)),
|
|
18587
18762
|
sha256: sha256(buf)
|
|
18588
18763
|
}],
|
|
18589
18764
|
submittedAt: orphan.createdAt,
|