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/cli.js
CHANGED
|
@@ -665,10 +665,10 @@ import { createReadStream } from "fs";
|
|
|
665
665
|
function sha256(data) {
|
|
666
666
|
return createHash("sha256").update(data).digest("hex");
|
|
667
667
|
}
|
|
668
|
-
async function sha256File(
|
|
668
|
+
async function sha256File(path52) {
|
|
669
669
|
return new Promise((resolve, reject) => {
|
|
670
670
|
const hash = createHash("sha256");
|
|
671
|
-
const stream = createReadStream(
|
|
671
|
+
const stream = createReadStream(path52);
|
|
672
672
|
stream.on("data", (chunk2) => hash.update(chunk2));
|
|
673
673
|
stream.on("error", reject);
|
|
674
674
|
stream.on("end", () => resolve(hash.digest("hex")));
|
|
@@ -4818,7 +4818,7 @@ async function confirm(question, auto) {
|
|
|
4818
4818
|
}
|
|
4819
4819
|
|
|
4820
4820
|
// src/cli/commands/account.ts
|
|
4821
|
-
import
|
|
4821
|
+
import path23 from "path";
|
|
4822
4822
|
import { existsSync as existsSync16 } from "fs";
|
|
4823
4823
|
|
|
4824
4824
|
// src/env.ts
|
|
@@ -6251,9 +6251,9 @@ var PixelLabClient = class {
|
|
|
6251
6251
|
apiKey;
|
|
6252
6252
|
http;
|
|
6253
6253
|
/** Retries transport failures, 429, and 5xx; see http.ts for the policy. */
|
|
6254
|
-
async request(
|
|
6254
|
+
async request(path52, init) {
|
|
6255
6255
|
const auth = this.apiKey.startsWith("Bearer ") ? this.apiKey : `Bearer ${this.apiKey}`;
|
|
6256
|
-
const res = await this.http(`${BASE}${
|
|
6256
|
+
const res = await this.http(`${BASE}${path52}`, {
|
|
6257
6257
|
...init,
|
|
6258
6258
|
headers: {
|
|
6259
6259
|
Authorization: auth,
|
|
@@ -6263,13 +6263,13 @@ var PixelLabClient = class {
|
|
|
6263
6263
|
});
|
|
6264
6264
|
const text = await res.text();
|
|
6265
6265
|
if (!res.ok) {
|
|
6266
|
-
throw new PixelLabError(`${init?.method ?? "GET"} ${
|
|
6266
|
+
throw new PixelLabError(`${init?.method ?? "GET"} ${path52} \u2192 ${res.status}`, res.status, text);
|
|
6267
6267
|
}
|
|
6268
6268
|
if (!text) return {};
|
|
6269
6269
|
try {
|
|
6270
6270
|
return JSON.parse(text);
|
|
6271
6271
|
} catch {
|
|
6272
|
-
throw new Error(`${init?.method ?? "GET"} ${
|
|
6272
|
+
throw new Error(`${init?.method ?? "GET"} ${path52} returned invalid JSON`);
|
|
6273
6273
|
}
|
|
6274
6274
|
}
|
|
6275
6275
|
async balance() {
|
|
@@ -6674,10 +6674,10 @@ var PixelLabClient = class {
|
|
|
6674
6674
|
const palette = args.paletteSwatchBase64 ? { color_image: { type: "base64", base64: args.paletteSwatchBase64, format: "png" }, force_colors: true } : {};
|
|
6675
6675
|
const encode = (image) => ({ type: "base64", base64: image.base64, format: image.format });
|
|
6676
6676
|
const south = args.reference?.south;
|
|
6677
|
-
let
|
|
6677
|
+
let path52;
|
|
6678
6678
|
let body;
|
|
6679
6679
|
if (args.mode === "v3") {
|
|
6680
|
-
|
|
6680
|
+
path52 = "/create-character-v3";
|
|
6681
6681
|
body = {
|
|
6682
6682
|
description: args.description,
|
|
6683
6683
|
image_size: imageSize,
|
|
@@ -6691,7 +6691,7 @@ var PixelLabClient = class {
|
|
|
6691
6691
|
...args.enhancePrompt ? { enhance_prompt: true } : {}
|
|
6692
6692
|
};
|
|
6693
6693
|
} else if (args.mode === "pro-flash") {
|
|
6694
|
-
|
|
6694
|
+
path52 = "/create-character-pro-flash";
|
|
6695
6695
|
const traits = args.styleTraits;
|
|
6696
6696
|
body = {
|
|
6697
6697
|
description: args.description,
|
|
@@ -6715,7 +6715,7 @@ var PixelLabClient = class {
|
|
|
6715
6715
|
} : {}
|
|
6716
6716
|
};
|
|
6717
6717
|
} else if (args.mode === "pro") {
|
|
6718
|
-
|
|
6718
|
+
path52 = "/create-character-pro";
|
|
6719
6719
|
body = {
|
|
6720
6720
|
description: args.description,
|
|
6721
6721
|
image_size: imageSize,
|
|
@@ -6729,7 +6729,7 @@ var PixelLabClient = class {
|
|
|
6729
6729
|
...!south && args.styleCharacterId ? { style_character_id: args.styleCharacterId } : {}
|
|
6730
6730
|
};
|
|
6731
6731
|
} else {
|
|
6732
|
-
|
|
6732
|
+
path52 = args.directions === 4 ? "/create-character-with-4-directions" : "/create-character-with-8-directions";
|
|
6733
6733
|
body = {
|
|
6734
6734
|
description: args.description,
|
|
6735
6735
|
image_size: imageSize,
|
|
@@ -6746,8 +6746,8 @@ var PixelLabClient = class {
|
|
|
6746
6746
|
...palette
|
|
6747
6747
|
};
|
|
6748
6748
|
}
|
|
6749
|
-
const raw = await this.request(
|
|
6750
|
-
return validateResponse(CharacterSubmitSchema, raw,
|
|
6749
|
+
const raw = await this.request(path52, { method: "POST", body: JSON.stringify(body) });
|
|
6750
|
+
return validateResponse(CharacterSubmitSchema, raw, path52.slice(1));
|
|
6751
6751
|
}
|
|
6752
6752
|
/** A text edit of an existing character, applied to every direction and stored as a sibling. */
|
|
6753
6753
|
async createCharacterState(args) {
|
|
@@ -9725,9 +9725,9 @@ var RetroDiffusionClient = class {
|
|
|
9725
9725
|
if (!response.ok) throw new ProviderError("retrodiffusion", `Retro Diffusion download failed (${response.status})`, { status: response.status });
|
|
9726
9726
|
return Buffer.from(await response.arrayBuffer());
|
|
9727
9727
|
}
|
|
9728
|
-
async call(
|
|
9728
|
+
async call(path52, init = {}) {
|
|
9729
9729
|
if (!this.token) throw new Error("RD_API_KEY is not set");
|
|
9730
|
-
const response = await this.request(`${this.baseUrl}${
|
|
9730
|
+
const response = await this.request(`${this.baseUrl}${path52}`, {
|
|
9731
9731
|
...init,
|
|
9732
9732
|
headers: {
|
|
9733
9733
|
"Content-Type": "application/json",
|
|
@@ -10036,8 +10036,13 @@ function retroError(value) {
|
|
|
10036
10036
|
}
|
|
10037
10037
|
|
|
10038
10038
|
// src/providers/scenario.ts
|
|
10039
|
+
import { createHash as createHash3 } from "crypto";
|
|
10040
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync3, renameSync, writeFileSync as writeFileSync2 } from "fs";
|
|
10041
|
+
import os2 from "os";
|
|
10042
|
+
import path13 from "path";
|
|
10039
10043
|
var DEFAULT_BASE_URL3 = "https://api.cloud.scenario.com/v1";
|
|
10040
10044
|
var SOURCE_PROTOCOL2 = "scenario:";
|
|
10045
|
+
var SUBMIT_RETRIES = 8;
|
|
10041
10046
|
var PROTECTED_PARAMETERS = /* @__PURE__ */ new Set([
|
|
10042
10047
|
"dryRun",
|
|
10043
10048
|
"height",
|
|
@@ -10084,7 +10089,11 @@ var ScenarioClient = class {
|
|
|
10084
10089
|
async submit(modelId, body, projectId) {
|
|
10085
10090
|
const response = await this.call(
|
|
10086
10091
|
`/generate/custom/${encodeURIComponent(modelId)}`,
|
|
10087
|
-
|
|
10092
|
+
// A 429 here means this account's concurrent-job limit refused the
|
|
10093
|
+
// request, so nothing was accepted or billed; wait it out instead of
|
|
10094
|
+
// failing the asset. The default four retries give up after about half
|
|
10095
|
+
// a minute, before a running job has finished.
|
|
10096
|
+
{ method: "POST", body: JSON.stringify(body), retries: SUBMIT_RETRIES },
|
|
10088
10097
|
{ projectId }
|
|
10089
10098
|
);
|
|
10090
10099
|
const job = response?.job;
|
|
@@ -10092,6 +10101,29 @@ var ScenarioClient = class {
|
|
|
10092
10101
|
if (!jobId) throw new Error("Scenario did not return an async job id");
|
|
10093
10102
|
return jobId;
|
|
10094
10103
|
}
|
|
10104
|
+
/**
|
|
10105
|
+
* Uploads one image (POST /assets) and returns its asset id, which a model's
|
|
10106
|
+
* reference input takes. Free, and idempotent per image: the id is kept by
|
|
10107
|
+
* content hash so a rerun does not upload the same bytes again.
|
|
10108
|
+
*/
|
|
10109
|
+
async uploadImage(image, projectId) {
|
|
10110
|
+
const bytes = Buffer.from(image.base64, "base64");
|
|
10111
|
+
const digest2 = sha256(bytes);
|
|
10112
|
+
const cached = readUploadCache()[uploadCacheKey(this.baseUrl, projectId, digest2)];
|
|
10113
|
+
if (cached) return cached;
|
|
10114
|
+
const response = await this.call(
|
|
10115
|
+
"/assets",
|
|
10116
|
+
{
|
|
10117
|
+
method: "POST",
|
|
10118
|
+
body: JSON.stringify({ image: image.base64, name: `pixelkiln-${digest2.slice(0, 12)}.${image.format === "png" ? "png" : "jpg"}` })
|
|
10119
|
+
},
|
|
10120
|
+
{ projectId }
|
|
10121
|
+
);
|
|
10122
|
+
const id = response?.asset?.id;
|
|
10123
|
+
if (typeof id !== "string" || !id) throw new Error("Scenario did not return an uploaded asset id");
|
|
10124
|
+
writeUploadCache(uploadCacheKey(this.baseUrl, projectId, digest2), id);
|
|
10125
|
+
return id;
|
|
10126
|
+
}
|
|
10095
10127
|
async job(id, projectId) {
|
|
10096
10128
|
const response = await this.call(`/jobs/${encodeURIComponent(id)}`, {}, { projectId });
|
|
10097
10129
|
const raw = response?.job;
|
|
@@ -10218,6 +10250,14 @@ var ScenarioProvider = class _ScenarioProvider {
|
|
|
10218
10250
|
supports(generator) {
|
|
10219
10251
|
return generator === "map";
|
|
10220
10252
|
}
|
|
10253
|
+
/**
|
|
10254
|
+
* An edit is a generation with the parent as a reference image, which the
|
|
10255
|
+
* editing models (GPT Image, Gemini, FLUX) take. Masked inpainting and the
|
|
10256
|
+
* PixelLab-specific modes have no common Scenario input.
|
|
10257
|
+
*/
|
|
10258
|
+
supportsRevision(mode2) {
|
|
10259
|
+
return mode2 === "image-to-image";
|
|
10260
|
+
}
|
|
10221
10261
|
estimate(spec) {
|
|
10222
10262
|
const options = scenarioOptions(spec);
|
|
10223
10263
|
return {
|
|
@@ -10238,11 +10278,31 @@ var ScenarioProvider = class _ScenarioProvider {
|
|
|
10238
10278
|
if (!Number.isInteger(count) || count < 1 || count > 4) {
|
|
10239
10279
|
throw new Error("Scenario numOutputs must be a whole number from 1 to 4");
|
|
10240
10280
|
}
|
|
10241
|
-
if (spec.width
|
|
10242
|
-
throw new Error("Scenario width and height must be
|
|
10281
|
+
if (!Number.isInteger(spec.width) || spec.width < 16 || spec.width > 4096 || !Number.isInteger(spec.height) || spec.height < 16 || spec.height > 4096) {
|
|
10282
|
+
throw new Error("Scenario width and height must be whole numbers from 16 to 4096");
|
|
10243
10283
|
}
|
|
10244
|
-
|
|
10245
|
-
|
|
10284
|
+
const revision = spec.revision;
|
|
10285
|
+
if (revision) {
|
|
10286
|
+
if (revision.sourceMembers?.length) {
|
|
10287
|
+
throw new Error(
|
|
10288
|
+
`Scenario edits one image; ${revision.sourceAssetId} is a ${revision.sourceMembers.length}-member set`
|
|
10289
|
+
);
|
|
10290
|
+
}
|
|
10291
|
+
if (revision.strength != null) {
|
|
10292
|
+
throw new Error("Scenario image-to-image takes no strength; use the model's own parameters");
|
|
10293
|
+
}
|
|
10294
|
+
}
|
|
10295
|
+
const parameter = referenceParameter(options);
|
|
10296
|
+
if ((styleImages.length || revision) && Object.hasOwn(options.parameters ?? {}, parameter)) {
|
|
10297
|
+
throw new Error(
|
|
10298
|
+
`Scenario parameters.${parameter} is the reference-image input and is filled from styleImages and revisions`
|
|
10299
|
+
);
|
|
10300
|
+
}
|
|
10301
|
+
if (styleImages.length + (revision ? 1 : 0) > 1 && options.referenceArray === false) {
|
|
10302
|
+
throw new Error(`Scenario ${parameter} takes one image; use one style image or a revision, not both`);
|
|
10303
|
+
}
|
|
10304
|
+
if (options.referenceParameter != null && (typeof options.referenceParameter !== "string" || !options.referenceParameter.trim())) {
|
|
10305
|
+
throw new Error("Scenario referenceParameter must be a non-empty string");
|
|
10246
10306
|
}
|
|
10247
10307
|
if (options.projectId != null && (typeof options.projectId !== "string" || !options.projectId.trim())) {
|
|
10248
10308
|
throw new Error("Scenario projectId must be a non-empty string");
|
|
@@ -10255,7 +10315,12 @@ var ScenarioProvider = class _ScenarioProvider {
|
|
|
10255
10315
|
async submit(spec, styleImages) {
|
|
10256
10316
|
this.validate(spec, styleImages);
|
|
10257
10317
|
const options = scenarioOptions(spec);
|
|
10258
|
-
const
|
|
10318
|
+
const references = [];
|
|
10319
|
+
if (spec.revision) references.push(readRevisionSource(spec));
|
|
10320
|
+
for (const image of styleImages) references.push({ base64: image.base64, format: image.format });
|
|
10321
|
+
const referenceAssetIds = [];
|
|
10322
|
+
for (const image of references) referenceAssetIds.push(await this.client.uploadImage(image, options.projectId));
|
|
10323
|
+
const body = requestBody(spec, options, referenceAssetIds);
|
|
10259
10324
|
const quote = await this.client.quote(options.modelId, body, options.projectId);
|
|
10260
10325
|
if (quote.total > options.maxComputeUnits + Number.EPSILON) {
|
|
10261
10326
|
throw new Error(
|
|
@@ -10272,7 +10337,8 @@ var ScenarioProvider = class _ScenarioProvider {
|
|
|
10272
10337
|
quotedComputeUnits: quote.total,
|
|
10273
10338
|
creativeUnitsCost: quote.creativeUnitsCost,
|
|
10274
10339
|
costDetails: quote.costDetails,
|
|
10275
|
-
ipDetectionCost: quote.ipDetectionCost
|
|
10340
|
+
ipDetectionCost: quote.ipDetectionCost,
|
|
10341
|
+
...referenceAssetIds.length ? { referenceAssetIds } : {}
|
|
10276
10342
|
}
|
|
10277
10343
|
};
|
|
10278
10344
|
}
|
|
@@ -10294,7 +10360,13 @@ var ScenarioProvider = class _ScenarioProvider {
|
|
|
10294
10360
|
}
|
|
10295
10361
|
const assetIds = scenarioAssetIds(job);
|
|
10296
10362
|
if (!assetIds.length) return { status: "failed", error: "Scenario job returned no asset IDs" };
|
|
10297
|
-
|
|
10363
|
+
let assets;
|
|
10364
|
+
try {
|
|
10365
|
+
assets = await this.assets(assetIds, reference.projectId);
|
|
10366
|
+
} catch (error) {
|
|
10367
|
+
if (error instanceof UnsupportedScenarioOutput) return { status: "failed", error: error.message };
|
|
10368
|
+
throw error;
|
|
10369
|
+
}
|
|
10298
10370
|
if (assets.length > 1) {
|
|
10299
10371
|
return {
|
|
10300
10372
|
status: "review",
|
|
@@ -10349,6 +10421,32 @@ var ScenarioProvider = class _ScenarioProvider {
|
|
|
10349
10421
|
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));
|
|
10350
10422
|
}
|
|
10351
10423
|
};
|
|
10424
|
+
function uploadCachePath() {
|
|
10425
|
+
return process.env.SCENARIO_UPLOAD_CACHE ?? path13.join(os2.tmpdir(), "pixelkiln-scenario", "uploads.json");
|
|
10426
|
+
}
|
|
10427
|
+
function uploadCacheKey(baseUrl, projectId, digest2) {
|
|
10428
|
+
return createHash3("sha256").update(`${baseUrl}
|
|
10429
|
+
${projectId ?? ""}
|
|
10430
|
+
${digest2}`).digest("hex");
|
|
10431
|
+
}
|
|
10432
|
+
function readUploadCache() {
|
|
10433
|
+
try {
|
|
10434
|
+
const value = JSON.parse(readFileSync3(uploadCachePath(), "utf8"));
|
|
10435
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
10436
|
+
} catch {
|
|
10437
|
+
return {};
|
|
10438
|
+
}
|
|
10439
|
+
}
|
|
10440
|
+
function writeUploadCache(key, assetId) {
|
|
10441
|
+
try {
|
|
10442
|
+
const file = uploadCachePath();
|
|
10443
|
+
mkdirSync2(path13.dirname(file), { recursive: true });
|
|
10444
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
10445
|
+
writeFileSync2(tmp, JSON.stringify({ ...readUploadCache(), [key]: assetId }));
|
|
10446
|
+
renameSync(tmp, file);
|
|
10447
|
+
} catch {
|
|
10448
|
+
}
|
|
10449
|
+
}
|
|
10352
10450
|
function scenarioOptions(spec) {
|
|
10353
10451
|
return spec.providerOptions;
|
|
10354
10452
|
}
|
|
@@ -10364,9 +10462,25 @@ function scenarioBaseUrl(value) {
|
|
|
10364
10462
|
}
|
|
10365
10463
|
return url.href.replace(/\/$/, "");
|
|
10366
10464
|
}
|
|
10367
|
-
function
|
|
10465
|
+
function referenceParameter(options) {
|
|
10466
|
+
return options.referenceParameter?.trim() || "referenceImages";
|
|
10467
|
+
}
|
|
10468
|
+
function readRevisionSource(spec) {
|
|
10469
|
+
const revision = spec.revision;
|
|
10470
|
+
if (!revision.sourceFile || !revision.sourceSha256 || !revision.sourceFormat) {
|
|
10471
|
+
throw new Error(`${spec.styleId}/${spec.assetId}: revision source is not ready`);
|
|
10472
|
+
}
|
|
10473
|
+
const bytes = readFileSync3(revision.sourceFile);
|
|
10474
|
+
if (sha256(bytes) !== revision.sourceSha256) {
|
|
10475
|
+
throw new Error(`${spec.styleId}/${spec.assetId}: revision source changed after the manifest was resolved`);
|
|
10476
|
+
}
|
|
10477
|
+
return { base64: bytes.toString("base64"), format: revision.sourceFormat };
|
|
10478
|
+
}
|
|
10479
|
+
function requestBody(spec, options, referenceAssetIds = []) {
|
|
10480
|
+
const parameter = referenceParameter(options);
|
|
10368
10481
|
return {
|
|
10369
10482
|
...options.parameters ?? {},
|
|
10483
|
+
...referenceAssetIds.length ? { [parameter]: options.referenceArray === false ? referenceAssetIds[0] : referenceAssetIds } : {},
|
|
10370
10484
|
prompt: spec.prompt,
|
|
10371
10485
|
width: spec.width,
|
|
10372
10486
|
height: spec.height,
|
|
@@ -10456,9 +10570,11 @@ function scenarioAssetIds(job) {
|
|
|
10456
10570
|
function assetDownloadUrl(asset) {
|
|
10457
10571
|
return asset.originalFileUrl || asset.url;
|
|
10458
10572
|
}
|
|
10573
|
+
var UnsupportedScenarioOutput = class extends Error {
|
|
10574
|
+
};
|
|
10459
10575
|
function assertPngAsset(asset) {
|
|
10460
10576
|
if (asset.originalMimeType && asset.originalMimeType !== MediaType.PNG) {
|
|
10461
|
-
throw new
|
|
10577
|
+
throw new UnsupportedScenarioOutput(
|
|
10462
10578
|
`Scenario asset ${asset.id} is ${asset.originalMimeType}; PixelKiln's Scenario MVP requires PNG output`
|
|
10463
10579
|
);
|
|
10464
10580
|
}
|
|
@@ -10586,7 +10702,7 @@ registerProvider({
|
|
|
10586
10702
|
// src/manifest.ts
|
|
10587
10703
|
import { readFile as readFile6 } from "fs/promises";
|
|
10588
10704
|
import { existsSync as existsSync8 } from "fs";
|
|
10589
|
-
import
|
|
10705
|
+
import path14 from "path";
|
|
10590
10706
|
|
|
10591
10707
|
// src/loop-directions.ts
|
|
10592
10708
|
var SINGLE_OUTPUT_FIELDS = ["file", "cell", "remoteId", "source", "sourceByStyle", "outputRole"];
|
|
@@ -10675,7 +10791,7 @@ function isObject2(value) {
|
|
|
10675
10791
|
|
|
10676
10792
|
// src/manifest.ts
|
|
10677
10793
|
async function loadManifest(manifestPath) {
|
|
10678
|
-
const abs =
|
|
10794
|
+
const abs = path14.resolve(manifestPath);
|
|
10679
10795
|
if (!existsSync8(abs)) throw new ProjectError(`No manifest at ${abs}`);
|
|
10680
10796
|
const expansion = expandLoopDirections(JSON.parse(await readFile6(abs, "utf8")));
|
|
10681
10797
|
if (expansion.issues.length) {
|
|
@@ -10768,7 +10884,7 @@ ${issues}`);
|
|
|
10768
10884
|
${unknownReferences.map((i) => ` ${i}`).join("\n")}`);
|
|
10769
10885
|
}
|
|
10770
10886
|
const loopFamilies = Object.keys(expansion.families).length ? { loopFamilies: expansion.families } : {};
|
|
10771
|
-
return { manifest: parsed.data, root:
|
|
10887
|
+
return { manifest: parsed.data, root: path14.dirname(abs), path: abs, ...loopFamilies };
|
|
10772
10888
|
}
|
|
10773
10889
|
function parentAssetId(asset) {
|
|
10774
10890
|
return asset?.revision?.from ?? asset?.state?.of ?? asset?.animation?.of ?? asset?.portrait?.of ?? asset?.outfit?.of;
|
|
@@ -10869,7 +10985,7 @@ async function resolveSpecs(loaded, filter) {
|
|
|
10869
10985
|
}
|
|
10870
10986
|
const styleImageCache = /* @__PURE__ */ new Map();
|
|
10871
10987
|
async function loadStyleImage(rel, what = "Style image") {
|
|
10872
|
-
const abs =
|
|
10988
|
+
const abs = path14.resolve(root, rel);
|
|
10873
10989
|
let hit = styleImageCache.get(abs);
|
|
10874
10990
|
if (!hit) {
|
|
10875
10991
|
if (!existsSync8(abs)) throw new Error(`${what} not found: ${abs}`);
|
|
@@ -10899,7 +11015,7 @@ async function resolveSpecs(loaded, filter) {
|
|
|
10899
11015
|
styleImageDimensions.push(loadedImage);
|
|
10900
11016
|
}
|
|
10901
11017
|
const resolvedImages = style.styleImages.map((image) => {
|
|
10902
|
-
const hit = styleImageCache.get(
|
|
11018
|
+
const hit = styleImageCache.get(path14.resolve(root, image.path));
|
|
10903
11019
|
return { base64: hit.base64, width: hit.width, height: hit.height, format: hit.format };
|
|
10904
11020
|
});
|
|
10905
11021
|
const styleSpecs = /* @__PURE__ */ new Map();
|
|
@@ -11000,9 +11116,9 @@ async function resolveSpecs(loaded, filter) {
|
|
|
11000
11116
|
} else {
|
|
11001
11117
|
prompt = [style.promptPrefix, subject, style.promptSuffix].map((p) => p.trim()).filter(Boolean).join(", ");
|
|
11002
11118
|
}
|
|
11003
|
-
const relFile = asset.file ??
|
|
11004
|
-
const outFile =
|
|
11005
|
-
const qualityOutFile = style.quality ?
|
|
11119
|
+
const relFile = asset.file ?? path14.join(asset.category ?? "", `${assetId}.png`);
|
|
11120
|
+
const outFile = path14.resolve(root, style.outDir, relFile);
|
|
11121
|
+
const qualityOutFile = style.quality ? path14.resolve(root, style.quality.outDir, pngPath(relFile)) : void 0;
|
|
11006
11122
|
const tileSize = generator === "tiles" ? size : style.tileSize ?? 32;
|
|
11007
11123
|
const tileVariations = tileFeatureOutputCount(generator === "tiles" ? style.tileFeature : void 0) ?? tileVariationCount(countNumberedDescriptions(prompt));
|
|
11008
11124
|
const terrainTiles = generator === "terrain" ? terrainTileCount(style.terrainTransitionSize) : 0;
|
|
@@ -11097,7 +11213,7 @@ async function resolveSpecs(loaded, filter) {
|
|
|
11097
11213
|
minGridConfidence: style.quality.minGridConfidence,
|
|
11098
11214
|
...style.quality.minTransparency == null ? {} : { minTransparency: style.quality.minTransparency },
|
|
11099
11215
|
...style.quality.fixerRevision ? { fixerRevision: style.quality.fixerRevision } : {},
|
|
11100
|
-
...style.quality.fixerPython ? { fixerPython:
|
|
11216
|
+
...style.quality.fixerPython ? { fixerPython: path14.resolve(root, style.quality.fixerPython) } : {},
|
|
11101
11217
|
...style.quality.fps == null ? {} : { fps: style.quality.fps }
|
|
11102
11218
|
}
|
|
11103
11219
|
} : {},
|
|
@@ -11233,7 +11349,7 @@ async function resolveSpecs(loaded, filter) {
|
|
|
11233
11349
|
animation: sourceSpec.character.animation,
|
|
11234
11350
|
outfit: {
|
|
11235
11351
|
reference: {
|
|
11236
|
-
path:
|
|
11352
|
+
path: path14.resolve(root, asset.outfit.reference),
|
|
11237
11353
|
sha256: referenceImage.hash,
|
|
11238
11354
|
width: referenceImage.width,
|
|
11239
11355
|
height: referenceImage.height,
|
|
@@ -11255,23 +11371,23 @@ async function resolveSpecs(loaded, filter) {
|
|
|
11255
11371
|
);
|
|
11256
11372
|
}
|
|
11257
11373
|
const sourceSpec = await finalize(asset.revision.from);
|
|
11258
|
-
const sourceStem = sourceSpec.quality?.outFile ?? (sourceSpec.source ?
|
|
11374
|
+
const sourceStem = sourceSpec.quality?.outFile ?? (sourceSpec.source ? path14.resolve(root, sourceSpec.source) : sourceSpec.outFile);
|
|
11259
11375
|
const sourceMembers = await revisionSourceMembers(sourceStem, `revision source ${asset.revision.from}`);
|
|
11260
11376
|
const sourceFile = sourceMembers?.[0]?.file ?? sourceStem;
|
|
11261
11377
|
const sourceImage = sourceMembers ? { ...sourceMembers[0], hash: sha256(JSON.stringify(sourceMembers.map((member) => member.sha256))) } : await optionalRevisionImage(sourceFile, "revision source");
|
|
11262
11378
|
const sourceFps = sourceSpec.character?.animation?.fps ?? sourceSpec.objectPro?.animation?.fps ?? sourceSpec.revision?.fps ?? sourceSpec.revision?.sourceFps;
|
|
11263
|
-
const maskFile = asset.revision.mask ?
|
|
11379
|
+
const maskFile = asset.revision.mask ? path14.resolve(root, asset.revision.mask) : void 0;
|
|
11264
11380
|
const maskImage = maskFile ? await optionalRevisionImage(maskFile, "revision mask", "png") : null;
|
|
11265
11381
|
if (sourceImage && maskImage && (sourceImage.width !== maskImage.width || sourceImage.height !== maskImage.height)) {
|
|
11266
11382
|
throw new Error(
|
|
11267
11383
|
`Revision mask for ${styleId}/${assetId} is ${maskImage.width}x${maskImage.height}; source ${asset.revision.from} is ${sourceImage.width}x${sourceImage.height}`
|
|
11268
11384
|
);
|
|
11269
11385
|
}
|
|
11270
|
-
const paletteImageFile = asset.revision.paletteImage ?
|
|
11386
|
+
const paletteImageFile = asset.revision.paletteImage ? path14.resolve(root, asset.revision.paletteImage) : void 0;
|
|
11271
11387
|
const paletteImage = paletteImageFile ? await optionalRevisionImage(paletteImageFile, "revision palette image", "png") : null;
|
|
11272
|
-
const lastFrameFile = asset.revision.lastFrame ?
|
|
11388
|
+
const lastFrameFile = asset.revision.lastFrame ? path14.resolve(root, asset.revision.lastFrame) : void 0;
|
|
11273
11389
|
const lastFrame = lastFrameFile ? await optionalRevisionImage(lastFrameFile, "revision last frame") : null;
|
|
11274
|
-
const keypointsFile = asset.revision.keypointsFile ?
|
|
11390
|
+
const keypointsFile = asset.revision.keypointsFile ? path14.resolve(root, asset.revision.keypointsFile) : void 0;
|
|
11275
11391
|
const keypoints = keypointsFile ? await optionalSkeletonSet(keypointsFile) : null;
|
|
11276
11392
|
resolved.revision = {
|
|
11277
11393
|
mode: asset.revision.mode,
|
|
@@ -11438,7 +11554,7 @@ async function resolveSpecs(loaded, filter) {
|
|
|
11438
11554
|
return specs;
|
|
11439
11555
|
}
|
|
11440
11556
|
function pngPath(file) {
|
|
11441
|
-
const extension =
|
|
11557
|
+
const extension = path14.extname(file);
|
|
11442
11558
|
return extension ? `${file.slice(0, -extension.length)}.png` : `${file}.png`;
|
|
11443
11559
|
}
|
|
11444
11560
|
async function resolveMapScene(root, styleId, assetId, generator, scene, provider) {
|
|
@@ -11448,12 +11564,12 @@ async function resolveMapScene(root, styleId, assetId, generator, scene, provide
|
|
|
11448
11564
|
if (!provider.supportsMapScene) {
|
|
11449
11565
|
throw new Error(`assets.${assetId}.scene: provider "${provider.id}" cannot draw into a scene; PixelLab's /map-objects can`);
|
|
11450
11566
|
}
|
|
11451
|
-
const file =
|
|
11567
|
+
const file = path14.resolve(root, scene.image);
|
|
11452
11568
|
const image = await optionalRevisionImage(file, "scene image", "png");
|
|
11453
11569
|
const base = { file, sha256: image?.hash ?? null, width: image?.width ?? null, height: image?.height ?? null };
|
|
11454
11570
|
const placement = scene.placement;
|
|
11455
11571
|
if ("mask" in placement) {
|
|
11456
|
-
const maskFile =
|
|
11572
|
+
const maskFile = path14.resolve(root, placement.mask);
|
|
11457
11573
|
const mask = await optionalRevisionImage(maskFile, "scene mask", "png");
|
|
11458
11574
|
if (image && mask && (image.width !== mask.width || image.height !== mask.height)) {
|
|
11459
11575
|
throw new Error(
|
|
@@ -11525,7 +11641,7 @@ async function resolveStyleImages(loaded, styleId) {
|
|
|
11525
11641
|
if (!style) throw new Error(`Unknown style "${styleId}"`);
|
|
11526
11642
|
const out = [];
|
|
11527
11643
|
for (const img of style.styleImages) {
|
|
11528
|
-
const file =
|
|
11644
|
+
const file = path14.resolve(loaded.root, img.path);
|
|
11529
11645
|
const buf = await readFile6(file);
|
|
11530
11646
|
const metadata = imageMetadata(buf);
|
|
11531
11647
|
if (!metadata) throw new Error(`Style image is not a readable PNG or JPEG: ${file}`);
|
|
@@ -11553,7 +11669,7 @@ async function resolveCharacterShape(asset, style, kind, files) {
|
|
|
11553
11669
|
};
|
|
11554
11670
|
if (asset.concept) {
|
|
11555
11671
|
const image = await files.load(asset.concept, "Concept image");
|
|
11556
|
-
shape.concept = { path:
|
|
11672
|
+
shape.concept = { path: path14.resolve(files.root, asset.concept), sha256: image.hash, width: image.width, height: image.height, format: image.format };
|
|
11557
11673
|
}
|
|
11558
11674
|
if (asset.reference) {
|
|
11559
11675
|
const byDirection = typeof asset.reference === "string" ? { south: asset.reference } : asset.reference;
|
|
@@ -11561,7 +11677,7 @@ async function resolveCharacterShape(asset, style, kind, files) {
|
|
|
11561
11677
|
for (const [direction, rel] of Object.entries(byDirection)) {
|
|
11562
11678
|
const image = await files.load(rel, `Reference sprite (${direction})`);
|
|
11563
11679
|
shape.reference[direction] = {
|
|
11564
|
-
path:
|
|
11680
|
+
path: path14.resolve(files.root, rel),
|
|
11565
11681
|
sha256: image.hash,
|
|
11566
11682
|
width: image.width,
|
|
11567
11683
|
height: image.height,
|
|
@@ -11582,7 +11698,7 @@ async function resolveCharacterShape(asset, style, kind, files) {
|
|
|
11582
11698
|
const animationMode = asset.animation.mode ?? (asset.animation.template ? "template" : "v3");
|
|
11583
11699
|
const pose = async (rel, what) => {
|
|
11584
11700
|
const image = await files.load(rel, what);
|
|
11585
|
-
return { path:
|
|
11701
|
+
return { path: path14.resolve(files.root, rel), sha256: image.hash, width: image.width, height: image.height, format: image.format };
|
|
11586
11702
|
};
|
|
11587
11703
|
shape.animation = {
|
|
11588
11704
|
mode: animationMode,
|
|
@@ -11613,7 +11729,7 @@ async function resolveObjectProShape(assetId, asset, style, kind, files) {
|
|
|
11613
11729
|
const south = typeof asset.reference === "string" ? asset.reference : asset.reference.south;
|
|
11614
11730
|
if (south) {
|
|
11615
11731
|
const image = await files.load(south, "Reference sprite");
|
|
11616
|
-
shape.reference = { path:
|
|
11732
|
+
shape.reference = { path: path14.resolve(files.root, south), sha256: image.hash, width: image.width, height: image.height, format: image.format };
|
|
11617
11733
|
}
|
|
11618
11734
|
}
|
|
11619
11735
|
if (asset.state) {
|
|
@@ -11641,7 +11757,7 @@ async function resolveObjectProShape(assetId, asset, style, kind, files) {
|
|
|
11641
11757
|
}
|
|
11642
11758
|
const pose = async (rel, what) => {
|
|
11643
11759
|
const image = await files.load(rel, what);
|
|
11644
|
-
return { path:
|
|
11760
|
+
return { path: path14.resolve(files.root, rel), sha256: image.hash, width: image.width, height: image.height, format: image.format };
|
|
11645
11761
|
};
|
|
11646
11762
|
shape.animation = {
|
|
11647
11763
|
mode: asset.animation.mode ?? "v3",
|
|
@@ -11658,9 +11774,9 @@ async function resolveObjectProShape(assetId, asset, style, kind, files) {
|
|
|
11658
11774
|
}
|
|
11659
11775
|
|
|
11660
11776
|
// src/pipeline/fetch.ts
|
|
11661
|
-
import { existsSync as existsSync9, readFileSync as
|
|
11777
|
+
import { existsSync as existsSync9, readFileSync as readFileSync4 } from "fs";
|
|
11662
11778
|
import { mkdir as mkdir4, readFile as readFile7, rename as rename4, rm as rm4, writeFile as writeFile4 } from "fs/promises";
|
|
11663
|
-
import
|
|
11779
|
+
import path15 from "path";
|
|
11664
11780
|
|
|
11665
11781
|
// src/source-url.ts
|
|
11666
11782
|
var SENSITIVE_QUERY_KEYS = /* @__PURE__ */ new Set([
|
|
@@ -11706,7 +11822,7 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
11706
11822
|
const result = { downloaded: 0, skipped: 0, failed: 0, ...opts.refresh ? { unchanged: 0 } : {} };
|
|
11707
11823
|
const specByKey = new Map(specs.map((s) => [lockKey(s.styleId, s.assetId), s]));
|
|
11708
11824
|
normalizeLockOutputPaths(lock, specs);
|
|
11709
|
-
const cacheDir = opts.cacheDir === false ? null :
|
|
11825
|
+
const cacheDir = opts.cacheDir === false ? null : path15.resolve(opts.cacheDir ?? path15.join(path15.dirname(lockPath), ".pixelkiln", "cache"));
|
|
11710
11826
|
const pending = Object.entries(lock.entries).filter(([key, e]) => {
|
|
11711
11827
|
if (e.provider !== provider.id || !specByKey.has(key)) return false;
|
|
11712
11828
|
if (specByKey.get(key)?.mirror) return false;
|
|
@@ -11717,7 +11833,7 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
11717
11833
|
if (spec && e.specHash === spec.specHash && e.outputs.length && !postprocessCurrent(e, spec)) {
|
|
11718
11834
|
return Boolean(opts.force) || e.outputs.every((output) => {
|
|
11719
11835
|
const file = resolveOutputPath(output.path, spec.root);
|
|
11720
|
-
return existsSync9(file) && sha256(
|
|
11836
|
+
return existsSync9(file) && sha256(readFileSync4(file)) === output.sha256;
|
|
11721
11837
|
});
|
|
11722
11838
|
}
|
|
11723
11839
|
if (!opts.repair) return false;
|
|
@@ -11725,7 +11841,7 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
11725
11841
|
return e.outputs.some((output) => {
|
|
11726
11842
|
const file = resolveOutputPath(output.path, spec.root);
|
|
11727
11843
|
if (!existsSync9(file)) return true;
|
|
11728
|
-
const current = sha256(
|
|
11844
|
+
const current = sha256(readFileSync4(file));
|
|
11729
11845
|
if (current === output.sha256) return false;
|
|
11730
11846
|
return Boolean(opts.force) || (e.supersededOutputs ?? []).some((old) => old.sha256 === current);
|
|
11731
11847
|
});
|
|
@@ -11808,7 +11924,7 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
11808
11924
|
if (!source.url) throw new Error(`no durable source URL to refresh ${source.role ?? "asset"} from`);
|
|
11809
11925
|
fresh = await provider.download(source.url);
|
|
11810
11926
|
if (recorded && sha256(fresh) === recordedRaw && !reapply) {
|
|
11811
|
-
log2(` current ${
|
|
11927
|
+
log2(` current ${path15.relative(process.cwd(), target)}`);
|
|
11812
11928
|
outputs.push({ ...recorded, path: portableOutputPath(target, spec.root) });
|
|
11813
11929
|
continue;
|
|
11814
11930
|
}
|
|
@@ -11832,9 +11948,9 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
11832
11948
|
}
|
|
11833
11949
|
if (reapply && !fresh) {
|
|
11834
11950
|
if (!recorded.raw) raw = await readFile7(target);
|
|
11835
|
-
log2(` reapply ${
|
|
11951
|
+
log2(` reapply ${path15.relative(process.cwd(), target)}`);
|
|
11836
11952
|
} else {
|
|
11837
|
-
log2(` changed ${
|
|
11953
|
+
log2(` changed ${path15.relative(process.cwd(), target)} (upstream)`);
|
|
11838
11954
|
}
|
|
11839
11955
|
} else {
|
|
11840
11956
|
const superseded = (entry.supersededOutputs ?? []).find(
|
|
@@ -11862,7 +11978,7 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
11862
11978
|
}
|
|
11863
11979
|
}
|
|
11864
11980
|
log2(
|
|
11865
|
-
` replace ${
|
|
11981
|
+
` replace ${path15.relative(process.cwd(), target)}` + (opts.force ? " (--force)" : " (previous tracked generation)")
|
|
11866
11982
|
);
|
|
11867
11983
|
}
|
|
11868
11984
|
}
|
|
@@ -11871,7 +11987,7 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
11871
11987
|
} else if (!raw && recorded && cacheDir) {
|
|
11872
11988
|
if (!reapply) final = await readCachedMedia(cacheDir, recorded.sha256, expectedMediaType);
|
|
11873
11989
|
if (!final && recordedRaw) raw = await readCachedMedia(cacheDir, recordedRaw, expectedMediaType);
|
|
11874
|
-
if (final || raw) log2(` cached ${
|
|
11990
|
+
if (final || raw) log2(` cached ${path15.relative(process.cwd(), target)}`);
|
|
11875
11991
|
}
|
|
11876
11992
|
if (!final && !raw) {
|
|
11877
11993
|
if (!source.url) {
|
|
@@ -11898,11 +12014,11 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
11898
12014
|
buf = processed.bytes;
|
|
11899
12015
|
if (processed.changed) {
|
|
11900
12016
|
rawHash = sha256(raw);
|
|
11901
|
-
log2(` snapped ${
|
|
12017
|
+
log2(` snapped ${path15.relative(process.cwd(), target)} to ${wanted.palette.colors.length} colours`);
|
|
11902
12018
|
}
|
|
11903
12019
|
}
|
|
11904
12020
|
if (cacheDir) await cacheMedia(cacheDir, buf, mediaType);
|
|
11905
|
-
await mkdir4(
|
|
12021
|
+
await mkdir4(path15.dirname(target), { recursive: true });
|
|
11906
12022
|
const tmp = `${target}.pixelkiln.tmp`;
|
|
11907
12023
|
await writeFile4(tmp, buf);
|
|
11908
12024
|
outputs.push({
|
|
@@ -11916,7 +12032,7 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
11916
12032
|
await saveLock(lockPath, lock);
|
|
11917
12033
|
await rename4(tmp, target);
|
|
11918
12034
|
wrote = true;
|
|
11919
|
-
log2(` wrote ${
|
|
12035
|
+
log2(` wrote ${path15.relative(process.cwd(), target)}`);
|
|
11920
12036
|
}
|
|
11921
12037
|
if (opts.refresh && !wrote) {
|
|
11922
12038
|
if (rotated) {
|
|
@@ -11960,7 +12076,7 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
11960
12076
|
return result;
|
|
11961
12077
|
}
|
|
11962
12078
|
async function readCachedMedia(cacheDir, hash, mediaType) {
|
|
11963
|
-
const file =
|
|
12079
|
+
const file = path15.join(cacheDir, cacheFileName(hash, mediaType));
|
|
11964
12080
|
if (!existsSync9(file)) return null;
|
|
11965
12081
|
try {
|
|
11966
12082
|
const buf = await readFile7(file);
|
|
@@ -11974,7 +12090,7 @@ async function readCachedMedia(cacheDir, hash, mediaType) {
|
|
|
11974
12090
|
async function cacheMedia(cacheDir, buf, mediaType, knownHash) {
|
|
11975
12091
|
const hash = knownHash ?? sha256(buf);
|
|
11976
12092
|
validateMedia(buf, mediaType);
|
|
11977
|
-
const file =
|
|
12093
|
+
const file = path15.join(cacheDir, cacheFileName(hash, mediaType));
|
|
11978
12094
|
if (await readCachedMedia(cacheDir, hash, mediaType)) return;
|
|
11979
12095
|
await mkdir4(cacheDir, { recursive: true });
|
|
11980
12096
|
const tmp = `${file}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
@@ -12017,7 +12133,7 @@ async function pushTags(provider, specs, lock, opts = {}) {
|
|
|
12017
12133
|
// src/pipeline/adopt.ts
|
|
12018
12134
|
import { existsSync as existsSync11 } from "fs";
|
|
12019
12135
|
import { readFile as readFile9, writeFile as writeFile6 } from "fs/promises";
|
|
12020
|
-
import
|
|
12136
|
+
import path16 from "path";
|
|
12021
12137
|
|
|
12022
12138
|
// src/cache.ts
|
|
12023
12139
|
import { mkdir as mkdir5, readFile as readFile8, rename as rename5, rm as rm5, writeFile as writeFile5 } from "fs/promises";
|
|
@@ -12036,10 +12152,10 @@ function isSha256Hash(value) {
|
|
|
12036
12152
|
function parseCache(value) {
|
|
12037
12153
|
return HashCacheSchema.parse(value);
|
|
12038
12154
|
}
|
|
12039
|
-
async function loadCache(
|
|
12040
|
-
if (!existsSync10(
|
|
12155
|
+
async function loadCache(path52) {
|
|
12156
|
+
if (!existsSync10(path52)) return { version: 1, hashes: {} };
|
|
12041
12157
|
try {
|
|
12042
|
-
const parsed = HashCacheSchema.safeParse(JSON.parse(await readFile8(
|
|
12158
|
+
const parsed = HashCacheSchema.safeParse(JSON.parse(await readFile8(path52, "utf8")));
|
|
12043
12159
|
if (!parsed.success) return { version: 1, hashes: {} };
|
|
12044
12160
|
return {
|
|
12045
12161
|
version: 1,
|
|
@@ -12051,18 +12167,18 @@ async function loadCache(path51) {
|
|
|
12051
12167
|
return { version: 1, hashes: {} };
|
|
12052
12168
|
}
|
|
12053
12169
|
}
|
|
12054
|
-
async function saveCache(
|
|
12170
|
+
async function saveCache(path52, cache) {
|
|
12055
12171
|
const sorted = {};
|
|
12056
12172
|
for (const key of Object.keys(cache.hashes).sort()) {
|
|
12057
12173
|
const hash = cache.hashes[key];
|
|
12058
12174
|
if (!isSha256Hash(hash)) throw new Error(`Refusing to cache invalid SHA-256 for ${key}`);
|
|
12059
12175
|
sorted[key] = hash;
|
|
12060
12176
|
}
|
|
12061
|
-
await mkdir5(pathModule.dirname(pathModule.resolve(
|
|
12062
|
-
const tmp = `${
|
|
12177
|
+
await mkdir5(pathModule.dirname(pathModule.resolve(path52)), { recursive: true });
|
|
12178
|
+
const tmp = `${path52}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
12063
12179
|
try {
|
|
12064
12180
|
await writeFile5(tmp, JSON.stringify({ version: 1, hashes: sorted }, null, 2) + "\n");
|
|
12065
|
-
await rename5(tmp,
|
|
12181
|
+
await rename5(tmp, path52);
|
|
12066
12182
|
} finally {
|
|
12067
12183
|
await rm5(tmp, { force: true });
|
|
12068
12184
|
}
|
|
@@ -12255,7 +12371,7 @@ function formatUnmatchedRemote(objects, limit = 15) {
|
|
|
12255
12371
|
// src/pipeline/adopt-characters.ts
|
|
12256
12372
|
import { existsSync as existsSync12 } from "fs";
|
|
12257
12373
|
import { mkdir as mkdir6, readFile as readFile10, writeFile as writeFile7 } from "fs/promises";
|
|
12258
|
-
import
|
|
12374
|
+
import path17 from "path";
|
|
12259
12375
|
async function adoptCharacters(provider, specs, lock, lockPath, opts) {
|
|
12260
12376
|
const log2 = opts.onProgress ?? (() => {
|
|
12261
12377
|
});
|
|
@@ -12307,11 +12423,11 @@ async function adoptCharacters(provider, specs, lock, lockPath, opts) {
|
|
|
12307
12423
|
const hash = sha256(bytes);
|
|
12308
12424
|
const file = expectedOutputPath(spec, source.role, index, sources.length);
|
|
12309
12425
|
if (existsSync12(file)) {
|
|
12310
|
-
if (sha256(await readFile10(file)) !== hash) result.differing.push(
|
|
12426
|
+
if (sha256(await readFile10(file)) !== hash) result.differing.push(path17.relative(process.cwd(), file));
|
|
12311
12427
|
} else {
|
|
12312
|
-
await mkdir6(
|
|
12428
|
+
await mkdir6(path17.dirname(file), { recursive: true });
|
|
12313
12429
|
await writeFile7(file, bytes);
|
|
12314
|
-
result.written.push(
|
|
12430
|
+
result.written.push(path17.relative(process.cwd(), file));
|
|
12315
12431
|
}
|
|
12316
12432
|
outputs.push({ path: portableOutputPath(file, spec.root), sha256: hash, ...source.role ? { role: source.role } : {} });
|
|
12317
12433
|
}
|
|
@@ -12350,7 +12466,7 @@ async function adoptCharacters(provider, specs, lock, lockPath, opts) {
|
|
|
12350
12466
|
} else {
|
|
12351
12467
|
const south = expectedOutputPath(spec, "south", 0, spec.character.directions);
|
|
12352
12468
|
if (!existsSync12(south)) {
|
|
12353
|
-
result.unmatched.push(`${key}: no remoteId and no ${
|
|
12469
|
+
result.unmatched.push(`${key}: no remoteId and no ${path17.relative(process.cwd(), south)} to match by`);
|
|
12354
12470
|
return false;
|
|
12355
12471
|
}
|
|
12356
12472
|
const localHash = sha256(await readFile10(south));
|
|
@@ -12362,7 +12478,7 @@ async function adoptCharacters(provider, specs, lock, lockPath, opts) {
|
|
|
12362
12478
|
}
|
|
12363
12479
|
}
|
|
12364
12480
|
if (!match) {
|
|
12365
|
-
result.unmatched.push(`${key}: no character's south rotation has the bytes of ${
|
|
12481
|
+
result.unmatched.push(`${key}: no character's south rotation has the bytes of ${path17.relative(process.cwd(), south)}`);
|
|
12366
12482
|
return false;
|
|
12367
12483
|
}
|
|
12368
12484
|
}
|
|
@@ -12432,7 +12548,7 @@ async function adoptCharacters(provider, specs, lock, lockPath, opts) {
|
|
|
12432
12548
|
if (!existsSync12(first)) {
|
|
12433
12549
|
const byName = candidates.find((a) => a.name === characterAnimationName(spec));
|
|
12434
12550
|
if (!byName) {
|
|
12435
|
-
result.unmatched.push(`${key}: no remoteId and no ${
|
|
12551
|
+
result.unmatched.push(`${key}: no remoteId and no ${path17.relative(process.cwd(), first)} to match by`);
|
|
12436
12552
|
return false;
|
|
12437
12553
|
}
|
|
12438
12554
|
match = byName;
|
|
@@ -12445,7 +12561,7 @@ async function adoptCharacters(provider, specs, lock, lockPath, opts) {
|
|
|
12445
12561
|
}
|
|
12446
12562
|
}
|
|
12447
12563
|
if (!match) {
|
|
12448
|
-
result.unmatched.push(`${key}: no ${animation.direction} animation of character ${parentId} starts with the bytes of ${
|
|
12564
|
+
result.unmatched.push(`${key}: no ${animation.direction} animation of character ${parentId} starts with the bytes of ${path17.relative(process.cwd(), first)}`);
|
|
12449
12565
|
return false;
|
|
12450
12566
|
}
|
|
12451
12567
|
}
|
|
@@ -12507,7 +12623,7 @@ async function adoptCharacters(provider, specs, lock, lockPath, opts) {
|
|
|
12507
12623
|
// src/pipeline/salvage.ts
|
|
12508
12624
|
import { readFile as readFile11 } from "fs/promises";
|
|
12509
12625
|
import { existsSync as existsSync13 } from "fs";
|
|
12510
|
-
import
|
|
12626
|
+
import path18 from "path";
|
|
12511
12627
|
function providerClaimId(provider, objectId) {
|
|
12512
12628
|
return `${encodeURIComponent(provider)}:${encodeURIComponent(objectId)}`;
|
|
12513
12629
|
}
|
|
@@ -12684,20 +12800,20 @@ function matchStyleByPattern(prompt, manifest) {
|
|
|
12684
12800
|
return null;
|
|
12685
12801
|
}
|
|
12686
12802
|
async function loadSiblingManifests(ownManifestPath, workspaceManifestPaths, claimPaths) {
|
|
12687
|
-
const own =
|
|
12803
|
+
const own = path18.resolve(ownManifestPath);
|
|
12688
12804
|
const siblingManifestPaths = [
|
|
12689
12805
|
.../* @__PURE__ */ new Set([
|
|
12690
12806
|
...workspaceManifestPaths,
|
|
12691
|
-
...claimPaths.map((c) =>
|
|
12807
|
+
...claimPaths.map((c) => path18.join(path18.dirname(path18.resolve(c)), "pixelkiln.manifest.json"))
|
|
12692
12808
|
])
|
|
12693
12809
|
];
|
|
12694
12810
|
const siblings = [];
|
|
12695
12811
|
for (const siblingManifestPath of siblingManifestPaths) {
|
|
12696
|
-
if (
|
|
12812
|
+
if (path18.resolve(siblingManifestPath) === own) continue;
|
|
12697
12813
|
if (!existsSync13(siblingManifestPath)) continue;
|
|
12698
12814
|
try {
|
|
12699
12815
|
const { manifest } = await loadManifest(siblingManifestPath);
|
|
12700
|
-
siblings.push({ label:
|
|
12816
|
+
siblings.push({ label: path18.basename(path18.dirname(siblingManifestPath)), manifest });
|
|
12701
12817
|
} catch {
|
|
12702
12818
|
}
|
|
12703
12819
|
}
|
|
@@ -12819,7 +12935,7 @@ async function applyTags(provider, decisions, existing, opts = {}) {
|
|
|
12819
12935
|
// src/workspace.ts
|
|
12820
12936
|
import { mkdir as mkdir7, readFile as readFile12, rename as rename6, rm as rm6, writeFile as writeFile8 } from "fs/promises";
|
|
12821
12937
|
import { existsSync as existsSync14 } from "fs";
|
|
12822
|
-
import
|
|
12938
|
+
import path19 from "path";
|
|
12823
12939
|
import { z as z5 } from "zod";
|
|
12824
12940
|
var WorkspaceProjectSchema = z5.object({
|
|
12825
12941
|
id: z5.string().min(1),
|
|
@@ -12861,7 +12977,7 @@ async function saveWorkspace(workspacePath, ws) {
|
|
|
12861
12977
|
version: 1,
|
|
12862
12978
|
projects: [...ws.projects].sort((a, b) => a.id.localeCompare(b.id))
|
|
12863
12979
|
};
|
|
12864
|
-
await mkdir7(
|
|
12980
|
+
await mkdir7(path19.dirname(path19.resolve(workspacePath)), { recursive: true });
|
|
12865
12981
|
const tmp = `${workspacePath}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
12866
12982
|
try {
|
|
12867
12983
|
await writeFile8(tmp, JSON.stringify(sorted, null, 2) + "\n");
|
|
@@ -12871,12 +12987,12 @@ async function saveWorkspace(workspacePath, ws) {
|
|
|
12871
12987
|
}
|
|
12872
12988
|
}
|
|
12873
12989
|
function toPortablePath(dir, absolute) {
|
|
12874
|
-
return
|
|
12990
|
+
return path19.relative(dir, absolute).split(path19.sep).join("/");
|
|
12875
12991
|
}
|
|
12876
12992
|
function resolveProject(dir, project) {
|
|
12877
12993
|
return {
|
|
12878
|
-
manifestPath:
|
|
12879
|
-
lockPath:
|
|
12994
|
+
manifestPath: path19.resolve(dir, project.manifest.split("/").join(path19.sep)),
|
|
12995
|
+
lockPath: path19.resolve(dir, project.lock.split("/").join(path19.sep))
|
|
12880
12996
|
};
|
|
12881
12997
|
}
|
|
12882
12998
|
function validateWorkspace(ws, dir) {
|
|
@@ -12889,7 +13005,7 @@ function validateWorkspace(ws, dir) {
|
|
|
12889
13005
|
const { manifestPath, lockPath } = resolveProject(dir, project);
|
|
12890
13006
|
lockOwners.set(lockPath, [...lockOwners.get(lockPath) ?? [], project.id]);
|
|
12891
13007
|
manifestOwners.set(manifestPath, [...manifestOwners.get(manifestPath) ?? [], project.id]);
|
|
12892
|
-
if (
|
|
13008
|
+
if (path19.isAbsolute(project.manifest) || path19.isAbsolute(project.lock)) {
|
|
12893
13009
|
diagnostics.push({
|
|
12894
13010
|
id: "absolute-path",
|
|
12895
13011
|
level: "warning",
|
|
@@ -12951,7 +13067,7 @@ function validateWorkspace(ws, dir) {
|
|
|
12951
13067
|
|
|
12952
13068
|
// src/pick/salvage-server.ts
|
|
12953
13069
|
import { mkdir as mkdir8, writeFile as writeFile9, readFile as readFile14 } from "fs/promises";
|
|
12954
|
-
import
|
|
13070
|
+
import path20 from "path";
|
|
12955
13071
|
|
|
12956
13072
|
// src/pick/salvage-sheet.ts
|
|
12957
13073
|
var escapeHtml = (s) => s.replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
@@ -13216,7 +13332,7 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
13216
13332
|
});
|
|
13217
13333
|
const html = renderSalvageSheet(orphans, {
|
|
13218
13334
|
styleId: ctx.styleId,
|
|
13219
|
-
importDir:
|
|
13335
|
+
importDir: path20.relative(process.cwd(), ctx.importDir) || "."
|
|
13220
13336
|
});
|
|
13221
13337
|
const byId = new Map(orphans.map((o) => [o.id, o]));
|
|
13222
13338
|
const existingTags = new Map(orphans.map((o) => [o.id, o.tags]));
|
|
@@ -13272,9 +13388,9 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
13272
13388
|
if (!buf.subarray(0, 8).equals(PNG_SIGNATURE2)) throw new Error("not a PNG");
|
|
13273
13389
|
decodePng(buf);
|
|
13274
13390
|
const assetId = idFromPrompt(orphan.prompt, taken);
|
|
13275
|
-
const rel =
|
|
13276
|
-
const outFile =
|
|
13277
|
-
await mkdir8(
|
|
13391
|
+
const rel = path20.join("_salvaged", `${assetId}.png`);
|
|
13392
|
+
const outFile = path20.resolve(ctx.importDir, rel);
|
|
13393
|
+
await mkdir8(path20.dirname(outFile), { recursive: true });
|
|
13278
13394
|
await writeFile9(outFile, buf);
|
|
13279
13395
|
ctx.manifest.assets[assetId] = {
|
|
13280
13396
|
prompt: orphan.prompt,
|
|
@@ -13307,7 +13423,7 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
13307
13423
|
sourceUrl: durableSource,
|
|
13308
13424
|
sourceUrls: durableSource ? [{ url: durableSource }] : [],
|
|
13309
13425
|
outputs: [{
|
|
13310
|
-
path: portableOutputPath(outFile,
|
|
13426
|
+
path: portableOutputPath(outFile, path20.dirname(ctx.manifestPath)),
|
|
13311
13427
|
sha256: sha256(buf)
|
|
13312
13428
|
}],
|
|
13313
13429
|
submittedAt: orphan.createdAt,
|
|
@@ -13342,19 +13458,19 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
13342
13458
|
}
|
|
13343
13459
|
|
|
13344
13460
|
// src/cli/project.ts
|
|
13345
|
-
import
|
|
13461
|
+
import path22 from "path";
|
|
13346
13462
|
import { existsSync as existsSync15 } from "fs";
|
|
13347
13463
|
|
|
13348
13464
|
// src/project.ts
|
|
13349
|
-
import
|
|
13465
|
+
import path21 from "path";
|
|
13350
13466
|
async function openProject(manifestPath = "pixelkiln.manifest.json", opts = {}) {
|
|
13351
|
-
const absolute =
|
|
13352
|
-
const root =
|
|
13467
|
+
const absolute = path21.resolve(manifestPath);
|
|
13468
|
+
const root = path21.dirname(absolute);
|
|
13353
13469
|
if (opts.env !== false) {
|
|
13354
13470
|
loadEnvFiles(root);
|
|
13355
|
-
if (
|
|
13471
|
+
if (path21.resolve(process.cwd()) !== root) loadEnvFiles(process.cwd());
|
|
13356
13472
|
}
|
|
13357
|
-
const lockPath =
|
|
13473
|
+
const lockPath = path21.resolve(opts.lockPath ?? path21.join(root, "pixelkiln.lock.json"));
|
|
13358
13474
|
const loaded = await loadManifest(absolute);
|
|
13359
13475
|
const specs = await resolveSpecs(loaded, { styles: opts.styles ?? [], assets: opts.assets ?? [] });
|
|
13360
13476
|
const lock = await loadLock(lockPath);
|
|
@@ -13485,7 +13601,7 @@ async function workspaceStatus(ws, dir) {
|
|
|
13485
13601
|
|
|
13486
13602
|
// src/cli/project.ts
|
|
13487
13603
|
async function provenanceFile(id, file) {
|
|
13488
|
-
const absolute =
|
|
13604
|
+
const absolute = path22.resolve(file);
|
|
13489
13605
|
return {
|
|
13490
13606
|
id,
|
|
13491
13607
|
path: absolute,
|
|
@@ -13564,7 +13680,7 @@ async function requireCompleteWorkspaceClaims(workspacePath) {
|
|
|
13564
13680
|
if (!existsSync15(workspacePath)) {
|
|
13565
13681
|
throw new ProjectError(`Workspace catalog not found: ${workspacePath}`);
|
|
13566
13682
|
}
|
|
13567
|
-
const dir =
|
|
13683
|
+
const dir = path22.dirname(path22.resolve(workspacePath));
|
|
13568
13684
|
const ws = await loadWorkspace(workspacePath);
|
|
13569
13685
|
const diagnostics = validateWorkspace(ws, dir);
|
|
13570
13686
|
const errors = diagnostics.filter((d) => d.level === "error");
|
|
@@ -13587,9 +13703,9 @@ async function openAccountProject(args) {
|
|
|
13587
13703
|
return project;
|
|
13588
13704
|
}
|
|
13589
13705
|
async function open(args) {
|
|
13590
|
-
if (!existsSync15(
|
|
13706
|
+
if (!existsSync15(path22.resolve(args.manifest))) {
|
|
13591
13707
|
throw new ProjectError(
|
|
13592
|
-
`No manifest at ${
|
|
13708
|
+
`No manifest at ${path22.resolve(args.manifest)}. Pass --manifest, or run \`pixelkiln init --from <dir>\`.`
|
|
13593
13709
|
);
|
|
13594
13710
|
}
|
|
13595
13711
|
const project = await openProject(args.manifest, { lockPath: args.lock, styles: args.styles, assets: args.assets });
|
|
@@ -13631,7 +13747,7 @@ function providerCache(mode2) {
|
|
|
13631
13747
|
|
|
13632
13748
|
// src/cli/commands/account.ts
|
|
13633
13749
|
async function runBalance(args) {
|
|
13634
|
-
loadEnvFiles(
|
|
13750
|
+
loadEnvFiles(path23.dirname(path23.resolve(args.manifest)));
|
|
13635
13751
|
loadEnvFiles(process.cwd());
|
|
13636
13752
|
const loaded = await loadManifest(args.manifest);
|
|
13637
13753
|
const selectedProvider = accountProviderId(loaded.manifest, args.provider, "balance");
|
|
@@ -13704,12 +13820,12 @@ async function runAdopt(args) {
|
|
|
13704
13820
|
tagged ${n} object(s) upstream`);
|
|
13705
13821
|
}
|
|
13706
13822
|
if (args.writePrompts) {
|
|
13707
|
-
const { filled, stillEmpty } = await writePromptsBack(
|
|
13823
|
+
const { filled, stillEmpty } = await writePromptsBack(path23.resolve(args.manifest), lock, {
|
|
13708
13824
|
onProgress: log,
|
|
13709
13825
|
provider: provider.id,
|
|
13710
13826
|
assetIds: specs.map((spec) => spec.assetId)
|
|
13711
13827
|
});
|
|
13712
|
-
log(` recovered ${filled} prompt(s) into ${
|
|
13828
|
+
log(` recovered ${filled} prompt(s) into ${path23.relative(process.cwd(), args.manifest)}`);
|
|
13713
13829
|
const reloaded = await loadManifest(args.manifest);
|
|
13714
13830
|
const rebased = (await resolveSpecs(reloaded, {
|
|
13715
13831
|
styles: args.styles,
|
|
@@ -13739,12 +13855,12 @@ async function runSalvage2(args) {
|
|
|
13739
13855
|
const provider = providerFor(accountProvider);
|
|
13740
13856
|
const jsonMode = args.dryRun && args.json;
|
|
13741
13857
|
const diag = jsonMode ? (msg = "") => console.error(msg) : log;
|
|
13742
|
-
const ownLock =
|
|
13858
|
+
const ownLock = path23.resolve(args.lock);
|
|
13743
13859
|
let workspaceProjects = [];
|
|
13744
13860
|
let workspaceDir = "";
|
|
13745
13861
|
if (args.workspace) {
|
|
13746
|
-
const workspacePath =
|
|
13747
|
-
workspaceDir =
|
|
13862
|
+
const workspacePath = path23.resolve(args.workspace);
|
|
13863
|
+
workspaceDir = path23.dirname(workspacePath);
|
|
13748
13864
|
const complete = await requireCompleteWorkspaceClaims(workspacePath);
|
|
13749
13865
|
workspaceProjects = complete.ws.projects;
|
|
13750
13866
|
for (const d of complete.diagnostics) diag(` WARN ${d.id}: ${d.message}`);
|
|
@@ -13754,11 +13870,11 @@ async function runSalvage2(args) {
|
|
|
13754
13870
|
.../* @__PURE__ */ new Set([
|
|
13755
13871
|
...workspaceLockPaths,
|
|
13756
13872
|
...existsSync16(ownLock) ? [ownLock] : [],
|
|
13757
|
-
...args.claims.map((c) =>
|
|
13873
|
+
...args.claims.map((c) => path23.resolve(c))
|
|
13758
13874
|
])
|
|
13759
13875
|
];
|
|
13760
13876
|
diag(` claim set (${lockPaths.length} lockfile(s)):`);
|
|
13761
|
-
for (const p of lockPaths) diag(` ${
|
|
13877
|
+
for (const p of lockPaths) diag(` ${path23.relative(process.cwd(), p)}`);
|
|
13762
13878
|
if (!args.claims.length && !args.workspace) {
|
|
13763
13879
|
diag(
|
|
13764
13880
|
`
|
|
@@ -13766,7 +13882,7 @@ async function runSalvage2(args) {
|
|
|
13766
13882
|
pass every other project's lockfile via --claims a.json,b.json, or
|
|
13767
13883
|
register every project in a workspace catalog and pass --workspace.`
|
|
13768
13884
|
);
|
|
13769
|
-
} else if (args.workspace && !workspaceProjects.some((p) => resolveProject(workspaceDir, p).manifestPath ===
|
|
13885
|
+
} else if (args.workspace && !workspaceProjects.some((p) => resolveProject(workspaceDir, p).manifestPath === path23.resolve(args.manifest))) {
|
|
13770
13886
|
diag(
|
|
13771
13887
|
`
|
|
13772
13888
|
This project's manifest is not registered in the workspace catalog. Its own
|
|
@@ -13885,7 +14001,7 @@ async function runSalvage2(args) {
|
|
|
13885
14001
|
manifestPath: loaded.path,
|
|
13886
14002
|
manifest: accountManifest,
|
|
13887
14003
|
styleId,
|
|
13888
|
-
importDir:
|
|
14004
|
+
importDir: path23.resolve(loaded.root, style.outDir),
|
|
13889
14005
|
lock,
|
|
13890
14006
|
lockPath: args.lock
|
|
13891
14007
|
},
|
|
@@ -14012,20 +14128,20 @@ async function runTag(args) {
|
|
|
14012
14128
|
}
|
|
14013
14129
|
|
|
14014
14130
|
// src/cli/commands/edit.ts
|
|
14015
|
-
import
|
|
14131
|
+
import path26 from "path";
|
|
14016
14132
|
|
|
14017
14133
|
// src/pipeline/hand-edit.ts
|
|
14018
14134
|
import { existsSync as existsSync17 } from "fs";
|
|
14019
14135
|
import { copyFile, mkdir as mkdir9, readFile as readFile16, rename as rename8, rm as rm8, writeFile as writeFile11 } from "fs/promises";
|
|
14020
|
-
import
|
|
14136
|
+
import path25 from "path";
|
|
14021
14137
|
import { z as z7 } from "zod";
|
|
14022
14138
|
|
|
14023
14139
|
// src/manifest-edit.ts
|
|
14024
14140
|
import { readFile as readFile15, rename as rename7, rm as rm7, writeFile as writeFile10 } from "fs/promises";
|
|
14025
|
-
import
|
|
14141
|
+
import path24 from "path";
|
|
14026
14142
|
import { z as z6 } from "zod";
|
|
14027
14143
|
var HexSha = z6.string().regex(/^[0-9a-f]{64}$/);
|
|
14028
|
-
var ProjectFileSchema = z6.string().min(1).refine((file) => !
|
|
14144
|
+
var ProjectFileSchema = z6.string().min(1).refine((file) => !path24.isAbsolute(file) && !file.split(/[\\/]/).includes(".."), "must be a path inside the project");
|
|
14029
14145
|
var AssetPatchSchema = z6.object({
|
|
14030
14146
|
prompt: z6.string().optional(),
|
|
14031
14147
|
/** Per-style override for the style being viewed; null removes it. */
|
|
@@ -14444,7 +14560,7 @@ function applyEdit(raw, edit) {
|
|
|
14444
14560
|
}
|
|
14445
14561
|
}
|
|
14446
14562
|
async function withCandidate(manifestPath, edit, use, unchanged) {
|
|
14447
|
-
const absolute =
|
|
14563
|
+
const absolute = path24.resolve(manifestPath);
|
|
14448
14564
|
const original = await readFile15(absolute, "utf8");
|
|
14449
14565
|
if (sha256(original) !== edit.expectedSha256) throw new ManifestDriftError(absolute);
|
|
14450
14566
|
let raw;
|
|
@@ -14458,9 +14574,9 @@ async function withCandidate(manifestPath, edit, use, unchanged) {
|
|
|
14458
14574
|
applyEdit(raw, edit);
|
|
14459
14575
|
const next = serializeLike(original, raw);
|
|
14460
14576
|
if (next === original) return unchanged(absolute, original);
|
|
14461
|
-
const tmp =
|
|
14462
|
-
|
|
14463
|
-
`.${
|
|
14577
|
+
const tmp = path24.join(
|
|
14578
|
+
path24.dirname(absolute),
|
|
14579
|
+
`.${path24.basename(absolute)}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`
|
|
14464
14580
|
);
|
|
14465
14581
|
try {
|
|
14466
14582
|
await writeFile10(tmp, next);
|
|
@@ -14533,11 +14649,11 @@ async function priceManifestEdit(manifestPath, edit, opts = {}) {
|
|
|
14533
14649
|
var HAND_EDIT_DIR = "edits";
|
|
14534
14650
|
function handEditPath(loaded, spec) {
|
|
14535
14651
|
const style = loaded.manifest.styles[spec.styleId];
|
|
14536
|
-
const outDir =
|
|
14537
|
-
const relative =
|
|
14538
|
-
const inside = relative && !relative.startsWith("..") && !
|
|
14539
|
-
const file = inside ? relative :
|
|
14540
|
-
return
|
|
14652
|
+
const outDir = path25.resolve(loaded.root, style?.outDir ?? ".");
|
|
14653
|
+
const relative = path25.relative(outDir, spec.outFile);
|
|
14654
|
+
const inside = relative && !relative.startsWith("..") && !path25.isAbsolute(relative);
|
|
14655
|
+
const file = inside ? relative : path25.basename(spec.outFile);
|
|
14656
|
+
return path25.join(outDir, HAND_EDIT_DIR, file.replace(/\.gif$/i, ".png"));
|
|
14541
14657
|
}
|
|
14542
14658
|
function handEditBases(spec, lock) {
|
|
14543
14659
|
const entry = lock.entries[lockKey(spec.styleId, spec.assetId)];
|
|
@@ -14579,7 +14695,7 @@ async function startHandEdit(loaded, lock, spec, opts = {}) {
|
|
|
14579
14695
|
`${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`
|
|
14580
14696
|
);
|
|
14581
14697
|
}
|
|
14582
|
-
const existing = spec.source ?
|
|
14698
|
+
const existing = spec.source ? path25.resolve(loaded.root, spec.source) : null;
|
|
14583
14699
|
const editPath = existing ?? handEditPath(loaded, spec);
|
|
14584
14700
|
const members = handEditMembers(loaded, spec, lock, editPath);
|
|
14585
14701
|
let created = false;
|
|
@@ -14590,7 +14706,7 @@ async function startHandEdit(loaded, lock, spec, opts = {}) {
|
|
|
14590
14706
|
`${spec.styleId}/${spec.assetId} has no generated PNG on disk to start from; generate or restore it first`
|
|
14591
14707
|
);
|
|
14592
14708
|
}
|
|
14593
|
-
await mkdir9(
|
|
14709
|
+
await mkdir9(path25.dirname(member.path), { recursive: true });
|
|
14594
14710
|
await copyFile(member.base, member.path);
|
|
14595
14711
|
created = true;
|
|
14596
14712
|
}
|
|
@@ -14728,7 +14844,7 @@ async function saveHandEdit(loaded, lock, spec, input) {
|
|
|
14728
14844
|
editor: input.editor,
|
|
14729
14845
|
protocol: input.protocol,
|
|
14730
14846
|
savedAt: (input.now ?? /* @__PURE__ */ new Date()).toISOString(),
|
|
14731
|
-
project: projectPath ?
|
|
14847
|
+
project: projectPath ? path25.basename(projectPath) : null,
|
|
14732
14848
|
outputs: incoming.map((frame, index) => ({
|
|
14733
14849
|
role: frame.role,
|
|
14734
14850
|
basedOn: basedOnHash(entry, index),
|
|
@@ -14788,7 +14904,7 @@ async function runEdit(args) {
|
|
|
14788
14904
|
const set = started.members.length > 1;
|
|
14789
14905
|
const kind = spec.generator === "frames" ? "frames" : "members";
|
|
14790
14906
|
log(` ${started.created ? "created" : "found"} ${started.source}${set ? ` (${started.members.length} ${kind})` : ""}${started.declared ? " and declared it as the asset's source" : ""}`);
|
|
14791
|
-
if (set) for (const member of started.members) log(` ${member.role ?? ""} ${
|
|
14907
|
+
if (set) for (const member of started.members) log(` ${member.role ?? ""} ${path26.relative(process.cwd(), member.path)}`);
|
|
14792
14908
|
if (args.noOpen) {
|
|
14793
14909
|
log(` edit ${set ? "them" : "it"} with your own tool, then run pixelkiln plan; mount and pack place ${set ? "them" : "it"} in place of the generated art`);
|
|
14794
14910
|
} else {
|
|
@@ -14798,24 +14914,24 @@ async function runEdit(args) {
|
|
|
14798
14914
|
}
|
|
14799
14915
|
|
|
14800
14916
|
// src/cli/commands/gallery.ts
|
|
14801
|
-
import
|
|
14917
|
+
import path36 from "path";
|
|
14802
14918
|
import { existsSync as existsSync27 } from "fs";
|
|
14803
14919
|
|
|
14804
14920
|
// src/gallery/snapshot.ts
|
|
14805
|
-
import { createHash as
|
|
14921
|
+
import { createHash as createHash4 } from "crypto";
|
|
14806
14922
|
import { existsSync as existsSync22 } from "fs";
|
|
14807
14923
|
import { readFile as readFile20, stat as stat2 } from "fs/promises";
|
|
14808
|
-
import
|
|
14924
|
+
import path32 from "path";
|
|
14809
14925
|
|
|
14810
14926
|
// src/gallery/edit.ts
|
|
14811
14927
|
import { readFile as readFile19, rename as rename9, writeFile as writeFile15 } from "fs/promises";
|
|
14812
|
-
import
|
|
14928
|
+
import path30 from "path";
|
|
14813
14929
|
import { z as z11 } from "zod";
|
|
14814
14930
|
|
|
14815
14931
|
// src/gallery/upload.ts
|
|
14816
14932
|
import { existsSync as existsSync18 } from "fs";
|
|
14817
14933
|
import { mkdir as mkdir10, writeFile as writeFile12 } from "fs/promises";
|
|
14818
|
-
import
|
|
14934
|
+
import path27 from "path";
|
|
14819
14935
|
import { z as z8 } from "zod";
|
|
14820
14936
|
var MAX_UPLOAD_BYTES = 8 * 1024 * 1024;
|
|
14821
14937
|
var UploadImageSchema = z8.object({
|
|
@@ -14828,12 +14944,12 @@ var UploadImageSchema = z8.object({
|
|
|
14828
14944
|
overwrite: z8.boolean().optional()
|
|
14829
14945
|
}).strict();
|
|
14830
14946
|
async function saveProjectImage(root, request) {
|
|
14831
|
-
const file =
|
|
14832
|
-
const relative =
|
|
14833
|
-
if (relative.startsWith("..") ||
|
|
14947
|
+
const file = path27.resolve(root, request.path);
|
|
14948
|
+
const relative = path27.relative(root, file);
|
|
14949
|
+
if (relative.startsWith("..") || path27.isAbsolute(relative)) {
|
|
14834
14950
|
throw new ManifestEditError("an upload must stay inside the project");
|
|
14835
14951
|
}
|
|
14836
|
-
const extension =
|
|
14952
|
+
const extension = path27.extname(file).toLowerCase();
|
|
14837
14953
|
const expected = extension === ".png" ? "png" : extension === ".jpg" || extension === ".jpeg" ? "jpeg" : null;
|
|
14838
14954
|
if (!expected) throw new ManifestEditError("an upload must be a .png, .jpg, or .jpeg path");
|
|
14839
14955
|
const bytes = Buffer.from(request.base64, "base64");
|
|
@@ -14846,17 +14962,17 @@ async function saveProjectImage(root, request) {
|
|
|
14846
14962
|
throw new ManifestEditError(`the upload is a ${metadata.format.toUpperCase()}; name it ${metadata.format === "png" ? ".png" : ".jpg"}`);
|
|
14847
14963
|
}
|
|
14848
14964
|
if (existsSync18(file) && !request.overwrite) {
|
|
14849
|
-
throw new ManifestEditError(`${relative.split(
|
|
14965
|
+
throw new ManifestEditError(`${relative.split(path27.sep).join("/")} already exists; choose another name, or replace it`);
|
|
14850
14966
|
}
|
|
14851
|
-
await mkdir10(
|
|
14967
|
+
await mkdir10(path27.dirname(file), { recursive: true });
|
|
14852
14968
|
await writeFile12(file, bytes);
|
|
14853
|
-
return relative.split(
|
|
14969
|
+
return relative.split(path27.sep).join("/");
|
|
14854
14970
|
}
|
|
14855
14971
|
|
|
14856
14972
|
// src/gallery/image-revision.ts
|
|
14857
14973
|
import { existsSync as existsSync19 } from "fs";
|
|
14858
14974
|
import { readFile as readFile17, rm as rm9, writeFile as writeFile13 } from "fs/promises";
|
|
14859
|
-
import
|
|
14975
|
+
import path28 from "path";
|
|
14860
14976
|
import { z as z9 } from "zod";
|
|
14861
14977
|
var InpaintRevisionRequestSchema = z9.object({
|
|
14862
14978
|
action: z9.literal("create-inpaint-revision"),
|
|
@@ -14875,14 +14991,14 @@ var InpaintRevisionRequestSchema = z9.object({
|
|
|
14875
14991
|
overwrite: z9.boolean().optional()
|
|
14876
14992
|
}).strict();
|
|
14877
14993
|
async function createInpaintRevision(manifestPath, request) {
|
|
14878
|
-
const root =
|
|
14879
|
-
if (
|
|
14994
|
+
const root = path28.dirname(path28.resolve(manifestPath));
|
|
14995
|
+
if (path28.extname(request.maskPath).toLowerCase() !== ".png") {
|
|
14880
14996
|
throw new ManifestEditError("the mask must be a .png path inside the project");
|
|
14881
14997
|
}
|
|
14882
14998
|
const bytes = Buffer.from(request.maskBase64, "base64");
|
|
14883
14999
|
const metadata = imageMetadata(bytes);
|
|
14884
15000
|
if (!metadata || metadata.format !== "png") throw new ManifestEditError("the mask is not a readable PNG");
|
|
14885
|
-
const file =
|
|
15001
|
+
const file = path28.resolve(root, request.maskPath);
|
|
14886
15002
|
const previous = existsSync19(file) ? await readFile17(file) : null;
|
|
14887
15003
|
const mask = await saveProjectImage(root, {
|
|
14888
15004
|
action: "upload-image",
|
|
@@ -14912,7 +15028,7 @@ async function createInpaintRevision(manifestPath, request) {
|
|
|
14912
15028
|
// src/gallery/skeleton.ts
|
|
14913
15029
|
import { existsSync as existsSync20 } from "fs";
|
|
14914
15030
|
import { mkdir as mkdir11, readFile as readFile18, rm as rm10, writeFile as writeFile14 } from "fs/promises";
|
|
14915
|
-
import
|
|
15031
|
+
import path29 from "path";
|
|
14916
15032
|
import { z as z10 } from "zod";
|
|
14917
15033
|
var SkeletonEstimateRequestSchema = z10.object({
|
|
14918
15034
|
project: z10.string().min(1).optional(),
|
|
@@ -14951,7 +15067,7 @@ var SkeletonKeypointsUpdateSchema = z10.object({
|
|
|
14951
15067
|
}).strict();
|
|
14952
15068
|
var DEFAULT_ESTIMATE_LIMIT = 10;
|
|
14953
15069
|
function skeletonSourceFile(spec) {
|
|
14954
|
-
if (spec.source) return
|
|
15070
|
+
if (spec.source) return path29.resolve(spec.root, spec.source);
|
|
14955
15071
|
if (spec.quality && existsSync20(spec.quality.outFile)) return spec.quality.outFile;
|
|
14956
15072
|
return spec.outFile;
|
|
14957
15073
|
}
|
|
@@ -14976,7 +15092,7 @@ function createGallerySkeletonHandlers(opts) {
|
|
|
14976
15092
|
if (!existsSync20(file)) throw new ManifestEditError(`${key} has no image on disk to estimate from`);
|
|
14977
15093
|
const bytes = await readFile18(file);
|
|
14978
15094
|
const metadata = imageMetadata(bytes);
|
|
14979
|
-
if (!metadata) throw new ManifestEditError(`${
|
|
15095
|
+
if (!metadata) throw new ManifestEditError(`${path29.basename(file)} is not a readable PNG or JPEG`);
|
|
14980
15096
|
const problem = estimateSkeletonSizeProblem(metadata.width, metadata.height);
|
|
14981
15097
|
if (problem) throw new ManifestEditError(`${key} is ${problem}`);
|
|
14982
15098
|
if (used >= limit) {
|
|
@@ -14993,7 +15109,7 @@ function createGallerySkeletonHandlers(opts) {
|
|
|
14993
15109
|
log2(` estimated a skeleton for ${key} (PixelLab estimate-skeleton, ${used}/${limit} this session${billed ? `, billed ${billed.amount} ${billed.unit}` : ""})`);
|
|
14994
15110
|
return {
|
|
14995
15111
|
set: scaffoldSkeletonSet(res.keypoints, request.frames ?? DEFAULT_SCAFFOLD_FRAMES),
|
|
14996
|
-
source:
|
|
15112
|
+
source: path29.relative(ctx.loaded.root, file).split(path29.sep).join("/"),
|
|
14997
15113
|
session: status()
|
|
14998
15114
|
};
|
|
14999
15115
|
}
|
|
@@ -15007,10 +15123,10 @@ function billedAmount(usage) {
|
|
|
15007
15123
|
return typeof amount === "number" && Number.isFinite(amount) ? { unit: type, amount } : null;
|
|
15008
15124
|
}
|
|
15009
15125
|
async function createSkeletonAnimation(manifestPath, request) {
|
|
15010
|
-
const root =
|
|
15011
|
-
const file =
|
|
15012
|
-
const relative =
|
|
15013
|
-
if (!request.keypointsFile.endsWith(".json") || relative.startsWith("..") ||
|
|
15126
|
+
const root = path29.dirname(path29.resolve(manifestPath));
|
|
15127
|
+
const file = path29.resolve(root, request.keypointsFile);
|
|
15128
|
+
const relative = path29.relative(root, file);
|
|
15129
|
+
if (!request.keypointsFile.endsWith(".json") || relative.startsWith("..") || path29.isAbsolute(relative)) {
|
|
15014
15130
|
throw new ManifestEditError("the keypoints file must be a .json path inside the project");
|
|
15015
15131
|
}
|
|
15016
15132
|
const existed = existsSync20(file);
|
|
@@ -15020,7 +15136,7 @@ async function createSkeletonAnimation(manifestPath, request) {
|
|
|
15020
15136
|
throw new ManifestEditError(`${request.keypointsFile} already exists; choose another path, or replace it`);
|
|
15021
15137
|
}
|
|
15022
15138
|
if (existed) previous = await readFile18(file);
|
|
15023
|
-
await mkdir11(
|
|
15139
|
+
await mkdir11(path29.dirname(file), { recursive: true });
|
|
15024
15140
|
await writeFile14(file, JSON.stringify(request.set, null, 2) + "\n");
|
|
15025
15141
|
} else if (!existed) {
|
|
15026
15142
|
throw new ManifestEditError(`${request.keypointsFile} does not exist; paste poses or estimate them from the sprite`);
|
|
@@ -15036,7 +15152,7 @@ async function createSkeletonAnimation(manifestPath, request) {
|
|
|
15036
15152
|
revision: {
|
|
15037
15153
|
mode: "animate-skeleton",
|
|
15038
15154
|
from: request.from,
|
|
15039
|
-
keypointsFile: relative.split(
|
|
15155
|
+
keypointsFile: relative.split(path29.sep).join("/"),
|
|
15040
15156
|
direction: request.direction,
|
|
15041
15157
|
...request.description ? { description: request.description } : {},
|
|
15042
15158
|
...request.skeletonTemplate ? { skeletonTemplate: request.skeletonTemplate } : {}
|
|
@@ -15057,19 +15173,19 @@ async function updateSkeletonKeypoints(ctx, request) {
|
|
|
15057
15173
|
if (!spec) throw new ManifestEditError(`"${key}" is not declared by the manifest`);
|
|
15058
15174
|
const file = spec.revision?.mode === "animate-skeleton" ? spec.revision.keypointsFile : void 0;
|
|
15059
15175
|
if (!file) throw new ManifestEditError(`${key} is not an animate-skeleton revision`);
|
|
15060
|
-
const relative =
|
|
15061
|
-
if (relative.startsWith("..") ||
|
|
15176
|
+
const relative = path29.relative(ctx.loaded.root, file);
|
|
15177
|
+
if (relative.startsWith("..") || path29.isAbsolute(relative)) {
|
|
15062
15178
|
throw new ManifestEditError(`${key}'s keypoints file is outside the project; edit it by hand`);
|
|
15063
15179
|
}
|
|
15064
15180
|
const current = existsSync20(file) ? sha256(await readFile18(file)) : null;
|
|
15065
15181
|
if (current !== request.expectedSha256) {
|
|
15066
15182
|
throw Object.assign(
|
|
15067
|
-
new Error(`${relative.split(
|
|
15183
|
+
new Error(`${relative.split(path29.sep).join("/")} changed on disk since this page loaded it; press Refresh and edit again`),
|
|
15068
15184
|
{ status: 409 }
|
|
15069
15185
|
);
|
|
15070
15186
|
}
|
|
15071
15187
|
await writeFile14(file, JSON.stringify(request.set, null, 2) + "\n");
|
|
15072
|
-
return relative.split(
|
|
15188
|
+
return relative.split(path29.sep).join("/");
|
|
15073
15189
|
}
|
|
15074
15190
|
|
|
15075
15191
|
// src/gallery/edit.ts
|
|
@@ -15123,7 +15239,7 @@ function createGalleryEditHandler(opts) {
|
|
|
15123
15239
|
const history = /* @__PURE__ */ new Map();
|
|
15124
15240
|
const manifestOf = async (project) => {
|
|
15125
15241
|
try {
|
|
15126
|
-
return
|
|
15242
|
+
return path30.resolve(await opts.manifestFor(typeof project === "string" ? project : void 0));
|
|
15127
15243
|
} catch {
|
|
15128
15244
|
return null;
|
|
15129
15245
|
}
|
|
@@ -15146,7 +15262,7 @@ function createGalleryEditHandler(opts) {
|
|
|
15146
15262
|
await writeFile15(tmp, last.before);
|
|
15147
15263
|
await rename9(tmp, manifestPath2);
|
|
15148
15264
|
stack.pop();
|
|
15149
|
-
log2(` manifest edit undone in ${
|
|
15265
|
+
log2(` manifest edit undone in ${path30.relative(process.cwd(), manifestPath2) || manifestPath2}`);
|
|
15150
15266
|
return opts.reload();
|
|
15151
15267
|
}
|
|
15152
15268
|
const manifestPath = await manifestOf(body?.project);
|
|
@@ -15208,7 +15324,7 @@ function createEditApplier(opts, log2) {
|
|
|
15208
15324
|
} catch (error) {
|
|
15209
15325
|
throw new ManifestEditError(error instanceof Error ? error.message : String(error));
|
|
15210
15326
|
}
|
|
15211
|
-
const written = await saveProjectImage(
|
|
15327
|
+
const written = await saveProjectImage(path30.dirname(path30.resolve(manifestPath2)), upload.data);
|
|
15212
15328
|
log2(` image added: ${written}`);
|
|
15213
15329
|
return opts.reload();
|
|
15214
15330
|
}
|
|
@@ -15275,7 +15391,7 @@ function createEditApplier(opts, log2) {
|
|
|
15275
15391
|
}
|
|
15276
15392
|
const result = await applyManifestEdit(manifestPath, parsed.data);
|
|
15277
15393
|
if (result.changed) {
|
|
15278
|
-
const relative =
|
|
15394
|
+
const relative = path30.relative(process.cwd(), result.manifestPath);
|
|
15279
15395
|
const shown = !relative || relative.startsWith("..") ? result.manifestPath : relative;
|
|
15280
15396
|
log2(` manifest edited: ${describeEdit(parsed.data)} in ${shown}`);
|
|
15281
15397
|
}
|
|
@@ -15327,7 +15443,7 @@ function createGalleryPriceHandler(opts) {
|
|
|
15327
15443
|
|
|
15328
15444
|
// src/pipeline/history.ts
|
|
15329
15445
|
import { existsSync as existsSync21 } from "fs";
|
|
15330
|
-
import
|
|
15446
|
+
import path31 from "path";
|
|
15331
15447
|
var HISTORY_ENV = "PIXELKILN_HISTORY";
|
|
15332
15448
|
function historyLimit(manifest, env = process.env) {
|
|
15333
15449
|
if (manifest?.history !== void 0) return manifest.history;
|
|
@@ -15417,7 +15533,7 @@ async function revertGeneration(provider, spec, lock, lockPath, opts) {
|
|
|
15417
15533
|
if (!existsSync21(file)) continue;
|
|
15418
15534
|
if (await sha256File(file) !== entry.outputs[i].sha256 && !opts.force) {
|
|
15419
15535
|
throw new OverwriteRefusedError(
|
|
15420
|
-
`${
|
|
15536
|
+
`${path31.relative(process.cwd(), file)} was changed after download; keep it with pixelkiln edit, or pass --force to replace it`,
|
|
15421
15537
|
[file]
|
|
15422
15538
|
);
|
|
15423
15539
|
}
|
|
@@ -15470,7 +15586,7 @@ async function revertGeneration(provider, spec, lock, lockPath, opts) {
|
|
|
15470
15586
|
// src/gallery/snapshot.ts
|
|
15471
15587
|
var PROJECT_FILE_TYPE = "application/octet-stream";
|
|
15472
15588
|
function galleryMediaId(absolutePath) {
|
|
15473
|
-
return
|
|
15589
|
+
return createHash4("sha256").update(path32.resolve(absolutePath)).digest("hex").slice(0, 24);
|
|
15474
15590
|
}
|
|
15475
15591
|
function galleryMediaRoute(id) {
|
|
15476
15592
|
return `/media/${id}`;
|
|
@@ -15489,7 +15605,7 @@ function describeHistory(media, entry, cacheDir) {
|
|
|
15489
15605
|
return (entry.history ?? []).map((generation, i) => {
|
|
15490
15606
|
const outputs = generation.outputs.map((output) => {
|
|
15491
15607
|
const mediaType = output.mediaType ?? MediaType.PNG;
|
|
15492
|
-
const file =
|
|
15608
|
+
const file = path32.join(cacheDir, cacheFileName(output.sha256, mediaType));
|
|
15493
15609
|
let url = null;
|
|
15494
15610
|
if (existsSync22(file)) {
|
|
15495
15611
|
const id = galleryMediaId(file);
|
|
@@ -15621,9 +15737,9 @@ async function describeOutput(media, root, absolutePath, recorded = {}) {
|
|
|
15621
15737
|
async function describeQuality(media, root, inspection) {
|
|
15622
15738
|
const outputPaths = inspection.outputs ?? [inspection.output];
|
|
15623
15739
|
const outputs = await Promise.all(
|
|
15624
|
-
outputPaths.map((file) => describeOutput(media, root,
|
|
15740
|
+
outputPaths.map((file) => describeOutput(media, root, path32.resolve(root, file)))
|
|
15625
15741
|
);
|
|
15626
|
-
const recordPath =
|
|
15742
|
+
const recordPath = path32.resolve(root, inspection.record);
|
|
15627
15743
|
const recordExists = existsSync22(recordPath);
|
|
15628
15744
|
let options = null;
|
|
15629
15745
|
let check = null;
|
|
@@ -15643,7 +15759,7 @@ async function describeQuality(media, root, inspection) {
|
|
|
15643
15759
|
return {
|
|
15644
15760
|
state: inspection.state,
|
|
15645
15761
|
reason: inspection.reason,
|
|
15646
|
-
output: portableOutputPath(
|
|
15762
|
+
output: portableOutputPath(path32.resolve(root, inspection.output), root),
|
|
15647
15763
|
record: portableOutputPath(recordPath, root),
|
|
15648
15764
|
recordExists,
|
|
15649
15765
|
outputs,
|
|
@@ -15683,7 +15799,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
15683
15799
|
const items = [];
|
|
15684
15800
|
const manifestText = await readFile20(loaded.path, "utf8");
|
|
15685
15801
|
const manifestSha256 = sha256(manifestText);
|
|
15686
|
-
const cacheDir =
|
|
15802
|
+
const cacheDir = path32.resolve(path32.dirname(opts.lockPath), ".pixelkiln", "cache");
|
|
15687
15803
|
let rawStyles = {};
|
|
15688
15804
|
try {
|
|
15689
15805
|
const raw = JSON.parse(manifestText);
|
|
@@ -15707,7 +15823,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
15707
15823
|
)
|
|
15708
15824
|
);
|
|
15709
15825
|
if (spec.source) {
|
|
15710
|
-
const editPath =
|
|
15826
|
+
const editPath = path32.resolve(root, spec.source);
|
|
15711
15827
|
const memberCount = sourceIsStem(spec.source, entry, root) ? entry.outputs.length : 1;
|
|
15712
15828
|
const editShas = await Promise.all(
|
|
15713
15829
|
Array.from({ length: memberCount }, async (_, index) => {
|
|
@@ -15756,7 +15872,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
15756
15872
|
editStatus = edits.some((member) => !member.exists) ? "missing" : editChanged.every((changed) => !changed) ? "same" : regenerated ? "regenerated-since" : "edited";
|
|
15757
15873
|
}
|
|
15758
15874
|
} else if (spec.source) {
|
|
15759
|
-
outputs = [await describeOutput(media, root,
|
|
15875
|
+
outputs = [await describeOutput(media, root, path32.resolve(root, spec.source))];
|
|
15760
15876
|
} else if (!entry && existsSync22(spec.outFile)) {
|
|
15761
15877
|
outputs = [await describeOutput(media, root, spec.outFile)];
|
|
15762
15878
|
} else {
|
|
@@ -15960,7 +16076,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
15960
16076
|
id: loaded.manifest.name,
|
|
15961
16077
|
name: loaded.manifest.name,
|
|
15962
16078
|
manifest: loaded.path,
|
|
15963
|
-
lock:
|
|
16079
|
+
lock: path32.resolve(opts.lockPath),
|
|
15964
16080
|
root,
|
|
15965
16081
|
provider: loaded.manifest.provider,
|
|
15966
16082
|
account: null,
|
|
@@ -15986,7 +16102,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
15986
16102
|
return { snapshot, media };
|
|
15987
16103
|
}
|
|
15988
16104
|
async function buildWorkspaceGallerySnapshot(opts) {
|
|
15989
|
-
const dir =
|
|
16105
|
+
const dir = path32.dirname(path32.resolve(opts.workspacePath));
|
|
15990
16106
|
const media = /* @__PURE__ */ new Map();
|
|
15991
16107
|
const projects = [];
|
|
15992
16108
|
const items = [];
|
|
@@ -16029,7 +16145,7 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
16029
16145
|
id: project.id,
|
|
16030
16146
|
name: loaded.manifest.name,
|
|
16031
16147
|
manifest: manifestPath,
|
|
16032
|
-
lock:
|
|
16148
|
+
lock: path32.resolve(lockPath),
|
|
16033
16149
|
root: loaded.root,
|
|
16034
16150
|
provider: loaded.manifest.provider,
|
|
16035
16151
|
account: project.account ?? null,
|
|
@@ -16045,8 +16161,8 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
16045
16161
|
id: project.id,
|
|
16046
16162
|
name: project.id,
|
|
16047
16163
|
manifest: manifestPath,
|
|
16048
|
-
lock:
|
|
16049
|
-
root:
|
|
16164
|
+
lock: path32.resolve(lockPath),
|
|
16165
|
+
root: path32.dirname(manifestPath),
|
|
16050
16166
|
provider: project.provider,
|
|
16051
16167
|
account: project.account ?? null,
|
|
16052
16168
|
manifestSha256: null,
|
|
@@ -16073,7 +16189,7 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
16073
16189
|
version: 1,
|
|
16074
16190
|
generatedAt: (opts.now ?? (() => /* @__PURE__ */ new Date()))().toISOString(),
|
|
16075
16191
|
project: null,
|
|
16076
|
-
workspace: { path:
|
|
16192
|
+
workspace: { path: path32.resolve(opts.workspacePath), dir, projects },
|
|
16077
16193
|
filter,
|
|
16078
16194
|
totals: {
|
|
16079
16195
|
entries: projects.reduce((sum, project) => sum + project.entries, 0),
|
|
@@ -16095,7 +16211,7 @@ import { createServer as createServer2 } from "http";
|
|
|
16095
16211
|
import { readFile as readFile21 } from "fs/promises";
|
|
16096
16212
|
|
|
16097
16213
|
// src/gallery/page.ts
|
|
16098
|
-
import { existsSync as existsSync23, readFileSync as
|
|
16214
|
+
import { existsSync as existsSync23, readFileSync as readFileSync5 } from "fs";
|
|
16099
16215
|
|
|
16100
16216
|
// src/gallery/client-bundle.ts
|
|
16101
16217
|
import { createRequire } from "module";
|
|
@@ -16135,8 +16251,8 @@ var client;
|
|
|
16135
16251
|
function clientAssets() {
|
|
16136
16252
|
const prebuilt = new URL("gallery.js", CLIENT_DIR);
|
|
16137
16253
|
client ??= {
|
|
16138
|
-
css:
|
|
16139
|
-
js: existsSync23(prebuilt) ?
|
|
16254
|
+
css: readFileSync5(new URL("gallery.css", CLIENT_DIR), "utf8"),
|
|
16255
|
+
js: existsSync23(prebuilt) ? readFileSync5(prebuilt, "utf8") : bundleGalleryClient()
|
|
16140
16256
|
};
|
|
16141
16257
|
return client;
|
|
16142
16258
|
}
|
|
@@ -16955,7 +17071,7 @@ import { readFile as readFile23 } from "fs/promises";
|
|
|
16955
17071
|
// src/pipeline/init.ts
|
|
16956
17072
|
import { readdir, readFile as readFile22, writeFile as writeFile16 } from "fs/promises";
|
|
16957
17073
|
import { existsSync as existsSync24 } from "fs";
|
|
16958
|
-
import
|
|
17074
|
+
import path33 from "path";
|
|
16959
17075
|
function pngSize(buf) {
|
|
16960
17076
|
if (buf.length < 24) return null;
|
|
16961
17077
|
const isPng = buf.readUInt32BE(0) === 2303741511 && buf.readUInt32BE(4) === 218765834;
|
|
@@ -16969,7 +17085,7 @@ function slugify(value) {
|
|
|
16969
17085
|
async function walk(dir, exclude) {
|
|
16970
17086
|
const out = [];
|
|
16971
17087
|
for (const entry of await readdir(dir, { withFileTypes: true })) {
|
|
16972
|
-
const full =
|
|
17088
|
+
const full = path33.join(dir, entry.name);
|
|
16973
17089
|
if (exclude.some((x) => full.includes(x))) continue;
|
|
16974
17090
|
if (entry.isDirectory()) out.push(...await walk(full, exclude));
|
|
16975
17091
|
else if (entry.name.toLowerCase().endsWith(".png")) out.push(full);
|
|
@@ -16988,9 +17104,9 @@ async function scanAssets(root, opts = {}) {
|
|
|
16988
17104
|
skipped.push(`${file} (not a readable PNG)`);
|
|
16989
17105
|
continue;
|
|
16990
17106
|
}
|
|
16991
|
-
const rel =
|
|
16992
|
-
const category =
|
|
16993
|
-
let id = slugify(
|
|
17107
|
+
const rel = path33.relative(root, file);
|
|
17108
|
+
const category = path33.dirname(rel) === "." ? "" : path33.dirname(rel);
|
|
17109
|
+
let id = slugify(path33.basename(rel));
|
|
16994
17110
|
if (seen.has(id)) {
|
|
16995
17111
|
const n = seen.get(id) + 1;
|
|
16996
17112
|
seen.set(id, n);
|
|
@@ -18004,14 +18120,14 @@ function createGenerateHandlers(opts) {
|
|
|
18004
18120
|
|
|
18005
18121
|
// src/gallery/editor.ts
|
|
18006
18122
|
import { readFile as readFile25 } from "fs/promises";
|
|
18007
|
-
import
|
|
18123
|
+
import path35 from "path";
|
|
18008
18124
|
|
|
18009
18125
|
// src/editor/install.ts
|
|
18010
|
-
import { createHash as
|
|
18126
|
+
import { createHash as createHash5 } from "crypto";
|
|
18011
18127
|
import { existsSync as existsSync26 } from "fs";
|
|
18012
18128
|
import { mkdir as mkdir12, readFile as readFile24, rename as rename10, rm as rm11, stat as stat3, writeFile as writeFile17 } from "fs/promises";
|
|
18013
|
-
import
|
|
18014
|
-
import
|
|
18129
|
+
import os3 from "os";
|
|
18130
|
+
import path34 from "path";
|
|
18015
18131
|
|
|
18016
18132
|
// tools/pixelorama-bridge/pin.json
|
|
18017
18133
|
var pin_default = {
|
|
@@ -18075,15 +18191,15 @@ var EditorInstallError = class extends Error {
|
|
|
18075
18191
|
};
|
|
18076
18192
|
function editorToolsRoot() {
|
|
18077
18193
|
const override = process.env.PIXELKILN_TOOLS_DIR?.trim();
|
|
18078
|
-
if (override) return
|
|
18079
|
-
if (process.platform === "darwin") return
|
|
18194
|
+
if (override) return path34.resolve(override);
|
|
18195
|
+
if (process.platform === "darwin") return path34.join(os3.homedir(), "Library", "Caches", "pixelkiln", "tools");
|
|
18080
18196
|
if (process.platform === "win32") {
|
|
18081
|
-
return
|
|
18197
|
+
return path34.join(process.env.LOCALAPPDATA ?? path34.join(os3.homedir(), "AppData", "Local"), "pixelkiln", "tools");
|
|
18082
18198
|
}
|
|
18083
|
-
return
|
|
18199
|
+
return path34.join(process.env.XDG_CACHE_HOME ?? path34.join(os3.homedir(), ".cache"), "pixelkiln", "tools");
|
|
18084
18200
|
}
|
|
18085
18201
|
function editorDir(pin = EDITOR_PIN, root = editorToolsRoot()) {
|
|
18086
|
-
return
|
|
18202
|
+
return path34.join(root, "pixelorama", pin.release ?? "unpublished");
|
|
18087
18203
|
}
|
|
18088
18204
|
function editorBaseUrl(pin = EDITOR_PIN) {
|
|
18089
18205
|
const override = process.env.PIXELKILN_EDITOR_URL?.trim();
|
|
@@ -18093,7 +18209,7 @@ function editorBaseUrl(pin = EDITOR_PIN) {
|
|
|
18093
18209
|
}
|
|
18094
18210
|
async function sha256Of(file) {
|
|
18095
18211
|
try {
|
|
18096
|
-
return
|
|
18212
|
+
return createHash5("sha256").update(await readFile24(file)).digest("hex");
|
|
18097
18213
|
} catch {
|
|
18098
18214
|
return null;
|
|
18099
18215
|
}
|
|
@@ -18106,7 +18222,7 @@ async function editorStatus(opts = {}) {
|
|
|
18106
18222
|
let totalBytes = 0;
|
|
18107
18223
|
for (const [name, expected] of Object.entries(pin.files)) {
|
|
18108
18224
|
totalBytes += expected.bytes;
|
|
18109
|
-
const file =
|
|
18225
|
+
const file = path34.join(dir, name);
|
|
18110
18226
|
const info = existsSync26(file) ? await stat3(file).catch(() => null) : null;
|
|
18111
18227
|
if (info && info.size === expected.bytes && await sha256Of(file) === expected.sha256) {
|
|
18112
18228
|
installedBytes += expected.bytes;
|
|
@@ -18160,11 +18276,11 @@ async function installEditor(opts = {}) {
|
|
|
18160
18276
|
if (bytes.length !== expected.bytes) {
|
|
18161
18277
|
throw new EditorInstallError(`${name}: expected ${expected.bytes} bytes, got ${bytes.length}`, name);
|
|
18162
18278
|
}
|
|
18163
|
-
const digest2 =
|
|
18279
|
+
const digest2 = createHash5("sha256").update(bytes).digest("hex");
|
|
18164
18280
|
if (digest2 !== expected.sha256) {
|
|
18165
18281
|
throw new EditorInstallError(`${name}: sha256 ${digest2.slice(0, 12)}\u2026 does not match the pinned ${expected.sha256.slice(0, 12)}\u2026`, name);
|
|
18166
18282
|
}
|
|
18167
|
-
const final =
|
|
18283
|
+
const final = path34.join(dir, name);
|
|
18168
18284
|
const tmp = `${final}.${process.pid}.part`;
|
|
18169
18285
|
try {
|
|
18170
18286
|
await writeFile17(tmp, bytes);
|
|
@@ -18210,7 +18326,7 @@ var EDITOR_CONTENT_TYPES = {
|
|
|
18210
18326
|
".json": "application/json; charset=utf-8"
|
|
18211
18327
|
};
|
|
18212
18328
|
function editorContentType(name) {
|
|
18213
|
-
return EDITOR_CONTENT_TYPES[
|
|
18329
|
+
return EDITOR_CONTENT_TYPES[path34.extname(name).toLowerCase()] ?? null;
|
|
18214
18330
|
}
|
|
18215
18331
|
|
|
18216
18332
|
// src/gallery/editor.ts
|
|
@@ -18286,7 +18402,7 @@ function createGalleryEditorHandlers(opts = {}) {
|
|
|
18286
18402
|
if (!status.installed) return null;
|
|
18287
18403
|
let bytes;
|
|
18288
18404
|
try {
|
|
18289
|
-
bytes = await readFile25(
|
|
18405
|
+
bytes = await readFile25(path35.join(dir, name));
|
|
18290
18406
|
} catch {
|
|
18291
18407
|
verified = null;
|
|
18292
18408
|
return null;
|
|
@@ -18326,7 +18442,7 @@ async function serveUntilStopped(initial, reload, args, access2) {
|
|
|
18326
18442
|
};
|
|
18327
18443
|
const loadProject = async (project) => {
|
|
18328
18444
|
const ctx = await access2.loadProject(project);
|
|
18329
|
-
loadProjectEnv(
|
|
18445
|
+
loadProjectEnv(path36.dirname(ctx.loaded.path));
|
|
18330
18446
|
return ctx;
|
|
18331
18447
|
};
|
|
18332
18448
|
const loadProjectEnv = (dir) => {
|
|
@@ -18335,7 +18451,7 @@ async function serveUntilStopped(initial, reload, args, access2) {
|
|
|
18335
18451
|
for (const [name, value] of Object.entries(declared)) {
|
|
18336
18452
|
if (credentialNames.has(name) && name in process.env && process.env[name] !== value) {
|
|
18337
18453
|
throw new Error(
|
|
18338
|
-
`${
|
|
18454
|
+
`${path36.relative(process.cwd(), dir) || "."} sets ${name} to a different value than the one this gallery already loaded; run a separate gallery for that project so its work uses its own account.`
|
|
18339
18455
|
);
|
|
18340
18456
|
}
|
|
18341
18457
|
}
|
|
@@ -18343,7 +18459,7 @@ async function serveUntilStopped(initial, reload, args, access2) {
|
|
|
18343
18459
|
};
|
|
18344
18460
|
const quoters = /* @__PURE__ */ new Map();
|
|
18345
18461
|
const quoteFor = async (project) => {
|
|
18346
|
-
loadProjectEnv(
|
|
18462
|
+
loadProjectEnv(path36.dirname(path36.resolve(await access2.manifestFor(project))));
|
|
18347
18463
|
const key = process.env.PIXELLAB_API_KEY;
|
|
18348
18464
|
if (!key) return null;
|
|
18349
18465
|
let quoter = quoters.get(key);
|
|
@@ -18394,7 +18510,7 @@ async function serveUntilStopped(initial, reload, args, access2) {
|
|
|
18394
18510
|
}
|
|
18395
18511
|
async function runGallery(args) {
|
|
18396
18512
|
if (args.workspace) {
|
|
18397
|
-
const workspacePath =
|
|
18513
|
+
const workspacePath = path36.resolve(args.workspace);
|
|
18398
18514
|
if (!existsSync27(workspacePath)) {
|
|
18399
18515
|
throw new Error(`Workspace catalog not found: ${workspacePath}`);
|
|
18400
18516
|
}
|
|
@@ -18415,7 +18531,7 @@ async function runGallery(args) {
|
|
|
18415
18531
|
const registered = async (projectId) => {
|
|
18416
18532
|
const project = (await loadWorkspace(workspacePath)).projects.find((candidate) => candidate.id === projectId);
|
|
18417
18533
|
if (!projectId || !project) throw new Error(`unknown workspace project "${projectId ?? ""}"`);
|
|
18418
|
-
return resolveProject(
|
|
18534
|
+
return resolveProject(path36.dirname(workspacePath), project);
|
|
18419
18535
|
};
|
|
18420
18536
|
await serveUntilStopped(initial, build2, args, {
|
|
18421
18537
|
manifestFor: async (projectId) => (await registered(projectId)).manifestPath,
|
|
@@ -18440,7 +18556,7 @@ async function runGallery(args) {
|
|
|
18440
18556
|
return buildGallerySnapshot({ ...fresh, filter });
|
|
18441
18557
|
};
|
|
18442
18558
|
await serveUntilStopped(await build(), reload, args, {
|
|
18443
|
-
manifestFor: () =>
|
|
18559
|
+
manifestFor: () => path36.resolve(args.manifest),
|
|
18444
18560
|
loadProject: () => openProject(args.manifest, { lockPath: args.lock, env: false })
|
|
18445
18561
|
});
|
|
18446
18562
|
}
|
|
@@ -18754,13 +18870,13 @@ import { existsSync as existsSync29 } from "fs";
|
|
|
18754
18870
|
// src/pipeline/cache-health.ts
|
|
18755
18871
|
import { existsSync as existsSync28 } from "fs";
|
|
18756
18872
|
import { readFile as readFile26, readdir as readdir2, rm as rm12 } from "fs/promises";
|
|
18757
|
-
import
|
|
18873
|
+
import path37 from "path";
|
|
18758
18874
|
async function inspectCaches(lock, lockPath, options = {}) {
|
|
18759
18875
|
if (options.prune && !existsSync28(lockPath)) {
|
|
18760
|
-
throw new Error(`Refusing to prune without an existing lockfile at ${
|
|
18876
|
+
throw new Error(`Refusing to prune without an existing lockfile at ${path37.resolve(lockPath)}`);
|
|
18761
18877
|
}
|
|
18762
|
-
const contentDir =
|
|
18763
|
-
const remotePath =
|
|
18878
|
+
const contentDir = path37.resolve(path37.dirname(lockPath), ".pixelkiln", "cache");
|
|
18879
|
+
const remotePath = path37.resolve(cachePathFor(lockPath));
|
|
18764
18880
|
const referenced = referencedHashes(lock);
|
|
18765
18881
|
let content = await inspectContentCache(contentDir, referenced);
|
|
18766
18882
|
let remoteHashes = await inspectRemoteHashCache(remotePath);
|
|
@@ -18772,7 +18888,7 @@ async function inspectCaches(lock, lockPath, options = {}) {
|
|
|
18772
18888
|
]);
|
|
18773
18889
|
for (const name of names) {
|
|
18774
18890
|
try {
|
|
18775
|
-
await rm12(
|
|
18891
|
+
await rm12(path37.join(contentDir, name), { force: true });
|
|
18776
18892
|
removed.contentFiles++;
|
|
18777
18893
|
} catch {
|
|
18778
18894
|
}
|
|
@@ -18820,7 +18936,7 @@ async function inspectContentCache(contentDir, referenced) {
|
|
|
18820
18936
|
continue;
|
|
18821
18937
|
}
|
|
18822
18938
|
report.files++;
|
|
18823
|
-
const file =
|
|
18939
|
+
const file = path37.join(contentDir, entry.name);
|
|
18824
18940
|
const mediaType = mediaTypeFromExtension(entry.name);
|
|
18825
18941
|
const expected = mediaType ? entry.name.slice(0, -4) : "";
|
|
18826
18942
|
let bytes;
|
|
@@ -18991,42 +19107,42 @@ This drops their provenance from the lockfile. The art on disk is`);
|
|
|
18991
19107
|
}
|
|
18992
19108
|
|
|
18993
19109
|
// src/cli/commands/init.ts
|
|
18994
|
-
import
|
|
19110
|
+
import path38 from "path";
|
|
18995
19111
|
import { existsSync as existsSync30 } from "fs";
|
|
18996
19112
|
async function runInit(args) {
|
|
18997
19113
|
if (!args.from) throw new Error("init needs --from <dir> pointing at your existing PNGs.");
|
|
18998
|
-
const root =
|
|
19114
|
+
const root = path38.resolve(args.from);
|
|
18999
19115
|
if (!existsSync30(root)) throw new Error(`No directory at ${root}`);
|
|
19000
19116
|
const generator = args.generator ?? "map";
|
|
19001
19117
|
if (generator !== "1dir" && generator !== "map") {
|
|
19002
19118
|
throw new Error(`--generator must be "1dir" or "map", got "${args.generator}".`);
|
|
19003
19119
|
}
|
|
19004
|
-
const target =
|
|
19120
|
+
const target = path38.resolve(args.out ?? "pixelkiln.manifest.json");
|
|
19005
19121
|
const { assets, skipped } = await scanAssets(root, { exclude: args.exclude });
|
|
19006
19122
|
if (!assets.length) throw new Error(`No PNGs found under ${root}`);
|
|
19007
19123
|
const manifest = buildManifest(
|
|
19008
|
-
args.name ??
|
|
19124
|
+
args.name ?? path38.basename(path38.dirname(target)),
|
|
19009
19125
|
args.styles[0] ?? "base",
|
|
19010
19126
|
generator,
|
|
19011
|
-
|
|
19127
|
+
path38.relative(path38.dirname(target), root) || ".",
|
|
19012
19128
|
assets
|
|
19013
19129
|
);
|
|
19014
19130
|
await writeManifestFile(target, manifest);
|
|
19015
|
-
log(` scanned ${assets.length} PNG(s) under ${
|
|
19131
|
+
log(` scanned ${assets.length} PNG(s) under ${path38.relative(process.cwd(), root)}`);
|
|
19016
19132
|
if (skipped.length) log(` skipped ${skipped.length} unreadable file(s)`);
|
|
19017
|
-
log(` wrote ${
|
|
19133
|
+
log(` wrote ${path38.relative(process.cwd(), target)}`);
|
|
19018
19134
|
log(`
|
|
19019
19135
|
Prompts are intentionally empty. To recover the real ones from your`);
|
|
19020
19136
|
log(` PixelLab account instead of inventing them:`);
|
|
19021
19137
|
log(`
|
|
19022
|
-
pixelkiln adopt --manifest ${
|
|
19138
|
+
pixelkiln adopt --manifest ${path38.relative(process.cwd(), target)} --write-prompts
|
|
19023
19139
|
`);
|
|
19024
19140
|
}
|
|
19025
19141
|
|
|
19026
19142
|
// src/pipeline/doctor.ts
|
|
19027
19143
|
import { constants, existsSync as existsSync31 } from "fs";
|
|
19028
19144
|
import { access, stat as stat4 } from "fs/promises";
|
|
19029
|
-
import
|
|
19145
|
+
import path39 from "path";
|
|
19030
19146
|
async function doctor(loaded, specs, lock, lockPath, opts = {}) {
|
|
19031
19147
|
const checks = [];
|
|
19032
19148
|
const add = (id, level, message6) => checks.push({ id, level, message: message6 });
|
|
@@ -19037,8 +19153,8 @@ async function doctor(loaded, specs, lock, lockPath, opts = {}) {
|
|
|
19037
19153
|
);
|
|
19038
19154
|
add("lock", "ok", `${Object.keys(lock.entries).length} valid v2 lock entr(ies)`);
|
|
19039
19155
|
const dirs = [...new Set(specs.flatMap((spec) => [
|
|
19040
|
-
|
|
19041
|
-
...spec.quality ? [
|
|
19156
|
+
path39.dirname(spec.outFile),
|
|
19157
|
+
...spec.quality ? [path39.dirname(spec.quality.outFile)] : []
|
|
19042
19158
|
]))];
|
|
19043
19159
|
const unwritable = [];
|
|
19044
19160
|
for (const dir of dirs) {
|
|
@@ -19061,7 +19177,7 @@ async function doctor(loaded, specs, lock, lockPath, opts = {}) {
|
|
|
19061
19177
|
for (let index = 0; index < entry.outputs.length; index++) {
|
|
19062
19178
|
const output = entry.outputs[index];
|
|
19063
19179
|
const spec = specByKey.get(key);
|
|
19064
|
-
const absolute = spec ? currentEntryOutputPath(entry, spec, index) : resolveOutputPath(output.path,
|
|
19180
|
+
const absolute = spec ? currentEntryOutputPath(entry, spec, index) : resolveOutputPath(output.path, path39.dirname(lockPath));
|
|
19065
19181
|
const owner = outputOwners.get(absolute);
|
|
19066
19182
|
if (owner && owner !== key) duplicateOutputs.push(`${output.path} (${owner}, ${key})`);
|
|
19067
19183
|
outputOwners.set(absolute, key);
|
|
@@ -19076,7 +19192,7 @@ async function doctor(loaded, specs, lock, lockPath, opts = {}) {
|
|
|
19076
19192
|
} else {
|
|
19077
19193
|
add("lock-outputs", "ok", `${outputOwners.size} recorded output path(s) are uniquely owned`);
|
|
19078
19194
|
}
|
|
19079
|
-
const cacheDir =
|
|
19195
|
+
const cacheDir = path39.join(path39.dirname(lockPath), ".pixelkiln", "cache");
|
|
19080
19196
|
const stranded = [];
|
|
19081
19197
|
const invalidState = [];
|
|
19082
19198
|
const staleMapUrls = [];
|
|
@@ -19092,7 +19208,7 @@ async function doctor(loaded, specs, lock, lockPath, opts = {}) {
|
|
|
19092
19208
|
const hasSource = Boolean(entry.sourceUrl || entry.sourceUrls?.length);
|
|
19093
19209
|
let hasCache = false;
|
|
19094
19210
|
for (const output of entry.outputs) {
|
|
19095
|
-
const cached =
|
|
19211
|
+
const cached = path39.join(
|
|
19096
19212
|
cacheDir,
|
|
19097
19213
|
cacheFileName(output.sha256, output.mediaType ?? MediaType.PNG)
|
|
19098
19214
|
);
|
|
@@ -19192,7 +19308,7 @@ async function nearestExistingDirectory(start) {
|
|
|
19192
19308
|
if ((await stat4(current)).isDirectory()) return current;
|
|
19193
19309
|
} catch {
|
|
19194
19310
|
}
|
|
19195
|
-
const parent =
|
|
19311
|
+
const parent = path39.dirname(current);
|
|
19196
19312
|
if (parent === current) return current;
|
|
19197
19313
|
current = parent;
|
|
19198
19314
|
}
|
|
@@ -19201,7 +19317,7 @@ async function nearestExistingDirectory(start) {
|
|
|
19201
19317
|
// src/pipeline/audit.ts
|
|
19202
19318
|
import { readFile as readFile27 } from "fs/promises";
|
|
19203
19319
|
import { existsSync as existsSync32 } from "fs";
|
|
19204
|
-
import
|
|
19320
|
+
import path40 from "path";
|
|
19205
19321
|
function paletteDistance(asset, reference) {
|
|
19206
19322
|
if (!asset.length || !reference.length) return 0;
|
|
19207
19323
|
let total = 0;
|
|
@@ -19266,7 +19382,7 @@ async function auditStyle(loaded, specs, styleId, lock) {
|
|
|
19266
19382
|
let referenceFromStyleImages = false;
|
|
19267
19383
|
const refPalettes = [];
|
|
19268
19384
|
for (const img of style.styleImages) {
|
|
19269
|
-
const abs =
|
|
19385
|
+
const abs = path40.resolve(loaded.root, img.path);
|
|
19270
19386
|
if (!existsSync32(abs)) continue;
|
|
19271
19387
|
try {
|
|
19272
19388
|
refPalettes.push(extractPalette(decodePng(await readFile27(abs)), 12));
|
|
@@ -19497,24 +19613,24 @@ async function runAudit(args) {
|
|
|
19497
19613
|
}
|
|
19498
19614
|
|
|
19499
19615
|
// src/cli/commands/pack.ts
|
|
19500
|
-
import
|
|
19616
|
+
import path42 from "path";
|
|
19501
19617
|
import { readFile as readFile28 } from "fs/promises";
|
|
19502
19618
|
|
|
19503
19619
|
// src/pipeline/pack.ts
|
|
19504
|
-
import { readFileSync as
|
|
19505
|
-
import
|
|
19620
|
+
import { readFileSync as readFileSync6 } from "fs";
|
|
19621
|
+
import path41 from "path";
|
|
19506
19622
|
var DEFAULT_FRAME_SET_FPS = 12;
|
|
19507
19623
|
function resolvePackInputs(raw, inputsFilePath) {
|
|
19508
19624
|
if (!Array.isArray(raw) || !raw.length) {
|
|
19509
19625
|
throw new Error("--inputs must be a non-empty JSON array of { id, path }");
|
|
19510
19626
|
}
|
|
19511
|
-
const baseDir =
|
|
19627
|
+
const baseDir = path41.dirname(path41.resolve(inputsFilePath));
|
|
19512
19628
|
return raw.map((entry, i) => {
|
|
19513
19629
|
const e = entry;
|
|
19514
19630
|
if (typeof e.id !== "string" || typeof e.path !== "string") {
|
|
19515
19631
|
throw new Error(`--inputs[${i}] needs string "id" and "path"`);
|
|
19516
19632
|
}
|
|
19517
|
-
return { id: e.id, path:
|
|
19633
|
+
return { id: e.id, path: path41.resolve(baseDir, e.path) };
|
|
19518
19634
|
});
|
|
19519
19635
|
}
|
|
19520
19636
|
function packSprites(inputs, options = {}) {
|
|
@@ -19535,7 +19651,7 @@ function packSprites(inputs, options = {}) {
|
|
|
19535
19651
|
for (const input of ordered) {
|
|
19536
19652
|
let sourceHash = null;
|
|
19537
19653
|
try {
|
|
19538
|
-
const bytes =
|
|
19654
|
+
const bytes = readFileSync6(input.path);
|
|
19539
19655
|
sourceHash = sha256(bytes);
|
|
19540
19656
|
const png = decodePng(bytes);
|
|
19541
19657
|
sprites.push({ id: input.id, width: png.width, height: png.height, pixels: png.pixels });
|
|
@@ -19598,7 +19714,7 @@ function packStyle(lock, styleId, manifestDir, options = {}) {
|
|
|
19598
19714
|
}
|
|
19599
19715
|
const inputs2 = Object.entries(options.sourceOverrides).map(([id, source]) => ({
|
|
19600
19716
|
id,
|
|
19601
|
-
path:
|
|
19717
|
+
path: path41.resolve(source)
|
|
19602
19718
|
}));
|
|
19603
19719
|
if (!inputs2.length) throw new Error(`No approved quality outputs for style "${styleId}".`);
|
|
19604
19720
|
const packed2 = packSprites(inputs2, options);
|
|
@@ -19622,7 +19738,7 @@ function packStyle(lock, styleId, manifestDir, options = {}) {
|
|
|
19622
19738
|
let outputs = resolveEntryOutputs(entry, id, manifestDir);
|
|
19623
19739
|
const source = sources[id];
|
|
19624
19740
|
if (source && outputs.length <= 1) {
|
|
19625
|
-
inputs.push({ id, path:
|
|
19741
|
+
inputs.push({ id, path: path41.resolve(manifestDir, source) });
|
|
19626
19742
|
continue;
|
|
19627
19743
|
}
|
|
19628
19744
|
if (source && sourceIsStem(source, entry, manifestDir)) {
|
|
@@ -19656,7 +19772,7 @@ function packStyle(lock, styleId, manifestDir, options = {}) {
|
|
|
19656
19772
|
}
|
|
19657
19773
|
}
|
|
19658
19774
|
for (const [id, source] of Object.entries(sources)) {
|
|
19659
|
-
if (!locked.has(id)) inputs.push({ id, path:
|
|
19775
|
+
if (!locked.has(id)) inputs.push({ id, path: path41.resolve(manifestDir, source) });
|
|
19660
19776
|
}
|
|
19661
19777
|
if (!inputs.length) {
|
|
19662
19778
|
throw new Error(`No readable sprites for style "${styleId}"; ${noOutput.length} skipped.`);
|
|
@@ -19693,7 +19809,7 @@ function mountSprites(placements, options) {
|
|
|
19693
19809
|
for (const p of placements) {
|
|
19694
19810
|
let sourceHash = null;
|
|
19695
19811
|
try {
|
|
19696
|
-
const bytes =
|
|
19812
|
+
const bytes = readFileSync6(p.path);
|
|
19697
19813
|
sourceHash = sha256(bytes);
|
|
19698
19814
|
const png = decodePng(bytes);
|
|
19699
19815
|
if (png.width > cellWidth || png.height > cellHeight) {
|
|
@@ -19764,7 +19880,7 @@ function mountStyle(lock, styleId, manifestDir, mount, cells, sources = {}, outp
|
|
|
19764
19880
|
const source = sources[id];
|
|
19765
19881
|
const entry = lock.entries[`${styleId}/${id}`];
|
|
19766
19882
|
if (source && !(entry && sourceIsStem(source, entry, manifestDir))) {
|
|
19767
|
-
placements.push({ id, path:
|
|
19883
|
+
placements.push({ id, path: path41.resolve(manifestDir, source), cell });
|
|
19768
19884
|
continue;
|
|
19769
19885
|
}
|
|
19770
19886
|
if (source && entry) {
|
|
@@ -19797,7 +19913,7 @@ function mountStyle(lock, styleId, manifestDir, mount, cells, sources = {}, outp
|
|
|
19797
19913
|
`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}`
|
|
19798
19914
|
);
|
|
19799
19915
|
}
|
|
19800
|
-
const basePng = mount.base ?
|
|
19916
|
+
const basePng = mount.base ? readFileSync6(path41.resolve(manifestDir, mount.base)) : void 0;
|
|
19801
19917
|
const mounted = mountSprites(placements, {
|
|
19802
19918
|
cellWidth: mount.cellWidth,
|
|
19803
19919
|
cellHeight: mount.cellHeight,
|
|
@@ -19807,7 +19923,7 @@ function mountStyle(lock, styleId, manifestDir, mount, cells, sources = {}, outp
|
|
|
19807
19923
|
...mounted,
|
|
19808
19924
|
sources: mount.base ? [{
|
|
19809
19925
|
id: "$base",
|
|
19810
|
-
path:
|
|
19926
|
+
path: path41.resolve(manifestDir, mount.base),
|
|
19811
19927
|
sha256: sha256(basePng),
|
|
19812
19928
|
included: true
|
|
19813
19929
|
}, ...mounted.sources] : mounted.sources,
|
|
@@ -20211,12 +20327,12 @@ async function runPack(args) {
|
|
|
20211
20327
|
if (args.primaryOnly || args.outputRoles.length) {
|
|
20212
20328
|
throw new Error("--primary-only and --output-role require manifest-driven pack");
|
|
20213
20329
|
}
|
|
20214
|
-
const raw = JSON.parse(await readFile28(
|
|
20330
|
+
const raw = JSON.parse(await readFile28(path42.resolve(args.inputs), "utf8"));
|
|
20215
20331
|
const inputs = resolvePackInputs(raw, args.inputs);
|
|
20216
20332
|
const { png, atlas, skipped, sources } = packSprites(inputs, { columns: args.columns });
|
|
20217
20333
|
const format = sheetFormat(args);
|
|
20218
|
-
const base =
|
|
20219
|
-
const { extension, document } = renderSheetDocument(format, atlas, { imageName:
|
|
20334
|
+
const base = path42.resolve(args.out.replace(/\.(?:png|json|tres)$/i, ""));
|
|
20335
|
+
const { extension, document } = renderSheetDocument(format, atlas, { imageName: path42.basename(`${base}.png`) });
|
|
20220
20336
|
const outputs = [
|
|
20221
20337
|
{ path: `${base}.png`, data: png },
|
|
20222
20338
|
{ path: `${base}${extension}`, data: document }
|
|
@@ -20229,7 +20345,7 @@ async function runPack(args) {
|
|
|
20229
20345
|
log(
|
|
20230
20346
|
` ${atlas.frames.length} sprite(s), ${atlas.sheet.width}x${atlas.sheet.height} in ${atlas.columns} column(s), ${(png.length / 1024).toFixed(1)} KB (${format})`
|
|
20231
20347
|
);
|
|
20232
|
-
log(` ${
|
|
20348
|
+
log(` ${path42.relative(process.cwd(), base)}.png + ${extension} + .pixelkiln.json`);
|
|
20233
20349
|
for (const s of skipped) log(` skipped ${s.id}: ${s.reason}`);
|
|
20234
20350
|
return;
|
|
20235
20351
|
}
|
|
@@ -20237,7 +20353,7 @@ async function runPack(args) {
|
|
|
20237
20353
|
if (args.primaryOnly && args.outputRoles.length) {
|
|
20238
20354
|
throw new Error("pack accepts either --primary-only or --output-role, not both");
|
|
20239
20355
|
}
|
|
20240
|
-
const manifestDir =
|
|
20356
|
+
const manifestDir = path42.dirname(path42.resolve(args.manifest));
|
|
20241
20357
|
const styleIds = args.styles.length ? args.styles : Object.keys(loaded.manifest.styles);
|
|
20242
20358
|
for (const styleId of styleIds) {
|
|
20243
20359
|
const style = loaded.manifest.styles[styleId];
|
|
@@ -20252,9 +20368,9 @@ async function runPack(args) {
|
|
|
20252
20368
|
sourceOverrides: qualitySources,
|
|
20253
20369
|
sources: manifestSources(loaded.manifest, styleId)
|
|
20254
20370
|
});
|
|
20255
|
-
const base = args.out ?
|
|
20371
|
+
const base = args.out ? path42.resolve(args.out.replace(/\.(?:png|json|tres)$/i, "")) : path42.resolve(manifestDir, style.outDir, `${styleId}-sheet`);
|
|
20256
20372
|
const format = sheetFormat(args);
|
|
20257
|
-
const { extension, document } = renderSheetDocument(format, atlas, { imageName:
|
|
20373
|
+
const { extension, document } = renderSheetDocument(format, atlas, { imageName: path42.basename(`${base}.png`) });
|
|
20258
20374
|
const outputs = [
|
|
20259
20375
|
{ path: `${base}.png`, data: png },
|
|
20260
20376
|
{ path: `${base}${extension}`, data: document }
|
|
@@ -20286,13 +20402,13 @@ async function runPack(args) {
|
|
|
20286
20402
|
log(
|
|
20287
20403
|
` ${styleId}: ${atlas.frames.length} sprite(s), ${atlas.sheet.width}x${atlas.sheet.height} in ${atlas.columns} column(s)` + (atlas.sets?.length ? `, ${atlas.sets.length} set(s)` : "") + ` (${format})`
|
|
20288
20404
|
);
|
|
20289
|
-
log(` ${
|
|
20405
|
+
log(` ${path42.relative(process.cwd(), base)}.png + ${extension} + .pixelkiln.json`);
|
|
20290
20406
|
for (const s of skipped) log(` skipped ${s.id}: ${s.reason}`);
|
|
20291
20407
|
}
|
|
20292
20408
|
}
|
|
20293
20409
|
async function runMount(args) {
|
|
20294
20410
|
const { loaded, lock } = await openProject2(args);
|
|
20295
|
-
const manifestDir =
|
|
20411
|
+
const manifestDir = path42.dirname(path42.resolve(args.manifest));
|
|
20296
20412
|
const styleIds = args.styles.length ? args.styles : Object.keys(loaded.manifest.styles);
|
|
20297
20413
|
for (const styleId of styleIds) {
|
|
20298
20414
|
const style = loaded.manifest.styles[styleId];
|
|
@@ -20331,9 +20447,9 @@ async function runMount(args) {
|
|
|
20331
20447
|
sources,
|
|
20332
20448
|
outputRoles
|
|
20333
20449
|
);
|
|
20334
|
-
const out =
|
|
20450
|
+
const out = path42.resolve(manifestDir, style.mount.out);
|
|
20335
20451
|
const format = sheetFormat(args);
|
|
20336
|
-
const { extension, document } = renderSheetDocument(format, atlas, { imageName:
|
|
20452
|
+
const { extension, document } = renderSheetDocument(format, atlas, { imageName: path42.basename(out) });
|
|
20337
20453
|
const metadata = out.replace(/\.png$/, "") + extension;
|
|
20338
20454
|
const companion = out.replace(/\.png$/, "") + ".pixelkiln.json";
|
|
20339
20455
|
const outputs = [
|
|
@@ -20353,7 +20469,7 @@ async function runMount(args) {
|
|
|
20353
20469
|
await provenanceFile("$lock", args.lock),
|
|
20354
20470
|
...qualityRecords,
|
|
20355
20471
|
...artifactSources.filter(
|
|
20356
|
-
(source) => source.id !== "$base" ||
|
|
20472
|
+
(source) => source.id !== "$base" || path42.resolve(source.path) !== out
|
|
20357
20473
|
)
|
|
20358
20474
|
],
|
|
20359
20475
|
options: {
|
|
@@ -20367,14 +20483,14 @@ async function runMount(args) {
|
|
|
20367
20483
|
log(
|
|
20368
20484
|
` ${styleId}: ${atlas.frames.length} cell(s) into ${atlas.sheet.width}x${atlas.sheet.height}` + (overBase ? ` over ${style.mount.base}` : " (new sheet)")
|
|
20369
20485
|
);
|
|
20370
|
-
log(` ${
|
|
20486
|
+
log(` ${path42.relative(process.cwd(), out)} + atlas/provenance JSON`);
|
|
20371
20487
|
for (const s of skipped) log(` skipped ${s.id}: ${s.reason}`);
|
|
20372
20488
|
}
|
|
20373
20489
|
}
|
|
20374
20490
|
async function runExport(args) {
|
|
20375
20491
|
const { loaded, specs, lock } = await openProject2(args);
|
|
20376
20492
|
const format = args.format ?? "generic";
|
|
20377
|
-
const manifestDir =
|
|
20493
|
+
const manifestDir = path42.dirname(path42.resolve(args.manifest));
|
|
20378
20494
|
const selected = specs.filter((spec) => {
|
|
20379
20495
|
if (spec.generator !== "tiles" && spec.generator !== "terrain") return false;
|
|
20380
20496
|
if (args.styles.length && !args.styles.includes(spec.styleId)) return false;
|
|
@@ -20390,12 +20506,12 @@ async function runExport(args) {
|
|
|
20390
20506
|
for (const spec of selected) {
|
|
20391
20507
|
const entry = lock.entries[lockKey(spec.styleId, spec.assetId)];
|
|
20392
20508
|
const style = loaded.manifest.styles[spec.styleId];
|
|
20393
|
-
const defaultBase =
|
|
20394
|
-
const base = args.out ?
|
|
20509
|
+
const defaultBase = path42.resolve(manifestDir, style.outDir, `${spec.assetId}-tileset`);
|
|
20510
|
+
const base = args.out ? path42.resolve(args.out.replace(/\.(?:png|json|tsj|tres)$/i, "")) : defaultBase;
|
|
20395
20511
|
const result = exportTileset(entry, spec, {
|
|
20396
20512
|
format,
|
|
20397
20513
|
manifestDir,
|
|
20398
|
-
imageName:
|
|
20514
|
+
imageName: path42.basename(`${base}.png`),
|
|
20399
20515
|
columns: args.columns
|
|
20400
20516
|
});
|
|
20401
20517
|
const outputs = [
|
|
@@ -20413,7 +20529,7 @@ async function runExport(args) {
|
|
|
20413
20529
|
asset: spec.assetId,
|
|
20414
20530
|
columns: args.columns ?? null,
|
|
20415
20531
|
format,
|
|
20416
|
-
image:
|
|
20532
|
+
image: path42.basename(`${base}.png`),
|
|
20417
20533
|
providerRules: result.generic.providerRules,
|
|
20418
20534
|
style: spec.styleId,
|
|
20419
20535
|
tileType: spec.tileType ?? null
|
|
@@ -20423,7 +20539,7 @@ async function runExport(args) {
|
|
|
20423
20539
|
` ${spec.styleId}/${spec.assetId}: ${result.generic.tiles.length} tile(s), ${result.generic.sheet.width}x${result.generic.sheet.height} (${format})`
|
|
20424
20540
|
);
|
|
20425
20541
|
log(
|
|
20426
|
-
` ${
|
|
20542
|
+
` ${path42.relative(process.cwd(), base)}.png + ${path42.basename(base)}${result.extension} + .pixelkiln.json`
|
|
20427
20543
|
);
|
|
20428
20544
|
}
|
|
20429
20545
|
}
|
|
@@ -20436,21 +20552,21 @@ function sheetFormat(args) {
|
|
|
20436
20552
|
}
|
|
20437
20553
|
|
|
20438
20554
|
// src/cli/commands/pixellab-utils.ts
|
|
20439
|
-
import
|
|
20555
|
+
import path44 from "path";
|
|
20440
20556
|
|
|
20441
20557
|
// src/pixellab-utils.ts
|
|
20442
20558
|
import { existsSync as existsSync33 } from "fs";
|
|
20443
20559
|
import { mkdir as mkdir13, readFile as readFile29, writeFile as writeFile18 } from "fs/promises";
|
|
20444
|
-
import
|
|
20560
|
+
import path43 from "path";
|
|
20445
20561
|
var UNZOOM_MIN_SIDE = 256;
|
|
20446
20562
|
var UNZOOM_MAX_AREA = 2048 * 2048;
|
|
20447
20563
|
function defaultUnzoomOut(input) {
|
|
20448
|
-
const ext =
|
|
20564
|
+
const ext = path43.extname(input);
|
|
20449
20565
|
return `${ext ? input.slice(0, -ext.length) : input}.unzoomed.png`;
|
|
20450
20566
|
}
|
|
20451
20567
|
async function unzoomFile(client2, input, opts = {}) {
|
|
20452
|
-
const out =
|
|
20453
|
-
if (
|
|
20568
|
+
const out = path43.resolve(opts.out ?? defaultUnzoomOut(input));
|
|
20569
|
+
if (path43.resolve(input) === out) throw new Error("unzoom will not overwrite its own input; pass a different --out");
|
|
20454
20570
|
if (existsSync33(out) && !opts.force) throw new Error(`${out} already exists; pass --force to replace it`);
|
|
20455
20571
|
const bytes = await readFile29(input);
|
|
20456
20572
|
const metadata = imageMetadata(bytes);
|
|
@@ -20465,7 +20581,7 @@ async function unzoomFile(client2, input, opts = {}) {
|
|
|
20465
20581
|
throw new Error(`${input} is ${width}x${height}; PixelLab's unzoom takes at most 2048x2048 worth of pixels`);
|
|
20466
20582
|
}
|
|
20467
20583
|
const res = await client2.unzoom({ image: { base64: bytes.toString("base64"), format }, quantize: opts.quantize });
|
|
20468
|
-
await mkdir13(
|
|
20584
|
+
await mkdir13(path43.dirname(out), { recursive: true });
|
|
20469
20585
|
await writeFile18(out, res.png);
|
|
20470
20586
|
return { out, original: res.originalSize, unzoomed: res.unzoomedSize, zoomFactor: res.zoomFactor, usage: res.usage };
|
|
20471
20587
|
}
|
|
@@ -20473,7 +20589,7 @@ var FONT_WEIGHTS = ["Bold", "Regular"];
|
|
|
20473
20589
|
var FONT_GLYPH_SIZES = [8, 16, 32, 64];
|
|
20474
20590
|
var FONT_COST_GENERATIONS = 25;
|
|
20475
20591
|
function fontOutputPaths(out) {
|
|
20476
|
-
const base =
|
|
20592
|
+
const base = path43.resolve(out.replace(/\.(ttf|png)$/i, ""));
|
|
20477
20593
|
return { ttf: `${base}.ttf`, atlas: `${base}.png` };
|
|
20478
20594
|
}
|
|
20479
20595
|
async function generateFont(client2, opts) {
|
|
@@ -20507,7 +20623,7 @@ async function generateFont(client2, opts) {
|
|
|
20507
20623
|
client2.download(job.download_ttf_url),
|
|
20508
20624
|
client2.download(job.download_atlas_url)
|
|
20509
20625
|
]);
|
|
20510
|
-
await mkdir13(
|
|
20626
|
+
await mkdir13(path43.dirname(ttf), { recursive: true });
|
|
20511
20627
|
await writeFile18(ttf, ttfBytes);
|
|
20512
20628
|
await writeFile18(atlas, atlasBytes);
|
|
20513
20629
|
return { jobId, ttf, atlas, glyphPx: job.glyph_px ?? null, usage: job.usage };
|
|
@@ -20523,7 +20639,7 @@ async function generateFont(client2, opts) {
|
|
|
20523
20639
|
|
|
20524
20640
|
// src/cli/commands/pixellab-utils.ts
|
|
20525
20641
|
function loadKeys(args) {
|
|
20526
|
-
loadEnvFiles(
|
|
20642
|
+
loadEnvFiles(path44.dirname(path44.resolve(args.manifest)));
|
|
20527
20643
|
loadEnvFiles(process.cwd());
|
|
20528
20644
|
}
|
|
20529
20645
|
async function runUnzoom(args) {
|
|
@@ -20589,11 +20705,11 @@ async function runFont(args) {
|
|
|
20589
20705
|
}
|
|
20590
20706
|
|
|
20591
20707
|
// src/cli/commands/quality.ts
|
|
20592
|
-
import
|
|
20708
|
+
import path46 from "path";
|
|
20593
20709
|
import { readFile as readFile31 } from "fs/promises";
|
|
20594
20710
|
|
|
20595
20711
|
// src/pipeline/quality-regression.ts
|
|
20596
|
-
import
|
|
20712
|
+
import path45 from "path";
|
|
20597
20713
|
import { existsSync as existsSync34 } from "fs";
|
|
20598
20714
|
import { readFile as readFile30 } from "fs/promises";
|
|
20599
20715
|
import { z as z13 } from "zod";
|
|
@@ -20601,7 +20717,7 @@ var SHA256_RE = /^[0-9a-f]{64}$/;
|
|
|
20601
20717
|
var HEX_RE = /^#[0-9a-f]{6}$/;
|
|
20602
20718
|
var MAX_REDMEAN_DISTANCE = 765;
|
|
20603
20719
|
var QualityPathSchema = z13.string().min(1).refine(
|
|
20604
|
-
(value) => !
|
|
20720
|
+
(value) => !path45.posix.isAbsolute(value) && !value.includes("\\"),
|
|
20605
20721
|
"expected a portable relative path"
|
|
20606
20722
|
);
|
|
20607
20723
|
var DEFAULT_QUALITY_TOLERANCES = {
|
|
@@ -20748,7 +20864,7 @@ function measureDecodedImageQuality(png, digest2) {
|
|
|
20748
20864
|
};
|
|
20749
20865
|
}
|
|
20750
20866
|
async function measureImageQuality(file) {
|
|
20751
|
-
const absolute =
|
|
20867
|
+
const absolute = path45.resolve(file);
|
|
20752
20868
|
let bytes;
|
|
20753
20869
|
try {
|
|
20754
20870
|
bytes = await readFile30(absolute);
|
|
@@ -20770,11 +20886,11 @@ function resolveQualityInputs(raw, inputsFilePath) {
|
|
|
20770
20886
|
}
|
|
20771
20887
|
const ids = parsed.data.map((entry) => entry.id);
|
|
20772
20888
|
if (new Set(ids).size !== ids.length) throw new Error("--inputs contains a duplicate quality case id.");
|
|
20773
|
-
const root =
|
|
20889
|
+
const root = path45.dirname(path45.resolve(inputsFilePath));
|
|
20774
20890
|
return parsed.data.map((entry) => ({
|
|
20775
20891
|
id: entry.id,
|
|
20776
|
-
file:
|
|
20777
|
-
...entry.record ? { record:
|
|
20892
|
+
file: path45.resolve(root, entry.path),
|
|
20893
|
+
...entry.record ? { record: path45.resolve(root, entry.record) } : {},
|
|
20778
20894
|
tolerances: QualityToleranceSchema.parse({
|
|
20779
20895
|
...DEFAULT_QUALITY_TOLERANCES,
|
|
20780
20896
|
...entry.tolerances
|
|
@@ -20782,7 +20898,7 @@ function resolveQualityInputs(raw, inputsFilePath) {
|
|
|
20782
20898
|
}));
|
|
20783
20899
|
}
|
|
20784
20900
|
function portableRelative2(from, to) {
|
|
20785
|
-
return
|
|
20901
|
+
return path45.relative(from, path45.resolve(to)).split(path45.sep).join("/") || ".";
|
|
20786
20902
|
}
|
|
20787
20903
|
async function verifyRecordForImage(recordPath, imagePath) {
|
|
20788
20904
|
const verification = await verifyArtifactBundle(recordPath);
|
|
@@ -20797,20 +20913,20 @@ async function verifyRecordForImage(recordPath, imagePath) {
|
|
|
20797
20913
|
if (record.kind !== "refine" || options === null || typeof options !== "object" || options.schema !== "pixelkiln-quality") {
|
|
20798
20914
|
throw new Error(`Quality record ${recordPath} is not a PixelKiln refinement record.`);
|
|
20799
20915
|
}
|
|
20800
|
-
const outputs = record.outputs.map((output) =>
|
|
20801
|
-
if (!outputs.includes(
|
|
20916
|
+
const outputs = record.outputs.map((output) => path45.resolve(path45.dirname(recordPath), output.path));
|
|
20917
|
+
if (!outputs.includes(path45.resolve(imagePath))) {
|
|
20802
20918
|
throw new Error(`Quality record ${recordPath} does not own ${imagePath}.`);
|
|
20803
20919
|
}
|
|
20804
20920
|
}
|
|
20805
20921
|
async function snapshotQualityBaseline(inputs, baselinePath, options = {}) {
|
|
20806
20922
|
if (!inputs.length) throw new Error("Cannot snapshot an empty quality baseline.");
|
|
20807
|
-
const absolute =
|
|
20923
|
+
const absolute = path45.resolve(baselinePath);
|
|
20808
20924
|
for (const input of inputs) {
|
|
20809
|
-
if (
|
|
20925
|
+
if (path45.resolve(input.file) === absolute || input.record && path45.resolve(input.record) === absolute) {
|
|
20810
20926
|
throw new Error(`Quality baseline output would overwrite an input: ${absolute}.`);
|
|
20811
20927
|
}
|
|
20812
20928
|
}
|
|
20813
|
-
const root =
|
|
20929
|
+
const root = path45.dirname(absolute);
|
|
20814
20930
|
const cases = [];
|
|
20815
20931
|
for (const input of [...inputs].sort((a, b) => a.id.localeCompare(b.id))) {
|
|
20816
20932
|
const expected = await measureImageQuality(input.file);
|
|
@@ -20850,7 +20966,7 @@ async function snapshotQualityBaseline(inputs, baselinePath, options = {}) {
|
|
|
20850
20966
|
return { baseline, path: absolute, changed: result.changed.length > 0 };
|
|
20851
20967
|
}
|
|
20852
20968
|
async function readQualityBaseline(baselinePath) {
|
|
20853
|
-
const absolute =
|
|
20969
|
+
const absolute = path45.resolve(baselinePath);
|
|
20854
20970
|
let raw;
|
|
20855
20971
|
try {
|
|
20856
20972
|
raw = JSON.parse(await readFile30(absolute, "utf8"));
|
|
@@ -20923,12 +21039,12 @@ function regressionViolations(expected, actual, tolerances) {
|
|
|
20923
21039
|
return { violations, warnings };
|
|
20924
21040
|
}
|
|
20925
21041
|
async function checkQualityBaseline(baselinePath) {
|
|
20926
|
-
const absolute =
|
|
21042
|
+
const absolute = path45.resolve(baselinePath);
|
|
20927
21043
|
const baseline = await readQualityBaseline(absolute);
|
|
20928
|
-
const root =
|
|
21044
|
+
const root = path45.dirname(absolute);
|
|
20929
21045
|
const cases = [];
|
|
20930
21046
|
for (const entry of baseline.cases) {
|
|
20931
|
-
const file =
|
|
21047
|
+
const file = path45.resolve(root, entry.file);
|
|
20932
21048
|
let actual = null;
|
|
20933
21049
|
const violations = [];
|
|
20934
21050
|
const warnings = [];
|
|
@@ -20941,7 +21057,7 @@ async function checkQualityBaseline(baselinePath) {
|
|
|
20941
21057
|
violations.push(message4(error));
|
|
20942
21058
|
}
|
|
20943
21059
|
if (entry.record) {
|
|
20944
|
-
const recordPath =
|
|
21060
|
+
const recordPath = path45.resolve(root, entry.record.path);
|
|
20945
21061
|
try {
|
|
20946
21062
|
const recordHash = await sha256File(recordPath);
|
|
20947
21063
|
if (recordHash !== entry.record.sha256) violations.push("quality record hash changed");
|
|
@@ -20983,15 +21099,15 @@ async function runQuality(args) {
|
|
|
20983
21099
|
if (!args.out) throw new Error("quality snapshot needs --out <pixelkiln.quality.json>.");
|
|
20984
21100
|
let raw;
|
|
20985
21101
|
try {
|
|
20986
|
-
raw = JSON.parse(await readFile31(
|
|
21102
|
+
raw = JSON.parse(await readFile31(path46.resolve(args.inputs), "utf8"));
|
|
20987
21103
|
} catch (error) {
|
|
20988
21104
|
throw new Error(
|
|
20989
|
-
`Could not read quality inputs ${
|
|
21105
|
+
`Could not read quality inputs ${path46.resolve(args.inputs)}: ${error instanceof Error ? error.message : String(error)}`,
|
|
20990
21106
|
{ cause: error }
|
|
20991
21107
|
);
|
|
20992
21108
|
}
|
|
20993
21109
|
const inputs = resolveQualityInputs(raw, args.inputs);
|
|
20994
|
-
if (
|
|
21110
|
+
if (path46.resolve(args.inputs) === path46.resolve(args.out)) {
|
|
20995
21111
|
throw new Error("quality snapshot --out must not overwrite its --inputs file.");
|
|
20996
21112
|
}
|
|
20997
21113
|
const result = await snapshotQualityBaseline(inputs, args.out, { force: args.force });
|
|
@@ -21004,7 +21120,7 @@ async function runQuality(args) {
|
|
|
21004
21120
|
}, null, 2));
|
|
21005
21121
|
} else {
|
|
21006
21122
|
log(
|
|
21007
|
-
` ${result.changed ? "wrote" : "unchanged"} ${
|
|
21123
|
+
` ${result.changed ? "wrote" : "unchanged"} ${path46.relative(process.cwd(), result.path)} (${result.baseline.cases.length} case(s))`
|
|
21008
21124
|
);
|
|
21009
21125
|
log(` Review the tolerances, commit the baseline, then run quality check in CI.`);
|
|
21010
21126
|
}
|
|
@@ -21030,10 +21146,10 @@ async function runQuality(args) {
|
|
|
21030
21146
|
}
|
|
21031
21147
|
|
|
21032
21148
|
// src/cli/commands/recipe.ts
|
|
21033
|
-
import
|
|
21149
|
+
import path48 from "path";
|
|
21034
21150
|
|
|
21035
21151
|
// src/recipes.ts
|
|
21036
|
-
import
|
|
21152
|
+
import path47 from "path";
|
|
21037
21153
|
import { existsSync as existsSync35 } from "fs";
|
|
21038
21154
|
import { readdir as readdir3, readFile as readFile32 } from "fs/promises";
|
|
21039
21155
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
@@ -21044,7 +21160,7 @@ var VERSION_RE = /^\d+\.\d+\.\d+$/;
|
|
|
21044
21160
|
var RECIPE_FILE = "pixelkiln.recipe.json";
|
|
21045
21161
|
var RECIPE_DIR_TOKEN = "{{recipeDir}}";
|
|
21046
21162
|
var PortablePathSchema = z14.string().min(1).refine((value) => {
|
|
21047
|
-
if (value.includes("\\") ||
|
|
21163
|
+
if (value.includes("\\") || path47.posix.isAbsolute(value)) return false;
|
|
21048
21164
|
const parts = value.split("/");
|
|
21049
21165
|
return !parts.includes("") && !parts.includes(".") && !parts.includes("..");
|
|
21050
21166
|
}, "expected a portable relative path without '.', '..', or backslashes");
|
|
@@ -21220,7 +21336,7 @@ function message5(error) {
|
|
|
21220
21336
|
return error instanceof Error ? error.message : String(error);
|
|
21221
21337
|
}
|
|
21222
21338
|
async function readRecipeFile(recipePath, bundled) {
|
|
21223
|
-
const absolute =
|
|
21339
|
+
const absolute = path47.resolve(recipePath);
|
|
21224
21340
|
let raw;
|
|
21225
21341
|
try {
|
|
21226
21342
|
raw = JSON.parse(await readFile32(absolute, "utf8"));
|
|
@@ -21234,13 +21350,13 @@ async function readRecipeFile(recipePath, bundled) {
|
|
|
21234
21350
|
` + parsed.error.issues.map((issue) => ` ${issue.path.join(".") || "$"}: ${issue.message}`).join("\n")
|
|
21235
21351
|
);
|
|
21236
21352
|
}
|
|
21237
|
-
return { recipe: parsed.data, authored: raw, path: absolute, dir:
|
|
21353
|
+
return { recipe: parsed.data, authored: raw, path: absolute, dir: path47.dirname(absolute), bundled };
|
|
21238
21354
|
}
|
|
21239
21355
|
async function walkRecipeFiles(root) {
|
|
21240
21356
|
if (!existsSync35(root)) return [];
|
|
21241
21357
|
const out = [];
|
|
21242
21358
|
for (const entry of await readdir3(root, { withFileTypes: true })) {
|
|
21243
|
-
const resolved =
|
|
21359
|
+
const resolved = path47.join(root, entry.name);
|
|
21244
21360
|
if (entry.isDirectory()) out.push(...await walkRecipeFiles(resolved));
|
|
21245
21361
|
else if (entry.isFile() && entry.name === RECIPE_FILE) out.push(resolved);
|
|
21246
21362
|
}
|
|
@@ -21251,7 +21367,7 @@ function bundledRecipeRoot() {
|
|
|
21251
21367
|
}
|
|
21252
21368
|
async function listBundledRecipes(root = bundledRecipeRoot()) {
|
|
21253
21369
|
const recipes = await Promise.all(
|
|
21254
|
-
(await walkRecipeFiles(
|
|
21370
|
+
(await walkRecipeFiles(path47.resolve(root))).map((file) => readRecipeFile(file, true))
|
|
21255
21371
|
);
|
|
21256
21372
|
return recipes.sort(
|
|
21257
21373
|
(a, b) => a.recipe.id.localeCompare(b.recipe.id) || compareVersions(b.recipe.version, a.recipe.version)
|
|
@@ -21274,9 +21390,9 @@ function parseSelector(selector) {
|
|
|
21274
21390
|
return { id: match[1], version: match[2] };
|
|
21275
21391
|
}
|
|
21276
21392
|
async function resolveRecipe(target, bundledRoot = bundledRecipeRoot()) {
|
|
21277
|
-
const local =
|
|
21393
|
+
const local = path47.resolve(target);
|
|
21278
21394
|
if (existsSync35(local)) {
|
|
21279
|
-
const recipePath =
|
|
21395
|
+
const recipePath = path47.basename(local) === RECIPE_FILE ? local : path47.join(local, RECIPE_FILE);
|
|
21280
21396
|
return readRecipeFile(recipePath, false);
|
|
21281
21397
|
}
|
|
21282
21398
|
if (target.startsWith(".") || target.startsWith("/") || target.includes("\\")) {
|
|
@@ -21290,7 +21406,7 @@ async function resolveRecipe(target, bundledRoot = bundledRecipeRoot()) {
|
|
|
21290
21406
|
return matches[0];
|
|
21291
21407
|
}
|
|
21292
21408
|
async function verifyFile(root, file) {
|
|
21293
|
-
const absolute =
|
|
21409
|
+
const absolute = path47.resolve(root, ...file.path.split("/"));
|
|
21294
21410
|
if (!existsSync35(absolute)) {
|
|
21295
21411
|
return { path: file.path, expectedSha256: file.sha256, actualSha256: null, status: "missing" };
|
|
21296
21412
|
}
|
|
@@ -21306,7 +21422,7 @@ async function verifyRecipe(target, options = {}) {
|
|
|
21306
21422
|
const loaded = await resolveRecipe(target, options.bundledRoot);
|
|
21307
21423
|
const actualIntegrity = recipeDigest(loaded.authored);
|
|
21308
21424
|
const files = await Promise.all(loaded.recipe.files.map((file) => verifyFile(loaded.dir, file)));
|
|
21309
|
-
const modelRoot = options.modelRoot ?
|
|
21425
|
+
const modelRoot = options.modelRoot ? path47.resolve(options.modelRoot) : null;
|
|
21310
21426
|
const models = modelRoot ? await Promise.all(loaded.recipe.models.map(async (model) => ({
|
|
21311
21427
|
...await verifyFile(modelRoot, model),
|
|
21312
21428
|
source: model.source,
|
|
@@ -21340,7 +21456,7 @@ async function verifyRecipe(target, options = {}) {
|
|
|
21340
21456
|
};
|
|
21341
21457
|
}
|
|
21342
21458
|
function renderStyle(style, destination, cwd) {
|
|
21343
|
-
const portableDir =
|
|
21459
|
+
const portableDir = path47.relative(cwd, destination).split(path47.sep).join("/") || ".";
|
|
21344
21460
|
const replace = (value) => {
|
|
21345
21461
|
if (typeof value === "string") return value.replaceAll(RECIPE_DIR_TOKEN, portableDir);
|
|
21346
21462
|
if (Array.isArray(value)) return value.map(replace);
|
|
@@ -21352,19 +21468,19 @@ function renderStyle(style, destination, cwd) {
|
|
|
21352
21468
|
return replace(style);
|
|
21353
21469
|
}
|
|
21354
21470
|
async function installRecipe(target, options = {}) {
|
|
21355
|
-
const cwd =
|
|
21471
|
+
const cwd = path47.resolve(options.cwd ?? process.cwd());
|
|
21356
21472
|
const loaded = await resolveRecipe(target, options.bundledRoot);
|
|
21357
21473
|
const verification = await verifyRecipe(loaded.path);
|
|
21358
21474
|
if (!verification.ok) {
|
|
21359
21475
|
throw new Error(`Recipe ${loaded.recipe.id}@${loaded.recipe.version} failed integrity verification.`);
|
|
21360
21476
|
}
|
|
21361
|
-
const destination = options.out ?
|
|
21477
|
+
const destination = options.out ? path47.resolve(cwd, options.out) : path47.join(cwd, "pixelkiln-recipes", ...loaded.recipe.id.split("/"), loaded.recipe.version);
|
|
21362
21478
|
const sources = [
|
|
21363
21479
|
{ path: RECIPE_FILE, source: loaded.path },
|
|
21364
|
-
...loaded.recipe.files.map((file) => ({ path: file.path, source:
|
|
21480
|
+
...loaded.recipe.files.map((file) => ({ path: file.path, source: path47.join(loaded.dir, ...file.path.split("/")) }))
|
|
21365
21481
|
];
|
|
21366
21482
|
const files = await Promise.all(sources.map(async (file) => ({
|
|
21367
|
-
path:
|
|
21483
|
+
path: path47.join(destination, ...file.path.split("/")),
|
|
21368
21484
|
data: await readFile32(file.source)
|
|
21369
21485
|
})));
|
|
21370
21486
|
if (!options.force) {
|
|
@@ -21464,14 +21580,14 @@ async function runRecipe(args) {
|
|
|
21464
21580
|
}, null, 2));
|
|
21465
21581
|
} else {
|
|
21466
21582
|
log(` installed ${result.recipe.id}@${result.recipe.version}`);
|
|
21467
|
-
log(` ${
|
|
21583
|
+
log(` ${path48.relative(process.cwd(), result.destination) || "."}`);
|
|
21468
21584
|
log(`
|
|
21469
21585
|
Add this entry under your manifest's styles object:`);
|
|
21470
21586
|
log(JSON.stringify({ [result.styleId]: result.style }, null, 2));
|
|
21471
21587
|
if (result.recipe.models.length) {
|
|
21472
21588
|
log(`
|
|
21473
21589
|
Models are not downloaded automatically. Verify them with:`);
|
|
21474
|
-
log(` pixelkiln recipe verify ${
|
|
21590
|
+
log(` pixelkiln recipe verify ${path48.relative(process.cwd(), result.destination)} --model-root <ComfyUI/models>`);
|
|
21475
21591
|
}
|
|
21476
21592
|
}
|
|
21477
21593
|
return;
|
|
@@ -21479,7 +21595,7 @@ async function runRecipe(args) {
|
|
|
21479
21595
|
}
|
|
21480
21596
|
|
|
21481
21597
|
// src/cli/commands/refine.ts
|
|
21482
|
-
import
|
|
21598
|
+
import path49 from "path";
|
|
21483
21599
|
async function runRefine(args) {
|
|
21484
21600
|
if (args.from || args.subcommand === "approve") {
|
|
21485
21601
|
if (!args.from) {
|
|
@@ -21509,12 +21625,12 @@ async function runRefine(args) {
|
|
|
21509
21625
|
` recovered ${result3.detection.columns}x${result3.detection.rows} native grid (${result3.detection.confidence}: ${result3.detection.consensus})`
|
|
21510
21626
|
);
|
|
21511
21627
|
log(` enforced ${result3.palette.length}-color palette without dithering`);
|
|
21512
|
-
log(` wrote ${
|
|
21513
|
-
log(` quality record: ${
|
|
21628
|
+
log(` wrote ${path49.relative(process.cwd(), result3.output)}`);
|
|
21629
|
+
log(` quality record: ${path49.relative(process.cwd(), result3.record)}`);
|
|
21514
21630
|
log(`
|
|
21515
21631
|
Automated checks passed. Human 1\xD7 review is still required:`);
|
|
21516
21632
|
log(
|
|
21517
|
-
` pixelkiln refine approve --from ${
|
|
21633
|
+
` pixelkiln refine approve --from ${path49.relative(process.cwd(), result3.record)} --reviewer "Your Name"`
|
|
21518
21634
|
);
|
|
21519
21635
|
}
|
|
21520
21636
|
return;
|
|
@@ -21542,7 +21658,7 @@ async function runRefine(args) {
|
|
|
21542
21658
|
if (args.json) log(JSON.stringify(result3, null, 2));
|
|
21543
21659
|
else {
|
|
21544
21660
|
log(
|
|
21545
|
-
result3.options.frameSet ? ` approved ${result3.outputs.length} frames in ${
|
|
21661
|
+
result3.options.frameSet ? ` approved ${result3.outputs.length} frames in ${path49.relative(process.cwd(), result3.record)} by ${args.reviewer.trim()}` : ` approved ${path49.relative(process.cwd(), result3.output)} by ${args.reviewer.trim()}`
|
|
21546
21662
|
);
|
|
21547
21663
|
}
|
|
21548
21664
|
return;
|
|
@@ -21551,7 +21667,7 @@ async function runRefine(args) {
|
|
|
21551
21667
|
if (args.json) {
|
|
21552
21668
|
log(JSON.stringify(result2, null, 2));
|
|
21553
21669
|
} else {
|
|
21554
|
-
log(` ${result2.safe ? "release-ready" : "not release-ready"}: ${
|
|
21670
|
+
log(` ${result2.safe ? "release-ready" : "not release-ready"}: ${path49.relative(process.cwd(), result2.output || result2.record)}`);
|
|
21555
21671
|
for (const reason of result2.reasons) log(` ${reason}`);
|
|
21556
21672
|
}
|
|
21557
21673
|
if (!result2.safe) process.exitCode = 1;
|
|
@@ -21597,7 +21713,7 @@ async function runRefine(args) {
|
|
|
21597
21713
|
log(` ${item.state.padEnd(16)} ${item.key} ${item.reason}`);
|
|
21598
21714
|
if (item.state === "needs-approval") {
|
|
21599
21715
|
log(
|
|
21600
|
-
` pixelkiln refine approve --from ${
|
|
21716
|
+
` pixelkiln refine approve --from ${path49.relative(process.cwd(), item.record)} --reviewer "Your Name"`
|
|
21601
21717
|
);
|
|
21602
21718
|
}
|
|
21603
21719
|
}
|
|
@@ -21608,7 +21724,7 @@ async function runRefine(args) {
|
|
|
21608
21724
|
}
|
|
21609
21725
|
|
|
21610
21726
|
// src/cli/commands/skeleton.ts
|
|
21611
|
-
import
|
|
21727
|
+
import path50 from "path";
|
|
21612
21728
|
import { existsSync as existsSync36 } from "fs";
|
|
21613
21729
|
import { readFile as readFile33, writeFile as writeFile19 } from "fs/promises";
|
|
21614
21730
|
|
|
@@ -21721,12 +21837,12 @@ var Canvas = class {
|
|
|
21721
21837
|
async function runEstimateSkeleton(args) {
|
|
21722
21838
|
const image = args.target;
|
|
21723
21839
|
if (!image) throw new UsageError("estimate-skeleton needs an image path: pixelkiln estimate-skeleton <image> [--out keypoints.json]");
|
|
21724
|
-
if (args.out && existsSync36(
|
|
21840
|
+
if (args.out && existsSync36(path50.resolve(args.out)) && !args.force) {
|
|
21725
21841
|
throw new UsageError(`${args.out} already exists; pass --force to replace it`);
|
|
21726
21842
|
}
|
|
21727
|
-
loadEnvFiles(
|
|
21843
|
+
loadEnvFiles(path50.dirname(path50.resolve(args.manifest)));
|
|
21728
21844
|
loadEnvFiles(process.cwd());
|
|
21729
|
-
const bytes = await readFile33(
|
|
21845
|
+
const bytes = await readFile33(path50.resolve(image));
|
|
21730
21846
|
const metadata = imageMetadata(bytes);
|
|
21731
21847
|
if (!metadata) throw new UsageError(`${image} is not a readable PNG or JPEG`);
|
|
21732
21848
|
const sizeProblem = estimateSkeletonSizeProblem(metadata.width, metadata.height);
|
|
@@ -21738,7 +21854,7 @@ async function runEstimateSkeleton(args) {
|
|
|
21738
21854
|
const frames = args.frames ?? DEFAULT_SCAFFOLD_FRAMES;
|
|
21739
21855
|
const json = JSON.stringify(scaffoldSkeletonSet(res.keypoints, frames), null, 2);
|
|
21740
21856
|
if (args.out) {
|
|
21741
|
-
await writeFile19(
|
|
21857
|
+
await writeFile19(path50.resolve(args.out), json + "\n");
|
|
21742
21858
|
log(` wrote ${args.out}: the estimated pose and ${frames} frames to edit`);
|
|
21743
21859
|
log(` preview it with: pixelkiln skeleton-preview ${args.out} --from ${image}`);
|
|
21744
21860
|
} else {
|
|
@@ -21751,8 +21867,8 @@ async function runSkeletonPreview(args) {
|
|
|
21751
21867
|
let sourceFile;
|
|
21752
21868
|
let label;
|
|
21753
21869
|
if (target.endsWith(".json")) {
|
|
21754
|
-
keypointsFile =
|
|
21755
|
-
sourceFile = args.from ?
|
|
21870
|
+
keypointsFile = path50.resolve(target);
|
|
21871
|
+
sourceFile = args.from ? path50.resolve(args.from) : void 0;
|
|
21756
21872
|
label = target;
|
|
21757
21873
|
} else {
|
|
21758
21874
|
const loaded = await loadManifest(args.manifest);
|
|
@@ -21764,7 +21880,7 @@ async function runSkeletonPreview(args) {
|
|
|
21764
21880
|
);
|
|
21765
21881
|
}
|
|
21766
21882
|
keypointsFile = spec.revision.keypointsFile;
|
|
21767
|
-
sourceFile = args.from ?
|
|
21883
|
+
sourceFile = args.from ? path50.resolve(args.from) : spec.revision.sourceFile;
|
|
21768
21884
|
label = `${spec.styleId}/${spec.assetId}`;
|
|
21769
21885
|
}
|
|
21770
21886
|
if (!existsSync36(keypointsFile)) throw new UsageError(`Keypoints file does not exist yet: ${keypointsFile}`);
|
|
@@ -21785,10 +21901,10 @@ async function runSkeletonPreview(args) {
|
|
|
21785
21901
|
source = decodePng(bytes);
|
|
21786
21902
|
}
|
|
21787
21903
|
}
|
|
21788
|
-
const out =
|
|
21904
|
+
const out = path50.resolve(args.out ?? keypointsFile.replace(/\.json$/, "") + ".preview.png");
|
|
21789
21905
|
await writeFile19(out, renderSkeletonSheet(set, { source }));
|
|
21790
21906
|
log(` ${label}: ${set.frames.length} frames after the starting pose`);
|
|
21791
|
-
const shown =
|
|
21907
|
+
const shown = path50.relative(process.cwd(), out);
|
|
21792
21908
|
log(` wrote ${shown.startsWith("..") ? out : shown}`);
|
|
21793
21909
|
}
|
|
21794
21910
|
|
|
@@ -21834,15 +21950,15 @@ async function runTools(args) {
|
|
|
21834
21950
|
}
|
|
21835
21951
|
|
|
21836
21952
|
// src/cli/commands/workspace.ts
|
|
21837
|
-
import
|
|
21953
|
+
import path51 from "path";
|
|
21838
21954
|
import { existsSync as existsSync37 } from "fs";
|
|
21839
21955
|
async function runWorkspace(args) {
|
|
21840
|
-
const workspacePath =
|
|
21841
|
-
const dir =
|
|
21956
|
+
const workspacePath = path51.resolve(args.workspace ?? "pixelkiln.workspace.json");
|
|
21957
|
+
const dir = path51.dirname(workspacePath);
|
|
21842
21958
|
if (args.subcommand === "add") {
|
|
21843
|
-
const manifestPath =
|
|
21959
|
+
const manifestPath = path51.resolve(args.target);
|
|
21844
21960
|
const loadedTarget = await loadManifest(manifestPath);
|
|
21845
|
-
const lockPath = args.explicitLock ?
|
|
21961
|
+
const lockPath = args.explicitLock ? path51.resolve(args.explicitLock) : path51.join(path51.dirname(manifestPath), "pixelkiln.lock.json");
|
|
21846
21962
|
const ws = await loadWorkspace(workspacePath);
|
|
21847
21963
|
const id = args.name ?? loadedTarget.manifest.name;
|
|
21848
21964
|
if (ws.projects.some((p) => p.id === id)) {
|
|
@@ -21862,7 +21978,7 @@ async function runWorkspace(args) {
|
|
|
21862
21978
|
...args.account ? { account: args.account } : {}
|
|
21863
21979
|
};
|
|
21864
21980
|
await saveWorkspace(workspacePath, { version: 1, projects: [...ws.projects, project] });
|
|
21865
|
-
log(` registered "${id}" in ${
|
|
21981
|
+
log(` registered "${id}" in ${path51.relative(process.cwd(), workspacePath)}`);
|
|
21866
21982
|
log(` manifest: ${project.manifest}`);
|
|
21867
21983
|
log(` lock: ${project.lock}`);
|
|
21868
21984
|
if (!existsSync37(lockPath)) {
|
|
@@ -21874,7 +21990,7 @@ async function runWorkspace(args) {
|
|
|
21874
21990
|
}
|
|
21875
21991
|
if (args.subcommand === "remove") {
|
|
21876
21992
|
const ws = await loadWorkspace(workspacePath);
|
|
21877
|
-
const resolvedTarget =
|
|
21993
|
+
const resolvedTarget = path51.resolve(args.target);
|
|
21878
21994
|
const match = ws.projects.find(
|
|
21879
21995
|
(p) => p.id === args.target || resolveProject(dir, p).manifestPath === resolvedTarget
|
|
21880
21996
|
);
|
|
@@ -21885,7 +22001,7 @@ async function runWorkspace(args) {
|
|
|
21885
22001
|
version: 1,
|
|
21886
22002
|
projects: ws.projects.filter((p) => p !== match)
|
|
21887
22003
|
});
|
|
21888
|
-
log(` removed "${match.id}" from ${
|
|
22004
|
+
log(` removed "${match.id}" from ${path51.relative(process.cwd(), workspacePath)}`);
|
|
21889
22005
|
return;
|
|
21890
22006
|
}
|
|
21891
22007
|
if (args.subcommand === "list") {
|
|
@@ -21897,9 +22013,9 @@ async function runWorkspace(args) {
|
|
|
21897
22013
|
if (args.json) {
|
|
21898
22014
|
log(JSON.stringify({ version: 1, workspace: workspacePath, projects: ws.projects, diagnostics }, null, 2));
|
|
21899
22015
|
} else if (!ws.projects.length) {
|
|
21900
|
-
log(` no projects registered in ${
|
|
22016
|
+
log(` no projects registered in ${path51.relative(process.cwd(), workspacePath)}`);
|
|
21901
22017
|
} else {
|
|
21902
|
-
log(` ${ws.projects.length} project(s) in ${
|
|
22018
|
+
log(` ${ws.projects.length} project(s) in ${path51.relative(process.cwd(), workspacePath)}:`);
|
|
21903
22019
|
for (const p of ws.projects) {
|
|
21904
22020
|
log(` ${p.id.padEnd(24)} ${p.manifest.padEnd(40)} (${p.provider}${p.account ? `, ${p.account}` : ""})`);
|
|
21905
22021
|
}
|
|
@@ -21917,7 +22033,7 @@ async function runWorkspace(args) {
|
|
|
21917
22033
|
if (args.json) {
|
|
21918
22034
|
log(JSON.stringify({ ...report, workspace: workspacePath }, null, 2));
|
|
21919
22035
|
} else {
|
|
21920
|
-
log(` workspace: ${
|
|
22036
|
+
log(` workspace: ${path51.relative(process.cwd(), workspacePath)}`);
|
|
21921
22037
|
for (const p of report.projects) {
|
|
21922
22038
|
if (p.error) {
|
|
21923
22039
|
log(`
|