pixelkiln 0.72.0 → 0.73.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/dist/cli.js +705 -486
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +409 -236
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1876 -259
- package/dist/index.d.ts +1876 -259
- package/dist/index.js +392 -219
- package/dist/index.js.map +1 -1
- package/docs/GENERATORS.md +1 -1
- package/docs/MANIFEST.md +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -67,7 +67,7 @@ function exitCodeFor(err) {
|
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
// src/cli/main.ts
|
|
70
|
-
import { existsSync as
|
|
70
|
+
import { existsSync as existsSync37 } from "fs";
|
|
71
71
|
import { readFile as readFile32 } from "fs/promises";
|
|
72
72
|
|
|
73
73
|
// src/cli/args.ts
|
|
@@ -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(path50) {
|
|
669
669
|
return new Promise((resolve, reject) => {
|
|
670
670
|
const hash = createHash("sha256");
|
|
671
|
-
const stream = createReadStream(
|
|
671
|
+
const stream = createReadStream(path50);
|
|
672
672
|
stream.on("data", (chunk2) => hash.update(chunk2));
|
|
673
673
|
stream.on("error", reject);
|
|
674
674
|
stream.on("end", () => resolve(hash.digest("hex")));
|
|
@@ -1810,7 +1810,7 @@ var AssetSchema = z.object({
|
|
|
1810
1810
|
path: ["mirror"]
|
|
1811
1811
|
});
|
|
1812
1812
|
}
|
|
1813
|
-
if (asset.prompt === void 0 && !asset.mirror && !asset.portrait && !asset.outfit) {
|
|
1813
|
+
if (asset.prompt === void 0 && !asset.mirror && !asset.portrait && !asset.outfit && !asset.animation?.template) {
|
|
1814
1814
|
context.addIssue({
|
|
1815
1815
|
code: z.ZodIssueCode.custom,
|
|
1816
1816
|
message: "Required",
|
|
@@ -6167,9 +6167,9 @@ var PixelLabClient = class {
|
|
|
6167
6167
|
apiKey;
|
|
6168
6168
|
http;
|
|
6169
6169
|
/** Retries transport failures, 429, and 5xx; see http.ts for the policy. */
|
|
6170
|
-
async request(
|
|
6170
|
+
async request(path50, init) {
|
|
6171
6171
|
const auth = this.apiKey.startsWith("Bearer ") ? this.apiKey : `Bearer ${this.apiKey}`;
|
|
6172
|
-
const res = await this.http(`${BASE}${
|
|
6172
|
+
const res = await this.http(`${BASE}${path50}`, {
|
|
6173
6173
|
...init,
|
|
6174
6174
|
headers: {
|
|
6175
6175
|
Authorization: auth,
|
|
@@ -6179,13 +6179,13 @@ var PixelLabClient = class {
|
|
|
6179
6179
|
});
|
|
6180
6180
|
const text = await res.text();
|
|
6181
6181
|
if (!res.ok) {
|
|
6182
|
-
throw new PixelLabError(`${init?.method ?? "GET"} ${
|
|
6182
|
+
throw new PixelLabError(`${init?.method ?? "GET"} ${path50} \u2192 ${res.status}`, res.status, text);
|
|
6183
6183
|
}
|
|
6184
6184
|
if (!text) return {};
|
|
6185
6185
|
try {
|
|
6186
6186
|
return JSON.parse(text);
|
|
6187
6187
|
} catch {
|
|
6188
|
-
throw new Error(`${init?.method ?? "GET"} ${
|
|
6188
|
+
throw new Error(`${init?.method ?? "GET"} ${path50} returned invalid JSON`);
|
|
6189
6189
|
}
|
|
6190
6190
|
}
|
|
6191
6191
|
async balance() {
|
|
@@ -6577,10 +6577,10 @@ var PixelLabClient = class {
|
|
|
6577
6577
|
const palette = args.paletteSwatchBase64 ? { color_image: { type: "base64", base64: args.paletteSwatchBase64, format: "png" }, force_colors: true } : {};
|
|
6578
6578
|
const encode = (image) => ({ type: "base64", base64: image.base64, format: image.format });
|
|
6579
6579
|
const south = args.reference?.south;
|
|
6580
|
-
let
|
|
6580
|
+
let path50;
|
|
6581
6581
|
let body;
|
|
6582
6582
|
if (args.mode === "v3") {
|
|
6583
|
-
|
|
6583
|
+
path50 = "/create-character-v3";
|
|
6584
6584
|
body = {
|
|
6585
6585
|
description: args.description,
|
|
6586
6586
|
image_size: imageSize,
|
|
@@ -6594,7 +6594,7 @@ var PixelLabClient = class {
|
|
|
6594
6594
|
...args.enhancePrompt ? { enhance_prompt: true } : {}
|
|
6595
6595
|
};
|
|
6596
6596
|
} else if (args.mode === "pro-flash") {
|
|
6597
|
-
|
|
6597
|
+
path50 = "/create-character-pro-flash";
|
|
6598
6598
|
const traits = args.styleTraits;
|
|
6599
6599
|
body = {
|
|
6600
6600
|
description: args.description,
|
|
@@ -6618,7 +6618,7 @@ var PixelLabClient = class {
|
|
|
6618
6618
|
} : {}
|
|
6619
6619
|
};
|
|
6620
6620
|
} else if (args.mode === "pro") {
|
|
6621
|
-
|
|
6621
|
+
path50 = "/create-character-pro";
|
|
6622
6622
|
body = {
|
|
6623
6623
|
description: args.description,
|
|
6624
6624
|
image_size: imageSize,
|
|
@@ -6632,7 +6632,7 @@ var PixelLabClient = class {
|
|
|
6632
6632
|
...!south && args.styleCharacterId ? { style_character_id: args.styleCharacterId } : {}
|
|
6633
6633
|
};
|
|
6634
6634
|
} else {
|
|
6635
|
-
|
|
6635
|
+
path50 = args.directions === 4 ? "/create-character-with-4-directions" : "/create-character-with-8-directions";
|
|
6636
6636
|
body = {
|
|
6637
6637
|
description: args.description,
|
|
6638
6638
|
image_size: imageSize,
|
|
@@ -6649,8 +6649,8 @@ var PixelLabClient = class {
|
|
|
6649
6649
|
...palette
|
|
6650
6650
|
};
|
|
6651
6651
|
}
|
|
6652
|
-
const raw = await this.request(
|
|
6653
|
-
return validateResponse(CharacterSubmitSchema, raw,
|
|
6652
|
+
const raw = await this.request(path50, { method: "POST", body: JSON.stringify(body) });
|
|
6653
|
+
return validateResponse(CharacterSubmitSchema, raw, path50.slice(1));
|
|
6654
6654
|
}
|
|
6655
6655
|
/** A text edit of an existing character, applied to every direction and stored as a sibling. */
|
|
6656
6656
|
async createCharacterState(args) {
|
|
@@ -7312,7 +7312,11 @@ function characterCost(spec) {
|
|
|
7312
7312
|
return 20;
|
|
7313
7313
|
}
|
|
7314
7314
|
if (character.mode === "standard") return 1;
|
|
7315
|
-
if (character.mode === "v3")
|
|
7315
|
+
if (character.mode === "v3") {
|
|
7316
|
+
const south = character.reference?.south;
|
|
7317
|
+
if (south) return Math.max(1, Math.ceil(south.width * south.height * 8 / 65536));
|
|
7318
|
+
return 1 + Math.max(1, Math.ceil(px * 8 / 65536));
|
|
7319
|
+
}
|
|
7316
7320
|
if (character.mode === "pro-flash") {
|
|
7317
7321
|
const south = character.reference?.south;
|
|
7318
7322
|
return south ? proFlashCharacterCost(south.width, south.height, true) : proFlashCharacterCost(spec.width, spec.height, false);
|
|
@@ -9506,9 +9510,9 @@ var RetroDiffusionClient = class {
|
|
|
9506
9510
|
if (!response.ok) throw new ProviderError("retrodiffusion", `Retro Diffusion download failed (${response.status})`, { status: response.status });
|
|
9507
9511
|
return Buffer.from(await response.arrayBuffer());
|
|
9508
9512
|
}
|
|
9509
|
-
async call(
|
|
9513
|
+
async call(path50, init = {}) {
|
|
9510
9514
|
if (!this.token) throw new Error("RD_API_KEY is not set");
|
|
9511
|
-
const response = await this.request(`${this.baseUrl}${
|
|
9515
|
+
const response = await this.request(`${this.baseUrl}${path50}`, {
|
|
9512
9516
|
...init,
|
|
9513
9517
|
headers: {
|
|
9514
9518
|
"Content-Type": "application/json",
|
|
@@ -11813,10 +11817,10 @@ function isSha256Hash(value) {
|
|
|
11813
11817
|
function parseCache(value) {
|
|
11814
11818
|
return HashCacheSchema.parse(value);
|
|
11815
11819
|
}
|
|
11816
|
-
async function loadCache(
|
|
11817
|
-
if (!existsSync10(
|
|
11820
|
+
async function loadCache(path50) {
|
|
11821
|
+
if (!existsSync10(path50)) return { version: 1, hashes: {} };
|
|
11818
11822
|
try {
|
|
11819
|
-
const parsed = HashCacheSchema.safeParse(JSON.parse(await readFile8(
|
|
11823
|
+
const parsed = HashCacheSchema.safeParse(JSON.parse(await readFile8(path50, "utf8")));
|
|
11820
11824
|
if (!parsed.success) return { version: 1, hashes: {} };
|
|
11821
11825
|
return {
|
|
11822
11826
|
version: 1,
|
|
@@ -11828,18 +11832,18 @@ async function loadCache(path49) {
|
|
|
11828
11832
|
return { version: 1, hashes: {} };
|
|
11829
11833
|
}
|
|
11830
11834
|
}
|
|
11831
|
-
async function saveCache(
|
|
11835
|
+
async function saveCache(path50, cache) {
|
|
11832
11836
|
const sorted = {};
|
|
11833
11837
|
for (const key of Object.keys(cache.hashes).sort()) {
|
|
11834
11838
|
const hash = cache.hashes[key];
|
|
11835
11839
|
if (!isSha256Hash(hash)) throw new Error(`Refusing to cache invalid SHA-256 for ${key}`);
|
|
11836
11840
|
sorted[key] = hash;
|
|
11837
11841
|
}
|
|
11838
|
-
await mkdir5(pathModule.dirname(pathModule.resolve(
|
|
11839
|
-
const tmp = `${
|
|
11842
|
+
await mkdir5(pathModule.dirname(pathModule.resolve(path50)), { recursive: true });
|
|
11843
|
+
const tmp = `${path50}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
11840
11844
|
try {
|
|
11841
11845
|
await writeFile5(tmp, JSON.stringify({ version: 1, hashes: sorted }, null, 2) + "\n");
|
|
11842
|
-
await rename5(tmp,
|
|
11846
|
+
await rename5(tmp, path50);
|
|
11843
11847
|
} finally {
|
|
11844
11848
|
await rm5(tmp, { force: true });
|
|
11845
11849
|
}
|
|
@@ -13826,13 +13830,16 @@ var NewAnimationSchema = z6.object({
|
|
|
13826
13830
|
direction: CharacterDirectionSchema.optional(),
|
|
13827
13831
|
frames: z6.number().int().min(4).max(16).optional(),
|
|
13828
13832
|
fps: z6.number().int().min(1).max(60).optional(),
|
|
13833
|
+
/** One loop per named direction, plus free mirrors; expanded when the manifest loads. */
|
|
13834
|
+
directions: z6.array(CharacterDirectionSchema).min(1).optional(),
|
|
13829
13835
|
mode: CharacterAnimationModeSchema.optional(),
|
|
13830
13836
|
/** What is being animated, when the parent's own description would mislead the model (`character` only). */
|
|
13831
13837
|
subject: z6.string().min(1).optional(),
|
|
13832
13838
|
enhancePrompt: z6.boolean().optional()
|
|
13833
13839
|
}).strict();
|
|
13834
13840
|
var NewAssetSchema = z6.object({
|
|
13835
|
-
|
|
13841
|
+
/** A portrait, an outfit, or a mirror has none of its own; the loader says where one is needed. */
|
|
13842
|
+
prompt: z6.string().optional(),
|
|
13836
13843
|
category: z6.string().optional(),
|
|
13837
13844
|
width: z6.number().int().optional(),
|
|
13838
13845
|
height: z6.number().int().optional(),
|
|
@@ -13867,7 +13874,24 @@ var NewAssetSchema = z6.object({
|
|
|
13867
13874
|
/** A pose or outfit of an existing `character`/`objectPro` base or state. */
|
|
13868
13875
|
state: NewStateSchema.optional(),
|
|
13869
13876
|
/** A loop of an existing `character`/`objectPro` base or state. */
|
|
13870
|
-
animation: NewAnimationSchema.optional()
|
|
13877
|
+
animation: NewAnimationSchema.optional(),
|
|
13878
|
+
/** A bust of an existing character. */
|
|
13879
|
+
portrait: CharacterPortraitSchema.optional(),
|
|
13880
|
+
/** An existing loop re-clothed from a reference image (uploaded first). */
|
|
13881
|
+
outfit: CharacterOutfitSchema.optional(),
|
|
13882
|
+
/** Another asset of the same style, flipped; free. */
|
|
13883
|
+
mirror: z6.string().min(1).optional(),
|
|
13884
|
+
/**
|
|
13885
|
+
* `character`/`objectPro` bases: the subject's own sprite (uploaded
|
|
13886
|
+
* first), or one per direction, which PixelLab rotates instead of drawing.
|
|
13887
|
+
*/
|
|
13888
|
+
reference: z6.union([z6.string().min(1), z6.record(CharacterDirectionSchema, z6.string().min(1))]).optional(),
|
|
13889
|
+
/** `character` pro bases: a concept image that seeds the design. */
|
|
13890
|
+
concept: z6.string().min(1).optional(),
|
|
13891
|
+
/** `character` pro bases: another character in the style whose look this one follows. */
|
|
13892
|
+
styleCharacter: z6.string().min(1).optional(),
|
|
13893
|
+
/** `character` standard humanoid bases: this character's proportions, over the style's. */
|
|
13894
|
+
proportions: CharacterProportionsSchema.optional()
|
|
13871
13895
|
}).strict().refine((asset) => !(asset.revision && (asset.state || asset.animation)), {
|
|
13872
13896
|
message: "an asset is a revision, a state, or an animation \u2014 not more than one"
|
|
13873
13897
|
}).refine((asset) => !(asset.state && asset.animation), {
|
|
@@ -13878,59 +13902,95 @@ var CANDIDATE_OPTION = {
|
|
|
13878
13902
|
comfyui: "numImages",
|
|
13879
13903
|
scenario: "numOutputs"
|
|
13880
13904
|
};
|
|
13905
|
+
var NewStyleSchema = z6.object({
|
|
13906
|
+
generator: z6.enum(["character", "objectPro"]),
|
|
13907
|
+
/** Where the style's art is written, inside the project. */
|
|
13908
|
+
outDir: z6.string().min(1).refine((dir) => !dir.split(/[\\/]/).includes("..") && !/^([a-z]:)?[\\/]/i.test(dir), {
|
|
13909
|
+
message: "outDir must be a path inside the project"
|
|
13910
|
+
}),
|
|
13911
|
+
size: z6.number().int().min(16).max(256).optional(),
|
|
13912
|
+
mode: CharacterModeSchema.optional(),
|
|
13913
|
+
directions: z6.union([z6.literal(4), z6.literal(8)]).optional(),
|
|
13914
|
+
template: z6.string().min(1).optional(),
|
|
13915
|
+
view: z6.string().min(1).max(64).optional(),
|
|
13916
|
+
proportions: CharacterProportionsSchema.optional(),
|
|
13917
|
+
isometric: z6.boolean().optional(),
|
|
13918
|
+
textGuidanceScale: z6.number().min(1).max(20).optional(),
|
|
13919
|
+
enhancePrompt: z6.boolean().optional(),
|
|
13920
|
+
promptPrefix: z6.string().optional(),
|
|
13921
|
+
promptSuffix: z6.string().optional(),
|
|
13922
|
+
palette: z6.array(z6.string().regex(/^#?[0-9a-f]{6}$/i, "expected a six-digit hex colour")).max(256).optional()
|
|
13923
|
+
}).strict();
|
|
13924
|
+
var PatchAssetEdit = z6.object({
|
|
13925
|
+
action: z6.literal("patch-asset"),
|
|
13926
|
+
assetId: z6.string().min(1),
|
|
13927
|
+
patch: AssetPatchSchema
|
|
13928
|
+
}).strict();
|
|
13929
|
+
var AddAssetEdit = z6.object({
|
|
13930
|
+
action: z6.literal("add-asset"),
|
|
13931
|
+
assetId: z6.string().min(1).regex(/^[^/\\]+$/, "asset ids cannot contain slashes"),
|
|
13932
|
+
asset: NewAssetSchema
|
|
13933
|
+
}).strict();
|
|
13934
|
+
var SetSourceEdit = z6.object({
|
|
13935
|
+
action: z6.literal("set-source"),
|
|
13936
|
+
assetId: z6.string().min(1),
|
|
13937
|
+
styleId: z6.string().min(1),
|
|
13938
|
+
/** Manifest-relative path of the art that stands in for this asset in this style. */
|
|
13939
|
+
source: z6.string().min(1)
|
|
13940
|
+
}).strict();
|
|
13941
|
+
var ClearSourceEdit = z6.object({
|
|
13942
|
+
action: z6.literal("clear-source"),
|
|
13943
|
+
assetId: z6.string().min(1),
|
|
13944
|
+
styleId: z6.string().min(1)
|
|
13945
|
+
}).strict();
|
|
13946
|
+
var PatchStyleEdit = z6.object({
|
|
13947
|
+
action: z6.literal("patch-style"),
|
|
13948
|
+
styleId: z6.string().min(1),
|
|
13949
|
+
patch: z6.object({
|
|
13950
|
+
/** Candidates per generation, written to the provider's own option. */
|
|
13951
|
+
candidates: z6.number().int().min(1).max(64).optional(),
|
|
13952
|
+
/** Empty clears the style's own value (inherit, or the default). */
|
|
13953
|
+
promptPrefix: z6.string().optional(),
|
|
13954
|
+
promptSuffix: z6.string().optional(),
|
|
13955
|
+
/** `#rrggbb` values; null or empty clears the style's own palette. */
|
|
13956
|
+
palette: z6.array(z6.string().regex(/^#?[0-9a-f]{6}$/i, "expected a six-digit hex colour")).max(256).nullable().optional(),
|
|
13957
|
+
/** Snap downloaded art to the palette; false clears the style's own value. */
|
|
13958
|
+
enforcePalette: z6.boolean().optional(),
|
|
13959
|
+
/** Provider view name (PixelLab: low top-down, high top-down, side); empty clears. */
|
|
13960
|
+
view: z6.string().max(64).nullable().optional(),
|
|
13961
|
+
/** pixflux only; null clears the style's own value. */
|
|
13962
|
+
noBackground: z6.boolean().nullable().optional()
|
|
13963
|
+
}).strict().refine((patch) => Object.keys(patch).length > 0, { message: "nothing to change" })
|
|
13964
|
+
}).strict();
|
|
13965
|
+
var AddStyleEdit = z6.object({
|
|
13966
|
+
action: z6.literal("add-style"),
|
|
13967
|
+
styleId: z6.string().min(1).regex(/^[^/\\:]+$/, "style ids cannot contain slashes or colons"),
|
|
13968
|
+
style: NewStyleSchema
|
|
13969
|
+
}).strict();
|
|
13970
|
+
var Envelope = {
|
|
13971
|
+
/** Workspace project id; omitted for a single-project gallery. */
|
|
13972
|
+
project: z6.string().min(1).optional(),
|
|
13973
|
+
/** Manifest bytes the page last saw; a mismatch refuses the write. */
|
|
13974
|
+
expectedSha256: HexSha
|
|
13975
|
+
};
|
|
13976
|
+
var SingleEdit = z6.discriminatedUnion("action", [PatchAssetEdit, AddAssetEdit, SetSourceEdit, ClearSourceEdit, PatchStyleEdit, AddStyleEdit]);
|
|
13881
13977
|
var ManifestEditSchema = z6.discriminatedUnion("action", [
|
|
13978
|
+
PatchAssetEdit.extend(Envelope),
|
|
13979
|
+
AddAssetEdit.extend(Envelope),
|
|
13980
|
+
SetSourceEdit.extend(Envelope),
|
|
13981
|
+
ClearSourceEdit.extend(Envelope),
|
|
13982
|
+
PatchStyleEdit.extend(Envelope),
|
|
13983
|
+
AddStyleEdit.extend(Envelope),
|
|
13984
|
+
/**
|
|
13985
|
+
* Several edits as one write: a new character's style, base, and loops
|
|
13986
|
+
* land together or not at all, validated once as the finished manifest.
|
|
13987
|
+
* Each edit sees the ones before it, so a base can name a style the same
|
|
13988
|
+
* batch adds.
|
|
13989
|
+
*/
|
|
13882
13990
|
z6.object({
|
|
13883
|
-
action: z6.literal("
|
|
13884
|
-
|
|
13885
|
-
|
|
13886
|
-
assetId: z6.string().min(1),
|
|
13887
|
-
/** Manifest bytes the page last saw; a mismatch refuses the write. */
|
|
13888
|
-
expectedSha256: HexSha,
|
|
13889
|
-
patch: AssetPatchSchema
|
|
13890
|
-
}).strict(),
|
|
13891
|
-
z6.object({
|
|
13892
|
-
action: z6.literal("add-asset"),
|
|
13893
|
-
project: z6.string().min(1).optional(),
|
|
13894
|
-
assetId: z6.string().min(1).regex(/^[^/\\]+$/, "asset ids cannot contain slashes"),
|
|
13895
|
-
expectedSha256: HexSha,
|
|
13896
|
-
asset: NewAssetSchema
|
|
13897
|
-
}).strict(),
|
|
13898
|
-
z6.object({
|
|
13899
|
-
action: z6.literal("set-source"),
|
|
13900
|
-
project: z6.string().min(1).optional(),
|
|
13901
|
-
assetId: z6.string().min(1),
|
|
13902
|
-
styleId: z6.string().min(1),
|
|
13903
|
-
/** Manifest-relative path of the art that stands in for this asset in this style. */
|
|
13904
|
-
source: z6.string().min(1),
|
|
13905
|
-
expectedSha256: HexSha
|
|
13906
|
-
}).strict(),
|
|
13907
|
-
z6.object({
|
|
13908
|
-
action: z6.literal("clear-source"),
|
|
13909
|
-
project: z6.string().min(1).optional(),
|
|
13910
|
-
assetId: z6.string().min(1),
|
|
13911
|
-
styleId: z6.string().min(1),
|
|
13912
|
-
expectedSha256: HexSha
|
|
13913
|
-
}).strict(),
|
|
13914
|
-
z6.object({
|
|
13915
|
-
action: z6.literal("patch-style"),
|
|
13916
|
-
project: z6.string().min(1).optional(),
|
|
13917
|
-
styleId: z6.string().min(1),
|
|
13918
|
-
expectedSha256: HexSha,
|
|
13919
|
-
patch: z6.object({
|
|
13920
|
-
/** Candidates per generation, written to the provider's own option. */
|
|
13921
|
-
candidates: z6.number().int().min(1).max(64).optional(),
|
|
13922
|
-
/** Empty clears the style's own value (inherit, or the default). */
|
|
13923
|
-
promptPrefix: z6.string().optional(),
|
|
13924
|
-
promptSuffix: z6.string().optional(),
|
|
13925
|
-
/** `#rrggbb` values; null or empty clears the style's own palette. */
|
|
13926
|
-
palette: z6.array(z6.string().regex(/^#?[0-9a-f]{6}$/i, "expected a six-digit hex colour")).max(256).nullable().optional(),
|
|
13927
|
-
/** Snap downloaded art to the palette; false clears the style's own value. */
|
|
13928
|
-
enforcePalette: z6.boolean().optional(),
|
|
13929
|
-
/** Provider view name (PixelLab: low top-down, high top-down, side); empty clears. */
|
|
13930
|
-
view: z6.string().max(64).nullable().optional(),
|
|
13931
|
-
/** pixflux only; null clears the style's own value. */
|
|
13932
|
-
noBackground: z6.boolean().nullable().optional()
|
|
13933
|
-
}).strict().refine((patch) => Object.keys(patch).length > 0, { message: "nothing to change" })
|
|
13991
|
+
action: z6.literal("batch"),
|
|
13992
|
+
...Envelope,
|
|
13993
|
+
edits: z6.array(SingleEdit).min(1).max(64)
|
|
13934
13994
|
}).strict()
|
|
13935
13995
|
]);
|
|
13936
13996
|
var ManifestDriftError = class extends Error {
|
|
@@ -13979,6 +14039,18 @@ function undeclaredAsset(raw, assetId) {
|
|
|
13979
14039
|
}
|
|
13980
14040
|
function applyEdit(raw, edit) {
|
|
13981
14041
|
raw.assets ??= {};
|
|
14042
|
+
if (edit.action === "batch") {
|
|
14043
|
+
for (const each of edit.edits) applyEdit(raw, each);
|
|
14044
|
+
return;
|
|
14045
|
+
}
|
|
14046
|
+
if (edit.action === "add-style") {
|
|
14047
|
+
raw.styles ??= {};
|
|
14048
|
+
if (Object.hasOwn(raw.styles, edit.styleId)) throw new ManifestEditError(`style "${edit.styleId}" already exists`);
|
|
14049
|
+
const style = { ...edit.style };
|
|
14050
|
+
if (edit.style.palette) style.palette = edit.style.palette.map((color) => "#" + color.replace(/^#/, "").toLowerCase());
|
|
14051
|
+
raw.styles[edit.styleId] = style;
|
|
14052
|
+
return;
|
|
14053
|
+
}
|
|
13982
14054
|
if (edit.action === "set-source" || edit.action === "clear-source") {
|
|
13983
14055
|
const asset2 = Object.hasOwn(raw.assets, edit.assetId) ? raw.assets[edit.assetId] : void 0;
|
|
13984
14056
|
if (!asset2) throw undeclaredAsset(raw, edit.assetId);
|
|
@@ -14056,6 +14128,15 @@ function applyEdit(raw, edit) {
|
|
|
14056
14128
|
if (edit.asset.animation && !declaredAssets(raw).has(edit.asset.animation.of)) {
|
|
14057
14129
|
throw new ManifestEditError(`animation parent "${edit.asset.animation.of}" is not declared by the manifest`);
|
|
14058
14130
|
}
|
|
14131
|
+
for (const [what, parent] of [
|
|
14132
|
+
["portrait", edit.asset.portrait?.of],
|
|
14133
|
+
["outfit", edit.asset.outfit?.of],
|
|
14134
|
+
["mirror", edit.asset.mirror]
|
|
14135
|
+
]) {
|
|
14136
|
+
if (parent && !declaredAssets(raw).has(parent)) {
|
|
14137
|
+
throw new ManifestEditError(`${what} source "${parent}" is not declared by the manifest`);
|
|
14138
|
+
}
|
|
14139
|
+
}
|
|
14059
14140
|
raw.assets[edit.assetId] = asset2;
|
|
14060
14141
|
return;
|
|
14061
14142
|
}
|
|
@@ -14083,11 +14164,10 @@ function applyEdit(raw, edit) {
|
|
|
14083
14164
|
else delete asset.tags;
|
|
14084
14165
|
}
|
|
14085
14166
|
}
|
|
14086
|
-
async function
|
|
14167
|
+
async function withCandidate(manifestPath, edit, use, unchanged) {
|
|
14087
14168
|
const absolute = path23.resolve(manifestPath);
|
|
14088
14169
|
const original = await readFile15(absolute, "utf8");
|
|
14089
|
-
|
|
14090
|
-
if (before !== edit.expectedSha256) throw new ManifestDriftError(absolute);
|
|
14170
|
+
if (sha256(original) !== edit.expectedSha256) throw new ManifestDriftError(absolute);
|
|
14091
14171
|
let raw;
|
|
14092
14172
|
try {
|
|
14093
14173
|
raw = JSON.parse(original);
|
|
@@ -14098,25 +14178,63 @@ async function applyManifestEdit(manifestPath, edit) {
|
|
|
14098
14178
|
}
|
|
14099
14179
|
applyEdit(raw, edit);
|
|
14100
14180
|
const next = serializeLike(original, raw);
|
|
14101
|
-
if (next === original) return
|
|
14181
|
+
if (next === original) return unchanged(absolute, original);
|
|
14102
14182
|
const tmp = path23.join(
|
|
14103
14183
|
path23.dirname(absolute),
|
|
14104
14184
|
`.${path23.basename(absolute)}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`
|
|
14105
14185
|
);
|
|
14106
14186
|
try {
|
|
14107
14187
|
await writeFile10(tmp, next);
|
|
14188
|
+
let specs;
|
|
14108
14189
|
try {
|
|
14109
|
-
|
|
14110
|
-
await resolveSpecs(loaded);
|
|
14190
|
+
specs = await resolveSpecs(await loadManifest(tmp));
|
|
14111
14191
|
} catch (error) {
|
|
14112
14192
|
const message6 = error instanceof Error ? error.message : String(error);
|
|
14113
14193
|
throw new ManifestEditError(message6.replace(tmp, absolute));
|
|
14114
14194
|
}
|
|
14115
|
-
await
|
|
14195
|
+
return await use({ absolute, original, next, tmp, specs });
|
|
14116
14196
|
} finally {
|
|
14117
14197
|
await rm7(tmp, { force: true });
|
|
14118
14198
|
}
|
|
14119
|
-
|
|
14199
|
+
}
|
|
14200
|
+
async function applyManifestEdit(manifestPath, edit) {
|
|
14201
|
+
return withCandidate(
|
|
14202
|
+
manifestPath,
|
|
14203
|
+
edit,
|
|
14204
|
+
async ({ absolute, next, tmp }) => {
|
|
14205
|
+
await rename7(tmp, absolute);
|
|
14206
|
+
return { manifestPath: absolute, sha256: sha256(next), changed: true };
|
|
14207
|
+
},
|
|
14208
|
+
(absolute, original) => ({ manifestPath: absolute, sha256: sha256(original), changed: false })
|
|
14209
|
+
);
|
|
14210
|
+
}
|
|
14211
|
+
async function priceManifestEdit(manifestPath, edit) {
|
|
14212
|
+
return withCandidate(
|
|
14213
|
+
manifestPath,
|
|
14214
|
+
edit,
|
|
14215
|
+
async ({ absolute, specs }) => {
|
|
14216
|
+
const before = new Map((await resolveSpecs(await loadManifest(absolute))).map((spec) => [`${spec.styleId}/${spec.assetId}`, spec.specHash]));
|
|
14217
|
+
const items = [];
|
|
14218
|
+
const totals = {};
|
|
14219
|
+
for (const spec of specs) {
|
|
14220
|
+
const key = `${spec.styleId}/${spec.assetId}`;
|
|
14221
|
+
const was = before.get(key);
|
|
14222
|
+
if (was === spec.specHash) continue;
|
|
14223
|
+
items.push({
|
|
14224
|
+
key,
|
|
14225
|
+
styleId: spec.styleId,
|
|
14226
|
+
assetId: spec.assetId,
|
|
14227
|
+
change: was === void 0 ? "new" : "changed",
|
|
14228
|
+
cost: spec.cost,
|
|
14229
|
+
costUnit: spec.costUnit,
|
|
14230
|
+
candidates: spec.candidates
|
|
14231
|
+
});
|
|
14232
|
+
totals[spec.costUnit] = (totals[spec.costUnit] ?? 0) + spec.cost;
|
|
14233
|
+
}
|
|
14234
|
+
return { items, totals };
|
|
14235
|
+
},
|
|
14236
|
+
() => ({ items: [], totals: {} })
|
|
14237
|
+
);
|
|
14120
14238
|
}
|
|
14121
14239
|
|
|
14122
14240
|
// src/pipeline/hand-edit.ts
|
|
@@ -14388,63 +14506,104 @@ async function runEdit(args) {
|
|
|
14388
14506
|
}
|
|
14389
14507
|
|
|
14390
14508
|
// src/cli/commands/gallery.ts
|
|
14391
|
-
import
|
|
14392
|
-
import { existsSync as
|
|
14509
|
+
import path34 from "path";
|
|
14510
|
+
import { existsSync as existsSync26 } from "fs";
|
|
14393
14511
|
|
|
14394
14512
|
// src/gallery/snapshot.ts
|
|
14395
14513
|
import { createHash as createHash3 } from "crypto";
|
|
14396
|
-
import { existsSync as
|
|
14514
|
+
import { existsSync as existsSync21 } from "fs";
|
|
14397
14515
|
import { readFile as readFile18, stat as stat2 } from "fs/promises";
|
|
14398
|
-
import
|
|
14516
|
+
import path30 from "path";
|
|
14399
14517
|
|
|
14400
14518
|
// src/gallery/edit.ts
|
|
14401
|
-
import
|
|
14402
|
-
import { z as
|
|
14519
|
+
import path28 from "path";
|
|
14520
|
+
import { z as z10 } from "zod";
|
|
14403
14521
|
|
|
14404
|
-
// src/gallery/
|
|
14522
|
+
// src/gallery/upload.ts
|
|
14405
14523
|
import { existsSync as existsSync18 } from "fs";
|
|
14406
|
-
import { mkdir as mkdir10,
|
|
14524
|
+
import { mkdir as mkdir10, writeFile as writeFile12 } from "fs/promises";
|
|
14407
14525
|
import path26 from "path";
|
|
14408
14526
|
import { z as z8 } from "zod";
|
|
14409
|
-
var
|
|
14527
|
+
var MAX_UPLOAD_BYTES = 8 * 1024 * 1024;
|
|
14528
|
+
var UploadImageSchema = z8.object({
|
|
14529
|
+
action: z8.literal("upload-image"),
|
|
14410
14530
|
project: z8.string().min(1).optional(),
|
|
14411
|
-
|
|
14412
|
-
|
|
14413
|
-
|
|
14531
|
+
/** Manifest-relative destination, ending .png, .jpg, or .jpeg. */
|
|
14532
|
+
path: z8.string().min(1),
|
|
14533
|
+
base64: z8.string().regex(/^[A-Za-z0-9+/]*={0,2}$/).max(Math.ceil(MAX_UPLOAD_BYTES / 3) * 4),
|
|
14534
|
+
/** Replace a file already at `path`. */
|
|
14535
|
+
overwrite: z8.boolean().optional()
|
|
14414
14536
|
}).strict();
|
|
14415
|
-
|
|
14416
|
-
|
|
14417
|
-
|
|
14418
|
-
|
|
14419
|
-
|
|
14537
|
+
async function saveProjectImage(root, request) {
|
|
14538
|
+
const file = path26.resolve(root, request.path);
|
|
14539
|
+
const relative = path26.relative(root, file);
|
|
14540
|
+
if (relative.startsWith("..") || path26.isAbsolute(relative)) {
|
|
14541
|
+
throw new ManifestEditError("an upload must stay inside the project");
|
|
14542
|
+
}
|
|
14543
|
+
const extension = path26.extname(file).toLowerCase();
|
|
14544
|
+
const expected = extension === ".png" ? "png" : extension === ".jpg" || extension === ".jpeg" ? "jpeg" : null;
|
|
14545
|
+
if (!expected) throw new ManifestEditError("an upload must be a .png, .jpg, or .jpeg path");
|
|
14546
|
+
const bytes = Buffer.from(request.base64, "base64");
|
|
14547
|
+
if (!bytes.length || bytes.length > MAX_UPLOAD_BYTES) {
|
|
14548
|
+
throw new ManifestEditError(`an upload must be between 1 byte and ${MAX_UPLOAD_BYTES / 1024 / 1024} MB`);
|
|
14549
|
+
}
|
|
14550
|
+
const metadata = imageMetadata(bytes);
|
|
14551
|
+
if (!metadata) throw new ManifestEditError("the upload is not a readable PNG or JPEG");
|
|
14552
|
+
if (metadata.format !== expected) {
|
|
14553
|
+
throw new ManifestEditError(`the upload is a ${metadata.format.toUpperCase()}; name it ${metadata.format === "png" ? ".png" : ".jpg"}`);
|
|
14554
|
+
}
|
|
14555
|
+
if (existsSync18(file) && !request.overwrite) {
|
|
14556
|
+
throw new ManifestEditError(`${relative.split(path26.sep).join("/")} already exists; choose another name, or replace it`);
|
|
14557
|
+
}
|
|
14558
|
+
await mkdir10(path26.dirname(file), { recursive: true });
|
|
14559
|
+
await writeFile12(file, bytes);
|
|
14560
|
+
return relative.split(path26.sep).join("/");
|
|
14561
|
+
}
|
|
14562
|
+
|
|
14563
|
+
// src/gallery/skeleton.ts
|
|
14564
|
+
import { existsSync as existsSync19 } from "fs";
|
|
14565
|
+
import { mkdir as mkdir11, readFile as readFile17, rm as rm9, writeFile as writeFile13 } from "fs/promises";
|
|
14566
|
+
import path27 from "path";
|
|
14567
|
+
import { z as z9 } from "zod";
|
|
14568
|
+
var SkeletonEstimateRequestSchema = z9.object({
|
|
14569
|
+
project: z9.string().min(1).optional(),
|
|
14570
|
+
styleId: z9.string().min(1),
|
|
14571
|
+
assetId: z9.string().min(1),
|
|
14572
|
+
frames: z9.number().int().min(3).max(15).optional()
|
|
14573
|
+
}).strict();
|
|
14574
|
+
var SkeletonAnimationRequestSchema = z9.object({
|
|
14575
|
+
action: z9.literal("create-skeleton-animation"),
|
|
14576
|
+
project: z9.string().min(1).optional(),
|
|
14577
|
+
expectedSha256: z9.string().regex(/^[0-9a-f]{64}$/),
|
|
14578
|
+
styleId: z9.string().min(1),
|
|
14420
14579
|
/** The sprite the animation poses. */
|
|
14421
|
-
from:
|
|
14422
|
-
assetId:
|
|
14580
|
+
from: z9.string().min(1),
|
|
14581
|
+
assetId: z9.string().min(1).regex(/^[^/\\]+$/, "asset ids cannot contain slashes"),
|
|
14423
14582
|
/** The motion, sent as PixelLab's `action`. */
|
|
14424
|
-
prompt:
|
|
14583
|
+
prompt: z9.string().min(1),
|
|
14425
14584
|
direction: CharacterDirectionSchema,
|
|
14426
|
-
description:
|
|
14427
|
-
skeletonTemplate:
|
|
14585
|
+
description: z9.string().min(1).optional(),
|
|
14586
|
+
skeletonTemplate: z9.string().min(1).optional(),
|
|
14428
14587
|
/** Manifest-relative JSON path inside the project. */
|
|
14429
|
-
keypointsFile:
|
|
14588
|
+
keypointsFile: z9.string().min(1),
|
|
14430
14589
|
/** Poses to write to `keypointsFile`; omitted to use a file that already exists. */
|
|
14431
14590
|
set: SkeletonSetSchema.optional(),
|
|
14432
14591
|
/** Replace an existing `keypointsFile` with `set`. */
|
|
14433
|
-
overwrite:
|
|
14592
|
+
overwrite: z9.boolean().optional()
|
|
14434
14593
|
}).strict();
|
|
14435
|
-
var SkeletonKeypointsUpdateSchema =
|
|
14436
|
-
action:
|
|
14437
|
-
project:
|
|
14438
|
-
styleId:
|
|
14439
|
-
assetId:
|
|
14594
|
+
var SkeletonKeypointsUpdateSchema = z9.object({
|
|
14595
|
+
action: z9.literal("update-skeleton-keypoints"),
|
|
14596
|
+
project: z9.string().min(1).optional(),
|
|
14597
|
+
styleId: z9.string().min(1),
|
|
14598
|
+
assetId: z9.string().min(1),
|
|
14440
14599
|
/** The file's SHA-256 when the page loaded it; anything else on disk is a concurrent edit. */
|
|
14441
|
-
expectedSha256:
|
|
14600
|
+
expectedSha256: z9.string().regex(/^[0-9a-f]{64}$/),
|
|
14442
14601
|
set: SkeletonSetSchema
|
|
14443
14602
|
}).strict();
|
|
14444
14603
|
var DEFAULT_ESTIMATE_LIMIT = 10;
|
|
14445
14604
|
function skeletonSourceFile(spec) {
|
|
14446
|
-
if (spec.source) return
|
|
14447
|
-
if (spec.quality &&
|
|
14605
|
+
if (spec.source) return path27.resolve(spec.root, spec.source);
|
|
14606
|
+
if (spec.quality && existsSync19(spec.quality.outFile)) return spec.quality.outFile;
|
|
14448
14607
|
return spec.outFile;
|
|
14449
14608
|
}
|
|
14450
14609
|
function createGallerySkeletonHandlers(opts) {
|
|
@@ -14465,10 +14624,10 @@ function createGallerySkeletonHandlers(opts) {
|
|
|
14465
14624
|
const spec = ctx.specs.find((candidate) => lockKey(candidate.styleId, candidate.assetId) === key);
|
|
14466
14625
|
if (!spec) throw new ManifestEditError(`"${key}" is not declared by the manifest`);
|
|
14467
14626
|
const file = skeletonSourceFile(spec);
|
|
14468
|
-
if (!
|
|
14627
|
+
if (!existsSync19(file)) throw new ManifestEditError(`${key} has no image on disk to estimate from`);
|
|
14469
14628
|
const bytes = await readFile17(file);
|
|
14470
14629
|
const metadata = imageMetadata(bytes);
|
|
14471
|
-
if (!metadata) throw new ManifestEditError(`${
|
|
14630
|
+
if (!metadata) throw new ManifestEditError(`${path27.basename(file)} is not a readable PNG or JPEG`);
|
|
14472
14631
|
const problem = estimateSkeletonSizeProblem(metadata.width, metadata.height);
|
|
14473
14632
|
if (problem) throw new ManifestEditError(`${key} is ${problem}`);
|
|
14474
14633
|
if (used >= limit) {
|
|
@@ -14485,7 +14644,7 @@ function createGallerySkeletonHandlers(opts) {
|
|
|
14485
14644
|
log2(` estimated a skeleton for ${key} (PixelLab estimate-skeleton, ${used}/${limit} this session${billed ? `, billed ${billed.amount} ${billed.unit}` : ""})`);
|
|
14486
14645
|
return {
|
|
14487
14646
|
set: scaffoldSkeletonSet(res.keypoints, request.frames ?? DEFAULT_SCAFFOLD_FRAMES),
|
|
14488
|
-
source:
|
|
14647
|
+
source: path27.relative(ctx.loaded.root, file).split(path27.sep).join("/"),
|
|
14489
14648
|
session: status()
|
|
14490
14649
|
};
|
|
14491
14650
|
}
|
|
@@ -14499,21 +14658,21 @@ function billedAmount(usage) {
|
|
|
14499
14658
|
return typeof amount === "number" && Number.isFinite(amount) ? { unit: type, amount } : null;
|
|
14500
14659
|
}
|
|
14501
14660
|
async function createSkeletonAnimation(manifestPath, request) {
|
|
14502
|
-
const root =
|
|
14503
|
-
const file =
|
|
14504
|
-
const relative =
|
|
14505
|
-
if (!request.keypointsFile.endsWith(".json") || relative.startsWith("..") ||
|
|
14661
|
+
const root = path27.dirname(path27.resolve(manifestPath));
|
|
14662
|
+
const file = path27.resolve(root, request.keypointsFile);
|
|
14663
|
+
const relative = path27.relative(root, file);
|
|
14664
|
+
if (!request.keypointsFile.endsWith(".json") || relative.startsWith("..") || path27.isAbsolute(relative)) {
|
|
14506
14665
|
throw new ManifestEditError("the keypoints file must be a .json path inside the project");
|
|
14507
14666
|
}
|
|
14508
|
-
const existed =
|
|
14667
|
+
const existed = existsSync19(file);
|
|
14509
14668
|
let previous = null;
|
|
14510
14669
|
if (request.set) {
|
|
14511
14670
|
if (existed && !request.overwrite) {
|
|
14512
14671
|
throw new ManifestEditError(`${request.keypointsFile} already exists; choose another path, or replace it`);
|
|
14513
14672
|
}
|
|
14514
14673
|
if (existed) previous = await readFile17(file);
|
|
14515
|
-
await
|
|
14516
|
-
await
|
|
14674
|
+
await mkdir11(path27.dirname(file), { recursive: true });
|
|
14675
|
+
await writeFile13(file, JSON.stringify(request.set, null, 2) + "\n");
|
|
14517
14676
|
} else if (!existed) {
|
|
14518
14677
|
throw new ManifestEditError(`${request.keypointsFile} does not exist; paste poses or estimate them from the sprite`);
|
|
14519
14678
|
}
|
|
@@ -14528,7 +14687,7 @@ async function createSkeletonAnimation(manifestPath, request) {
|
|
|
14528
14687
|
revision: {
|
|
14529
14688
|
mode: "animate-skeleton",
|
|
14530
14689
|
from: request.from,
|
|
14531
|
-
keypointsFile: relative.split(
|
|
14690
|
+
keypointsFile: relative.split(path27.sep).join("/"),
|
|
14532
14691
|
direction: request.direction,
|
|
14533
14692
|
...request.description ? { description: request.description } : {},
|
|
14534
14693
|
...request.skeletonTemplate ? { skeletonTemplate: request.skeletonTemplate } : {}
|
|
@@ -14537,7 +14696,7 @@ async function createSkeletonAnimation(manifestPath, request) {
|
|
|
14537
14696
|
});
|
|
14538
14697
|
} catch (error) {
|
|
14539
14698
|
if (request.set) {
|
|
14540
|
-
if (previous) await
|
|
14699
|
+
if (previous) await writeFile13(file, previous);
|
|
14541
14700
|
else await rm9(file, { force: true });
|
|
14542
14701
|
}
|
|
14543
14702
|
throw error;
|
|
@@ -14549,56 +14708,56 @@ async function updateSkeletonKeypoints(ctx, request) {
|
|
|
14549
14708
|
if (!spec) throw new ManifestEditError(`"${key}" is not declared by the manifest`);
|
|
14550
14709
|
const file = spec.revision?.mode === "animate-skeleton" ? spec.revision.keypointsFile : void 0;
|
|
14551
14710
|
if (!file) throw new ManifestEditError(`${key} is not an animate-skeleton revision`);
|
|
14552
|
-
const relative =
|
|
14553
|
-
if (relative.startsWith("..") ||
|
|
14711
|
+
const relative = path27.relative(ctx.loaded.root, file);
|
|
14712
|
+
if (relative.startsWith("..") || path27.isAbsolute(relative)) {
|
|
14554
14713
|
throw new ManifestEditError(`${key}'s keypoints file is outside the project; edit it by hand`);
|
|
14555
14714
|
}
|
|
14556
|
-
const current =
|
|
14715
|
+
const current = existsSync19(file) ? sha256(await readFile17(file)) : null;
|
|
14557
14716
|
if (current !== request.expectedSha256) {
|
|
14558
14717
|
throw Object.assign(
|
|
14559
|
-
new Error(`${relative.split(
|
|
14718
|
+
new Error(`${relative.split(path27.sep).join("/")} changed on disk since this page loaded it; press Refresh and edit again`),
|
|
14560
14719
|
{ status: 409 }
|
|
14561
14720
|
);
|
|
14562
14721
|
}
|
|
14563
|
-
await
|
|
14564
|
-
return relative.split(
|
|
14722
|
+
await writeFile13(file, JSON.stringify(request.set, null, 2) + "\n");
|
|
14723
|
+
return relative.split(path27.sep).join("/");
|
|
14565
14724
|
}
|
|
14566
14725
|
|
|
14567
14726
|
// src/gallery/edit.ts
|
|
14568
|
-
var Base64 =
|
|
14569
|
-
var HandEditRequestSchema =
|
|
14570
|
-
|
|
14571
|
-
action:
|
|
14572
|
-
project:
|
|
14573
|
-
assetId:
|
|
14574
|
-
styleId:
|
|
14575
|
-
expectedSha256:
|
|
14727
|
+
var Base64 = z10.string().regex(/^[A-Za-z0-9+/]*={0,2}$/).max(Math.ceil(MAX_HAND_EDIT_BYTES / 3) * 4);
|
|
14728
|
+
var HandEditRequestSchema = z10.discriminatedUnion("action", [
|
|
14729
|
+
z10.object({
|
|
14730
|
+
action: z10.literal("start-edit"),
|
|
14731
|
+
project: z10.string().min(1).optional(),
|
|
14732
|
+
assetId: z10.string().min(1),
|
|
14733
|
+
styleId: z10.string().min(1),
|
|
14734
|
+
expectedSha256: z10.string().regex(/^[0-9a-f]{64}$/),
|
|
14576
14735
|
/** Also launch the author's editor on the file. */
|
|
14577
|
-
open:
|
|
14736
|
+
open: z10.boolean().optional()
|
|
14578
14737
|
}).strict(),
|
|
14579
|
-
|
|
14580
|
-
action:
|
|
14581
|
-
project:
|
|
14582
|
-
assetId:
|
|
14583
|
-
styleId:
|
|
14584
|
-
expectedSha256:
|
|
14738
|
+
z10.object({
|
|
14739
|
+
action: z10.literal("detach-edit"),
|
|
14740
|
+
project: z10.string().min(1).optional(),
|
|
14741
|
+
assetId: z10.string().min(1),
|
|
14742
|
+
styleId: z10.string().min(1),
|
|
14743
|
+
expectedSha256: z10.string().regex(/^[0-9a-f]{64}$/)
|
|
14585
14744
|
}).strict(),
|
|
14586
14745
|
/** From the in-browser editor: the page holds the bytes, the server validates and writes. */
|
|
14587
|
-
|
|
14588
|
-
action:
|
|
14589
|
-
project:
|
|
14590
|
-
assetId:
|
|
14591
|
-
styleId:
|
|
14592
|
-
expectedSha256:
|
|
14746
|
+
z10.object({
|
|
14747
|
+
action: z10.literal("save-edit"),
|
|
14748
|
+
project: z10.string().min(1).optional(),
|
|
14749
|
+
assetId: z10.string().min(1),
|
|
14750
|
+
styleId: z10.string().min(1),
|
|
14751
|
+
expectedSha256: z10.string().regex(/^[0-9a-f]{64}$/),
|
|
14593
14752
|
/** The flattened image; for a frame set, `frames` instead. */
|
|
14594
14753
|
png: Base64.min(1).optional(),
|
|
14595
14754
|
/** Every frame of a set, tagged with the role it was opened under (null for one added in the editor). */
|
|
14596
|
-
frames:
|
|
14755
|
+
frames: z10.array(z10.object({ role: z10.string().min(1).nullable(), png: Base64.min(1) }).strict()).min(1).max(256).optional(),
|
|
14597
14756
|
/** The editor's layered project file, kept beside the edit. */
|
|
14598
14757
|
pxo: Base64.optional(),
|
|
14599
14758
|
/** Editor identity from the bridge's ready message. */
|
|
14600
|
-
editor:
|
|
14601
|
-
protocol:
|
|
14759
|
+
editor: z10.string().min(1).max(200),
|
|
14760
|
+
protocol: z10.number().int().positive()
|
|
14602
14761
|
}).strict()
|
|
14603
14762
|
]);
|
|
14604
14763
|
function createGalleryEditHandler(opts) {
|
|
@@ -14639,6 +14798,21 @@ function createGalleryEditHandler(opts) {
|
|
|
14639
14798
|
}
|
|
14640
14799
|
return opts.reload();
|
|
14641
14800
|
}
|
|
14801
|
+
const upload = UploadImageSchema.safeParse(body);
|
|
14802
|
+
if (upload.success) {
|
|
14803
|
+
let manifestPath2;
|
|
14804
|
+
try {
|
|
14805
|
+
manifestPath2 = await opts.manifestFor(upload.data.project);
|
|
14806
|
+
} catch (error) {
|
|
14807
|
+
throw new ManifestEditError(error instanceof Error ? error.message : String(error));
|
|
14808
|
+
}
|
|
14809
|
+
const written = await saveProjectImage(path28.dirname(path28.resolve(manifestPath2)), upload.data);
|
|
14810
|
+
log2(` image added: ${written}`);
|
|
14811
|
+
return opts.reload();
|
|
14812
|
+
}
|
|
14813
|
+
if (body?.action === "upload-image") {
|
|
14814
|
+
throw new ManifestEditError("invalid upload: " + upload.error.issues.slice(0, 2).map((issue) => `${issue.path.join(".")}: ${issue.message}`).join("; "));
|
|
14815
|
+
}
|
|
14642
14816
|
const poses = SkeletonKeypointsUpdateSchema.safeParse(body);
|
|
14643
14817
|
if (poses.success) {
|
|
14644
14818
|
if (!opts.loadProject) throw new ManifestEditError("pose edits are not available in this gallery");
|
|
@@ -14682,18 +14856,51 @@ function createGalleryEditHandler(opts) {
|
|
|
14682
14856
|
}
|
|
14683
14857
|
const result = await applyManifestEdit(manifestPath, parsed.data);
|
|
14684
14858
|
if (result.changed) {
|
|
14685
|
-
const relative =
|
|
14859
|
+
const relative = path28.relative(process.cwd(), result.manifestPath);
|
|
14686
14860
|
const shown = !relative || relative.startsWith("..") ? result.manifestPath : relative;
|
|
14687
|
-
|
|
14688
|
-
log2(` manifest edited: ${parsed.data.action === "add-asset" ? "added" : "changed"} ${subject} in ${shown}`);
|
|
14861
|
+
log2(` manifest edited: ${describeEdit(parsed.data)} in ${shown}`);
|
|
14689
14862
|
}
|
|
14690
14863
|
return opts.reload();
|
|
14691
14864
|
};
|
|
14692
14865
|
}
|
|
14866
|
+
function describeEdit(edit) {
|
|
14867
|
+
switch (edit.action) {
|
|
14868
|
+
case "batch":
|
|
14869
|
+
return `${edit.edits.length} changes (${edit.edits.map(describeEdit).join("; ")})`;
|
|
14870
|
+
case "add-style":
|
|
14871
|
+
return `added style ${edit.styleId}`;
|
|
14872
|
+
case "patch-style":
|
|
14873
|
+
return `changed style ${edit.styleId}`;
|
|
14874
|
+
case "set-source":
|
|
14875
|
+
case "clear-source":
|
|
14876
|
+
return `changed ${edit.styleId}/${edit.assetId} source`;
|
|
14877
|
+
case "add-asset":
|
|
14878
|
+
return `added ${edit.assetId}`;
|
|
14879
|
+
case "patch-asset":
|
|
14880
|
+
return `changed ${edit.assetId}`;
|
|
14881
|
+
}
|
|
14882
|
+
}
|
|
14883
|
+
function createGalleryPriceHandler(opts) {
|
|
14884
|
+
return async (body) => {
|
|
14885
|
+
const parsed = ManifestEditSchema.safeParse(body);
|
|
14886
|
+
if (!parsed.success) {
|
|
14887
|
+
throw new ManifestEditError(
|
|
14888
|
+
"invalid edit: " + parsed.error.issues.slice(0, 3).map((issue) => `${issue.path.join(".")}: ${issue.message}`).join("; ")
|
|
14889
|
+
);
|
|
14890
|
+
}
|
|
14891
|
+
let manifestPath;
|
|
14892
|
+
try {
|
|
14893
|
+
manifestPath = await opts.manifestFor(parsed.data.project);
|
|
14894
|
+
} catch (error) {
|
|
14895
|
+
throw new ManifestEditError(error instanceof Error ? error.message : String(error));
|
|
14896
|
+
}
|
|
14897
|
+
return priceManifestEdit(manifestPath, parsed.data);
|
|
14898
|
+
};
|
|
14899
|
+
}
|
|
14693
14900
|
|
|
14694
14901
|
// src/pipeline/history.ts
|
|
14695
|
-
import { existsSync as
|
|
14696
|
-
import
|
|
14902
|
+
import { existsSync as existsSync20 } from "fs";
|
|
14903
|
+
import path29 from "path";
|
|
14697
14904
|
var HISTORY_ENV = "PIXELKILN_HISTORY";
|
|
14698
14905
|
function historyLimit(manifest, env = process.env) {
|
|
14699
14906
|
if (manifest?.history !== void 0) return manifest.history;
|
|
@@ -14780,10 +14987,10 @@ async function revertGeneration(provider, spec, lock, lockPath, opts) {
|
|
|
14780
14987
|
}
|
|
14781
14988
|
for (let i = 0; i < entry.outputs.length; i++) {
|
|
14782
14989
|
const file = currentEntryOutputPath(entry, spec, i);
|
|
14783
|
-
if (!
|
|
14990
|
+
if (!existsSync20(file)) continue;
|
|
14784
14991
|
if (await sha256File(file) !== entry.outputs[i].sha256 && !opts.force) {
|
|
14785
14992
|
throw new OverwriteRefusedError(
|
|
14786
|
-
`${
|
|
14993
|
+
`${path29.relative(process.cwd(), file)} was changed after download; keep it with pixelkiln edit, or pass --force to replace it`,
|
|
14787
14994
|
[file]
|
|
14788
14995
|
);
|
|
14789
14996
|
}
|
|
@@ -14836,7 +15043,7 @@ async function revertGeneration(provider, spec, lock, lockPath, opts) {
|
|
|
14836
15043
|
// src/gallery/snapshot.ts
|
|
14837
15044
|
var PROJECT_FILE_TYPE = "application/octet-stream";
|
|
14838
15045
|
function galleryMediaId(absolutePath) {
|
|
14839
|
-
return createHash3("sha256").update(
|
|
15046
|
+
return createHash3("sha256").update(path30.resolve(absolutePath)).digest("hex").slice(0, 24);
|
|
14840
15047
|
}
|
|
14841
15048
|
function galleryMediaRoute(id) {
|
|
14842
15049
|
return `/media/${id}`;
|
|
@@ -14855,9 +15062,9 @@ function describeHistory(media, entry, cacheDir) {
|
|
|
14855
15062
|
return (entry.history ?? []).map((generation, i) => {
|
|
14856
15063
|
const outputs = generation.outputs.map((output) => {
|
|
14857
15064
|
const mediaType = output.mediaType ?? MediaType.PNG;
|
|
14858
|
-
const file =
|
|
15065
|
+
const file = path30.join(cacheDir, cacheFileName(output.sha256, mediaType));
|
|
14859
15066
|
let url = null;
|
|
14860
|
-
if (
|
|
15067
|
+
if (existsSync21(file)) {
|
|
14861
15068
|
const id = galleryMediaId(file);
|
|
14862
15069
|
media.set(id, { path: file, contentType: mediaType });
|
|
14863
15070
|
url = `${galleryMediaRoute(id)}?v=${output.sha256.slice(0, 24)}`;
|
|
@@ -14987,10 +15194,10 @@ async function describeOutput(media, root, absolutePath, recorded = {}) {
|
|
|
14987
15194
|
async function describeQuality(media, root, inspection) {
|
|
14988
15195
|
const outputPaths = inspection.outputs ?? [inspection.output];
|
|
14989
15196
|
const outputs = await Promise.all(
|
|
14990
|
-
outputPaths.map((file) => describeOutput(media, root,
|
|
15197
|
+
outputPaths.map((file) => describeOutput(media, root, path30.resolve(root, file)))
|
|
14991
15198
|
);
|
|
14992
|
-
const recordPath =
|
|
14993
|
-
const recordExists =
|
|
15199
|
+
const recordPath = path30.resolve(root, inspection.record);
|
|
15200
|
+
const recordExists = existsSync21(recordPath);
|
|
14994
15201
|
let options = null;
|
|
14995
15202
|
let check = null;
|
|
14996
15203
|
if (recordExists) {
|
|
@@ -15009,7 +15216,7 @@ async function describeQuality(media, root, inspection) {
|
|
|
15009
15216
|
return {
|
|
15010
15217
|
state: inspection.state,
|
|
15011
15218
|
reason: inspection.reason,
|
|
15012
|
-
output: portableOutputPath(
|
|
15219
|
+
output: portableOutputPath(path30.resolve(root, inspection.output), root),
|
|
15013
15220
|
record: portableOutputPath(recordPath, root),
|
|
15014
15221
|
recordExists,
|
|
15015
15222
|
outputs,
|
|
@@ -15049,7 +15256,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
15049
15256
|
const items = [];
|
|
15050
15257
|
const manifestText = await readFile18(loaded.path, "utf8");
|
|
15051
15258
|
const manifestSha256 = sha256(manifestText);
|
|
15052
|
-
const cacheDir =
|
|
15259
|
+
const cacheDir = path30.resolve(path30.dirname(opts.lockPath), ".pixelkiln", "cache");
|
|
15053
15260
|
let rawStyles = {};
|
|
15054
15261
|
try {
|
|
15055
15262
|
const raw = JSON.parse(manifestText);
|
|
@@ -15073,12 +15280,12 @@ async function buildGallerySnapshot(opts) {
|
|
|
15073
15280
|
)
|
|
15074
15281
|
);
|
|
15075
15282
|
if (spec.source) {
|
|
15076
|
-
const editPath =
|
|
15283
|
+
const editPath = path30.resolve(root, spec.source);
|
|
15077
15284
|
const memberCount = sourceIsStem(spec.source, entry, root) ? entry.outputs.length : 1;
|
|
15078
15285
|
const editShas = await Promise.all(
|
|
15079
15286
|
Array.from({ length: memberCount }, async (_, index) => {
|
|
15080
15287
|
const file = sourceOutputPath(spec.source, entry, index, root);
|
|
15081
|
-
return { file, sha256:
|
|
15288
|
+
return { file, sha256: existsSync21(file) ? await sha256File(file) : null };
|
|
15082
15289
|
})
|
|
15083
15290
|
);
|
|
15084
15291
|
edits = await Promise.all(editShas.map(({ file, sha256: editSha }) => describeOutput(media, root, file, { sha256: editSha })));
|
|
@@ -15086,7 +15293,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
15086
15293
|
const companion = await readHandEditCompanion(editPath);
|
|
15087
15294
|
if (companion) {
|
|
15088
15295
|
const projectPath = handEditProjectPath(editPath);
|
|
15089
|
-
const project = companion.project &&
|
|
15296
|
+
const project = companion.project && existsSync21(projectPath) ? await fileInfo(projectPath) : null;
|
|
15090
15297
|
let projectUrl = null;
|
|
15091
15298
|
if (project) {
|
|
15092
15299
|
const id = galleryMediaId(projectPath);
|
|
@@ -15122,8 +15329,8 @@ async function buildGallerySnapshot(opts) {
|
|
|
15122
15329
|
editStatus = edits.some((member) => !member.exists) ? "missing" : editChanged.every((changed) => !changed) ? "same" : regenerated ? "regenerated-since" : "edited";
|
|
15123
15330
|
}
|
|
15124
15331
|
} else if (spec.source) {
|
|
15125
|
-
outputs = [await describeOutput(media, root,
|
|
15126
|
-
} else if (!entry &&
|
|
15332
|
+
outputs = [await describeOutput(media, root, path30.resolve(root, spec.source))];
|
|
15333
|
+
} else if (!entry && existsSync21(spec.outFile)) {
|
|
15127
15334
|
outputs = [await describeOutput(media, root, spec.outFile)];
|
|
15128
15335
|
} else {
|
|
15129
15336
|
outputs = [];
|
|
@@ -15324,7 +15531,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
15324
15531
|
id: loaded.manifest.name,
|
|
15325
15532
|
name: loaded.manifest.name,
|
|
15326
15533
|
manifest: loaded.path,
|
|
15327
|
-
lock:
|
|
15534
|
+
lock: path30.resolve(opts.lockPath),
|
|
15328
15535
|
root,
|
|
15329
15536
|
provider: loaded.manifest.provider,
|
|
15330
15537
|
account: null,
|
|
@@ -15350,7 +15557,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
15350
15557
|
return { snapshot, media };
|
|
15351
15558
|
}
|
|
15352
15559
|
async function buildWorkspaceGallerySnapshot(opts) {
|
|
15353
|
-
const dir =
|
|
15560
|
+
const dir = path30.dirname(path30.resolve(opts.workspacePath));
|
|
15354
15561
|
const media = /* @__PURE__ */ new Map();
|
|
15355
15562
|
const projects = [];
|
|
15356
15563
|
const items = [];
|
|
@@ -15393,7 +15600,7 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
15393
15600
|
id: project.id,
|
|
15394
15601
|
name: loaded.manifest.name,
|
|
15395
15602
|
manifest: manifestPath,
|
|
15396
|
-
lock:
|
|
15603
|
+
lock: path30.resolve(lockPath),
|
|
15397
15604
|
root: loaded.root,
|
|
15398
15605
|
provider: loaded.manifest.provider,
|
|
15399
15606
|
account: project.account ?? null,
|
|
@@ -15409,8 +15616,8 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
15409
15616
|
id: project.id,
|
|
15410
15617
|
name: project.id,
|
|
15411
15618
|
manifest: manifestPath,
|
|
15412
|
-
lock:
|
|
15413
|
-
root:
|
|
15619
|
+
lock: path30.resolve(lockPath),
|
|
15620
|
+
root: path30.dirname(manifestPath),
|
|
15414
15621
|
provider: project.provider,
|
|
15415
15622
|
account: project.account ?? null,
|
|
15416
15623
|
manifestSha256: null,
|
|
@@ -15437,7 +15644,7 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
15437
15644
|
version: 1,
|
|
15438
15645
|
generatedAt: (opts.now ?? (() => /* @__PURE__ */ new Date()))().toISOString(),
|
|
15439
15646
|
project: null,
|
|
15440
|
-
workspace: { path:
|
|
15647
|
+
workspace: { path: path30.resolve(opts.workspacePath), dir, projects },
|
|
15441
15648
|
filter,
|
|
15442
15649
|
totals: {
|
|
15443
15650
|
entries: projects.reduce((sum, project) => sum + project.entries, 0),
|
|
@@ -15459,7 +15666,7 @@ import { createServer as createServer2 } from "http";
|
|
|
15459
15666
|
import { readFile as readFile19 } from "fs/promises";
|
|
15460
15667
|
|
|
15461
15668
|
// src/gallery/page.ts
|
|
15462
|
-
import { existsSync as
|
|
15669
|
+
import { existsSync as existsSync22, readFileSync as readFileSync4 } from "fs";
|
|
15463
15670
|
|
|
15464
15671
|
// src/gallery/client-bundle.ts
|
|
15465
15672
|
import { createRequire } from "module";
|
|
@@ -15500,7 +15707,7 @@ function clientAssets() {
|
|
|
15500
15707
|
const prebuilt = new URL("gallery.js", CLIENT_DIR);
|
|
15501
15708
|
client ??= {
|
|
15502
15709
|
css: readFileSync4(new URL("gallery.css", CLIENT_DIR), "utf8"),
|
|
15503
|
-
js:
|
|
15710
|
+
js: existsSync22(prebuilt) ? readFileSync4(prebuilt, "utf8") : bundleGalleryClient()
|
|
15504
15711
|
};
|
|
15505
15712
|
return client;
|
|
15506
15713
|
}
|
|
@@ -15602,7 +15809,7 @@ async function readJsonBody(req) {
|
|
|
15602
15809
|
async function serveGallery(opts) {
|
|
15603
15810
|
const log2 = opts.onProgress ?? (() => {
|
|
15604
15811
|
});
|
|
15605
|
-
const session = opts.edit || opts.generate || opts.editor || opts.skeleton ? randomBytes(16).toString("hex") : null;
|
|
15812
|
+
const session = opts.edit || opts.generate || opts.editor || opts.skeleton || opts.price ? randomBytes(16).toString("hex") : null;
|
|
15606
15813
|
let media = /* @__PURE__ */ new Map();
|
|
15607
15814
|
let loading = null;
|
|
15608
15815
|
const reviewAssets = /* @__PURE__ */ new Map();
|
|
@@ -15677,6 +15884,17 @@ async function serveGallery(opts) {
|
|
|
15677
15884
|
}
|
|
15678
15885
|
return;
|
|
15679
15886
|
}
|
|
15887
|
+
if (req.method === "POST" && url2.pathname === "/api/price") {
|
|
15888
|
+
if (!opts.price || !session) return fail(405, "pricing an edit needs the gallery started with --edit");
|
|
15889
|
+
try {
|
|
15890
|
+
const body = await guardedBody("price requests");
|
|
15891
|
+
if (body === void 0) return;
|
|
15892
|
+
json(200, await opts.price(body));
|
|
15893
|
+
} catch (err) {
|
|
15894
|
+
reportError("price", err);
|
|
15895
|
+
}
|
|
15896
|
+
return;
|
|
15897
|
+
}
|
|
15680
15898
|
if (req.method === "POST" && url2.pathname === "/api/skeleton/estimate") {
|
|
15681
15899
|
if (!opts.skeleton || !session) return fail(405, "skeleton estimates need the gallery started with --edit");
|
|
15682
15900
|
try {
|
|
@@ -15871,7 +16089,7 @@ async function serveGallery(opts) {
|
|
|
15871
16089
|
|
|
15872
16090
|
// src/gallery/generate.ts
|
|
15873
16091
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
15874
|
-
import { z as
|
|
16092
|
+
import { z as z11 } from "zod";
|
|
15875
16093
|
|
|
15876
16094
|
// src/pipeline/poll.ts
|
|
15877
16095
|
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
@@ -16282,13 +16500,13 @@ function ownedReferenceImageId(spec, lock, providerId) {
|
|
|
16282
16500
|
}
|
|
16283
16501
|
|
|
16284
16502
|
// src/pick/server.ts
|
|
16285
|
-
import { existsSync as
|
|
16503
|
+
import { existsSync as existsSync24 } from "fs";
|
|
16286
16504
|
import { readFile as readFile21 } from "fs/promises";
|
|
16287
16505
|
|
|
16288
16506
|
// src/pipeline/init.ts
|
|
16289
|
-
import { readdir, readFile as readFile20, writeFile as
|
|
16290
|
-
import { existsSync as
|
|
16291
|
-
import
|
|
16507
|
+
import { readdir, readFile as readFile20, writeFile as writeFile14 } from "fs/promises";
|
|
16508
|
+
import { existsSync as existsSync23 } from "fs";
|
|
16509
|
+
import path31 from "path";
|
|
16292
16510
|
function pngSize(buf) {
|
|
16293
16511
|
if (buf.length < 24) return null;
|
|
16294
16512
|
const isPng = buf.readUInt32BE(0) === 2303741511 && buf.readUInt32BE(4) === 218765834;
|
|
@@ -16302,7 +16520,7 @@ function slugify(value) {
|
|
|
16302
16520
|
async function walk(dir, exclude) {
|
|
16303
16521
|
const out = [];
|
|
16304
16522
|
for (const entry of await readdir(dir, { withFileTypes: true })) {
|
|
16305
|
-
const full =
|
|
16523
|
+
const full = path31.join(dir, entry.name);
|
|
16306
16524
|
if (exclude.some((x) => full.includes(x))) continue;
|
|
16307
16525
|
if (entry.isDirectory()) out.push(...await walk(full, exclude));
|
|
16308
16526
|
else if (entry.name.toLowerCase().endsWith(".png")) out.push(full);
|
|
@@ -16321,9 +16539,9 @@ async function scanAssets(root, opts = {}) {
|
|
|
16321
16539
|
skipped.push(`${file} (not a readable PNG)`);
|
|
16322
16540
|
continue;
|
|
16323
16541
|
}
|
|
16324
|
-
const rel =
|
|
16325
|
-
const category =
|
|
16326
|
-
let id = slugify(
|
|
16542
|
+
const rel = path31.relative(root, file);
|
|
16543
|
+
const category = path31.dirname(rel) === "." ? "" : path31.dirname(rel);
|
|
16544
|
+
let id = slugify(path31.basename(rel));
|
|
16327
16545
|
if (seen.has(id)) {
|
|
16328
16546
|
const n = seen.get(id) + 1;
|
|
16329
16547
|
seen.set(id, n);
|
|
@@ -16381,10 +16599,10 @@ function mode(values) {
|
|
|
16381
16599
|
return [...counts.entries()].sort((a, b) => b[1] - a[1])[0]?.[0] ?? 64;
|
|
16382
16600
|
}
|
|
16383
16601
|
async function writeManifestFile(target, manifest) {
|
|
16384
|
-
if (
|
|
16602
|
+
if (existsSync23(target)) {
|
|
16385
16603
|
throw new Error(`${target} already exists; refusing to overwrite an existing manifest.`);
|
|
16386
16604
|
}
|
|
16387
|
-
await
|
|
16605
|
+
await writeFile14(target, JSON.stringify(manifest, null, 2) + "\n");
|
|
16388
16606
|
}
|
|
16389
16607
|
|
|
16390
16608
|
// src/pick/sheet.ts
|
|
@@ -16797,7 +17015,7 @@ async function currentArt(entry, spec) {
|
|
|
16797
17015
|
const previous = primaryOutput({ ...entry, outputs: entry.supersededOutputs });
|
|
16798
17016
|
if (!previous) return null;
|
|
16799
17017
|
const file = resolveOutputPath(previous.path, spec.root);
|
|
16800
|
-
if (!
|
|
17018
|
+
if (!existsSync24(file)) return null;
|
|
16801
17019
|
const contentType = previous.mediaType ?? mediaTypeFromExtension(file);
|
|
16802
17020
|
if (!contentType) return null;
|
|
16803
17021
|
let width = entry.width;
|
|
@@ -16971,26 +17189,26 @@ async function runPicker(provider, lock, lockPath, opts = {}) {
|
|
|
16971
17189
|
}
|
|
16972
17190
|
|
|
16973
17191
|
// src/gallery/generate.ts
|
|
16974
|
-
var GenerateRequestSchema =
|
|
16975
|
-
project:
|
|
16976
|
-
keys:
|
|
17192
|
+
var GenerateRequestSchema = z11.object({
|
|
17193
|
+
project: z11.string().min(1).optional(),
|
|
17194
|
+
keys: z11.array(z11.string().min(1)).min(1).max(500),
|
|
16977
17195
|
/**
|
|
16978
17196
|
* Regenerate work that is up to date, as `gen --force` does, and let the
|
|
16979
17197
|
* fetch replace a locally modified or untracked file. With `restore`, put
|
|
16980
17198
|
* the recorded bytes back over a modified file.
|
|
16981
17199
|
*/
|
|
16982
|
-
force:
|
|
17200
|
+
force: z11.boolean().optional(),
|
|
16983
17201
|
/** Advance in-flight, review, or selected work without submitting anything. */
|
|
16984
|
-
resume:
|
|
17202
|
+
resume: z11.boolean().optional(),
|
|
16985
17203
|
/** Re-download downloaded outputs and replace files whose object changed upstream. */
|
|
16986
|
-
refresh:
|
|
17204
|
+
refresh: z11.boolean().optional(),
|
|
16987
17205
|
/** Put recorded outputs back on disk from the cache or the provider, without generating. */
|
|
16988
|
-
restore:
|
|
17206
|
+
restore: z11.boolean().optional(),
|
|
16989
17207
|
/**
|
|
16990
17208
|
* Bring a replaced generation back: 1-based position in the record's
|
|
16991
17209
|
* history (newest first) or an output hash prefix. One key at a time.
|
|
16992
17210
|
*/
|
|
16993
|
-
revert:
|
|
17211
|
+
revert: z11.string().min(1).max(64).optional()
|
|
16994
17212
|
}).strict().refine((request) => [request.resume, request.refresh, request.restore, request.revert !== void 0].filter(Boolean).length <= 1, {
|
|
16995
17213
|
message: "resume, refresh, restore, and revert are exclusive"
|
|
16996
17214
|
}).refine((request) => request.revert === void 0 || request.keys.length === 1, { message: "revert takes one key" });
|
|
@@ -17301,14 +17519,14 @@ function createGenerateHandlers(opts) {
|
|
|
17301
17519
|
|
|
17302
17520
|
// src/gallery/editor.ts
|
|
17303
17521
|
import { readFile as readFile23 } from "fs/promises";
|
|
17304
|
-
import
|
|
17522
|
+
import path33 from "path";
|
|
17305
17523
|
|
|
17306
17524
|
// src/editor/install.ts
|
|
17307
17525
|
import { createHash as createHash4 } from "crypto";
|
|
17308
|
-
import { existsSync as
|
|
17309
|
-
import { mkdir as
|
|
17526
|
+
import { existsSync as existsSync25 } from "fs";
|
|
17527
|
+
import { mkdir as mkdir12, readFile as readFile22, rename as rename9, rm as rm10, stat as stat3, writeFile as writeFile15 } from "fs/promises";
|
|
17310
17528
|
import os2 from "os";
|
|
17311
|
-
import
|
|
17529
|
+
import path32 from "path";
|
|
17312
17530
|
|
|
17313
17531
|
// tools/pixelorama-bridge/pin.json
|
|
17314
17532
|
var pin_default = {
|
|
@@ -17372,15 +17590,15 @@ var EditorInstallError = class extends Error {
|
|
|
17372
17590
|
};
|
|
17373
17591
|
function editorToolsRoot() {
|
|
17374
17592
|
const override = process.env.PIXELKILN_TOOLS_DIR?.trim();
|
|
17375
|
-
if (override) return
|
|
17376
|
-
if (process.platform === "darwin") return
|
|
17593
|
+
if (override) return path32.resolve(override);
|
|
17594
|
+
if (process.platform === "darwin") return path32.join(os2.homedir(), "Library", "Caches", "pixelkiln", "tools");
|
|
17377
17595
|
if (process.platform === "win32") {
|
|
17378
|
-
return
|
|
17596
|
+
return path32.join(process.env.LOCALAPPDATA ?? path32.join(os2.homedir(), "AppData", "Local"), "pixelkiln", "tools");
|
|
17379
17597
|
}
|
|
17380
|
-
return
|
|
17598
|
+
return path32.join(process.env.XDG_CACHE_HOME ?? path32.join(os2.homedir(), ".cache"), "pixelkiln", "tools");
|
|
17381
17599
|
}
|
|
17382
17600
|
function editorDir(pin = EDITOR_PIN, root = editorToolsRoot()) {
|
|
17383
|
-
return
|
|
17601
|
+
return path32.join(root, "pixelorama", pin.release ?? "unpublished");
|
|
17384
17602
|
}
|
|
17385
17603
|
function editorBaseUrl(pin = EDITOR_PIN) {
|
|
17386
17604
|
const override = process.env.PIXELKILN_EDITOR_URL?.trim();
|
|
@@ -17403,8 +17621,8 @@ async function editorStatus(opts = {}) {
|
|
|
17403
17621
|
let totalBytes = 0;
|
|
17404
17622
|
for (const [name, expected] of Object.entries(pin.files)) {
|
|
17405
17623
|
totalBytes += expected.bytes;
|
|
17406
|
-
const file =
|
|
17407
|
-
const info =
|
|
17624
|
+
const file = path32.join(dir, name);
|
|
17625
|
+
const info = existsSync25(file) ? await stat3(file).catch(() => null) : null;
|
|
17408
17626
|
if (info && info.size === expected.bytes && await sha256Of(file) === expected.sha256) {
|
|
17409
17627
|
installedBytes += expected.bytes;
|
|
17410
17628
|
} else {
|
|
@@ -17433,7 +17651,7 @@ async function installEditor(opts = {}) {
|
|
|
17433
17651
|
);
|
|
17434
17652
|
}
|
|
17435
17653
|
if (!baseUrl) throw new EditorInstallError("no download location for the editor build");
|
|
17436
|
-
await
|
|
17654
|
+
await mkdir12(dir, { recursive: true });
|
|
17437
17655
|
const downloaded = [];
|
|
17438
17656
|
const skipped = Object.keys(pin.files).filter((name) => !before.missing.includes(name));
|
|
17439
17657
|
let fetchedBytes = before.installedBytes;
|
|
@@ -17461,10 +17679,10 @@ async function installEditor(opts = {}) {
|
|
|
17461
17679
|
if (digest2 !== expected.sha256) {
|
|
17462
17680
|
throw new EditorInstallError(`${name}: sha256 ${digest2.slice(0, 12)}\u2026 does not match the pinned ${expected.sha256.slice(0, 12)}\u2026`, name);
|
|
17463
17681
|
}
|
|
17464
|
-
const final =
|
|
17682
|
+
const final = path32.join(dir, name);
|
|
17465
17683
|
const tmp = `${final}.${process.pid}.part`;
|
|
17466
17684
|
try {
|
|
17467
|
-
await
|
|
17685
|
+
await writeFile15(tmp, bytes);
|
|
17468
17686
|
await rename9(tmp, final);
|
|
17469
17687
|
} finally {
|
|
17470
17688
|
await rm10(tmp, { force: true });
|
|
@@ -17507,7 +17725,7 @@ var EDITOR_CONTENT_TYPES = {
|
|
|
17507
17725
|
".json": "application/json; charset=utf-8"
|
|
17508
17726
|
};
|
|
17509
17727
|
function editorContentType(name) {
|
|
17510
|
-
return EDITOR_CONTENT_TYPES[
|
|
17728
|
+
return EDITOR_CONTENT_TYPES[path32.extname(name).toLowerCase()] ?? null;
|
|
17511
17729
|
}
|
|
17512
17730
|
|
|
17513
17731
|
// src/gallery/editor.ts
|
|
@@ -17583,7 +17801,7 @@ function createGalleryEditorHandlers(opts = {}) {
|
|
|
17583
17801
|
if (!status.installed) return null;
|
|
17584
17802
|
let bytes;
|
|
17585
17803
|
try {
|
|
17586
|
-
bytes = await readFile23(
|
|
17804
|
+
bytes = await readFile23(path33.join(dir, name));
|
|
17587
17805
|
} catch {
|
|
17588
17806
|
verified = null;
|
|
17589
17807
|
return null;
|
|
@@ -17623,13 +17841,13 @@ async function serveUntilStopped(initial, reload, args, access2) {
|
|
|
17623
17841
|
};
|
|
17624
17842
|
const loadProject = async (project) => {
|
|
17625
17843
|
const ctx = await access2.loadProject(project);
|
|
17626
|
-
const dir =
|
|
17844
|
+
const dir = path34.dirname(ctx.loaded.path);
|
|
17627
17845
|
const declared = readEnvFiles(dir);
|
|
17628
17846
|
const credentialNames = new Set(availableProviders().flatMap((id) => providerCredentialEnvs(providerFactory(id))));
|
|
17629
17847
|
for (const [name, value] of Object.entries(declared)) {
|
|
17630
17848
|
if (credentialNames.has(name) && name in process.env && process.env[name] !== value) {
|
|
17631
17849
|
throw new Error(
|
|
17632
|
-
`${
|
|
17850
|
+
`${path34.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.`
|
|
17633
17851
|
);
|
|
17634
17852
|
}
|
|
17635
17853
|
}
|
|
@@ -17657,6 +17875,7 @@ async function serveUntilStopped(initial, reload, args, access2) {
|
|
|
17657
17875
|
),
|
|
17658
17876
|
...args.edit ? { edit: createGalleryEditHandler({ manifestFor: access2.manifestFor, loadProject: access2.loadProject, reload, onProgress: log }) } : {},
|
|
17659
17877
|
...budget ? { generate: createGenerateHandlers({ loadProject, providerFor, budget, reload, onProgress: log }) } : {},
|
|
17878
|
+
...args.edit ? { price: createGalleryPriceHandler({ manifestFor: access2.manifestFor }) } : {},
|
|
17660
17879
|
// Estimating acts on the author's behalf and feeds a manifest edit, so it follows the write gate.
|
|
17661
17880
|
...args.edit ? { skeleton: createGallerySkeletonHandlers({ loadProject, limit: args.estimateLimit, onProgress: log }) } : {},
|
|
17662
17881
|
// The editor exists to write hand edits back, so it follows the write gate.
|
|
@@ -17676,8 +17895,8 @@ async function serveUntilStopped(initial, reload, args, access2) {
|
|
|
17676
17895
|
}
|
|
17677
17896
|
async function runGallery(args) {
|
|
17678
17897
|
if (args.workspace) {
|
|
17679
|
-
const workspacePath =
|
|
17680
|
-
if (!
|
|
17898
|
+
const workspacePath = path34.resolve(args.workspace);
|
|
17899
|
+
if (!existsSync26(workspacePath)) {
|
|
17681
17900
|
throw new Error(`Workspace catalog not found: ${workspacePath}`);
|
|
17682
17901
|
}
|
|
17683
17902
|
const filter2 = { styles: args.styles, assets: args.assets };
|
|
@@ -17697,7 +17916,7 @@ async function runGallery(args) {
|
|
|
17697
17916
|
const registered = async (projectId) => {
|
|
17698
17917
|
const project = (await loadWorkspace(workspacePath)).projects.find((candidate) => candidate.id === projectId);
|
|
17699
17918
|
if (!projectId || !project) throw new Error(`unknown workspace project "${projectId ?? ""}"`);
|
|
17700
|
-
return resolveProject(
|
|
17919
|
+
return resolveProject(path34.dirname(workspacePath), project);
|
|
17701
17920
|
};
|
|
17702
17921
|
await serveUntilStopped(initial, build2, args, {
|
|
17703
17922
|
manifestFor: async (projectId) => (await registered(projectId)).manifestPath,
|
|
@@ -17722,7 +17941,7 @@ async function runGallery(args) {
|
|
|
17722
17941
|
return buildGallerySnapshot({ ...fresh, filter });
|
|
17723
17942
|
};
|
|
17724
17943
|
await serveUntilStopped(await build(), reload, args, {
|
|
17725
|
-
manifestFor: () =>
|
|
17944
|
+
manifestFor: () => path34.resolve(args.manifest),
|
|
17726
17945
|
loadProject: () => openProject(args.manifest, { lockPath: args.lock, env: false })
|
|
17727
17946
|
});
|
|
17728
17947
|
}
|
|
@@ -18031,18 +18250,18 @@ async function runRestoreGeneration(args) {
|
|
|
18031
18250
|
}
|
|
18032
18251
|
|
|
18033
18252
|
// src/cli/commands/housekeeping.ts
|
|
18034
|
-
import { existsSync as
|
|
18253
|
+
import { existsSync as existsSync28 } from "fs";
|
|
18035
18254
|
|
|
18036
18255
|
// src/pipeline/cache-health.ts
|
|
18037
|
-
import { existsSync as
|
|
18256
|
+
import { existsSync as existsSync27 } from "fs";
|
|
18038
18257
|
import { readFile as readFile24, readdir as readdir2, rm as rm11 } from "fs/promises";
|
|
18039
|
-
import
|
|
18258
|
+
import path35 from "path";
|
|
18040
18259
|
async function inspectCaches(lock, lockPath, options = {}) {
|
|
18041
|
-
if (options.prune && !
|
|
18042
|
-
throw new Error(`Refusing to prune without an existing lockfile at ${
|
|
18260
|
+
if (options.prune && !existsSync27(lockPath)) {
|
|
18261
|
+
throw new Error(`Refusing to prune without an existing lockfile at ${path35.resolve(lockPath)}`);
|
|
18043
18262
|
}
|
|
18044
|
-
const contentDir =
|
|
18045
|
-
const remotePath =
|
|
18263
|
+
const contentDir = path35.resolve(path35.dirname(lockPath), ".pixelkiln", "cache");
|
|
18264
|
+
const remotePath = path35.resolve(cachePathFor(lockPath));
|
|
18046
18265
|
const referenced = referencedHashes(lock);
|
|
18047
18266
|
let content = await inspectContentCache(contentDir, referenced);
|
|
18048
18267
|
let remoteHashes = await inspectRemoteHashCache(remotePath);
|
|
@@ -18054,7 +18273,7 @@ async function inspectCaches(lock, lockPath, options = {}) {
|
|
|
18054
18273
|
]);
|
|
18055
18274
|
for (const name of names) {
|
|
18056
18275
|
try {
|
|
18057
|
-
await rm11(
|
|
18276
|
+
await rm11(path35.join(contentDir, name), { force: true });
|
|
18058
18277
|
removed.contentFiles++;
|
|
18059
18278
|
} catch {
|
|
18060
18279
|
}
|
|
@@ -18081,7 +18300,7 @@ async function inspectCaches(lock, lockPath, options = {}) {
|
|
|
18081
18300
|
async function inspectContentCache(contentDir, referenced) {
|
|
18082
18301
|
const report = {
|
|
18083
18302
|
path: contentDir,
|
|
18084
|
-
exists:
|
|
18303
|
+
exists: existsSync27(contentDir),
|
|
18085
18304
|
files: 0,
|
|
18086
18305
|
bytes: 0,
|
|
18087
18306
|
valid: 0,
|
|
@@ -18102,7 +18321,7 @@ async function inspectContentCache(contentDir, referenced) {
|
|
|
18102
18321
|
continue;
|
|
18103
18322
|
}
|
|
18104
18323
|
report.files++;
|
|
18105
|
-
const file =
|
|
18324
|
+
const file = path35.join(contentDir, entry.name);
|
|
18106
18325
|
const mediaType = mediaTypeFromExtension(entry.name);
|
|
18107
18326
|
const expected = mediaType ? entry.name.slice(0, -4) : "";
|
|
18108
18327
|
let bytes;
|
|
@@ -18145,7 +18364,7 @@ async function inspectContentCache(contentDir, referenced) {
|
|
|
18145
18364
|
async function inspectRemoteHashCache(remotePath) {
|
|
18146
18365
|
const report = {
|
|
18147
18366
|
path: remotePath,
|
|
18148
|
-
exists:
|
|
18367
|
+
exists: existsSync27(remotePath),
|
|
18149
18368
|
entries: 0,
|
|
18150
18369
|
valid: 0,
|
|
18151
18370
|
invalidIds: [],
|
|
@@ -18218,7 +18437,7 @@ async function runAccept(args) {
|
|
|
18218
18437
|
let intact = true;
|
|
18219
18438
|
for (const output of entry.outputs) {
|
|
18220
18439
|
const file = resolveOutputPath(output.path, item.spec.root);
|
|
18221
|
-
if (!
|
|
18440
|
+
if (!existsSync28(file) || await sha256File(file) !== output.sha256) {
|
|
18222
18441
|
intact = false;
|
|
18223
18442
|
break;
|
|
18224
18443
|
}
|
|
@@ -18273,42 +18492,42 @@ This drops their provenance from the lockfile. The art on disk is`);
|
|
|
18273
18492
|
}
|
|
18274
18493
|
|
|
18275
18494
|
// src/cli/commands/init.ts
|
|
18276
|
-
import
|
|
18277
|
-
import { existsSync as
|
|
18495
|
+
import path36 from "path";
|
|
18496
|
+
import { existsSync as existsSync29 } from "fs";
|
|
18278
18497
|
async function runInit(args) {
|
|
18279
18498
|
if (!args.from) throw new Error("init needs --from <dir> pointing at your existing PNGs.");
|
|
18280
|
-
const root =
|
|
18281
|
-
if (!
|
|
18499
|
+
const root = path36.resolve(args.from);
|
|
18500
|
+
if (!existsSync29(root)) throw new Error(`No directory at ${root}`);
|
|
18282
18501
|
const generator = args.generator ?? "map";
|
|
18283
18502
|
if (generator !== "1dir" && generator !== "map") {
|
|
18284
18503
|
throw new Error(`--generator must be "1dir" or "map", got "${args.generator}".`);
|
|
18285
18504
|
}
|
|
18286
|
-
const target =
|
|
18505
|
+
const target = path36.resolve(args.out ?? "pixelkiln.manifest.json");
|
|
18287
18506
|
const { assets, skipped } = await scanAssets(root, { exclude: args.exclude });
|
|
18288
18507
|
if (!assets.length) throw new Error(`No PNGs found under ${root}`);
|
|
18289
18508
|
const manifest = buildManifest(
|
|
18290
|
-
args.name ??
|
|
18509
|
+
args.name ?? path36.basename(path36.dirname(target)),
|
|
18291
18510
|
args.styles[0] ?? "base",
|
|
18292
18511
|
generator,
|
|
18293
|
-
|
|
18512
|
+
path36.relative(path36.dirname(target), root) || ".",
|
|
18294
18513
|
assets
|
|
18295
18514
|
);
|
|
18296
18515
|
await writeManifestFile(target, manifest);
|
|
18297
|
-
log(` scanned ${assets.length} PNG(s) under ${
|
|
18516
|
+
log(` scanned ${assets.length} PNG(s) under ${path36.relative(process.cwd(), root)}`);
|
|
18298
18517
|
if (skipped.length) log(` skipped ${skipped.length} unreadable file(s)`);
|
|
18299
|
-
log(` wrote ${
|
|
18518
|
+
log(` wrote ${path36.relative(process.cwd(), target)}`);
|
|
18300
18519
|
log(`
|
|
18301
18520
|
Prompts are intentionally empty. To recover the real ones from your`);
|
|
18302
18521
|
log(` PixelLab account instead of inventing them:`);
|
|
18303
18522
|
log(`
|
|
18304
|
-
pixelkiln adopt --manifest ${
|
|
18523
|
+
pixelkiln adopt --manifest ${path36.relative(process.cwd(), target)} --write-prompts
|
|
18305
18524
|
`);
|
|
18306
18525
|
}
|
|
18307
18526
|
|
|
18308
18527
|
// src/pipeline/doctor.ts
|
|
18309
|
-
import { constants, existsSync as
|
|
18528
|
+
import { constants, existsSync as existsSync30 } from "fs";
|
|
18310
18529
|
import { access, stat as stat4 } from "fs/promises";
|
|
18311
|
-
import
|
|
18530
|
+
import path37 from "path";
|
|
18312
18531
|
async function doctor(loaded, specs, lock, lockPath, opts = {}) {
|
|
18313
18532
|
const checks = [];
|
|
18314
18533
|
const add = (id, level, message6) => checks.push({ id, level, message: message6 });
|
|
@@ -18319,8 +18538,8 @@ async function doctor(loaded, specs, lock, lockPath, opts = {}) {
|
|
|
18319
18538
|
);
|
|
18320
18539
|
add("lock", "ok", `${Object.keys(lock.entries).length} valid v2 lock entr(ies)`);
|
|
18321
18540
|
const dirs = [...new Set(specs.flatMap((spec) => [
|
|
18322
|
-
|
|
18323
|
-
...spec.quality ? [
|
|
18541
|
+
path37.dirname(spec.outFile),
|
|
18542
|
+
...spec.quality ? [path37.dirname(spec.quality.outFile)] : []
|
|
18324
18543
|
]))];
|
|
18325
18544
|
const unwritable = [];
|
|
18326
18545
|
for (const dir of dirs) {
|
|
@@ -18343,7 +18562,7 @@ async function doctor(loaded, specs, lock, lockPath, opts = {}) {
|
|
|
18343
18562
|
for (let index = 0; index < entry.outputs.length; index++) {
|
|
18344
18563
|
const output = entry.outputs[index];
|
|
18345
18564
|
const spec = specByKey.get(key);
|
|
18346
|
-
const absolute = spec ? currentEntryOutputPath(entry, spec, index) : resolveOutputPath(output.path,
|
|
18565
|
+
const absolute = spec ? currentEntryOutputPath(entry, spec, index) : resolveOutputPath(output.path, path37.dirname(lockPath));
|
|
18347
18566
|
const owner = outputOwners.get(absolute);
|
|
18348
18567
|
if (owner && owner !== key) duplicateOutputs.push(`${output.path} (${owner}, ${key})`);
|
|
18349
18568
|
outputOwners.set(absolute, key);
|
|
@@ -18358,7 +18577,7 @@ async function doctor(loaded, specs, lock, lockPath, opts = {}) {
|
|
|
18358
18577
|
} else {
|
|
18359
18578
|
add("lock-outputs", "ok", `${outputOwners.size} recorded output path(s) are uniquely owned`);
|
|
18360
18579
|
}
|
|
18361
|
-
const cacheDir =
|
|
18580
|
+
const cacheDir = path37.join(path37.dirname(lockPath), ".pixelkiln", "cache");
|
|
18362
18581
|
const stranded = [];
|
|
18363
18582
|
const invalidState = [];
|
|
18364
18583
|
const staleMapUrls = [];
|
|
@@ -18374,11 +18593,11 @@ async function doctor(loaded, specs, lock, lockPath, opts = {}) {
|
|
|
18374
18593
|
const hasSource = Boolean(entry.sourceUrl || entry.sourceUrls?.length);
|
|
18375
18594
|
let hasCache = false;
|
|
18376
18595
|
for (const output of entry.outputs) {
|
|
18377
|
-
const cached =
|
|
18596
|
+
const cached = path37.join(
|
|
18378
18597
|
cacheDir,
|
|
18379
18598
|
cacheFileName(output.sha256, output.mediaType ?? MediaType.PNG)
|
|
18380
18599
|
);
|
|
18381
|
-
if (
|
|
18600
|
+
if (existsSync30(cached) && await sha256File(cached) === output.sha256) {
|
|
18382
18601
|
hasCache = true;
|
|
18383
18602
|
break;
|
|
18384
18603
|
}
|
|
@@ -18474,7 +18693,7 @@ async function nearestExistingDirectory(start) {
|
|
|
18474
18693
|
if ((await stat4(current)).isDirectory()) return current;
|
|
18475
18694
|
} catch {
|
|
18476
18695
|
}
|
|
18477
|
-
const parent =
|
|
18696
|
+
const parent = path37.dirname(current);
|
|
18478
18697
|
if (parent === current) return current;
|
|
18479
18698
|
current = parent;
|
|
18480
18699
|
}
|
|
@@ -18482,8 +18701,8 @@ async function nearestExistingDirectory(start) {
|
|
|
18482
18701
|
|
|
18483
18702
|
// src/pipeline/audit.ts
|
|
18484
18703
|
import { readFile as readFile25 } from "fs/promises";
|
|
18485
|
-
import { existsSync as
|
|
18486
|
-
import
|
|
18704
|
+
import { existsSync as existsSync31 } from "fs";
|
|
18705
|
+
import path38 from "path";
|
|
18487
18706
|
function paletteDistance(asset, reference) {
|
|
18488
18707
|
if (!asset.length || !reference.length) return 0;
|
|
18489
18708
|
let total = 0;
|
|
@@ -18519,7 +18738,7 @@ async function auditStyle(loaded, specs, styleId, lock) {
|
|
|
18519
18738
|
const unreadable = [];
|
|
18520
18739
|
for (const spec of mine) {
|
|
18521
18740
|
for (const output of resolveSpecOutputs(spec, lock, loaded.root)) {
|
|
18522
|
-
if (!
|
|
18741
|
+
if (!existsSync31(output.absolutePath)) {
|
|
18523
18742
|
missing.push(output.id);
|
|
18524
18743
|
continue;
|
|
18525
18744
|
}
|
|
@@ -18548,8 +18767,8 @@ async function auditStyle(loaded, specs, styleId, lock) {
|
|
|
18548
18767
|
let referenceFromStyleImages = false;
|
|
18549
18768
|
const refPalettes = [];
|
|
18550
18769
|
for (const img of style.styleImages) {
|
|
18551
|
-
const abs =
|
|
18552
|
-
if (!
|
|
18770
|
+
const abs = path38.resolve(loaded.root, img.path);
|
|
18771
|
+
if (!existsSync31(abs)) continue;
|
|
18553
18772
|
try {
|
|
18554
18773
|
refPalettes.push(extractPalette(decodePng(await readFile25(abs)), 12));
|
|
18555
18774
|
} catch {
|
|
@@ -18779,24 +18998,24 @@ async function runAudit(args) {
|
|
|
18779
18998
|
}
|
|
18780
18999
|
|
|
18781
19000
|
// src/cli/commands/pack.ts
|
|
18782
|
-
import
|
|
19001
|
+
import path40 from "path";
|
|
18783
19002
|
import { readFile as readFile26 } from "fs/promises";
|
|
18784
19003
|
|
|
18785
19004
|
// src/pipeline/pack.ts
|
|
18786
19005
|
import { readFileSync as readFileSync5 } from "fs";
|
|
18787
|
-
import
|
|
19006
|
+
import path39 from "path";
|
|
18788
19007
|
var DEFAULT_FRAME_SET_FPS = 12;
|
|
18789
19008
|
function resolvePackInputs(raw, inputsFilePath) {
|
|
18790
19009
|
if (!Array.isArray(raw) || !raw.length) {
|
|
18791
19010
|
throw new Error("--inputs must be a non-empty JSON array of { id, path }");
|
|
18792
19011
|
}
|
|
18793
|
-
const baseDir =
|
|
19012
|
+
const baseDir = path39.dirname(path39.resolve(inputsFilePath));
|
|
18794
19013
|
return raw.map((entry, i) => {
|
|
18795
19014
|
const e = entry;
|
|
18796
19015
|
if (typeof e.id !== "string" || typeof e.path !== "string") {
|
|
18797
19016
|
throw new Error(`--inputs[${i}] needs string "id" and "path"`);
|
|
18798
19017
|
}
|
|
18799
|
-
return { id: e.id, path:
|
|
19018
|
+
return { id: e.id, path: path39.resolve(baseDir, e.path) };
|
|
18800
19019
|
});
|
|
18801
19020
|
}
|
|
18802
19021
|
function packSprites(inputs, options = {}) {
|
|
@@ -18880,7 +19099,7 @@ function packStyle(lock, styleId, manifestDir, options = {}) {
|
|
|
18880
19099
|
}
|
|
18881
19100
|
const inputs2 = Object.entries(options.sourceOverrides).map(([id, source]) => ({
|
|
18882
19101
|
id,
|
|
18883
|
-
path:
|
|
19102
|
+
path: path39.resolve(source)
|
|
18884
19103
|
}));
|
|
18885
19104
|
if (!inputs2.length) throw new Error(`No approved quality outputs for style "${styleId}".`);
|
|
18886
19105
|
const packed2 = packSprites(inputs2, options);
|
|
@@ -18904,7 +19123,7 @@ function packStyle(lock, styleId, manifestDir, options = {}) {
|
|
|
18904
19123
|
let outputs = resolveEntryOutputs(entry, id, manifestDir);
|
|
18905
19124
|
const source = sources[id];
|
|
18906
19125
|
if (source && outputs.length <= 1) {
|
|
18907
|
-
inputs.push({ id, path:
|
|
19126
|
+
inputs.push({ id, path: path39.resolve(manifestDir, source) });
|
|
18908
19127
|
continue;
|
|
18909
19128
|
}
|
|
18910
19129
|
if (source && sourceIsStem(source, entry, manifestDir)) {
|
|
@@ -18938,7 +19157,7 @@ function packStyle(lock, styleId, manifestDir, options = {}) {
|
|
|
18938
19157
|
}
|
|
18939
19158
|
}
|
|
18940
19159
|
for (const [id, source] of Object.entries(sources)) {
|
|
18941
|
-
if (!locked.has(id)) inputs.push({ id, path:
|
|
19160
|
+
if (!locked.has(id)) inputs.push({ id, path: path39.resolve(manifestDir, source) });
|
|
18942
19161
|
}
|
|
18943
19162
|
if (!inputs.length) {
|
|
18944
19163
|
throw new Error(`No readable sprites for style "${styleId}"; ${noOutput.length} skipped.`);
|
|
@@ -19046,7 +19265,7 @@ function mountStyle(lock, styleId, manifestDir, mount, cells, sources = {}, outp
|
|
|
19046
19265
|
const source = sources[id];
|
|
19047
19266
|
const entry = lock.entries[`${styleId}/${id}`];
|
|
19048
19267
|
if (source && !(entry && sourceIsStem(source, entry, manifestDir))) {
|
|
19049
|
-
placements.push({ id, path:
|
|
19268
|
+
placements.push({ id, path: path39.resolve(manifestDir, source), cell });
|
|
19050
19269
|
continue;
|
|
19051
19270
|
}
|
|
19052
19271
|
if (source && entry) {
|
|
@@ -19079,7 +19298,7 @@ function mountStyle(lock, styleId, manifestDir, mount, cells, sources = {}, outp
|
|
|
19079
19298
|
`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}`
|
|
19080
19299
|
);
|
|
19081
19300
|
}
|
|
19082
|
-
const basePng = mount.base ? readFileSync5(
|
|
19301
|
+
const basePng = mount.base ? readFileSync5(path39.resolve(manifestDir, mount.base)) : void 0;
|
|
19083
19302
|
const mounted = mountSprites(placements, {
|
|
19084
19303
|
cellWidth: mount.cellWidth,
|
|
19085
19304
|
cellHeight: mount.cellHeight,
|
|
@@ -19089,7 +19308,7 @@ function mountStyle(lock, styleId, manifestDir, mount, cells, sources = {}, outp
|
|
|
19089
19308
|
...mounted,
|
|
19090
19309
|
sources: mount.base ? [{
|
|
19091
19310
|
id: "$base",
|
|
19092
|
-
path:
|
|
19311
|
+
path: path39.resolve(manifestDir, mount.base),
|
|
19093
19312
|
sha256: sha256(basePng),
|
|
19094
19313
|
included: true
|
|
19095
19314
|
}, ...mounted.sources] : mounted.sources,
|
|
@@ -19493,12 +19712,12 @@ async function runPack(args) {
|
|
|
19493
19712
|
if (args.primaryOnly || args.outputRoles.length) {
|
|
19494
19713
|
throw new Error("--primary-only and --output-role require manifest-driven pack");
|
|
19495
19714
|
}
|
|
19496
|
-
const raw = JSON.parse(await readFile26(
|
|
19715
|
+
const raw = JSON.parse(await readFile26(path40.resolve(args.inputs), "utf8"));
|
|
19497
19716
|
const inputs = resolvePackInputs(raw, args.inputs);
|
|
19498
19717
|
const { png, atlas, skipped, sources } = packSprites(inputs, { columns: args.columns });
|
|
19499
19718
|
const format = sheetFormat(args);
|
|
19500
|
-
const base =
|
|
19501
|
-
const { extension, document } = renderSheetDocument(format, atlas, { imageName:
|
|
19719
|
+
const base = path40.resolve(args.out.replace(/\.(?:png|json|tres)$/i, ""));
|
|
19720
|
+
const { extension, document } = renderSheetDocument(format, atlas, { imageName: path40.basename(`${base}.png`) });
|
|
19502
19721
|
const outputs = [
|
|
19503
19722
|
{ path: `${base}.png`, data: png },
|
|
19504
19723
|
{ path: `${base}${extension}`, data: document }
|
|
@@ -19511,7 +19730,7 @@ async function runPack(args) {
|
|
|
19511
19730
|
log(
|
|
19512
19731
|
` ${atlas.frames.length} sprite(s), ${atlas.sheet.width}x${atlas.sheet.height} in ${atlas.columns} column(s), ${(png.length / 1024).toFixed(1)} KB (${format})`
|
|
19513
19732
|
);
|
|
19514
|
-
log(` ${
|
|
19733
|
+
log(` ${path40.relative(process.cwd(), base)}.png + ${extension} + .pixelkiln.json`);
|
|
19515
19734
|
for (const s of skipped) log(` skipped ${s.id}: ${s.reason}`);
|
|
19516
19735
|
return;
|
|
19517
19736
|
}
|
|
@@ -19519,7 +19738,7 @@ async function runPack(args) {
|
|
|
19519
19738
|
if (args.primaryOnly && args.outputRoles.length) {
|
|
19520
19739
|
throw new Error("pack accepts either --primary-only or --output-role, not both");
|
|
19521
19740
|
}
|
|
19522
|
-
const manifestDir =
|
|
19741
|
+
const manifestDir = path40.dirname(path40.resolve(args.manifest));
|
|
19523
19742
|
const styleIds = args.styles.length ? args.styles : Object.keys(loaded.manifest.styles);
|
|
19524
19743
|
for (const styleId of styleIds) {
|
|
19525
19744
|
const style = loaded.manifest.styles[styleId];
|
|
@@ -19534,9 +19753,9 @@ async function runPack(args) {
|
|
|
19534
19753
|
sourceOverrides: qualitySources,
|
|
19535
19754
|
sources: manifestSources(loaded.manifest, styleId)
|
|
19536
19755
|
});
|
|
19537
|
-
const base = args.out ?
|
|
19756
|
+
const base = args.out ? path40.resolve(args.out.replace(/\.(?:png|json|tres)$/i, "")) : path40.resolve(manifestDir, style.outDir, `${styleId}-sheet`);
|
|
19538
19757
|
const format = sheetFormat(args);
|
|
19539
|
-
const { extension, document } = renderSheetDocument(format, atlas, { imageName:
|
|
19758
|
+
const { extension, document } = renderSheetDocument(format, atlas, { imageName: path40.basename(`${base}.png`) });
|
|
19540
19759
|
const outputs = [
|
|
19541
19760
|
{ path: `${base}.png`, data: png },
|
|
19542
19761
|
{ path: `${base}${extension}`, data: document }
|
|
@@ -19568,13 +19787,13 @@ async function runPack(args) {
|
|
|
19568
19787
|
log(
|
|
19569
19788
|
` ${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})`
|
|
19570
19789
|
);
|
|
19571
|
-
log(` ${
|
|
19790
|
+
log(` ${path40.relative(process.cwd(), base)}.png + ${extension} + .pixelkiln.json`);
|
|
19572
19791
|
for (const s of skipped) log(` skipped ${s.id}: ${s.reason}`);
|
|
19573
19792
|
}
|
|
19574
19793
|
}
|
|
19575
19794
|
async function runMount(args) {
|
|
19576
19795
|
const { loaded, lock } = await openProject2(args);
|
|
19577
|
-
const manifestDir =
|
|
19796
|
+
const manifestDir = path40.dirname(path40.resolve(args.manifest));
|
|
19578
19797
|
const styleIds = args.styles.length ? args.styles : Object.keys(loaded.manifest.styles);
|
|
19579
19798
|
for (const styleId of styleIds) {
|
|
19580
19799
|
const style = loaded.manifest.styles[styleId];
|
|
@@ -19613,9 +19832,9 @@ async function runMount(args) {
|
|
|
19613
19832
|
sources,
|
|
19614
19833
|
outputRoles
|
|
19615
19834
|
);
|
|
19616
|
-
const out =
|
|
19835
|
+
const out = path40.resolve(manifestDir, style.mount.out);
|
|
19617
19836
|
const format = sheetFormat(args);
|
|
19618
|
-
const { extension, document } = renderSheetDocument(format, atlas, { imageName:
|
|
19837
|
+
const { extension, document } = renderSheetDocument(format, atlas, { imageName: path40.basename(out) });
|
|
19619
19838
|
const metadata = out.replace(/\.png$/, "") + extension;
|
|
19620
19839
|
const companion = out.replace(/\.png$/, "") + ".pixelkiln.json";
|
|
19621
19840
|
const outputs = [
|
|
@@ -19635,7 +19854,7 @@ async function runMount(args) {
|
|
|
19635
19854
|
await provenanceFile("$lock", args.lock),
|
|
19636
19855
|
...qualityRecords,
|
|
19637
19856
|
...artifactSources.filter(
|
|
19638
|
-
(source) => source.id !== "$base" ||
|
|
19857
|
+
(source) => source.id !== "$base" || path40.resolve(source.path) !== out
|
|
19639
19858
|
)
|
|
19640
19859
|
],
|
|
19641
19860
|
options: {
|
|
@@ -19649,14 +19868,14 @@ async function runMount(args) {
|
|
|
19649
19868
|
log(
|
|
19650
19869
|
` ${styleId}: ${atlas.frames.length} cell(s) into ${atlas.sheet.width}x${atlas.sheet.height}` + (overBase ? ` over ${style.mount.base}` : " (new sheet)")
|
|
19651
19870
|
);
|
|
19652
|
-
log(` ${
|
|
19871
|
+
log(` ${path40.relative(process.cwd(), out)} + atlas/provenance JSON`);
|
|
19653
19872
|
for (const s of skipped) log(` skipped ${s.id}: ${s.reason}`);
|
|
19654
19873
|
}
|
|
19655
19874
|
}
|
|
19656
19875
|
async function runExport(args) {
|
|
19657
19876
|
const { loaded, specs, lock } = await openProject2(args);
|
|
19658
19877
|
const format = args.format ?? "generic";
|
|
19659
|
-
const manifestDir =
|
|
19878
|
+
const manifestDir = path40.dirname(path40.resolve(args.manifest));
|
|
19660
19879
|
const selected = specs.filter((spec) => {
|
|
19661
19880
|
if (spec.generator !== "tiles" && spec.generator !== "terrain") return false;
|
|
19662
19881
|
if (args.styles.length && !args.styles.includes(spec.styleId)) return false;
|
|
@@ -19672,12 +19891,12 @@ async function runExport(args) {
|
|
|
19672
19891
|
for (const spec of selected) {
|
|
19673
19892
|
const entry = lock.entries[lockKey(spec.styleId, spec.assetId)];
|
|
19674
19893
|
const style = loaded.manifest.styles[spec.styleId];
|
|
19675
|
-
const defaultBase =
|
|
19676
|
-
const base = args.out ?
|
|
19894
|
+
const defaultBase = path40.resolve(manifestDir, style.outDir, `${spec.assetId}-tileset`);
|
|
19895
|
+
const base = args.out ? path40.resolve(args.out.replace(/\.(?:png|json|tsj|tres)$/i, "")) : defaultBase;
|
|
19677
19896
|
const result = exportTileset(entry, spec, {
|
|
19678
19897
|
format,
|
|
19679
19898
|
manifestDir,
|
|
19680
|
-
imageName:
|
|
19899
|
+
imageName: path40.basename(`${base}.png`),
|
|
19681
19900
|
columns: args.columns
|
|
19682
19901
|
});
|
|
19683
19902
|
const outputs = [
|
|
@@ -19695,7 +19914,7 @@ async function runExport(args) {
|
|
|
19695
19914
|
asset: spec.assetId,
|
|
19696
19915
|
columns: args.columns ?? null,
|
|
19697
19916
|
format,
|
|
19698
|
-
image:
|
|
19917
|
+
image: path40.basename(`${base}.png`),
|
|
19699
19918
|
providerRules: result.generic.providerRules,
|
|
19700
19919
|
style: spec.styleId,
|
|
19701
19920
|
tileType: spec.tileType ?? null
|
|
@@ -19705,7 +19924,7 @@ async function runExport(args) {
|
|
|
19705
19924
|
` ${spec.styleId}/${spec.assetId}: ${result.generic.tiles.length} tile(s), ${result.generic.sheet.width}x${result.generic.sheet.height} (${format})`
|
|
19706
19925
|
);
|
|
19707
19926
|
log(
|
|
19708
|
-
` ${
|
|
19927
|
+
` ${path40.relative(process.cwd(), base)}.png + ${path40.basename(base)}${result.extension} + .pixelkiln.json`
|
|
19709
19928
|
);
|
|
19710
19929
|
}
|
|
19711
19930
|
}
|
|
@@ -19718,22 +19937,22 @@ function sheetFormat(args) {
|
|
|
19718
19937
|
}
|
|
19719
19938
|
|
|
19720
19939
|
// src/cli/commands/pixellab-utils.ts
|
|
19721
|
-
import
|
|
19940
|
+
import path42 from "path";
|
|
19722
19941
|
|
|
19723
19942
|
// src/pixellab-utils.ts
|
|
19724
|
-
import { existsSync as
|
|
19725
|
-
import { mkdir as
|
|
19726
|
-
import
|
|
19943
|
+
import { existsSync as existsSync32 } from "fs";
|
|
19944
|
+
import { mkdir as mkdir13, readFile as readFile27, writeFile as writeFile16 } from "fs/promises";
|
|
19945
|
+
import path41 from "path";
|
|
19727
19946
|
var UNZOOM_MIN_SIDE = 256;
|
|
19728
19947
|
var UNZOOM_MAX_AREA = 2048 * 2048;
|
|
19729
19948
|
function defaultUnzoomOut(input) {
|
|
19730
|
-
const ext =
|
|
19949
|
+
const ext = path41.extname(input);
|
|
19731
19950
|
return `${ext ? input.slice(0, -ext.length) : input}.unzoomed.png`;
|
|
19732
19951
|
}
|
|
19733
19952
|
async function unzoomFile(client2, input, opts = {}) {
|
|
19734
|
-
const out =
|
|
19735
|
-
if (
|
|
19736
|
-
if (
|
|
19953
|
+
const out = path41.resolve(opts.out ?? defaultUnzoomOut(input));
|
|
19954
|
+
if (path41.resolve(input) === out) throw new Error("unzoom will not overwrite its own input; pass a different --out");
|
|
19955
|
+
if (existsSync32(out) && !opts.force) throw new Error(`${out} already exists; pass --force to replace it`);
|
|
19737
19956
|
const bytes = await readFile27(input);
|
|
19738
19957
|
const metadata = imageMetadata(bytes);
|
|
19739
19958
|
if (!metadata) throw new Error(`${input} is not a readable PNG or JPEG`);
|
|
@@ -19747,22 +19966,22 @@ async function unzoomFile(client2, input, opts = {}) {
|
|
|
19747
19966
|
throw new Error(`${input} is ${width}x${height}; PixelLab's unzoom takes at most 2048x2048 worth of pixels`);
|
|
19748
19967
|
}
|
|
19749
19968
|
const res = await client2.unzoom({ image: { base64: bytes.toString("base64"), format }, quantize: opts.quantize });
|
|
19750
|
-
await
|
|
19751
|
-
await
|
|
19969
|
+
await mkdir13(path41.dirname(out), { recursive: true });
|
|
19970
|
+
await writeFile16(out, res.png);
|
|
19752
19971
|
return { out, original: res.originalSize, unzoomed: res.unzoomedSize, zoomFactor: res.zoomFactor, usage: res.usage };
|
|
19753
19972
|
}
|
|
19754
19973
|
var FONT_WEIGHTS = ["Bold", "Regular"];
|
|
19755
19974
|
var FONT_GLYPH_SIZES = [8, 16, 32, 64];
|
|
19756
19975
|
var FONT_COST_GENERATIONS = 25;
|
|
19757
19976
|
function fontOutputPaths(out) {
|
|
19758
|
-
const base =
|
|
19977
|
+
const base = path41.resolve(out.replace(/\.(ttf|png)$/i, ""));
|
|
19759
19978
|
return { ttf: `${base}.ttf`, atlas: `${base}.png` };
|
|
19760
19979
|
}
|
|
19761
19980
|
async function generateFont(client2, opts) {
|
|
19762
19981
|
const { ttf, atlas } = fontOutputPaths(opts.out);
|
|
19763
19982
|
if (!opts.force) {
|
|
19764
19983
|
for (const file of [ttf, atlas]) {
|
|
19765
|
-
if (
|
|
19984
|
+
if (existsSync32(file)) throw new Error(`${file} already exists; pass --force to replace it`);
|
|
19766
19985
|
}
|
|
19767
19986
|
}
|
|
19768
19987
|
const sleep3 = opts.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
@@ -19789,9 +20008,9 @@ async function generateFont(client2, opts) {
|
|
|
19789
20008
|
client2.download(job.download_ttf_url),
|
|
19790
20009
|
client2.download(job.download_atlas_url)
|
|
19791
20010
|
]);
|
|
19792
|
-
await
|
|
19793
|
-
await
|
|
19794
|
-
await
|
|
20011
|
+
await mkdir13(path41.dirname(ttf), { recursive: true });
|
|
20012
|
+
await writeFile16(ttf, ttfBytes);
|
|
20013
|
+
await writeFile16(atlas, atlasBytes);
|
|
19795
20014
|
return { jobId, ttf, atlas, glyphPx: job.glyph_px ?? null, usage: job.usage };
|
|
19796
20015
|
}
|
|
19797
20016
|
if (Date.now() - started > timeout) {
|
|
@@ -19805,7 +20024,7 @@ async function generateFont(client2, opts) {
|
|
|
19805
20024
|
|
|
19806
20025
|
// src/cli/commands/pixellab-utils.ts
|
|
19807
20026
|
function loadKeys(args) {
|
|
19808
|
-
loadEnvFiles(
|
|
20027
|
+
loadEnvFiles(path42.dirname(path42.resolve(args.manifest)));
|
|
19809
20028
|
loadEnvFiles(process.cwd());
|
|
19810
20029
|
}
|
|
19811
20030
|
async function runUnzoom(args) {
|
|
@@ -19871,19 +20090,19 @@ async function runFont(args) {
|
|
|
19871
20090
|
}
|
|
19872
20091
|
|
|
19873
20092
|
// src/cli/commands/quality.ts
|
|
19874
|
-
import
|
|
20093
|
+
import path44 from "path";
|
|
19875
20094
|
import { readFile as readFile29 } from "fs/promises";
|
|
19876
20095
|
|
|
19877
20096
|
// src/pipeline/quality-regression.ts
|
|
19878
|
-
import
|
|
19879
|
-
import { existsSync as
|
|
20097
|
+
import path43 from "path";
|
|
20098
|
+
import { existsSync as existsSync33 } from "fs";
|
|
19880
20099
|
import { readFile as readFile28 } from "fs/promises";
|
|
19881
|
-
import { z as
|
|
20100
|
+
import { z as z12 } from "zod";
|
|
19882
20101
|
var SHA256_RE = /^[0-9a-f]{64}$/;
|
|
19883
20102
|
var HEX_RE = /^#[0-9a-f]{6}$/;
|
|
19884
20103
|
var MAX_REDMEAN_DISTANCE = 765;
|
|
19885
|
-
var QualityPathSchema =
|
|
19886
|
-
(value) => !
|
|
20104
|
+
var QualityPathSchema = z12.string().min(1).refine(
|
|
20105
|
+
(value) => !path43.posix.isAbsolute(value) && !value.includes("\\"),
|
|
19887
20106
|
"expected a portable relative path"
|
|
19888
20107
|
);
|
|
19889
20108
|
var DEFAULT_QUALITY_TOLERANCES = {
|
|
@@ -19896,53 +20115,53 @@ var DEFAULT_QUALITY_TOLERANCES = {
|
|
|
19896
20115
|
maxEdgeContrastDrop: 0.03,
|
|
19897
20116
|
maxIsolatedPixelIncrease: 5e-3
|
|
19898
20117
|
};
|
|
19899
|
-
var QualityToleranceSchema =
|
|
19900
|
-
requireExactHash:
|
|
19901
|
-
maxColorCountIncrease:
|
|
19902
|
-
maxNewColors:
|
|
19903
|
-
maxTransparencyDelta:
|
|
19904
|
-
maxPartialAlphaIncrease:
|
|
19905
|
-
maxEdgeDensityDelta:
|
|
19906
|
-
maxEdgeContrastDrop:
|
|
19907
|
-
maxIsolatedPixelIncrease:
|
|
20118
|
+
var QualityToleranceSchema = z12.object({
|
|
20119
|
+
requireExactHash: z12.boolean(),
|
|
20120
|
+
maxColorCountIncrease: z12.number().int().min(0),
|
|
20121
|
+
maxNewColors: z12.number().int().min(0),
|
|
20122
|
+
maxTransparencyDelta: z12.number().min(0).max(1),
|
|
20123
|
+
maxPartialAlphaIncrease: z12.number().min(0).max(1),
|
|
20124
|
+
maxEdgeDensityDelta: z12.number().min(0).max(1),
|
|
20125
|
+
maxEdgeContrastDrop: z12.number().min(0).max(1),
|
|
20126
|
+
maxIsolatedPixelIncrease: z12.number().min(0).max(1)
|
|
19908
20127
|
}).strict();
|
|
19909
|
-
var ImageQualityMetricsSchema =
|
|
19910
|
-
sha256:
|
|
19911
|
-
width:
|
|
19912
|
-
height:
|
|
19913
|
-
colorCount:
|
|
19914
|
-
colors:
|
|
19915
|
-
transparency:
|
|
19916
|
-
partialAlpha:
|
|
19917
|
-
edgeDensity:
|
|
19918
|
-
meanEdgeContrast:
|
|
19919
|
-
isolatedPixelRatio:
|
|
20128
|
+
var ImageQualityMetricsSchema = z12.object({
|
|
20129
|
+
sha256: z12.string().regex(SHA256_RE),
|
|
20130
|
+
width: z12.number().int().min(1),
|
|
20131
|
+
height: z12.number().int().min(1),
|
|
20132
|
+
colorCount: z12.number().int().min(0),
|
|
20133
|
+
colors: z12.array(z12.string().regex(HEX_RE)),
|
|
20134
|
+
transparency: z12.number().min(0).max(1),
|
|
20135
|
+
partialAlpha: z12.number().min(0).max(1),
|
|
20136
|
+
edgeDensity: z12.number().min(0).max(1),
|
|
20137
|
+
meanEdgeContrast: z12.number().min(0).max(1),
|
|
20138
|
+
isolatedPixelRatio: z12.number().min(0).max(1)
|
|
19920
20139
|
}).strict();
|
|
19921
|
-
var QualityBaselineCaseSchema =
|
|
19922
|
-
id:
|
|
20140
|
+
var QualityBaselineCaseSchema = z12.object({
|
|
20141
|
+
id: z12.string().regex(/^[a-z0-9][a-z0-9/_-]*$/),
|
|
19923
20142
|
file: QualityPathSchema,
|
|
19924
|
-
record:
|
|
20143
|
+
record: z12.object({
|
|
19925
20144
|
path: QualityPathSchema,
|
|
19926
|
-
sha256:
|
|
20145
|
+
sha256: z12.string().regex(SHA256_RE)
|
|
19927
20146
|
}).strict().optional(),
|
|
19928
20147
|
expected: ImageQualityMetricsSchema,
|
|
19929
20148
|
tolerances: QualityToleranceSchema
|
|
19930
20149
|
}).strict();
|
|
19931
|
-
var QualityBaselineSchema =
|
|
19932
|
-
$schema:
|
|
19933
|
-
format:
|
|
19934
|
-
schemaVersion:
|
|
19935
|
-
cases:
|
|
20150
|
+
var QualityBaselineSchema = z12.object({
|
|
20151
|
+
$schema: z12.string().url().optional(),
|
|
20152
|
+
format: z12.literal("pixelkiln-quality-baseline"),
|
|
20153
|
+
schemaVersion: z12.literal(1),
|
|
20154
|
+
cases: z12.array(QualityBaselineCaseSchema).min(1)
|
|
19936
20155
|
}).strict().superRefine((baseline, context) => {
|
|
19937
20156
|
const ids = baseline.cases.map((entry) => entry.id);
|
|
19938
20157
|
if (new Set(ids).size !== ids.length) {
|
|
19939
|
-
context.addIssue({ code:
|
|
20158
|
+
context.addIssue({ code: z12.ZodIssueCode.custom, path: ["cases"], message: "duplicate case id" });
|
|
19940
20159
|
}
|
|
19941
20160
|
});
|
|
19942
|
-
var QualityInputSchema =
|
|
19943
|
-
id:
|
|
19944
|
-
path:
|
|
19945
|
-
record:
|
|
20161
|
+
var QualityInputSchema = z12.object({
|
|
20162
|
+
id: z12.string().regex(/^[a-z0-9][a-z0-9/_-]*$/),
|
|
20163
|
+
path: z12.string().min(1),
|
|
20164
|
+
record: z12.string().min(1).optional(),
|
|
19946
20165
|
tolerances: QualityToleranceSchema.partial().optional()
|
|
19947
20166
|
}).strict();
|
|
19948
20167
|
function message4(error) {
|
|
@@ -20030,7 +20249,7 @@ function measureDecodedImageQuality(png, digest2) {
|
|
|
20030
20249
|
};
|
|
20031
20250
|
}
|
|
20032
20251
|
async function measureImageQuality(file) {
|
|
20033
|
-
const absolute =
|
|
20252
|
+
const absolute = path43.resolve(file);
|
|
20034
20253
|
let bytes;
|
|
20035
20254
|
try {
|
|
20036
20255
|
bytes = await readFile28(absolute);
|
|
@@ -20044,7 +20263,7 @@ async function measureImageQuality(file) {
|
|
|
20044
20263
|
}
|
|
20045
20264
|
}
|
|
20046
20265
|
function resolveQualityInputs(raw, inputsFilePath) {
|
|
20047
|
-
const parsed =
|
|
20266
|
+
const parsed = z12.array(QualityInputSchema).min(1).safeParse(raw);
|
|
20048
20267
|
if (!parsed.success) {
|
|
20049
20268
|
throw new Error(
|
|
20050
20269
|
"--inputs must be a non-empty JSON array of quality cases:\n" + parsed.error.issues.map((issue) => ` ${issue.path.join(".") || "$"}: ${issue.message}`).join("\n")
|
|
@@ -20052,11 +20271,11 @@ function resolveQualityInputs(raw, inputsFilePath) {
|
|
|
20052
20271
|
}
|
|
20053
20272
|
const ids = parsed.data.map((entry) => entry.id);
|
|
20054
20273
|
if (new Set(ids).size !== ids.length) throw new Error("--inputs contains a duplicate quality case id.");
|
|
20055
|
-
const root =
|
|
20274
|
+
const root = path43.dirname(path43.resolve(inputsFilePath));
|
|
20056
20275
|
return parsed.data.map((entry) => ({
|
|
20057
20276
|
id: entry.id,
|
|
20058
|
-
file:
|
|
20059
|
-
...entry.record ? { record:
|
|
20277
|
+
file: path43.resolve(root, entry.path),
|
|
20278
|
+
...entry.record ? { record: path43.resolve(root, entry.record) } : {},
|
|
20060
20279
|
tolerances: QualityToleranceSchema.parse({
|
|
20061
20280
|
...DEFAULT_QUALITY_TOLERANCES,
|
|
20062
20281
|
...entry.tolerances
|
|
@@ -20064,7 +20283,7 @@ function resolveQualityInputs(raw, inputsFilePath) {
|
|
|
20064
20283
|
}));
|
|
20065
20284
|
}
|
|
20066
20285
|
function portableRelative2(from, to) {
|
|
20067
|
-
return
|
|
20286
|
+
return path43.relative(from, path43.resolve(to)).split(path43.sep).join("/") || ".";
|
|
20068
20287
|
}
|
|
20069
20288
|
async function verifyRecordForImage(recordPath, imagePath) {
|
|
20070
20289
|
const verification = await verifyArtifactBundle(recordPath);
|
|
@@ -20079,20 +20298,20 @@ async function verifyRecordForImage(recordPath, imagePath) {
|
|
|
20079
20298
|
if (record.kind !== "refine" || options === null || typeof options !== "object" || options.schema !== "pixelkiln-quality") {
|
|
20080
20299
|
throw new Error(`Quality record ${recordPath} is not a PixelKiln refinement record.`);
|
|
20081
20300
|
}
|
|
20082
|
-
const outputs = record.outputs.map((output) =>
|
|
20083
|
-
if (!outputs.includes(
|
|
20301
|
+
const outputs = record.outputs.map((output) => path43.resolve(path43.dirname(recordPath), output.path));
|
|
20302
|
+
if (!outputs.includes(path43.resolve(imagePath))) {
|
|
20084
20303
|
throw new Error(`Quality record ${recordPath} does not own ${imagePath}.`);
|
|
20085
20304
|
}
|
|
20086
20305
|
}
|
|
20087
20306
|
async function snapshotQualityBaseline(inputs, baselinePath, options = {}) {
|
|
20088
20307
|
if (!inputs.length) throw new Error("Cannot snapshot an empty quality baseline.");
|
|
20089
|
-
const absolute =
|
|
20308
|
+
const absolute = path43.resolve(baselinePath);
|
|
20090
20309
|
for (const input of inputs) {
|
|
20091
|
-
if (
|
|
20310
|
+
if (path43.resolve(input.file) === absolute || input.record && path43.resolve(input.record) === absolute) {
|
|
20092
20311
|
throw new Error(`Quality baseline output would overwrite an input: ${absolute}.`);
|
|
20093
20312
|
}
|
|
20094
20313
|
}
|
|
20095
|
-
const root =
|
|
20314
|
+
const root = path43.dirname(absolute);
|
|
20096
20315
|
const cases = [];
|
|
20097
20316
|
for (const input of [...inputs].sort((a, b) => a.id.localeCompare(b.id))) {
|
|
20098
20317
|
const expected = await measureImageQuality(input.file);
|
|
@@ -20119,7 +20338,7 @@ async function snapshotQualityBaseline(inputs, baselinePath, options = {}) {
|
|
|
20119
20338
|
cases
|
|
20120
20339
|
});
|
|
20121
20340
|
const data = Buffer.from(JSON.stringify(baseline, null, 2) + "\n");
|
|
20122
|
-
if (
|
|
20341
|
+
if (existsSync33(absolute) && !options.force) {
|
|
20123
20342
|
const current = await readFile28(absolute);
|
|
20124
20343
|
if (!current.equals(data)) {
|
|
20125
20344
|
throw new OverwriteRefusedError(
|
|
@@ -20132,7 +20351,7 @@ async function snapshotQualityBaseline(inputs, baselinePath, options = {}) {
|
|
|
20132
20351
|
return { baseline, path: absolute, changed: result.changed.length > 0 };
|
|
20133
20352
|
}
|
|
20134
20353
|
async function readQualityBaseline(baselinePath) {
|
|
20135
|
-
const absolute =
|
|
20354
|
+
const absolute = path43.resolve(baselinePath);
|
|
20136
20355
|
let raw;
|
|
20137
20356
|
try {
|
|
20138
20357
|
raw = JSON.parse(await readFile28(absolute, "utf8"));
|
|
@@ -20205,12 +20424,12 @@ function regressionViolations(expected, actual, tolerances) {
|
|
|
20205
20424
|
return { violations, warnings };
|
|
20206
20425
|
}
|
|
20207
20426
|
async function checkQualityBaseline(baselinePath) {
|
|
20208
|
-
const absolute =
|
|
20427
|
+
const absolute = path43.resolve(baselinePath);
|
|
20209
20428
|
const baseline = await readQualityBaseline(absolute);
|
|
20210
|
-
const root =
|
|
20429
|
+
const root = path43.dirname(absolute);
|
|
20211
20430
|
const cases = [];
|
|
20212
20431
|
for (const entry of baseline.cases) {
|
|
20213
|
-
const file =
|
|
20432
|
+
const file = path43.resolve(root, entry.file);
|
|
20214
20433
|
let actual = null;
|
|
20215
20434
|
const violations = [];
|
|
20216
20435
|
const warnings = [];
|
|
@@ -20223,7 +20442,7 @@ async function checkQualityBaseline(baselinePath) {
|
|
|
20223
20442
|
violations.push(message4(error));
|
|
20224
20443
|
}
|
|
20225
20444
|
if (entry.record) {
|
|
20226
|
-
const recordPath =
|
|
20445
|
+
const recordPath = path43.resolve(root, entry.record.path);
|
|
20227
20446
|
try {
|
|
20228
20447
|
const recordHash = await sha256File(recordPath);
|
|
20229
20448
|
if (recordHash !== entry.record.sha256) violations.push("quality record hash changed");
|
|
@@ -20265,15 +20484,15 @@ async function runQuality(args) {
|
|
|
20265
20484
|
if (!args.out) throw new Error("quality snapshot needs --out <pixelkiln.quality.json>.");
|
|
20266
20485
|
let raw;
|
|
20267
20486
|
try {
|
|
20268
|
-
raw = JSON.parse(await readFile29(
|
|
20487
|
+
raw = JSON.parse(await readFile29(path44.resolve(args.inputs), "utf8"));
|
|
20269
20488
|
} catch (error) {
|
|
20270
20489
|
throw new Error(
|
|
20271
|
-
`Could not read quality inputs ${
|
|
20490
|
+
`Could not read quality inputs ${path44.resolve(args.inputs)}: ${error instanceof Error ? error.message : String(error)}`,
|
|
20272
20491
|
{ cause: error }
|
|
20273
20492
|
);
|
|
20274
20493
|
}
|
|
20275
20494
|
const inputs = resolveQualityInputs(raw, args.inputs);
|
|
20276
|
-
if (
|
|
20495
|
+
if (path44.resolve(args.inputs) === path44.resolve(args.out)) {
|
|
20277
20496
|
throw new Error("quality snapshot --out must not overwrite its --inputs file.");
|
|
20278
20497
|
}
|
|
20279
20498
|
const result = await snapshotQualityBaseline(inputs, args.out, { force: args.force });
|
|
@@ -20286,7 +20505,7 @@ async function runQuality(args) {
|
|
|
20286
20505
|
}, null, 2));
|
|
20287
20506
|
} else {
|
|
20288
20507
|
log(
|
|
20289
|
-
` ${result.changed ? "wrote" : "unchanged"} ${
|
|
20508
|
+
` ${result.changed ? "wrote" : "unchanged"} ${path44.relative(process.cwd(), result.path)} (${result.baseline.cases.length} case(s))`
|
|
20290
20509
|
);
|
|
20291
20510
|
log(` Review the tolerances, commit the baseline, then run quality check in CI.`);
|
|
20292
20511
|
}
|
|
@@ -20312,54 +20531,54 @@ async function runQuality(args) {
|
|
|
20312
20531
|
}
|
|
20313
20532
|
|
|
20314
20533
|
// src/cli/commands/recipe.ts
|
|
20315
|
-
import
|
|
20534
|
+
import path46 from "path";
|
|
20316
20535
|
|
|
20317
20536
|
// src/recipes.ts
|
|
20318
|
-
import
|
|
20319
|
-
import { existsSync as
|
|
20537
|
+
import path45 from "path";
|
|
20538
|
+
import { existsSync as existsSync34 } from "fs";
|
|
20320
20539
|
import { readdir as readdir3, readFile as readFile30 } from "fs/promises";
|
|
20321
20540
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
20322
|
-
import { z as
|
|
20541
|
+
import { z as z13 } from "zod";
|
|
20323
20542
|
var SHA256_RE2 = /^[0-9a-f]{64}$/;
|
|
20324
20543
|
var RECIPE_ID_RE = /^[a-z0-9][a-z0-9-]*\/[a-z0-9][a-z0-9-]*$/;
|
|
20325
20544
|
var VERSION_RE = /^\d+\.\d+\.\d+$/;
|
|
20326
20545
|
var RECIPE_FILE = "pixelkiln.recipe.json";
|
|
20327
20546
|
var RECIPE_DIR_TOKEN = "{{recipeDir}}";
|
|
20328
|
-
var PortablePathSchema =
|
|
20329
|
-
if (value.includes("\\") ||
|
|
20547
|
+
var PortablePathSchema = z13.string().min(1).refine((value) => {
|
|
20548
|
+
if (value.includes("\\") || path45.posix.isAbsolute(value)) return false;
|
|
20330
20549
|
const parts = value.split("/");
|
|
20331
20550
|
return !parts.includes("") && !parts.includes(".") && !parts.includes("..");
|
|
20332
20551
|
}, "expected a portable relative path without '.', '..', or backslashes");
|
|
20333
|
-
var RecipeBindingSchema =
|
|
20334
|
-
nodeId:
|
|
20335
|
-
input:
|
|
20552
|
+
var RecipeBindingSchema = z13.object({
|
|
20553
|
+
nodeId: z13.string().min(1),
|
|
20554
|
+
input: z13.string().min(1)
|
|
20336
20555
|
}).strict();
|
|
20337
|
-
var RecipeSchema =
|
|
20338
|
-
$schema:
|
|
20339
|
-
format:
|
|
20340
|
-
schemaVersion:
|
|
20341
|
-
id:
|
|
20342
|
-
version:
|
|
20343
|
-
provider:
|
|
20344
|
-
summary:
|
|
20345
|
-
files:
|
|
20556
|
+
var RecipeSchema = z13.object({
|
|
20557
|
+
$schema: z13.string().url().optional(),
|
|
20558
|
+
format: z13.literal("pixelkiln-recipe"),
|
|
20559
|
+
schemaVersion: z13.literal(1),
|
|
20560
|
+
id: z13.string().regex(RECIPE_ID_RE, "expected provider/name"),
|
|
20561
|
+
version: z13.string().regex(VERSION_RE, "expected x.y.z"),
|
|
20562
|
+
provider: z13.string().regex(/^[a-z0-9][a-z0-9-]*$/),
|
|
20563
|
+
summary: z13.string().min(1).max(240),
|
|
20564
|
+
files: z13.array(z13.object({
|
|
20346
20565
|
path: PortablePathSchema,
|
|
20347
|
-
role:
|
|
20348
|
-
sha256:
|
|
20566
|
+
role: z13.enum(["workflow", "reference"]),
|
|
20567
|
+
sha256: z13.string().regex(SHA256_RE2)
|
|
20349
20568
|
}).strict()).default([]),
|
|
20350
|
-
models:
|
|
20569
|
+
models: z13.array(z13.object({
|
|
20351
20570
|
path: PortablePathSchema,
|
|
20352
|
-
sha256:
|
|
20353
|
-
source:
|
|
20354
|
-
license:
|
|
20571
|
+
sha256: z13.string().regex(SHA256_RE2),
|
|
20572
|
+
source: z13.string().url(),
|
|
20573
|
+
license: z13.string().min(1)
|
|
20355
20574
|
}).strict()).default([]),
|
|
20356
|
-
styleId:
|
|
20575
|
+
styleId: z13.string().regex(/^[a-z0-9][a-z0-9-]*$/),
|
|
20357
20576
|
style: StyleSchema,
|
|
20358
|
-
workflow:
|
|
20577
|
+
workflow: z13.object({
|
|
20359
20578
|
path: PortablePathSchema,
|
|
20360
|
-
outputNodeId:
|
|
20361
|
-
numImages:
|
|
20362
|
-
bindings:
|
|
20579
|
+
outputNodeId: z13.string().min(1),
|
|
20580
|
+
numImages: z13.number().int().min(1).max(16),
|
|
20581
|
+
bindings: z13.object({
|
|
20363
20582
|
prompt: RecipeBindingSchema,
|
|
20364
20583
|
width: RecipeBindingSchema.optional(),
|
|
20365
20584
|
height: RecipeBindingSchema.optional(),
|
|
@@ -20370,67 +20589,67 @@ var RecipeSchema = z12.object({
|
|
|
20370
20589
|
strength: RecipeBindingSchema.optional()
|
|
20371
20590
|
}).strict()
|
|
20372
20591
|
}).strict().optional(),
|
|
20373
|
-
quality:
|
|
20374
|
-
stage:
|
|
20375
|
-
workingCanvas:
|
|
20376
|
-
width:
|
|
20377
|
-
height:
|
|
20592
|
+
quality: z13.object({
|
|
20593
|
+
stage: z13.enum(["composition-source", "production-candidate"]),
|
|
20594
|
+
workingCanvas: z13.object({
|
|
20595
|
+
width: z13.number().int().min(16),
|
|
20596
|
+
height: z13.number().int().min(16)
|
|
20378
20597
|
}).strict(),
|
|
20379
|
-
recommendedNativeSize:
|
|
20380
|
-
min:
|
|
20381
|
-
max:
|
|
20598
|
+
recommendedNativeSize: z13.object({
|
|
20599
|
+
min: z13.number().int().min(1),
|
|
20600
|
+
max: z13.number().int().min(1)
|
|
20382
20601
|
}).strict(),
|
|
20383
|
-
paletteColors:
|
|
20384
|
-
min:
|
|
20385
|
-
max:
|
|
20602
|
+
paletteColors: z13.object({
|
|
20603
|
+
min: z13.number().int().min(1),
|
|
20604
|
+
max: z13.number().int().min(1)
|
|
20386
20605
|
}).strict(),
|
|
20387
|
-
background:
|
|
20388
|
-
checks:
|
|
20606
|
+
background: z13.enum(["opaque", "transparent", "full-bleed"]),
|
|
20607
|
+
checks: z13.array(z13.enum([
|
|
20389
20608
|
"prompt-coverage",
|
|
20390
20609
|
"native-grid",
|
|
20391
20610
|
"final-palette",
|
|
20392
20611
|
"alpha-edge",
|
|
20393
20612
|
"human-1x"
|
|
20394
20613
|
])).min(1),
|
|
20395
|
-
notes:
|
|
20614
|
+
notes: z13.array(z13.string().min(1)).default([])
|
|
20396
20615
|
}).strict(),
|
|
20397
|
-
integrity:
|
|
20398
|
-
algorithm:
|
|
20399
|
-
digest:
|
|
20616
|
+
integrity: z13.object({
|
|
20617
|
+
algorithm: z13.literal("sha256"),
|
|
20618
|
+
digest: z13.string().regex(SHA256_RE2)
|
|
20400
20619
|
}).strict()
|
|
20401
20620
|
}).strict().superRefine((recipe, context) => {
|
|
20402
20621
|
if (recipe.style.provider && recipe.style.provider !== recipe.provider) {
|
|
20403
20622
|
context.addIssue({
|
|
20404
|
-
code:
|
|
20623
|
+
code: z13.ZodIssueCode.custom,
|
|
20405
20624
|
path: ["style", "provider"],
|
|
20406
20625
|
message: "must match the recipe provider"
|
|
20407
20626
|
});
|
|
20408
20627
|
}
|
|
20409
20628
|
const filePaths = recipe.files.map((file) => file.path);
|
|
20410
20629
|
if (new Set(filePaths).size !== filePaths.length) {
|
|
20411
|
-
context.addIssue({ code:
|
|
20630
|
+
context.addIssue({ code: z13.ZodIssueCode.custom, path: ["files"], message: "duplicate file path" });
|
|
20412
20631
|
}
|
|
20413
20632
|
const modelPaths = recipe.models.map((model) => model.path);
|
|
20414
20633
|
if (new Set(modelPaths).size !== modelPaths.length) {
|
|
20415
|
-
context.addIssue({ code:
|
|
20634
|
+
context.addIssue({ code: z13.ZodIssueCode.custom, path: ["models"], message: "duplicate model path" });
|
|
20416
20635
|
}
|
|
20417
20636
|
if (filePaths.includes(RECIPE_FILE)) {
|
|
20418
20637
|
context.addIssue({
|
|
20419
|
-
code:
|
|
20638
|
+
code: z13.ZodIssueCode.custom,
|
|
20420
20639
|
path: ["files"],
|
|
20421
20640
|
message: `${RECIPE_FILE} cannot hash itself`
|
|
20422
20641
|
});
|
|
20423
20642
|
}
|
|
20424
20643
|
if (recipe.quality.recommendedNativeSize.min > recipe.quality.recommendedNativeSize.max) {
|
|
20425
20644
|
context.addIssue({
|
|
20426
|
-
code:
|
|
20645
|
+
code: z13.ZodIssueCode.custom,
|
|
20427
20646
|
path: ["quality", "recommendedNativeSize"],
|
|
20428
20647
|
message: "min cannot exceed max"
|
|
20429
20648
|
});
|
|
20430
20649
|
}
|
|
20431
20650
|
if (recipe.quality.paletteColors.min > recipe.quality.paletteColors.max) {
|
|
20432
20651
|
context.addIssue({
|
|
20433
|
-
code:
|
|
20652
|
+
code: z13.ZodIssueCode.custom,
|
|
20434
20653
|
path: ["quality", "paletteColors"],
|
|
20435
20654
|
message: "min cannot exceed max"
|
|
20436
20655
|
});
|
|
@@ -20439,7 +20658,7 @@ var RecipeSchema = z12.object({
|
|
|
20439
20658
|
(file) => file.path === recipe.workflow.path && file.role === "workflow"
|
|
20440
20659
|
)) {
|
|
20441
20660
|
context.addIssue({
|
|
20442
|
-
code:
|
|
20661
|
+
code: z13.ZodIssueCode.custom,
|
|
20443
20662
|
path: ["workflow", "path"],
|
|
20444
20663
|
message: "must name a file whose role is workflow"
|
|
20445
20664
|
});
|
|
@@ -20447,7 +20666,7 @@ var RecipeSchema = z12.object({
|
|
|
20447
20666
|
if (recipe.provider === "comfyui") {
|
|
20448
20667
|
if (!recipe.workflow) {
|
|
20449
20668
|
context.addIssue({
|
|
20450
|
-
code:
|
|
20669
|
+
code: z13.ZodIssueCode.custom,
|
|
20451
20670
|
path: ["workflow"],
|
|
20452
20671
|
message: "ComfyUI recipes require workflow metadata"
|
|
20453
20672
|
});
|
|
@@ -20456,7 +20675,7 @@ var RecipeSchema = z12.object({
|
|
|
20456
20675
|
const options = recipe.style.providerOptions.comfyui;
|
|
20457
20676
|
if (!options || typeof options !== "object" || Array.isArray(options)) {
|
|
20458
20677
|
context.addIssue({
|
|
20459
|
-
code:
|
|
20678
|
+
code: z13.ZodIssueCode.custom,
|
|
20460
20679
|
path: ["style", "providerOptions", "comfyui"],
|
|
20461
20680
|
message: "ComfyUI recipes require ComfyUI provider options"
|
|
20462
20681
|
});
|
|
@@ -20470,7 +20689,7 @@ var RecipeSchema = z12.object({
|
|
|
20470
20689
|
};
|
|
20471
20690
|
if (JSON.stringify(canonical3(options)) !== JSON.stringify(canonical3(expected))) {
|
|
20472
20691
|
context.addIssue({
|
|
20473
|
-
code:
|
|
20692
|
+
code: z13.ZodIssueCode.custom,
|
|
20474
20693
|
path: ["style", "providerOptions", "comfyui"],
|
|
20475
20694
|
message: "must match workflow metadata and use {{recipeDir}} for workflowFile"
|
|
20476
20695
|
});
|
|
@@ -20502,7 +20721,7 @@ function message5(error) {
|
|
|
20502
20721
|
return error instanceof Error ? error.message : String(error);
|
|
20503
20722
|
}
|
|
20504
20723
|
async function readRecipeFile(recipePath, bundled) {
|
|
20505
|
-
const absolute =
|
|
20724
|
+
const absolute = path45.resolve(recipePath);
|
|
20506
20725
|
let raw;
|
|
20507
20726
|
try {
|
|
20508
20727
|
raw = JSON.parse(await readFile30(absolute, "utf8"));
|
|
@@ -20516,13 +20735,13 @@ async function readRecipeFile(recipePath, bundled) {
|
|
|
20516
20735
|
` + parsed.error.issues.map((issue) => ` ${issue.path.join(".") || "$"}: ${issue.message}`).join("\n")
|
|
20517
20736
|
);
|
|
20518
20737
|
}
|
|
20519
|
-
return { recipe: parsed.data, authored: raw, path: absolute, dir:
|
|
20738
|
+
return { recipe: parsed.data, authored: raw, path: absolute, dir: path45.dirname(absolute), bundled };
|
|
20520
20739
|
}
|
|
20521
20740
|
async function walkRecipeFiles(root) {
|
|
20522
|
-
if (!
|
|
20741
|
+
if (!existsSync34(root)) return [];
|
|
20523
20742
|
const out = [];
|
|
20524
20743
|
for (const entry of await readdir3(root, { withFileTypes: true })) {
|
|
20525
|
-
const resolved =
|
|
20744
|
+
const resolved = path45.join(root, entry.name);
|
|
20526
20745
|
if (entry.isDirectory()) out.push(...await walkRecipeFiles(resolved));
|
|
20527
20746
|
else if (entry.isFile() && entry.name === RECIPE_FILE) out.push(resolved);
|
|
20528
20747
|
}
|
|
@@ -20533,7 +20752,7 @@ function bundledRecipeRoot() {
|
|
|
20533
20752
|
}
|
|
20534
20753
|
async function listBundledRecipes(root = bundledRecipeRoot()) {
|
|
20535
20754
|
const recipes = await Promise.all(
|
|
20536
|
-
(await walkRecipeFiles(
|
|
20755
|
+
(await walkRecipeFiles(path45.resolve(root))).map((file) => readRecipeFile(file, true))
|
|
20537
20756
|
);
|
|
20538
20757
|
return recipes.sort(
|
|
20539
20758
|
(a, b) => a.recipe.id.localeCompare(b.recipe.id) || compareVersions(b.recipe.version, a.recipe.version)
|
|
@@ -20556,9 +20775,9 @@ function parseSelector(selector) {
|
|
|
20556
20775
|
return { id: match[1], version: match[2] };
|
|
20557
20776
|
}
|
|
20558
20777
|
async function resolveRecipe(target, bundledRoot = bundledRecipeRoot()) {
|
|
20559
|
-
const local =
|
|
20560
|
-
if (
|
|
20561
|
-
const recipePath =
|
|
20778
|
+
const local = path45.resolve(target);
|
|
20779
|
+
if (existsSync34(local)) {
|
|
20780
|
+
const recipePath = path45.basename(local) === RECIPE_FILE ? local : path45.join(local, RECIPE_FILE);
|
|
20562
20781
|
return readRecipeFile(recipePath, false);
|
|
20563
20782
|
}
|
|
20564
20783
|
if (target.startsWith(".") || target.startsWith("/") || target.includes("\\")) {
|
|
@@ -20572,8 +20791,8 @@ async function resolveRecipe(target, bundledRoot = bundledRecipeRoot()) {
|
|
|
20572
20791
|
return matches[0];
|
|
20573
20792
|
}
|
|
20574
20793
|
async function verifyFile(root, file) {
|
|
20575
|
-
const absolute =
|
|
20576
|
-
if (!
|
|
20794
|
+
const absolute = path45.resolve(root, ...file.path.split("/"));
|
|
20795
|
+
if (!existsSync34(absolute)) {
|
|
20577
20796
|
return { path: file.path, expectedSha256: file.sha256, actualSha256: null, status: "missing" };
|
|
20578
20797
|
}
|
|
20579
20798
|
const actualSha256 = await sha256File(absolute);
|
|
@@ -20588,7 +20807,7 @@ async function verifyRecipe(target, options = {}) {
|
|
|
20588
20807
|
const loaded = await resolveRecipe(target, options.bundledRoot);
|
|
20589
20808
|
const actualIntegrity = recipeDigest(loaded.authored);
|
|
20590
20809
|
const files = await Promise.all(loaded.recipe.files.map((file) => verifyFile(loaded.dir, file)));
|
|
20591
|
-
const modelRoot = options.modelRoot ?
|
|
20810
|
+
const modelRoot = options.modelRoot ? path45.resolve(options.modelRoot) : null;
|
|
20592
20811
|
const models = modelRoot ? await Promise.all(loaded.recipe.models.map(async (model) => ({
|
|
20593
20812
|
...await verifyFile(modelRoot, model),
|
|
20594
20813
|
source: model.source,
|
|
@@ -20622,7 +20841,7 @@ async function verifyRecipe(target, options = {}) {
|
|
|
20622
20841
|
};
|
|
20623
20842
|
}
|
|
20624
20843
|
function renderStyle(style, destination, cwd) {
|
|
20625
|
-
const portableDir =
|
|
20844
|
+
const portableDir = path45.relative(cwd, destination).split(path45.sep).join("/") || ".";
|
|
20626
20845
|
const replace = (value) => {
|
|
20627
20846
|
if (typeof value === "string") return value.replaceAll(RECIPE_DIR_TOKEN, portableDir);
|
|
20628
20847
|
if (Array.isArray(value)) return value.map(replace);
|
|
@@ -20634,24 +20853,24 @@ function renderStyle(style, destination, cwd) {
|
|
|
20634
20853
|
return replace(style);
|
|
20635
20854
|
}
|
|
20636
20855
|
async function installRecipe(target, options = {}) {
|
|
20637
|
-
const cwd =
|
|
20856
|
+
const cwd = path45.resolve(options.cwd ?? process.cwd());
|
|
20638
20857
|
const loaded = await resolveRecipe(target, options.bundledRoot);
|
|
20639
20858
|
const verification = await verifyRecipe(loaded.path);
|
|
20640
20859
|
if (!verification.ok) {
|
|
20641
20860
|
throw new Error(`Recipe ${loaded.recipe.id}@${loaded.recipe.version} failed integrity verification.`);
|
|
20642
20861
|
}
|
|
20643
|
-
const destination = options.out ?
|
|
20862
|
+
const destination = options.out ? path45.resolve(cwd, options.out) : path45.join(cwd, "pixelkiln-recipes", ...loaded.recipe.id.split("/"), loaded.recipe.version);
|
|
20644
20863
|
const sources = [
|
|
20645
20864
|
{ path: RECIPE_FILE, source: loaded.path },
|
|
20646
|
-
...loaded.recipe.files.map((file) => ({ path: file.path, source:
|
|
20865
|
+
...loaded.recipe.files.map((file) => ({ path: file.path, source: path45.join(loaded.dir, ...file.path.split("/")) }))
|
|
20647
20866
|
];
|
|
20648
20867
|
const files = await Promise.all(sources.map(async (file) => ({
|
|
20649
|
-
path:
|
|
20868
|
+
path: path45.join(destination, ...file.path.split("/")),
|
|
20650
20869
|
data: await readFile30(file.source)
|
|
20651
20870
|
})));
|
|
20652
20871
|
if (!options.force) {
|
|
20653
20872
|
for (const file of files) {
|
|
20654
|
-
if (!
|
|
20873
|
+
if (!existsSync34(file.path)) continue;
|
|
20655
20874
|
const current = await readFile30(file.path);
|
|
20656
20875
|
if (!current.equals(file.data)) {
|
|
20657
20876
|
throw new OverwriteRefusedError(
|
|
@@ -20746,14 +20965,14 @@ async function runRecipe(args) {
|
|
|
20746
20965
|
}, null, 2));
|
|
20747
20966
|
} else {
|
|
20748
20967
|
log(` installed ${result.recipe.id}@${result.recipe.version}`);
|
|
20749
|
-
log(` ${
|
|
20968
|
+
log(` ${path46.relative(process.cwd(), result.destination) || "."}`);
|
|
20750
20969
|
log(`
|
|
20751
20970
|
Add this entry under your manifest's styles object:`);
|
|
20752
20971
|
log(JSON.stringify({ [result.styleId]: result.style }, null, 2));
|
|
20753
20972
|
if (result.recipe.models.length) {
|
|
20754
20973
|
log(`
|
|
20755
20974
|
Models are not downloaded automatically. Verify them with:`);
|
|
20756
|
-
log(` pixelkiln recipe verify ${
|
|
20975
|
+
log(` pixelkiln recipe verify ${path46.relative(process.cwd(), result.destination)} --model-root <ComfyUI/models>`);
|
|
20757
20976
|
}
|
|
20758
20977
|
}
|
|
20759
20978
|
return;
|
|
@@ -20761,7 +20980,7 @@ async function runRecipe(args) {
|
|
|
20761
20980
|
}
|
|
20762
20981
|
|
|
20763
20982
|
// src/cli/commands/refine.ts
|
|
20764
|
-
import
|
|
20983
|
+
import path47 from "path";
|
|
20765
20984
|
async function runRefine(args) {
|
|
20766
20985
|
if (args.from || args.subcommand === "approve") {
|
|
20767
20986
|
if (!args.from) {
|
|
@@ -20791,12 +21010,12 @@ async function runRefine(args) {
|
|
|
20791
21010
|
` recovered ${result3.detection.columns}x${result3.detection.rows} native grid (${result3.detection.confidence}: ${result3.detection.consensus})`
|
|
20792
21011
|
);
|
|
20793
21012
|
log(` enforced ${result3.palette.length}-color palette without dithering`);
|
|
20794
|
-
log(` wrote ${
|
|
20795
|
-
log(` quality record: ${
|
|
21013
|
+
log(` wrote ${path47.relative(process.cwd(), result3.output)}`);
|
|
21014
|
+
log(` quality record: ${path47.relative(process.cwd(), result3.record)}`);
|
|
20796
21015
|
log(`
|
|
20797
21016
|
Automated checks passed. Human 1\xD7 review is still required:`);
|
|
20798
21017
|
log(
|
|
20799
|
-
` pixelkiln refine approve --from ${
|
|
21018
|
+
` pixelkiln refine approve --from ${path47.relative(process.cwd(), result3.record)} --reviewer "Your Name"`
|
|
20800
21019
|
);
|
|
20801
21020
|
}
|
|
20802
21021
|
return;
|
|
@@ -20824,7 +21043,7 @@ async function runRefine(args) {
|
|
|
20824
21043
|
if (args.json) log(JSON.stringify(result3, null, 2));
|
|
20825
21044
|
else {
|
|
20826
21045
|
log(
|
|
20827
|
-
result3.options.frameSet ? ` approved ${result3.outputs.length} frames in ${
|
|
21046
|
+
result3.options.frameSet ? ` approved ${result3.outputs.length} frames in ${path47.relative(process.cwd(), result3.record)} by ${args.reviewer.trim()}` : ` approved ${path47.relative(process.cwd(), result3.output)} by ${args.reviewer.trim()}`
|
|
20828
21047
|
);
|
|
20829
21048
|
}
|
|
20830
21049
|
return;
|
|
@@ -20833,7 +21052,7 @@ async function runRefine(args) {
|
|
|
20833
21052
|
if (args.json) {
|
|
20834
21053
|
log(JSON.stringify(result2, null, 2));
|
|
20835
21054
|
} else {
|
|
20836
|
-
log(` ${result2.safe ? "release-ready" : "not release-ready"}: ${
|
|
21055
|
+
log(` ${result2.safe ? "release-ready" : "not release-ready"}: ${path47.relative(process.cwd(), result2.output || result2.record)}`);
|
|
20837
21056
|
for (const reason of result2.reasons) log(` ${reason}`);
|
|
20838
21057
|
}
|
|
20839
21058
|
if (!result2.safe) process.exitCode = 1;
|
|
@@ -20879,7 +21098,7 @@ async function runRefine(args) {
|
|
|
20879
21098
|
log(` ${item.state.padEnd(16)} ${item.key} ${item.reason}`);
|
|
20880
21099
|
if (item.state === "needs-approval") {
|
|
20881
21100
|
log(
|
|
20882
|
-
` pixelkiln refine approve --from ${
|
|
21101
|
+
` pixelkiln refine approve --from ${path47.relative(process.cwd(), item.record)} --reviewer "Your Name"`
|
|
20883
21102
|
);
|
|
20884
21103
|
}
|
|
20885
21104
|
}
|
|
@@ -20890,9 +21109,9 @@ async function runRefine(args) {
|
|
|
20890
21109
|
}
|
|
20891
21110
|
|
|
20892
21111
|
// src/cli/commands/skeleton.ts
|
|
20893
|
-
import
|
|
20894
|
-
import { existsSync as
|
|
20895
|
-
import { readFile as readFile31, writeFile as
|
|
21112
|
+
import path48 from "path";
|
|
21113
|
+
import { existsSync as existsSync35 } from "fs";
|
|
21114
|
+
import { readFile as readFile31, writeFile as writeFile17 } from "fs/promises";
|
|
20896
21115
|
|
|
20897
21116
|
// src/skeleton-preview.ts
|
|
20898
21117
|
var RIGHT = [255, 140, 40, 255];
|
|
@@ -21003,12 +21222,12 @@ var Canvas = class {
|
|
|
21003
21222
|
async function runEstimateSkeleton(args) {
|
|
21004
21223
|
const image = args.target;
|
|
21005
21224
|
if (!image) throw new UsageError("estimate-skeleton needs an image path: pixelkiln estimate-skeleton <image> [--out keypoints.json]");
|
|
21006
|
-
if (args.out &&
|
|
21225
|
+
if (args.out && existsSync35(path48.resolve(args.out)) && !args.force) {
|
|
21007
21226
|
throw new UsageError(`${args.out} already exists; pass --force to replace it`);
|
|
21008
21227
|
}
|
|
21009
|
-
loadEnvFiles(
|
|
21228
|
+
loadEnvFiles(path48.dirname(path48.resolve(args.manifest)));
|
|
21010
21229
|
loadEnvFiles(process.cwd());
|
|
21011
|
-
const bytes = await readFile31(
|
|
21230
|
+
const bytes = await readFile31(path48.resolve(image));
|
|
21012
21231
|
const metadata = imageMetadata(bytes);
|
|
21013
21232
|
if (!metadata) throw new UsageError(`${image} is not a readable PNG or JPEG`);
|
|
21014
21233
|
const sizeProblem = estimateSkeletonSizeProblem(metadata.width, metadata.height);
|
|
@@ -21020,7 +21239,7 @@ async function runEstimateSkeleton(args) {
|
|
|
21020
21239
|
const frames = args.frames ?? DEFAULT_SCAFFOLD_FRAMES;
|
|
21021
21240
|
const json = JSON.stringify(scaffoldSkeletonSet(res.keypoints, frames), null, 2);
|
|
21022
21241
|
if (args.out) {
|
|
21023
|
-
await
|
|
21242
|
+
await writeFile17(path48.resolve(args.out), json + "\n");
|
|
21024
21243
|
log(` wrote ${args.out}: the estimated pose and ${frames} frames to edit`);
|
|
21025
21244
|
log(` preview it with: pixelkiln skeleton-preview ${args.out} --from ${image}`);
|
|
21026
21245
|
} else {
|
|
@@ -21033,8 +21252,8 @@ async function runSkeletonPreview(args) {
|
|
|
21033
21252
|
let sourceFile;
|
|
21034
21253
|
let label;
|
|
21035
21254
|
if (target.endsWith(".json")) {
|
|
21036
|
-
keypointsFile =
|
|
21037
|
-
sourceFile = args.from ?
|
|
21255
|
+
keypointsFile = path48.resolve(target);
|
|
21256
|
+
sourceFile = args.from ? path48.resolve(args.from) : void 0;
|
|
21038
21257
|
label = target;
|
|
21039
21258
|
} else {
|
|
21040
21259
|
const loaded = await loadManifest(args.manifest);
|
|
@@ -21046,10 +21265,10 @@ async function runSkeletonPreview(args) {
|
|
|
21046
21265
|
);
|
|
21047
21266
|
}
|
|
21048
21267
|
keypointsFile = spec.revision.keypointsFile;
|
|
21049
|
-
sourceFile = args.from ?
|
|
21268
|
+
sourceFile = args.from ? path48.resolve(args.from) : spec.revision.sourceFile;
|
|
21050
21269
|
label = `${spec.styleId}/${spec.assetId}`;
|
|
21051
21270
|
}
|
|
21052
|
-
if (!
|
|
21271
|
+
if (!existsSync35(keypointsFile)) throw new UsageError(`Keypoints file does not exist yet: ${keypointsFile}`);
|
|
21053
21272
|
let json;
|
|
21054
21273
|
try {
|
|
21055
21274
|
json = JSON.parse(await readFile31(keypointsFile, "utf8"));
|
|
@@ -21059,7 +21278,7 @@ async function runSkeletonPreview(args) {
|
|
|
21059
21278
|
const set = parseSkeletonSet(json, keypointsFile);
|
|
21060
21279
|
let source;
|
|
21061
21280
|
if (sourceFile) {
|
|
21062
|
-
if (!
|
|
21281
|
+
if (!existsSync35(sourceFile)) {
|
|
21063
21282
|
log(` source image does not exist yet, drawing on a plain ground: ${sourceFile}`);
|
|
21064
21283
|
} else {
|
|
21065
21284
|
const bytes = await readFile31(sourceFile);
|
|
@@ -21067,10 +21286,10 @@ async function runSkeletonPreview(args) {
|
|
|
21067
21286
|
source = decodePng(bytes);
|
|
21068
21287
|
}
|
|
21069
21288
|
}
|
|
21070
|
-
const out =
|
|
21071
|
-
await
|
|
21289
|
+
const out = path48.resolve(args.out ?? keypointsFile.replace(/\.json$/, "") + ".preview.png");
|
|
21290
|
+
await writeFile17(out, renderSkeletonSheet(set, { source }));
|
|
21072
21291
|
log(` ${label}: ${set.frames.length} frames after the starting pose`);
|
|
21073
|
-
const shown =
|
|
21292
|
+
const shown = path48.relative(process.cwd(), out);
|
|
21074
21293
|
log(` wrote ${shown.startsWith("..") ? out : shown}`);
|
|
21075
21294
|
}
|
|
21076
21295
|
|
|
@@ -21116,15 +21335,15 @@ async function runTools(args) {
|
|
|
21116
21335
|
}
|
|
21117
21336
|
|
|
21118
21337
|
// src/cli/commands/workspace.ts
|
|
21119
|
-
import
|
|
21120
|
-
import { existsSync as
|
|
21338
|
+
import path49 from "path";
|
|
21339
|
+
import { existsSync as existsSync36 } from "fs";
|
|
21121
21340
|
async function runWorkspace(args) {
|
|
21122
|
-
const workspacePath =
|
|
21123
|
-
const dir =
|
|
21341
|
+
const workspacePath = path49.resolve(args.workspace ?? "pixelkiln.workspace.json");
|
|
21342
|
+
const dir = path49.dirname(workspacePath);
|
|
21124
21343
|
if (args.subcommand === "add") {
|
|
21125
|
-
const manifestPath =
|
|
21344
|
+
const manifestPath = path49.resolve(args.target);
|
|
21126
21345
|
const loadedTarget = await loadManifest(manifestPath);
|
|
21127
|
-
const lockPath = args.explicitLock ?
|
|
21346
|
+
const lockPath = args.explicitLock ? path49.resolve(args.explicitLock) : path49.join(path49.dirname(manifestPath), "pixelkiln.lock.json");
|
|
21128
21347
|
const ws = await loadWorkspace(workspacePath);
|
|
21129
21348
|
const id = args.name ?? loadedTarget.manifest.name;
|
|
21130
21349
|
if (ws.projects.some((p) => p.id === id)) {
|
|
@@ -21144,10 +21363,10 @@ async function runWorkspace(args) {
|
|
|
21144
21363
|
...args.account ? { account: args.account } : {}
|
|
21145
21364
|
};
|
|
21146
21365
|
await saveWorkspace(workspacePath, { version: 1, projects: [...ws.projects, project] });
|
|
21147
|
-
log(` registered "${id}" in ${
|
|
21366
|
+
log(` registered "${id}" in ${path49.relative(process.cwd(), workspacePath)}`);
|
|
21148
21367
|
log(` manifest: ${project.manifest}`);
|
|
21149
21368
|
log(` lock: ${project.lock}`);
|
|
21150
|
-
if (!
|
|
21369
|
+
if (!existsSync36(lockPath)) {
|
|
21151
21370
|
log(
|
|
21152
21371
|
` warning: no lockfile there yet; this project contributes no claims until one is generated`
|
|
21153
21372
|
);
|
|
@@ -21156,7 +21375,7 @@ async function runWorkspace(args) {
|
|
|
21156
21375
|
}
|
|
21157
21376
|
if (args.subcommand === "remove") {
|
|
21158
21377
|
const ws = await loadWorkspace(workspacePath);
|
|
21159
|
-
const resolvedTarget =
|
|
21378
|
+
const resolvedTarget = path49.resolve(args.target);
|
|
21160
21379
|
const match = ws.projects.find(
|
|
21161
21380
|
(p) => p.id === args.target || resolveProject(dir, p).manifestPath === resolvedTarget
|
|
21162
21381
|
);
|
|
@@ -21167,11 +21386,11 @@ async function runWorkspace(args) {
|
|
|
21167
21386
|
version: 1,
|
|
21168
21387
|
projects: ws.projects.filter((p) => p !== match)
|
|
21169
21388
|
});
|
|
21170
|
-
log(` removed "${match.id}" from ${
|
|
21389
|
+
log(` removed "${match.id}" from ${path49.relative(process.cwd(), workspacePath)}`);
|
|
21171
21390
|
return;
|
|
21172
21391
|
}
|
|
21173
21392
|
if (args.subcommand === "list") {
|
|
21174
|
-
if (!
|
|
21393
|
+
if (!existsSync36(workspacePath)) {
|
|
21175
21394
|
throw new Error(`Workspace catalog not found: ${workspacePath}`);
|
|
21176
21395
|
}
|
|
21177
21396
|
const ws = await loadWorkspace(workspacePath);
|
|
@@ -21179,9 +21398,9 @@ async function runWorkspace(args) {
|
|
|
21179
21398
|
if (args.json) {
|
|
21180
21399
|
log(JSON.stringify({ version: 1, workspace: workspacePath, projects: ws.projects, diagnostics }, null, 2));
|
|
21181
21400
|
} else if (!ws.projects.length) {
|
|
21182
|
-
log(` no projects registered in ${
|
|
21401
|
+
log(` no projects registered in ${path49.relative(process.cwd(), workspacePath)}`);
|
|
21183
21402
|
} else {
|
|
21184
|
-
log(` ${ws.projects.length} project(s) in ${
|
|
21403
|
+
log(` ${ws.projects.length} project(s) in ${path49.relative(process.cwd(), workspacePath)}:`);
|
|
21185
21404
|
for (const p of ws.projects) {
|
|
21186
21405
|
log(` ${p.id.padEnd(24)} ${p.manifest.padEnd(40)} (${p.provider}${p.account ? `, ${p.account}` : ""})`);
|
|
21187
21406
|
}
|
|
@@ -21191,7 +21410,7 @@ async function runWorkspace(args) {
|
|
|
21191
21410
|
return;
|
|
21192
21411
|
}
|
|
21193
21412
|
if (args.subcommand === "status") {
|
|
21194
|
-
if (!
|
|
21413
|
+
if (!existsSync36(workspacePath)) {
|
|
21195
21414
|
throw new Error(`Workspace catalog not found: ${workspacePath}`);
|
|
21196
21415
|
}
|
|
21197
21416
|
const ws = await loadWorkspace(workspacePath);
|
|
@@ -21199,7 +21418,7 @@ async function runWorkspace(args) {
|
|
|
21199
21418
|
if (args.json) {
|
|
21200
21419
|
log(JSON.stringify({ ...report, workspace: workspacePath }, null, 2));
|
|
21201
21420
|
} else {
|
|
21202
|
-
log(` workspace: ${
|
|
21421
|
+
log(` workspace: ${path49.relative(process.cwd(), workspacePath)}`);
|
|
21203
21422
|
for (const p of report.projects) {
|
|
21204
21423
|
if (p.error) {
|
|
21205
21424
|
log(`
|
|
@@ -21253,7 +21472,7 @@ async function runHelp() {
|
|
|
21253
21472
|
async function runVersion() {
|
|
21254
21473
|
for (const up of ["../package.json", "../../package.json"]) {
|
|
21255
21474
|
const url = new URL(up, import.meta.url);
|
|
21256
|
-
if (!
|
|
21475
|
+
if (!existsSync37(url)) continue;
|
|
21257
21476
|
const pkg = JSON.parse(await readFile32(url, "utf8"));
|
|
21258
21477
|
if (pkg.name === "pixelkiln") {
|
|
21259
21478
|
log(`${pkg.name} ${pkg.version}`);
|