pixelkiln 0.84.0 → 0.85.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/PROVIDERS.md +3 -3
- package/dist/cli.js +462 -346
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +549 -433
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +25 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +444 -328
- package/dist/index.js.map +1 -1
- package/docs/REVISIONS.md +4 -2
- package/docs/SCENARIO.md +174 -7
- package/package.json +1 -1
- package/skills/pixelkiln/references/scenario.md +8 -4
package/dist/index.js
CHANGED
|
@@ -1816,9 +1816,9 @@ var PixelLabClient = class {
|
|
|
1816
1816
|
apiKey;
|
|
1817
1817
|
http;
|
|
1818
1818
|
/** Retries transport failures, 429, and 5xx; see http.ts for the policy. */
|
|
1819
|
-
async request(
|
|
1819
|
+
async request(path38, init) {
|
|
1820
1820
|
const auth = this.apiKey.startsWith("Bearer ") ? this.apiKey : `Bearer ${this.apiKey}`;
|
|
1821
|
-
const res = await this.http(`${BASE}${
|
|
1821
|
+
const res = await this.http(`${BASE}${path38}`, {
|
|
1822
1822
|
...init,
|
|
1823
1823
|
headers: {
|
|
1824
1824
|
Authorization: auth,
|
|
@@ -1828,13 +1828,13 @@ var PixelLabClient = class {
|
|
|
1828
1828
|
});
|
|
1829
1829
|
const text = await res.text();
|
|
1830
1830
|
if (!res.ok) {
|
|
1831
|
-
throw new PixelLabError(`${init?.method ?? "GET"} ${
|
|
1831
|
+
throw new PixelLabError(`${init?.method ?? "GET"} ${path38} \u2192 ${res.status}`, res.status, text);
|
|
1832
1832
|
}
|
|
1833
1833
|
if (!text) return {};
|
|
1834
1834
|
try {
|
|
1835
1835
|
return JSON.parse(text);
|
|
1836
1836
|
} catch {
|
|
1837
|
-
throw new Error(`${init?.method ?? "GET"} ${
|
|
1837
|
+
throw new Error(`${init?.method ?? "GET"} ${path38} returned invalid JSON`);
|
|
1838
1838
|
}
|
|
1839
1839
|
}
|
|
1840
1840
|
async balance() {
|
|
@@ -2239,10 +2239,10 @@ var PixelLabClient = class {
|
|
|
2239
2239
|
const palette = args.paletteSwatchBase64 ? { color_image: { type: "base64", base64: args.paletteSwatchBase64, format: "png" }, force_colors: true } : {};
|
|
2240
2240
|
const encode = (image) => ({ type: "base64", base64: image.base64, format: image.format });
|
|
2241
2241
|
const south = args.reference?.south;
|
|
2242
|
-
let
|
|
2242
|
+
let path38;
|
|
2243
2243
|
let body;
|
|
2244
2244
|
if (args.mode === "v3") {
|
|
2245
|
-
|
|
2245
|
+
path38 = "/create-character-v3";
|
|
2246
2246
|
body = {
|
|
2247
2247
|
description: args.description,
|
|
2248
2248
|
image_size: imageSize,
|
|
@@ -2256,7 +2256,7 @@ var PixelLabClient = class {
|
|
|
2256
2256
|
...args.enhancePrompt ? { enhance_prompt: true } : {}
|
|
2257
2257
|
};
|
|
2258
2258
|
} else if (args.mode === "pro-flash") {
|
|
2259
|
-
|
|
2259
|
+
path38 = "/create-character-pro-flash";
|
|
2260
2260
|
const traits = args.styleTraits;
|
|
2261
2261
|
body = {
|
|
2262
2262
|
description: args.description,
|
|
@@ -2280,7 +2280,7 @@ var PixelLabClient = class {
|
|
|
2280
2280
|
} : {}
|
|
2281
2281
|
};
|
|
2282
2282
|
} else if (args.mode === "pro") {
|
|
2283
|
-
|
|
2283
|
+
path38 = "/create-character-pro";
|
|
2284
2284
|
body = {
|
|
2285
2285
|
description: args.description,
|
|
2286
2286
|
image_size: imageSize,
|
|
@@ -2294,7 +2294,7 @@ var PixelLabClient = class {
|
|
|
2294
2294
|
...!south && args.styleCharacterId ? { style_character_id: args.styleCharacterId } : {}
|
|
2295
2295
|
};
|
|
2296
2296
|
} else {
|
|
2297
|
-
|
|
2297
|
+
path38 = args.directions === 4 ? "/create-character-with-4-directions" : "/create-character-with-8-directions";
|
|
2298
2298
|
body = {
|
|
2299
2299
|
description: args.description,
|
|
2300
2300
|
image_size: imageSize,
|
|
@@ -2311,8 +2311,8 @@ var PixelLabClient = class {
|
|
|
2311
2311
|
...palette
|
|
2312
2312
|
};
|
|
2313
2313
|
}
|
|
2314
|
-
const raw = await this.request(
|
|
2315
|
-
return validateResponse(CharacterSubmitSchema, raw,
|
|
2314
|
+
const raw = await this.request(path38, { method: "POST", body: JSON.stringify(body) });
|
|
2315
|
+
return validateResponse(CharacterSubmitSchema, raw, path38.slice(1));
|
|
2316
2316
|
}
|
|
2317
2317
|
/** A text edit of an existing character, applied to every direction and stored as a sibling. */
|
|
2318
2318
|
async createCharacterState(args) {
|
|
@@ -3525,10 +3525,10 @@ import { createReadStream } from "fs";
|
|
|
3525
3525
|
function sha256(data) {
|
|
3526
3526
|
return createHash("sha256").update(data).digest("hex");
|
|
3527
3527
|
}
|
|
3528
|
-
async function sha256File(
|
|
3528
|
+
async function sha256File(path38) {
|
|
3529
3529
|
return new Promise((resolve, reject) => {
|
|
3530
3530
|
const hash = createHash("sha256");
|
|
3531
|
-
const stream = createReadStream(
|
|
3531
|
+
const stream = createReadStream(path38);
|
|
3532
3532
|
stream.on("data", (chunk2) => hash.update(chunk2));
|
|
3533
3533
|
stream.on("error", reject);
|
|
3534
3534
|
stream.on("end", () => resolve(hash.digest("hex")));
|
|
@@ -5932,9 +5932,9 @@ var RetroDiffusionClient = class {
|
|
|
5932
5932
|
if (!response.ok) throw new ProviderError("retrodiffusion", `Retro Diffusion download failed (${response.status})`, { status: response.status });
|
|
5933
5933
|
return Buffer.from(await response.arrayBuffer());
|
|
5934
5934
|
}
|
|
5935
|
-
async call(
|
|
5935
|
+
async call(path38, init = {}) {
|
|
5936
5936
|
if (!this.token) throw new Error("RD_API_KEY is not set");
|
|
5937
|
-
const response = await this.request(`${this.baseUrl}${
|
|
5937
|
+
const response = await this.request(`${this.baseUrl}${path38}`, {
|
|
5938
5938
|
...init,
|
|
5939
5939
|
headers: {
|
|
5940
5940
|
"Content-Type": "application/json",
|
|
@@ -6243,8 +6243,13 @@ function retroError(value) {
|
|
|
6243
6243
|
}
|
|
6244
6244
|
|
|
6245
6245
|
// src/providers/scenario.ts
|
|
6246
|
+
import { createHash as createHash2 } from "crypto";
|
|
6247
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, renameSync, writeFileSync as writeFileSync2 } from "fs";
|
|
6248
|
+
import os2 from "os";
|
|
6249
|
+
import path2 from "path";
|
|
6246
6250
|
var DEFAULT_BASE_URL2 = "https://api.cloud.scenario.com/v1";
|
|
6247
6251
|
var SOURCE_PROTOCOL2 = "scenario:";
|
|
6252
|
+
var SUBMIT_RETRIES = 8;
|
|
6248
6253
|
var PROTECTED_PARAMETERS = /* @__PURE__ */ new Set([
|
|
6249
6254
|
"dryRun",
|
|
6250
6255
|
"height",
|
|
@@ -6291,7 +6296,11 @@ var ScenarioClient = class {
|
|
|
6291
6296
|
async submit(modelId, body, projectId) {
|
|
6292
6297
|
const response = await this.call(
|
|
6293
6298
|
`/generate/custom/${encodeURIComponent(modelId)}`,
|
|
6294
|
-
|
|
6299
|
+
// A 429 here means this account's concurrent-job limit refused the
|
|
6300
|
+
// request, so nothing was accepted or billed; wait it out instead of
|
|
6301
|
+
// failing the asset. The default four retries give up after about half
|
|
6302
|
+
// a minute, before a running job has finished.
|
|
6303
|
+
{ method: "POST", body: JSON.stringify(body), retries: SUBMIT_RETRIES },
|
|
6295
6304
|
{ projectId }
|
|
6296
6305
|
);
|
|
6297
6306
|
const job = response?.job;
|
|
@@ -6299,6 +6308,29 @@ var ScenarioClient = class {
|
|
|
6299
6308
|
if (!jobId) throw new Error("Scenario did not return an async job id");
|
|
6300
6309
|
return jobId;
|
|
6301
6310
|
}
|
|
6311
|
+
/**
|
|
6312
|
+
* Uploads one image (POST /assets) and returns its asset id, which a model's
|
|
6313
|
+
* reference input takes. Free, and idempotent per image: the id is kept by
|
|
6314
|
+
* content hash so a rerun does not upload the same bytes again.
|
|
6315
|
+
*/
|
|
6316
|
+
async uploadImage(image, projectId) {
|
|
6317
|
+
const bytes = Buffer.from(image.base64, "base64");
|
|
6318
|
+
const digest2 = sha256(bytes);
|
|
6319
|
+
const cached = readUploadCache()[uploadCacheKey(this.baseUrl, projectId, digest2)];
|
|
6320
|
+
if (cached) return cached;
|
|
6321
|
+
const response = await this.call(
|
|
6322
|
+
"/assets",
|
|
6323
|
+
{
|
|
6324
|
+
method: "POST",
|
|
6325
|
+
body: JSON.stringify({ image: image.base64, name: `pixelkiln-${digest2.slice(0, 12)}.${image.format === "png" ? "png" : "jpg"}` })
|
|
6326
|
+
},
|
|
6327
|
+
{ projectId }
|
|
6328
|
+
);
|
|
6329
|
+
const id = response?.asset?.id;
|
|
6330
|
+
if (typeof id !== "string" || !id) throw new Error("Scenario did not return an uploaded asset id");
|
|
6331
|
+
writeUploadCache(uploadCacheKey(this.baseUrl, projectId, digest2), id);
|
|
6332
|
+
return id;
|
|
6333
|
+
}
|
|
6302
6334
|
async job(id, projectId) {
|
|
6303
6335
|
const response = await this.call(`/jobs/${encodeURIComponent(id)}`, {}, { projectId });
|
|
6304
6336
|
const raw = response?.job;
|
|
@@ -6425,6 +6457,14 @@ var ScenarioProvider = class _ScenarioProvider {
|
|
|
6425
6457
|
supports(generator) {
|
|
6426
6458
|
return generator === "map";
|
|
6427
6459
|
}
|
|
6460
|
+
/**
|
|
6461
|
+
* An edit is a generation with the parent as a reference image, which the
|
|
6462
|
+
* editing models (GPT Image, Gemini, FLUX) take. Masked inpainting and the
|
|
6463
|
+
* PixelLab-specific modes have no common Scenario input.
|
|
6464
|
+
*/
|
|
6465
|
+
supportsRevision(mode2) {
|
|
6466
|
+
return mode2 === "image-to-image";
|
|
6467
|
+
}
|
|
6428
6468
|
estimate(spec) {
|
|
6429
6469
|
const options = scenarioOptions(spec);
|
|
6430
6470
|
return {
|
|
@@ -6445,11 +6485,31 @@ var ScenarioProvider = class _ScenarioProvider {
|
|
|
6445
6485
|
if (!Number.isInteger(count) || count < 1 || count > 4) {
|
|
6446
6486
|
throw new Error("Scenario numOutputs must be a whole number from 1 to 4");
|
|
6447
6487
|
}
|
|
6448
|
-
if (spec.width
|
|
6449
|
-
throw new Error("Scenario width and height must be
|
|
6488
|
+
if (!Number.isInteger(spec.width) || spec.width < 16 || spec.width > 4096 || !Number.isInteger(spec.height) || spec.height < 16 || spec.height > 4096) {
|
|
6489
|
+
throw new Error("Scenario width and height must be whole numbers from 16 to 4096");
|
|
6450
6490
|
}
|
|
6451
|
-
|
|
6452
|
-
|
|
6491
|
+
const revision = spec.revision;
|
|
6492
|
+
if (revision) {
|
|
6493
|
+
if (revision.sourceMembers?.length) {
|
|
6494
|
+
throw new Error(
|
|
6495
|
+
`Scenario edits one image; ${revision.sourceAssetId} is a ${revision.sourceMembers.length}-member set`
|
|
6496
|
+
);
|
|
6497
|
+
}
|
|
6498
|
+
if (revision.strength != null) {
|
|
6499
|
+
throw new Error("Scenario image-to-image takes no strength; use the model's own parameters");
|
|
6500
|
+
}
|
|
6501
|
+
}
|
|
6502
|
+
const parameter = referenceParameter(options);
|
|
6503
|
+
if ((styleImages.length || revision) && Object.hasOwn(options.parameters ?? {}, parameter)) {
|
|
6504
|
+
throw new Error(
|
|
6505
|
+
`Scenario parameters.${parameter} is the reference-image input and is filled from styleImages and revisions`
|
|
6506
|
+
);
|
|
6507
|
+
}
|
|
6508
|
+
if (styleImages.length + (revision ? 1 : 0) > 1 && options.referenceArray === false) {
|
|
6509
|
+
throw new Error(`Scenario ${parameter} takes one image; use one style image or a revision, not both`);
|
|
6510
|
+
}
|
|
6511
|
+
if (options.referenceParameter != null && (typeof options.referenceParameter !== "string" || !options.referenceParameter.trim())) {
|
|
6512
|
+
throw new Error("Scenario referenceParameter must be a non-empty string");
|
|
6453
6513
|
}
|
|
6454
6514
|
if (options.projectId != null && (typeof options.projectId !== "string" || !options.projectId.trim())) {
|
|
6455
6515
|
throw new Error("Scenario projectId must be a non-empty string");
|
|
@@ -6462,7 +6522,12 @@ var ScenarioProvider = class _ScenarioProvider {
|
|
|
6462
6522
|
async submit(spec, styleImages) {
|
|
6463
6523
|
this.validate(spec, styleImages);
|
|
6464
6524
|
const options = scenarioOptions(spec);
|
|
6465
|
-
const
|
|
6525
|
+
const references = [];
|
|
6526
|
+
if (spec.revision) references.push(readRevisionSource(spec));
|
|
6527
|
+
for (const image of styleImages) references.push({ base64: image.base64, format: image.format });
|
|
6528
|
+
const referenceAssetIds = [];
|
|
6529
|
+
for (const image of references) referenceAssetIds.push(await this.client.uploadImage(image, options.projectId));
|
|
6530
|
+
const body = requestBody(spec, options, referenceAssetIds);
|
|
6466
6531
|
const quote = await this.client.quote(options.modelId, body, options.projectId);
|
|
6467
6532
|
if (quote.total > options.maxComputeUnits + Number.EPSILON) {
|
|
6468
6533
|
throw new Error(
|
|
@@ -6479,7 +6544,8 @@ var ScenarioProvider = class _ScenarioProvider {
|
|
|
6479
6544
|
quotedComputeUnits: quote.total,
|
|
6480
6545
|
creativeUnitsCost: quote.creativeUnitsCost,
|
|
6481
6546
|
costDetails: quote.costDetails,
|
|
6482
|
-
ipDetectionCost: quote.ipDetectionCost
|
|
6547
|
+
ipDetectionCost: quote.ipDetectionCost,
|
|
6548
|
+
...referenceAssetIds.length ? { referenceAssetIds } : {}
|
|
6483
6549
|
}
|
|
6484
6550
|
};
|
|
6485
6551
|
}
|
|
@@ -6501,7 +6567,13 @@ var ScenarioProvider = class _ScenarioProvider {
|
|
|
6501
6567
|
}
|
|
6502
6568
|
const assetIds = scenarioAssetIds(job);
|
|
6503
6569
|
if (!assetIds.length) return { status: "failed", error: "Scenario job returned no asset IDs" };
|
|
6504
|
-
|
|
6570
|
+
let assets;
|
|
6571
|
+
try {
|
|
6572
|
+
assets = await this.assets(assetIds, reference.projectId);
|
|
6573
|
+
} catch (error) {
|
|
6574
|
+
if (error instanceof UnsupportedScenarioOutput) return { status: "failed", error: error.message };
|
|
6575
|
+
throw error;
|
|
6576
|
+
}
|
|
6505
6577
|
if (assets.length > 1) {
|
|
6506
6578
|
return {
|
|
6507
6579
|
status: "review",
|
|
@@ -6556,6 +6628,32 @@ var ScenarioProvider = class _ScenarioProvider {
|
|
|
6556
6628
|
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
6629
|
}
|
|
6558
6630
|
};
|
|
6631
|
+
function uploadCachePath() {
|
|
6632
|
+
return process.env.SCENARIO_UPLOAD_CACHE ?? path2.join(os2.tmpdir(), "pixelkiln-scenario", "uploads.json");
|
|
6633
|
+
}
|
|
6634
|
+
function uploadCacheKey(baseUrl, projectId, digest2) {
|
|
6635
|
+
return createHash2("sha256").update(`${baseUrl}
|
|
6636
|
+
${projectId ?? ""}
|
|
6637
|
+
${digest2}`).digest("hex");
|
|
6638
|
+
}
|
|
6639
|
+
function readUploadCache() {
|
|
6640
|
+
try {
|
|
6641
|
+
const value = JSON.parse(readFileSync2(uploadCachePath(), "utf8"));
|
|
6642
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
6643
|
+
} catch {
|
|
6644
|
+
return {};
|
|
6645
|
+
}
|
|
6646
|
+
}
|
|
6647
|
+
function writeUploadCache(key, assetId) {
|
|
6648
|
+
try {
|
|
6649
|
+
const file = uploadCachePath();
|
|
6650
|
+
mkdirSync2(path2.dirname(file), { recursive: true });
|
|
6651
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
6652
|
+
writeFileSync2(tmp, JSON.stringify({ ...readUploadCache(), [key]: assetId }));
|
|
6653
|
+
renameSync(tmp, file);
|
|
6654
|
+
} catch {
|
|
6655
|
+
}
|
|
6656
|
+
}
|
|
6559
6657
|
function scenarioOptions(spec) {
|
|
6560
6658
|
return spec.providerOptions;
|
|
6561
6659
|
}
|
|
@@ -6571,9 +6669,25 @@ function scenarioBaseUrl(value) {
|
|
|
6571
6669
|
}
|
|
6572
6670
|
return url.href.replace(/\/$/, "");
|
|
6573
6671
|
}
|
|
6574
|
-
function
|
|
6672
|
+
function referenceParameter(options) {
|
|
6673
|
+
return options.referenceParameter?.trim() || "referenceImages";
|
|
6674
|
+
}
|
|
6675
|
+
function readRevisionSource(spec) {
|
|
6676
|
+
const revision = spec.revision;
|
|
6677
|
+
if (!revision.sourceFile || !revision.sourceSha256 || !revision.sourceFormat) {
|
|
6678
|
+
throw new Error(`${spec.styleId}/${spec.assetId}: revision source is not ready`);
|
|
6679
|
+
}
|
|
6680
|
+
const bytes = readFileSync2(revision.sourceFile);
|
|
6681
|
+
if (sha256(bytes) !== revision.sourceSha256) {
|
|
6682
|
+
throw new Error(`${spec.styleId}/${spec.assetId}: revision source changed after the manifest was resolved`);
|
|
6683
|
+
}
|
|
6684
|
+
return { base64: bytes.toString("base64"), format: revision.sourceFormat };
|
|
6685
|
+
}
|
|
6686
|
+
function requestBody(spec, options, referenceAssetIds = []) {
|
|
6687
|
+
const parameter = referenceParameter(options);
|
|
6575
6688
|
return {
|
|
6576
6689
|
...options.parameters ?? {},
|
|
6690
|
+
...referenceAssetIds.length ? { [parameter]: options.referenceArray === false ? referenceAssetIds[0] : referenceAssetIds } : {},
|
|
6577
6691
|
prompt: spec.prompt,
|
|
6578
6692
|
width: spec.width,
|
|
6579
6693
|
height: spec.height,
|
|
@@ -6663,9 +6777,11 @@ function scenarioAssetIds(job) {
|
|
|
6663
6777
|
function assetDownloadUrl(asset) {
|
|
6664
6778
|
return asset.originalFileUrl || asset.url;
|
|
6665
6779
|
}
|
|
6780
|
+
var UnsupportedScenarioOutput = class extends Error {
|
|
6781
|
+
};
|
|
6666
6782
|
function assertPngAsset(asset) {
|
|
6667
6783
|
if (asset.originalMimeType && asset.originalMimeType !== MediaType.PNG) {
|
|
6668
|
-
throw new
|
|
6784
|
+
throw new UnsupportedScenarioOutput(
|
|
6669
6785
|
`Scenario asset ${asset.id} is ${asset.originalMimeType}; PixelKiln's Scenario MVP requires PNG output`
|
|
6670
6786
|
);
|
|
6671
6787
|
}
|
|
@@ -6732,7 +6848,7 @@ function redactScenarioError(value, secrets) {
|
|
|
6732
6848
|
// src/providers/comfyui.ts
|
|
6733
6849
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
6734
6850
|
import { readFile } from "fs/promises";
|
|
6735
|
-
import
|
|
6851
|
+
import path3 from "path";
|
|
6736
6852
|
var DEFAULT_BASE_URL3 = "http://127.0.0.1:8188";
|
|
6737
6853
|
var BUILTIN_BINDINGS = /* @__PURE__ */ new Set([
|
|
6738
6854
|
"prompt",
|
|
@@ -6869,7 +6985,7 @@ var ComfyUIProvider = class _ComfyUIProvider {
|
|
|
6869
6985
|
}
|
|
6870
6986
|
async resolveOptions(value, context) {
|
|
6871
6987
|
const options = parseOptions(value);
|
|
6872
|
-
const workflowPath =
|
|
6988
|
+
const workflowPath = path3.resolve(context.root, options.workflowFile);
|
|
6873
6989
|
let raw;
|
|
6874
6990
|
try {
|
|
6875
6991
|
raw = await readFile(workflowPath, "utf8");
|
|
@@ -6924,7 +7040,7 @@ var ComfyUIProvider = class _ComfyUIProvider {
|
|
|
6924
7040
|
if (typeof item !== "string") {
|
|
6925
7041
|
throw new Error(`ComfyUI ${label} must be a manifest-relative PNG or JPEG path`);
|
|
6926
7042
|
}
|
|
6927
|
-
const file =
|
|
7043
|
+
const file = path3.resolve(context.root, item);
|
|
6928
7044
|
let bytes;
|
|
6929
7045
|
try {
|
|
6930
7046
|
bytes = await readFile(file);
|
|
@@ -7636,7 +7752,7 @@ function canonical(value) {
|
|
|
7636
7752
|
}
|
|
7637
7753
|
|
|
7638
7754
|
// src/providers/fake.ts
|
|
7639
|
-
import { createHash as
|
|
7755
|
+
import { createHash as createHash3 } from "crypto";
|
|
7640
7756
|
var FAKE_PNG = Buffer.from(
|
|
7641
7757
|
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==",
|
|
7642
7758
|
"base64"
|
|
@@ -7738,7 +7854,7 @@ var FakeProvider = class {
|
|
|
7738
7854
|
}
|
|
7739
7855
|
async download(url) {
|
|
7740
7856
|
if (!url.startsWith("fake://")) throw new Error(`fake provider cannot download ${url}`);
|
|
7741
|
-
const salt =
|
|
7857
|
+
const salt = createHash3("sha256").update(url).digest();
|
|
7742
7858
|
return Buffer.concat([FAKE_PNG, salt.subarray(0, 4)]);
|
|
7743
7859
|
}
|
|
7744
7860
|
async balance() {
|
|
@@ -7887,7 +8003,7 @@ registerProvider({
|
|
|
7887
8003
|
// src/manifest.ts
|
|
7888
8004
|
import { readFile as readFile3 } from "fs/promises";
|
|
7889
8005
|
import { existsSync as existsSync4 } from "fs";
|
|
7890
|
-
import
|
|
8006
|
+
import path6 from "path";
|
|
7891
8007
|
|
|
7892
8008
|
// src/loop-directions.ts
|
|
7893
8009
|
var SINGLE_OUTPUT_FIELDS = ["file", "cell", "remoteId", "source", "sourceByStyle", "outputRole"];
|
|
@@ -7976,12 +8092,12 @@ function isObject2(value) {
|
|
|
7976
8092
|
|
|
7977
8093
|
// src/outputs.ts
|
|
7978
8094
|
import { existsSync as existsSync3 } from "fs";
|
|
7979
|
-
import
|
|
8095
|
+
import path5 from "path";
|
|
7980
8096
|
|
|
7981
8097
|
// src/lock.ts
|
|
7982
8098
|
import { readFile as readFile2, writeFile, rename, mkdir, rmdir, rm, stat } from "fs/promises";
|
|
7983
8099
|
import { existsSync as existsSync2 } from "fs";
|
|
7984
|
-
import
|
|
8100
|
+
import path4 from "path";
|
|
7985
8101
|
async function loadLock(lockPath) {
|
|
7986
8102
|
if (!existsSync2(lockPath)) return { version: 2, entries: {} };
|
|
7987
8103
|
try {
|
|
@@ -7998,7 +8114,7 @@ var saveQueues = /* @__PURE__ */ new Map();
|
|
|
7998
8114
|
var dirtyPatches = /* @__PURE__ */ new WeakMap();
|
|
7999
8115
|
var dirtyDeletes = /* @__PURE__ */ new WeakMap();
|
|
8000
8116
|
function saveLock(lockPath, lock) {
|
|
8001
|
-
const queueKey =
|
|
8117
|
+
const queueKey = path4.resolve(lockPath);
|
|
8002
8118
|
const previous = saveQueues.get(queueKey) ?? Promise.resolve();
|
|
8003
8119
|
const next = previous.catch(() => {
|
|
8004
8120
|
}).then(() => writeLockNow(lockPath, lock));
|
|
@@ -8010,7 +8126,7 @@ function saveLock(lockPath, lock) {
|
|
|
8010
8126
|
return next;
|
|
8011
8127
|
}
|
|
8012
8128
|
async function writeLockNow(lockPath, lock) {
|
|
8013
|
-
await mkdir(
|
|
8129
|
+
await mkdir(path4.dirname(path4.resolve(lockPath)), { recursive: true });
|
|
8014
8130
|
const release = await acquireFileLock(lockPath);
|
|
8015
8131
|
try {
|
|
8016
8132
|
await writeLockWhileHeld(lockPath, lock);
|
|
@@ -8155,22 +8271,22 @@ function totalSpend(lock, unit = "generations") {
|
|
|
8155
8271
|
|
|
8156
8272
|
// src/outputs.ts
|
|
8157
8273
|
function portableOutputPath(file, manifestDir) {
|
|
8158
|
-
const root =
|
|
8159
|
-
const absolute =
|
|
8160
|
-
const relative =
|
|
8161
|
-
const value =
|
|
8162
|
-
return value.split(
|
|
8274
|
+
const root = path5.resolve(manifestDir);
|
|
8275
|
+
const absolute = path5.isAbsolute(file) ? path5.normalize(file) : path5.resolve(root, file);
|
|
8276
|
+
const relative = path5.relative(root, absolute);
|
|
8277
|
+
const value = path5.isAbsolute(relative) ? absolute : relative;
|
|
8278
|
+
return value.split(path5.sep).join("/");
|
|
8163
8279
|
}
|
|
8164
8280
|
function resolveOutputPath(recordedPath, manifestDir) {
|
|
8165
|
-
if (
|
|
8166
|
-
if (
|
|
8167
|
-
return
|
|
8281
|
+
if (path5.isAbsolute(recordedPath)) return path5.normalize(recordedPath);
|
|
8282
|
+
if (path5.win32.isAbsolute(recordedPath)) return recordedPath;
|
|
8283
|
+
return path5.resolve(manifestDir, recordedPath.split(/[\\/]/).join(path5.sep));
|
|
8168
8284
|
}
|
|
8169
8285
|
function expectedOutputPath(spec, role, index, total, mediaType) {
|
|
8170
8286
|
return memberPath(spec.outFile, role, index, total, mediaType);
|
|
8171
8287
|
}
|
|
8172
8288
|
function memberPath(file, role, index, total, mediaType) {
|
|
8173
|
-
const originalExt =
|
|
8289
|
+
const originalExt = path5.extname(file);
|
|
8174
8290
|
const ext = mediaType ? mediaExtension(mediaType) : originalExt || ".png";
|
|
8175
8291
|
const stem = originalExt ? file.slice(0, -originalExt.length) : file;
|
|
8176
8292
|
if (total === 1) return `${stem}${ext}`;
|
|
@@ -8184,10 +8300,10 @@ function isFrameSetEntry(entry) {
|
|
|
8184
8300
|
return entry.generator === "frames" && isMemberSetEntry(entry);
|
|
8185
8301
|
}
|
|
8186
8302
|
function sourceIsStem(source, entry, manifestDir) {
|
|
8187
|
-
return isMemberSetEntry(entry) && !existsSync3(
|
|
8303
|
+
return isMemberSetEntry(entry) && !existsSync3(path5.resolve(manifestDir, source));
|
|
8188
8304
|
}
|
|
8189
8305
|
function sourceOutputPath(source, entry, index, manifestDir) {
|
|
8190
|
-
const absolute =
|
|
8306
|
+
const absolute = path5.resolve(manifestDir, source);
|
|
8191
8307
|
if (!sourceIsStem(source, entry, manifestDir)) return absolute;
|
|
8192
8308
|
const output = entry.outputs[index];
|
|
8193
8309
|
return memberPath(absolute, output.role, index, entry.outputs.length, output.mediaType);
|
|
@@ -8274,7 +8390,7 @@ function resolveSpecOutputs(spec, lock, manifestDir) {
|
|
|
8274
8390
|
id: spec.assetId,
|
|
8275
8391
|
index: 0,
|
|
8276
8392
|
path: spec.outFile,
|
|
8277
|
-
absolutePath:
|
|
8393
|
+
absolutePath: path5.resolve(spec.outFile),
|
|
8278
8394
|
sha256: ""
|
|
8279
8395
|
}];
|
|
8280
8396
|
}
|
|
@@ -8290,7 +8406,7 @@ function frameSetFps(entry) {
|
|
|
8290
8406
|
|
|
8291
8407
|
// src/manifest.ts
|
|
8292
8408
|
async function loadManifest(manifestPath) {
|
|
8293
|
-
const abs =
|
|
8409
|
+
const abs = path6.resolve(manifestPath);
|
|
8294
8410
|
if (!existsSync4(abs)) throw new ProjectError(`No manifest at ${abs}`);
|
|
8295
8411
|
const expansion = expandLoopDirections(JSON.parse(await readFile3(abs, "utf8")));
|
|
8296
8412
|
if (expansion.issues.length) {
|
|
@@ -8383,7 +8499,7 @@ ${issues}`);
|
|
|
8383
8499
|
${unknownReferences.map((i) => ` ${i}`).join("\n")}`);
|
|
8384
8500
|
}
|
|
8385
8501
|
const loopFamilies = Object.keys(expansion.families).length ? { loopFamilies: expansion.families } : {};
|
|
8386
|
-
return { manifest: parsed.data, root:
|
|
8502
|
+
return { manifest: parsed.data, root: path6.dirname(abs), path: abs, ...loopFamilies };
|
|
8387
8503
|
}
|
|
8388
8504
|
function parentAssetId(asset) {
|
|
8389
8505
|
return asset?.revision?.from ?? asset?.state?.of ?? asset?.animation?.of ?? asset?.portrait?.of ?? asset?.outfit?.of;
|
|
@@ -8484,7 +8600,7 @@ async function resolveSpecs(loaded, filter) {
|
|
|
8484
8600
|
}
|
|
8485
8601
|
const styleImageCache = /* @__PURE__ */ new Map();
|
|
8486
8602
|
async function loadStyleImage(rel, what = "Style image") {
|
|
8487
|
-
const abs =
|
|
8603
|
+
const abs = path6.resolve(root, rel);
|
|
8488
8604
|
let hit = styleImageCache.get(abs);
|
|
8489
8605
|
if (!hit) {
|
|
8490
8606
|
if (!existsSync4(abs)) throw new Error(`${what} not found: ${abs}`);
|
|
@@ -8514,7 +8630,7 @@ async function resolveSpecs(loaded, filter) {
|
|
|
8514
8630
|
styleImageDimensions.push(loadedImage);
|
|
8515
8631
|
}
|
|
8516
8632
|
const resolvedImages = style.styleImages.map((image) => {
|
|
8517
|
-
const hit = styleImageCache.get(
|
|
8633
|
+
const hit = styleImageCache.get(path6.resolve(root, image.path));
|
|
8518
8634
|
return { base64: hit.base64, width: hit.width, height: hit.height, format: hit.format };
|
|
8519
8635
|
});
|
|
8520
8636
|
const styleSpecs = /* @__PURE__ */ new Map();
|
|
@@ -8615,9 +8731,9 @@ async function resolveSpecs(loaded, filter) {
|
|
|
8615
8731
|
} else {
|
|
8616
8732
|
prompt = [style.promptPrefix, subject, style.promptSuffix].map((p) => p.trim()).filter(Boolean).join(", ");
|
|
8617
8733
|
}
|
|
8618
|
-
const relFile = asset.file ??
|
|
8619
|
-
const outFile =
|
|
8620
|
-
const qualityOutFile = style.quality ?
|
|
8734
|
+
const relFile = asset.file ?? path6.join(asset.category ?? "", `${assetId}.png`);
|
|
8735
|
+
const outFile = path6.resolve(root, style.outDir, relFile);
|
|
8736
|
+
const qualityOutFile = style.quality ? path6.resolve(root, style.quality.outDir, pngPath(relFile)) : void 0;
|
|
8621
8737
|
const tileSize = generator === "tiles" ? size : style.tileSize ?? 32;
|
|
8622
8738
|
const tileVariations = tileFeatureOutputCount(generator === "tiles" ? style.tileFeature : void 0) ?? tileVariationCount(countNumberedDescriptions(prompt));
|
|
8623
8739
|
const terrainTiles = generator === "terrain" ? terrainTileCount(style.terrainTransitionSize) : 0;
|
|
@@ -8712,7 +8828,7 @@ async function resolveSpecs(loaded, filter) {
|
|
|
8712
8828
|
minGridConfidence: style.quality.minGridConfidence,
|
|
8713
8829
|
...style.quality.minTransparency == null ? {} : { minTransparency: style.quality.minTransparency },
|
|
8714
8830
|
...style.quality.fixerRevision ? { fixerRevision: style.quality.fixerRevision } : {},
|
|
8715
|
-
...style.quality.fixerPython ? { fixerPython:
|
|
8831
|
+
...style.quality.fixerPython ? { fixerPython: path6.resolve(root, style.quality.fixerPython) } : {},
|
|
8716
8832
|
...style.quality.fps == null ? {} : { fps: style.quality.fps }
|
|
8717
8833
|
}
|
|
8718
8834
|
} : {},
|
|
@@ -8848,7 +8964,7 @@ async function resolveSpecs(loaded, filter) {
|
|
|
8848
8964
|
animation: sourceSpec.character.animation,
|
|
8849
8965
|
outfit: {
|
|
8850
8966
|
reference: {
|
|
8851
|
-
path:
|
|
8967
|
+
path: path6.resolve(root, asset.outfit.reference),
|
|
8852
8968
|
sha256: referenceImage.hash,
|
|
8853
8969
|
width: referenceImage.width,
|
|
8854
8970
|
height: referenceImage.height,
|
|
@@ -8870,23 +8986,23 @@ async function resolveSpecs(loaded, filter) {
|
|
|
8870
8986
|
);
|
|
8871
8987
|
}
|
|
8872
8988
|
const sourceSpec = await finalize(asset.revision.from);
|
|
8873
|
-
const sourceStem = sourceSpec.quality?.outFile ?? (sourceSpec.source ?
|
|
8989
|
+
const sourceStem = sourceSpec.quality?.outFile ?? (sourceSpec.source ? path6.resolve(root, sourceSpec.source) : sourceSpec.outFile);
|
|
8874
8990
|
const sourceMembers = await revisionSourceMembers(sourceStem, `revision source ${asset.revision.from}`);
|
|
8875
8991
|
const sourceFile = sourceMembers?.[0]?.file ?? sourceStem;
|
|
8876
8992
|
const sourceImage = sourceMembers ? { ...sourceMembers[0], hash: sha256(JSON.stringify(sourceMembers.map((member) => member.sha256))) } : await optionalRevisionImage(sourceFile, "revision source");
|
|
8877
8993
|
const sourceFps = sourceSpec.character?.animation?.fps ?? sourceSpec.objectPro?.animation?.fps ?? sourceSpec.revision?.fps ?? sourceSpec.revision?.sourceFps;
|
|
8878
|
-
const maskFile = asset.revision.mask ?
|
|
8994
|
+
const maskFile = asset.revision.mask ? path6.resolve(root, asset.revision.mask) : void 0;
|
|
8879
8995
|
const maskImage = maskFile ? await optionalRevisionImage(maskFile, "revision mask", "png") : null;
|
|
8880
8996
|
if (sourceImage && maskImage && (sourceImage.width !== maskImage.width || sourceImage.height !== maskImage.height)) {
|
|
8881
8997
|
throw new Error(
|
|
8882
8998
|
`Revision mask for ${styleId}/${assetId} is ${maskImage.width}x${maskImage.height}; source ${asset.revision.from} is ${sourceImage.width}x${sourceImage.height}`
|
|
8883
8999
|
);
|
|
8884
9000
|
}
|
|
8885
|
-
const paletteImageFile = asset.revision.paletteImage ?
|
|
9001
|
+
const paletteImageFile = asset.revision.paletteImage ? path6.resolve(root, asset.revision.paletteImage) : void 0;
|
|
8886
9002
|
const paletteImage = paletteImageFile ? await optionalRevisionImage(paletteImageFile, "revision palette image", "png") : null;
|
|
8887
|
-
const lastFrameFile = asset.revision.lastFrame ?
|
|
9003
|
+
const lastFrameFile = asset.revision.lastFrame ? path6.resolve(root, asset.revision.lastFrame) : void 0;
|
|
8888
9004
|
const lastFrame = lastFrameFile ? await optionalRevisionImage(lastFrameFile, "revision last frame") : null;
|
|
8889
|
-
const keypointsFile = asset.revision.keypointsFile ?
|
|
9005
|
+
const keypointsFile = asset.revision.keypointsFile ? path6.resolve(root, asset.revision.keypointsFile) : void 0;
|
|
8890
9006
|
const keypoints = keypointsFile ? await optionalSkeletonSet(keypointsFile) : null;
|
|
8891
9007
|
resolved.revision = {
|
|
8892
9008
|
mode: asset.revision.mode,
|
|
@@ -9053,7 +9169,7 @@ async function resolveSpecs(loaded, filter) {
|
|
|
9053
9169
|
return specs;
|
|
9054
9170
|
}
|
|
9055
9171
|
function pngPath(file) {
|
|
9056
|
-
const extension =
|
|
9172
|
+
const extension = path6.extname(file);
|
|
9057
9173
|
return extension ? `${file.slice(0, -extension.length)}.png` : `${file}.png`;
|
|
9058
9174
|
}
|
|
9059
9175
|
async function resolveMapScene(root, styleId, assetId, generator, scene, provider) {
|
|
@@ -9063,12 +9179,12 @@ async function resolveMapScene(root, styleId, assetId, generator, scene, provide
|
|
|
9063
9179
|
if (!provider.supportsMapScene) {
|
|
9064
9180
|
throw new Error(`assets.${assetId}.scene: provider "${provider.id}" cannot draw into a scene; PixelLab's /map-objects can`);
|
|
9065
9181
|
}
|
|
9066
|
-
const file =
|
|
9182
|
+
const file = path6.resolve(root, scene.image);
|
|
9067
9183
|
const image = await optionalRevisionImage(file, "scene image", "png");
|
|
9068
9184
|
const base = { file, sha256: image?.hash ?? null, width: image?.width ?? null, height: image?.height ?? null };
|
|
9069
9185
|
const placement = scene.placement;
|
|
9070
9186
|
if ("mask" in placement) {
|
|
9071
|
-
const maskFile =
|
|
9187
|
+
const maskFile = path6.resolve(root, placement.mask);
|
|
9072
9188
|
const mask = await optionalRevisionImage(maskFile, "scene mask", "png");
|
|
9073
9189
|
if (image && mask && (image.width !== mask.width || image.height !== mask.height)) {
|
|
9074
9190
|
throw new Error(
|
|
@@ -9140,7 +9256,7 @@ async function resolveStyleImages(loaded, styleId) {
|
|
|
9140
9256
|
if (!style) throw new Error(`Unknown style "${styleId}"`);
|
|
9141
9257
|
const out = [];
|
|
9142
9258
|
for (const img of style.styleImages) {
|
|
9143
|
-
const file =
|
|
9259
|
+
const file = path6.resolve(loaded.root, img.path);
|
|
9144
9260
|
const buf = await readFile3(file);
|
|
9145
9261
|
const metadata = imageMetadata(buf);
|
|
9146
9262
|
if (!metadata) throw new Error(`Style image is not a readable PNG or JPEG: ${file}`);
|
|
@@ -9171,7 +9287,7 @@ async function resolveCharacterShape(asset, style, kind, files) {
|
|
|
9171
9287
|
};
|
|
9172
9288
|
if (asset.concept) {
|
|
9173
9289
|
const image = await files.load(asset.concept, "Concept image");
|
|
9174
|
-
shape.concept = { path:
|
|
9290
|
+
shape.concept = { path: path6.resolve(files.root, asset.concept), sha256: image.hash, width: image.width, height: image.height, format: image.format };
|
|
9175
9291
|
}
|
|
9176
9292
|
if (asset.reference) {
|
|
9177
9293
|
const byDirection = typeof asset.reference === "string" ? { south: asset.reference } : asset.reference;
|
|
@@ -9179,7 +9295,7 @@ async function resolveCharacterShape(asset, style, kind, files) {
|
|
|
9179
9295
|
for (const [direction, rel] of Object.entries(byDirection)) {
|
|
9180
9296
|
const image = await files.load(rel, `Reference sprite (${direction})`);
|
|
9181
9297
|
shape.reference[direction] = {
|
|
9182
|
-
path:
|
|
9298
|
+
path: path6.resolve(files.root, rel),
|
|
9183
9299
|
sha256: image.hash,
|
|
9184
9300
|
width: image.width,
|
|
9185
9301
|
height: image.height,
|
|
@@ -9200,7 +9316,7 @@ async function resolveCharacterShape(asset, style, kind, files) {
|
|
|
9200
9316
|
const animationMode = asset.animation.mode ?? (asset.animation.template ? "template" : "v3");
|
|
9201
9317
|
const pose = async (rel, what) => {
|
|
9202
9318
|
const image = await files.load(rel, what);
|
|
9203
|
-
return { path:
|
|
9319
|
+
return { path: path6.resolve(files.root, rel), sha256: image.hash, width: image.width, height: image.height, format: image.format };
|
|
9204
9320
|
};
|
|
9205
9321
|
shape.animation = {
|
|
9206
9322
|
mode: animationMode,
|
|
@@ -9231,7 +9347,7 @@ async function resolveObjectProShape(assetId, asset, style, kind, files) {
|
|
|
9231
9347
|
const south = typeof asset.reference === "string" ? asset.reference : asset.reference.south;
|
|
9232
9348
|
if (south) {
|
|
9233
9349
|
const image = await files.load(south, "Reference sprite");
|
|
9234
|
-
shape.reference = { path:
|
|
9350
|
+
shape.reference = { path: path6.resolve(files.root, south), sha256: image.hash, width: image.width, height: image.height, format: image.format };
|
|
9235
9351
|
}
|
|
9236
9352
|
}
|
|
9237
9353
|
if (asset.state) {
|
|
@@ -9259,7 +9375,7 @@ async function resolveObjectProShape(assetId, asset, style, kind, files) {
|
|
|
9259
9375
|
}
|
|
9260
9376
|
const pose = async (rel, what) => {
|
|
9261
9377
|
const image = await files.load(rel, what);
|
|
9262
|
-
return { path:
|
|
9378
|
+
return { path: path6.resolve(files.root, rel), sha256: image.hash, width: image.width, height: image.height, format: image.format };
|
|
9263
9379
|
};
|
|
9264
9380
|
shape.animation = {
|
|
9265
9381
|
mode: asset.animation.mode ?? "v3",
|
|
@@ -9279,11 +9395,11 @@ function characterDirections(directions) {
|
|
|
9279
9395
|
}
|
|
9280
9396
|
|
|
9281
9397
|
// src/project.ts
|
|
9282
|
-
import
|
|
9398
|
+
import path14 from "path";
|
|
9283
9399
|
|
|
9284
9400
|
// src/env.ts
|
|
9285
|
-
import { readFileSync as
|
|
9286
|
-
import
|
|
9401
|
+
import { readFileSync as readFileSync3, existsSync as existsSync5 } from "fs";
|
|
9402
|
+
import path7 from "path";
|
|
9287
9403
|
function loadEnvFiles(dir) {
|
|
9288
9404
|
const loaded = [];
|
|
9289
9405
|
for (const [file, vars] of envFiles(dir)) {
|
|
@@ -9297,10 +9413,10 @@ function loadEnvFiles(dir) {
|
|
|
9297
9413
|
function envFiles(dir) {
|
|
9298
9414
|
const out = [];
|
|
9299
9415
|
for (const name of [".env.local", ".env"]) {
|
|
9300
|
-
const file =
|
|
9416
|
+
const file = path7.join(dir, name);
|
|
9301
9417
|
if (!existsSync5(file)) continue;
|
|
9302
9418
|
try {
|
|
9303
|
-
out.push([file, parseEnv(
|
|
9419
|
+
out.push([file, parseEnv(readFileSync3(file, "utf8"))]);
|
|
9304
9420
|
} catch {
|
|
9305
9421
|
}
|
|
9306
9422
|
}
|
|
@@ -9327,11 +9443,11 @@ function parseEnv(contents) {
|
|
|
9327
9443
|
|
|
9328
9444
|
// src/pipeline/plan.ts
|
|
9329
9445
|
import { existsSync as existsSync8 } from "fs";
|
|
9330
|
-
import
|
|
9446
|
+
import path13 from "path";
|
|
9331
9447
|
|
|
9332
9448
|
// src/pipeline/mirror.ts
|
|
9333
9449
|
import { mkdir as mkdir2, readFile as readFile4, rename as rename2, writeFile as writeFile2 } from "fs/promises";
|
|
9334
|
-
import
|
|
9450
|
+
import path8 from "path";
|
|
9335
9451
|
|
|
9336
9452
|
// src/palette.ts
|
|
9337
9453
|
function normalizePalette(values) {
|
|
@@ -9470,7 +9586,7 @@ async function deriveMirror(spec, lock, lockPath, opts = {}) {
|
|
|
9470
9586
|
for (const [index, member] of members.entries()) {
|
|
9471
9587
|
const bytes = flipHorizontal(await readFile4(member.file));
|
|
9472
9588
|
const target = expectedOutputPath(spec, member.role, index, members.length, MediaType.PNG);
|
|
9473
|
-
await mkdir2(
|
|
9589
|
+
await mkdir2(path8.dirname(target), { recursive: true });
|
|
9474
9590
|
const tmp = `${target}.pixelkiln.tmp`;
|
|
9475
9591
|
await writeFile2(tmp, bytes);
|
|
9476
9592
|
await rename2(tmp, target);
|
|
@@ -9533,28 +9649,28 @@ async function deriveMirror(spec, lock, lockPath, opts = {}) {
|
|
|
9533
9649
|
|
|
9534
9650
|
// src/pipeline/quality-profile.ts
|
|
9535
9651
|
import { existsSync as existsSync6 } from "fs";
|
|
9536
|
-
import
|
|
9652
|
+
import path11 from "path";
|
|
9537
9653
|
|
|
9538
9654
|
// src/pipeline/refine.ts
|
|
9539
|
-
import
|
|
9655
|
+
import path10 from "path";
|
|
9540
9656
|
import { execFile } from "child_process";
|
|
9541
9657
|
import { mkdtemp, readFile as readFile6, rm as rm3 } from "fs/promises";
|
|
9542
9658
|
import { tmpdir } from "os";
|
|
9543
9659
|
import { promisify } from "util";
|
|
9544
9660
|
|
|
9545
9661
|
// src/artifacts.ts
|
|
9546
|
-
import
|
|
9547
|
-
import { createHash as
|
|
9662
|
+
import path9 from "path";
|
|
9663
|
+
import { createHash as createHash4, randomUUID as randomUUID3 } from "crypto";
|
|
9548
9664
|
import { mkdir as mkdir3, readFile as readFile5, rename as rename3, rm as rm2, writeFile as writeFile3 } from "fs/promises";
|
|
9549
9665
|
var activeTransactions = /* @__PURE__ */ new Set();
|
|
9550
9666
|
function message(error) {
|
|
9551
9667
|
return error instanceof Error ? error.message : String(error);
|
|
9552
9668
|
}
|
|
9553
9669
|
function digest(data) {
|
|
9554
|
-
return
|
|
9670
|
+
return createHash4("sha256").update(data).digest("hex");
|
|
9555
9671
|
}
|
|
9556
9672
|
function portableRelative(from, to) {
|
|
9557
|
-
return
|
|
9673
|
+
return path9.relative(from, path9.resolve(to)).split(path9.sep).join("/") || ".";
|
|
9558
9674
|
}
|
|
9559
9675
|
function canonical2(value) {
|
|
9560
9676
|
if (value === null || typeof value === "string" || typeof value === "boolean") return value;
|
|
@@ -9604,7 +9720,7 @@ function parseArtifactManifest(absolute, data) {
|
|
|
9604
9720
|
return raw;
|
|
9605
9721
|
}
|
|
9606
9722
|
async function readArtifactBundleManifest(manifestPath) {
|
|
9607
|
-
const absolute =
|
|
9723
|
+
const absolute = path9.resolve(manifestPath);
|
|
9608
9724
|
return parseArtifactManifest(absolute, await readFile5(absolute, "utf8"));
|
|
9609
9725
|
}
|
|
9610
9726
|
async function readOptional(file) {
|
|
@@ -9628,8 +9744,8 @@ function processIsAlive(pid) {
|
|
|
9628
9744
|
}
|
|
9629
9745
|
}
|
|
9630
9746
|
function validTemporaryPath(candidate, destination, type) {
|
|
9631
|
-
return
|
|
9632
|
-
`.${
|
|
9747
|
+
return path9.dirname(candidate) === path9.dirname(destination) && path9.basename(candidate).startsWith(
|
|
9748
|
+
`.${path9.basename(destination)}.pixelkiln-${type}-`
|
|
9633
9749
|
);
|
|
9634
9750
|
}
|
|
9635
9751
|
function parseTransaction(journal, data) {
|
|
@@ -9649,7 +9765,7 @@ async function removeTransactionFiles(journal) {
|
|
|
9649
9765
|
await rm2(transactionMarker(journal), { force: true });
|
|
9650
9766
|
}
|
|
9651
9767
|
async function recoverArtifactTransaction(recoveryFile, allowedDestinations) {
|
|
9652
|
-
const journal =
|
|
9768
|
+
const journal = path9.resolve(recoveryFile);
|
|
9653
9769
|
const bytes = await readOptional(journal);
|
|
9654
9770
|
if (!bytes) {
|
|
9655
9771
|
await rm2(transactionMarker(journal), { force: true });
|
|
@@ -9665,7 +9781,7 @@ async function recoverArtifactTransaction(recoveryFile, allowedDestinations) {
|
|
|
9665
9781
|
);
|
|
9666
9782
|
}
|
|
9667
9783
|
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(
|
|
9784
|
+
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
9785
|
throw new Error(
|
|
9670
9786
|
`Refusing unsafe artifact recovery from ${journal}; its destinations or temporary paths do not match the current bundle.`
|
|
9671
9787
|
);
|
|
@@ -9711,7 +9827,7 @@ async function recoverArtifactTransaction(recoveryFile, allowedDestinations) {
|
|
|
9711
9827
|
await removeTransactionFiles(journal);
|
|
9712
9828
|
}
|
|
9713
9829
|
function createArtifactBundleManifest(manifestPath, outputs, provenance) {
|
|
9714
|
-
const root =
|
|
9830
|
+
const root = path9.dirname(path9.resolve(manifestPath));
|
|
9715
9831
|
const sources = provenance.sources.map((source) => ({
|
|
9716
9832
|
...source,
|
|
9717
9833
|
path: portableRelative(root, source.path)
|
|
@@ -9741,11 +9857,11 @@ function withArtifactManifest(manifestPath, outputs, provenance) {
|
|
|
9741
9857
|
];
|
|
9742
9858
|
}
|
|
9743
9859
|
async function writeManagedArtifactBundle(manifestPath, outputs, provenance, options = {}) {
|
|
9744
|
-
const absoluteManifest =
|
|
9860
|
+
const absoluteManifest = path9.resolve(manifestPath);
|
|
9745
9861
|
const recoveryFile = `${absoluteManifest}.transaction`;
|
|
9746
9862
|
const allowedDestinations = /* @__PURE__ */ new Set([
|
|
9747
9863
|
absoluteManifest,
|
|
9748
|
-
...outputs.map((output) =>
|
|
9864
|
+
...outputs.map((output) => path9.resolve(output.path))
|
|
9749
9865
|
]);
|
|
9750
9866
|
await recoverArtifactTransaction(recoveryFile, allowedDestinations);
|
|
9751
9867
|
const existingManifestBytes = await readOptional(absoluteManifest);
|
|
@@ -9763,13 +9879,13 @@ async function writeManagedArtifactBundle(manifestPath, outputs, provenance, opt
|
|
|
9763
9879
|
}
|
|
9764
9880
|
}
|
|
9765
9881
|
if (!options.force) {
|
|
9766
|
-
const root =
|
|
9882
|
+
const root = path9.dirname(absoluteManifest);
|
|
9767
9883
|
const recorded = new Map(
|
|
9768
|
-
previous?.outputs.map((output) => [
|
|
9884
|
+
previous?.outputs.map((output) => [path9.resolve(root, output.path), output.sha256]) ?? []
|
|
9769
9885
|
);
|
|
9770
9886
|
const conflicts = [];
|
|
9771
9887
|
for (const output of outputs) {
|
|
9772
|
-
const destination =
|
|
9888
|
+
const destination = path9.resolve(output.path);
|
|
9773
9889
|
const current = await readOptional(destination);
|
|
9774
9890
|
if (!current || digest(current) === digest(output.data)) continue;
|
|
9775
9891
|
const expected = recorded.get(destination);
|
|
@@ -9791,15 +9907,15 @@ Pass { force: true } (CLI: --force) to take ownership and replace the complete b
|
|
|
9791
9907
|
);
|
|
9792
9908
|
}
|
|
9793
9909
|
async function verifyArtifactBundle(manifestPath) {
|
|
9794
|
-
const absolute =
|
|
9910
|
+
const absolute = path9.resolve(manifestPath);
|
|
9795
9911
|
const raw = await readArtifactBundleManifest(absolute);
|
|
9796
|
-
const root =
|
|
9912
|
+
const root = path9.dirname(absolute);
|
|
9797
9913
|
const changedSources = [];
|
|
9798
9914
|
const changedOutputs = [];
|
|
9799
9915
|
for (const source of raw.sources) {
|
|
9800
9916
|
let actual = null;
|
|
9801
9917
|
try {
|
|
9802
|
-
actual = digest(await readFile5(
|
|
9918
|
+
actual = digest(await readFile5(path9.resolve(root, source.path)));
|
|
9803
9919
|
} catch (error) {
|
|
9804
9920
|
if (!isCode(error, "ENOENT")) throw error;
|
|
9805
9921
|
}
|
|
@@ -9807,7 +9923,7 @@ async function verifyArtifactBundle(manifestPath) {
|
|
|
9807
9923
|
}
|
|
9808
9924
|
for (const output of raw.outputs) {
|
|
9809
9925
|
try {
|
|
9810
|
-
if (digest(await readFile5(
|
|
9926
|
+
if (digest(await readFile5(path9.resolve(root, output.path))) !== output.sha256) {
|
|
9811
9927
|
changedOutputs.push(output.path);
|
|
9812
9928
|
}
|
|
9813
9929
|
} catch (error) {
|
|
@@ -9863,7 +9979,7 @@ async function writeArtifactBundle(files, options = {}) {
|
|
|
9863
9979
|
if (!files.length) throw new Error("An artifact bundle must contain at least one file.");
|
|
9864
9980
|
const normalized = files.map((file) => {
|
|
9865
9981
|
if (!file.path.trim()) throw new Error("Artifact paths cannot be empty.");
|
|
9866
|
-
return { destination:
|
|
9982
|
+
return { destination: path9.resolve(file.path), data: Buffer.from(file.data) };
|
|
9867
9983
|
});
|
|
9868
9984
|
const destinations = /* @__PURE__ */ new Set();
|
|
9869
9985
|
for (const artifact of normalized) {
|
|
@@ -9893,21 +10009,21 @@ async function writeArtifactBundle(files, options = {}) {
|
|
|
9893
10009
|
if (!changed.length) return { changed: [], unchanged };
|
|
9894
10010
|
const token = randomUUID3();
|
|
9895
10011
|
for (const [index, artifact] of changed.entries()) {
|
|
9896
|
-
const basename =
|
|
9897
|
-
artifact.stage =
|
|
9898
|
-
|
|
10012
|
+
const basename = path9.basename(artifact.destination);
|
|
10013
|
+
artifact.stage = path9.join(
|
|
10014
|
+
path9.dirname(artifact.destination),
|
|
9899
10015
|
`.${basename}.pixelkiln-stage-${token}-${index}`
|
|
9900
10016
|
);
|
|
9901
10017
|
if (artifact.existed) {
|
|
9902
|
-
artifact.backup =
|
|
9903
|
-
|
|
10018
|
+
artifact.backup = path9.join(
|
|
10019
|
+
path9.dirname(artifact.destination),
|
|
9904
10020
|
`.${basename}.pixelkiln-backup-${token}-${index}`
|
|
9905
10021
|
);
|
|
9906
10022
|
}
|
|
9907
10023
|
}
|
|
9908
|
-
const journal = options.recoveryFile ?
|
|
10024
|
+
const journal = options.recoveryFile ? path9.resolve(options.recoveryFile) : null;
|
|
9909
10025
|
if (journal) {
|
|
9910
|
-
await mkdir3(
|
|
10026
|
+
await mkdir3(path9.dirname(journal), { recursive: true });
|
|
9911
10027
|
const transaction = {
|
|
9912
10028
|
format: "pixelkiln-artifact-transaction",
|
|
9913
10029
|
version: 1,
|
|
@@ -9932,7 +10048,7 @@ async function writeArtifactBundle(files, options = {}) {
|
|
|
9932
10048
|
}
|
|
9933
10049
|
try {
|
|
9934
10050
|
for (const [index, artifact] of changed.entries()) {
|
|
9935
|
-
await mkdir3(
|
|
10051
|
+
await mkdir3(path9.dirname(artifact.destination), { recursive: true });
|
|
9936
10052
|
await options.beforeStage?.(artifact.destination, index);
|
|
9937
10053
|
await writeFile3(artifact.stage, artifact.data, { flag: "wx" });
|
|
9938
10054
|
}
|
|
@@ -10198,10 +10314,10 @@ function sameFrameGrid(first, next) {
|
|
|
10198
10314
|
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
10315
|
}
|
|
10200
10316
|
async function refineAsset(options) {
|
|
10201
|
-
const source =
|
|
10202
|
-
const output =
|
|
10317
|
+
const source = path10.resolve(options.source);
|
|
10318
|
+
const output = path10.resolve(options.output);
|
|
10203
10319
|
if (source === output) throw new Error("Refinement source and output must be different files.");
|
|
10204
|
-
const record =
|
|
10320
|
+
const record = path10.resolve(qualityRecordPath(output));
|
|
10205
10321
|
const palette = normalizePalette(options.palette).map((color) => color.hex);
|
|
10206
10322
|
const minGridConfidence = requireConfidence(options.minGridConfidence ?? "high");
|
|
10207
10323
|
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 +10340,8 @@ async function refineAsset(options) {
|
|
|
10224
10340
|
);
|
|
10225
10341
|
}
|
|
10226
10342
|
}
|
|
10227
|
-
const temp = await mkdtemp(
|
|
10228
|
-
const recovered =
|
|
10343
|
+
const temp = await mkdtemp(path10.join(tmpdir(), "pixelkiln-refine-"));
|
|
10344
|
+
const recovered = path10.join(temp, "native.png");
|
|
10229
10345
|
const fixerCommand = compatibilityCommand ?? options.fixerPython ?? process.env.PIXELKILN_PIXEL_FIXER_PYTHON ?? "python3";
|
|
10230
10346
|
const args = compatibilityCommand ? [...options.fixerArgsPrefix ?? [], source, "--extract", recovered] : ["-c", PYTHON_FIXER_BRIDGE, source, recovered];
|
|
10231
10347
|
try {
|
|
@@ -10310,18 +10426,18 @@ async function refineFrameSet(options) {
|
|
|
10310
10426
|
if (roles.some((role) => !role.trim()) || new Set(roles).size !== roles.length) {
|
|
10311
10427
|
throw new Error("Frame-set roles must be non-empty and unique.");
|
|
10312
10428
|
}
|
|
10313
|
-
const output =
|
|
10314
|
-
const record =
|
|
10429
|
+
const output = path10.resolve(options.output);
|
|
10430
|
+
const record = path10.resolve(qualityRecordPath(output));
|
|
10315
10431
|
const palette = normalizeRefinementPalette(options.palette);
|
|
10316
10432
|
const finalOutputs = options.sources.map((source, index) => qualityFrameOutputPath(output, source.role, index));
|
|
10317
10433
|
if (new Set(finalOutputs).size !== finalOutputs.length) {
|
|
10318
10434
|
throw new Error("Frame-set roles resolve to duplicate output filenames.");
|
|
10319
10435
|
}
|
|
10320
|
-
const sourceSet = new Set(options.sources.map((source) =>
|
|
10436
|
+
const sourceSet = new Set(options.sources.map((source) => path10.resolve(source.path)));
|
|
10321
10437
|
if (finalOutputs.some((file) => sourceSet.has(file))) {
|
|
10322
10438
|
throw new Error("Frame-set refinement sources and outputs must be different files.");
|
|
10323
10439
|
}
|
|
10324
|
-
const temp = await mkdtemp(
|
|
10440
|
+
const temp = await mkdtemp(path10.join(tmpdir(), "pixelkiln-refine-frames-"));
|
|
10325
10441
|
try {
|
|
10326
10442
|
const frames = [];
|
|
10327
10443
|
const outputs = [];
|
|
@@ -10329,8 +10445,8 @@ async function refineFrameSet(options) {
|
|
|
10329
10445
|
const sourcePaths = [];
|
|
10330
10446
|
let firstOptions = null;
|
|
10331
10447
|
for (const [index, sourceEntry] of options.sources.entries()) {
|
|
10332
|
-
const source =
|
|
10333
|
-
const temporaryOutput =
|
|
10448
|
+
const source = path10.resolve(sourceEntry.path);
|
|
10449
|
+
const temporaryOutput = path10.join(temp, `${String(index).padStart(2, "0")}.png`);
|
|
10334
10450
|
const refined = await refineAsset({
|
|
10335
10451
|
source,
|
|
10336
10452
|
output: temporaryOutput,
|
|
@@ -10405,7 +10521,7 @@ async function refineFrameSet(options) {
|
|
|
10405
10521
|
);
|
|
10406
10522
|
return {
|
|
10407
10523
|
sources: sourcePaths,
|
|
10408
|
-
outputs: outputs.map((item) =>
|
|
10524
|
+
outputs: outputs.map((item) => path10.resolve(item.path)),
|
|
10409
10525
|
record,
|
|
10410
10526
|
frames,
|
|
10411
10527
|
palette,
|
|
@@ -10416,12 +10532,12 @@ async function refineFrameSet(options) {
|
|
|
10416
10532
|
}
|
|
10417
10533
|
}
|
|
10418
10534
|
async function checkQualityRecord(recordPath) {
|
|
10419
|
-
const record =
|
|
10535
|
+
const record = path10.resolve(recordPath);
|
|
10420
10536
|
const manifest = await readArtifactBundleManifest(record);
|
|
10421
10537
|
const options = parseRecordOptions(manifest, record);
|
|
10422
10538
|
const verification = await verifyArtifactBundle(record);
|
|
10423
|
-
const sources = manifest.sources.map((source2) =>
|
|
10424
|
-
const outputs = manifest.outputs.map((output2) =>
|
|
10539
|
+
const sources = manifest.sources.map((source2) => path10.resolve(path10.dirname(record), source2.path));
|
|
10540
|
+
const outputs = manifest.outputs.map((output2) => path10.resolve(path10.dirname(record), output2.path));
|
|
10425
10541
|
const source = sources[0] ?? "";
|
|
10426
10542
|
const output = outputs[0] ?? "";
|
|
10427
10543
|
const expectedCount = options.frameSet?.count ?? 1;
|
|
@@ -10454,7 +10570,7 @@ async function checkQualityRecord(recordPath) {
|
|
|
10454
10570
|
async function approveQualityRecord(recordPath, approval) {
|
|
10455
10571
|
const reviewer = approval.reviewer.trim();
|
|
10456
10572
|
if (!reviewer) throw new Error("A human reviewer name is required.");
|
|
10457
|
-
const record =
|
|
10573
|
+
const record = path10.resolve(recordPath);
|
|
10458
10574
|
const manifest = await readArtifactBundleManifest(record);
|
|
10459
10575
|
const options = parseRecordOptions(manifest, record);
|
|
10460
10576
|
const verification = await verifyArtifactBundle(record);
|
|
@@ -10475,14 +10591,14 @@ async function approveQualityRecord(recordPath, approval) {
|
|
|
10475
10591
|
`A refinement record must contain exactly ${expectedCount} source(s) and output(s).`
|
|
10476
10592
|
);
|
|
10477
10593
|
}
|
|
10478
|
-
const root =
|
|
10594
|
+
const root = path10.dirname(record);
|
|
10479
10595
|
const outputs = await Promise.all(manifest.outputs.map(async (entry) => {
|
|
10480
|
-
const outputPath =
|
|
10596
|
+
const outputPath = path10.resolve(root, entry.path);
|
|
10481
10597
|
return { path: outputPath, data: await readFile6(outputPath) };
|
|
10482
10598
|
}));
|
|
10483
10599
|
const sources = manifest.sources.map((source) => ({
|
|
10484
10600
|
...source,
|
|
10485
|
-
path:
|
|
10601
|
+
path: path10.resolve(root, source.path)
|
|
10486
10602
|
}));
|
|
10487
10603
|
const approved = {
|
|
10488
10604
|
...options,
|
|
@@ -10514,7 +10630,7 @@ async function qualitySource(spec, lock) {
|
|
|
10514
10630
|
if (spec.generator === "frames") {
|
|
10515
10631
|
return { items: null, reason: "frame-set quality requires ordered downloaded provider outputs" };
|
|
10516
10632
|
}
|
|
10517
|
-
const source =
|
|
10633
|
+
const source = path11.resolve(spec.root, spec.source);
|
|
10518
10634
|
return existsSync6(source) ? { items: [{ role: "source", path: source }] } : { items: null, reason: `declared source is missing: ${spec.source}` };
|
|
10519
10635
|
}
|
|
10520
10636
|
const entry = lock.entries[lockKey(spec.styleId, spec.assetId)];
|
|
@@ -10581,7 +10697,7 @@ async function inspectQualityProfile(spec, lock) {
|
|
|
10581
10697
|
const expectedRevision = spec.quality.fixerRevision ?? PIXEL_ART_FIXER_REVISION;
|
|
10582
10698
|
const expectedTransparency = spec.quality.minTransparency ?? null;
|
|
10583
10699
|
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) =>
|
|
10700
|
+
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
10701
|
if (contractChanged) {
|
|
10586
10702
|
return {
|
|
10587
10703
|
key,
|
|
@@ -10745,7 +10861,7 @@ async function requireApprovedQualitySources(specs, lock, assetIds, outputRoles)
|
|
|
10745
10861
|
|
|
10746
10862
|
// src/pipeline/revision.ts
|
|
10747
10863
|
import { existsSync as existsSync7 } from "fs";
|
|
10748
|
-
import
|
|
10864
|
+
import path12 from "path";
|
|
10749
10865
|
async function inspectRevisionReadiness(spec, lock) {
|
|
10750
10866
|
if (spec.mirror) return inspectMirror(spec, lock, /* @__PURE__ */ new Set());
|
|
10751
10867
|
if (spec.character?.kind === "outfit") return inspectOutfit(spec, lock, /* @__PURE__ */ new Set());
|
|
@@ -10888,8 +11004,8 @@ async function inspectSourceMembers(spec, lock) {
|
|
|
10888
11004
|
if (parent.source) return { ready: true, reason: "declared source set is present" };
|
|
10889
11005
|
const entry = lock.entries[lockKey(parent.styleId, parent.assetId)];
|
|
10890
11006
|
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) =>
|
|
11007
|
+
const recorded = entry.outputs.map((_, index) => path12.resolve(currentEntryOutputPath(entry, parent, index))).sort();
|
|
11008
|
+
const found = members.map((member) => path12.resolve(member.file)).sort();
|
|
10893
11009
|
if (recorded.length !== found.length || recorded.some((file, index) => file !== found[index])) {
|
|
10894
11010
|
return {
|
|
10895
11011
|
ready: false,
|
|
@@ -10938,7 +11054,7 @@ async function inspectParent(spec, lock, seen) {
|
|
|
10938
11054
|
};
|
|
10939
11055
|
}
|
|
10940
11056
|
if (spec.source) {
|
|
10941
|
-
const source =
|
|
11057
|
+
const source = path12.resolve(spec.root, spec.source);
|
|
10942
11058
|
return existsSync7(source) ? { ready: true, reason: "declared source is present" } : { ready: false, reason: `declared source is missing: ${spec.source}` };
|
|
10943
11059
|
}
|
|
10944
11060
|
const entry = lock.entries[key];
|
|
@@ -11017,7 +11133,7 @@ async function buildPlan(specs, lock, opts = {}) {
|
|
|
11017
11133
|
state = "missing";
|
|
11018
11134
|
reason = "--force";
|
|
11019
11135
|
} else if (!entry && spec.source && spec.generator !== "frames") {
|
|
11020
|
-
if (existsSync8(
|
|
11136
|
+
if (existsSync8(path13.resolve(spec.root, spec.source))) {
|
|
11021
11137
|
state = "ok";
|
|
11022
11138
|
reason = `placed from ${spec.source}; not generated`;
|
|
11023
11139
|
} else {
|
|
@@ -11159,13 +11275,13 @@ function summarize(plan) {
|
|
|
11159
11275
|
|
|
11160
11276
|
// src/project.ts
|
|
11161
11277
|
async function openProject(manifestPath = "pixelkiln.manifest.json", opts = {}) {
|
|
11162
|
-
const absolute =
|
|
11163
|
-
const root =
|
|
11278
|
+
const absolute = path14.resolve(manifestPath);
|
|
11279
|
+
const root = path14.dirname(absolute);
|
|
11164
11280
|
if (opts.env !== false) {
|
|
11165
11281
|
loadEnvFiles(root);
|
|
11166
|
-
if (
|
|
11282
|
+
if (path14.resolve(process.cwd()) !== root) loadEnvFiles(process.cwd());
|
|
11167
11283
|
}
|
|
11168
|
-
const lockPath =
|
|
11284
|
+
const lockPath = path14.resolve(opts.lockPath ?? path14.join(root, "pixelkiln.lock.json"));
|
|
11169
11285
|
const loaded = await loadManifest(absolute);
|
|
11170
11286
|
const specs = await resolveSpecs(loaded, { styles: opts.styles ?? [], assets: opts.assets ?? [] });
|
|
11171
11287
|
const lock = await loadLock(lockPath);
|
|
@@ -11232,7 +11348,7 @@ function shouldPersistSourceUrl(value) {
|
|
|
11232
11348
|
}
|
|
11233
11349
|
|
|
11234
11350
|
// src/recipes.ts
|
|
11235
|
-
import
|
|
11351
|
+
import path15 from "path";
|
|
11236
11352
|
import { existsSync as existsSync9 } from "fs";
|
|
11237
11353
|
import { readdir, readFile as readFile7 } from "fs/promises";
|
|
11238
11354
|
import { fileURLToPath } from "url";
|
|
@@ -11243,7 +11359,7 @@ var VERSION_RE = /^\d+\.\d+\.\d+$/;
|
|
|
11243
11359
|
var RECIPE_FILE = "pixelkiln.recipe.json";
|
|
11244
11360
|
var RECIPE_DIR_TOKEN = "{{recipeDir}}";
|
|
11245
11361
|
var PortablePathSchema = z4.string().min(1).refine((value) => {
|
|
11246
|
-
if (value.includes("\\") ||
|
|
11362
|
+
if (value.includes("\\") || path15.posix.isAbsolute(value)) return false;
|
|
11247
11363
|
const parts = value.split("/");
|
|
11248
11364
|
return !parts.includes("") && !parts.includes(".") && !parts.includes("..");
|
|
11249
11365
|
}, "expected a portable relative path without '.', '..', or backslashes");
|
|
@@ -11419,7 +11535,7 @@ function message3(error) {
|
|
|
11419
11535
|
return error instanceof Error ? error.message : String(error);
|
|
11420
11536
|
}
|
|
11421
11537
|
async function readRecipeFile(recipePath, bundled) {
|
|
11422
|
-
const absolute =
|
|
11538
|
+
const absolute = path15.resolve(recipePath);
|
|
11423
11539
|
let raw;
|
|
11424
11540
|
try {
|
|
11425
11541
|
raw = JSON.parse(await readFile7(absolute, "utf8"));
|
|
@@ -11433,13 +11549,13 @@ async function readRecipeFile(recipePath, bundled) {
|
|
|
11433
11549
|
` + parsed.error.issues.map((issue) => ` ${issue.path.join(".") || "$"}: ${issue.message}`).join("\n")
|
|
11434
11550
|
);
|
|
11435
11551
|
}
|
|
11436
|
-
return { recipe: parsed.data, authored: raw, path: absolute, dir:
|
|
11552
|
+
return { recipe: parsed.data, authored: raw, path: absolute, dir: path15.dirname(absolute), bundled };
|
|
11437
11553
|
}
|
|
11438
11554
|
async function walkRecipeFiles(root) {
|
|
11439
11555
|
if (!existsSync9(root)) return [];
|
|
11440
11556
|
const out = [];
|
|
11441
11557
|
for (const entry of await readdir(root, { withFileTypes: true })) {
|
|
11442
|
-
const resolved =
|
|
11558
|
+
const resolved = path15.join(root, entry.name);
|
|
11443
11559
|
if (entry.isDirectory()) out.push(...await walkRecipeFiles(resolved));
|
|
11444
11560
|
else if (entry.isFile() && entry.name === RECIPE_FILE) out.push(resolved);
|
|
11445
11561
|
}
|
|
@@ -11450,7 +11566,7 @@ function bundledRecipeRoot() {
|
|
|
11450
11566
|
}
|
|
11451
11567
|
async function listBundledRecipes(root = bundledRecipeRoot()) {
|
|
11452
11568
|
const recipes = await Promise.all(
|
|
11453
|
-
(await walkRecipeFiles(
|
|
11569
|
+
(await walkRecipeFiles(path15.resolve(root))).map((file) => readRecipeFile(file, true))
|
|
11454
11570
|
);
|
|
11455
11571
|
return recipes.sort(
|
|
11456
11572
|
(a, b) => a.recipe.id.localeCompare(b.recipe.id) || compareVersions(b.recipe.version, a.recipe.version)
|
|
@@ -11473,9 +11589,9 @@ function parseSelector(selector) {
|
|
|
11473
11589
|
return { id: match[1], version: match[2] };
|
|
11474
11590
|
}
|
|
11475
11591
|
async function resolveRecipe(target, bundledRoot = bundledRecipeRoot()) {
|
|
11476
|
-
const local =
|
|
11592
|
+
const local = path15.resolve(target);
|
|
11477
11593
|
if (existsSync9(local)) {
|
|
11478
|
-
const recipePath =
|
|
11594
|
+
const recipePath = path15.basename(local) === RECIPE_FILE ? local : path15.join(local, RECIPE_FILE);
|
|
11479
11595
|
return readRecipeFile(recipePath, false);
|
|
11480
11596
|
}
|
|
11481
11597
|
if (target.startsWith(".") || target.startsWith("/") || target.includes("\\")) {
|
|
@@ -11489,7 +11605,7 @@ async function resolveRecipe(target, bundledRoot = bundledRecipeRoot()) {
|
|
|
11489
11605
|
return matches[0];
|
|
11490
11606
|
}
|
|
11491
11607
|
async function verifyFile(root, file) {
|
|
11492
|
-
const absolute =
|
|
11608
|
+
const absolute = path15.resolve(root, ...file.path.split("/"));
|
|
11493
11609
|
if (!existsSync9(absolute)) {
|
|
11494
11610
|
return { path: file.path, expectedSha256: file.sha256, actualSha256: null, status: "missing" };
|
|
11495
11611
|
}
|
|
@@ -11505,7 +11621,7 @@ async function verifyRecipe(target, options = {}) {
|
|
|
11505
11621
|
const loaded = await resolveRecipe(target, options.bundledRoot);
|
|
11506
11622
|
const actualIntegrity = recipeDigest(loaded.authored);
|
|
11507
11623
|
const files = await Promise.all(loaded.recipe.files.map((file) => verifyFile(loaded.dir, file)));
|
|
11508
|
-
const modelRoot = options.modelRoot ?
|
|
11624
|
+
const modelRoot = options.modelRoot ? path15.resolve(options.modelRoot) : null;
|
|
11509
11625
|
const models = modelRoot ? await Promise.all(loaded.recipe.models.map(async (model) => ({
|
|
11510
11626
|
...await verifyFile(modelRoot, model),
|
|
11511
11627
|
source: model.source,
|
|
@@ -11539,7 +11655,7 @@ async function verifyRecipe(target, options = {}) {
|
|
|
11539
11655
|
};
|
|
11540
11656
|
}
|
|
11541
11657
|
function renderStyle(style, destination, cwd) {
|
|
11542
|
-
const portableDir =
|
|
11658
|
+
const portableDir = path15.relative(cwd, destination).split(path15.sep).join("/") || ".";
|
|
11543
11659
|
const replace = (value) => {
|
|
11544
11660
|
if (typeof value === "string") return value.replaceAll(RECIPE_DIR_TOKEN, portableDir);
|
|
11545
11661
|
if (Array.isArray(value)) return value.map(replace);
|
|
@@ -11551,19 +11667,19 @@ function renderStyle(style, destination, cwd) {
|
|
|
11551
11667
|
return replace(style);
|
|
11552
11668
|
}
|
|
11553
11669
|
async function installRecipe(target, options = {}) {
|
|
11554
|
-
const cwd =
|
|
11670
|
+
const cwd = path15.resolve(options.cwd ?? process.cwd());
|
|
11555
11671
|
const loaded = await resolveRecipe(target, options.bundledRoot);
|
|
11556
11672
|
const verification = await verifyRecipe(loaded.path);
|
|
11557
11673
|
if (!verification.ok) {
|
|
11558
11674
|
throw new Error(`Recipe ${loaded.recipe.id}@${loaded.recipe.version} failed integrity verification.`);
|
|
11559
11675
|
}
|
|
11560
|
-
const destination = options.out ?
|
|
11676
|
+
const destination = options.out ? path15.resolve(cwd, options.out) : path15.join(cwd, "pixelkiln-recipes", ...loaded.recipe.id.split("/"), loaded.recipe.version);
|
|
11561
11677
|
const sources = [
|
|
11562
11678
|
{ path: RECIPE_FILE, source: loaded.path },
|
|
11563
|
-
...loaded.recipe.files.map((file) => ({ path: file.path, source:
|
|
11679
|
+
...loaded.recipe.files.map((file) => ({ path: file.path, source: path15.join(loaded.dir, ...file.path.split("/")) }))
|
|
11564
11680
|
];
|
|
11565
11681
|
const files = await Promise.all(sources.map(async (file) => ({
|
|
11566
|
-
path:
|
|
11682
|
+
path: path15.join(destination, ...file.path.split("/")),
|
|
11567
11683
|
data: await readFile7(file.source)
|
|
11568
11684
|
})));
|
|
11569
11685
|
if (!options.force) {
|
|
@@ -11595,7 +11711,7 @@ async function installRecipe(target, options = {}) {
|
|
|
11595
11711
|
// src/pipeline/audit.ts
|
|
11596
11712
|
import { readFile as readFile8 } from "fs/promises";
|
|
11597
11713
|
import { existsSync as existsSync10 } from "fs";
|
|
11598
|
-
import
|
|
11714
|
+
import path16 from "path";
|
|
11599
11715
|
function paletteDistance(asset, reference) {
|
|
11600
11716
|
if (!asset.length || !reference.length) return 0;
|
|
11601
11717
|
let total = 0;
|
|
@@ -11660,7 +11776,7 @@ async function auditStyle(loaded, specs, styleId, lock) {
|
|
|
11660
11776
|
let referenceFromStyleImages = false;
|
|
11661
11777
|
const refPalettes = [];
|
|
11662
11778
|
for (const img of style.styleImages) {
|
|
11663
|
-
const abs =
|
|
11779
|
+
const abs = path16.resolve(loaded.root, img.path);
|
|
11664
11780
|
if (!existsSync10(abs)) continue;
|
|
11665
11781
|
try {
|
|
11666
11782
|
refPalettes.push(extractPalette(decodePng(await readFile8(abs)), 12));
|
|
@@ -11741,16 +11857,16 @@ function hex(c) {
|
|
|
11741
11857
|
// src/pixellab-utils.ts
|
|
11742
11858
|
import { existsSync as existsSync11 } from "fs";
|
|
11743
11859
|
import { mkdir as mkdir4, readFile as readFile9, writeFile as writeFile4 } from "fs/promises";
|
|
11744
|
-
import
|
|
11860
|
+
import path17 from "path";
|
|
11745
11861
|
var UNZOOM_MIN_SIDE = 256;
|
|
11746
11862
|
var UNZOOM_MAX_AREA = 2048 * 2048;
|
|
11747
11863
|
function defaultUnzoomOut(input) {
|
|
11748
|
-
const ext =
|
|
11864
|
+
const ext = path17.extname(input);
|
|
11749
11865
|
return `${ext ? input.slice(0, -ext.length) : input}.unzoomed.png`;
|
|
11750
11866
|
}
|
|
11751
11867
|
async function unzoomFile(client2, input, opts = {}) {
|
|
11752
|
-
const out =
|
|
11753
|
-
if (
|
|
11868
|
+
const out = path17.resolve(opts.out ?? defaultUnzoomOut(input));
|
|
11869
|
+
if (path17.resolve(input) === out) throw new Error("unzoom will not overwrite its own input; pass a different --out");
|
|
11754
11870
|
if (existsSync11(out) && !opts.force) throw new Error(`${out} already exists; pass --force to replace it`);
|
|
11755
11871
|
const bytes = await readFile9(input);
|
|
11756
11872
|
const metadata = imageMetadata(bytes);
|
|
@@ -11765,7 +11881,7 @@ async function unzoomFile(client2, input, opts = {}) {
|
|
|
11765
11881
|
throw new Error(`${input} is ${width}x${height}; PixelLab's unzoom takes at most 2048x2048 worth of pixels`);
|
|
11766
11882
|
}
|
|
11767
11883
|
const res = await client2.unzoom({ image: { base64: bytes.toString("base64"), format }, quantize: opts.quantize });
|
|
11768
|
-
await mkdir4(
|
|
11884
|
+
await mkdir4(path17.dirname(out), { recursive: true });
|
|
11769
11885
|
await writeFile4(out, res.png);
|
|
11770
11886
|
return { out, original: res.originalSize, unzoomed: res.unzoomedSize, zoomFactor: res.zoomFactor, usage: res.usage };
|
|
11771
11887
|
}
|
|
@@ -11773,7 +11889,7 @@ var FONT_WEIGHTS = ["Bold", "Regular"];
|
|
|
11773
11889
|
var FONT_GLYPH_SIZES = [8, 16, 32, 64];
|
|
11774
11890
|
var FONT_COST_GENERATIONS = 25;
|
|
11775
11891
|
function fontOutputPaths(out) {
|
|
11776
|
-
const base =
|
|
11892
|
+
const base = path17.resolve(out.replace(/\.(ttf|png)$/i, ""));
|
|
11777
11893
|
return { ttf: `${base}.ttf`, atlas: `${base}.png` };
|
|
11778
11894
|
}
|
|
11779
11895
|
async function generateFont(client2, opts) {
|
|
@@ -11807,7 +11923,7 @@ async function generateFont(client2, opts) {
|
|
|
11807
11923
|
client2.download(job.download_ttf_url),
|
|
11808
11924
|
client2.download(job.download_atlas_url)
|
|
11809
11925
|
]);
|
|
11810
|
-
await mkdir4(
|
|
11926
|
+
await mkdir4(path17.dirname(ttf), { recursive: true });
|
|
11811
11927
|
await writeFile4(ttf, ttfBytes);
|
|
11812
11928
|
await writeFile4(atlas, atlasBytes);
|
|
11813
11929
|
return { jobId, ttf, atlas, glyphPx: job.glyph_px ?? null, usage: job.usage };
|
|
@@ -11822,7 +11938,7 @@ async function generateFont(client2, opts) {
|
|
|
11822
11938
|
}
|
|
11823
11939
|
|
|
11824
11940
|
// src/pipeline/quality-regression.ts
|
|
11825
|
-
import
|
|
11941
|
+
import path18 from "path";
|
|
11826
11942
|
import { existsSync as existsSync12 } from "fs";
|
|
11827
11943
|
import { readFile as readFile10 } from "fs/promises";
|
|
11828
11944
|
import { z as z5 } from "zod";
|
|
@@ -11830,7 +11946,7 @@ var SHA256_RE2 = /^[0-9a-f]{64}$/;
|
|
|
11830
11946
|
var HEX_RE = /^#[0-9a-f]{6}$/;
|
|
11831
11947
|
var MAX_REDMEAN_DISTANCE = 765;
|
|
11832
11948
|
var QualityPathSchema = z5.string().min(1).refine(
|
|
11833
|
-
(value) => !
|
|
11949
|
+
(value) => !path18.posix.isAbsolute(value) && !value.includes("\\"),
|
|
11834
11950
|
"expected a portable relative path"
|
|
11835
11951
|
);
|
|
11836
11952
|
var DEFAULT_QUALITY_TOLERANCES = {
|
|
@@ -11977,7 +12093,7 @@ function measureDecodedImageQuality(png, digest2) {
|
|
|
11977
12093
|
};
|
|
11978
12094
|
}
|
|
11979
12095
|
async function measureImageQuality(file) {
|
|
11980
|
-
const absolute =
|
|
12096
|
+
const absolute = path18.resolve(file);
|
|
11981
12097
|
let bytes;
|
|
11982
12098
|
try {
|
|
11983
12099
|
bytes = await readFile10(absolute);
|
|
@@ -11999,11 +12115,11 @@ function resolveQualityInputs(raw, inputsFilePath) {
|
|
|
11999
12115
|
}
|
|
12000
12116
|
const ids = parsed.data.map((entry) => entry.id);
|
|
12001
12117
|
if (new Set(ids).size !== ids.length) throw new Error("--inputs contains a duplicate quality case id.");
|
|
12002
|
-
const root =
|
|
12118
|
+
const root = path18.dirname(path18.resolve(inputsFilePath));
|
|
12003
12119
|
return parsed.data.map((entry) => ({
|
|
12004
12120
|
id: entry.id,
|
|
12005
|
-
file:
|
|
12006
|
-
...entry.record ? { record:
|
|
12121
|
+
file: path18.resolve(root, entry.path),
|
|
12122
|
+
...entry.record ? { record: path18.resolve(root, entry.record) } : {},
|
|
12007
12123
|
tolerances: QualityToleranceSchema.parse({
|
|
12008
12124
|
...DEFAULT_QUALITY_TOLERANCES,
|
|
12009
12125
|
...entry.tolerances
|
|
@@ -12011,7 +12127,7 @@ function resolveQualityInputs(raw, inputsFilePath) {
|
|
|
12011
12127
|
}));
|
|
12012
12128
|
}
|
|
12013
12129
|
function portableRelative2(from, to) {
|
|
12014
|
-
return
|
|
12130
|
+
return path18.relative(from, path18.resolve(to)).split(path18.sep).join("/") || ".";
|
|
12015
12131
|
}
|
|
12016
12132
|
async function verifyRecordForImage(recordPath, imagePath) {
|
|
12017
12133
|
const verification = await verifyArtifactBundle(recordPath);
|
|
@@ -12026,20 +12142,20 @@ async function verifyRecordForImage(recordPath, imagePath) {
|
|
|
12026
12142
|
if (record.kind !== "refine" || options === null || typeof options !== "object" || options.schema !== "pixelkiln-quality") {
|
|
12027
12143
|
throw new Error(`Quality record ${recordPath} is not a PixelKiln refinement record.`);
|
|
12028
12144
|
}
|
|
12029
|
-
const outputs = record.outputs.map((output) =>
|
|
12030
|
-
if (!outputs.includes(
|
|
12145
|
+
const outputs = record.outputs.map((output) => path18.resolve(path18.dirname(recordPath), output.path));
|
|
12146
|
+
if (!outputs.includes(path18.resolve(imagePath))) {
|
|
12031
12147
|
throw new Error(`Quality record ${recordPath} does not own ${imagePath}.`);
|
|
12032
12148
|
}
|
|
12033
12149
|
}
|
|
12034
12150
|
async function snapshotQualityBaseline(inputs, baselinePath, options = {}) {
|
|
12035
12151
|
if (!inputs.length) throw new Error("Cannot snapshot an empty quality baseline.");
|
|
12036
|
-
const absolute =
|
|
12152
|
+
const absolute = path18.resolve(baselinePath);
|
|
12037
12153
|
for (const input of inputs) {
|
|
12038
|
-
if (
|
|
12154
|
+
if (path18.resolve(input.file) === absolute || input.record && path18.resolve(input.record) === absolute) {
|
|
12039
12155
|
throw new Error(`Quality baseline output would overwrite an input: ${absolute}.`);
|
|
12040
12156
|
}
|
|
12041
12157
|
}
|
|
12042
|
-
const root =
|
|
12158
|
+
const root = path18.dirname(absolute);
|
|
12043
12159
|
const cases = [];
|
|
12044
12160
|
for (const input of [...inputs].sort((a, b) => a.id.localeCompare(b.id))) {
|
|
12045
12161
|
const expected = await measureImageQuality(input.file);
|
|
@@ -12079,7 +12195,7 @@ async function snapshotQualityBaseline(inputs, baselinePath, options = {}) {
|
|
|
12079
12195
|
return { baseline, path: absolute, changed: result.changed.length > 0 };
|
|
12080
12196
|
}
|
|
12081
12197
|
async function readQualityBaseline(baselinePath) {
|
|
12082
|
-
const absolute =
|
|
12198
|
+
const absolute = path18.resolve(baselinePath);
|
|
12083
12199
|
let raw;
|
|
12084
12200
|
try {
|
|
12085
12201
|
raw = JSON.parse(await readFile10(absolute, "utf8"));
|
|
@@ -12152,12 +12268,12 @@ function regressionViolations(expected, actual, tolerances) {
|
|
|
12152
12268
|
return { violations, warnings };
|
|
12153
12269
|
}
|
|
12154
12270
|
async function checkQualityBaseline(baselinePath) {
|
|
12155
|
-
const absolute =
|
|
12271
|
+
const absolute = path18.resolve(baselinePath);
|
|
12156
12272
|
const baseline = await readQualityBaseline(absolute);
|
|
12157
|
-
const root =
|
|
12273
|
+
const root = path18.dirname(absolute);
|
|
12158
12274
|
const cases = [];
|
|
12159
12275
|
for (const entry of baseline.cases) {
|
|
12160
|
-
const file =
|
|
12276
|
+
const file = path18.resolve(root, entry.file);
|
|
12161
12277
|
let actual = null;
|
|
12162
12278
|
const violations = [];
|
|
12163
12279
|
const warnings = [];
|
|
@@ -12170,7 +12286,7 @@ async function checkQualityBaseline(baselinePath) {
|
|
|
12170
12286
|
violations.push(message4(error));
|
|
12171
12287
|
}
|
|
12172
12288
|
if (entry.record) {
|
|
12173
|
-
const recordPath =
|
|
12289
|
+
const recordPath = path18.resolve(root, entry.record.path);
|
|
12174
12290
|
try {
|
|
12175
12291
|
const recordHash = await sha256File(recordPath);
|
|
12176
12292
|
if (recordHash !== entry.record.sha256) violations.push("quality record hash changed");
|
|
@@ -12208,7 +12324,7 @@ async function checkQualityBaseline(baselinePath) {
|
|
|
12208
12324
|
// src/pipeline/cache-health.ts
|
|
12209
12325
|
import { existsSync as existsSync16 } from "fs";
|
|
12210
12326
|
import { readFile as readFile13, readdir as readdir2, rm as rm6 } from "fs/promises";
|
|
12211
|
-
import
|
|
12327
|
+
import path21 from "path";
|
|
12212
12328
|
|
|
12213
12329
|
// src/cache.ts
|
|
12214
12330
|
import { mkdir as mkdir5, readFile as readFile11, rename as rename4, rm as rm4, writeFile as writeFile5 } from "fs/promises";
|
|
@@ -12227,10 +12343,10 @@ function isSha256Hash(value) {
|
|
|
12227
12343
|
function parseCache(value) {
|
|
12228
12344
|
return HashCacheSchema.parse(value);
|
|
12229
12345
|
}
|
|
12230
|
-
async function loadCache(
|
|
12231
|
-
if (!existsSync13(
|
|
12346
|
+
async function loadCache(path38) {
|
|
12347
|
+
if (!existsSync13(path38)) return { version: 1, hashes: {} };
|
|
12232
12348
|
try {
|
|
12233
|
-
const parsed = HashCacheSchema.safeParse(JSON.parse(await readFile11(
|
|
12349
|
+
const parsed = HashCacheSchema.safeParse(JSON.parse(await readFile11(path38, "utf8")));
|
|
12234
12350
|
if (!parsed.success) return { version: 1, hashes: {} };
|
|
12235
12351
|
return {
|
|
12236
12352
|
version: 1,
|
|
@@ -12242,18 +12358,18 @@ async function loadCache(path37) {
|
|
|
12242
12358
|
return { version: 1, hashes: {} };
|
|
12243
12359
|
}
|
|
12244
12360
|
}
|
|
12245
|
-
async function saveCache(
|
|
12361
|
+
async function saveCache(path38, cache) {
|
|
12246
12362
|
const sorted = {};
|
|
12247
12363
|
for (const key of Object.keys(cache.hashes).sort()) {
|
|
12248
12364
|
const hash = cache.hashes[key];
|
|
12249
12365
|
if (!isSha256Hash(hash)) throw new Error(`Refusing to cache invalid SHA-256 for ${key}`);
|
|
12250
12366
|
sorted[key] = hash;
|
|
12251
12367
|
}
|
|
12252
|
-
await mkdir5(pathModule.dirname(pathModule.resolve(
|
|
12253
|
-
const tmp = `${
|
|
12368
|
+
await mkdir5(pathModule.dirname(pathModule.resolve(path38)), { recursive: true });
|
|
12369
|
+
const tmp = `${path38}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
12254
12370
|
try {
|
|
12255
12371
|
await writeFile5(tmp, JSON.stringify({ version: 1, hashes: sorted }, null, 2) + "\n");
|
|
12256
|
-
await rename4(tmp,
|
|
12372
|
+
await rename4(tmp, path38);
|
|
12257
12373
|
} finally {
|
|
12258
12374
|
await rm4(tmp, { force: true });
|
|
12259
12375
|
}
|
|
@@ -12278,19 +12394,19 @@ function cachePathFor(lockPath) {
|
|
|
12278
12394
|
|
|
12279
12395
|
// src/pipeline/history.ts
|
|
12280
12396
|
import { existsSync as existsSync15 } from "fs";
|
|
12281
|
-
import
|
|
12397
|
+
import path20 from "path";
|
|
12282
12398
|
|
|
12283
12399
|
// src/pipeline/fetch.ts
|
|
12284
|
-
import { existsSync as existsSync14, readFileSync as
|
|
12400
|
+
import { existsSync as existsSync14, readFileSync as readFileSync4 } from "fs";
|
|
12285
12401
|
import { mkdir as mkdir6, readFile as readFile12, rename as rename5, rm as rm5, writeFile as writeFile6 } from "fs/promises";
|
|
12286
|
-
import
|
|
12402
|
+
import path19 from "path";
|
|
12287
12403
|
async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
12288
12404
|
const log = opts.onProgress ?? (() => {
|
|
12289
12405
|
});
|
|
12290
12406
|
const result = { downloaded: 0, skipped: 0, failed: 0, ...opts.refresh ? { unchanged: 0 } : {} };
|
|
12291
12407
|
const specByKey = new Map(specs.map((s) => [lockKey(s.styleId, s.assetId), s]));
|
|
12292
12408
|
normalizeLockOutputPaths(lock, specs);
|
|
12293
|
-
const cacheDir = opts.cacheDir === false ? null :
|
|
12409
|
+
const cacheDir = opts.cacheDir === false ? null : path19.resolve(opts.cacheDir ?? path19.join(path19.dirname(lockPath), ".pixelkiln", "cache"));
|
|
12294
12410
|
const pending = Object.entries(lock.entries).filter(([key, e]) => {
|
|
12295
12411
|
if (e.provider !== provider.id || !specByKey.has(key)) return false;
|
|
12296
12412
|
if (specByKey.get(key)?.mirror) return false;
|
|
@@ -12301,7 +12417,7 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
12301
12417
|
if (spec && e.specHash === spec.specHash && e.outputs.length && !postprocessCurrent(e, spec)) {
|
|
12302
12418
|
return Boolean(opts.force) || e.outputs.every((output) => {
|
|
12303
12419
|
const file = resolveOutputPath(output.path, spec.root);
|
|
12304
|
-
return existsSync14(file) && sha256(
|
|
12420
|
+
return existsSync14(file) && sha256(readFileSync4(file)) === output.sha256;
|
|
12305
12421
|
});
|
|
12306
12422
|
}
|
|
12307
12423
|
if (!opts.repair) return false;
|
|
@@ -12309,7 +12425,7 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
12309
12425
|
return e.outputs.some((output) => {
|
|
12310
12426
|
const file = resolveOutputPath(output.path, spec.root);
|
|
12311
12427
|
if (!existsSync14(file)) return true;
|
|
12312
|
-
const current = sha256(
|
|
12428
|
+
const current = sha256(readFileSync4(file));
|
|
12313
12429
|
if (current === output.sha256) return false;
|
|
12314
12430
|
return Boolean(opts.force) || (e.supersededOutputs ?? []).some((old) => old.sha256 === current);
|
|
12315
12431
|
});
|
|
@@ -12392,7 +12508,7 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
12392
12508
|
if (!source.url) throw new Error(`no durable source URL to refresh ${source.role ?? "asset"} from`);
|
|
12393
12509
|
fresh = await provider.download(source.url);
|
|
12394
12510
|
if (recorded && sha256(fresh) === recordedRaw && !reapply) {
|
|
12395
|
-
log(` current ${
|
|
12511
|
+
log(` current ${path19.relative(process.cwd(), target)}`);
|
|
12396
12512
|
outputs.push({ ...recorded, path: portableOutputPath(target, spec.root) });
|
|
12397
12513
|
continue;
|
|
12398
12514
|
}
|
|
@@ -12416,9 +12532,9 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
12416
12532
|
}
|
|
12417
12533
|
if (reapply && !fresh) {
|
|
12418
12534
|
if (!recorded.raw) raw = await readFile12(target);
|
|
12419
|
-
log(` reapply ${
|
|
12535
|
+
log(` reapply ${path19.relative(process.cwd(), target)}`);
|
|
12420
12536
|
} else {
|
|
12421
|
-
log(` changed ${
|
|
12537
|
+
log(` changed ${path19.relative(process.cwd(), target)} (upstream)`);
|
|
12422
12538
|
}
|
|
12423
12539
|
} else {
|
|
12424
12540
|
const superseded = (entry.supersededOutputs ?? []).find(
|
|
@@ -12446,7 +12562,7 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
12446
12562
|
}
|
|
12447
12563
|
}
|
|
12448
12564
|
log(
|
|
12449
|
-
` replace ${
|
|
12565
|
+
` replace ${path19.relative(process.cwd(), target)}` + (opts.force ? " (--force)" : " (previous tracked generation)")
|
|
12450
12566
|
);
|
|
12451
12567
|
}
|
|
12452
12568
|
}
|
|
@@ -12455,7 +12571,7 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
12455
12571
|
} else if (!raw && recorded && cacheDir) {
|
|
12456
12572
|
if (!reapply) final = await readCachedMedia(cacheDir, recorded.sha256, expectedMediaType);
|
|
12457
12573
|
if (!final && recordedRaw) raw = await readCachedMedia(cacheDir, recordedRaw, expectedMediaType);
|
|
12458
|
-
if (final || raw) log(` cached ${
|
|
12574
|
+
if (final || raw) log(` cached ${path19.relative(process.cwd(), target)}`);
|
|
12459
12575
|
}
|
|
12460
12576
|
if (!final && !raw) {
|
|
12461
12577
|
if (!source.url) {
|
|
@@ -12482,11 +12598,11 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
12482
12598
|
buf = processed.bytes;
|
|
12483
12599
|
if (processed.changed) {
|
|
12484
12600
|
rawHash = sha256(raw);
|
|
12485
|
-
log(` snapped ${
|
|
12601
|
+
log(` snapped ${path19.relative(process.cwd(), target)} to ${wanted.palette.colors.length} colours`);
|
|
12486
12602
|
}
|
|
12487
12603
|
}
|
|
12488
12604
|
if (cacheDir) await cacheMedia(cacheDir, buf, mediaType);
|
|
12489
|
-
await mkdir6(
|
|
12605
|
+
await mkdir6(path19.dirname(target), { recursive: true });
|
|
12490
12606
|
const tmp = `${target}.pixelkiln.tmp`;
|
|
12491
12607
|
await writeFile6(tmp, buf);
|
|
12492
12608
|
outputs.push({
|
|
@@ -12500,7 +12616,7 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
12500
12616
|
await saveLock(lockPath, lock);
|
|
12501
12617
|
await rename5(tmp, target);
|
|
12502
12618
|
wrote = true;
|
|
12503
|
-
log(` wrote ${
|
|
12619
|
+
log(` wrote ${path19.relative(process.cwd(), target)}`);
|
|
12504
12620
|
}
|
|
12505
12621
|
if (opts.refresh && !wrote) {
|
|
12506
12622
|
if (rotated) {
|
|
@@ -12544,7 +12660,7 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
12544
12660
|
return result;
|
|
12545
12661
|
}
|
|
12546
12662
|
async function readCachedMedia(cacheDir, hash, mediaType) {
|
|
12547
|
-
const file =
|
|
12663
|
+
const file = path19.join(cacheDir, cacheFileName(hash, mediaType));
|
|
12548
12664
|
if (!existsSync14(file)) return null;
|
|
12549
12665
|
try {
|
|
12550
12666
|
const buf = await readFile12(file);
|
|
@@ -12558,7 +12674,7 @@ async function readCachedMedia(cacheDir, hash, mediaType) {
|
|
|
12558
12674
|
async function cacheMedia(cacheDir, buf, mediaType, knownHash) {
|
|
12559
12675
|
const hash = knownHash ?? sha256(buf);
|
|
12560
12676
|
validateMedia(buf, mediaType);
|
|
12561
|
-
const file =
|
|
12677
|
+
const file = path19.join(cacheDir, cacheFileName(hash, mediaType));
|
|
12562
12678
|
if (await readCachedMedia(cacheDir, hash, mediaType)) return;
|
|
12563
12679
|
await mkdir6(cacheDir, { recursive: true });
|
|
12564
12680
|
const tmp = `${file}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
@@ -12688,7 +12804,7 @@ async function revertGeneration(provider, spec, lock, lockPath, opts) {
|
|
|
12688
12804
|
if (!existsSync15(file)) continue;
|
|
12689
12805
|
if (await sha256File(file) !== entry.outputs[i].sha256 && !opts.force) {
|
|
12690
12806
|
throw new OverwriteRefusedError(
|
|
12691
|
-
`${
|
|
12807
|
+
`${path20.relative(process.cwd(), file)} was changed after download; keep it with pixelkiln edit, or pass --force to replace it`,
|
|
12692
12808
|
[file]
|
|
12693
12809
|
);
|
|
12694
12810
|
}
|
|
@@ -12741,10 +12857,10 @@ async function revertGeneration(provider, spec, lock, lockPath, opts) {
|
|
|
12741
12857
|
// src/pipeline/cache-health.ts
|
|
12742
12858
|
async function inspectCaches(lock, lockPath, options = {}) {
|
|
12743
12859
|
if (options.prune && !existsSync16(lockPath)) {
|
|
12744
|
-
throw new Error(`Refusing to prune without an existing lockfile at ${
|
|
12860
|
+
throw new Error(`Refusing to prune without an existing lockfile at ${path21.resolve(lockPath)}`);
|
|
12745
12861
|
}
|
|
12746
|
-
const contentDir =
|
|
12747
|
-
const remotePath =
|
|
12862
|
+
const contentDir = path21.resolve(path21.dirname(lockPath), ".pixelkiln", "cache");
|
|
12863
|
+
const remotePath = path21.resolve(cachePathFor(lockPath));
|
|
12748
12864
|
const referenced = referencedHashes(lock);
|
|
12749
12865
|
let content = await inspectContentCache(contentDir, referenced);
|
|
12750
12866
|
let remoteHashes = await inspectRemoteHashCache(remotePath);
|
|
@@ -12756,7 +12872,7 @@ async function inspectCaches(lock, lockPath, options = {}) {
|
|
|
12756
12872
|
]);
|
|
12757
12873
|
for (const name of names) {
|
|
12758
12874
|
try {
|
|
12759
|
-
await rm6(
|
|
12875
|
+
await rm6(path21.join(contentDir, name), { force: true });
|
|
12760
12876
|
removed.contentFiles++;
|
|
12761
12877
|
} catch {
|
|
12762
12878
|
}
|
|
@@ -12804,7 +12920,7 @@ async function inspectContentCache(contentDir, referenced) {
|
|
|
12804
12920
|
continue;
|
|
12805
12921
|
}
|
|
12806
12922
|
report.files++;
|
|
12807
|
-
const file =
|
|
12923
|
+
const file = path21.join(contentDir, entry.name);
|
|
12808
12924
|
const mediaType = mediaTypeFromExtension(entry.name);
|
|
12809
12925
|
const expected = mediaType ? entry.name.slice(0, -4) : "";
|
|
12810
12926
|
let bytes;
|
|
@@ -12871,20 +12987,20 @@ async function inspectRemoteHashCache(remotePath) {
|
|
|
12871
12987
|
}
|
|
12872
12988
|
|
|
12873
12989
|
// src/pipeline/pack.ts
|
|
12874
|
-
import { readFileSync as
|
|
12875
|
-
import
|
|
12990
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
12991
|
+
import path22 from "path";
|
|
12876
12992
|
var DEFAULT_FRAME_SET_FPS = 12;
|
|
12877
12993
|
function resolvePackInputs(raw, inputsFilePath) {
|
|
12878
12994
|
if (!Array.isArray(raw) || !raw.length) {
|
|
12879
12995
|
throw new Error("--inputs must be a non-empty JSON array of { id, path }");
|
|
12880
12996
|
}
|
|
12881
|
-
const baseDir =
|
|
12997
|
+
const baseDir = path22.dirname(path22.resolve(inputsFilePath));
|
|
12882
12998
|
return raw.map((entry, i) => {
|
|
12883
12999
|
const e = entry;
|
|
12884
13000
|
if (typeof e.id !== "string" || typeof e.path !== "string") {
|
|
12885
13001
|
throw new Error(`--inputs[${i}] needs string "id" and "path"`);
|
|
12886
13002
|
}
|
|
12887
|
-
return { id: e.id, path:
|
|
13003
|
+
return { id: e.id, path: path22.resolve(baseDir, e.path) };
|
|
12888
13004
|
});
|
|
12889
13005
|
}
|
|
12890
13006
|
function packSprites(inputs, options = {}) {
|
|
@@ -12905,7 +13021,7 @@ function packSprites(inputs, options = {}) {
|
|
|
12905
13021
|
for (const input of ordered) {
|
|
12906
13022
|
let sourceHash = null;
|
|
12907
13023
|
try {
|
|
12908
|
-
const bytes =
|
|
13024
|
+
const bytes = readFileSync5(input.path);
|
|
12909
13025
|
sourceHash = sha256(bytes);
|
|
12910
13026
|
const png = decodePng(bytes);
|
|
12911
13027
|
sprites.push({ id: input.id, width: png.width, height: png.height, pixels: png.pixels });
|
|
@@ -12968,7 +13084,7 @@ function packStyle(lock, styleId, manifestDir, options = {}) {
|
|
|
12968
13084
|
}
|
|
12969
13085
|
const inputs2 = Object.entries(options.sourceOverrides).map(([id, source]) => ({
|
|
12970
13086
|
id,
|
|
12971
|
-
path:
|
|
13087
|
+
path: path22.resolve(source)
|
|
12972
13088
|
}));
|
|
12973
13089
|
if (!inputs2.length) throw new Error(`No approved quality outputs for style "${styleId}".`);
|
|
12974
13090
|
const packed2 = packSprites(inputs2, options);
|
|
@@ -12992,7 +13108,7 @@ function packStyle(lock, styleId, manifestDir, options = {}) {
|
|
|
12992
13108
|
let outputs = resolveEntryOutputs(entry, id, manifestDir);
|
|
12993
13109
|
const source = sources[id];
|
|
12994
13110
|
if (source && outputs.length <= 1) {
|
|
12995
|
-
inputs.push({ id, path:
|
|
13111
|
+
inputs.push({ id, path: path22.resolve(manifestDir, source) });
|
|
12996
13112
|
continue;
|
|
12997
13113
|
}
|
|
12998
13114
|
if (source && sourceIsStem(source, entry, manifestDir)) {
|
|
@@ -13026,7 +13142,7 @@ function packStyle(lock, styleId, manifestDir, options = {}) {
|
|
|
13026
13142
|
}
|
|
13027
13143
|
}
|
|
13028
13144
|
for (const [id, source] of Object.entries(sources)) {
|
|
13029
|
-
if (!locked.has(id)) inputs.push({ id, path:
|
|
13145
|
+
if (!locked.has(id)) inputs.push({ id, path: path22.resolve(manifestDir, source) });
|
|
13030
13146
|
}
|
|
13031
13147
|
if (!inputs.length) {
|
|
13032
13148
|
throw new Error(`No readable sprites for style "${styleId}"; ${noOutput.length} skipped.`);
|
|
@@ -13063,7 +13179,7 @@ function mountSprites(placements, options) {
|
|
|
13063
13179
|
for (const p of placements) {
|
|
13064
13180
|
let sourceHash = null;
|
|
13065
13181
|
try {
|
|
13066
|
-
const bytes =
|
|
13182
|
+
const bytes = readFileSync5(p.path);
|
|
13067
13183
|
sourceHash = sha256(bytes);
|
|
13068
13184
|
const png = decodePng(bytes);
|
|
13069
13185
|
if (png.width > cellWidth || png.height > cellHeight) {
|
|
@@ -13134,7 +13250,7 @@ function mountStyle(lock, styleId, manifestDir, mount, cells, sources = {}, outp
|
|
|
13134
13250
|
const source = sources[id];
|
|
13135
13251
|
const entry = lock.entries[`${styleId}/${id}`];
|
|
13136
13252
|
if (source && !(entry && sourceIsStem(source, entry, manifestDir))) {
|
|
13137
|
-
placements.push({ id, path:
|
|
13253
|
+
placements.push({ id, path: path22.resolve(manifestDir, source), cell });
|
|
13138
13254
|
continue;
|
|
13139
13255
|
}
|
|
13140
13256
|
if (source && entry) {
|
|
@@ -13167,7 +13283,7 @@ function mountStyle(lock, styleId, manifestDir, mount, cells, sources = {}, outp
|
|
|
13167
13283
|
`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
13284
|
);
|
|
13169
13285
|
}
|
|
13170
|
-
const basePng = mount.base ?
|
|
13286
|
+
const basePng = mount.base ? readFileSync5(path22.resolve(manifestDir, mount.base)) : void 0;
|
|
13171
13287
|
const mounted = mountSprites(placements, {
|
|
13172
13288
|
cellWidth: mount.cellWidth,
|
|
13173
13289
|
cellHeight: mount.cellHeight,
|
|
@@ -13177,7 +13293,7 @@ function mountStyle(lock, styleId, manifestDir, mount, cells, sources = {}, outp
|
|
|
13177
13293
|
...mounted,
|
|
13178
13294
|
sources: mount.base ? [{
|
|
13179
13295
|
id: "$base",
|
|
13180
|
-
path:
|
|
13296
|
+
path: path22.resolve(manifestDir, mount.base),
|
|
13181
13297
|
sha256: sha256(basePng),
|
|
13182
13298
|
included: true
|
|
13183
13299
|
}, ...mounted.sources] : mounted.sources,
|
|
@@ -13989,7 +14105,7 @@ async function poll(provider, lock, lockPath, opts = {}) {
|
|
|
13989
14105
|
// src/pipeline/doctor.ts
|
|
13990
14106
|
import { constants, existsSync as existsSync17 } from "fs";
|
|
13991
14107
|
import { access, stat as stat2 } from "fs/promises";
|
|
13992
|
-
import
|
|
14108
|
+
import path23 from "path";
|
|
13993
14109
|
async function doctor(loaded, specs, lock, lockPath, opts = {}) {
|
|
13994
14110
|
const checks = [];
|
|
13995
14111
|
const add = (id, level, message6) => checks.push({ id, level, message: message6 });
|
|
@@ -14000,8 +14116,8 @@ async function doctor(loaded, specs, lock, lockPath, opts = {}) {
|
|
|
14000
14116
|
);
|
|
14001
14117
|
add("lock", "ok", `${Object.keys(lock.entries).length} valid v2 lock entr(ies)`);
|
|
14002
14118
|
const dirs = [...new Set(specs.flatMap((spec) => [
|
|
14003
|
-
|
|
14004
|
-
...spec.quality ? [
|
|
14119
|
+
path23.dirname(spec.outFile),
|
|
14120
|
+
...spec.quality ? [path23.dirname(spec.quality.outFile)] : []
|
|
14005
14121
|
]))];
|
|
14006
14122
|
const unwritable = [];
|
|
14007
14123
|
for (const dir of dirs) {
|
|
@@ -14024,7 +14140,7 @@ async function doctor(loaded, specs, lock, lockPath, opts = {}) {
|
|
|
14024
14140
|
for (let index = 0; index < entry.outputs.length; index++) {
|
|
14025
14141
|
const output = entry.outputs[index];
|
|
14026
14142
|
const spec = specByKey.get(key);
|
|
14027
|
-
const absolute = spec ? currentEntryOutputPath(entry, spec, index) : resolveOutputPath(output.path,
|
|
14143
|
+
const absolute = spec ? currentEntryOutputPath(entry, spec, index) : resolveOutputPath(output.path, path23.dirname(lockPath));
|
|
14028
14144
|
const owner = outputOwners.get(absolute);
|
|
14029
14145
|
if (owner && owner !== key) duplicateOutputs.push(`${output.path} (${owner}, ${key})`);
|
|
14030
14146
|
outputOwners.set(absolute, key);
|
|
@@ -14039,7 +14155,7 @@ async function doctor(loaded, specs, lock, lockPath, opts = {}) {
|
|
|
14039
14155
|
} else {
|
|
14040
14156
|
add("lock-outputs", "ok", `${outputOwners.size} recorded output path(s) are uniquely owned`);
|
|
14041
14157
|
}
|
|
14042
|
-
const cacheDir =
|
|
14158
|
+
const cacheDir = path23.join(path23.dirname(lockPath), ".pixelkiln", "cache");
|
|
14043
14159
|
const stranded = [];
|
|
14044
14160
|
const invalidState = [];
|
|
14045
14161
|
const staleMapUrls = [];
|
|
@@ -14055,7 +14171,7 @@ async function doctor(loaded, specs, lock, lockPath, opts = {}) {
|
|
|
14055
14171
|
const hasSource = Boolean(entry.sourceUrl || entry.sourceUrls?.length);
|
|
14056
14172
|
let hasCache = false;
|
|
14057
14173
|
for (const output of entry.outputs) {
|
|
14058
|
-
const cached =
|
|
14174
|
+
const cached = path23.join(
|
|
14059
14175
|
cacheDir,
|
|
14060
14176
|
cacheFileName(output.sha256, output.mediaType ?? MediaType.PNG)
|
|
14061
14177
|
);
|
|
@@ -14155,7 +14271,7 @@ async function nearestExistingDirectory(start) {
|
|
|
14155
14271
|
if ((await stat2(current)).isDirectory()) return current;
|
|
14156
14272
|
} catch {
|
|
14157
14273
|
}
|
|
14158
|
-
const parent =
|
|
14274
|
+
const parent = path23.dirname(current);
|
|
14159
14275
|
if (parent === current) return current;
|
|
14160
14276
|
current = parent;
|
|
14161
14277
|
}
|
|
@@ -14164,7 +14280,7 @@ async function nearestExistingDirectory(start) {
|
|
|
14164
14280
|
// src/pipeline/adopt.ts
|
|
14165
14281
|
import { existsSync as existsSync18 } from "fs";
|
|
14166
14282
|
import { readFile as readFile14, writeFile as writeFile7 } from "fs/promises";
|
|
14167
|
-
import
|
|
14283
|
+
import path24 from "path";
|
|
14168
14284
|
async function adopt(provider, specs, lock, lockPath, opts = {}) {
|
|
14169
14285
|
const log = opts.onProgress ?? (() => {
|
|
14170
14286
|
});
|
|
@@ -14303,7 +14419,7 @@ import { readFile as readFile17 } from "fs/promises";
|
|
|
14303
14419
|
// src/pipeline/init.ts
|
|
14304
14420
|
import { readdir as readdir3, readFile as readFile15, writeFile as writeFile8 } from "fs/promises";
|
|
14305
14421
|
import { existsSync as existsSync19 } from "fs";
|
|
14306
|
-
import
|
|
14422
|
+
import path25 from "path";
|
|
14307
14423
|
function pngSize(buf) {
|
|
14308
14424
|
if (buf.length < 24) return null;
|
|
14309
14425
|
const isPng = buf.readUInt32BE(0) === 2303741511 && buf.readUInt32BE(4) === 218765834;
|
|
@@ -14317,7 +14433,7 @@ function slugify(value) {
|
|
|
14317
14433
|
async function walk(dir, exclude) {
|
|
14318
14434
|
const out = [];
|
|
14319
14435
|
for (const entry of await readdir3(dir, { withFileTypes: true })) {
|
|
14320
|
-
const full =
|
|
14436
|
+
const full = path25.join(dir, entry.name);
|
|
14321
14437
|
if (exclude.some((x) => full.includes(x))) continue;
|
|
14322
14438
|
if (entry.isDirectory()) out.push(...await walk(full, exclude));
|
|
14323
14439
|
else if (entry.name.toLowerCase().endsWith(".png")) out.push(full);
|
|
@@ -14336,9 +14452,9 @@ async function scanAssets(root, opts = {}) {
|
|
|
14336
14452
|
skipped.push(`${file} (not a readable PNG)`);
|
|
14337
14453
|
continue;
|
|
14338
14454
|
}
|
|
14339
|
-
const rel =
|
|
14340
|
-
const category =
|
|
14341
|
-
let id = slugify(
|
|
14455
|
+
const rel = path25.relative(root, file);
|
|
14456
|
+
const category = path25.dirname(rel) === "." ? "" : path25.dirname(rel);
|
|
14457
|
+
let id = slugify(path25.basename(rel));
|
|
14342
14458
|
if (seen.has(id)) {
|
|
14343
14459
|
const n = seen.get(id) + 1;
|
|
14344
14460
|
seen.set(id, n);
|
|
@@ -15061,15 +15177,15 @@ async function runPicker(provider, lock, lockPath, opts = {}) {
|
|
|
15061
15177
|
}
|
|
15062
15178
|
|
|
15063
15179
|
// src/gallery/snapshot.ts
|
|
15064
|
-
import { createHash as
|
|
15180
|
+
import { createHash as createHash5 } from "crypto";
|
|
15065
15181
|
import { existsSync as existsSync26 } from "fs";
|
|
15066
15182
|
import { readFile as readFile24, stat as stat3 } from "fs/promises";
|
|
15067
|
-
import
|
|
15183
|
+
import path33 from "path";
|
|
15068
15184
|
|
|
15069
15185
|
// src/workspace.ts
|
|
15070
15186
|
import { mkdir as mkdir7, readFile as readFile18, rename as rename6, rm as rm7, writeFile as writeFile9 } from "fs/promises";
|
|
15071
15187
|
import { existsSync as existsSync21 } from "fs";
|
|
15072
|
-
import
|
|
15188
|
+
import path26 from "path";
|
|
15073
15189
|
import { z as z7 } from "zod";
|
|
15074
15190
|
var WorkspaceProjectSchema = z7.object({
|
|
15075
15191
|
id: z7.string().min(1),
|
|
@@ -15111,7 +15227,7 @@ async function saveWorkspace(workspacePath, ws) {
|
|
|
15111
15227
|
version: 1,
|
|
15112
15228
|
projects: [...ws.projects].sort((a, b) => a.id.localeCompare(b.id))
|
|
15113
15229
|
};
|
|
15114
|
-
await mkdir7(
|
|
15230
|
+
await mkdir7(path26.dirname(path26.resolve(workspacePath)), { recursive: true });
|
|
15115
15231
|
const tmp = `${workspacePath}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
15116
15232
|
try {
|
|
15117
15233
|
await writeFile9(tmp, JSON.stringify(sorted, null, 2) + "\n");
|
|
@@ -15121,12 +15237,12 @@ async function saveWorkspace(workspacePath, ws) {
|
|
|
15121
15237
|
}
|
|
15122
15238
|
}
|
|
15123
15239
|
function toPortablePath(dir, absolute) {
|
|
15124
|
-
return
|
|
15240
|
+
return path26.relative(dir, absolute).split(path26.sep).join("/");
|
|
15125
15241
|
}
|
|
15126
15242
|
function resolveProject(dir, project) {
|
|
15127
15243
|
return {
|
|
15128
|
-
manifestPath:
|
|
15129
|
-
lockPath:
|
|
15244
|
+
manifestPath: path26.resolve(dir, project.manifest.split("/").join(path26.sep)),
|
|
15245
|
+
lockPath: path26.resolve(dir, project.lock.split("/").join(path26.sep))
|
|
15130
15246
|
};
|
|
15131
15247
|
}
|
|
15132
15248
|
function validateWorkspace(ws, dir) {
|
|
@@ -15139,7 +15255,7 @@ function validateWorkspace(ws, dir) {
|
|
|
15139
15255
|
const { manifestPath, lockPath } = resolveProject(dir, project);
|
|
15140
15256
|
lockOwners.set(lockPath, [...lockOwners.get(lockPath) ?? [], project.id]);
|
|
15141
15257
|
manifestOwners.set(manifestPath, [...manifestOwners.get(manifestPath) ?? [], project.id]);
|
|
15142
|
-
if (
|
|
15258
|
+
if (path26.isAbsolute(project.manifest) || path26.isAbsolute(project.lock)) {
|
|
15143
15259
|
diagnostics.push({
|
|
15144
15260
|
id: "absolute-path",
|
|
15145
15261
|
level: "warning",
|
|
@@ -15201,15 +15317,15 @@ function validateWorkspace(ws, dir) {
|
|
|
15201
15317
|
|
|
15202
15318
|
// src/gallery/edit.ts
|
|
15203
15319
|
import { readFile as readFile23, rename as rename9, writeFile as writeFile15 } from "fs/promises";
|
|
15204
|
-
import
|
|
15320
|
+
import path32 from "path";
|
|
15205
15321
|
import { z as z13 } from "zod";
|
|
15206
15322
|
|
|
15207
15323
|
// src/manifest-edit.ts
|
|
15208
15324
|
import { readFile as readFile19, rename as rename7, rm as rm8, writeFile as writeFile10 } from "fs/promises";
|
|
15209
|
-
import
|
|
15325
|
+
import path27 from "path";
|
|
15210
15326
|
import { z as z8 } from "zod";
|
|
15211
15327
|
var HexSha = z8.string().regex(/^[0-9a-f]{64}$/);
|
|
15212
|
-
var ProjectFileSchema = z8.string().min(1).refine((file) => !
|
|
15328
|
+
var ProjectFileSchema = z8.string().min(1).refine((file) => !path27.isAbsolute(file) && !file.split(/[\\/]/).includes(".."), "must be a path inside the project");
|
|
15213
15329
|
var AssetPatchSchema = z8.object({
|
|
15214
15330
|
prompt: z8.string().optional(),
|
|
15215
15331
|
/** Per-style override for the style being viewed; null removes it. */
|
|
@@ -15628,7 +15744,7 @@ function applyEdit(raw, edit) {
|
|
|
15628
15744
|
}
|
|
15629
15745
|
}
|
|
15630
15746
|
async function withCandidate(manifestPath, edit, use, unchanged) {
|
|
15631
|
-
const absolute =
|
|
15747
|
+
const absolute = path27.resolve(manifestPath);
|
|
15632
15748
|
const original = await readFile19(absolute, "utf8");
|
|
15633
15749
|
if (sha256(original) !== edit.expectedSha256) throw new ManifestDriftError(absolute);
|
|
15634
15750
|
let raw;
|
|
@@ -15642,9 +15758,9 @@ async function withCandidate(manifestPath, edit, use, unchanged) {
|
|
|
15642
15758
|
applyEdit(raw, edit);
|
|
15643
15759
|
const next = serializeLike(original, raw);
|
|
15644
15760
|
if (next === original) return unchanged(absolute, original);
|
|
15645
|
-
const tmp =
|
|
15646
|
-
|
|
15647
|
-
`.${
|
|
15761
|
+
const tmp = path27.join(
|
|
15762
|
+
path27.dirname(absolute),
|
|
15763
|
+
`.${path27.basename(absolute)}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`
|
|
15648
15764
|
);
|
|
15649
15765
|
try {
|
|
15650
15766
|
await writeFile10(tmp, next);
|
|
@@ -15675,16 +15791,16 @@ async function applyManifestEdit(manifestPath, edit) {
|
|
|
15675
15791
|
// src/pipeline/hand-edit.ts
|
|
15676
15792
|
import { existsSync as existsSync22 } from "fs";
|
|
15677
15793
|
import { copyFile, mkdir as mkdir8, readFile as readFile20, rename as rename8, rm as rm9, writeFile as writeFile11 } from "fs/promises";
|
|
15678
|
-
import
|
|
15794
|
+
import path28 from "path";
|
|
15679
15795
|
import { z as z9 } from "zod";
|
|
15680
15796
|
var HAND_EDIT_DIR = "edits";
|
|
15681
15797
|
function handEditPath(loaded, spec) {
|
|
15682
15798
|
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
|
|
15799
|
+
const outDir = path28.resolve(loaded.root, style?.outDir ?? ".");
|
|
15800
|
+
const relative = path28.relative(outDir, spec.outFile);
|
|
15801
|
+
const inside = relative && !relative.startsWith("..") && !path28.isAbsolute(relative);
|
|
15802
|
+
const file = inside ? relative : path28.basename(spec.outFile);
|
|
15803
|
+
return path28.join(outDir, HAND_EDIT_DIR, file.replace(/\.gif$/i, ".png"));
|
|
15688
15804
|
}
|
|
15689
15805
|
function handEditBases(spec, lock) {
|
|
15690
15806
|
const entry = lock.entries[lockKey(spec.styleId, spec.assetId)];
|
|
@@ -15730,7 +15846,7 @@ async function startHandEdit(loaded, lock, spec, opts = {}) {
|
|
|
15730
15846
|
`${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
15847
|
);
|
|
15732
15848
|
}
|
|
15733
|
-
const existing = spec.source ?
|
|
15849
|
+
const existing = spec.source ? path28.resolve(loaded.root, spec.source) : null;
|
|
15734
15850
|
const editPath = existing ?? handEditPath(loaded, spec);
|
|
15735
15851
|
const members = handEditMembers(loaded, spec, lock, editPath);
|
|
15736
15852
|
let created = false;
|
|
@@ -15741,7 +15857,7 @@ async function startHandEdit(loaded, lock, spec, opts = {}) {
|
|
|
15741
15857
|
`${spec.styleId}/${spec.assetId} has no generated PNG on disk to start from; generate or restore it first`
|
|
15742
15858
|
);
|
|
15743
15859
|
}
|
|
15744
|
-
await mkdir8(
|
|
15860
|
+
await mkdir8(path28.dirname(member.path), { recursive: true });
|
|
15745
15861
|
await copyFile(member.base, member.path);
|
|
15746
15862
|
created = true;
|
|
15747
15863
|
}
|
|
@@ -15879,7 +15995,7 @@ async function saveHandEdit(loaded, lock, spec, input) {
|
|
|
15879
15995
|
editor: input.editor,
|
|
15880
15996
|
protocol: input.protocol,
|
|
15881
15997
|
savedAt: (input.now ?? /* @__PURE__ */ new Date()).toISOString(),
|
|
15882
|
-
project: projectPath ?
|
|
15998
|
+
project: projectPath ? path28.basename(projectPath) : null,
|
|
15883
15999
|
outputs: incoming.map((frame, index) => ({
|
|
15884
16000
|
role: frame.role,
|
|
15885
16001
|
basedOn: basedOnHash(entry, index),
|
|
@@ -15923,7 +16039,7 @@ function openInEditor(file, editor = process.env.PIXELKILN_EDITOR) {
|
|
|
15923
16039
|
// src/gallery/upload.ts
|
|
15924
16040
|
import { existsSync as existsSync23 } from "fs";
|
|
15925
16041
|
import { mkdir as mkdir9, writeFile as writeFile12 } from "fs/promises";
|
|
15926
|
-
import
|
|
16042
|
+
import path29 from "path";
|
|
15927
16043
|
import { z as z10 } from "zod";
|
|
15928
16044
|
var MAX_UPLOAD_BYTES = 8 * 1024 * 1024;
|
|
15929
16045
|
var UploadImageSchema = z10.object({
|
|
@@ -15936,12 +16052,12 @@ var UploadImageSchema = z10.object({
|
|
|
15936
16052
|
overwrite: z10.boolean().optional()
|
|
15937
16053
|
}).strict();
|
|
15938
16054
|
async function saveProjectImage(root, request) {
|
|
15939
|
-
const file =
|
|
15940
|
-
const relative =
|
|
15941
|
-
if (relative.startsWith("..") ||
|
|
16055
|
+
const file = path29.resolve(root, request.path);
|
|
16056
|
+
const relative = path29.relative(root, file);
|
|
16057
|
+
if (relative.startsWith("..") || path29.isAbsolute(relative)) {
|
|
15942
16058
|
throw new ManifestEditError("an upload must stay inside the project");
|
|
15943
16059
|
}
|
|
15944
|
-
const extension =
|
|
16060
|
+
const extension = path29.extname(file).toLowerCase();
|
|
15945
16061
|
const expected = extension === ".png" ? "png" : extension === ".jpg" || extension === ".jpeg" ? "jpeg" : null;
|
|
15946
16062
|
if (!expected) throw new ManifestEditError("an upload must be a .png, .jpg, or .jpeg path");
|
|
15947
16063
|
const bytes = Buffer.from(request.base64, "base64");
|
|
@@ -15954,17 +16070,17 @@ async function saveProjectImage(root, request) {
|
|
|
15954
16070
|
throw new ManifestEditError(`the upload is a ${metadata.format.toUpperCase()}; name it ${metadata.format === "png" ? ".png" : ".jpg"}`);
|
|
15955
16071
|
}
|
|
15956
16072
|
if (existsSync23(file) && !request.overwrite) {
|
|
15957
|
-
throw new ManifestEditError(`${relative.split(
|
|
16073
|
+
throw new ManifestEditError(`${relative.split(path29.sep).join("/")} already exists; choose another name, or replace it`);
|
|
15958
16074
|
}
|
|
15959
|
-
await mkdir9(
|
|
16075
|
+
await mkdir9(path29.dirname(file), { recursive: true });
|
|
15960
16076
|
await writeFile12(file, bytes);
|
|
15961
|
-
return relative.split(
|
|
16077
|
+
return relative.split(path29.sep).join("/");
|
|
15962
16078
|
}
|
|
15963
16079
|
|
|
15964
16080
|
// src/gallery/image-revision.ts
|
|
15965
16081
|
import { existsSync as existsSync24 } from "fs";
|
|
15966
16082
|
import { readFile as readFile21, rm as rm10, writeFile as writeFile13 } from "fs/promises";
|
|
15967
|
-
import
|
|
16083
|
+
import path30 from "path";
|
|
15968
16084
|
import { z as z11 } from "zod";
|
|
15969
16085
|
var InpaintRevisionRequestSchema = z11.object({
|
|
15970
16086
|
action: z11.literal("create-inpaint-revision"),
|
|
@@ -15983,14 +16099,14 @@ var InpaintRevisionRequestSchema = z11.object({
|
|
|
15983
16099
|
overwrite: z11.boolean().optional()
|
|
15984
16100
|
}).strict();
|
|
15985
16101
|
async function createInpaintRevision(manifestPath, request) {
|
|
15986
|
-
const root =
|
|
15987
|
-
if (
|
|
16102
|
+
const root = path30.dirname(path30.resolve(manifestPath));
|
|
16103
|
+
if (path30.extname(request.maskPath).toLowerCase() !== ".png") {
|
|
15988
16104
|
throw new ManifestEditError("the mask must be a .png path inside the project");
|
|
15989
16105
|
}
|
|
15990
16106
|
const bytes = Buffer.from(request.maskBase64, "base64");
|
|
15991
16107
|
const metadata = imageMetadata(bytes);
|
|
15992
16108
|
if (!metadata || metadata.format !== "png") throw new ManifestEditError("the mask is not a readable PNG");
|
|
15993
|
-
const file =
|
|
16109
|
+
const file = path30.resolve(root, request.maskPath);
|
|
15994
16110
|
const previous = existsSync24(file) ? await readFile21(file) : null;
|
|
15995
16111
|
const mask = await saveProjectImage(root, {
|
|
15996
16112
|
action: "upload-image",
|
|
@@ -16020,7 +16136,7 @@ async function createInpaintRevision(manifestPath, request) {
|
|
|
16020
16136
|
// src/gallery/skeleton.ts
|
|
16021
16137
|
import { existsSync as existsSync25 } from "fs";
|
|
16022
16138
|
import { mkdir as mkdir10, readFile as readFile22, rm as rm11, writeFile as writeFile14 } from "fs/promises";
|
|
16023
|
-
import
|
|
16139
|
+
import path31 from "path";
|
|
16024
16140
|
import { z as z12 } from "zod";
|
|
16025
16141
|
var SkeletonEstimateRequestSchema = z12.object({
|
|
16026
16142
|
project: z12.string().min(1).optional(),
|
|
@@ -16058,10 +16174,10 @@ var SkeletonKeypointsUpdateSchema = z12.object({
|
|
|
16058
16174
|
set: SkeletonSetSchema
|
|
16059
16175
|
}).strict();
|
|
16060
16176
|
async function createSkeletonAnimation(manifestPath, request) {
|
|
16061
|
-
const root =
|
|
16062
|
-
const file =
|
|
16063
|
-
const relative =
|
|
16064
|
-
if (!request.keypointsFile.endsWith(".json") || relative.startsWith("..") ||
|
|
16177
|
+
const root = path31.dirname(path31.resolve(manifestPath));
|
|
16178
|
+
const file = path31.resolve(root, request.keypointsFile);
|
|
16179
|
+
const relative = path31.relative(root, file);
|
|
16180
|
+
if (!request.keypointsFile.endsWith(".json") || relative.startsWith("..") || path31.isAbsolute(relative)) {
|
|
16065
16181
|
throw new ManifestEditError("the keypoints file must be a .json path inside the project");
|
|
16066
16182
|
}
|
|
16067
16183
|
const existed = existsSync25(file);
|
|
@@ -16071,7 +16187,7 @@ async function createSkeletonAnimation(manifestPath, request) {
|
|
|
16071
16187
|
throw new ManifestEditError(`${request.keypointsFile} already exists; choose another path, or replace it`);
|
|
16072
16188
|
}
|
|
16073
16189
|
if (existed) previous = await readFile22(file);
|
|
16074
|
-
await mkdir10(
|
|
16190
|
+
await mkdir10(path31.dirname(file), { recursive: true });
|
|
16075
16191
|
await writeFile14(file, JSON.stringify(request.set, null, 2) + "\n");
|
|
16076
16192
|
} else if (!existed) {
|
|
16077
16193
|
throw new ManifestEditError(`${request.keypointsFile} does not exist; paste poses or estimate them from the sprite`);
|
|
@@ -16087,7 +16203,7 @@ async function createSkeletonAnimation(manifestPath, request) {
|
|
|
16087
16203
|
revision: {
|
|
16088
16204
|
mode: "animate-skeleton",
|
|
16089
16205
|
from: request.from,
|
|
16090
|
-
keypointsFile: relative.split(
|
|
16206
|
+
keypointsFile: relative.split(path31.sep).join("/"),
|
|
16091
16207
|
direction: request.direction,
|
|
16092
16208
|
...request.description ? { description: request.description } : {},
|
|
16093
16209
|
...request.skeletonTemplate ? { skeletonTemplate: request.skeletonTemplate } : {}
|
|
@@ -16108,19 +16224,19 @@ async function updateSkeletonKeypoints(ctx, request) {
|
|
|
16108
16224
|
if (!spec) throw new ManifestEditError(`"${key}" is not declared by the manifest`);
|
|
16109
16225
|
const file = spec.revision?.mode === "animate-skeleton" ? spec.revision.keypointsFile : void 0;
|
|
16110
16226
|
if (!file) throw new ManifestEditError(`${key} is not an animate-skeleton revision`);
|
|
16111
|
-
const relative =
|
|
16112
|
-
if (relative.startsWith("..") ||
|
|
16227
|
+
const relative = path31.relative(ctx.loaded.root, file);
|
|
16228
|
+
if (relative.startsWith("..") || path31.isAbsolute(relative)) {
|
|
16113
16229
|
throw new ManifestEditError(`${key}'s keypoints file is outside the project; edit it by hand`);
|
|
16114
16230
|
}
|
|
16115
16231
|
const current = existsSync25(file) ? sha256(await readFile22(file)) : null;
|
|
16116
16232
|
if (current !== request.expectedSha256) {
|
|
16117
16233
|
throw Object.assign(
|
|
16118
|
-
new Error(`${relative.split(
|
|
16234
|
+
new Error(`${relative.split(path31.sep).join("/")} changed on disk since this page loaded it; press Refresh and edit again`),
|
|
16119
16235
|
{ status: 409 }
|
|
16120
16236
|
);
|
|
16121
16237
|
}
|
|
16122
16238
|
await writeFile14(file, JSON.stringify(request.set, null, 2) + "\n");
|
|
16123
|
-
return relative.split(
|
|
16239
|
+
return relative.split(path31.sep).join("/");
|
|
16124
16240
|
}
|
|
16125
16241
|
|
|
16126
16242
|
// src/gallery/edit.ts
|
|
@@ -16174,7 +16290,7 @@ function createGalleryEditHandler(opts) {
|
|
|
16174
16290
|
const history = /* @__PURE__ */ new Map();
|
|
16175
16291
|
const manifestOf = async (project) => {
|
|
16176
16292
|
try {
|
|
16177
|
-
return
|
|
16293
|
+
return path32.resolve(await opts.manifestFor(typeof project === "string" ? project : void 0));
|
|
16178
16294
|
} catch {
|
|
16179
16295
|
return null;
|
|
16180
16296
|
}
|
|
@@ -16197,7 +16313,7 @@ function createGalleryEditHandler(opts) {
|
|
|
16197
16313
|
await writeFile15(tmp, last.before);
|
|
16198
16314
|
await rename9(tmp, manifestPath2);
|
|
16199
16315
|
stack.pop();
|
|
16200
|
-
log(` manifest edit undone in ${
|
|
16316
|
+
log(` manifest edit undone in ${path32.relative(process.cwd(), manifestPath2) || manifestPath2}`);
|
|
16201
16317
|
return opts.reload();
|
|
16202
16318
|
}
|
|
16203
16319
|
const manifestPath = await manifestOf(body?.project);
|
|
@@ -16259,7 +16375,7 @@ function createEditApplier(opts, log) {
|
|
|
16259
16375
|
} catch (error) {
|
|
16260
16376
|
throw new ManifestEditError(error instanceof Error ? error.message : String(error));
|
|
16261
16377
|
}
|
|
16262
|
-
const written = await saveProjectImage(
|
|
16378
|
+
const written = await saveProjectImage(path32.dirname(path32.resolve(manifestPath2)), upload.data);
|
|
16263
16379
|
log(` image added: ${written}`);
|
|
16264
16380
|
return opts.reload();
|
|
16265
16381
|
}
|
|
@@ -16326,7 +16442,7 @@ function createEditApplier(opts, log) {
|
|
|
16326
16442
|
}
|
|
16327
16443
|
const result = await applyManifestEdit(manifestPath, parsed.data);
|
|
16328
16444
|
if (result.changed) {
|
|
16329
|
-
const relative =
|
|
16445
|
+
const relative = path32.relative(process.cwd(), result.manifestPath);
|
|
16330
16446
|
const shown = !relative || relative.startsWith("..") ? result.manifestPath : relative;
|
|
16331
16447
|
log(` manifest edited: ${describeEdit(parsed.data)} in ${shown}`);
|
|
16332
16448
|
}
|
|
@@ -16354,7 +16470,7 @@ function describeEdit(edit) {
|
|
|
16354
16470
|
// src/gallery/snapshot.ts
|
|
16355
16471
|
var PROJECT_FILE_TYPE = "application/octet-stream";
|
|
16356
16472
|
function galleryMediaId(absolutePath) {
|
|
16357
|
-
return
|
|
16473
|
+
return createHash5("sha256").update(path33.resolve(absolutePath)).digest("hex").slice(0, 24);
|
|
16358
16474
|
}
|
|
16359
16475
|
function galleryMediaRoute(id) {
|
|
16360
16476
|
return `/media/${id}`;
|
|
@@ -16373,7 +16489,7 @@ function describeHistory(media, entry, cacheDir) {
|
|
|
16373
16489
|
return (entry.history ?? []).map((generation, i) => {
|
|
16374
16490
|
const outputs = generation.outputs.map((output) => {
|
|
16375
16491
|
const mediaType = output.mediaType ?? MediaType.PNG;
|
|
16376
|
-
const file =
|
|
16492
|
+
const file = path33.join(cacheDir, cacheFileName(output.sha256, mediaType));
|
|
16377
16493
|
let url = null;
|
|
16378
16494
|
if (existsSync26(file)) {
|
|
16379
16495
|
const id = galleryMediaId(file);
|
|
@@ -16505,9 +16621,9 @@ async function describeOutput(media, root, absolutePath, recorded = {}) {
|
|
|
16505
16621
|
async function describeQuality(media, root, inspection) {
|
|
16506
16622
|
const outputPaths = inspection.outputs ?? [inspection.output];
|
|
16507
16623
|
const outputs = await Promise.all(
|
|
16508
|
-
outputPaths.map((file) => describeOutput(media, root,
|
|
16624
|
+
outputPaths.map((file) => describeOutput(media, root, path33.resolve(root, file)))
|
|
16509
16625
|
);
|
|
16510
|
-
const recordPath =
|
|
16626
|
+
const recordPath = path33.resolve(root, inspection.record);
|
|
16511
16627
|
const recordExists = existsSync26(recordPath);
|
|
16512
16628
|
let options = null;
|
|
16513
16629
|
let check = null;
|
|
@@ -16527,7 +16643,7 @@ async function describeQuality(media, root, inspection) {
|
|
|
16527
16643
|
return {
|
|
16528
16644
|
state: inspection.state,
|
|
16529
16645
|
reason: inspection.reason,
|
|
16530
|
-
output: portableOutputPath(
|
|
16646
|
+
output: portableOutputPath(path33.resolve(root, inspection.output), root),
|
|
16531
16647
|
record: portableOutputPath(recordPath, root),
|
|
16532
16648
|
recordExists,
|
|
16533
16649
|
outputs,
|
|
@@ -16567,7 +16683,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
16567
16683
|
const items = [];
|
|
16568
16684
|
const manifestText = await readFile24(loaded.path, "utf8");
|
|
16569
16685
|
const manifestSha256 = sha256(manifestText);
|
|
16570
|
-
const cacheDir =
|
|
16686
|
+
const cacheDir = path33.resolve(path33.dirname(opts.lockPath), ".pixelkiln", "cache");
|
|
16571
16687
|
let rawStyles = {};
|
|
16572
16688
|
try {
|
|
16573
16689
|
const raw = JSON.parse(manifestText);
|
|
@@ -16591,7 +16707,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
16591
16707
|
)
|
|
16592
16708
|
);
|
|
16593
16709
|
if (spec.source) {
|
|
16594
|
-
const editPath =
|
|
16710
|
+
const editPath = path33.resolve(root, spec.source);
|
|
16595
16711
|
const memberCount = sourceIsStem(spec.source, entry, root) ? entry.outputs.length : 1;
|
|
16596
16712
|
const editShas = await Promise.all(
|
|
16597
16713
|
Array.from({ length: memberCount }, async (_, index) => {
|
|
@@ -16640,7 +16756,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
16640
16756
|
editStatus = edits.some((member) => !member.exists) ? "missing" : editChanged.every((changed) => !changed) ? "same" : regenerated ? "regenerated-since" : "edited";
|
|
16641
16757
|
}
|
|
16642
16758
|
} else if (spec.source) {
|
|
16643
|
-
outputs = [await describeOutput(media, root,
|
|
16759
|
+
outputs = [await describeOutput(media, root, path33.resolve(root, spec.source))];
|
|
16644
16760
|
} else if (!entry && existsSync26(spec.outFile)) {
|
|
16645
16761
|
outputs = [await describeOutput(media, root, spec.outFile)];
|
|
16646
16762
|
} else {
|
|
@@ -16844,7 +16960,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
16844
16960
|
id: loaded.manifest.name,
|
|
16845
16961
|
name: loaded.manifest.name,
|
|
16846
16962
|
manifest: loaded.path,
|
|
16847
|
-
lock:
|
|
16963
|
+
lock: path33.resolve(opts.lockPath),
|
|
16848
16964
|
root,
|
|
16849
16965
|
provider: loaded.manifest.provider,
|
|
16850
16966
|
account: null,
|
|
@@ -16870,7 +16986,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
16870
16986
|
return { snapshot, media };
|
|
16871
16987
|
}
|
|
16872
16988
|
async function buildWorkspaceGallerySnapshot(opts) {
|
|
16873
|
-
const dir =
|
|
16989
|
+
const dir = path33.dirname(path33.resolve(opts.workspacePath));
|
|
16874
16990
|
const media = /* @__PURE__ */ new Map();
|
|
16875
16991
|
const projects = [];
|
|
16876
16992
|
const items = [];
|
|
@@ -16913,7 +17029,7 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
16913
17029
|
id: project.id,
|
|
16914
17030
|
name: loaded.manifest.name,
|
|
16915
17031
|
manifest: manifestPath,
|
|
16916
|
-
lock:
|
|
17032
|
+
lock: path33.resolve(lockPath),
|
|
16917
17033
|
root: loaded.root,
|
|
16918
17034
|
provider: loaded.manifest.provider,
|
|
16919
17035
|
account: project.account ?? null,
|
|
@@ -16929,8 +17045,8 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
16929
17045
|
id: project.id,
|
|
16930
17046
|
name: project.id,
|
|
16931
17047
|
manifest: manifestPath,
|
|
16932
|
-
lock:
|
|
16933
|
-
root:
|
|
17048
|
+
lock: path33.resolve(lockPath),
|
|
17049
|
+
root: path33.dirname(manifestPath),
|
|
16934
17050
|
provider: project.provider,
|
|
16935
17051
|
account: project.account ?? null,
|
|
16936
17052
|
manifestSha256: null,
|
|
@@ -16957,7 +17073,7 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
16957
17073
|
version: 1,
|
|
16958
17074
|
generatedAt: (opts.now ?? (() => /* @__PURE__ */ new Date()))().toISOString(),
|
|
16959
17075
|
project: null,
|
|
16960
|
-
workspace: { path:
|
|
17076
|
+
workspace: { path: path33.resolve(opts.workspacePath), dir, projects },
|
|
16961
17077
|
filter,
|
|
16962
17078
|
totals: {
|
|
16963
17079
|
entries: projects.reduce((sum, project) => sum + project.entries, 0),
|
|
@@ -16974,7 +17090,7 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
16974
17090
|
}
|
|
16975
17091
|
|
|
16976
17092
|
// src/gallery/page.ts
|
|
16977
|
-
import { existsSync as existsSync27, readFileSync as
|
|
17093
|
+
import { existsSync as existsSync27, readFileSync as readFileSync6 } from "fs";
|
|
16978
17094
|
|
|
16979
17095
|
// src/gallery/client-bundle.ts
|
|
16980
17096
|
import { createRequire } from "module";
|
|
@@ -17014,8 +17130,8 @@ var client;
|
|
|
17014
17130
|
function clientAssets() {
|
|
17015
17131
|
const prebuilt = new URL("gallery.js", CLIENT_DIR);
|
|
17016
17132
|
client ??= {
|
|
17017
|
-
css:
|
|
17018
|
-
js: existsSync27(prebuilt) ?
|
|
17133
|
+
css: readFileSync6(new URL("gallery.css", CLIENT_DIR), "utf8"),
|
|
17134
|
+
js: existsSync27(prebuilt) ? readFileSync6(prebuilt, "utf8") : bundleGalleryClient()
|
|
17019
17135
|
};
|
|
17020
17136
|
return client;
|
|
17021
17137
|
}
|
|
@@ -17157,11 +17273,11 @@ var EDITOR_PIN = pin_default;
|
|
|
17157
17273
|
var EDITOR_RELEASE_BASE = "https://github.com/gfargo/pixelkiln/releases/download";
|
|
17158
17274
|
|
|
17159
17275
|
// src/editor/install.ts
|
|
17160
|
-
import { createHash as
|
|
17276
|
+
import { createHash as createHash6 } from "crypto";
|
|
17161
17277
|
import { existsSync as existsSync28 } from "fs";
|
|
17162
17278
|
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
|
|
17279
|
+
import os3 from "os";
|
|
17280
|
+
import path34 from "path";
|
|
17165
17281
|
var EditorInstallError = class extends Error {
|
|
17166
17282
|
constructor(message6, file) {
|
|
17167
17283
|
super(message6);
|
|
@@ -17172,15 +17288,15 @@ var EditorInstallError = class extends Error {
|
|
|
17172
17288
|
};
|
|
17173
17289
|
function editorToolsRoot() {
|
|
17174
17290
|
const override = process.env.PIXELKILN_TOOLS_DIR?.trim();
|
|
17175
|
-
if (override) return
|
|
17176
|
-
if (process.platform === "darwin") return
|
|
17291
|
+
if (override) return path34.resolve(override);
|
|
17292
|
+
if (process.platform === "darwin") return path34.join(os3.homedir(), "Library", "Caches", "pixelkiln", "tools");
|
|
17177
17293
|
if (process.platform === "win32") {
|
|
17178
|
-
return
|
|
17294
|
+
return path34.join(process.env.LOCALAPPDATA ?? path34.join(os3.homedir(), "AppData", "Local"), "pixelkiln", "tools");
|
|
17179
17295
|
}
|
|
17180
|
-
return
|
|
17296
|
+
return path34.join(process.env.XDG_CACHE_HOME ?? path34.join(os3.homedir(), ".cache"), "pixelkiln", "tools");
|
|
17181
17297
|
}
|
|
17182
17298
|
function editorDir(pin = EDITOR_PIN, root = editorToolsRoot()) {
|
|
17183
|
-
return
|
|
17299
|
+
return path34.join(root, "pixelorama", pin.release ?? "unpublished");
|
|
17184
17300
|
}
|
|
17185
17301
|
function editorBaseUrl(pin = EDITOR_PIN) {
|
|
17186
17302
|
const override = process.env.PIXELKILN_EDITOR_URL?.trim();
|
|
@@ -17190,7 +17306,7 @@ function editorBaseUrl(pin = EDITOR_PIN) {
|
|
|
17190
17306
|
}
|
|
17191
17307
|
async function sha256Of(file) {
|
|
17192
17308
|
try {
|
|
17193
|
-
return
|
|
17309
|
+
return createHash6("sha256").update(await readFile25(file)).digest("hex");
|
|
17194
17310
|
} catch {
|
|
17195
17311
|
return null;
|
|
17196
17312
|
}
|
|
@@ -17203,7 +17319,7 @@ async function editorStatus(opts = {}) {
|
|
|
17203
17319
|
let totalBytes = 0;
|
|
17204
17320
|
for (const [name, expected] of Object.entries(pin.files)) {
|
|
17205
17321
|
totalBytes += expected.bytes;
|
|
17206
|
-
const file =
|
|
17322
|
+
const file = path34.join(dir, name);
|
|
17207
17323
|
const info = existsSync28(file) ? await stat4(file).catch(() => null) : null;
|
|
17208
17324
|
if (info && info.size === expected.bytes && await sha256Of(file) === expected.sha256) {
|
|
17209
17325
|
installedBytes += expected.bytes;
|
|
@@ -17257,11 +17373,11 @@ async function installEditor(opts = {}) {
|
|
|
17257
17373
|
if (bytes.length !== expected.bytes) {
|
|
17258
17374
|
throw new EditorInstallError(`${name}: expected ${expected.bytes} bytes, got ${bytes.length}`, name);
|
|
17259
17375
|
}
|
|
17260
|
-
const digest2 =
|
|
17376
|
+
const digest2 = createHash6("sha256").update(bytes).digest("hex");
|
|
17261
17377
|
if (digest2 !== expected.sha256) {
|
|
17262
17378
|
throw new EditorInstallError(`${name}: sha256 ${digest2.slice(0, 12)}\u2026 does not match the pinned ${expected.sha256.slice(0, 12)}\u2026`, name);
|
|
17263
17379
|
}
|
|
17264
|
-
const final =
|
|
17380
|
+
const final = path34.join(dir, name);
|
|
17265
17381
|
const tmp = `${final}.${process.pid}.part`;
|
|
17266
17382
|
try {
|
|
17267
17383
|
await writeFile16(tmp, bytes);
|
|
@@ -17307,12 +17423,12 @@ var EDITOR_CONTENT_TYPES = {
|
|
|
17307
17423
|
".json": "application/json; charset=utf-8"
|
|
17308
17424
|
};
|
|
17309
17425
|
function editorContentType(name) {
|
|
17310
|
-
return EDITOR_CONTENT_TYPES[
|
|
17426
|
+
return EDITOR_CONTENT_TYPES[path34.extname(name).toLowerCase()] ?? null;
|
|
17311
17427
|
}
|
|
17312
17428
|
|
|
17313
17429
|
// src/gallery/editor.ts
|
|
17314
17430
|
import { readFile as readFile26 } from "fs/promises";
|
|
17315
|
-
import
|
|
17431
|
+
import path35 from "path";
|
|
17316
17432
|
function editorRoute(release, name = "index.html") {
|
|
17317
17433
|
return `/editor/${encodeURIComponent(release)}/${encodeURIComponent(name)}`;
|
|
17318
17434
|
}
|
|
@@ -17385,7 +17501,7 @@ function createGalleryEditorHandlers(opts = {}) {
|
|
|
17385
17501
|
if (!status.installed) return null;
|
|
17386
17502
|
let bytes;
|
|
17387
17503
|
try {
|
|
17388
|
-
bytes = await readFile26(
|
|
17504
|
+
bytes = await readFile26(path35.join(dir, name));
|
|
17389
17505
|
} catch {
|
|
17390
17506
|
verified = null;
|
|
17391
17507
|
return null;
|
|
@@ -18078,7 +18194,7 @@ function createGenerateHandlers(opts) {
|
|
|
18078
18194
|
// src/pipeline/salvage.ts
|
|
18079
18195
|
import { readFile as readFile28 } from "fs/promises";
|
|
18080
18196
|
import { existsSync as existsSync29 } from "fs";
|
|
18081
|
-
import
|
|
18197
|
+
import path36 from "path";
|
|
18082
18198
|
function providerClaimId(provider, objectId) {
|
|
18083
18199
|
return `${encodeURIComponent(provider)}:${encodeURIComponent(objectId)}`;
|
|
18084
18200
|
}
|
|
@@ -18192,20 +18308,20 @@ function matchOrphanStyle(prompt, manifest) {
|
|
|
18192
18308
|
return matchStyleByPattern(prompt, manifest);
|
|
18193
18309
|
}
|
|
18194
18310
|
async function loadSiblingManifests(ownManifestPath, workspaceManifestPaths, claimPaths) {
|
|
18195
|
-
const own =
|
|
18311
|
+
const own = path36.resolve(ownManifestPath);
|
|
18196
18312
|
const siblingManifestPaths = [
|
|
18197
18313
|
.../* @__PURE__ */ new Set([
|
|
18198
18314
|
...workspaceManifestPaths,
|
|
18199
|
-
...claimPaths.map((c) =>
|
|
18315
|
+
...claimPaths.map((c) => path36.join(path36.dirname(path36.resolve(c)), "pixelkiln.manifest.json"))
|
|
18200
18316
|
])
|
|
18201
18317
|
];
|
|
18202
18318
|
const siblings = [];
|
|
18203
18319
|
for (const siblingManifestPath of siblingManifestPaths) {
|
|
18204
|
-
if (
|
|
18320
|
+
if (path36.resolve(siblingManifestPath) === own) continue;
|
|
18205
18321
|
if (!existsSync29(siblingManifestPath)) continue;
|
|
18206
18322
|
try {
|
|
18207
18323
|
const { manifest } = await loadManifest(siblingManifestPath);
|
|
18208
|
-
siblings.push({ label:
|
|
18324
|
+
siblings.push({ label: path36.basename(path36.dirname(siblingManifestPath)), manifest });
|
|
18209
18325
|
} catch {
|
|
18210
18326
|
}
|
|
18211
18327
|
}
|
|
@@ -18326,7 +18442,7 @@ async function applyTags(provider, decisions, existing, opts = {}) {
|
|
|
18326
18442
|
|
|
18327
18443
|
// src/pick/salvage-server.ts
|
|
18328
18444
|
import { mkdir as mkdir12, writeFile as writeFile17, readFile as readFile29 } from "fs/promises";
|
|
18329
|
-
import
|
|
18445
|
+
import path37 from "path";
|
|
18330
18446
|
|
|
18331
18447
|
// src/pick/salvage-sheet.ts
|
|
18332
18448
|
var escapeHtml3 = (s) => s.replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
@@ -18492,7 +18608,7 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
18492
18608
|
});
|
|
18493
18609
|
const html = renderSalvageSheet(orphans, {
|
|
18494
18610
|
styleId: ctx.styleId,
|
|
18495
|
-
importDir:
|
|
18611
|
+
importDir: path37.relative(process.cwd(), ctx.importDir) || "."
|
|
18496
18612
|
});
|
|
18497
18613
|
const byId = new Map(orphans.map((o) => [o.id, o]));
|
|
18498
18614
|
const existingTags = new Map(orphans.map((o) => [o.id, o.tags]));
|
|
@@ -18548,9 +18664,9 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
18548
18664
|
if (!buf.subarray(0, 8).equals(PNG_SIGNATURE2)) throw new Error("not a PNG");
|
|
18549
18665
|
decodePng(buf);
|
|
18550
18666
|
const assetId = idFromPrompt(orphan.prompt, taken);
|
|
18551
|
-
const rel =
|
|
18552
|
-
const outFile =
|
|
18553
|
-
await mkdir12(
|
|
18667
|
+
const rel = path37.join("_salvaged", `${assetId}.png`);
|
|
18668
|
+
const outFile = path37.resolve(ctx.importDir, rel);
|
|
18669
|
+
await mkdir12(path37.dirname(outFile), { recursive: true });
|
|
18554
18670
|
await writeFile17(outFile, buf);
|
|
18555
18671
|
ctx.manifest.assets[assetId] = {
|
|
18556
18672
|
prompt: orphan.prompt,
|
|
@@ -18583,7 +18699,7 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
18583
18699
|
sourceUrl: durableSource,
|
|
18584
18700
|
sourceUrls: durableSource ? [{ url: durableSource }] : [],
|
|
18585
18701
|
outputs: [{
|
|
18586
|
-
path: portableOutputPath(outFile,
|
|
18702
|
+
path: portableOutputPath(outFile, path37.dirname(ctx.manifestPath)),
|
|
18587
18703
|
sha256: sha256(buf)
|
|
18588
18704
|
}],
|
|
18589
18705
|
submittedAt: orphan.createdAt,
|