pixelkiln 0.84.0 → 0.86.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/PROVIDERS.md +3 -3
- package/dist/cli.js +528 -353
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +615 -440
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +107 -4
- package/dist/index.d.ts +107 -4
- package/dist/index.js +510 -335
- package/dist/index.js.map +1 -1
- package/docs/MANIFEST.md +2 -1
- package/docs/REVISIONS.md +43 -11
- package/docs/SCENARIO.md +203 -7
- package/package.json +1 -1
- package/schema/manifest.schema.json +4 -0
- package/skills/pixelkiln/references/revisions.md +4 -2
- package/skills/pixelkiln/references/scenario.md +8 -4
package/dist/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")));
|
|
@@ -777,6 +777,7 @@ function specHash(spec, styleImageHashes, providerOptionIdentity = spec.provider
|
|
|
777
777
|
revision: spec.revision ? {
|
|
778
778
|
mode: spec.revision.mode,
|
|
779
779
|
from: spec.revision.sourceAssetId,
|
|
780
|
+
fromStyle: spec.revision.sourceStyleId,
|
|
780
781
|
sourceSha256: spec.revision.sourceSha256,
|
|
781
782
|
maskSha256: spec.revision.maskSha256,
|
|
782
783
|
strength: spec.revision.strength,
|
|
@@ -1126,8 +1127,16 @@ var RevisionDitheringSchema = z.enum(["none", "2x2", "4x4", "8x8"]);
|
|
|
1126
1127
|
var RevisionSchema = z.object({
|
|
1127
1128
|
/** What kind of controlled change the provider workflow performs. */
|
|
1128
1129
|
mode: RevisionModeSchema,
|
|
1129
|
-
/** Parent asset id in
|
|
1130
|
+
/** Parent asset id. It lives in this asset's own style unless `fromStyle` names another. */
|
|
1130
1131
|
from: z.string().min(1),
|
|
1132
|
+
/**
|
|
1133
|
+
* The style the parent lives in, when it is not this asset's own. The
|
|
1134
|
+
* parent is generated by that style's provider and model, and this
|
|
1135
|
+
* revision starts from its output file, so a chain (generate with one
|
|
1136
|
+
* model, clean up with another) is declared instead of restated by hand.
|
|
1137
|
+
* Styles may depend on one another but not in a cycle.
|
|
1138
|
+
*/
|
|
1139
|
+
fromStyle: z.string().min(1).optional(),
|
|
1131
1140
|
/** Manifest-relative black/white PNG. Required only for masked inpainting. */
|
|
1132
1141
|
mask: z.string().min(1).optional(),
|
|
1133
1142
|
/** Provider-neutral edit strength. The active adapter must bind it explicitly. */
|
|
@@ -1988,6 +1997,7 @@ var LockEntrySchema = z.object({
|
|
|
1988
1997
|
revision: z.object({
|
|
1989
1998
|
mode: RevisionModeSchema,
|
|
1990
1999
|
sourceAssetId: z.string().min(1),
|
|
2000
|
+
sourceStyleId: z.string().min(1).optional(),
|
|
1991
2001
|
sourceSha256: z.string().regex(/^[0-9a-f]{64}$/),
|
|
1992
2002
|
maskSha256: z.string().regex(/^[0-9a-f]{64}$/).optional(),
|
|
1993
2003
|
strength: z.number().min(0).max(1).optional(),
|
|
@@ -4405,7 +4415,7 @@ async function inspectRevision(spec, lock, seen) {
|
|
|
4405
4415
|
if (!dependency.ready) {
|
|
4406
4416
|
return {
|
|
4407
4417
|
ready: false,
|
|
4408
|
-
reason: `revision source ${spec.styleId}/${revision.sourceAssetId} is not ready: ${dependency.reason}`
|
|
4418
|
+
reason: `revision source ${revision.sourceStyleId ?? spec.styleId}/${revision.sourceAssetId} is not ready: ${dependency.reason}`
|
|
4409
4419
|
};
|
|
4410
4420
|
}
|
|
4411
4421
|
if (revision.sourceMembers) {
|
|
@@ -4818,7 +4828,7 @@ async function confirm(question, auto) {
|
|
|
4818
4828
|
}
|
|
4819
4829
|
|
|
4820
4830
|
// src/cli/commands/account.ts
|
|
4821
|
-
import
|
|
4831
|
+
import path23 from "path";
|
|
4822
4832
|
import { existsSync as existsSync16 } from "fs";
|
|
4823
4833
|
|
|
4824
4834
|
// src/env.ts
|
|
@@ -6251,9 +6261,9 @@ var PixelLabClient = class {
|
|
|
6251
6261
|
apiKey;
|
|
6252
6262
|
http;
|
|
6253
6263
|
/** Retries transport failures, 429, and 5xx; see http.ts for the policy. */
|
|
6254
|
-
async request(
|
|
6264
|
+
async request(path52, init) {
|
|
6255
6265
|
const auth = this.apiKey.startsWith("Bearer ") ? this.apiKey : `Bearer ${this.apiKey}`;
|
|
6256
|
-
const res = await this.http(`${BASE}${
|
|
6266
|
+
const res = await this.http(`${BASE}${path52}`, {
|
|
6257
6267
|
...init,
|
|
6258
6268
|
headers: {
|
|
6259
6269
|
Authorization: auth,
|
|
@@ -6263,13 +6273,13 @@ var PixelLabClient = class {
|
|
|
6263
6273
|
});
|
|
6264
6274
|
const text = await res.text();
|
|
6265
6275
|
if (!res.ok) {
|
|
6266
|
-
throw new PixelLabError(`${init?.method ?? "GET"} ${
|
|
6276
|
+
throw new PixelLabError(`${init?.method ?? "GET"} ${path52} \u2192 ${res.status}`, res.status, text);
|
|
6267
6277
|
}
|
|
6268
6278
|
if (!text) return {};
|
|
6269
6279
|
try {
|
|
6270
6280
|
return JSON.parse(text);
|
|
6271
6281
|
} catch {
|
|
6272
|
-
throw new Error(`${init?.method ?? "GET"} ${
|
|
6282
|
+
throw new Error(`${init?.method ?? "GET"} ${path52} returned invalid JSON`);
|
|
6273
6283
|
}
|
|
6274
6284
|
}
|
|
6275
6285
|
async balance() {
|
|
@@ -6674,10 +6684,10 @@ var PixelLabClient = class {
|
|
|
6674
6684
|
const palette = args.paletteSwatchBase64 ? { color_image: { type: "base64", base64: args.paletteSwatchBase64, format: "png" }, force_colors: true } : {};
|
|
6675
6685
|
const encode = (image) => ({ type: "base64", base64: image.base64, format: image.format });
|
|
6676
6686
|
const south = args.reference?.south;
|
|
6677
|
-
let
|
|
6687
|
+
let path52;
|
|
6678
6688
|
let body;
|
|
6679
6689
|
if (args.mode === "v3") {
|
|
6680
|
-
|
|
6690
|
+
path52 = "/create-character-v3";
|
|
6681
6691
|
body = {
|
|
6682
6692
|
description: args.description,
|
|
6683
6693
|
image_size: imageSize,
|
|
@@ -6691,7 +6701,7 @@ var PixelLabClient = class {
|
|
|
6691
6701
|
...args.enhancePrompt ? { enhance_prompt: true } : {}
|
|
6692
6702
|
};
|
|
6693
6703
|
} else if (args.mode === "pro-flash") {
|
|
6694
|
-
|
|
6704
|
+
path52 = "/create-character-pro-flash";
|
|
6695
6705
|
const traits = args.styleTraits;
|
|
6696
6706
|
body = {
|
|
6697
6707
|
description: args.description,
|
|
@@ -6715,7 +6725,7 @@ var PixelLabClient = class {
|
|
|
6715
6725
|
} : {}
|
|
6716
6726
|
};
|
|
6717
6727
|
} else if (args.mode === "pro") {
|
|
6718
|
-
|
|
6728
|
+
path52 = "/create-character-pro";
|
|
6719
6729
|
body = {
|
|
6720
6730
|
description: args.description,
|
|
6721
6731
|
image_size: imageSize,
|
|
@@ -6729,7 +6739,7 @@ var PixelLabClient = class {
|
|
|
6729
6739
|
...!south && args.styleCharacterId ? { style_character_id: args.styleCharacterId } : {}
|
|
6730
6740
|
};
|
|
6731
6741
|
} else {
|
|
6732
|
-
|
|
6742
|
+
path52 = args.directions === 4 ? "/create-character-with-4-directions" : "/create-character-with-8-directions";
|
|
6733
6743
|
body = {
|
|
6734
6744
|
description: args.description,
|
|
6735
6745
|
image_size: imageSize,
|
|
@@ -6746,8 +6756,8 @@ var PixelLabClient = class {
|
|
|
6746
6756
|
...palette
|
|
6747
6757
|
};
|
|
6748
6758
|
}
|
|
6749
|
-
const raw = await this.request(
|
|
6750
|
-
return validateResponse(CharacterSubmitSchema, raw,
|
|
6759
|
+
const raw = await this.request(path52, { method: "POST", body: JSON.stringify(body) });
|
|
6760
|
+
return validateResponse(CharacterSubmitSchema, raw, path52.slice(1));
|
|
6751
6761
|
}
|
|
6752
6762
|
/** A text edit of an existing character, applied to every direction and stored as a sibling. */
|
|
6753
6763
|
async createCharacterState(args) {
|
|
@@ -9725,9 +9735,9 @@ var RetroDiffusionClient = class {
|
|
|
9725
9735
|
if (!response.ok) throw new ProviderError("retrodiffusion", `Retro Diffusion download failed (${response.status})`, { status: response.status });
|
|
9726
9736
|
return Buffer.from(await response.arrayBuffer());
|
|
9727
9737
|
}
|
|
9728
|
-
async call(
|
|
9738
|
+
async call(path52, init = {}) {
|
|
9729
9739
|
if (!this.token) throw new Error("RD_API_KEY is not set");
|
|
9730
|
-
const response = await this.request(`${this.baseUrl}${
|
|
9740
|
+
const response = await this.request(`${this.baseUrl}${path52}`, {
|
|
9731
9741
|
...init,
|
|
9732
9742
|
headers: {
|
|
9733
9743
|
"Content-Type": "application/json",
|
|
@@ -10036,8 +10046,13 @@ function retroError(value) {
|
|
|
10036
10046
|
}
|
|
10037
10047
|
|
|
10038
10048
|
// src/providers/scenario.ts
|
|
10049
|
+
import { createHash as createHash3 } from "crypto";
|
|
10050
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync3, renameSync, writeFileSync as writeFileSync2 } from "fs";
|
|
10051
|
+
import os2 from "os";
|
|
10052
|
+
import path13 from "path";
|
|
10039
10053
|
var DEFAULT_BASE_URL3 = "https://api.cloud.scenario.com/v1";
|
|
10040
10054
|
var SOURCE_PROTOCOL2 = "scenario:";
|
|
10055
|
+
var SUBMIT_RETRIES = 8;
|
|
10041
10056
|
var PROTECTED_PARAMETERS = /* @__PURE__ */ new Set([
|
|
10042
10057
|
"dryRun",
|
|
10043
10058
|
"height",
|
|
@@ -10084,7 +10099,11 @@ var ScenarioClient = class {
|
|
|
10084
10099
|
async submit(modelId, body, projectId) {
|
|
10085
10100
|
const response = await this.call(
|
|
10086
10101
|
`/generate/custom/${encodeURIComponent(modelId)}`,
|
|
10087
|
-
|
|
10102
|
+
// A 429 here means this account's concurrent-job limit refused the
|
|
10103
|
+
// request, so nothing was accepted or billed; wait it out instead of
|
|
10104
|
+
// failing the asset. The default four retries give up after about half
|
|
10105
|
+
// a minute, before a running job has finished.
|
|
10106
|
+
{ method: "POST", body: JSON.stringify(body), retries: SUBMIT_RETRIES },
|
|
10088
10107
|
{ projectId }
|
|
10089
10108
|
);
|
|
10090
10109
|
const job = response?.job;
|
|
@@ -10092,6 +10111,29 @@ var ScenarioClient = class {
|
|
|
10092
10111
|
if (!jobId) throw new Error("Scenario did not return an async job id");
|
|
10093
10112
|
return jobId;
|
|
10094
10113
|
}
|
|
10114
|
+
/**
|
|
10115
|
+
* Uploads one image (POST /assets) and returns its asset id, which a model's
|
|
10116
|
+
* reference input takes. Free, and idempotent per image: the id is kept by
|
|
10117
|
+
* content hash so a rerun does not upload the same bytes again.
|
|
10118
|
+
*/
|
|
10119
|
+
async uploadImage(image, projectId) {
|
|
10120
|
+
const bytes = Buffer.from(image.base64, "base64");
|
|
10121
|
+
const digest2 = sha256(bytes);
|
|
10122
|
+
const cached = readUploadCache()[uploadCacheKey(this.baseUrl, projectId, digest2)];
|
|
10123
|
+
if (cached) return cached;
|
|
10124
|
+
const response = await this.call(
|
|
10125
|
+
"/assets",
|
|
10126
|
+
{
|
|
10127
|
+
method: "POST",
|
|
10128
|
+
body: JSON.stringify({ image: image.base64, name: `pixelkiln-${digest2.slice(0, 12)}.${image.format === "png" ? "png" : "jpg"}` })
|
|
10129
|
+
},
|
|
10130
|
+
{ projectId }
|
|
10131
|
+
);
|
|
10132
|
+
const id = response?.asset?.id;
|
|
10133
|
+
if (typeof id !== "string" || !id) throw new Error("Scenario did not return an uploaded asset id");
|
|
10134
|
+
writeUploadCache(uploadCacheKey(this.baseUrl, projectId, digest2), id);
|
|
10135
|
+
return id;
|
|
10136
|
+
}
|
|
10095
10137
|
async job(id, projectId) {
|
|
10096
10138
|
const response = await this.call(`/jobs/${encodeURIComponent(id)}`, {}, { projectId });
|
|
10097
10139
|
const raw = response?.job;
|
|
@@ -10218,6 +10260,14 @@ var ScenarioProvider = class _ScenarioProvider {
|
|
|
10218
10260
|
supports(generator) {
|
|
10219
10261
|
return generator === "map";
|
|
10220
10262
|
}
|
|
10263
|
+
/**
|
|
10264
|
+
* An edit is a generation with the parent as a reference image, which the
|
|
10265
|
+
* editing models (GPT Image, Gemini, FLUX) take. Masked inpainting and the
|
|
10266
|
+
* PixelLab-specific modes have no common Scenario input.
|
|
10267
|
+
*/
|
|
10268
|
+
supportsRevision(mode2) {
|
|
10269
|
+
return mode2 === "image-to-image";
|
|
10270
|
+
}
|
|
10221
10271
|
estimate(spec) {
|
|
10222
10272
|
const options = scenarioOptions(spec);
|
|
10223
10273
|
return {
|
|
@@ -10238,11 +10288,31 @@ var ScenarioProvider = class _ScenarioProvider {
|
|
|
10238
10288
|
if (!Number.isInteger(count) || count < 1 || count > 4) {
|
|
10239
10289
|
throw new Error("Scenario numOutputs must be a whole number from 1 to 4");
|
|
10240
10290
|
}
|
|
10241
|
-
if (spec.width
|
|
10242
|
-
throw new Error("Scenario width and height must be
|
|
10291
|
+
if (!Number.isInteger(spec.width) || spec.width < 16 || spec.width > 4096 || !Number.isInteger(spec.height) || spec.height < 16 || spec.height > 4096) {
|
|
10292
|
+
throw new Error("Scenario width and height must be whole numbers from 16 to 4096");
|
|
10243
10293
|
}
|
|
10244
|
-
|
|
10245
|
-
|
|
10294
|
+
const revision = spec.revision;
|
|
10295
|
+
if (revision) {
|
|
10296
|
+
if (revision.sourceMembers?.length) {
|
|
10297
|
+
throw new Error(
|
|
10298
|
+
`Scenario edits one image; ${revision.sourceAssetId} is a ${revision.sourceMembers.length}-member set`
|
|
10299
|
+
);
|
|
10300
|
+
}
|
|
10301
|
+
if (revision.strength != null) {
|
|
10302
|
+
throw new Error("Scenario image-to-image takes no strength; use the model's own parameters");
|
|
10303
|
+
}
|
|
10304
|
+
}
|
|
10305
|
+
const parameter = referenceParameter(options);
|
|
10306
|
+
if ((styleImages.length || revision) && Object.hasOwn(options.parameters ?? {}, parameter)) {
|
|
10307
|
+
throw new Error(
|
|
10308
|
+
`Scenario parameters.${parameter} is the reference-image input and is filled from styleImages and revisions`
|
|
10309
|
+
);
|
|
10310
|
+
}
|
|
10311
|
+
if (styleImages.length + (revision ? 1 : 0) > 1 && options.referenceArray === false) {
|
|
10312
|
+
throw new Error(`Scenario ${parameter} takes one image; use one style image or a revision, not both`);
|
|
10313
|
+
}
|
|
10314
|
+
if (options.referenceParameter != null && (typeof options.referenceParameter !== "string" || !options.referenceParameter.trim())) {
|
|
10315
|
+
throw new Error("Scenario referenceParameter must be a non-empty string");
|
|
10246
10316
|
}
|
|
10247
10317
|
if (options.projectId != null && (typeof options.projectId !== "string" || !options.projectId.trim())) {
|
|
10248
10318
|
throw new Error("Scenario projectId must be a non-empty string");
|
|
@@ -10255,7 +10325,12 @@ var ScenarioProvider = class _ScenarioProvider {
|
|
|
10255
10325
|
async submit(spec, styleImages) {
|
|
10256
10326
|
this.validate(spec, styleImages);
|
|
10257
10327
|
const options = scenarioOptions(spec);
|
|
10258
|
-
const
|
|
10328
|
+
const references = [];
|
|
10329
|
+
if (spec.revision) references.push(readRevisionSource(spec));
|
|
10330
|
+
for (const image of styleImages) references.push({ base64: image.base64, format: image.format });
|
|
10331
|
+
const referenceAssetIds = [];
|
|
10332
|
+
for (const image of references) referenceAssetIds.push(await this.client.uploadImage(image, options.projectId));
|
|
10333
|
+
const body = requestBody(spec, options, referenceAssetIds);
|
|
10259
10334
|
const quote = await this.client.quote(options.modelId, body, options.projectId);
|
|
10260
10335
|
if (quote.total > options.maxComputeUnits + Number.EPSILON) {
|
|
10261
10336
|
throw new Error(
|
|
@@ -10272,7 +10347,8 @@ var ScenarioProvider = class _ScenarioProvider {
|
|
|
10272
10347
|
quotedComputeUnits: quote.total,
|
|
10273
10348
|
creativeUnitsCost: quote.creativeUnitsCost,
|
|
10274
10349
|
costDetails: quote.costDetails,
|
|
10275
|
-
ipDetectionCost: quote.ipDetectionCost
|
|
10350
|
+
ipDetectionCost: quote.ipDetectionCost,
|
|
10351
|
+
...referenceAssetIds.length ? { referenceAssetIds } : {}
|
|
10276
10352
|
}
|
|
10277
10353
|
};
|
|
10278
10354
|
}
|
|
@@ -10294,7 +10370,13 @@ var ScenarioProvider = class _ScenarioProvider {
|
|
|
10294
10370
|
}
|
|
10295
10371
|
const assetIds = scenarioAssetIds(job);
|
|
10296
10372
|
if (!assetIds.length) return { status: "failed", error: "Scenario job returned no asset IDs" };
|
|
10297
|
-
|
|
10373
|
+
let assets;
|
|
10374
|
+
try {
|
|
10375
|
+
assets = await this.assets(assetIds, reference.projectId);
|
|
10376
|
+
} catch (error) {
|
|
10377
|
+
if (error instanceof UnsupportedScenarioOutput) return { status: "failed", error: error.message };
|
|
10378
|
+
throw error;
|
|
10379
|
+
}
|
|
10298
10380
|
if (assets.length > 1) {
|
|
10299
10381
|
return {
|
|
10300
10382
|
status: "review",
|
|
@@ -10349,6 +10431,32 @@ var ScenarioProvider = class _ScenarioProvider {
|
|
|
10349
10431
|
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
10432
|
}
|
|
10351
10433
|
};
|
|
10434
|
+
function uploadCachePath() {
|
|
10435
|
+
return process.env.SCENARIO_UPLOAD_CACHE ?? path13.join(os2.tmpdir(), "pixelkiln-scenario", "uploads.json");
|
|
10436
|
+
}
|
|
10437
|
+
function uploadCacheKey(baseUrl, projectId, digest2) {
|
|
10438
|
+
return createHash3("sha256").update(`${baseUrl}
|
|
10439
|
+
${projectId ?? ""}
|
|
10440
|
+
${digest2}`).digest("hex");
|
|
10441
|
+
}
|
|
10442
|
+
function readUploadCache() {
|
|
10443
|
+
try {
|
|
10444
|
+
const value = JSON.parse(readFileSync3(uploadCachePath(), "utf8"));
|
|
10445
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
10446
|
+
} catch {
|
|
10447
|
+
return {};
|
|
10448
|
+
}
|
|
10449
|
+
}
|
|
10450
|
+
function writeUploadCache(key, assetId) {
|
|
10451
|
+
try {
|
|
10452
|
+
const file = uploadCachePath();
|
|
10453
|
+
mkdirSync2(path13.dirname(file), { recursive: true });
|
|
10454
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
10455
|
+
writeFileSync2(tmp, JSON.stringify({ ...readUploadCache(), [key]: assetId }));
|
|
10456
|
+
renameSync(tmp, file);
|
|
10457
|
+
} catch {
|
|
10458
|
+
}
|
|
10459
|
+
}
|
|
10352
10460
|
function scenarioOptions(spec) {
|
|
10353
10461
|
return spec.providerOptions;
|
|
10354
10462
|
}
|
|
@@ -10364,9 +10472,25 @@ function scenarioBaseUrl(value) {
|
|
|
10364
10472
|
}
|
|
10365
10473
|
return url.href.replace(/\/$/, "");
|
|
10366
10474
|
}
|
|
10367
|
-
function
|
|
10475
|
+
function referenceParameter(options) {
|
|
10476
|
+
return options.referenceParameter?.trim() || "referenceImages";
|
|
10477
|
+
}
|
|
10478
|
+
function readRevisionSource(spec) {
|
|
10479
|
+
const revision = spec.revision;
|
|
10480
|
+
if (!revision.sourceFile || !revision.sourceSha256 || !revision.sourceFormat) {
|
|
10481
|
+
throw new Error(`${spec.styleId}/${spec.assetId}: revision source is not ready`);
|
|
10482
|
+
}
|
|
10483
|
+
const bytes = readFileSync3(revision.sourceFile);
|
|
10484
|
+
if (sha256(bytes) !== revision.sourceSha256) {
|
|
10485
|
+
throw new Error(`${spec.styleId}/${spec.assetId}: revision source changed after the manifest was resolved`);
|
|
10486
|
+
}
|
|
10487
|
+
return { base64: bytes.toString("base64"), format: revision.sourceFormat };
|
|
10488
|
+
}
|
|
10489
|
+
function requestBody(spec, options, referenceAssetIds = []) {
|
|
10490
|
+
const parameter = referenceParameter(options);
|
|
10368
10491
|
return {
|
|
10369
10492
|
...options.parameters ?? {},
|
|
10493
|
+
...referenceAssetIds.length ? { [parameter]: options.referenceArray === false ? referenceAssetIds[0] : referenceAssetIds } : {},
|
|
10370
10494
|
prompt: spec.prompt,
|
|
10371
10495
|
width: spec.width,
|
|
10372
10496
|
height: spec.height,
|
|
@@ -10456,9 +10580,11 @@ function scenarioAssetIds(job) {
|
|
|
10456
10580
|
function assetDownloadUrl(asset) {
|
|
10457
10581
|
return asset.originalFileUrl || asset.url;
|
|
10458
10582
|
}
|
|
10583
|
+
var UnsupportedScenarioOutput = class extends Error {
|
|
10584
|
+
};
|
|
10459
10585
|
function assertPngAsset(asset) {
|
|
10460
10586
|
if (asset.originalMimeType && asset.originalMimeType !== MediaType.PNG) {
|
|
10461
|
-
throw new
|
|
10587
|
+
throw new UnsupportedScenarioOutput(
|
|
10462
10588
|
`Scenario asset ${asset.id} is ${asset.originalMimeType}; PixelKiln's Scenario MVP requires PNG output`
|
|
10463
10589
|
);
|
|
10464
10590
|
}
|
|
@@ -10586,7 +10712,7 @@ registerProvider({
|
|
|
10586
10712
|
// src/manifest.ts
|
|
10587
10713
|
import { readFile as readFile6 } from "fs/promises";
|
|
10588
10714
|
import { existsSync as existsSync8 } from "fs";
|
|
10589
|
-
import
|
|
10715
|
+
import path14 from "path";
|
|
10590
10716
|
|
|
10591
10717
|
// src/loop-directions.ts
|
|
10592
10718
|
var SINGLE_OUTPUT_FIELDS = ["file", "cell", "remoteId", "source", "sourceByStyle", "outputRole"];
|
|
@@ -10675,7 +10801,7 @@ function isObject2(value) {
|
|
|
10675
10801
|
|
|
10676
10802
|
// src/manifest.ts
|
|
10677
10803
|
async function loadManifest(manifestPath) {
|
|
10678
|
-
const abs =
|
|
10804
|
+
const abs = path14.resolve(manifestPath);
|
|
10679
10805
|
if (!existsSync8(abs)) throw new ProjectError(`No manifest at ${abs}`);
|
|
10680
10806
|
const expansion = expandLoopDirections(JSON.parse(await readFile6(abs, "utf8")));
|
|
10681
10807
|
if (expansion.issues.length) {
|
|
@@ -10720,13 +10846,24 @@ ${issues}`);
|
|
|
10720
10846
|
}
|
|
10721
10847
|
}
|
|
10722
10848
|
if (asset.revision) {
|
|
10723
|
-
|
|
10849
|
+
const parent = parsed.data.assets[asset.revision.from];
|
|
10850
|
+
if (!parent) {
|
|
10724
10851
|
unknownReferences.push(
|
|
10725
10852
|
`assets.${assetId}.revision.from: unknown asset "${asset.revision.from}"`
|
|
10726
10853
|
);
|
|
10727
10854
|
} else if (asset.revision.from === assetId) {
|
|
10728
10855
|
unknownReferences.push(`assets.${assetId}.revision.from: an asset cannot revise itself`);
|
|
10729
10856
|
}
|
|
10857
|
+
const fromStyle = asset.revision.fromStyle;
|
|
10858
|
+
if (fromStyle) {
|
|
10859
|
+
if (!styleIds.has(fromStyle)) {
|
|
10860
|
+
unknownReferences.push(`assets.${assetId}.revision.fromStyle: unknown style "${fromStyle}"`);
|
|
10861
|
+
} else if (parent && parent.styles.length && !parent.styles.includes(fromStyle)) {
|
|
10862
|
+
unknownReferences.push(
|
|
10863
|
+
`assets.${assetId}.revision.fromStyle: "${asset.revision.from}" is not in style "${fromStyle}"`
|
|
10864
|
+
);
|
|
10865
|
+
}
|
|
10866
|
+
}
|
|
10730
10867
|
}
|
|
10731
10868
|
for (const shape of ["state", "animation"]) {
|
|
10732
10869
|
const of = asset[shape]?.of;
|
|
@@ -10768,7 +10905,7 @@ ${issues}`);
|
|
|
10768
10905
|
${unknownReferences.map((i) => ` ${i}`).join("\n")}`);
|
|
10769
10906
|
}
|
|
10770
10907
|
const loopFamilies = Object.keys(expansion.families).length ? { loopFamilies: expansion.families } : {};
|
|
10771
|
-
return { manifest: parsed.data, root:
|
|
10908
|
+
return { manifest: parsed.data, root: path14.dirname(abs), path: abs, ...loopFamilies };
|
|
10772
10909
|
}
|
|
10773
10910
|
function parentAssetId(asset) {
|
|
10774
10911
|
return asset?.revision?.from ?? asset?.state?.of ?? asset?.animation?.of ?? asset?.portrait?.of ?? asset?.outfit?.of;
|
|
@@ -10842,7 +10979,7 @@ async function resolveSpecs(loaded, filter) {
|
|
|
10842
10979
|
return provider;
|
|
10843
10980
|
};
|
|
10844
10981
|
const specs = [];
|
|
10845
|
-
const
|
|
10982
|
+
const requestedStyleIds = Object.keys(manifest.styles).filter(
|
|
10846
10983
|
(id) => !filter?.styles?.length || filter.styles.includes(id)
|
|
10847
10984
|
);
|
|
10848
10985
|
for (const unknownStyle of filter?.styles ?? []) {
|
|
@@ -10867,9 +11004,42 @@ async function resolveSpecs(loaded, filter) {
|
|
|
10867
11004
|
current = parentAssetId(manifest.assets[current]);
|
|
10868
11005
|
}
|
|
10869
11006
|
}
|
|
11007
|
+
const styleDependencies = /* @__PURE__ */ new Map();
|
|
11008
|
+
for (const styleId of Object.keys(manifest.styles)) {
|
|
11009
|
+
const needed = /* @__PURE__ */ new Set();
|
|
11010
|
+
for (const assetId of resolutionAssetIds) {
|
|
11011
|
+
const asset = manifest.assets[assetId];
|
|
11012
|
+
if (asset?.styles.length && !asset.styles.includes(styleId)) continue;
|
|
11013
|
+
const fromStyle = asset?.revision?.fromStyle;
|
|
11014
|
+
if (fromStyle && fromStyle !== styleId) needed.add(fromStyle);
|
|
11015
|
+
}
|
|
11016
|
+
styleDependencies.set(styleId, needed);
|
|
11017
|
+
}
|
|
11018
|
+
const styleIds = [];
|
|
11019
|
+
const emitStyles = new Set(requestedStyleIds);
|
|
11020
|
+
{
|
|
11021
|
+
const placed = /* @__PURE__ */ new Set();
|
|
11022
|
+
const visiting = [];
|
|
11023
|
+
const place = (styleId) => {
|
|
11024
|
+
if (placed.has(styleId)) return;
|
|
11025
|
+
const cycleStart = visiting.indexOf(styleId);
|
|
11026
|
+
if (cycleStart >= 0) {
|
|
11027
|
+
throw new Error(
|
|
11028
|
+
`Styles revise each other in a cycle: ${[...visiting.slice(cycleStart), styleId].join(" -> ")}. revision.fromStyle must form a chain, not a loop.`
|
|
11029
|
+
);
|
|
11030
|
+
}
|
|
11031
|
+
visiting.push(styleId);
|
|
11032
|
+
for (const dependency of styleDependencies.get(styleId) ?? []) place(dependency);
|
|
11033
|
+
visiting.pop();
|
|
11034
|
+
placed.add(styleId);
|
|
11035
|
+
styleIds.push(styleId);
|
|
11036
|
+
};
|
|
11037
|
+
for (const styleId of requestedStyleIds) place(styleId);
|
|
11038
|
+
}
|
|
11039
|
+
const styleFinalizers = /* @__PURE__ */ new Map();
|
|
10870
11040
|
const styleImageCache = /* @__PURE__ */ new Map();
|
|
10871
11041
|
async function loadStyleImage(rel, what = "Style image") {
|
|
10872
|
-
const abs =
|
|
11042
|
+
const abs = path14.resolve(root, rel);
|
|
10873
11043
|
let hit = styleImageCache.get(abs);
|
|
10874
11044
|
if (!hit) {
|
|
10875
11045
|
if (!existsSync8(abs)) throw new Error(`${what} not found: ${abs}`);
|
|
@@ -10899,7 +11069,7 @@ async function resolveSpecs(loaded, filter) {
|
|
|
10899
11069
|
styleImageDimensions.push(loadedImage);
|
|
10900
11070
|
}
|
|
10901
11071
|
const resolvedImages = style.styleImages.map((image) => {
|
|
10902
|
-
const hit = styleImageCache.get(
|
|
11072
|
+
const hit = styleImageCache.get(path14.resolve(root, image.path));
|
|
10903
11073
|
return { base64: hit.base64, width: hit.width, height: hit.height, format: hit.format };
|
|
10904
11074
|
});
|
|
10905
11075
|
const styleSpecs = /* @__PURE__ */ new Map();
|
|
@@ -11000,9 +11170,9 @@ async function resolveSpecs(loaded, filter) {
|
|
|
11000
11170
|
} else {
|
|
11001
11171
|
prompt = [style.promptPrefix, subject, style.promptSuffix].map((p) => p.trim()).filter(Boolean).join(", ");
|
|
11002
11172
|
}
|
|
11003
|
-
const relFile = asset.file ??
|
|
11004
|
-
const outFile =
|
|
11005
|
-
const qualityOutFile = style.quality ?
|
|
11173
|
+
const relFile = asset.file ?? path14.join(asset.category ?? "", `${assetId}.png`);
|
|
11174
|
+
const outFile = path14.resolve(root, style.outDir, relFile);
|
|
11175
|
+
const qualityOutFile = style.quality ? path14.resolve(root, style.quality.outDir, pngPath(relFile)) : void 0;
|
|
11006
11176
|
const tileSize = generator === "tiles" ? size : style.tileSize ?? 32;
|
|
11007
11177
|
const tileVariations = tileFeatureOutputCount(generator === "tiles" ? style.tileFeature : void 0) ?? tileVariationCount(countNumberedDescriptions(prompt));
|
|
11008
11178
|
const terrainTiles = generator === "terrain" ? terrainTileCount(style.terrainTransitionSize) : 0;
|
|
@@ -11097,7 +11267,7 @@ async function resolveSpecs(loaded, filter) {
|
|
|
11097
11267
|
minGridConfidence: style.quality.minGridConfidence,
|
|
11098
11268
|
...style.quality.minTransparency == null ? {} : { minTransparency: style.quality.minTransparency },
|
|
11099
11269
|
...style.quality.fixerRevision ? { fixerRevision: style.quality.fixerRevision } : {},
|
|
11100
|
-
...style.quality.fixerPython ? { fixerPython:
|
|
11270
|
+
...style.quality.fixerPython ? { fixerPython: path14.resolve(root, style.quality.fixerPython) } : {},
|
|
11101
11271
|
...style.quality.fps == null ? {} : { fps: style.quality.fps }
|
|
11102
11272
|
}
|
|
11103
11273
|
} : {},
|
|
@@ -11233,7 +11403,7 @@ async function resolveSpecs(loaded, filter) {
|
|
|
11233
11403
|
animation: sourceSpec.character.animation,
|
|
11234
11404
|
outfit: {
|
|
11235
11405
|
reference: {
|
|
11236
|
-
path:
|
|
11406
|
+
path: path14.resolve(root, asset.outfit.reference),
|
|
11237
11407
|
sha256: referenceImage.hash,
|
|
11238
11408
|
width: referenceImage.width,
|
|
11239
11409
|
height: referenceImage.height,
|
|
@@ -11254,28 +11424,30 @@ async function resolveSpecs(loaded, filter) {
|
|
|
11254
11424
|
`Provider "${activeProvider.id}" does not support ${asset.revision.mode} revisions`
|
|
11255
11425
|
);
|
|
11256
11426
|
}
|
|
11257
|
-
const
|
|
11258
|
-
const
|
|
11427
|
+
const sourceStyleId = asset.revision.fromStyle && asset.revision.fromStyle !== styleId ? asset.revision.fromStyle : void 0;
|
|
11428
|
+
const sourceSpec = sourceStyleId ? await styleFinalizers.get(sourceStyleId)(asset.revision.from) : await finalize(asset.revision.from);
|
|
11429
|
+
const sourceStem = sourceSpec.quality?.outFile ?? (sourceSpec.source ? path14.resolve(root, sourceSpec.source) : sourceSpec.outFile);
|
|
11259
11430
|
const sourceMembers = await revisionSourceMembers(sourceStem, `revision source ${asset.revision.from}`);
|
|
11260
11431
|
const sourceFile = sourceMembers?.[0]?.file ?? sourceStem;
|
|
11261
11432
|
const sourceImage = sourceMembers ? { ...sourceMembers[0], hash: sha256(JSON.stringify(sourceMembers.map((member) => member.sha256))) } : await optionalRevisionImage(sourceFile, "revision source");
|
|
11262
11433
|
const sourceFps = sourceSpec.character?.animation?.fps ?? sourceSpec.objectPro?.animation?.fps ?? sourceSpec.revision?.fps ?? sourceSpec.revision?.sourceFps;
|
|
11263
|
-
const maskFile = asset.revision.mask ?
|
|
11434
|
+
const maskFile = asset.revision.mask ? path14.resolve(root, asset.revision.mask) : void 0;
|
|
11264
11435
|
const maskImage = maskFile ? await optionalRevisionImage(maskFile, "revision mask", "png") : null;
|
|
11265
11436
|
if (sourceImage && maskImage && (sourceImage.width !== maskImage.width || sourceImage.height !== maskImage.height)) {
|
|
11266
11437
|
throw new Error(
|
|
11267
11438
|
`Revision mask for ${styleId}/${assetId} is ${maskImage.width}x${maskImage.height}; source ${asset.revision.from} is ${sourceImage.width}x${sourceImage.height}`
|
|
11268
11439
|
);
|
|
11269
11440
|
}
|
|
11270
|
-
const paletteImageFile = asset.revision.paletteImage ?
|
|
11441
|
+
const paletteImageFile = asset.revision.paletteImage ? path14.resolve(root, asset.revision.paletteImage) : void 0;
|
|
11271
11442
|
const paletteImage = paletteImageFile ? await optionalRevisionImage(paletteImageFile, "revision palette image", "png") : null;
|
|
11272
|
-
const lastFrameFile = asset.revision.lastFrame ?
|
|
11443
|
+
const lastFrameFile = asset.revision.lastFrame ? path14.resolve(root, asset.revision.lastFrame) : void 0;
|
|
11273
11444
|
const lastFrame = lastFrameFile ? await optionalRevisionImage(lastFrameFile, "revision last frame") : null;
|
|
11274
|
-
const keypointsFile = asset.revision.keypointsFile ?
|
|
11445
|
+
const keypointsFile = asset.revision.keypointsFile ? path14.resolve(root, asset.revision.keypointsFile) : void 0;
|
|
11275
11446
|
const keypoints = keypointsFile ? await optionalSkeletonSet(keypointsFile) : null;
|
|
11276
11447
|
resolved.revision = {
|
|
11277
11448
|
mode: asset.revision.mode,
|
|
11278
11449
|
sourceAssetId: asset.revision.from,
|
|
11450
|
+
...sourceStyleId ? { sourceStyleId } : {},
|
|
11279
11451
|
sourceFile,
|
|
11280
11452
|
sourceSha256: sourceImage?.hash ?? null,
|
|
11281
11453
|
sourceWidth: sourceImage?.width ?? null,
|
|
@@ -11397,6 +11569,8 @@ async function resolveSpecs(loaded, filter) {
|
|
|
11397
11569
|
finalized.add(assetId);
|
|
11398
11570
|
return resolved;
|
|
11399
11571
|
};
|
|
11572
|
+
styleFinalizers.set(styleId, finalize);
|
|
11573
|
+
if (!emitStyles.has(styleId)) continue;
|
|
11400
11574
|
for (const assetId of Object.keys(manifest.assets)) {
|
|
11401
11575
|
if (!requestedAssetIds.has(assetId) || !styleSpecs.has(assetId)) continue;
|
|
11402
11576
|
specs.push(await finalize(assetId));
|
|
@@ -11438,7 +11612,7 @@ async function resolveSpecs(loaded, filter) {
|
|
|
11438
11612
|
return specs;
|
|
11439
11613
|
}
|
|
11440
11614
|
function pngPath(file) {
|
|
11441
|
-
const extension =
|
|
11615
|
+
const extension = path14.extname(file);
|
|
11442
11616
|
return extension ? `${file.slice(0, -extension.length)}.png` : `${file}.png`;
|
|
11443
11617
|
}
|
|
11444
11618
|
async function resolveMapScene(root, styleId, assetId, generator, scene, provider) {
|
|
@@ -11448,12 +11622,12 @@ async function resolveMapScene(root, styleId, assetId, generator, scene, provide
|
|
|
11448
11622
|
if (!provider.supportsMapScene) {
|
|
11449
11623
|
throw new Error(`assets.${assetId}.scene: provider "${provider.id}" cannot draw into a scene; PixelLab's /map-objects can`);
|
|
11450
11624
|
}
|
|
11451
|
-
const file =
|
|
11625
|
+
const file = path14.resolve(root, scene.image);
|
|
11452
11626
|
const image = await optionalRevisionImage(file, "scene image", "png");
|
|
11453
11627
|
const base = { file, sha256: image?.hash ?? null, width: image?.width ?? null, height: image?.height ?? null };
|
|
11454
11628
|
const placement = scene.placement;
|
|
11455
11629
|
if ("mask" in placement) {
|
|
11456
|
-
const maskFile =
|
|
11630
|
+
const maskFile = path14.resolve(root, placement.mask);
|
|
11457
11631
|
const mask = await optionalRevisionImage(maskFile, "scene mask", "png");
|
|
11458
11632
|
if (image && mask && (image.width !== mask.width || image.height !== mask.height)) {
|
|
11459
11633
|
throw new Error(
|
|
@@ -11525,7 +11699,7 @@ async function resolveStyleImages(loaded, styleId) {
|
|
|
11525
11699
|
if (!style) throw new Error(`Unknown style "${styleId}"`);
|
|
11526
11700
|
const out = [];
|
|
11527
11701
|
for (const img of style.styleImages) {
|
|
11528
|
-
const file =
|
|
11702
|
+
const file = path14.resolve(loaded.root, img.path);
|
|
11529
11703
|
const buf = await readFile6(file);
|
|
11530
11704
|
const metadata = imageMetadata(buf);
|
|
11531
11705
|
if (!metadata) throw new Error(`Style image is not a readable PNG or JPEG: ${file}`);
|
|
@@ -11553,7 +11727,7 @@ async function resolveCharacterShape(asset, style, kind, files) {
|
|
|
11553
11727
|
};
|
|
11554
11728
|
if (asset.concept) {
|
|
11555
11729
|
const image = await files.load(asset.concept, "Concept image");
|
|
11556
|
-
shape.concept = { path:
|
|
11730
|
+
shape.concept = { path: path14.resolve(files.root, asset.concept), sha256: image.hash, width: image.width, height: image.height, format: image.format };
|
|
11557
11731
|
}
|
|
11558
11732
|
if (asset.reference) {
|
|
11559
11733
|
const byDirection = typeof asset.reference === "string" ? { south: asset.reference } : asset.reference;
|
|
@@ -11561,7 +11735,7 @@ async function resolveCharacterShape(asset, style, kind, files) {
|
|
|
11561
11735
|
for (const [direction, rel] of Object.entries(byDirection)) {
|
|
11562
11736
|
const image = await files.load(rel, `Reference sprite (${direction})`);
|
|
11563
11737
|
shape.reference[direction] = {
|
|
11564
|
-
path:
|
|
11738
|
+
path: path14.resolve(files.root, rel),
|
|
11565
11739
|
sha256: image.hash,
|
|
11566
11740
|
width: image.width,
|
|
11567
11741
|
height: image.height,
|
|
@@ -11582,7 +11756,7 @@ async function resolveCharacterShape(asset, style, kind, files) {
|
|
|
11582
11756
|
const animationMode = asset.animation.mode ?? (asset.animation.template ? "template" : "v3");
|
|
11583
11757
|
const pose = async (rel, what) => {
|
|
11584
11758
|
const image = await files.load(rel, what);
|
|
11585
|
-
return { path:
|
|
11759
|
+
return { path: path14.resolve(files.root, rel), sha256: image.hash, width: image.width, height: image.height, format: image.format };
|
|
11586
11760
|
};
|
|
11587
11761
|
shape.animation = {
|
|
11588
11762
|
mode: animationMode,
|
|
@@ -11613,7 +11787,7 @@ async function resolveObjectProShape(assetId, asset, style, kind, files) {
|
|
|
11613
11787
|
const south = typeof asset.reference === "string" ? asset.reference : asset.reference.south;
|
|
11614
11788
|
if (south) {
|
|
11615
11789
|
const image = await files.load(south, "Reference sprite");
|
|
11616
|
-
shape.reference = { path:
|
|
11790
|
+
shape.reference = { path: path14.resolve(files.root, south), sha256: image.hash, width: image.width, height: image.height, format: image.format };
|
|
11617
11791
|
}
|
|
11618
11792
|
}
|
|
11619
11793
|
if (asset.state) {
|
|
@@ -11641,7 +11815,7 @@ async function resolveObjectProShape(assetId, asset, style, kind, files) {
|
|
|
11641
11815
|
}
|
|
11642
11816
|
const pose = async (rel, what) => {
|
|
11643
11817
|
const image = await files.load(rel, what);
|
|
11644
|
-
return { path:
|
|
11818
|
+
return { path: path14.resolve(files.root, rel), sha256: image.hash, width: image.width, height: image.height, format: image.format };
|
|
11645
11819
|
};
|
|
11646
11820
|
shape.animation = {
|
|
11647
11821
|
mode: asset.animation.mode ?? "v3",
|
|
@@ -11658,9 +11832,9 @@ async function resolveObjectProShape(assetId, asset, style, kind, files) {
|
|
|
11658
11832
|
}
|
|
11659
11833
|
|
|
11660
11834
|
// src/pipeline/fetch.ts
|
|
11661
|
-
import { existsSync as existsSync9, readFileSync as
|
|
11835
|
+
import { existsSync as existsSync9, readFileSync as readFileSync4 } from "fs";
|
|
11662
11836
|
import { mkdir as mkdir4, readFile as readFile7, rename as rename4, rm as rm4, writeFile as writeFile4 } from "fs/promises";
|
|
11663
|
-
import
|
|
11837
|
+
import path15 from "path";
|
|
11664
11838
|
|
|
11665
11839
|
// src/source-url.ts
|
|
11666
11840
|
var SENSITIVE_QUERY_KEYS = /* @__PURE__ */ new Set([
|
|
@@ -11706,7 +11880,7 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
11706
11880
|
const result = { downloaded: 0, skipped: 0, failed: 0, ...opts.refresh ? { unchanged: 0 } : {} };
|
|
11707
11881
|
const specByKey = new Map(specs.map((s) => [lockKey(s.styleId, s.assetId), s]));
|
|
11708
11882
|
normalizeLockOutputPaths(lock, specs);
|
|
11709
|
-
const cacheDir = opts.cacheDir === false ? null :
|
|
11883
|
+
const cacheDir = opts.cacheDir === false ? null : path15.resolve(opts.cacheDir ?? path15.join(path15.dirname(lockPath), ".pixelkiln", "cache"));
|
|
11710
11884
|
const pending = Object.entries(lock.entries).filter(([key, e]) => {
|
|
11711
11885
|
if (e.provider !== provider.id || !specByKey.has(key)) return false;
|
|
11712
11886
|
if (specByKey.get(key)?.mirror) return false;
|
|
@@ -11717,7 +11891,7 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
11717
11891
|
if (spec && e.specHash === spec.specHash && e.outputs.length && !postprocessCurrent(e, spec)) {
|
|
11718
11892
|
return Boolean(opts.force) || e.outputs.every((output) => {
|
|
11719
11893
|
const file = resolveOutputPath(output.path, spec.root);
|
|
11720
|
-
return existsSync9(file) && sha256(
|
|
11894
|
+
return existsSync9(file) && sha256(readFileSync4(file)) === output.sha256;
|
|
11721
11895
|
});
|
|
11722
11896
|
}
|
|
11723
11897
|
if (!opts.repair) return false;
|
|
@@ -11725,7 +11899,7 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
11725
11899
|
return e.outputs.some((output) => {
|
|
11726
11900
|
const file = resolveOutputPath(output.path, spec.root);
|
|
11727
11901
|
if (!existsSync9(file)) return true;
|
|
11728
|
-
const current = sha256(
|
|
11902
|
+
const current = sha256(readFileSync4(file));
|
|
11729
11903
|
if (current === output.sha256) return false;
|
|
11730
11904
|
return Boolean(opts.force) || (e.supersededOutputs ?? []).some((old) => old.sha256 === current);
|
|
11731
11905
|
});
|
|
@@ -11808,7 +11982,7 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
11808
11982
|
if (!source.url) throw new Error(`no durable source URL to refresh ${source.role ?? "asset"} from`);
|
|
11809
11983
|
fresh = await provider.download(source.url);
|
|
11810
11984
|
if (recorded && sha256(fresh) === recordedRaw && !reapply) {
|
|
11811
|
-
log2(` current ${
|
|
11985
|
+
log2(` current ${path15.relative(process.cwd(), target)}`);
|
|
11812
11986
|
outputs.push({ ...recorded, path: portableOutputPath(target, spec.root) });
|
|
11813
11987
|
continue;
|
|
11814
11988
|
}
|
|
@@ -11832,9 +12006,9 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
11832
12006
|
}
|
|
11833
12007
|
if (reapply && !fresh) {
|
|
11834
12008
|
if (!recorded.raw) raw = await readFile7(target);
|
|
11835
|
-
log2(` reapply ${
|
|
12009
|
+
log2(` reapply ${path15.relative(process.cwd(), target)}`);
|
|
11836
12010
|
} else {
|
|
11837
|
-
log2(` changed ${
|
|
12011
|
+
log2(` changed ${path15.relative(process.cwd(), target)} (upstream)`);
|
|
11838
12012
|
}
|
|
11839
12013
|
} else {
|
|
11840
12014
|
const superseded = (entry.supersededOutputs ?? []).find(
|
|
@@ -11862,7 +12036,7 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
11862
12036
|
}
|
|
11863
12037
|
}
|
|
11864
12038
|
log2(
|
|
11865
|
-
` replace ${
|
|
12039
|
+
` replace ${path15.relative(process.cwd(), target)}` + (opts.force ? " (--force)" : " (previous tracked generation)")
|
|
11866
12040
|
);
|
|
11867
12041
|
}
|
|
11868
12042
|
}
|
|
@@ -11871,7 +12045,7 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
11871
12045
|
} else if (!raw && recorded && cacheDir) {
|
|
11872
12046
|
if (!reapply) final = await readCachedMedia(cacheDir, recorded.sha256, expectedMediaType);
|
|
11873
12047
|
if (!final && recordedRaw) raw = await readCachedMedia(cacheDir, recordedRaw, expectedMediaType);
|
|
11874
|
-
if (final || raw) log2(` cached ${
|
|
12048
|
+
if (final || raw) log2(` cached ${path15.relative(process.cwd(), target)}`);
|
|
11875
12049
|
}
|
|
11876
12050
|
if (!final && !raw) {
|
|
11877
12051
|
if (!source.url) {
|
|
@@ -11898,11 +12072,11 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
11898
12072
|
buf = processed.bytes;
|
|
11899
12073
|
if (processed.changed) {
|
|
11900
12074
|
rawHash = sha256(raw);
|
|
11901
|
-
log2(` snapped ${
|
|
12075
|
+
log2(` snapped ${path15.relative(process.cwd(), target)} to ${wanted.palette.colors.length} colours`);
|
|
11902
12076
|
}
|
|
11903
12077
|
}
|
|
11904
12078
|
if (cacheDir) await cacheMedia(cacheDir, buf, mediaType);
|
|
11905
|
-
await mkdir4(
|
|
12079
|
+
await mkdir4(path15.dirname(target), { recursive: true });
|
|
11906
12080
|
const tmp = `${target}.pixelkiln.tmp`;
|
|
11907
12081
|
await writeFile4(tmp, buf);
|
|
11908
12082
|
outputs.push({
|
|
@@ -11916,7 +12090,7 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
11916
12090
|
await saveLock(lockPath, lock);
|
|
11917
12091
|
await rename4(tmp, target);
|
|
11918
12092
|
wrote = true;
|
|
11919
|
-
log2(` wrote ${
|
|
12093
|
+
log2(` wrote ${path15.relative(process.cwd(), target)}`);
|
|
11920
12094
|
}
|
|
11921
12095
|
if (opts.refresh && !wrote) {
|
|
11922
12096
|
if (rotated) {
|
|
@@ -11960,7 +12134,7 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
11960
12134
|
return result;
|
|
11961
12135
|
}
|
|
11962
12136
|
async function readCachedMedia(cacheDir, hash, mediaType) {
|
|
11963
|
-
const file =
|
|
12137
|
+
const file = path15.join(cacheDir, cacheFileName(hash, mediaType));
|
|
11964
12138
|
if (!existsSync9(file)) return null;
|
|
11965
12139
|
try {
|
|
11966
12140
|
const buf = await readFile7(file);
|
|
@@ -11974,7 +12148,7 @@ async function readCachedMedia(cacheDir, hash, mediaType) {
|
|
|
11974
12148
|
async function cacheMedia(cacheDir, buf, mediaType, knownHash) {
|
|
11975
12149
|
const hash = knownHash ?? sha256(buf);
|
|
11976
12150
|
validateMedia(buf, mediaType);
|
|
11977
|
-
const file =
|
|
12151
|
+
const file = path15.join(cacheDir, cacheFileName(hash, mediaType));
|
|
11978
12152
|
if (await readCachedMedia(cacheDir, hash, mediaType)) return;
|
|
11979
12153
|
await mkdir4(cacheDir, { recursive: true });
|
|
11980
12154
|
const tmp = `${file}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
@@ -12017,7 +12191,7 @@ async function pushTags(provider, specs, lock, opts = {}) {
|
|
|
12017
12191
|
// src/pipeline/adopt.ts
|
|
12018
12192
|
import { existsSync as existsSync11 } from "fs";
|
|
12019
12193
|
import { readFile as readFile9, writeFile as writeFile6 } from "fs/promises";
|
|
12020
|
-
import
|
|
12194
|
+
import path16 from "path";
|
|
12021
12195
|
|
|
12022
12196
|
// src/cache.ts
|
|
12023
12197
|
import { mkdir as mkdir5, readFile as readFile8, rename as rename5, rm as rm5, writeFile as writeFile5 } from "fs/promises";
|
|
@@ -12036,10 +12210,10 @@ function isSha256Hash(value) {
|
|
|
12036
12210
|
function parseCache(value) {
|
|
12037
12211
|
return HashCacheSchema.parse(value);
|
|
12038
12212
|
}
|
|
12039
|
-
async function loadCache(
|
|
12040
|
-
if (!existsSync10(
|
|
12213
|
+
async function loadCache(path52) {
|
|
12214
|
+
if (!existsSync10(path52)) return { version: 1, hashes: {} };
|
|
12041
12215
|
try {
|
|
12042
|
-
const parsed = HashCacheSchema.safeParse(JSON.parse(await readFile8(
|
|
12216
|
+
const parsed = HashCacheSchema.safeParse(JSON.parse(await readFile8(path52, "utf8")));
|
|
12043
12217
|
if (!parsed.success) return { version: 1, hashes: {} };
|
|
12044
12218
|
return {
|
|
12045
12219
|
version: 1,
|
|
@@ -12051,18 +12225,18 @@ async function loadCache(path51) {
|
|
|
12051
12225
|
return { version: 1, hashes: {} };
|
|
12052
12226
|
}
|
|
12053
12227
|
}
|
|
12054
|
-
async function saveCache(
|
|
12228
|
+
async function saveCache(path52, cache) {
|
|
12055
12229
|
const sorted = {};
|
|
12056
12230
|
for (const key of Object.keys(cache.hashes).sort()) {
|
|
12057
12231
|
const hash = cache.hashes[key];
|
|
12058
12232
|
if (!isSha256Hash(hash)) throw new Error(`Refusing to cache invalid SHA-256 for ${key}`);
|
|
12059
12233
|
sorted[key] = hash;
|
|
12060
12234
|
}
|
|
12061
|
-
await mkdir5(pathModule.dirname(pathModule.resolve(
|
|
12062
|
-
const tmp = `${
|
|
12235
|
+
await mkdir5(pathModule.dirname(pathModule.resolve(path52)), { recursive: true });
|
|
12236
|
+
const tmp = `${path52}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
12063
12237
|
try {
|
|
12064
12238
|
await writeFile5(tmp, JSON.stringify({ version: 1, hashes: sorted }, null, 2) + "\n");
|
|
12065
|
-
await rename5(tmp,
|
|
12239
|
+
await rename5(tmp, path52);
|
|
12066
12240
|
} finally {
|
|
12067
12241
|
await rm5(tmp, { force: true });
|
|
12068
12242
|
}
|
|
@@ -12255,7 +12429,7 @@ function formatUnmatchedRemote(objects, limit = 15) {
|
|
|
12255
12429
|
// src/pipeline/adopt-characters.ts
|
|
12256
12430
|
import { existsSync as existsSync12 } from "fs";
|
|
12257
12431
|
import { mkdir as mkdir6, readFile as readFile10, writeFile as writeFile7 } from "fs/promises";
|
|
12258
|
-
import
|
|
12432
|
+
import path17 from "path";
|
|
12259
12433
|
async function adoptCharacters(provider, specs, lock, lockPath, opts) {
|
|
12260
12434
|
const log2 = opts.onProgress ?? (() => {
|
|
12261
12435
|
});
|
|
@@ -12307,11 +12481,11 @@ async function adoptCharacters(provider, specs, lock, lockPath, opts) {
|
|
|
12307
12481
|
const hash = sha256(bytes);
|
|
12308
12482
|
const file = expectedOutputPath(spec, source.role, index, sources.length);
|
|
12309
12483
|
if (existsSync12(file)) {
|
|
12310
|
-
if (sha256(await readFile10(file)) !== hash) result.differing.push(
|
|
12484
|
+
if (sha256(await readFile10(file)) !== hash) result.differing.push(path17.relative(process.cwd(), file));
|
|
12311
12485
|
} else {
|
|
12312
|
-
await mkdir6(
|
|
12486
|
+
await mkdir6(path17.dirname(file), { recursive: true });
|
|
12313
12487
|
await writeFile7(file, bytes);
|
|
12314
|
-
result.written.push(
|
|
12488
|
+
result.written.push(path17.relative(process.cwd(), file));
|
|
12315
12489
|
}
|
|
12316
12490
|
outputs.push({ path: portableOutputPath(file, spec.root), sha256: hash, ...source.role ? { role: source.role } : {} });
|
|
12317
12491
|
}
|
|
@@ -12350,7 +12524,7 @@ async function adoptCharacters(provider, specs, lock, lockPath, opts) {
|
|
|
12350
12524
|
} else {
|
|
12351
12525
|
const south = expectedOutputPath(spec, "south", 0, spec.character.directions);
|
|
12352
12526
|
if (!existsSync12(south)) {
|
|
12353
|
-
result.unmatched.push(`${key}: no remoteId and no ${
|
|
12527
|
+
result.unmatched.push(`${key}: no remoteId and no ${path17.relative(process.cwd(), south)} to match by`);
|
|
12354
12528
|
return false;
|
|
12355
12529
|
}
|
|
12356
12530
|
const localHash = sha256(await readFile10(south));
|
|
@@ -12362,7 +12536,7 @@ async function adoptCharacters(provider, specs, lock, lockPath, opts) {
|
|
|
12362
12536
|
}
|
|
12363
12537
|
}
|
|
12364
12538
|
if (!match) {
|
|
12365
|
-
result.unmatched.push(`${key}: no character's south rotation has the bytes of ${
|
|
12539
|
+
result.unmatched.push(`${key}: no character's south rotation has the bytes of ${path17.relative(process.cwd(), south)}`);
|
|
12366
12540
|
return false;
|
|
12367
12541
|
}
|
|
12368
12542
|
}
|
|
@@ -12432,7 +12606,7 @@ async function adoptCharacters(provider, specs, lock, lockPath, opts) {
|
|
|
12432
12606
|
if (!existsSync12(first)) {
|
|
12433
12607
|
const byName = candidates.find((a) => a.name === characterAnimationName(spec));
|
|
12434
12608
|
if (!byName) {
|
|
12435
|
-
result.unmatched.push(`${key}: no remoteId and no ${
|
|
12609
|
+
result.unmatched.push(`${key}: no remoteId and no ${path17.relative(process.cwd(), first)} to match by`);
|
|
12436
12610
|
return false;
|
|
12437
12611
|
}
|
|
12438
12612
|
match = byName;
|
|
@@ -12445,7 +12619,7 @@ async function adoptCharacters(provider, specs, lock, lockPath, opts) {
|
|
|
12445
12619
|
}
|
|
12446
12620
|
}
|
|
12447
12621
|
if (!match) {
|
|
12448
|
-
result.unmatched.push(`${key}: no ${animation.direction} animation of character ${parentId} starts with the bytes of ${
|
|
12622
|
+
result.unmatched.push(`${key}: no ${animation.direction} animation of character ${parentId} starts with the bytes of ${path17.relative(process.cwd(), first)}`);
|
|
12449
12623
|
return false;
|
|
12450
12624
|
}
|
|
12451
12625
|
}
|
|
@@ -12507,7 +12681,7 @@ async function adoptCharacters(provider, specs, lock, lockPath, opts) {
|
|
|
12507
12681
|
// src/pipeline/salvage.ts
|
|
12508
12682
|
import { readFile as readFile11 } from "fs/promises";
|
|
12509
12683
|
import { existsSync as existsSync13 } from "fs";
|
|
12510
|
-
import
|
|
12684
|
+
import path18 from "path";
|
|
12511
12685
|
function providerClaimId(provider, objectId) {
|
|
12512
12686
|
return `${encodeURIComponent(provider)}:${encodeURIComponent(objectId)}`;
|
|
12513
12687
|
}
|
|
@@ -12684,20 +12858,20 @@ function matchStyleByPattern(prompt, manifest) {
|
|
|
12684
12858
|
return null;
|
|
12685
12859
|
}
|
|
12686
12860
|
async function loadSiblingManifests(ownManifestPath, workspaceManifestPaths, claimPaths) {
|
|
12687
|
-
const own =
|
|
12861
|
+
const own = path18.resolve(ownManifestPath);
|
|
12688
12862
|
const siblingManifestPaths = [
|
|
12689
12863
|
.../* @__PURE__ */ new Set([
|
|
12690
12864
|
...workspaceManifestPaths,
|
|
12691
|
-
...claimPaths.map((c) =>
|
|
12865
|
+
...claimPaths.map((c) => path18.join(path18.dirname(path18.resolve(c)), "pixelkiln.manifest.json"))
|
|
12692
12866
|
])
|
|
12693
12867
|
];
|
|
12694
12868
|
const siblings = [];
|
|
12695
12869
|
for (const siblingManifestPath of siblingManifestPaths) {
|
|
12696
|
-
if (
|
|
12870
|
+
if (path18.resolve(siblingManifestPath) === own) continue;
|
|
12697
12871
|
if (!existsSync13(siblingManifestPath)) continue;
|
|
12698
12872
|
try {
|
|
12699
12873
|
const { manifest } = await loadManifest(siblingManifestPath);
|
|
12700
|
-
siblings.push({ label:
|
|
12874
|
+
siblings.push({ label: path18.basename(path18.dirname(siblingManifestPath)), manifest });
|
|
12701
12875
|
} catch {
|
|
12702
12876
|
}
|
|
12703
12877
|
}
|
|
@@ -12819,7 +12993,7 @@ async function applyTags(provider, decisions, existing, opts = {}) {
|
|
|
12819
12993
|
// src/workspace.ts
|
|
12820
12994
|
import { mkdir as mkdir7, readFile as readFile12, rename as rename6, rm as rm6, writeFile as writeFile8 } from "fs/promises";
|
|
12821
12995
|
import { existsSync as existsSync14 } from "fs";
|
|
12822
|
-
import
|
|
12996
|
+
import path19 from "path";
|
|
12823
12997
|
import { z as z5 } from "zod";
|
|
12824
12998
|
var WorkspaceProjectSchema = z5.object({
|
|
12825
12999
|
id: z5.string().min(1),
|
|
@@ -12861,7 +13035,7 @@ async function saveWorkspace(workspacePath, ws) {
|
|
|
12861
13035
|
version: 1,
|
|
12862
13036
|
projects: [...ws.projects].sort((a, b) => a.id.localeCompare(b.id))
|
|
12863
13037
|
};
|
|
12864
|
-
await mkdir7(
|
|
13038
|
+
await mkdir7(path19.dirname(path19.resolve(workspacePath)), { recursive: true });
|
|
12865
13039
|
const tmp = `${workspacePath}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
12866
13040
|
try {
|
|
12867
13041
|
await writeFile8(tmp, JSON.stringify(sorted, null, 2) + "\n");
|
|
@@ -12871,12 +13045,12 @@ async function saveWorkspace(workspacePath, ws) {
|
|
|
12871
13045
|
}
|
|
12872
13046
|
}
|
|
12873
13047
|
function toPortablePath(dir, absolute) {
|
|
12874
|
-
return
|
|
13048
|
+
return path19.relative(dir, absolute).split(path19.sep).join("/");
|
|
12875
13049
|
}
|
|
12876
13050
|
function resolveProject(dir, project) {
|
|
12877
13051
|
return {
|
|
12878
|
-
manifestPath:
|
|
12879
|
-
lockPath:
|
|
13052
|
+
manifestPath: path19.resolve(dir, project.manifest.split("/").join(path19.sep)),
|
|
13053
|
+
lockPath: path19.resolve(dir, project.lock.split("/").join(path19.sep))
|
|
12880
13054
|
};
|
|
12881
13055
|
}
|
|
12882
13056
|
function validateWorkspace(ws, dir) {
|
|
@@ -12889,7 +13063,7 @@ function validateWorkspace(ws, dir) {
|
|
|
12889
13063
|
const { manifestPath, lockPath } = resolveProject(dir, project);
|
|
12890
13064
|
lockOwners.set(lockPath, [...lockOwners.get(lockPath) ?? [], project.id]);
|
|
12891
13065
|
manifestOwners.set(manifestPath, [...manifestOwners.get(manifestPath) ?? [], project.id]);
|
|
12892
|
-
if (
|
|
13066
|
+
if (path19.isAbsolute(project.manifest) || path19.isAbsolute(project.lock)) {
|
|
12893
13067
|
diagnostics.push({
|
|
12894
13068
|
id: "absolute-path",
|
|
12895
13069
|
level: "warning",
|
|
@@ -12951,7 +13125,7 @@ function validateWorkspace(ws, dir) {
|
|
|
12951
13125
|
|
|
12952
13126
|
// src/pick/salvage-server.ts
|
|
12953
13127
|
import { mkdir as mkdir8, writeFile as writeFile9, readFile as readFile14 } from "fs/promises";
|
|
12954
|
-
import
|
|
13128
|
+
import path20 from "path";
|
|
12955
13129
|
|
|
12956
13130
|
// src/pick/salvage-sheet.ts
|
|
12957
13131
|
var escapeHtml = (s) => s.replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
@@ -13216,7 +13390,7 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
13216
13390
|
});
|
|
13217
13391
|
const html = renderSalvageSheet(orphans, {
|
|
13218
13392
|
styleId: ctx.styleId,
|
|
13219
|
-
importDir:
|
|
13393
|
+
importDir: path20.relative(process.cwd(), ctx.importDir) || "."
|
|
13220
13394
|
});
|
|
13221
13395
|
const byId = new Map(orphans.map((o) => [o.id, o]));
|
|
13222
13396
|
const existingTags = new Map(orphans.map((o) => [o.id, o.tags]));
|
|
@@ -13272,9 +13446,9 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
13272
13446
|
if (!buf.subarray(0, 8).equals(PNG_SIGNATURE2)) throw new Error("not a PNG");
|
|
13273
13447
|
decodePng(buf);
|
|
13274
13448
|
const assetId = idFromPrompt(orphan.prompt, taken);
|
|
13275
|
-
const rel =
|
|
13276
|
-
const outFile =
|
|
13277
|
-
await mkdir8(
|
|
13449
|
+
const rel = path20.join("_salvaged", `${assetId}.png`);
|
|
13450
|
+
const outFile = path20.resolve(ctx.importDir, rel);
|
|
13451
|
+
await mkdir8(path20.dirname(outFile), { recursive: true });
|
|
13278
13452
|
await writeFile9(outFile, buf);
|
|
13279
13453
|
ctx.manifest.assets[assetId] = {
|
|
13280
13454
|
prompt: orphan.prompt,
|
|
@@ -13307,7 +13481,7 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
13307
13481
|
sourceUrl: durableSource,
|
|
13308
13482
|
sourceUrls: durableSource ? [{ url: durableSource }] : [],
|
|
13309
13483
|
outputs: [{
|
|
13310
|
-
path: portableOutputPath(outFile,
|
|
13484
|
+
path: portableOutputPath(outFile, path20.dirname(ctx.manifestPath)),
|
|
13311
13485
|
sha256: sha256(buf)
|
|
13312
13486
|
}],
|
|
13313
13487
|
submittedAt: orphan.createdAt,
|
|
@@ -13342,19 +13516,19 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
13342
13516
|
}
|
|
13343
13517
|
|
|
13344
13518
|
// src/cli/project.ts
|
|
13345
|
-
import
|
|
13519
|
+
import path22 from "path";
|
|
13346
13520
|
import { existsSync as existsSync15 } from "fs";
|
|
13347
13521
|
|
|
13348
13522
|
// src/project.ts
|
|
13349
|
-
import
|
|
13523
|
+
import path21 from "path";
|
|
13350
13524
|
async function openProject(manifestPath = "pixelkiln.manifest.json", opts = {}) {
|
|
13351
|
-
const absolute =
|
|
13352
|
-
const root =
|
|
13525
|
+
const absolute = path21.resolve(manifestPath);
|
|
13526
|
+
const root = path21.dirname(absolute);
|
|
13353
13527
|
if (opts.env !== false) {
|
|
13354
13528
|
loadEnvFiles(root);
|
|
13355
|
-
if (
|
|
13529
|
+
if (path21.resolve(process.cwd()) !== root) loadEnvFiles(process.cwd());
|
|
13356
13530
|
}
|
|
13357
|
-
const lockPath =
|
|
13531
|
+
const lockPath = path21.resolve(opts.lockPath ?? path21.join(root, "pixelkiln.lock.json"));
|
|
13358
13532
|
const loaded = await loadManifest(absolute);
|
|
13359
13533
|
const specs = await resolveSpecs(loaded, { styles: opts.styles ?? [], assets: opts.assets ?? [] });
|
|
13360
13534
|
const lock = await loadLock(lockPath);
|
|
@@ -13485,7 +13659,7 @@ async function workspaceStatus(ws, dir) {
|
|
|
13485
13659
|
|
|
13486
13660
|
// src/cli/project.ts
|
|
13487
13661
|
async function provenanceFile(id, file) {
|
|
13488
|
-
const absolute =
|
|
13662
|
+
const absolute = path22.resolve(file);
|
|
13489
13663
|
return {
|
|
13490
13664
|
id,
|
|
13491
13665
|
path: absolute,
|
|
@@ -13564,7 +13738,7 @@ async function requireCompleteWorkspaceClaims(workspacePath) {
|
|
|
13564
13738
|
if (!existsSync15(workspacePath)) {
|
|
13565
13739
|
throw new ProjectError(`Workspace catalog not found: ${workspacePath}`);
|
|
13566
13740
|
}
|
|
13567
|
-
const dir =
|
|
13741
|
+
const dir = path22.dirname(path22.resolve(workspacePath));
|
|
13568
13742
|
const ws = await loadWorkspace(workspacePath);
|
|
13569
13743
|
const diagnostics = validateWorkspace(ws, dir);
|
|
13570
13744
|
const errors = diagnostics.filter((d) => d.level === "error");
|
|
@@ -13587,9 +13761,9 @@ async function openAccountProject(args) {
|
|
|
13587
13761
|
return project;
|
|
13588
13762
|
}
|
|
13589
13763
|
async function open(args) {
|
|
13590
|
-
if (!existsSync15(
|
|
13764
|
+
if (!existsSync15(path22.resolve(args.manifest))) {
|
|
13591
13765
|
throw new ProjectError(
|
|
13592
|
-
`No manifest at ${
|
|
13766
|
+
`No manifest at ${path22.resolve(args.manifest)}. Pass --manifest, or run \`pixelkiln init --from <dir>\`.`
|
|
13593
13767
|
);
|
|
13594
13768
|
}
|
|
13595
13769
|
const project = await openProject(args.manifest, { lockPath: args.lock, styles: args.styles, assets: args.assets });
|
|
@@ -13631,7 +13805,7 @@ function providerCache(mode2) {
|
|
|
13631
13805
|
|
|
13632
13806
|
// src/cli/commands/account.ts
|
|
13633
13807
|
async function runBalance(args) {
|
|
13634
|
-
loadEnvFiles(
|
|
13808
|
+
loadEnvFiles(path23.dirname(path23.resolve(args.manifest)));
|
|
13635
13809
|
loadEnvFiles(process.cwd());
|
|
13636
13810
|
const loaded = await loadManifest(args.manifest);
|
|
13637
13811
|
const selectedProvider = accountProviderId(loaded.manifest, args.provider, "balance");
|
|
@@ -13704,12 +13878,12 @@ async function runAdopt(args) {
|
|
|
13704
13878
|
tagged ${n} object(s) upstream`);
|
|
13705
13879
|
}
|
|
13706
13880
|
if (args.writePrompts) {
|
|
13707
|
-
const { filled, stillEmpty } = await writePromptsBack(
|
|
13881
|
+
const { filled, stillEmpty } = await writePromptsBack(path23.resolve(args.manifest), lock, {
|
|
13708
13882
|
onProgress: log,
|
|
13709
13883
|
provider: provider.id,
|
|
13710
13884
|
assetIds: specs.map((spec) => spec.assetId)
|
|
13711
13885
|
});
|
|
13712
|
-
log(` recovered ${filled} prompt(s) into ${
|
|
13886
|
+
log(` recovered ${filled} prompt(s) into ${path23.relative(process.cwd(), args.manifest)}`);
|
|
13713
13887
|
const reloaded = await loadManifest(args.manifest);
|
|
13714
13888
|
const rebased = (await resolveSpecs(reloaded, {
|
|
13715
13889
|
styles: args.styles,
|
|
@@ -13739,12 +13913,12 @@ async function runSalvage2(args) {
|
|
|
13739
13913
|
const provider = providerFor(accountProvider);
|
|
13740
13914
|
const jsonMode = args.dryRun && args.json;
|
|
13741
13915
|
const diag = jsonMode ? (msg = "") => console.error(msg) : log;
|
|
13742
|
-
const ownLock =
|
|
13916
|
+
const ownLock = path23.resolve(args.lock);
|
|
13743
13917
|
let workspaceProjects = [];
|
|
13744
13918
|
let workspaceDir = "";
|
|
13745
13919
|
if (args.workspace) {
|
|
13746
|
-
const workspacePath =
|
|
13747
|
-
workspaceDir =
|
|
13920
|
+
const workspacePath = path23.resolve(args.workspace);
|
|
13921
|
+
workspaceDir = path23.dirname(workspacePath);
|
|
13748
13922
|
const complete = await requireCompleteWorkspaceClaims(workspacePath);
|
|
13749
13923
|
workspaceProjects = complete.ws.projects;
|
|
13750
13924
|
for (const d of complete.diagnostics) diag(` WARN ${d.id}: ${d.message}`);
|
|
@@ -13754,11 +13928,11 @@ async function runSalvage2(args) {
|
|
|
13754
13928
|
.../* @__PURE__ */ new Set([
|
|
13755
13929
|
...workspaceLockPaths,
|
|
13756
13930
|
...existsSync16(ownLock) ? [ownLock] : [],
|
|
13757
|
-
...args.claims.map((c) =>
|
|
13931
|
+
...args.claims.map((c) => path23.resolve(c))
|
|
13758
13932
|
])
|
|
13759
13933
|
];
|
|
13760
13934
|
diag(` claim set (${lockPaths.length} lockfile(s)):`);
|
|
13761
|
-
for (const p of lockPaths) diag(` ${
|
|
13935
|
+
for (const p of lockPaths) diag(` ${path23.relative(process.cwd(), p)}`);
|
|
13762
13936
|
if (!args.claims.length && !args.workspace) {
|
|
13763
13937
|
diag(
|
|
13764
13938
|
`
|
|
@@ -13766,7 +13940,7 @@ async function runSalvage2(args) {
|
|
|
13766
13940
|
pass every other project's lockfile via --claims a.json,b.json, or
|
|
13767
13941
|
register every project in a workspace catalog and pass --workspace.`
|
|
13768
13942
|
);
|
|
13769
|
-
} else if (args.workspace && !workspaceProjects.some((p) => resolveProject(workspaceDir, p).manifestPath ===
|
|
13943
|
+
} else if (args.workspace && !workspaceProjects.some((p) => resolveProject(workspaceDir, p).manifestPath === path23.resolve(args.manifest))) {
|
|
13770
13944
|
diag(
|
|
13771
13945
|
`
|
|
13772
13946
|
This project's manifest is not registered in the workspace catalog. Its own
|
|
@@ -13885,7 +14059,7 @@ async function runSalvage2(args) {
|
|
|
13885
14059
|
manifestPath: loaded.path,
|
|
13886
14060
|
manifest: accountManifest,
|
|
13887
14061
|
styleId,
|
|
13888
|
-
importDir:
|
|
14062
|
+
importDir: path23.resolve(loaded.root, style.outDir),
|
|
13889
14063
|
lock,
|
|
13890
14064
|
lockPath: args.lock
|
|
13891
14065
|
},
|
|
@@ -14012,20 +14186,20 @@ async function runTag(args) {
|
|
|
14012
14186
|
}
|
|
14013
14187
|
|
|
14014
14188
|
// src/cli/commands/edit.ts
|
|
14015
|
-
import
|
|
14189
|
+
import path26 from "path";
|
|
14016
14190
|
|
|
14017
14191
|
// src/pipeline/hand-edit.ts
|
|
14018
14192
|
import { existsSync as existsSync17 } from "fs";
|
|
14019
14193
|
import { copyFile, mkdir as mkdir9, readFile as readFile16, rename as rename8, rm as rm8, writeFile as writeFile11 } from "fs/promises";
|
|
14020
|
-
import
|
|
14194
|
+
import path25 from "path";
|
|
14021
14195
|
import { z as z7 } from "zod";
|
|
14022
14196
|
|
|
14023
14197
|
// src/manifest-edit.ts
|
|
14024
14198
|
import { readFile as readFile15, rename as rename7, rm as rm7, writeFile as writeFile10 } from "fs/promises";
|
|
14025
|
-
import
|
|
14199
|
+
import path24 from "path";
|
|
14026
14200
|
import { z as z6 } from "zod";
|
|
14027
14201
|
var HexSha = z6.string().regex(/^[0-9a-f]{64}$/);
|
|
14028
|
-
var ProjectFileSchema = z6.string().min(1).refine((file) => !
|
|
14202
|
+
var ProjectFileSchema = z6.string().min(1).refine((file) => !path24.isAbsolute(file) && !file.split(/[\\/]/).includes(".."), "must be a path inside the project");
|
|
14029
14203
|
var AssetPatchSchema = z6.object({
|
|
14030
14204
|
prompt: z6.string().optional(),
|
|
14031
14205
|
/** Per-style override for the style being viewed; null removes it. */
|
|
@@ -14444,7 +14618,7 @@ function applyEdit(raw, edit) {
|
|
|
14444
14618
|
}
|
|
14445
14619
|
}
|
|
14446
14620
|
async function withCandidate(manifestPath, edit, use, unchanged) {
|
|
14447
|
-
const absolute =
|
|
14621
|
+
const absolute = path24.resolve(manifestPath);
|
|
14448
14622
|
const original = await readFile15(absolute, "utf8");
|
|
14449
14623
|
if (sha256(original) !== edit.expectedSha256) throw new ManifestDriftError(absolute);
|
|
14450
14624
|
let raw;
|
|
@@ -14458,9 +14632,9 @@ async function withCandidate(manifestPath, edit, use, unchanged) {
|
|
|
14458
14632
|
applyEdit(raw, edit);
|
|
14459
14633
|
const next = serializeLike(original, raw);
|
|
14460
14634
|
if (next === original) return unchanged(absolute, original);
|
|
14461
|
-
const tmp =
|
|
14462
|
-
|
|
14463
|
-
`.${
|
|
14635
|
+
const tmp = path24.join(
|
|
14636
|
+
path24.dirname(absolute),
|
|
14637
|
+
`.${path24.basename(absolute)}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`
|
|
14464
14638
|
);
|
|
14465
14639
|
try {
|
|
14466
14640
|
await writeFile10(tmp, next);
|
|
@@ -14533,11 +14707,11 @@ async function priceManifestEdit(manifestPath, edit, opts = {}) {
|
|
|
14533
14707
|
var HAND_EDIT_DIR = "edits";
|
|
14534
14708
|
function handEditPath(loaded, spec) {
|
|
14535
14709
|
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
|
|
14710
|
+
const outDir = path25.resolve(loaded.root, style?.outDir ?? ".");
|
|
14711
|
+
const relative = path25.relative(outDir, spec.outFile);
|
|
14712
|
+
const inside = relative && !relative.startsWith("..") && !path25.isAbsolute(relative);
|
|
14713
|
+
const file = inside ? relative : path25.basename(spec.outFile);
|
|
14714
|
+
return path25.join(outDir, HAND_EDIT_DIR, file.replace(/\.gif$/i, ".png"));
|
|
14541
14715
|
}
|
|
14542
14716
|
function handEditBases(spec, lock) {
|
|
14543
14717
|
const entry = lock.entries[lockKey(spec.styleId, spec.assetId)];
|
|
@@ -14579,7 +14753,7 @@ async function startHandEdit(loaded, lock, spec, opts = {}) {
|
|
|
14579
14753
|
`${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
14754
|
);
|
|
14581
14755
|
}
|
|
14582
|
-
const existing = spec.source ?
|
|
14756
|
+
const existing = spec.source ? path25.resolve(loaded.root, spec.source) : null;
|
|
14583
14757
|
const editPath = existing ?? handEditPath(loaded, spec);
|
|
14584
14758
|
const members = handEditMembers(loaded, spec, lock, editPath);
|
|
14585
14759
|
let created = false;
|
|
@@ -14590,7 +14764,7 @@ async function startHandEdit(loaded, lock, spec, opts = {}) {
|
|
|
14590
14764
|
`${spec.styleId}/${spec.assetId} has no generated PNG on disk to start from; generate or restore it first`
|
|
14591
14765
|
);
|
|
14592
14766
|
}
|
|
14593
|
-
await mkdir9(
|
|
14767
|
+
await mkdir9(path25.dirname(member.path), { recursive: true });
|
|
14594
14768
|
await copyFile(member.base, member.path);
|
|
14595
14769
|
created = true;
|
|
14596
14770
|
}
|
|
@@ -14728,7 +14902,7 @@ async function saveHandEdit(loaded, lock, spec, input) {
|
|
|
14728
14902
|
editor: input.editor,
|
|
14729
14903
|
protocol: input.protocol,
|
|
14730
14904
|
savedAt: (input.now ?? /* @__PURE__ */ new Date()).toISOString(),
|
|
14731
|
-
project: projectPath ?
|
|
14905
|
+
project: projectPath ? path25.basename(projectPath) : null,
|
|
14732
14906
|
outputs: incoming.map((frame, index) => ({
|
|
14733
14907
|
role: frame.role,
|
|
14734
14908
|
basedOn: basedOnHash(entry, index),
|
|
@@ -14788,7 +14962,7 @@ async function runEdit(args) {
|
|
|
14788
14962
|
const set = started.members.length > 1;
|
|
14789
14963
|
const kind = spec.generator === "frames" ? "frames" : "members";
|
|
14790
14964
|
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 ?? ""} ${
|
|
14965
|
+
if (set) for (const member of started.members) log(` ${member.role ?? ""} ${path26.relative(process.cwd(), member.path)}`);
|
|
14792
14966
|
if (args.noOpen) {
|
|
14793
14967
|
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
14968
|
} else {
|
|
@@ -14798,24 +14972,24 @@ async function runEdit(args) {
|
|
|
14798
14972
|
}
|
|
14799
14973
|
|
|
14800
14974
|
// src/cli/commands/gallery.ts
|
|
14801
|
-
import
|
|
14975
|
+
import path36 from "path";
|
|
14802
14976
|
import { existsSync as existsSync27 } from "fs";
|
|
14803
14977
|
|
|
14804
14978
|
// src/gallery/snapshot.ts
|
|
14805
|
-
import { createHash as
|
|
14979
|
+
import { createHash as createHash4 } from "crypto";
|
|
14806
14980
|
import { existsSync as existsSync22 } from "fs";
|
|
14807
14981
|
import { readFile as readFile20, stat as stat2 } from "fs/promises";
|
|
14808
|
-
import
|
|
14982
|
+
import path32 from "path";
|
|
14809
14983
|
|
|
14810
14984
|
// src/gallery/edit.ts
|
|
14811
14985
|
import { readFile as readFile19, rename as rename9, writeFile as writeFile15 } from "fs/promises";
|
|
14812
|
-
import
|
|
14986
|
+
import path30 from "path";
|
|
14813
14987
|
import { z as z11 } from "zod";
|
|
14814
14988
|
|
|
14815
14989
|
// src/gallery/upload.ts
|
|
14816
14990
|
import { existsSync as existsSync18 } from "fs";
|
|
14817
14991
|
import { mkdir as mkdir10, writeFile as writeFile12 } from "fs/promises";
|
|
14818
|
-
import
|
|
14992
|
+
import path27 from "path";
|
|
14819
14993
|
import { z as z8 } from "zod";
|
|
14820
14994
|
var MAX_UPLOAD_BYTES = 8 * 1024 * 1024;
|
|
14821
14995
|
var UploadImageSchema = z8.object({
|
|
@@ -14828,12 +15002,12 @@ var UploadImageSchema = z8.object({
|
|
|
14828
15002
|
overwrite: z8.boolean().optional()
|
|
14829
15003
|
}).strict();
|
|
14830
15004
|
async function saveProjectImage(root, request) {
|
|
14831
|
-
const file =
|
|
14832
|
-
const relative =
|
|
14833
|
-
if (relative.startsWith("..") ||
|
|
15005
|
+
const file = path27.resolve(root, request.path);
|
|
15006
|
+
const relative = path27.relative(root, file);
|
|
15007
|
+
if (relative.startsWith("..") || path27.isAbsolute(relative)) {
|
|
14834
15008
|
throw new ManifestEditError("an upload must stay inside the project");
|
|
14835
15009
|
}
|
|
14836
|
-
const extension =
|
|
15010
|
+
const extension = path27.extname(file).toLowerCase();
|
|
14837
15011
|
const expected = extension === ".png" ? "png" : extension === ".jpg" || extension === ".jpeg" ? "jpeg" : null;
|
|
14838
15012
|
if (!expected) throw new ManifestEditError("an upload must be a .png, .jpg, or .jpeg path");
|
|
14839
15013
|
const bytes = Buffer.from(request.base64, "base64");
|
|
@@ -14846,17 +15020,17 @@ async function saveProjectImage(root, request) {
|
|
|
14846
15020
|
throw new ManifestEditError(`the upload is a ${metadata.format.toUpperCase()}; name it ${metadata.format === "png" ? ".png" : ".jpg"}`);
|
|
14847
15021
|
}
|
|
14848
15022
|
if (existsSync18(file) && !request.overwrite) {
|
|
14849
|
-
throw new ManifestEditError(`${relative.split(
|
|
15023
|
+
throw new ManifestEditError(`${relative.split(path27.sep).join("/")} already exists; choose another name, or replace it`);
|
|
14850
15024
|
}
|
|
14851
|
-
await mkdir10(
|
|
15025
|
+
await mkdir10(path27.dirname(file), { recursive: true });
|
|
14852
15026
|
await writeFile12(file, bytes);
|
|
14853
|
-
return relative.split(
|
|
15027
|
+
return relative.split(path27.sep).join("/");
|
|
14854
15028
|
}
|
|
14855
15029
|
|
|
14856
15030
|
// src/gallery/image-revision.ts
|
|
14857
15031
|
import { existsSync as existsSync19 } from "fs";
|
|
14858
15032
|
import { readFile as readFile17, rm as rm9, writeFile as writeFile13 } from "fs/promises";
|
|
14859
|
-
import
|
|
15033
|
+
import path28 from "path";
|
|
14860
15034
|
import { z as z9 } from "zod";
|
|
14861
15035
|
var InpaintRevisionRequestSchema = z9.object({
|
|
14862
15036
|
action: z9.literal("create-inpaint-revision"),
|
|
@@ -14875,14 +15049,14 @@ var InpaintRevisionRequestSchema = z9.object({
|
|
|
14875
15049
|
overwrite: z9.boolean().optional()
|
|
14876
15050
|
}).strict();
|
|
14877
15051
|
async function createInpaintRevision(manifestPath, request) {
|
|
14878
|
-
const root =
|
|
14879
|
-
if (
|
|
15052
|
+
const root = path28.dirname(path28.resolve(manifestPath));
|
|
15053
|
+
if (path28.extname(request.maskPath).toLowerCase() !== ".png") {
|
|
14880
15054
|
throw new ManifestEditError("the mask must be a .png path inside the project");
|
|
14881
15055
|
}
|
|
14882
15056
|
const bytes = Buffer.from(request.maskBase64, "base64");
|
|
14883
15057
|
const metadata = imageMetadata(bytes);
|
|
14884
15058
|
if (!metadata || metadata.format !== "png") throw new ManifestEditError("the mask is not a readable PNG");
|
|
14885
|
-
const file =
|
|
15059
|
+
const file = path28.resolve(root, request.maskPath);
|
|
14886
15060
|
const previous = existsSync19(file) ? await readFile17(file) : null;
|
|
14887
15061
|
const mask = await saveProjectImage(root, {
|
|
14888
15062
|
action: "upload-image",
|
|
@@ -14912,7 +15086,7 @@ async function createInpaintRevision(manifestPath, request) {
|
|
|
14912
15086
|
// src/gallery/skeleton.ts
|
|
14913
15087
|
import { existsSync as existsSync20 } from "fs";
|
|
14914
15088
|
import { mkdir as mkdir11, readFile as readFile18, rm as rm10, writeFile as writeFile14 } from "fs/promises";
|
|
14915
|
-
import
|
|
15089
|
+
import path29 from "path";
|
|
14916
15090
|
import { z as z10 } from "zod";
|
|
14917
15091
|
var SkeletonEstimateRequestSchema = z10.object({
|
|
14918
15092
|
project: z10.string().min(1).optional(),
|
|
@@ -14951,7 +15125,7 @@ var SkeletonKeypointsUpdateSchema = z10.object({
|
|
|
14951
15125
|
}).strict();
|
|
14952
15126
|
var DEFAULT_ESTIMATE_LIMIT = 10;
|
|
14953
15127
|
function skeletonSourceFile(spec) {
|
|
14954
|
-
if (spec.source) return
|
|
15128
|
+
if (spec.source) return path29.resolve(spec.root, spec.source);
|
|
14955
15129
|
if (spec.quality && existsSync20(spec.quality.outFile)) return spec.quality.outFile;
|
|
14956
15130
|
return spec.outFile;
|
|
14957
15131
|
}
|
|
@@ -14976,7 +15150,7 @@ function createGallerySkeletonHandlers(opts) {
|
|
|
14976
15150
|
if (!existsSync20(file)) throw new ManifestEditError(`${key} has no image on disk to estimate from`);
|
|
14977
15151
|
const bytes = await readFile18(file);
|
|
14978
15152
|
const metadata = imageMetadata(bytes);
|
|
14979
|
-
if (!metadata) throw new ManifestEditError(`${
|
|
15153
|
+
if (!metadata) throw new ManifestEditError(`${path29.basename(file)} is not a readable PNG or JPEG`);
|
|
14980
15154
|
const problem = estimateSkeletonSizeProblem(metadata.width, metadata.height);
|
|
14981
15155
|
if (problem) throw new ManifestEditError(`${key} is ${problem}`);
|
|
14982
15156
|
if (used >= limit) {
|
|
@@ -14993,7 +15167,7 @@ function createGallerySkeletonHandlers(opts) {
|
|
|
14993
15167
|
log2(` estimated a skeleton for ${key} (PixelLab estimate-skeleton, ${used}/${limit} this session${billed ? `, billed ${billed.amount} ${billed.unit}` : ""})`);
|
|
14994
15168
|
return {
|
|
14995
15169
|
set: scaffoldSkeletonSet(res.keypoints, request.frames ?? DEFAULT_SCAFFOLD_FRAMES),
|
|
14996
|
-
source:
|
|
15170
|
+
source: path29.relative(ctx.loaded.root, file).split(path29.sep).join("/"),
|
|
14997
15171
|
session: status()
|
|
14998
15172
|
};
|
|
14999
15173
|
}
|
|
@@ -15007,10 +15181,10 @@ function billedAmount(usage) {
|
|
|
15007
15181
|
return typeof amount === "number" && Number.isFinite(amount) ? { unit: type, amount } : null;
|
|
15008
15182
|
}
|
|
15009
15183
|
async function createSkeletonAnimation(manifestPath, request) {
|
|
15010
|
-
const root =
|
|
15011
|
-
const file =
|
|
15012
|
-
const relative =
|
|
15013
|
-
if (!request.keypointsFile.endsWith(".json") || relative.startsWith("..") ||
|
|
15184
|
+
const root = path29.dirname(path29.resolve(manifestPath));
|
|
15185
|
+
const file = path29.resolve(root, request.keypointsFile);
|
|
15186
|
+
const relative = path29.relative(root, file);
|
|
15187
|
+
if (!request.keypointsFile.endsWith(".json") || relative.startsWith("..") || path29.isAbsolute(relative)) {
|
|
15014
15188
|
throw new ManifestEditError("the keypoints file must be a .json path inside the project");
|
|
15015
15189
|
}
|
|
15016
15190
|
const existed = existsSync20(file);
|
|
@@ -15020,7 +15194,7 @@ async function createSkeletonAnimation(manifestPath, request) {
|
|
|
15020
15194
|
throw new ManifestEditError(`${request.keypointsFile} already exists; choose another path, or replace it`);
|
|
15021
15195
|
}
|
|
15022
15196
|
if (existed) previous = await readFile18(file);
|
|
15023
|
-
await mkdir11(
|
|
15197
|
+
await mkdir11(path29.dirname(file), { recursive: true });
|
|
15024
15198
|
await writeFile14(file, JSON.stringify(request.set, null, 2) + "\n");
|
|
15025
15199
|
} else if (!existed) {
|
|
15026
15200
|
throw new ManifestEditError(`${request.keypointsFile} does not exist; paste poses or estimate them from the sprite`);
|
|
@@ -15036,7 +15210,7 @@ async function createSkeletonAnimation(manifestPath, request) {
|
|
|
15036
15210
|
revision: {
|
|
15037
15211
|
mode: "animate-skeleton",
|
|
15038
15212
|
from: request.from,
|
|
15039
|
-
keypointsFile: relative.split(
|
|
15213
|
+
keypointsFile: relative.split(path29.sep).join("/"),
|
|
15040
15214
|
direction: request.direction,
|
|
15041
15215
|
...request.description ? { description: request.description } : {},
|
|
15042
15216
|
...request.skeletonTemplate ? { skeletonTemplate: request.skeletonTemplate } : {}
|
|
@@ -15057,19 +15231,19 @@ async function updateSkeletonKeypoints(ctx, request) {
|
|
|
15057
15231
|
if (!spec) throw new ManifestEditError(`"${key}" is not declared by the manifest`);
|
|
15058
15232
|
const file = spec.revision?.mode === "animate-skeleton" ? spec.revision.keypointsFile : void 0;
|
|
15059
15233
|
if (!file) throw new ManifestEditError(`${key} is not an animate-skeleton revision`);
|
|
15060
|
-
const relative =
|
|
15061
|
-
if (relative.startsWith("..") ||
|
|
15234
|
+
const relative = path29.relative(ctx.loaded.root, file);
|
|
15235
|
+
if (relative.startsWith("..") || path29.isAbsolute(relative)) {
|
|
15062
15236
|
throw new ManifestEditError(`${key}'s keypoints file is outside the project; edit it by hand`);
|
|
15063
15237
|
}
|
|
15064
15238
|
const current = existsSync20(file) ? sha256(await readFile18(file)) : null;
|
|
15065
15239
|
if (current !== request.expectedSha256) {
|
|
15066
15240
|
throw Object.assign(
|
|
15067
|
-
new Error(`${relative.split(
|
|
15241
|
+
new Error(`${relative.split(path29.sep).join("/")} changed on disk since this page loaded it; press Refresh and edit again`),
|
|
15068
15242
|
{ status: 409 }
|
|
15069
15243
|
);
|
|
15070
15244
|
}
|
|
15071
15245
|
await writeFile14(file, JSON.stringify(request.set, null, 2) + "\n");
|
|
15072
|
-
return relative.split(
|
|
15246
|
+
return relative.split(path29.sep).join("/");
|
|
15073
15247
|
}
|
|
15074
15248
|
|
|
15075
15249
|
// src/gallery/edit.ts
|
|
@@ -15123,7 +15297,7 @@ function createGalleryEditHandler(opts) {
|
|
|
15123
15297
|
const history = /* @__PURE__ */ new Map();
|
|
15124
15298
|
const manifestOf = async (project) => {
|
|
15125
15299
|
try {
|
|
15126
|
-
return
|
|
15300
|
+
return path30.resolve(await opts.manifestFor(typeof project === "string" ? project : void 0));
|
|
15127
15301
|
} catch {
|
|
15128
15302
|
return null;
|
|
15129
15303
|
}
|
|
@@ -15146,7 +15320,7 @@ function createGalleryEditHandler(opts) {
|
|
|
15146
15320
|
await writeFile15(tmp, last.before);
|
|
15147
15321
|
await rename9(tmp, manifestPath2);
|
|
15148
15322
|
stack.pop();
|
|
15149
|
-
log2(` manifest edit undone in ${
|
|
15323
|
+
log2(` manifest edit undone in ${path30.relative(process.cwd(), manifestPath2) || manifestPath2}`);
|
|
15150
15324
|
return opts.reload();
|
|
15151
15325
|
}
|
|
15152
15326
|
const manifestPath = await manifestOf(body?.project);
|
|
@@ -15208,7 +15382,7 @@ function createEditApplier(opts, log2) {
|
|
|
15208
15382
|
} catch (error) {
|
|
15209
15383
|
throw new ManifestEditError(error instanceof Error ? error.message : String(error));
|
|
15210
15384
|
}
|
|
15211
|
-
const written = await saveProjectImage(
|
|
15385
|
+
const written = await saveProjectImage(path30.dirname(path30.resolve(manifestPath2)), upload.data);
|
|
15212
15386
|
log2(` image added: ${written}`);
|
|
15213
15387
|
return opts.reload();
|
|
15214
15388
|
}
|
|
@@ -15275,7 +15449,7 @@ function createEditApplier(opts, log2) {
|
|
|
15275
15449
|
}
|
|
15276
15450
|
const result = await applyManifestEdit(manifestPath, parsed.data);
|
|
15277
15451
|
if (result.changed) {
|
|
15278
|
-
const relative =
|
|
15452
|
+
const relative = path30.relative(process.cwd(), result.manifestPath);
|
|
15279
15453
|
const shown = !relative || relative.startsWith("..") ? result.manifestPath : relative;
|
|
15280
15454
|
log2(` manifest edited: ${describeEdit(parsed.data)} in ${shown}`);
|
|
15281
15455
|
}
|
|
@@ -15327,7 +15501,7 @@ function createGalleryPriceHandler(opts) {
|
|
|
15327
15501
|
|
|
15328
15502
|
// src/pipeline/history.ts
|
|
15329
15503
|
import { existsSync as existsSync21 } from "fs";
|
|
15330
|
-
import
|
|
15504
|
+
import path31 from "path";
|
|
15331
15505
|
var HISTORY_ENV = "PIXELKILN_HISTORY";
|
|
15332
15506
|
function historyLimit(manifest, env = process.env) {
|
|
15333
15507
|
if (manifest?.history !== void 0) return manifest.history;
|
|
@@ -15417,7 +15591,7 @@ async function revertGeneration(provider, spec, lock, lockPath, opts) {
|
|
|
15417
15591
|
if (!existsSync21(file)) continue;
|
|
15418
15592
|
if (await sha256File(file) !== entry.outputs[i].sha256 && !opts.force) {
|
|
15419
15593
|
throw new OverwriteRefusedError(
|
|
15420
|
-
`${
|
|
15594
|
+
`${path31.relative(process.cwd(), file)} was changed after download; keep it with pixelkiln edit, or pass --force to replace it`,
|
|
15421
15595
|
[file]
|
|
15422
15596
|
);
|
|
15423
15597
|
}
|
|
@@ -15470,7 +15644,7 @@ async function revertGeneration(provider, spec, lock, lockPath, opts) {
|
|
|
15470
15644
|
// src/gallery/snapshot.ts
|
|
15471
15645
|
var PROJECT_FILE_TYPE = "application/octet-stream";
|
|
15472
15646
|
function galleryMediaId(absolutePath) {
|
|
15473
|
-
return
|
|
15647
|
+
return createHash4("sha256").update(path32.resolve(absolutePath)).digest("hex").slice(0, 24);
|
|
15474
15648
|
}
|
|
15475
15649
|
function galleryMediaRoute(id) {
|
|
15476
15650
|
return `/media/${id}`;
|
|
@@ -15489,7 +15663,7 @@ function describeHistory(media, entry, cacheDir) {
|
|
|
15489
15663
|
return (entry.history ?? []).map((generation, i) => {
|
|
15490
15664
|
const outputs = generation.outputs.map((output) => {
|
|
15491
15665
|
const mediaType = output.mediaType ?? MediaType.PNG;
|
|
15492
|
-
const file =
|
|
15666
|
+
const file = path32.join(cacheDir, cacheFileName(output.sha256, mediaType));
|
|
15493
15667
|
let url = null;
|
|
15494
15668
|
if (existsSync22(file)) {
|
|
15495
15669
|
const id = galleryMediaId(file);
|
|
@@ -15621,9 +15795,9 @@ async function describeOutput(media, root, absolutePath, recorded = {}) {
|
|
|
15621
15795
|
async function describeQuality(media, root, inspection) {
|
|
15622
15796
|
const outputPaths = inspection.outputs ?? [inspection.output];
|
|
15623
15797
|
const outputs = await Promise.all(
|
|
15624
|
-
outputPaths.map((file) => describeOutput(media, root,
|
|
15798
|
+
outputPaths.map((file) => describeOutput(media, root, path32.resolve(root, file)))
|
|
15625
15799
|
);
|
|
15626
|
-
const recordPath =
|
|
15800
|
+
const recordPath = path32.resolve(root, inspection.record);
|
|
15627
15801
|
const recordExists = existsSync22(recordPath);
|
|
15628
15802
|
let options = null;
|
|
15629
15803
|
let check = null;
|
|
@@ -15643,7 +15817,7 @@ async function describeQuality(media, root, inspection) {
|
|
|
15643
15817
|
return {
|
|
15644
15818
|
state: inspection.state,
|
|
15645
15819
|
reason: inspection.reason,
|
|
15646
|
-
output: portableOutputPath(
|
|
15820
|
+
output: portableOutputPath(path32.resolve(root, inspection.output), root),
|
|
15647
15821
|
record: portableOutputPath(recordPath, root),
|
|
15648
15822
|
recordExists,
|
|
15649
15823
|
outputs,
|
|
@@ -15683,7 +15857,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
15683
15857
|
const items = [];
|
|
15684
15858
|
const manifestText = await readFile20(loaded.path, "utf8");
|
|
15685
15859
|
const manifestSha256 = sha256(manifestText);
|
|
15686
|
-
const cacheDir =
|
|
15860
|
+
const cacheDir = path32.resolve(path32.dirname(opts.lockPath), ".pixelkiln", "cache");
|
|
15687
15861
|
let rawStyles = {};
|
|
15688
15862
|
try {
|
|
15689
15863
|
const raw = JSON.parse(manifestText);
|
|
@@ -15707,7 +15881,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
15707
15881
|
)
|
|
15708
15882
|
);
|
|
15709
15883
|
if (spec.source) {
|
|
15710
|
-
const editPath =
|
|
15884
|
+
const editPath = path32.resolve(root, spec.source);
|
|
15711
15885
|
const memberCount = sourceIsStem(spec.source, entry, root) ? entry.outputs.length : 1;
|
|
15712
15886
|
const editShas = await Promise.all(
|
|
15713
15887
|
Array.from({ length: memberCount }, async (_, index) => {
|
|
@@ -15756,7 +15930,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
15756
15930
|
editStatus = edits.some((member) => !member.exists) ? "missing" : editChanged.every((changed) => !changed) ? "same" : regenerated ? "regenerated-since" : "edited";
|
|
15757
15931
|
}
|
|
15758
15932
|
} else if (spec.source) {
|
|
15759
|
-
outputs = [await describeOutput(media, root,
|
|
15933
|
+
outputs = [await describeOutput(media, root, path32.resolve(root, spec.source))];
|
|
15760
15934
|
} else if (!entry && existsSync22(spec.outFile)) {
|
|
15761
15935
|
outputs = [await describeOutput(media, root, spec.outFile)];
|
|
15762
15936
|
} else {
|
|
@@ -15797,7 +15971,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
15797
15971
|
recordedSpecHash: entry?.specHash ?? null,
|
|
15798
15972
|
currentSpecHash: spec.specHash,
|
|
15799
15973
|
revision: entry?.revision ?? null,
|
|
15800
|
-
revisionParentKey: spec.revision ? lockKey(spec.styleId, spec.revision.sourceAssetId) : entry?.revision ? lockKey(spec.styleId, entry.revision.sourceAssetId) : null,
|
|
15974
|
+
revisionParentKey: spec.revision ? lockKey(spec.revision.sourceStyleId ?? spec.styleId, spec.revision.sourceAssetId) : entry?.revision ? lockKey(entry.revision.sourceStyleId ?? spec.styleId, entry.revision.sourceAssetId) : null,
|
|
15801
15975
|
mirrorOfKey: spec.mirror ? lockKey(spec.styleId, spec.mirror.sourceAssetId) : entry?.mirror ? lockKey(spec.styleId, entry.mirror.sourceAssetId) : null,
|
|
15802
15976
|
skeleton: spec.revision?.mode === "animate-skeleton" && spec.revision.keypointsFile ? {
|
|
15803
15977
|
keypointsFile: portableOutputPath(spec.revision.keypointsFile, root),
|
|
@@ -15867,7 +16041,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
15867
16041
|
recordedSpecHash: entry.specHash,
|
|
15868
16042
|
currentSpecHash: null,
|
|
15869
16043
|
revision: entry.revision,
|
|
15870
|
-
revisionParentKey: entry.revision ? lockKey(entry.styleId, entry.revision.sourceAssetId) : null,
|
|
16044
|
+
revisionParentKey: entry.revision ? lockKey(entry.revision.sourceStyleId ?? entry.styleId, entry.revision.sourceAssetId) : null,
|
|
15871
16045
|
mirrorOfKey: entry.mirror ? lockKey(entry.styleId, entry.mirror.sourceAssetId) : null,
|
|
15872
16046
|
skeleton: null,
|
|
15873
16047
|
character: describeCharacter(void 0, entry),
|
|
@@ -15960,7 +16134,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
15960
16134
|
id: loaded.manifest.name,
|
|
15961
16135
|
name: loaded.manifest.name,
|
|
15962
16136
|
manifest: loaded.path,
|
|
15963
|
-
lock:
|
|
16137
|
+
lock: path32.resolve(opts.lockPath),
|
|
15964
16138
|
root,
|
|
15965
16139
|
provider: loaded.manifest.provider,
|
|
15966
16140
|
account: null,
|
|
@@ -15986,7 +16160,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
15986
16160
|
return { snapshot, media };
|
|
15987
16161
|
}
|
|
15988
16162
|
async function buildWorkspaceGallerySnapshot(opts) {
|
|
15989
|
-
const dir =
|
|
16163
|
+
const dir = path32.dirname(path32.resolve(opts.workspacePath));
|
|
15990
16164
|
const media = /* @__PURE__ */ new Map();
|
|
15991
16165
|
const projects = [];
|
|
15992
16166
|
const items = [];
|
|
@@ -16029,7 +16203,7 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
16029
16203
|
id: project.id,
|
|
16030
16204
|
name: loaded.manifest.name,
|
|
16031
16205
|
manifest: manifestPath,
|
|
16032
|
-
lock:
|
|
16206
|
+
lock: path32.resolve(lockPath),
|
|
16033
16207
|
root: loaded.root,
|
|
16034
16208
|
provider: loaded.manifest.provider,
|
|
16035
16209
|
account: project.account ?? null,
|
|
@@ -16045,8 +16219,8 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
16045
16219
|
id: project.id,
|
|
16046
16220
|
name: project.id,
|
|
16047
16221
|
manifest: manifestPath,
|
|
16048
|
-
lock:
|
|
16049
|
-
root:
|
|
16222
|
+
lock: path32.resolve(lockPath),
|
|
16223
|
+
root: path32.dirname(manifestPath),
|
|
16050
16224
|
provider: project.provider,
|
|
16051
16225
|
account: project.account ?? null,
|
|
16052
16226
|
manifestSha256: null,
|
|
@@ -16073,7 +16247,7 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
16073
16247
|
version: 1,
|
|
16074
16248
|
generatedAt: (opts.now ?? (() => /* @__PURE__ */ new Date()))().toISOString(),
|
|
16075
16249
|
project: null,
|
|
16076
|
-
workspace: { path:
|
|
16250
|
+
workspace: { path: path32.resolve(opts.workspacePath), dir, projects },
|
|
16077
16251
|
filter,
|
|
16078
16252
|
totals: {
|
|
16079
16253
|
entries: projects.reduce((sum, project) => sum + project.entries, 0),
|
|
@@ -16095,7 +16269,7 @@ import { createServer as createServer2 } from "http";
|
|
|
16095
16269
|
import { readFile as readFile21 } from "fs/promises";
|
|
16096
16270
|
|
|
16097
16271
|
// src/gallery/page.ts
|
|
16098
|
-
import { existsSync as existsSync23, readFileSync as
|
|
16272
|
+
import { existsSync as existsSync23, readFileSync as readFileSync5 } from "fs";
|
|
16099
16273
|
|
|
16100
16274
|
// src/gallery/client-bundle.ts
|
|
16101
16275
|
import { createRequire } from "module";
|
|
@@ -16135,8 +16309,8 @@ var client;
|
|
|
16135
16309
|
function clientAssets() {
|
|
16136
16310
|
const prebuilt = new URL("gallery.js", CLIENT_DIR);
|
|
16137
16311
|
client ??= {
|
|
16138
|
-
css:
|
|
16139
|
-
js: existsSync23(prebuilt) ?
|
|
16312
|
+
css: readFileSync5(new URL("gallery.css", CLIENT_DIR), "utf8"),
|
|
16313
|
+
js: existsSync23(prebuilt) ? readFileSync5(prebuilt, "utf8") : bundleGalleryClient()
|
|
16140
16314
|
};
|
|
16141
16315
|
return client;
|
|
16142
16316
|
}
|
|
@@ -16803,6 +16977,7 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
|
|
|
16803
16977
|
revision: spec.revision ? {
|
|
16804
16978
|
mode: spec.revision.mode,
|
|
16805
16979
|
sourceAssetId: spec.revision.sourceAssetId,
|
|
16980
|
+
...spec.revision.sourceStyleId ? { sourceStyleId: spec.revision.sourceStyleId } : {},
|
|
16806
16981
|
sourceSha256: spec.revision.sourceSha256,
|
|
16807
16982
|
...spec.revision.maskSha256 ? { maskSha256: spec.revision.maskSha256 } : {},
|
|
16808
16983
|
...spec.revision.strength == null ? {} : { strength: spec.revision.strength },
|
|
@@ -16955,7 +17130,7 @@ import { readFile as readFile23 } from "fs/promises";
|
|
|
16955
17130
|
// src/pipeline/init.ts
|
|
16956
17131
|
import { readdir, readFile as readFile22, writeFile as writeFile16 } from "fs/promises";
|
|
16957
17132
|
import { existsSync as existsSync24 } from "fs";
|
|
16958
|
-
import
|
|
17133
|
+
import path33 from "path";
|
|
16959
17134
|
function pngSize(buf) {
|
|
16960
17135
|
if (buf.length < 24) return null;
|
|
16961
17136
|
const isPng = buf.readUInt32BE(0) === 2303741511 && buf.readUInt32BE(4) === 218765834;
|
|
@@ -16969,7 +17144,7 @@ function slugify(value) {
|
|
|
16969
17144
|
async function walk(dir, exclude) {
|
|
16970
17145
|
const out = [];
|
|
16971
17146
|
for (const entry of await readdir(dir, { withFileTypes: true })) {
|
|
16972
|
-
const full =
|
|
17147
|
+
const full = path33.join(dir, entry.name);
|
|
16973
17148
|
if (exclude.some((x) => full.includes(x))) continue;
|
|
16974
17149
|
if (entry.isDirectory()) out.push(...await walk(full, exclude));
|
|
16975
17150
|
else if (entry.name.toLowerCase().endsWith(".png")) out.push(full);
|
|
@@ -16988,9 +17163,9 @@ async function scanAssets(root, opts = {}) {
|
|
|
16988
17163
|
skipped.push(`${file} (not a readable PNG)`);
|
|
16989
17164
|
continue;
|
|
16990
17165
|
}
|
|
16991
|
-
const rel =
|
|
16992
|
-
const category =
|
|
16993
|
-
let id = slugify(
|
|
17166
|
+
const rel = path33.relative(root, file);
|
|
17167
|
+
const category = path33.dirname(rel) === "." ? "" : path33.dirname(rel);
|
|
17168
|
+
let id = slugify(path33.basename(rel));
|
|
16994
17169
|
if (seen.has(id)) {
|
|
16995
17170
|
const n = seen.get(id) + 1;
|
|
16996
17171
|
seen.set(id, n);
|
|
@@ -18004,14 +18179,14 @@ function createGenerateHandlers(opts) {
|
|
|
18004
18179
|
|
|
18005
18180
|
// src/gallery/editor.ts
|
|
18006
18181
|
import { readFile as readFile25 } from "fs/promises";
|
|
18007
|
-
import
|
|
18182
|
+
import path35 from "path";
|
|
18008
18183
|
|
|
18009
18184
|
// src/editor/install.ts
|
|
18010
|
-
import { createHash as
|
|
18185
|
+
import { createHash as createHash5 } from "crypto";
|
|
18011
18186
|
import { existsSync as existsSync26 } from "fs";
|
|
18012
18187
|
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
|
|
18188
|
+
import os3 from "os";
|
|
18189
|
+
import path34 from "path";
|
|
18015
18190
|
|
|
18016
18191
|
// tools/pixelorama-bridge/pin.json
|
|
18017
18192
|
var pin_default = {
|
|
@@ -18075,15 +18250,15 @@ var EditorInstallError = class extends Error {
|
|
|
18075
18250
|
};
|
|
18076
18251
|
function editorToolsRoot() {
|
|
18077
18252
|
const override = process.env.PIXELKILN_TOOLS_DIR?.trim();
|
|
18078
|
-
if (override) return
|
|
18079
|
-
if (process.platform === "darwin") return
|
|
18253
|
+
if (override) return path34.resolve(override);
|
|
18254
|
+
if (process.platform === "darwin") return path34.join(os3.homedir(), "Library", "Caches", "pixelkiln", "tools");
|
|
18080
18255
|
if (process.platform === "win32") {
|
|
18081
|
-
return
|
|
18256
|
+
return path34.join(process.env.LOCALAPPDATA ?? path34.join(os3.homedir(), "AppData", "Local"), "pixelkiln", "tools");
|
|
18082
18257
|
}
|
|
18083
|
-
return
|
|
18258
|
+
return path34.join(process.env.XDG_CACHE_HOME ?? path34.join(os3.homedir(), ".cache"), "pixelkiln", "tools");
|
|
18084
18259
|
}
|
|
18085
18260
|
function editorDir(pin = EDITOR_PIN, root = editorToolsRoot()) {
|
|
18086
|
-
return
|
|
18261
|
+
return path34.join(root, "pixelorama", pin.release ?? "unpublished");
|
|
18087
18262
|
}
|
|
18088
18263
|
function editorBaseUrl(pin = EDITOR_PIN) {
|
|
18089
18264
|
const override = process.env.PIXELKILN_EDITOR_URL?.trim();
|
|
@@ -18093,7 +18268,7 @@ function editorBaseUrl(pin = EDITOR_PIN) {
|
|
|
18093
18268
|
}
|
|
18094
18269
|
async function sha256Of(file) {
|
|
18095
18270
|
try {
|
|
18096
|
-
return
|
|
18271
|
+
return createHash5("sha256").update(await readFile24(file)).digest("hex");
|
|
18097
18272
|
} catch {
|
|
18098
18273
|
return null;
|
|
18099
18274
|
}
|
|
@@ -18106,7 +18281,7 @@ async function editorStatus(opts = {}) {
|
|
|
18106
18281
|
let totalBytes = 0;
|
|
18107
18282
|
for (const [name, expected] of Object.entries(pin.files)) {
|
|
18108
18283
|
totalBytes += expected.bytes;
|
|
18109
|
-
const file =
|
|
18284
|
+
const file = path34.join(dir, name);
|
|
18110
18285
|
const info = existsSync26(file) ? await stat3(file).catch(() => null) : null;
|
|
18111
18286
|
if (info && info.size === expected.bytes && await sha256Of(file) === expected.sha256) {
|
|
18112
18287
|
installedBytes += expected.bytes;
|
|
@@ -18160,11 +18335,11 @@ async function installEditor(opts = {}) {
|
|
|
18160
18335
|
if (bytes.length !== expected.bytes) {
|
|
18161
18336
|
throw new EditorInstallError(`${name}: expected ${expected.bytes} bytes, got ${bytes.length}`, name);
|
|
18162
18337
|
}
|
|
18163
|
-
const digest2 =
|
|
18338
|
+
const digest2 = createHash5("sha256").update(bytes).digest("hex");
|
|
18164
18339
|
if (digest2 !== expected.sha256) {
|
|
18165
18340
|
throw new EditorInstallError(`${name}: sha256 ${digest2.slice(0, 12)}\u2026 does not match the pinned ${expected.sha256.slice(0, 12)}\u2026`, name);
|
|
18166
18341
|
}
|
|
18167
|
-
const final =
|
|
18342
|
+
const final = path34.join(dir, name);
|
|
18168
18343
|
const tmp = `${final}.${process.pid}.part`;
|
|
18169
18344
|
try {
|
|
18170
18345
|
await writeFile17(tmp, bytes);
|
|
@@ -18210,7 +18385,7 @@ var EDITOR_CONTENT_TYPES = {
|
|
|
18210
18385
|
".json": "application/json; charset=utf-8"
|
|
18211
18386
|
};
|
|
18212
18387
|
function editorContentType(name) {
|
|
18213
|
-
return EDITOR_CONTENT_TYPES[
|
|
18388
|
+
return EDITOR_CONTENT_TYPES[path34.extname(name).toLowerCase()] ?? null;
|
|
18214
18389
|
}
|
|
18215
18390
|
|
|
18216
18391
|
// src/gallery/editor.ts
|
|
@@ -18286,7 +18461,7 @@ function createGalleryEditorHandlers(opts = {}) {
|
|
|
18286
18461
|
if (!status.installed) return null;
|
|
18287
18462
|
let bytes;
|
|
18288
18463
|
try {
|
|
18289
|
-
bytes = await readFile25(
|
|
18464
|
+
bytes = await readFile25(path35.join(dir, name));
|
|
18290
18465
|
} catch {
|
|
18291
18466
|
verified = null;
|
|
18292
18467
|
return null;
|
|
@@ -18326,7 +18501,7 @@ async function serveUntilStopped(initial, reload, args, access2) {
|
|
|
18326
18501
|
};
|
|
18327
18502
|
const loadProject = async (project) => {
|
|
18328
18503
|
const ctx = await access2.loadProject(project);
|
|
18329
|
-
loadProjectEnv(
|
|
18504
|
+
loadProjectEnv(path36.dirname(ctx.loaded.path));
|
|
18330
18505
|
return ctx;
|
|
18331
18506
|
};
|
|
18332
18507
|
const loadProjectEnv = (dir) => {
|
|
@@ -18335,7 +18510,7 @@ async function serveUntilStopped(initial, reload, args, access2) {
|
|
|
18335
18510
|
for (const [name, value] of Object.entries(declared)) {
|
|
18336
18511
|
if (credentialNames.has(name) && name in process.env && process.env[name] !== value) {
|
|
18337
18512
|
throw new Error(
|
|
18338
|
-
`${
|
|
18513
|
+
`${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
18514
|
);
|
|
18340
18515
|
}
|
|
18341
18516
|
}
|
|
@@ -18343,7 +18518,7 @@ async function serveUntilStopped(initial, reload, args, access2) {
|
|
|
18343
18518
|
};
|
|
18344
18519
|
const quoters = /* @__PURE__ */ new Map();
|
|
18345
18520
|
const quoteFor = async (project) => {
|
|
18346
|
-
loadProjectEnv(
|
|
18521
|
+
loadProjectEnv(path36.dirname(path36.resolve(await access2.manifestFor(project))));
|
|
18347
18522
|
const key = process.env.PIXELLAB_API_KEY;
|
|
18348
18523
|
if (!key) return null;
|
|
18349
18524
|
let quoter = quoters.get(key);
|
|
@@ -18394,7 +18569,7 @@ async function serveUntilStopped(initial, reload, args, access2) {
|
|
|
18394
18569
|
}
|
|
18395
18570
|
async function runGallery(args) {
|
|
18396
18571
|
if (args.workspace) {
|
|
18397
|
-
const workspacePath =
|
|
18572
|
+
const workspacePath = path36.resolve(args.workspace);
|
|
18398
18573
|
if (!existsSync27(workspacePath)) {
|
|
18399
18574
|
throw new Error(`Workspace catalog not found: ${workspacePath}`);
|
|
18400
18575
|
}
|
|
@@ -18415,7 +18590,7 @@ async function runGallery(args) {
|
|
|
18415
18590
|
const registered = async (projectId) => {
|
|
18416
18591
|
const project = (await loadWorkspace(workspacePath)).projects.find((candidate) => candidate.id === projectId);
|
|
18417
18592
|
if (!projectId || !project) throw new Error(`unknown workspace project "${projectId ?? ""}"`);
|
|
18418
|
-
return resolveProject(
|
|
18593
|
+
return resolveProject(path36.dirname(workspacePath), project);
|
|
18419
18594
|
};
|
|
18420
18595
|
await serveUntilStopped(initial, build2, args, {
|
|
18421
18596
|
manifestFor: async (projectId) => (await registered(projectId)).manifestPath,
|
|
@@ -18440,7 +18615,7 @@ async function runGallery(args) {
|
|
|
18440
18615
|
return buildGallerySnapshot({ ...fresh, filter });
|
|
18441
18616
|
};
|
|
18442
18617
|
await serveUntilStopped(await build(), reload, args, {
|
|
18443
|
-
manifestFor: () =>
|
|
18618
|
+
manifestFor: () => path36.resolve(args.manifest),
|
|
18444
18619
|
loadProject: () => openProject(args.manifest, { lockPath: args.lock, env: false })
|
|
18445
18620
|
});
|
|
18446
18621
|
}
|
|
@@ -18754,13 +18929,13 @@ import { existsSync as existsSync29 } from "fs";
|
|
|
18754
18929
|
// src/pipeline/cache-health.ts
|
|
18755
18930
|
import { existsSync as existsSync28 } from "fs";
|
|
18756
18931
|
import { readFile as readFile26, readdir as readdir2, rm as rm12 } from "fs/promises";
|
|
18757
|
-
import
|
|
18932
|
+
import path37 from "path";
|
|
18758
18933
|
async function inspectCaches(lock, lockPath, options = {}) {
|
|
18759
18934
|
if (options.prune && !existsSync28(lockPath)) {
|
|
18760
|
-
throw new Error(`Refusing to prune without an existing lockfile at ${
|
|
18935
|
+
throw new Error(`Refusing to prune without an existing lockfile at ${path37.resolve(lockPath)}`);
|
|
18761
18936
|
}
|
|
18762
|
-
const contentDir =
|
|
18763
|
-
const remotePath =
|
|
18937
|
+
const contentDir = path37.resolve(path37.dirname(lockPath), ".pixelkiln", "cache");
|
|
18938
|
+
const remotePath = path37.resolve(cachePathFor(lockPath));
|
|
18764
18939
|
const referenced = referencedHashes(lock);
|
|
18765
18940
|
let content = await inspectContentCache(contentDir, referenced);
|
|
18766
18941
|
let remoteHashes = await inspectRemoteHashCache(remotePath);
|
|
@@ -18772,7 +18947,7 @@ async function inspectCaches(lock, lockPath, options = {}) {
|
|
|
18772
18947
|
]);
|
|
18773
18948
|
for (const name of names) {
|
|
18774
18949
|
try {
|
|
18775
|
-
await rm12(
|
|
18950
|
+
await rm12(path37.join(contentDir, name), { force: true });
|
|
18776
18951
|
removed.contentFiles++;
|
|
18777
18952
|
} catch {
|
|
18778
18953
|
}
|
|
@@ -18820,7 +18995,7 @@ async function inspectContentCache(contentDir, referenced) {
|
|
|
18820
18995
|
continue;
|
|
18821
18996
|
}
|
|
18822
18997
|
report.files++;
|
|
18823
|
-
const file =
|
|
18998
|
+
const file = path37.join(contentDir, entry.name);
|
|
18824
18999
|
const mediaType = mediaTypeFromExtension(entry.name);
|
|
18825
19000
|
const expected = mediaType ? entry.name.slice(0, -4) : "";
|
|
18826
19001
|
let bytes;
|
|
@@ -18991,42 +19166,42 @@ This drops their provenance from the lockfile. The art on disk is`);
|
|
|
18991
19166
|
}
|
|
18992
19167
|
|
|
18993
19168
|
// src/cli/commands/init.ts
|
|
18994
|
-
import
|
|
19169
|
+
import path38 from "path";
|
|
18995
19170
|
import { existsSync as existsSync30 } from "fs";
|
|
18996
19171
|
async function runInit(args) {
|
|
18997
19172
|
if (!args.from) throw new Error("init needs --from <dir> pointing at your existing PNGs.");
|
|
18998
|
-
const root =
|
|
19173
|
+
const root = path38.resolve(args.from);
|
|
18999
19174
|
if (!existsSync30(root)) throw new Error(`No directory at ${root}`);
|
|
19000
19175
|
const generator = args.generator ?? "map";
|
|
19001
19176
|
if (generator !== "1dir" && generator !== "map") {
|
|
19002
19177
|
throw new Error(`--generator must be "1dir" or "map", got "${args.generator}".`);
|
|
19003
19178
|
}
|
|
19004
|
-
const target =
|
|
19179
|
+
const target = path38.resolve(args.out ?? "pixelkiln.manifest.json");
|
|
19005
19180
|
const { assets, skipped } = await scanAssets(root, { exclude: args.exclude });
|
|
19006
19181
|
if (!assets.length) throw new Error(`No PNGs found under ${root}`);
|
|
19007
19182
|
const manifest = buildManifest(
|
|
19008
|
-
args.name ??
|
|
19183
|
+
args.name ?? path38.basename(path38.dirname(target)),
|
|
19009
19184
|
args.styles[0] ?? "base",
|
|
19010
19185
|
generator,
|
|
19011
|
-
|
|
19186
|
+
path38.relative(path38.dirname(target), root) || ".",
|
|
19012
19187
|
assets
|
|
19013
19188
|
);
|
|
19014
19189
|
await writeManifestFile(target, manifest);
|
|
19015
|
-
log(` scanned ${assets.length} PNG(s) under ${
|
|
19190
|
+
log(` scanned ${assets.length} PNG(s) under ${path38.relative(process.cwd(), root)}`);
|
|
19016
19191
|
if (skipped.length) log(` skipped ${skipped.length} unreadable file(s)`);
|
|
19017
|
-
log(` wrote ${
|
|
19192
|
+
log(` wrote ${path38.relative(process.cwd(), target)}`);
|
|
19018
19193
|
log(`
|
|
19019
19194
|
Prompts are intentionally empty. To recover the real ones from your`);
|
|
19020
19195
|
log(` PixelLab account instead of inventing them:`);
|
|
19021
19196
|
log(`
|
|
19022
|
-
pixelkiln adopt --manifest ${
|
|
19197
|
+
pixelkiln adopt --manifest ${path38.relative(process.cwd(), target)} --write-prompts
|
|
19023
19198
|
`);
|
|
19024
19199
|
}
|
|
19025
19200
|
|
|
19026
19201
|
// src/pipeline/doctor.ts
|
|
19027
19202
|
import { constants, existsSync as existsSync31 } from "fs";
|
|
19028
19203
|
import { access, stat as stat4 } from "fs/promises";
|
|
19029
|
-
import
|
|
19204
|
+
import path39 from "path";
|
|
19030
19205
|
async function doctor(loaded, specs, lock, lockPath, opts = {}) {
|
|
19031
19206
|
const checks = [];
|
|
19032
19207
|
const add = (id, level, message6) => checks.push({ id, level, message: message6 });
|
|
@@ -19037,8 +19212,8 @@ async function doctor(loaded, specs, lock, lockPath, opts = {}) {
|
|
|
19037
19212
|
);
|
|
19038
19213
|
add("lock", "ok", `${Object.keys(lock.entries).length} valid v2 lock entr(ies)`);
|
|
19039
19214
|
const dirs = [...new Set(specs.flatMap((spec) => [
|
|
19040
|
-
|
|
19041
|
-
...spec.quality ? [
|
|
19215
|
+
path39.dirname(spec.outFile),
|
|
19216
|
+
...spec.quality ? [path39.dirname(spec.quality.outFile)] : []
|
|
19042
19217
|
]))];
|
|
19043
19218
|
const unwritable = [];
|
|
19044
19219
|
for (const dir of dirs) {
|
|
@@ -19061,7 +19236,7 @@ async function doctor(loaded, specs, lock, lockPath, opts = {}) {
|
|
|
19061
19236
|
for (let index = 0; index < entry.outputs.length; index++) {
|
|
19062
19237
|
const output = entry.outputs[index];
|
|
19063
19238
|
const spec = specByKey.get(key);
|
|
19064
|
-
const absolute = spec ? currentEntryOutputPath(entry, spec, index) : resolveOutputPath(output.path,
|
|
19239
|
+
const absolute = spec ? currentEntryOutputPath(entry, spec, index) : resolveOutputPath(output.path, path39.dirname(lockPath));
|
|
19065
19240
|
const owner = outputOwners.get(absolute);
|
|
19066
19241
|
if (owner && owner !== key) duplicateOutputs.push(`${output.path} (${owner}, ${key})`);
|
|
19067
19242
|
outputOwners.set(absolute, key);
|
|
@@ -19076,7 +19251,7 @@ async function doctor(loaded, specs, lock, lockPath, opts = {}) {
|
|
|
19076
19251
|
} else {
|
|
19077
19252
|
add("lock-outputs", "ok", `${outputOwners.size} recorded output path(s) are uniquely owned`);
|
|
19078
19253
|
}
|
|
19079
|
-
const cacheDir =
|
|
19254
|
+
const cacheDir = path39.join(path39.dirname(lockPath), ".pixelkiln", "cache");
|
|
19080
19255
|
const stranded = [];
|
|
19081
19256
|
const invalidState = [];
|
|
19082
19257
|
const staleMapUrls = [];
|
|
@@ -19092,7 +19267,7 @@ async function doctor(loaded, specs, lock, lockPath, opts = {}) {
|
|
|
19092
19267
|
const hasSource = Boolean(entry.sourceUrl || entry.sourceUrls?.length);
|
|
19093
19268
|
let hasCache = false;
|
|
19094
19269
|
for (const output of entry.outputs) {
|
|
19095
|
-
const cached =
|
|
19270
|
+
const cached = path39.join(
|
|
19096
19271
|
cacheDir,
|
|
19097
19272
|
cacheFileName(output.sha256, output.mediaType ?? MediaType.PNG)
|
|
19098
19273
|
);
|
|
@@ -19192,7 +19367,7 @@ async function nearestExistingDirectory(start) {
|
|
|
19192
19367
|
if ((await stat4(current)).isDirectory()) return current;
|
|
19193
19368
|
} catch {
|
|
19194
19369
|
}
|
|
19195
|
-
const parent =
|
|
19370
|
+
const parent = path39.dirname(current);
|
|
19196
19371
|
if (parent === current) return current;
|
|
19197
19372
|
current = parent;
|
|
19198
19373
|
}
|
|
@@ -19201,7 +19376,7 @@ async function nearestExistingDirectory(start) {
|
|
|
19201
19376
|
// src/pipeline/audit.ts
|
|
19202
19377
|
import { readFile as readFile27 } from "fs/promises";
|
|
19203
19378
|
import { existsSync as existsSync32 } from "fs";
|
|
19204
|
-
import
|
|
19379
|
+
import path40 from "path";
|
|
19205
19380
|
function paletteDistance(asset, reference) {
|
|
19206
19381
|
if (!asset.length || !reference.length) return 0;
|
|
19207
19382
|
let total = 0;
|
|
@@ -19266,7 +19441,7 @@ async function auditStyle(loaded, specs, styleId, lock) {
|
|
|
19266
19441
|
let referenceFromStyleImages = false;
|
|
19267
19442
|
const refPalettes = [];
|
|
19268
19443
|
for (const img of style.styleImages) {
|
|
19269
|
-
const abs =
|
|
19444
|
+
const abs = path40.resolve(loaded.root, img.path);
|
|
19270
19445
|
if (!existsSync32(abs)) continue;
|
|
19271
19446
|
try {
|
|
19272
19447
|
refPalettes.push(extractPalette(decodePng(await readFile27(abs)), 12));
|
|
@@ -19497,24 +19672,24 @@ async function runAudit(args) {
|
|
|
19497
19672
|
}
|
|
19498
19673
|
|
|
19499
19674
|
// src/cli/commands/pack.ts
|
|
19500
|
-
import
|
|
19675
|
+
import path42 from "path";
|
|
19501
19676
|
import { readFile as readFile28 } from "fs/promises";
|
|
19502
19677
|
|
|
19503
19678
|
// src/pipeline/pack.ts
|
|
19504
|
-
import { readFileSync as
|
|
19505
|
-
import
|
|
19679
|
+
import { readFileSync as readFileSync6 } from "fs";
|
|
19680
|
+
import path41 from "path";
|
|
19506
19681
|
var DEFAULT_FRAME_SET_FPS = 12;
|
|
19507
19682
|
function resolvePackInputs(raw, inputsFilePath) {
|
|
19508
19683
|
if (!Array.isArray(raw) || !raw.length) {
|
|
19509
19684
|
throw new Error("--inputs must be a non-empty JSON array of { id, path }");
|
|
19510
19685
|
}
|
|
19511
|
-
const baseDir =
|
|
19686
|
+
const baseDir = path41.dirname(path41.resolve(inputsFilePath));
|
|
19512
19687
|
return raw.map((entry, i) => {
|
|
19513
19688
|
const e = entry;
|
|
19514
19689
|
if (typeof e.id !== "string" || typeof e.path !== "string") {
|
|
19515
19690
|
throw new Error(`--inputs[${i}] needs string "id" and "path"`);
|
|
19516
19691
|
}
|
|
19517
|
-
return { id: e.id, path:
|
|
19692
|
+
return { id: e.id, path: path41.resolve(baseDir, e.path) };
|
|
19518
19693
|
});
|
|
19519
19694
|
}
|
|
19520
19695
|
function packSprites(inputs, options = {}) {
|
|
@@ -19535,7 +19710,7 @@ function packSprites(inputs, options = {}) {
|
|
|
19535
19710
|
for (const input of ordered) {
|
|
19536
19711
|
let sourceHash = null;
|
|
19537
19712
|
try {
|
|
19538
|
-
const bytes =
|
|
19713
|
+
const bytes = readFileSync6(input.path);
|
|
19539
19714
|
sourceHash = sha256(bytes);
|
|
19540
19715
|
const png = decodePng(bytes);
|
|
19541
19716
|
sprites.push({ id: input.id, width: png.width, height: png.height, pixels: png.pixels });
|
|
@@ -19598,7 +19773,7 @@ function packStyle(lock, styleId, manifestDir, options = {}) {
|
|
|
19598
19773
|
}
|
|
19599
19774
|
const inputs2 = Object.entries(options.sourceOverrides).map(([id, source]) => ({
|
|
19600
19775
|
id,
|
|
19601
|
-
path:
|
|
19776
|
+
path: path41.resolve(source)
|
|
19602
19777
|
}));
|
|
19603
19778
|
if (!inputs2.length) throw new Error(`No approved quality outputs for style "${styleId}".`);
|
|
19604
19779
|
const packed2 = packSprites(inputs2, options);
|
|
@@ -19622,7 +19797,7 @@ function packStyle(lock, styleId, manifestDir, options = {}) {
|
|
|
19622
19797
|
let outputs = resolveEntryOutputs(entry, id, manifestDir);
|
|
19623
19798
|
const source = sources[id];
|
|
19624
19799
|
if (source && outputs.length <= 1) {
|
|
19625
|
-
inputs.push({ id, path:
|
|
19800
|
+
inputs.push({ id, path: path41.resolve(manifestDir, source) });
|
|
19626
19801
|
continue;
|
|
19627
19802
|
}
|
|
19628
19803
|
if (source && sourceIsStem(source, entry, manifestDir)) {
|
|
@@ -19656,7 +19831,7 @@ function packStyle(lock, styleId, manifestDir, options = {}) {
|
|
|
19656
19831
|
}
|
|
19657
19832
|
}
|
|
19658
19833
|
for (const [id, source] of Object.entries(sources)) {
|
|
19659
|
-
if (!locked.has(id)) inputs.push({ id, path:
|
|
19834
|
+
if (!locked.has(id)) inputs.push({ id, path: path41.resolve(manifestDir, source) });
|
|
19660
19835
|
}
|
|
19661
19836
|
if (!inputs.length) {
|
|
19662
19837
|
throw new Error(`No readable sprites for style "${styleId}"; ${noOutput.length} skipped.`);
|
|
@@ -19693,7 +19868,7 @@ function mountSprites(placements, options) {
|
|
|
19693
19868
|
for (const p of placements) {
|
|
19694
19869
|
let sourceHash = null;
|
|
19695
19870
|
try {
|
|
19696
|
-
const bytes =
|
|
19871
|
+
const bytes = readFileSync6(p.path);
|
|
19697
19872
|
sourceHash = sha256(bytes);
|
|
19698
19873
|
const png = decodePng(bytes);
|
|
19699
19874
|
if (png.width > cellWidth || png.height > cellHeight) {
|
|
@@ -19764,7 +19939,7 @@ function mountStyle(lock, styleId, manifestDir, mount, cells, sources = {}, outp
|
|
|
19764
19939
|
const source = sources[id];
|
|
19765
19940
|
const entry = lock.entries[`${styleId}/${id}`];
|
|
19766
19941
|
if (source && !(entry && sourceIsStem(source, entry, manifestDir))) {
|
|
19767
|
-
placements.push({ id, path:
|
|
19942
|
+
placements.push({ id, path: path41.resolve(manifestDir, source), cell });
|
|
19768
19943
|
continue;
|
|
19769
19944
|
}
|
|
19770
19945
|
if (source && entry) {
|
|
@@ -19797,7 +19972,7 @@ function mountStyle(lock, styleId, manifestDir, mount, cells, sources = {}, outp
|
|
|
19797
19972
|
`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
19973
|
);
|
|
19799
19974
|
}
|
|
19800
|
-
const basePng = mount.base ?
|
|
19975
|
+
const basePng = mount.base ? readFileSync6(path41.resolve(manifestDir, mount.base)) : void 0;
|
|
19801
19976
|
const mounted = mountSprites(placements, {
|
|
19802
19977
|
cellWidth: mount.cellWidth,
|
|
19803
19978
|
cellHeight: mount.cellHeight,
|
|
@@ -19807,7 +19982,7 @@ function mountStyle(lock, styleId, manifestDir, mount, cells, sources = {}, outp
|
|
|
19807
19982
|
...mounted,
|
|
19808
19983
|
sources: mount.base ? [{
|
|
19809
19984
|
id: "$base",
|
|
19810
|
-
path:
|
|
19985
|
+
path: path41.resolve(manifestDir, mount.base),
|
|
19811
19986
|
sha256: sha256(basePng),
|
|
19812
19987
|
included: true
|
|
19813
19988
|
}, ...mounted.sources] : mounted.sources,
|
|
@@ -20211,12 +20386,12 @@ async function runPack(args) {
|
|
|
20211
20386
|
if (args.primaryOnly || args.outputRoles.length) {
|
|
20212
20387
|
throw new Error("--primary-only and --output-role require manifest-driven pack");
|
|
20213
20388
|
}
|
|
20214
|
-
const raw = JSON.parse(await readFile28(
|
|
20389
|
+
const raw = JSON.parse(await readFile28(path42.resolve(args.inputs), "utf8"));
|
|
20215
20390
|
const inputs = resolvePackInputs(raw, args.inputs);
|
|
20216
20391
|
const { png, atlas, skipped, sources } = packSprites(inputs, { columns: args.columns });
|
|
20217
20392
|
const format = sheetFormat(args);
|
|
20218
|
-
const base =
|
|
20219
|
-
const { extension, document } = renderSheetDocument(format, atlas, { imageName:
|
|
20393
|
+
const base = path42.resolve(args.out.replace(/\.(?:png|json|tres)$/i, ""));
|
|
20394
|
+
const { extension, document } = renderSheetDocument(format, atlas, { imageName: path42.basename(`${base}.png`) });
|
|
20220
20395
|
const outputs = [
|
|
20221
20396
|
{ path: `${base}.png`, data: png },
|
|
20222
20397
|
{ path: `${base}${extension}`, data: document }
|
|
@@ -20229,7 +20404,7 @@ async function runPack(args) {
|
|
|
20229
20404
|
log(
|
|
20230
20405
|
` ${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
20406
|
);
|
|
20232
|
-
log(` ${
|
|
20407
|
+
log(` ${path42.relative(process.cwd(), base)}.png + ${extension} + .pixelkiln.json`);
|
|
20233
20408
|
for (const s of skipped) log(` skipped ${s.id}: ${s.reason}`);
|
|
20234
20409
|
return;
|
|
20235
20410
|
}
|
|
@@ -20237,7 +20412,7 @@ async function runPack(args) {
|
|
|
20237
20412
|
if (args.primaryOnly && args.outputRoles.length) {
|
|
20238
20413
|
throw new Error("pack accepts either --primary-only or --output-role, not both");
|
|
20239
20414
|
}
|
|
20240
|
-
const manifestDir =
|
|
20415
|
+
const manifestDir = path42.dirname(path42.resolve(args.manifest));
|
|
20241
20416
|
const styleIds = args.styles.length ? args.styles : Object.keys(loaded.manifest.styles);
|
|
20242
20417
|
for (const styleId of styleIds) {
|
|
20243
20418
|
const style = loaded.manifest.styles[styleId];
|
|
@@ -20252,9 +20427,9 @@ async function runPack(args) {
|
|
|
20252
20427
|
sourceOverrides: qualitySources,
|
|
20253
20428
|
sources: manifestSources(loaded.manifest, styleId)
|
|
20254
20429
|
});
|
|
20255
|
-
const base = args.out ?
|
|
20430
|
+
const base = args.out ? path42.resolve(args.out.replace(/\.(?:png|json|tres)$/i, "")) : path42.resolve(manifestDir, style.outDir, `${styleId}-sheet`);
|
|
20256
20431
|
const format = sheetFormat(args);
|
|
20257
|
-
const { extension, document } = renderSheetDocument(format, atlas, { imageName:
|
|
20432
|
+
const { extension, document } = renderSheetDocument(format, atlas, { imageName: path42.basename(`${base}.png`) });
|
|
20258
20433
|
const outputs = [
|
|
20259
20434
|
{ path: `${base}.png`, data: png },
|
|
20260
20435
|
{ path: `${base}${extension}`, data: document }
|
|
@@ -20286,13 +20461,13 @@ async function runPack(args) {
|
|
|
20286
20461
|
log(
|
|
20287
20462
|
` ${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
20463
|
);
|
|
20289
|
-
log(` ${
|
|
20464
|
+
log(` ${path42.relative(process.cwd(), base)}.png + ${extension} + .pixelkiln.json`);
|
|
20290
20465
|
for (const s of skipped) log(` skipped ${s.id}: ${s.reason}`);
|
|
20291
20466
|
}
|
|
20292
20467
|
}
|
|
20293
20468
|
async function runMount(args) {
|
|
20294
20469
|
const { loaded, lock } = await openProject2(args);
|
|
20295
|
-
const manifestDir =
|
|
20470
|
+
const manifestDir = path42.dirname(path42.resolve(args.manifest));
|
|
20296
20471
|
const styleIds = args.styles.length ? args.styles : Object.keys(loaded.manifest.styles);
|
|
20297
20472
|
for (const styleId of styleIds) {
|
|
20298
20473
|
const style = loaded.manifest.styles[styleId];
|
|
@@ -20331,9 +20506,9 @@ async function runMount(args) {
|
|
|
20331
20506
|
sources,
|
|
20332
20507
|
outputRoles
|
|
20333
20508
|
);
|
|
20334
|
-
const out =
|
|
20509
|
+
const out = path42.resolve(manifestDir, style.mount.out);
|
|
20335
20510
|
const format = sheetFormat(args);
|
|
20336
|
-
const { extension, document } = renderSheetDocument(format, atlas, { imageName:
|
|
20511
|
+
const { extension, document } = renderSheetDocument(format, atlas, { imageName: path42.basename(out) });
|
|
20337
20512
|
const metadata = out.replace(/\.png$/, "") + extension;
|
|
20338
20513
|
const companion = out.replace(/\.png$/, "") + ".pixelkiln.json";
|
|
20339
20514
|
const outputs = [
|
|
@@ -20353,7 +20528,7 @@ async function runMount(args) {
|
|
|
20353
20528
|
await provenanceFile("$lock", args.lock),
|
|
20354
20529
|
...qualityRecords,
|
|
20355
20530
|
...artifactSources.filter(
|
|
20356
|
-
(source) => source.id !== "$base" ||
|
|
20531
|
+
(source) => source.id !== "$base" || path42.resolve(source.path) !== out
|
|
20357
20532
|
)
|
|
20358
20533
|
],
|
|
20359
20534
|
options: {
|
|
@@ -20367,14 +20542,14 @@ async function runMount(args) {
|
|
|
20367
20542
|
log(
|
|
20368
20543
|
` ${styleId}: ${atlas.frames.length} cell(s) into ${atlas.sheet.width}x${atlas.sheet.height}` + (overBase ? ` over ${style.mount.base}` : " (new sheet)")
|
|
20369
20544
|
);
|
|
20370
|
-
log(` ${
|
|
20545
|
+
log(` ${path42.relative(process.cwd(), out)} + atlas/provenance JSON`);
|
|
20371
20546
|
for (const s of skipped) log(` skipped ${s.id}: ${s.reason}`);
|
|
20372
20547
|
}
|
|
20373
20548
|
}
|
|
20374
20549
|
async function runExport(args) {
|
|
20375
20550
|
const { loaded, specs, lock } = await openProject2(args);
|
|
20376
20551
|
const format = args.format ?? "generic";
|
|
20377
|
-
const manifestDir =
|
|
20552
|
+
const manifestDir = path42.dirname(path42.resolve(args.manifest));
|
|
20378
20553
|
const selected = specs.filter((spec) => {
|
|
20379
20554
|
if (spec.generator !== "tiles" && spec.generator !== "terrain") return false;
|
|
20380
20555
|
if (args.styles.length && !args.styles.includes(spec.styleId)) return false;
|
|
@@ -20390,12 +20565,12 @@ async function runExport(args) {
|
|
|
20390
20565
|
for (const spec of selected) {
|
|
20391
20566
|
const entry = lock.entries[lockKey(spec.styleId, spec.assetId)];
|
|
20392
20567
|
const style = loaded.manifest.styles[spec.styleId];
|
|
20393
|
-
const defaultBase =
|
|
20394
|
-
const base = args.out ?
|
|
20568
|
+
const defaultBase = path42.resolve(manifestDir, style.outDir, `${spec.assetId}-tileset`);
|
|
20569
|
+
const base = args.out ? path42.resolve(args.out.replace(/\.(?:png|json|tsj|tres)$/i, "")) : defaultBase;
|
|
20395
20570
|
const result = exportTileset(entry, spec, {
|
|
20396
20571
|
format,
|
|
20397
20572
|
manifestDir,
|
|
20398
|
-
imageName:
|
|
20573
|
+
imageName: path42.basename(`${base}.png`),
|
|
20399
20574
|
columns: args.columns
|
|
20400
20575
|
});
|
|
20401
20576
|
const outputs = [
|
|
@@ -20413,7 +20588,7 @@ async function runExport(args) {
|
|
|
20413
20588
|
asset: spec.assetId,
|
|
20414
20589
|
columns: args.columns ?? null,
|
|
20415
20590
|
format,
|
|
20416
|
-
image:
|
|
20591
|
+
image: path42.basename(`${base}.png`),
|
|
20417
20592
|
providerRules: result.generic.providerRules,
|
|
20418
20593
|
style: spec.styleId,
|
|
20419
20594
|
tileType: spec.tileType ?? null
|
|
@@ -20423,7 +20598,7 @@ async function runExport(args) {
|
|
|
20423
20598
|
` ${spec.styleId}/${spec.assetId}: ${result.generic.tiles.length} tile(s), ${result.generic.sheet.width}x${result.generic.sheet.height} (${format})`
|
|
20424
20599
|
);
|
|
20425
20600
|
log(
|
|
20426
|
-
` ${
|
|
20601
|
+
` ${path42.relative(process.cwd(), base)}.png + ${path42.basename(base)}${result.extension} + .pixelkiln.json`
|
|
20427
20602
|
);
|
|
20428
20603
|
}
|
|
20429
20604
|
}
|
|
@@ -20436,21 +20611,21 @@ function sheetFormat(args) {
|
|
|
20436
20611
|
}
|
|
20437
20612
|
|
|
20438
20613
|
// src/cli/commands/pixellab-utils.ts
|
|
20439
|
-
import
|
|
20614
|
+
import path44 from "path";
|
|
20440
20615
|
|
|
20441
20616
|
// src/pixellab-utils.ts
|
|
20442
20617
|
import { existsSync as existsSync33 } from "fs";
|
|
20443
20618
|
import { mkdir as mkdir13, readFile as readFile29, writeFile as writeFile18 } from "fs/promises";
|
|
20444
|
-
import
|
|
20619
|
+
import path43 from "path";
|
|
20445
20620
|
var UNZOOM_MIN_SIDE = 256;
|
|
20446
20621
|
var UNZOOM_MAX_AREA = 2048 * 2048;
|
|
20447
20622
|
function defaultUnzoomOut(input) {
|
|
20448
|
-
const ext =
|
|
20623
|
+
const ext = path43.extname(input);
|
|
20449
20624
|
return `${ext ? input.slice(0, -ext.length) : input}.unzoomed.png`;
|
|
20450
20625
|
}
|
|
20451
20626
|
async function unzoomFile(client2, input, opts = {}) {
|
|
20452
|
-
const out =
|
|
20453
|
-
if (
|
|
20627
|
+
const out = path43.resolve(opts.out ?? defaultUnzoomOut(input));
|
|
20628
|
+
if (path43.resolve(input) === out) throw new Error("unzoom will not overwrite its own input; pass a different --out");
|
|
20454
20629
|
if (existsSync33(out) && !opts.force) throw new Error(`${out} already exists; pass --force to replace it`);
|
|
20455
20630
|
const bytes = await readFile29(input);
|
|
20456
20631
|
const metadata = imageMetadata(bytes);
|
|
@@ -20465,7 +20640,7 @@ async function unzoomFile(client2, input, opts = {}) {
|
|
|
20465
20640
|
throw new Error(`${input} is ${width}x${height}; PixelLab's unzoom takes at most 2048x2048 worth of pixels`);
|
|
20466
20641
|
}
|
|
20467
20642
|
const res = await client2.unzoom({ image: { base64: bytes.toString("base64"), format }, quantize: opts.quantize });
|
|
20468
|
-
await mkdir13(
|
|
20643
|
+
await mkdir13(path43.dirname(out), { recursive: true });
|
|
20469
20644
|
await writeFile18(out, res.png);
|
|
20470
20645
|
return { out, original: res.originalSize, unzoomed: res.unzoomedSize, zoomFactor: res.zoomFactor, usage: res.usage };
|
|
20471
20646
|
}
|
|
@@ -20473,7 +20648,7 @@ var FONT_WEIGHTS = ["Bold", "Regular"];
|
|
|
20473
20648
|
var FONT_GLYPH_SIZES = [8, 16, 32, 64];
|
|
20474
20649
|
var FONT_COST_GENERATIONS = 25;
|
|
20475
20650
|
function fontOutputPaths(out) {
|
|
20476
|
-
const base =
|
|
20651
|
+
const base = path43.resolve(out.replace(/\.(ttf|png)$/i, ""));
|
|
20477
20652
|
return { ttf: `${base}.ttf`, atlas: `${base}.png` };
|
|
20478
20653
|
}
|
|
20479
20654
|
async function generateFont(client2, opts) {
|
|
@@ -20507,7 +20682,7 @@ async function generateFont(client2, opts) {
|
|
|
20507
20682
|
client2.download(job.download_ttf_url),
|
|
20508
20683
|
client2.download(job.download_atlas_url)
|
|
20509
20684
|
]);
|
|
20510
|
-
await mkdir13(
|
|
20685
|
+
await mkdir13(path43.dirname(ttf), { recursive: true });
|
|
20511
20686
|
await writeFile18(ttf, ttfBytes);
|
|
20512
20687
|
await writeFile18(atlas, atlasBytes);
|
|
20513
20688
|
return { jobId, ttf, atlas, glyphPx: job.glyph_px ?? null, usage: job.usage };
|
|
@@ -20523,7 +20698,7 @@ async function generateFont(client2, opts) {
|
|
|
20523
20698
|
|
|
20524
20699
|
// src/cli/commands/pixellab-utils.ts
|
|
20525
20700
|
function loadKeys(args) {
|
|
20526
|
-
loadEnvFiles(
|
|
20701
|
+
loadEnvFiles(path44.dirname(path44.resolve(args.manifest)));
|
|
20527
20702
|
loadEnvFiles(process.cwd());
|
|
20528
20703
|
}
|
|
20529
20704
|
async function runUnzoom(args) {
|
|
@@ -20589,11 +20764,11 @@ async function runFont(args) {
|
|
|
20589
20764
|
}
|
|
20590
20765
|
|
|
20591
20766
|
// src/cli/commands/quality.ts
|
|
20592
|
-
import
|
|
20767
|
+
import path46 from "path";
|
|
20593
20768
|
import { readFile as readFile31 } from "fs/promises";
|
|
20594
20769
|
|
|
20595
20770
|
// src/pipeline/quality-regression.ts
|
|
20596
|
-
import
|
|
20771
|
+
import path45 from "path";
|
|
20597
20772
|
import { existsSync as existsSync34 } from "fs";
|
|
20598
20773
|
import { readFile as readFile30 } from "fs/promises";
|
|
20599
20774
|
import { z as z13 } from "zod";
|
|
@@ -20601,7 +20776,7 @@ var SHA256_RE = /^[0-9a-f]{64}$/;
|
|
|
20601
20776
|
var HEX_RE = /^#[0-9a-f]{6}$/;
|
|
20602
20777
|
var MAX_REDMEAN_DISTANCE = 765;
|
|
20603
20778
|
var QualityPathSchema = z13.string().min(1).refine(
|
|
20604
|
-
(value) => !
|
|
20779
|
+
(value) => !path45.posix.isAbsolute(value) && !value.includes("\\"),
|
|
20605
20780
|
"expected a portable relative path"
|
|
20606
20781
|
);
|
|
20607
20782
|
var DEFAULT_QUALITY_TOLERANCES = {
|
|
@@ -20748,7 +20923,7 @@ function measureDecodedImageQuality(png, digest2) {
|
|
|
20748
20923
|
};
|
|
20749
20924
|
}
|
|
20750
20925
|
async function measureImageQuality(file) {
|
|
20751
|
-
const absolute =
|
|
20926
|
+
const absolute = path45.resolve(file);
|
|
20752
20927
|
let bytes;
|
|
20753
20928
|
try {
|
|
20754
20929
|
bytes = await readFile30(absolute);
|
|
@@ -20770,11 +20945,11 @@ function resolveQualityInputs(raw, inputsFilePath) {
|
|
|
20770
20945
|
}
|
|
20771
20946
|
const ids = parsed.data.map((entry) => entry.id);
|
|
20772
20947
|
if (new Set(ids).size !== ids.length) throw new Error("--inputs contains a duplicate quality case id.");
|
|
20773
|
-
const root =
|
|
20948
|
+
const root = path45.dirname(path45.resolve(inputsFilePath));
|
|
20774
20949
|
return parsed.data.map((entry) => ({
|
|
20775
20950
|
id: entry.id,
|
|
20776
|
-
file:
|
|
20777
|
-
...entry.record ? { record:
|
|
20951
|
+
file: path45.resolve(root, entry.path),
|
|
20952
|
+
...entry.record ? { record: path45.resolve(root, entry.record) } : {},
|
|
20778
20953
|
tolerances: QualityToleranceSchema.parse({
|
|
20779
20954
|
...DEFAULT_QUALITY_TOLERANCES,
|
|
20780
20955
|
...entry.tolerances
|
|
@@ -20782,7 +20957,7 @@ function resolveQualityInputs(raw, inputsFilePath) {
|
|
|
20782
20957
|
}));
|
|
20783
20958
|
}
|
|
20784
20959
|
function portableRelative2(from, to) {
|
|
20785
|
-
return
|
|
20960
|
+
return path45.relative(from, path45.resolve(to)).split(path45.sep).join("/") || ".";
|
|
20786
20961
|
}
|
|
20787
20962
|
async function verifyRecordForImage(recordPath, imagePath) {
|
|
20788
20963
|
const verification = await verifyArtifactBundle(recordPath);
|
|
@@ -20797,20 +20972,20 @@ async function verifyRecordForImage(recordPath, imagePath) {
|
|
|
20797
20972
|
if (record.kind !== "refine" || options === null || typeof options !== "object" || options.schema !== "pixelkiln-quality") {
|
|
20798
20973
|
throw new Error(`Quality record ${recordPath} is not a PixelKiln refinement record.`);
|
|
20799
20974
|
}
|
|
20800
|
-
const outputs = record.outputs.map((output) =>
|
|
20801
|
-
if (!outputs.includes(
|
|
20975
|
+
const outputs = record.outputs.map((output) => path45.resolve(path45.dirname(recordPath), output.path));
|
|
20976
|
+
if (!outputs.includes(path45.resolve(imagePath))) {
|
|
20802
20977
|
throw new Error(`Quality record ${recordPath} does not own ${imagePath}.`);
|
|
20803
20978
|
}
|
|
20804
20979
|
}
|
|
20805
20980
|
async function snapshotQualityBaseline(inputs, baselinePath, options = {}) {
|
|
20806
20981
|
if (!inputs.length) throw new Error("Cannot snapshot an empty quality baseline.");
|
|
20807
|
-
const absolute =
|
|
20982
|
+
const absolute = path45.resolve(baselinePath);
|
|
20808
20983
|
for (const input of inputs) {
|
|
20809
|
-
if (
|
|
20984
|
+
if (path45.resolve(input.file) === absolute || input.record && path45.resolve(input.record) === absolute) {
|
|
20810
20985
|
throw new Error(`Quality baseline output would overwrite an input: ${absolute}.`);
|
|
20811
20986
|
}
|
|
20812
20987
|
}
|
|
20813
|
-
const root =
|
|
20988
|
+
const root = path45.dirname(absolute);
|
|
20814
20989
|
const cases = [];
|
|
20815
20990
|
for (const input of [...inputs].sort((a, b) => a.id.localeCompare(b.id))) {
|
|
20816
20991
|
const expected = await measureImageQuality(input.file);
|
|
@@ -20850,7 +21025,7 @@ async function snapshotQualityBaseline(inputs, baselinePath, options = {}) {
|
|
|
20850
21025
|
return { baseline, path: absolute, changed: result.changed.length > 0 };
|
|
20851
21026
|
}
|
|
20852
21027
|
async function readQualityBaseline(baselinePath) {
|
|
20853
|
-
const absolute =
|
|
21028
|
+
const absolute = path45.resolve(baselinePath);
|
|
20854
21029
|
let raw;
|
|
20855
21030
|
try {
|
|
20856
21031
|
raw = JSON.parse(await readFile30(absolute, "utf8"));
|
|
@@ -20923,12 +21098,12 @@ function regressionViolations(expected, actual, tolerances) {
|
|
|
20923
21098
|
return { violations, warnings };
|
|
20924
21099
|
}
|
|
20925
21100
|
async function checkQualityBaseline(baselinePath) {
|
|
20926
|
-
const absolute =
|
|
21101
|
+
const absolute = path45.resolve(baselinePath);
|
|
20927
21102
|
const baseline = await readQualityBaseline(absolute);
|
|
20928
|
-
const root =
|
|
21103
|
+
const root = path45.dirname(absolute);
|
|
20929
21104
|
const cases = [];
|
|
20930
21105
|
for (const entry of baseline.cases) {
|
|
20931
|
-
const file =
|
|
21106
|
+
const file = path45.resolve(root, entry.file);
|
|
20932
21107
|
let actual = null;
|
|
20933
21108
|
const violations = [];
|
|
20934
21109
|
const warnings = [];
|
|
@@ -20941,7 +21116,7 @@ async function checkQualityBaseline(baselinePath) {
|
|
|
20941
21116
|
violations.push(message4(error));
|
|
20942
21117
|
}
|
|
20943
21118
|
if (entry.record) {
|
|
20944
|
-
const recordPath =
|
|
21119
|
+
const recordPath = path45.resolve(root, entry.record.path);
|
|
20945
21120
|
try {
|
|
20946
21121
|
const recordHash = await sha256File(recordPath);
|
|
20947
21122
|
if (recordHash !== entry.record.sha256) violations.push("quality record hash changed");
|
|
@@ -20983,15 +21158,15 @@ async function runQuality(args) {
|
|
|
20983
21158
|
if (!args.out) throw new Error("quality snapshot needs --out <pixelkiln.quality.json>.");
|
|
20984
21159
|
let raw;
|
|
20985
21160
|
try {
|
|
20986
|
-
raw = JSON.parse(await readFile31(
|
|
21161
|
+
raw = JSON.parse(await readFile31(path46.resolve(args.inputs), "utf8"));
|
|
20987
21162
|
} catch (error) {
|
|
20988
21163
|
throw new Error(
|
|
20989
|
-
`Could not read quality inputs ${
|
|
21164
|
+
`Could not read quality inputs ${path46.resolve(args.inputs)}: ${error instanceof Error ? error.message : String(error)}`,
|
|
20990
21165
|
{ cause: error }
|
|
20991
21166
|
);
|
|
20992
21167
|
}
|
|
20993
21168
|
const inputs = resolveQualityInputs(raw, args.inputs);
|
|
20994
|
-
if (
|
|
21169
|
+
if (path46.resolve(args.inputs) === path46.resolve(args.out)) {
|
|
20995
21170
|
throw new Error("quality snapshot --out must not overwrite its --inputs file.");
|
|
20996
21171
|
}
|
|
20997
21172
|
const result = await snapshotQualityBaseline(inputs, args.out, { force: args.force });
|
|
@@ -21004,7 +21179,7 @@ async function runQuality(args) {
|
|
|
21004
21179
|
}, null, 2));
|
|
21005
21180
|
} else {
|
|
21006
21181
|
log(
|
|
21007
|
-
` ${result.changed ? "wrote" : "unchanged"} ${
|
|
21182
|
+
` ${result.changed ? "wrote" : "unchanged"} ${path46.relative(process.cwd(), result.path)} (${result.baseline.cases.length} case(s))`
|
|
21008
21183
|
);
|
|
21009
21184
|
log(` Review the tolerances, commit the baseline, then run quality check in CI.`);
|
|
21010
21185
|
}
|
|
@@ -21030,10 +21205,10 @@ async function runQuality(args) {
|
|
|
21030
21205
|
}
|
|
21031
21206
|
|
|
21032
21207
|
// src/cli/commands/recipe.ts
|
|
21033
|
-
import
|
|
21208
|
+
import path48 from "path";
|
|
21034
21209
|
|
|
21035
21210
|
// src/recipes.ts
|
|
21036
|
-
import
|
|
21211
|
+
import path47 from "path";
|
|
21037
21212
|
import { existsSync as existsSync35 } from "fs";
|
|
21038
21213
|
import { readdir as readdir3, readFile as readFile32 } from "fs/promises";
|
|
21039
21214
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
@@ -21044,7 +21219,7 @@ var VERSION_RE = /^\d+\.\d+\.\d+$/;
|
|
|
21044
21219
|
var RECIPE_FILE = "pixelkiln.recipe.json";
|
|
21045
21220
|
var RECIPE_DIR_TOKEN = "{{recipeDir}}";
|
|
21046
21221
|
var PortablePathSchema = z14.string().min(1).refine((value) => {
|
|
21047
|
-
if (value.includes("\\") ||
|
|
21222
|
+
if (value.includes("\\") || path47.posix.isAbsolute(value)) return false;
|
|
21048
21223
|
const parts = value.split("/");
|
|
21049
21224
|
return !parts.includes("") && !parts.includes(".") && !parts.includes("..");
|
|
21050
21225
|
}, "expected a portable relative path without '.', '..', or backslashes");
|
|
@@ -21220,7 +21395,7 @@ function message5(error) {
|
|
|
21220
21395
|
return error instanceof Error ? error.message : String(error);
|
|
21221
21396
|
}
|
|
21222
21397
|
async function readRecipeFile(recipePath, bundled) {
|
|
21223
|
-
const absolute =
|
|
21398
|
+
const absolute = path47.resolve(recipePath);
|
|
21224
21399
|
let raw;
|
|
21225
21400
|
try {
|
|
21226
21401
|
raw = JSON.parse(await readFile32(absolute, "utf8"));
|
|
@@ -21234,13 +21409,13 @@ async function readRecipeFile(recipePath, bundled) {
|
|
|
21234
21409
|
` + parsed.error.issues.map((issue) => ` ${issue.path.join(".") || "$"}: ${issue.message}`).join("\n")
|
|
21235
21410
|
);
|
|
21236
21411
|
}
|
|
21237
|
-
return { recipe: parsed.data, authored: raw, path: absolute, dir:
|
|
21412
|
+
return { recipe: parsed.data, authored: raw, path: absolute, dir: path47.dirname(absolute), bundled };
|
|
21238
21413
|
}
|
|
21239
21414
|
async function walkRecipeFiles(root) {
|
|
21240
21415
|
if (!existsSync35(root)) return [];
|
|
21241
21416
|
const out = [];
|
|
21242
21417
|
for (const entry of await readdir3(root, { withFileTypes: true })) {
|
|
21243
|
-
const resolved =
|
|
21418
|
+
const resolved = path47.join(root, entry.name);
|
|
21244
21419
|
if (entry.isDirectory()) out.push(...await walkRecipeFiles(resolved));
|
|
21245
21420
|
else if (entry.isFile() && entry.name === RECIPE_FILE) out.push(resolved);
|
|
21246
21421
|
}
|
|
@@ -21251,7 +21426,7 @@ function bundledRecipeRoot() {
|
|
|
21251
21426
|
}
|
|
21252
21427
|
async function listBundledRecipes(root = bundledRecipeRoot()) {
|
|
21253
21428
|
const recipes = await Promise.all(
|
|
21254
|
-
(await walkRecipeFiles(
|
|
21429
|
+
(await walkRecipeFiles(path47.resolve(root))).map((file) => readRecipeFile(file, true))
|
|
21255
21430
|
);
|
|
21256
21431
|
return recipes.sort(
|
|
21257
21432
|
(a, b) => a.recipe.id.localeCompare(b.recipe.id) || compareVersions(b.recipe.version, a.recipe.version)
|
|
@@ -21274,9 +21449,9 @@ function parseSelector(selector) {
|
|
|
21274
21449
|
return { id: match[1], version: match[2] };
|
|
21275
21450
|
}
|
|
21276
21451
|
async function resolveRecipe(target, bundledRoot = bundledRecipeRoot()) {
|
|
21277
|
-
const local =
|
|
21452
|
+
const local = path47.resolve(target);
|
|
21278
21453
|
if (existsSync35(local)) {
|
|
21279
|
-
const recipePath =
|
|
21454
|
+
const recipePath = path47.basename(local) === RECIPE_FILE ? local : path47.join(local, RECIPE_FILE);
|
|
21280
21455
|
return readRecipeFile(recipePath, false);
|
|
21281
21456
|
}
|
|
21282
21457
|
if (target.startsWith(".") || target.startsWith("/") || target.includes("\\")) {
|
|
@@ -21290,7 +21465,7 @@ async function resolveRecipe(target, bundledRoot = bundledRecipeRoot()) {
|
|
|
21290
21465
|
return matches[0];
|
|
21291
21466
|
}
|
|
21292
21467
|
async function verifyFile(root, file) {
|
|
21293
|
-
const absolute =
|
|
21468
|
+
const absolute = path47.resolve(root, ...file.path.split("/"));
|
|
21294
21469
|
if (!existsSync35(absolute)) {
|
|
21295
21470
|
return { path: file.path, expectedSha256: file.sha256, actualSha256: null, status: "missing" };
|
|
21296
21471
|
}
|
|
@@ -21306,7 +21481,7 @@ async function verifyRecipe(target, options = {}) {
|
|
|
21306
21481
|
const loaded = await resolveRecipe(target, options.bundledRoot);
|
|
21307
21482
|
const actualIntegrity = recipeDigest(loaded.authored);
|
|
21308
21483
|
const files = await Promise.all(loaded.recipe.files.map((file) => verifyFile(loaded.dir, file)));
|
|
21309
|
-
const modelRoot = options.modelRoot ?
|
|
21484
|
+
const modelRoot = options.modelRoot ? path47.resolve(options.modelRoot) : null;
|
|
21310
21485
|
const models = modelRoot ? await Promise.all(loaded.recipe.models.map(async (model) => ({
|
|
21311
21486
|
...await verifyFile(modelRoot, model),
|
|
21312
21487
|
source: model.source,
|
|
@@ -21340,7 +21515,7 @@ async function verifyRecipe(target, options = {}) {
|
|
|
21340
21515
|
};
|
|
21341
21516
|
}
|
|
21342
21517
|
function renderStyle(style, destination, cwd) {
|
|
21343
|
-
const portableDir =
|
|
21518
|
+
const portableDir = path47.relative(cwd, destination).split(path47.sep).join("/") || ".";
|
|
21344
21519
|
const replace = (value) => {
|
|
21345
21520
|
if (typeof value === "string") return value.replaceAll(RECIPE_DIR_TOKEN, portableDir);
|
|
21346
21521
|
if (Array.isArray(value)) return value.map(replace);
|
|
@@ -21352,19 +21527,19 @@ function renderStyle(style, destination, cwd) {
|
|
|
21352
21527
|
return replace(style);
|
|
21353
21528
|
}
|
|
21354
21529
|
async function installRecipe(target, options = {}) {
|
|
21355
|
-
const cwd =
|
|
21530
|
+
const cwd = path47.resolve(options.cwd ?? process.cwd());
|
|
21356
21531
|
const loaded = await resolveRecipe(target, options.bundledRoot);
|
|
21357
21532
|
const verification = await verifyRecipe(loaded.path);
|
|
21358
21533
|
if (!verification.ok) {
|
|
21359
21534
|
throw new Error(`Recipe ${loaded.recipe.id}@${loaded.recipe.version} failed integrity verification.`);
|
|
21360
21535
|
}
|
|
21361
|
-
const destination = options.out ?
|
|
21536
|
+
const destination = options.out ? path47.resolve(cwd, options.out) : path47.join(cwd, "pixelkiln-recipes", ...loaded.recipe.id.split("/"), loaded.recipe.version);
|
|
21362
21537
|
const sources = [
|
|
21363
21538
|
{ path: RECIPE_FILE, source: loaded.path },
|
|
21364
|
-
...loaded.recipe.files.map((file) => ({ path: file.path, source:
|
|
21539
|
+
...loaded.recipe.files.map((file) => ({ path: file.path, source: path47.join(loaded.dir, ...file.path.split("/")) }))
|
|
21365
21540
|
];
|
|
21366
21541
|
const files = await Promise.all(sources.map(async (file) => ({
|
|
21367
|
-
path:
|
|
21542
|
+
path: path47.join(destination, ...file.path.split("/")),
|
|
21368
21543
|
data: await readFile32(file.source)
|
|
21369
21544
|
})));
|
|
21370
21545
|
if (!options.force) {
|
|
@@ -21464,14 +21639,14 @@ async function runRecipe(args) {
|
|
|
21464
21639
|
}, null, 2));
|
|
21465
21640
|
} else {
|
|
21466
21641
|
log(` installed ${result.recipe.id}@${result.recipe.version}`);
|
|
21467
|
-
log(` ${
|
|
21642
|
+
log(` ${path48.relative(process.cwd(), result.destination) || "."}`);
|
|
21468
21643
|
log(`
|
|
21469
21644
|
Add this entry under your manifest's styles object:`);
|
|
21470
21645
|
log(JSON.stringify({ [result.styleId]: result.style }, null, 2));
|
|
21471
21646
|
if (result.recipe.models.length) {
|
|
21472
21647
|
log(`
|
|
21473
21648
|
Models are not downloaded automatically. Verify them with:`);
|
|
21474
|
-
log(` pixelkiln recipe verify ${
|
|
21649
|
+
log(` pixelkiln recipe verify ${path48.relative(process.cwd(), result.destination)} --model-root <ComfyUI/models>`);
|
|
21475
21650
|
}
|
|
21476
21651
|
}
|
|
21477
21652
|
return;
|
|
@@ -21479,7 +21654,7 @@ async function runRecipe(args) {
|
|
|
21479
21654
|
}
|
|
21480
21655
|
|
|
21481
21656
|
// src/cli/commands/refine.ts
|
|
21482
|
-
import
|
|
21657
|
+
import path49 from "path";
|
|
21483
21658
|
async function runRefine(args) {
|
|
21484
21659
|
if (args.from || args.subcommand === "approve") {
|
|
21485
21660
|
if (!args.from) {
|
|
@@ -21509,12 +21684,12 @@ async function runRefine(args) {
|
|
|
21509
21684
|
` recovered ${result3.detection.columns}x${result3.detection.rows} native grid (${result3.detection.confidence}: ${result3.detection.consensus})`
|
|
21510
21685
|
);
|
|
21511
21686
|
log(` enforced ${result3.palette.length}-color palette without dithering`);
|
|
21512
|
-
log(` wrote ${
|
|
21513
|
-
log(` quality record: ${
|
|
21687
|
+
log(` wrote ${path49.relative(process.cwd(), result3.output)}`);
|
|
21688
|
+
log(` quality record: ${path49.relative(process.cwd(), result3.record)}`);
|
|
21514
21689
|
log(`
|
|
21515
21690
|
Automated checks passed. Human 1\xD7 review is still required:`);
|
|
21516
21691
|
log(
|
|
21517
|
-
` pixelkiln refine approve --from ${
|
|
21692
|
+
` pixelkiln refine approve --from ${path49.relative(process.cwd(), result3.record)} --reviewer "Your Name"`
|
|
21518
21693
|
);
|
|
21519
21694
|
}
|
|
21520
21695
|
return;
|
|
@@ -21542,7 +21717,7 @@ async function runRefine(args) {
|
|
|
21542
21717
|
if (args.json) log(JSON.stringify(result3, null, 2));
|
|
21543
21718
|
else {
|
|
21544
21719
|
log(
|
|
21545
|
-
result3.options.frameSet ? ` approved ${result3.outputs.length} frames in ${
|
|
21720
|
+
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
21721
|
);
|
|
21547
21722
|
}
|
|
21548
21723
|
return;
|
|
@@ -21551,7 +21726,7 @@ async function runRefine(args) {
|
|
|
21551
21726
|
if (args.json) {
|
|
21552
21727
|
log(JSON.stringify(result2, null, 2));
|
|
21553
21728
|
} else {
|
|
21554
|
-
log(` ${result2.safe ? "release-ready" : "not release-ready"}: ${
|
|
21729
|
+
log(` ${result2.safe ? "release-ready" : "not release-ready"}: ${path49.relative(process.cwd(), result2.output || result2.record)}`);
|
|
21555
21730
|
for (const reason of result2.reasons) log(` ${reason}`);
|
|
21556
21731
|
}
|
|
21557
21732
|
if (!result2.safe) process.exitCode = 1;
|
|
@@ -21597,7 +21772,7 @@ async function runRefine(args) {
|
|
|
21597
21772
|
log(` ${item.state.padEnd(16)} ${item.key} ${item.reason}`);
|
|
21598
21773
|
if (item.state === "needs-approval") {
|
|
21599
21774
|
log(
|
|
21600
|
-
` pixelkiln refine approve --from ${
|
|
21775
|
+
` pixelkiln refine approve --from ${path49.relative(process.cwd(), item.record)} --reviewer "Your Name"`
|
|
21601
21776
|
);
|
|
21602
21777
|
}
|
|
21603
21778
|
}
|
|
@@ -21608,7 +21783,7 @@ async function runRefine(args) {
|
|
|
21608
21783
|
}
|
|
21609
21784
|
|
|
21610
21785
|
// src/cli/commands/skeleton.ts
|
|
21611
|
-
import
|
|
21786
|
+
import path50 from "path";
|
|
21612
21787
|
import { existsSync as existsSync36 } from "fs";
|
|
21613
21788
|
import { readFile as readFile33, writeFile as writeFile19 } from "fs/promises";
|
|
21614
21789
|
|
|
@@ -21721,12 +21896,12 @@ var Canvas = class {
|
|
|
21721
21896
|
async function runEstimateSkeleton(args) {
|
|
21722
21897
|
const image = args.target;
|
|
21723
21898
|
if (!image) throw new UsageError("estimate-skeleton needs an image path: pixelkiln estimate-skeleton <image> [--out keypoints.json]");
|
|
21724
|
-
if (args.out && existsSync36(
|
|
21899
|
+
if (args.out && existsSync36(path50.resolve(args.out)) && !args.force) {
|
|
21725
21900
|
throw new UsageError(`${args.out} already exists; pass --force to replace it`);
|
|
21726
21901
|
}
|
|
21727
|
-
loadEnvFiles(
|
|
21902
|
+
loadEnvFiles(path50.dirname(path50.resolve(args.manifest)));
|
|
21728
21903
|
loadEnvFiles(process.cwd());
|
|
21729
|
-
const bytes = await readFile33(
|
|
21904
|
+
const bytes = await readFile33(path50.resolve(image));
|
|
21730
21905
|
const metadata = imageMetadata(bytes);
|
|
21731
21906
|
if (!metadata) throw new UsageError(`${image} is not a readable PNG or JPEG`);
|
|
21732
21907
|
const sizeProblem = estimateSkeletonSizeProblem(metadata.width, metadata.height);
|
|
@@ -21738,7 +21913,7 @@ async function runEstimateSkeleton(args) {
|
|
|
21738
21913
|
const frames = args.frames ?? DEFAULT_SCAFFOLD_FRAMES;
|
|
21739
21914
|
const json = JSON.stringify(scaffoldSkeletonSet(res.keypoints, frames), null, 2);
|
|
21740
21915
|
if (args.out) {
|
|
21741
|
-
await writeFile19(
|
|
21916
|
+
await writeFile19(path50.resolve(args.out), json + "\n");
|
|
21742
21917
|
log(` wrote ${args.out}: the estimated pose and ${frames} frames to edit`);
|
|
21743
21918
|
log(` preview it with: pixelkiln skeleton-preview ${args.out} --from ${image}`);
|
|
21744
21919
|
} else {
|
|
@@ -21751,8 +21926,8 @@ async function runSkeletonPreview(args) {
|
|
|
21751
21926
|
let sourceFile;
|
|
21752
21927
|
let label;
|
|
21753
21928
|
if (target.endsWith(".json")) {
|
|
21754
|
-
keypointsFile =
|
|
21755
|
-
sourceFile = args.from ?
|
|
21929
|
+
keypointsFile = path50.resolve(target);
|
|
21930
|
+
sourceFile = args.from ? path50.resolve(args.from) : void 0;
|
|
21756
21931
|
label = target;
|
|
21757
21932
|
} else {
|
|
21758
21933
|
const loaded = await loadManifest(args.manifest);
|
|
@@ -21764,7 +21939,7 @@ async function runSkeletonPreview(args) {
|
|
|
21764
21939
|
);
|
|
21765
21940
|
}
|
|
21766
21941
|
keypointsFile = spec.revision.keypointsFile;
|
|
21767
|
-
sourceFile = args.from ?
|
|
21942
|
+
sourceFile = args.from ? path50.resolve(args.from) : spec.revision.sourceFile;
|
|
21768
21943
|
label = `${spec.styleId}/${spec.assetId}`;
|
|
21769
21944
|
}
|
|
21770
21945
|
if (!existsSync36(keypointsFile)) throw new UsageError(`Keypoints file does not exist yet: ${keypointsFile}`);
|
|
@@ -21785,10 +21960,10 @@ async function runSkeletonPreview(args) {
|
|
|
21785
21960
|
source = decodePng(bytes);
|
|
21786
21961
|
}
|
|
21787
21962
|
}
|
|
21788
|
-
const out =
|
|
21963
|
+
const out = path50.resolve(args.out ?? keypointsFile.replace(/\.json$/, "") + ".preview.png");
|
|
21789
21964
|
await writeFile19(out, renderSkeletonSheet(set, { source }));
|
|
21790
21965
|
log(` ${label}: ${set.frames.length} frames after the starting pose`);
|
|
21791
|
-
const shown =
|
|
21966
|
+
const shown = path50.relative(process.cwd(), out);
|
|
21792
21967
|
log(` wrote ${shown.startsWith("..") ? out : shown}`);
|
|
21793
21968
|
}
|
|
21794
21969
|
|
|
@@ -21834,15 +22009,15 @@ async function runTools(args) {
|
|
|
21834
22009
|
}
|
|
21835
22010
|
|
|
21836
22011
|
// src/cli/commands/workspace.ts
|
|
21837
|
-
import
|
|
22012
|
+
import path51 from "path";
|
|
21838
22013
|
import { existsSync as existsSync37 } from "fs";
|
|
21839
22014
|
async function runWorkspace(args) {
|
|
21840
|
-
const workspacePath =
|
|
21841
|
-
const dir =
|
|
22015
|
+
const workspacePath = path51.resolve(args.workspace ?? "pixelkiln.workspace.json");
|
|
22016
|
+
const dir = path51.dirname(workspacePath);
|
|
21842
22017
|
if (args.subcommand === "add") {
|
|
21843
|
-
const manifestPath =
|
|
22018
|
+
const manifestPath = path51.resolve(args.target);
|
|
21844
22019
|
const loadedTarget = await loadManifest(manifestPath);
|
|
21845
|
-
const lockPath = args.explicitLock ?
|
|
22020
|
+
const lockPath = args.explicitLock ? path51.resolve(args.explicitLock) : path51.join(path51.dirname(manifestPath), "pixelkiln.lock.json");
|
|
21846
22021
|
const ws = await loadWorkspace(workspacePath);
|
|
21847
22022
|
const id = args.name ?? loadedTarget.manifest.name;
|
|
21848
22023
|
if (ws.projects.some((p) => p.id === id)) {
|
|
@@ -21862,7 +22037,7 @@ async function runWorkspace(args) {
|
|
|
21862
22037
|
...args.account ? { account: args.account } : {}
|
|
21863
22038
|
};
|
|
21864
22039
|
await saveWorkspace(workspacePath, { version: 1, projects: [...ws.projects, project] });
|
|
21865
|
-
log(` registered "${id}" in ${
|
|
22040
|
+
log(` registered "${id}" in ${path51.relative(process.cwd(), workspacePath)}`);
|
|
21866
22041
|
log(` manifest: ${project.manifest}`);
|
|
21867
22042
|
log(` lock: ${project.lock}`);
|
|
21868
22043
|
if (!existsSync37(lockPath)) {
|
|
@@ -21874,7 +22049,7 @@ async function runWorkspace(args) {
|
|
|
21874
22049
|
}
|
|
21875
22050
|
if (args.subcommand === "remove") {
|
|
21876
22051
|
const ws = await loadWorkspace(workspacePath);
|
|
21877
|
-
const resolvedTarget =
|
|
22052
|
+
const resolvedTarget = path51.resolve(args.target);
|
|
21878
22053
|
const match = ws.projects.find(
|
|
21879
22054
|
(p) => p.id === args.target || resolveProject(dir, p).manifestPath === resolvedTarget
|
|
21880
22055
|
);
|
|
@@ -21885,7 +22060,7 @@ async function runWorkspace(args) {
|
|
|
21885
22060
|
version: 1,
|
|
21886
22061
|
projects: ws.projects.filter((p) => p !== match)
|
|
21887
22062
|
});
|
|
21888
|
-
log(` removed "${match.id}" from ${
|
|
22063
|
+
log(` removed "${match.id}" from ${path51.relative(process.cwd(), workspacePath)}`);
|
|
21889
22064
|
return;
|
|
21890
22065
|
}
|
|
21891
22066
|
if (args.subcommand === "list") {
|
|
@@ -21897,9 +22072,9 @@ async function runWorkspace(args) {
|
|
|
21897
22072
|
if (args.json) {
|
|
21898
22073
|
log(JSON.stringify({ version: 1, workspace: workspacePath, projects: ws.projects, diagnostics }, null, 2));
|
|
21899
22074
|
} else if (!ws.projects.length) {
|
|
21900
|
-
log(` no projects registered in ${
|
|
22075
|
+
log(` no projects registered in ${path51.relative(process.cwd(), workspacePath)}`);
|
|
21901
22076
|
} else {
|
|
21902
|
-
log(` ${ws.projects.length} project(s) in ${
|
|
22077
|
+
log(` ${ws.projects.length} project(s) in ${path51.relative(process.cwd(), workspacePath)}:`);
|
|
21903
22078
|
for (const p of ws.projects) {
|
|
21904
22079
|
log(` ${p.id.padEnd(24)} ${p.manifest.padEnd(40)} (${p.provider}${p.account ? `, ${p.account}` : ""})`);
|
|
21905
22080
|
}
|
|
@@ -21917,7 +22092,7 @@ async function runWorkspace(args) {
|
|
|
21917
22092
|
if (args.json) {
|
|
21918
22093
|
log(JSON.stringify({ ...report, workspace: workspacePath }, null, 2));
|
|
21919
22094
|
} else {
|
|
21920
|
-
log(` workspace: ${
|
|
22095
|
+
log(` workspace: ${path51.relative(process.cwd(), workspacePath)}`);
|
|
21921
22096
|
for (const p of report.projects) {
|
|
21922
22097
|
if (p.error) {
|
|
21923
22098
|
log(`
|