pixelkiln 0.22.0 → 0.24.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.d.ts +1 -1
- package/dist/cli.js +684 -292
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +429 -84
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +238 -105
- package/dist/index.d.ts +238 -105
- package/dist/index.js +417 -72
- package/dist/index.js.map +1 -1
- package/docs/ARTIFACTS.md +10 -5
- package/docs/CLI.md +42 -5
- package/docs/MANIFEST.md +5 -2
- package/package.json +1 -1
- package/schema/manifest.schema.json +8 -0
- package/skills/pixelkiln/SKILL.md +4 -1
package/dist/cli.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
-
import
|
|
5
|
-
import { existsSync as
|
|
4
|
+
import path28 from "path";
|
|
5
|
+
import { existsSync as existsSync22 } from "fs";
|
|
6
6
|
import { readFile as readFile22 } from "fs/promises";
|
|
7
7
|
|
|
8
8
|
// src/env.ts
|
|
@@ -134,10 +134,10 @@ import { createReadStream } from "fs";
|
|
|
134
134
|
function sha256(data) {
|
|
135
135
|
return createHash("sha256").update(data).digest("hex");
|
|
136
136
|
}
|
|
137
|
-
async function sha256File(
|
|
137
|
+
async function sha256File(path29) {
|
|
138
138
|
return new Promise((resolve, reject) => {
|
|
139
139
|
const hash = createHash("sha256");
|
|
140
|
-
const stream = createReadStream(
|
|
140
|
+
const stream = createReadStream(path29);
|
|
141
141
|
stream.on("data", (chunk2) => hash.update(chunk2));
|
|
142
142
|
stream.on("error", reject);
|
|
143
143
|
stream.on("end", () => resolve(hash.digest("hex")));
|
|
@@ -1617,11 +1617,11 @@ var PixelLabClient = class {
|
|
|
1617
1617
|
* common than the failure mode of retrying (a duplicate object), and a
|
|
1618
1618
|
* duplicate is visible and free to delete whereas a silent gap is neither.
|
|
1619
1619
|
*/
|
|
1620
|
-
async request(
|
|
1620
|
+
async request(path29, init, attempt = 0) {
|
|
1621
1621
|
const auth = this.apiKey.startsWith("Bearer ") ? this.apiKey : `Bearer ${this.apiKey}`;
|
|
1622
1622
|
let res;
|
|
1623
1623
|
try {
|
|
1624
|
-
res = await fetch(`${BASE}${
|
|
1624
|
+
res = await fetch(`${BASE}${path29}`, {
|
|
1625
1625
|
...init,
|
|
1626
1626
|
signal: init?.signal ?? AbortSignal.timeout(this.timeoutMs),
|
|
1627
1627
|
headers: {
|
|
@@ -1633,24 +1633,24 @@ var PixelLabClient = class {
|
|
|
1633
1633
|
} catch (err) {
|
|
1634
1634
|
if (attempt < MAX_RETRIES) {
|
|
1635
1635
|
await sleep(backoffMs(attempt));
|
|
1636
|
-
return this.request(
|
|
1636
|
+
return this.request(path29, init, attempt + 1);
|
|
1637
1637
|
}
|
|
1638
1638
|
throw err;
|
|
1639
1639
|
}
|
|
1640
1640
|
if (!res.ok && shouldRetry(res.status) && attempt < MAX_RETRIES) {
|
|
1641
1641
|
const waitMs = retryAfterMs(res.headers.get("retry-after")) ?? backoffMs(attempt);
|
|
1642
1642
|
await sleep(waitMs);
|
|
1643
|
-
return this.request(
|
|
1643
|
+
return this.request(path29, init, attempt + 1);
|
|
1644
1644
|
}
|
|
1645
1645
|
const text = await res.text();
|
|
1646
1646
|
if (!res.ok) {
|
|
1647
|
-
throw new PixelLabError(`${init?.method ?? "GET"} ${
|
|
1647
|
+
throw new PixelLabError(`${init?.method ?? "GET"} ${path29} \u2192 ${res.status}`, res.status, text);
|
|
1648
1648
|
}
|
|
1649
1649
|
if (!text) return {};
|
|
1650
1650
|
try {
|
|
1651
1651
|
return JSON.parse(text);
|
|
1652
1652
|
} catch {
|
|
1653
|
-
throw new Error(`${init?.method ?? "GET"} ${
|
|
1653
|
+
throw new Error(`${init?.method ?? "GET"} ${path29} returned invalid JSON`);
|
|
1654
1654
|
}
|
|
1655
1655
|
}
|
|
1656
1656
|
async balance() {
|
|
@@ -2112,6 +2112,15 @@ var AssetSchema = z2.object({
|
|
|
2112
2112
|
* that wasn't. `prompt` still records what was asked for.
|
|
2113
2113
|
*/
|
|
2114
2114
|
source: z2.string().optional(),
|
|
2115
|
+
/**
|
|
2116
|
+
* Per-style replacement for `source`, keyed by style id.
|
|
2117
|
+
*
|
|
2118
|
+
* A hand edit belongs to one generation: the same asset generated in two
|
|
2119
|
+
* styles is two different pictures, so the touch-up of one must not be
|
|
2120
|
+
* placed for the other. `pixelkiln edit` writes here when an asset is in
|
|
2121
|
+
* more than one style, and to `source` when it is in exactly one.
|
|
2122
|
+
*/
|
|
2123
|
+
sourceByStyle: z2.record(z2.string().min(1)).default({}),
|
|
2115
2124
|
/** Controlled generation derived from another asset in the same style. */
|
|
2116
2125
|
revision: RevisionSchema.optional(),
|
|
2117
2126
|
/**
|
|
@@ -2146,7 +2155,7 @@ var AssetSchema = z2.object({
|
|
|
2146
2155
|
z2.array(z2.union([z2.string(), z2.number().finite(), z2.boolean()])).min(2).max(64)
|
|
2147
2156
|
])).default({})
|
|
2148
2157
|
}).strict().superRefine((asset, context) => {
|
|
2149
|
-
if (asset.source && asset.revision) {
|
|
2158
|
+
if ((asset.source || Object.keys(asset.sourceByStyle).length) && asset.revision) {
|
|
2150
2159
|
context.addIssue({
|
|
2151
2160
|
code: z2.ZodIssueCode.custom,
|
|
2152
2161
|
message: "source and revision are mutually exclusive",
|
|
@@ -2613,9 +2622,9 @@ var RetroDiffusionClient = class {
|
|
|
2613
2622
|
}
|
|
2614
2623
|
return balance;
|
|
2615
2624
|
}
|
|
2616
|
-
async call(
|
|
2625
|
+
async call(path29, init = {}) {
|
|
2617
2626
|
if (!this.token) throw new Error("RD_API_KEY is not set");
|
|
2618
|
-
const response = await this.request(`${this.baseUrl}${
|
|
2627
|
+
const response = await this.request(`${this.baseUrl}${path29}`, {
|
|
2619
2628
|
...init,
|
|
2620
2629
|
headers: {
|
|
2621
2630
|
"Content-Type": "application/json",
|
|
@@ -3784,6 +3793,11 @@ ${issues}`);
|
|
|
3784
3793
|
unknownReferences.push(`assets.${assetId}.promptByStyle: unknown style "${styleId}"`);
|
|
3785
3794
|
}
|
|
3786
3795
|
}
|
|
3796
|
+
for (const styleId of Object.keys(asset.sourceByStyle)) {
|
|
3797
|
+
if (!styleIds.has(styleId)) {
|
|
3798
|
+
unknownReferences.push(`assets.${assetId}.sourceByStyle: unknown style "${styleId}"`);
|
|
3799
|
+
}
|
|
3800
|
+
}
|
|
3787
3801
|
if (asset.revision) {
|
|
3788
3802
|
if (!parsed.data.assets[asset.revision.from]) {
|
|
3789
3803
|
unknownReferences.push(
|
|
@@ -4048,7 +4062,7 @@ async function resolveSpecs(loaded, filter) {
|
|
|
4048
4062
|
}
|
|
4049
4063
|
} : {},
|
|
4050
4064
|
tags,
|
|
4051
|
-
source: asset.source,
|
|
4065
|
+
source: asset.sourceByStyle[styleId] ?? asset.source,
|
|
4052
4066
|
// Revision identity is attached after every dependency in this style
|
|
4053
4067
|
// has a concrete output target. Finalization below computes the hash.
|
|
4054
4068
|
specHash: ""
|
|
@@ -4306,16 +4320,24 @@ function packStyle(lock, styleId, manifestDir, options = {}) {
|
|
|
4306
4320
|
}
|
|
4307
4321
|
const prefix = `${styleId}/`;
|
|
4308
4322
|
const entries = Object.entries(lock.entries).filter(([key]) => key.startsWith(prefix));
|
|
4309
|
-
|
|
4323
|
+
const sources = options.sources ?? {};
|
|
4324
|
+
if (!entries.length && !Object.keys(sources).length) {
|
|
4310
4325
|
throw new Error(
|
|
4311
4326
|
`No locked assets for style "${styleId}". Run \`pixelkiln gen --style ${styleId}\` first.`
|
|
4312
4327
|
);
|
|
4313
4328
|
}
|
|
4314
4329
|
const inputs = [];
|
|
4315
4330
|
const noOutput = [];
|
|
4331
|
+
const locked = /* @__PURE__ */ new Set();
|
|
4316
4332
|
for (const [key, entry] of entries) {
|
|
4317
4333
|
const id = key.slice(prefix.length);
|
|
4334
|
+
locked.add(id);
|
|
4318
4335
|
const outputs = resolveEntryOutputs(entry, id, manifestDir);
|
|
4336
|
+
const source = sources[id];
|
|
4337
|
+
if (source && outputs.length <= 1) {
|
|
4338
|
+
inputs.push({ id, path: path7.resolve(manifestDir, source) });
|
|
4339
|
+
continue;
|
|
4340
|
+
}
|
|
4319
4341
|
let selected = outputs;
|
|
4320
4342
|
if (options.outputRoles?.length) {
|
|
4321
4343
|
selected = outputs.filter(
|
|
@@ -4332,6 +4354,9 @@ function packStyle(lock, styleId, manifestDir, options = {}) {
|
|
|
4332
4354
|
}
|
|
4333
4355
|
for (const output of selected) inputs.push({ id: output.id, path: output.absolutePath });
|
|
4334
4356
|
}
|
|
4357
|
+
for (const [id, source] of Object.entries(sources)) {
|
|
4358
|
+
if (!locked.has(id)) inputs.push({ id, path: path7.resolve(manifestDir, source) });
|
|
4359
|
+
}
|
|
4335
4360
|
if (!inputs.length) {
|
|
4336
4361
|
throw new Error(`No readable sprites for style "${styleId}" \u2014 ${noOutput.length} skipped.`);
|
|
4337
4362
|
}
|
|
@@ -6821,10 +6846,10 @@ function isSha256Hash(value) {
|
|
|
6821
6846
|
function parseCache(value) {
|
|
6822
6847
|
return HashCacheSchema.parse(value);
|
|
6823
6848
|
}
|
|
6824
|
-
async function loadCache(
|
|
6825
|
-
if (!existsSync11(
|
|
6849
|
+
async function loadCache(path29) {
|
|
6850
|
+
if (!existsSync11(path29)) return { version: 1, hashes: {} };
|
|
6826
6851
|
try {
|
|
6827
|
-
const parsed = HashCacheSchema.safeParse(JSON.parse(await readFile8(
|
|
6852
|
+
const parsed = HashCacheSchema.safeParse(JSON.parse(await readFile8(path29, "utf8")));
|
|
6828
6853
|
if (!parsed.success) return { version: 1, hashes: {} };
|
|
6829
6854
|
return {
|
|
6830
6855
|
version: 1,
|
|
@@ -6836,18 +6861,18 @@ async function loadCache(path27) {
|
|
|
6836
6861
|
return { version: 1, hashes: {} };
|
|
6837
6862
|
}
|
|
6838
6863
|
}
|
|
6839
|
-
async function saveCache(
|
|
6864
|
+
async function saveCache(path29, cache) {
|
|
6840
6865
|
const sorted = {};
|
|
6841
6866
|
for (const key of Object.keys(cache.hashes).sort()) {
|
|
6842
6867
|
const hash = cache.hashes[key];
|
|
6843
6868
|
if (!isSha256Hash(hash)) throw new Error(`Refusing to cache invalid SHA-256 for ${key}`);
|
|
6844
6869
|
sorted[key] = hash;
|
|
6845
6870
|
}
|
|
6846
|
-
await mkdir4(pathModule.dirname(pathModule.resolve(
|
|
6847
|
-
const tmp = `${
|
|
6871
|
+
await mkdir4(pathModule.dirname(pathModule.resolve(path29)), { recursive: true });
|
|
6872
|
+
const tmp = `${path29}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
6848
6873
|
try {
|
|
6849
6874
|
await writeFile4(tmp, JSON.stringify({ version: 1, hashes: sorted }, null, 2) + "\n");
|
|
6850
|
-
await rename4(tmp,
|
|
6875
|
+
await rename4(tmp, path29);
|
|
6851
6876
|
} finally {
|
|
6852
6877
|
await rm5(tmp, { force: true });
|
|
6853
6878
|
}
|
|
@@ -7096,6 +7121,7 @@ function buildManifest(name, styleId, generator, outDir, scanned) {
|
|
|
7096
7121
|
assets[asset.id] = {
|
|
7097
7122
|
prompt: "",
|
|
7098
7123
|
promptByStyle: {},
|
|
7124
|
+
sourceByStyle: {},
|
|
7099
7125
|
category: asset.category || void 0,
|
|
7100
7126
|
file: asset.file,
|
|
7101
7127
|
tags: [],
|
|
@@ -7789,9 +7815,9 @@ async function runPicker(provider, lock, lockPath, opts = {}) {
|
|
|
7789
7815
|
|
|
7790
7816
|
// src/gallery/snapshot.ts
|
|
7791
7817
|
import { createHash as createHash3 } from "crypto";
|
|
7792
|
-
import { existsSync as
|
|
7818
|
+
import { existsSync as existsSync17 } from "fs";
|
|
7793
7819
|
import { readFile as readFile15, stat as stat3 } from "fs/promises";
|
|
7794
|
-
import
|
|
7820
|
+
import path22 from "path";
|
|
7795
7821
|
|
|
7796
7822
|
// src/workspace.ts
|
|
7797
7823
|
import { mkdir as mkdir5, readFile as readFile13, rename as rename5, rm as rm6, writeFile as writeFile7 } from "fs/promises";
|
|
@@ -7927,6 +7953,10 @@ function validateWorkspace(ws, dir) {
|
|
|
7927
7953
|
}
|
|
7928
7954
|
|
|
7929
7955
|
// src/gallery/edit.ts
|
|
7956
|
+
import path21 from "path";
|
|
7957
|
+
import { z as z6 } from "zod";
|
|
7958
|
+
|
|
7959
|
+
// src/manifest-edit.ts
|
|
7930
7960
|
import { readFile as readFile14, rename as rename6, rm as rm7, writeFile as writeFile8 } from "fs/promises";
|
|
7931
7961
|
import path19 from "path";
|
|
7932
7962
|
import { z as z5 } from "zod";
|
|
@@ -7973,6 +8003,22 @@ var ManifestEditSchema = z5.discriminatedUnion("action", [
|
|
|
7973
8003
|
expectedSha256: HexSha,
|
|
7974
8004
|
asset: NewAssetSchema
|
|
7975
8005
|
}).strict(),
|
|
8006
|
+
z5.object({
|
|
8007
|
+
action: z5.literal("set-source"),
|
|
8008
|
+
project: z5.string().min(1).optional(),
|
|
8009
|
+
assetId: z5.string().min(1),
|
|
8010
|
+
styleId: z5.string().min(1),
|
|
8011
|
+
/** Manifest-relative path of the art that stands in for this asset in this style. */
|
|
8012
|
+
source: z5.string().min(1),
|
|
8013
|
+
expectedSha256: HexSha
|
|
8014
|
+
}).strict(),
|
|
8015
|
+
z5.object({
|
|
8016
|
+
action: z5.literal("clear-source"),
|
|
8017
|
+
project: z5.string().min(1).optional(),
|
|
8018
|
+
assetId: z5.string().min(1),
|
|
8019
|
+
styleId: z5.string().min(1),
|
|
8020
|
+
expectedSha256: HexSha
|
|
8021
|
+
}).strict(),
|
|
7976
8022
|
z5.object({
|
|
7977
8023
|
action: z5.literal("patch-style"),
|
|
7978
8024
|
project: z5.string().min(1).optional(),
|
|
@@ -7985,7 +8031,11 @@ var ManifestEditSchema = z5.discriminatedUnion("action", [
|
|
|
7985
8031
|
promptPrefix: z5.string().optional(),
|
|
7986
8032
|
promptSuffix: z5.string().optional(),
|
|
7987
8033
|
/** `#rrggbb` values; null or empty clears the style's own palette. */
|
|
7988
|
-
palette: z5.array(z5.string().regex(/^#?[0-9a-f]{6}$/i, "expected a six-digit hex colour")).max(256).nullable().optional()
|
|
8034
|
+
palette: z5.array(z5.string().regex(/^#?[0-9a-f]{6}$/i, "expected a six-digit hex colour")).max(256).nullable().optional(),
|
|
8035
|
+
/** Provider view name (PixelLab: low top-down, high top-down, side); empty clears. */
|
|
8036
|
+
view: z5.string().max(64).nullable().optional(),
|
|
8037
|
+
/** pixflux only; null clears the style's own value. */
|
|
8038
|
+
noBackground: z5.boolean().nullable().optional()
|
|
7989
8039
|
}).strict().refine((patch) => Object.keys(patch).length > 0, { message: "nothing to change" })
|
|
7990
8040
|
}).strict()
|
|
7991
8041
|
]);
|
|
@@ -8019,8 +8069,38 @@ function setOrDelete(target, key, value) {
|
|
|
8019
8069
|
if (value === null || value === void 0) delete target[key];
|
|
8020
8070
|
else target[key] = value;
|
|
8021
8071
|
}
|
|
8072
|
+
function assetStyles(raw, asset) {
|
|
8073
|
+
const own = Array.isArray(asset.styles) ? asset.styles : [];
|
|
8074
|
+
return own.length ? own : Object.keys(raw.styles ?? {});
|
|
8075
|
+
}
|
|
8022
8076
|
function applyEdit(raw, edit) {
|
|
8023
8077
|
raw.assets ??= {};
|
|
8078
|
+
if (edit.action === "set-source" || edit.action === "clear-source") {
|
|
8079
|
+
const asset2 = Object.hasOwn(raw.assets, edit.assetId) ? raw.assets[edit.assetId] : void 0;
|
|
8080
|
+
if (!asset2) throw new ManifestEditError(`asset "${edit.assetId}" is not declared by the manifest`);
|
|
8081
|
+
if (!raw.styles || !Object.hasOwn(raw.styles, edit.styleId)) {
|
|
8082
|
+
throw new ManifestEditError(`unknown style "${edit.styleId}"`);
|
|
8083
|
+
}
|
|
8084
|
+
if (!assetStyles(raw, asset2).includes(edit.styleId)) {
|
|
8085
|
+
throw new ManifestEditError(`asset "${edit.assetId}" is not in style "${edit.styleId}"`);
|
|
8086
|
+
}
|
|
8087
|
+
const single = assetStyles(raw, asset2).length === 1;
|
|
8088
|
+
const byStyle = { ...asset2.sourceByStyle ?? {} };
|
|
8089
|
+
if (edit.action === "set-source") {
|
|
8090
|
+
if (single) {
|
|
8091
|
+
asset2.source = edit.source;
|
|
8092
|
+
delete byStyle[edit.styleId];
|
|
8093
|
+
} else {
|
|
8094
|
+
byStyle[edit.styleId] = edit.source;
|
|
8095
|
+
}
|
|
8096
|
+
} else {
|
|
8097
|
+
delete byStyle[edit.styleId];
|
|
8098
|
+
if (single) delete asset2.source;
|
|
8099
|
+
}
|
|
8100
|
+
if (Object.keys(byStyle).length) asset2.sourceByStyle = byStyle;
|
|
8101
|
+
else delete asset2.sourceByStyle;
|
|
8102
|
+
return;
|
|
8103
|
+
}
|
|
8024
8104
|
if (edit.action === "patch-style") {
|
|
8025
8105
|
const style = raw.styles && Object.hasOwn(raw.styles, edit.styleId) ? raw.styles[edit.styleId] : void 0;
|
|
8026
8106
|
if (!style) throw new ManifestEditError(`style "${edit.styleId}" is not declared by the manifest`);
|
|
@@ -8043,6 +8123,8 @@ function applyEdit(raw, edit) {
|
|
|
8043
8123
|
const colors = (patch2.palette ?? []).map((color) => "#" + color.replace(/^#/, "").toLowerCase());
|
|
8044
8124
|
setOrDelete(style, "palette", colors.length ? colors : null);
|
|
8045
8125
|
}
|
|
8126
|
+
if (patch2.view !== void 0) setOrDelete(style, "view", patch2.view?.trim() || null);
|
|
8127
|
+
if (patch2.noBackground !== void 0) setOrDelete(style, "noBackground", patch2.noBackground);
|
|
8046
8128
|
return;
|
|
8047
8129
|
}
|
|
8048
8130
|
if (edit.action === "add-asset") {
|
|
@@ -8122,10 +8204,134 @@ async function applyManifestEdit(manifestPath, edit) {
|
|
|
8122
8204
|
}
|
|
8123
8205
|
return { manifestPath: absolute, sha256: sha256(next), changed: true };
|
|
8124
8206
|
}
|
|
8207
|
+
|
|
8208
|
+
// src/pipeline/hand-edit.ts
|
|
8209
|
+
import { spawn as spawn2 } from "child_process";
|
|
8210
|
+
import { existsSync as existsSync16 } from "fs";
|
|
8211
|
+
import { copyFile, mkdir as mkdir6 } from "fs/promises";
|
|
8212
|
+
import path20 from "path";
|
|
8213
|
+
var HAND_EDIT_DIR = "edits";
|
|
8214
|
+
function handEditPath(loaded, spec) {
|
|
8215
|
+
const style = loaded.manifest.styles[spec.styleId];
|
|
8216
|
+
const outDir = path20.resolve(loaded.root, style?.outDir ?? ".");
|
|
8217
|
+
const relative = path20.relative(outDir, spec.outFile);
|
|
8218
|
+
const inside = relative && !relative.startsWith("..") && !path20.isAbsolute(relative);
|
|
8219
|
+
const file = inside ? relative : path20.basename(spec.outFile);
|
|
8220
|
+
return path20.join(outDir, HAND_EDIT_DIR, file.replace(/\.gif$/i, ".png"));
|
|
8221
|
+
}
|
|
8222
|
+
function handEditBase(spec, lock) {
|
|
8223
|
+
const entry = lock.entries[lockKey(spec.styleId, spec.assetId)];
|
|
8224
|
+
if (entry) {
|
|
8225
|
+
if (entry.outputs.length !== 1) return null;
|
|
8226
|
+
const primary = primaryOutput(entry);
|
|
8227
|
+
if (!primary || primary.mediaType && primary.mediaType !== "image/png") return null;
|
|
8228
|
+
const file = currentEntryOutputPath(entry, spec, 0);
|
|
8229
|
+
return existsSync16(file) ? file : null;
|
|
8230
|
+
}
|
|
8231
|
+
return existsSync16(spec.outFile) ? spec.outFile : null;
|
|
8232
|
+
}
|
|
8233
|
+
async function startHandEdit(loaded, lock, spec, opts = {}) {
|
|
8234
|
+
const entry = lock.entries[lockKey(spec.styleId, spec.assetId)];
|
|
8235
|
+
if (entry && entry.outputs.length > 1) {
|
|
8236
|
+
throw new ManifestEditError(
|
|
8237
|
+
`${spec.styleId}/${spec.assetId} has ${entry.outputs.length} outputs; hand edits currently cover single-image assets`
|
|
8238
|
+
);
|
|
8239
|
+
}
|
|
8240
|
+
const existing = spec.source ? path20.resolve(loaded.root, spec.source) : null;
|
|
8241
|
+
const editPath = existing ?? handEditPath(loaded, spec);
|
|
8242
|
+
let created = false;
|
|
8243
|
+
if (!existsSync16(editPath)) {
|
|
8244
|
+
const base = handEditBase(spec, lock);
|
|
8245
|
+
if (!base) {
|
|
8246
|
+
throw new ManifestEditError(
|
|
8247
|
+
`${spec.styleId}/${spec.assetId} has no generated PNG on disk to start from; generate or restore it first`
|
|
8248
|
+
);
|
|
8249
|
+
}
|
|
8250
|
+
await mkdir6(path20.dirname(editPath), { recursive: true });
|
|
8251
|
+
await copyFile(base, editPath);
|
|
8252
|
+
created = true;
|
|
8253
|
+
}
|
|
8254
|
+
const source = portableOutputPath(editPath, loaded.root);
|
|
8255
|
+
let declared = false;
|
|
8256
|
+
let manifestSha256 = opts.expectedSha256 ?? await sha256File(loaded.path);
|
|
8257
|
+
if (spec.source !== source) {
|
|
8258
|
+
const result = await applyManifestEdit(loaded.path, {
|
|
8259
|
+
action: "set-source",
|
|
8260
|
+
assetId: spec.assetId,
|
|
8261
|
+
styleId: spec.styleId,
|
|
8262
|
+
source,
|
|
8263
|
+
expectedSha256: manifestSha256
|
|
8264
|
+
});
|
|
8265
|
+
declared = result.changed;
|
|
8266
|
+
manifestSha256 = result.sha256;
|
|
8267
|
+
}
|
|
8268
|
+
return { editPath, source, created, declared, manifestSha256 };
|
|
8269
|
+
}
|
|
8270
|
+
async function detachHandEdit(loaded, spec, opts = {}) {
|
|
8271
|
+
if (!spec.source) {
|
|
8272
|
+
return { manifestSha256: opts.expectedSha256 ?? await sha256File(loaded.path), changed: false };
|
|
8273
|
+
}
|
|
8274
|
+
const result = await applyManifestEdit(loaded.path, {
|
|
8275
|
+
action: "clear-source",
|
|
8276
|
+
assetId: spec.assetId,
|
|
8277
|
+
styleId: spec.styleId,
|
|
8278
|
+
expectedSha256: opts.expectedSha256 ?? await sha256File(loaded.path)
|
|
8279
|
+
});
|
|
8280
|
+
return { manifestSha256: result.sha256, changed: result.changed };
|
|
8281
|
+
}
|
|
8282
|
+
function openInEditor(file, editor = process.env.PIXELKILN_EDITOR) {
|
|
8283
|
+
const command = editor?.trim() ? editor.trim().split(/\s+/) : process.platform === "darwin" ? ["open"] : process.platform === "win32" ? ["cmd", "/c", "start", ""] : ["xdg-open"];
|
|
8284
|
+
const [program, ...args] = command;
|
|
8285
|
+
const child = spawn2(program, [...args, file], { stdio: "ignore", detached: true });
|
|
8286
|
+
child.on("error", () => {
|
|
8287
|
+
});
|
|
8288
|
+
child.unref();
|
|
8289
|
+
return command.join(" ");
|
|
8290
|
+
}
|
|
8291
|
+
|
|
8292
|
+
// src/gallery/edit.ts
|
|
8293
|
+
var HandEditRequestSchema = z6.discriminatedUnion("action", [
|
|
8294
|
+
z6.object({
|
|
8295
|
+
action: z6.literal("start-edit"),
|
|
8296
|
+
project: z6.string().min(1).optional(),
|
|
8297
|
+
assetId: z6.string().min(1),
|
|
8298
|
+
styleId: z6.string().min(1),
|
|
8299
|
+
expectedSha256: z6.string().regex(/^[0-9a-f]{64}$/),
|
|
8300
|
+
/** Also launch the author's editor on the file. */
|
|
8301
|
+
open: z6.boolean().optional()
|
|
8302
|
+
}).strict(),
|
|
8303
|
+
z6.object({
|
|
8304
|
+
action: z6.literal("detach-edit"),
|
|
8305
|
+
project: z6.string().min(1).optional(),
|
|
8306
|
+
assetId: z6.string().min(1),
|
|
8307
|
+
styleId: z6.string().min(1),
|
|
8308
|
+
expectedSha256: z6.string().regex(/^[0-9a-f]{64}$/)
|
|
8309
|
+
}).strict()
|
|
8310
|
+
]);
|
|
8125
8311
|
function createGalleryEditHandler(opts) {
|
|
8126
8312
|
const log2 = opts.onProgress ?? (() => {
|
|
8127
8313
|
});
|
|
8128
8314
|
return async (body) => {
|
|
8315
|
+
const handEdit = HandEditRequestSchema.safeParse(body);
|
|
8316
|
+
if (handEdit.success) {
|
|
8317
|
+
if (!opts.loadProject) throw new ManifestEditError("hand edits are not available in this gallery");
|
|
8318
|
+
const request = handEdit.data;
|
|
8319
|
+
const ctx = await opts.loadProject(request.project);
|
|
8320
|
+
const spec = ctx.specs.find((candidate) => lockKey(candidate.styleId, candidate.assetId) === lockKey(request.styleId, request.assetId));
|
|
8321
|
+
if (!spec) throw new ManifestEditError(`"${request.styleId}/${request.assetId}" is not declared by the manifest`);
|
|
8322
|
+
if (request.action === "start-edit") {
|
|
8323
|
+
const started = await startHandEdit(ctx.loaded, ctx.lock, spec, { expectedSha256: request.expectedSha256 });
|
|
8324
|
+
log2(` hand edit ${started.created ? "created" : "found"}: ${started.source}${started.declared ? " (declared in the manifest)" : ""}`);
|
|
8325
|
+
if (request.open) {
|
|
8326
|
+
const command = (opts.openEditor ?? openInEditor)(started.editPath);
|
|
8327
|
+
log2(` opened with: ${command}`);
|
|
8328
|
+
}
|
|
8329
|
+
} else {
|
|
8330
|
+
const result2 = await detachHandEdit(ctx.loaded, spec, { expectedSha256: request.expectedSha256 });
|
|
8331
|
+
if (result2.changed) log2(` hand edit detached: ${request.styleId}/${request.assetId} (file kept)`);
|
|
8332
|
+
}
|
|
8333
|
+
return opts.reload();
|
|
8334
|
+
}
|
|
8129
8335
|
const parsed = ManifestEditSchema.safeParse(body);
|
|
8130
8336
|
if (!parsed.success) {
|
|
8131
8337
|
throw new ManifestEditError(
|
|
@@ -8140,9 +8346,9 @@ function createGalleryEditHandler(opts) {
|
|
|
8140
8346
|
}
|
|
8141
8347
|
const result = await applyManifestEdit(manifestPath, parsed.data);
|
|
8142
8348
|
if (result.changed) {
|
|
8143
|
-
const relative =
|
|
8349
|
+
const relative = path21.relative(process.cwd(), result.manifestPath);
|
|
8144
8350
|
const shown = !relative || relative.startsWith("..") ? result.manifestPath : relative;
|
|
8145
|
-
const subject = parsed.data.action === "patch-style" ? `style ${parsed.data.styleId}` : parsed.data.assetId;
|
|
8351
|
+
const subject = parsed.data.action === "patch-style" ? `style ${parsed.data.styleId}` : parsed.data.action === "set-source" || parsed.data.action === "clear-source" ? `${parsed.data.styleId}/${parsed.data.assetId} source` : parsed.data.assetId;
|
|
8146
8352
|
log2(` manifest edited: ${parsed.data.action === "add-asset" ? "added" : "changed"} ${subject} in ${shown}`);
|
|
8147
8353
|
}
|
|
8148
8354
|
return opts.reload();
|
|
@@ -8151,7 +8357,7 @@ function createGalleryEditHandler(opts) {
|
|
|
8151
8357
|
|
|
8152
8358
|
// src/gallery/snapshot.ts
|
|
8153
8359
|
function galleryMediaId(absolutePath) {
|
|
8154
|
-
return createHash3("sha256").update(
|
|
8360
|
+
return createHash3("sha256").update(path22.resolve(absolutePath)).digest("hex").slice(0, 24);
|
|
8155
8361
|
}
|
|
8156
8362
|
function galleryMediaRoute(id) {
|
|
8157
8363
|
return `/media/${id}`;
|
|
@@ -8176,23 +8382,24 @@ function metadataFps(entry) {
|
|
|
8176
8382
|
}
|
|
8177
8383
|
return null;
|
|
8178
8384
|
}
|
|
8179
|
-
async function
|
|
8385
|
+
async function fileInfo(absolutePath) {
|
|
8180
8386
|
try {
|
|
8181
8387
|
const info = await stat3(absolutePath);
|
|
8182
|
-
return info.isFile() ? info.size : null;
|
|
8388
|
+
return info.isFile() ? { bytes: info.size, modifiedAt: info.mtime.toISOString() } : null;
|
|
8183
8389
|
} catch {
|
|
8184
8390
|
return null;
|
|
8185
8391
|
}
|
|
8186
8392
|
}
|
|
8187
8393
|
async function describeOutput(media, root, absolutePath, recorded = {}) {
|
|
8188
|
-
const
|
|
8189
|
-
const exists =
|
|
8394
|
+
const info = await fileInfo(absolutePath);
|
|
8395
|
+
const exists = info !== null;
|
|
8190
8396
|
const mediaType = recorded.mediaType ?? mediaTypeFromExtension(absolutePath);
|
|
8191
8397
|
let url = null;
|
|
8192
8398
|
if (exists && mediaType) {
|
|
8193
8399
|
const id = galleryMediaId(absolutePath);
|
|
8194
8400
|
media.set(id, { path: absolutePath, contentType: mediaType });
|
|
8195
|
-
|
|
8401
|
+
const version = recorded.sha256 ?? `${info.bytes}-${Date.parse(info.modifiedAt).toString(36)}`;
|
|
8402
|
+
url = `${galleryMediaRoute(id)}?v=${version.slice(0, 24)}`;
|
|
8196
8403
|
}
|
|
8197
8404
|
return {
|
|
8198
8405
|
path: portableOutputPath(absolutePath, root),
|
|
@@ -8201,17 +8408,18 @@ async function describeOutput(media, root, absolutePath, recorded = {}) {
|
|
|
8201
8408
|
role: recorded.role ?? null,
|
|
8202
8409
|
mediaType,
|
|
8203
8410
|
exists,
|
|
8204
|
-
bytes,
|
|
8411
|
+
bytes: info?.bytes ?? null,
|
|
8412
|
+
modifiedAt: info?.modifiedAt ?? null,
|
|
8205
8413
|
url
|
|
8206
8414
|
};
|
|
8207
8415
|
}
|
|
8208
8416
|
async function describeQuality(media, root, inspection) {
|
|
8209
8417
|
const outputPaths = inspection.outputs ?? [inspection.output];
|
|
8210
8418
|
const outputs = await Promise.all(
|
|
8211
|
-
outputPaths.map((file) => describeOutput(media, root,
|
|
8419
|
+
outputPaths.map((file) => describeOutput(media, root, path22.resolve(root, file)))
|
|
8212
8420
|
);
|
|
8213
|
-
const recordPath =
|
|
8214
|
-
const recordExists =
|
|
8421
|
+
const recordPath = path22.resolve(root, inspection.record);
|
|
8422
|
+
const recordExists = existsSync17(recordPath);
|
|
8215
8423
|
let options = null;
|
|
8216
8424
|
let check = null;
|
|
8217
8425
|
if (recordExists) {
|
|
@@ -8230,7 +8438,7 @@ async function describeQuality(media, root, inspection) {
|
|
|
8230
8438
|
return {
|
|
8231
8439
|
state: inspection.state,
|
|
8232
8440
|
reason: inspection.reason,
|
|
8233
|
-
output: portableOutputPath(
|
|
8441
|
+
output: portableOutputPath(path22.resolve(root, inspection.output), root),
|
|
8234
8442
|
record: portableOutputPath(recordPath, root),
|
|
8235
8443
|
recordExists,
|
|
8236
8444
|
outputs,
|
|
@@ -8280,15 +8488,24 @@ async function buildGallerySnapshot(opts) {
|
|
|
8280
8488
|
const entry = lock.entries[key];
|
|
8281
8489
|
const asset = loaded.manifest.assets[spec.assetId] ?? null;
|
|
8282
8490
|
let outputs;
|
|
8491
|
+
let edit = null;
|
|
8492
|
+
let editStatus = null;
|
|
8283
8493
|
if (entry && entry.outputs.length) {
|
|
8284
8494
|
outputs = await Promise.all(
|
|
8285
8495
|
entry.outputs.map(
|
|
8286
8496
|
(output, index) => describeOutput(media, root, currentEntryOutputPath(entry, spec, index), output)
|
|
8287
8497
|
)
|
|
8288
8498
|
);
|
|
8499
|
+
if (spec.source) {
|
|
8500
|
+
const editPath = path22.resolve(root, spec.source);
|
|
8501
|
+
const editSha = existsSync17(editPath) ? await sha256File(editPath) : null;
|
|
8502
|
+
edit = await describeOutput(media, root, editPath, { sha256: editSha });
|
|
8503
|
+
const generated = outputs[0];
|
|
8504
|
+
editStatus = !edit.exists ? "missing" : generated && editSha === generated.sha256 ? "same" : generated?.modifiedAt && edit.modifiedAt && generated.modifiedAt > edit.modifiedAt && generated.exists ? "regenerated-since" : "edited";
|
|
8505
|
+
}
|
|
8289
8506
|
} else if (spec.source) {
|
|
8290
|
-
outputs = [await describeOutput(media, root,
|
|
8291
|
-
} else if (!entry &&
|
|
8507
|
+
outputs = [await describeOutput(media, root, path22.resolve(root, spec.source))];
|
|
8508
|
+
} else if (!entry && existsSync17(spec.outFile)) {
|
|
8292
8509
|
outputs = [await describeOutput(media, root, spec.outFile)];
|
|
8293
8510
|
} else {
|
|
8294
8511
|
outputs = [];
|
|
@@ -8332,6 +8549,8 @@ async function buildGallerySnapshot(opts) {
|
|
|
8332
8549
|
providerMetadata: entry?.providerMetadata ?? {},
|
|
8333
8550
|
asset,
|
|
8334
8551
|
source: spec.source ?? null,
|
|
8552
|
+
edit,
|
|
8553
|
+
editStatus,
|
|
8335
8554
|
tags: spec.tags,
|
|
8336
8555
|
category: asset?.category ?? null
|
|
8337
8556
|
});
|
|
@@ -8384,6 +8603,8 @@ async function buildGallerySnapshot(opts) {
|
|
|
8384
8603
|
providerMetadata: entry.providerMetadata ?? {},
|
|
8385
8604
|
asset: null,
|
|
8386
8605
|
source: null,
|
|
8606
|
+
edit: null,
|
|
8607
|
+
editStatus: null,
|
|
8387
8608
|
tags: [],
|
|
8388
8609
|
category: null
|
|
8389
8610
|
});
|
|
@@ -8422,6 +8643,8 @@ async function buildGallerySnapshot(opts) {
|
|
|
8422
8643
|
promptPrefix: style?.promptPrefix ?? "",
|
|
8423
8644
|
promptSuffix: style?.promptSuffix ?? "",
|
|
8424
8645
|
palette: style?.palette ?? [],
|
|
8646
|
+
view: style?.view ?? null,
|
|
8647
|
+
noBackground: style?.noBackground ?? true,
|
|
8425
8648
|
quality: Boolean(style?.quality),
|
|
8426
8649
|
tags: style?.tags ?? [],
|
|
8427
8650
|
extends: typeof rawStyle?.extends === "string" ? rawStyle.extends : null,
|
|
@@ -8449,7 +8672,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
8449
8672
|
id: loaded.manifest.name,
|
|
8450
8673
|
name: loaded.manifest.name,
|
|
8451
8674
|
manifest: loaded.path,
|
|
8452
|
-
lock:
|
|
8675
|
+
lock: path22.resolve(opts.lockPath),
|
|
8453
8676
|
root,
|
|
8454
8677
|
provider: loaded.manifest.provider,
|
|
8455
8678
|
account: null,
|
|
@@ -8474,7 +8697,7 @@ async function buildGallerySnapshot(opts) {
|
|
|
8474
8697
|
return { snapshot, media };
|
|
8475
8698
|
}
|
|
8476
8699
|
async function buildWorkspaceGallerySnapshot(opts) {
|
|
8477
|
-
const dir =
|
|
8700
|
+
const dir = path22.dirname(path22.resolve(opts.workspacePath));
|
|
8478
8701
|
const media = /* @__PURE__ */ new Map();
|
|
8479
8702
|
const projects = [];
|
|
8480
8703
|
const items = [];
|
|
@@ -8517,7 +8740,7 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
8517
8740
|
id: project.id,
|
|
8518
8741
|
name: loaded.manifest.name,
|
|
8519
8742
|
manifest: manifestPath,
|
|
8520
|
-
lock:
|
|
8743
|
+
lock: path22.resolve(lockPath),
|
|
8521
8744
|
root: loaded.root,
|
|
8522
8745
|
provider: loaded.manifest.provider,
|
|
8523
8746
|
account: project.account ?? null,
|
|
@@ -8532,8 +8755,8 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
8532
8755
|
id: project.id,
|
|
8533
8756
|
name: project.id,
|
|
8534
8757
|
manifest: manifestPath,
|
|
8535
|
-
lock:
|
|
8536
|
-
root:
|
|
8758
|
+
lock: path22.resolve(lockPath),
|
|
8759
|
+
root: path22.dirname(manifestPath),
|
|
8537
8760
|
provider: project.provider,
|
|
8538
8761
|
account: project.account ?? null,
|
|
8539
8762
|
manifestSha256: null,
|
|
@@ -8559,7 +8782,7 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
8559
8782
|
version: 1,
|
|
8560
8783
|
generatedAt: (opts.now ?? (() => /* @__PURE__ */ new Date()))().toISOString(),
|
|
8561
8784
|
project: null,
|
|
8562
|
-
workspace: { path:
|
|
8785
|
+
workspace: { path: path22.resolve(opts.workspacePath), dir, projects },
|
|
8563
8786
|
filter,
|
|
8564
8787
|
totals: {
|
|
8565
8788
|
entries: projects.reduce((sum, project) => sum + project.entries, 0),
|
|
@@ -8576,7 +8799,7 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
8576
8799
|
}
|
|
8577
8800
|
|
|
8578
8801
|
// src/gallery/server.ts
|
|
8579
|
-
import { spawn as
|
|
8802
|
+
import { spawn as spawn3 } from "child_process";
|
|
8580
8803
|
import { randomBytes } from "crypto";
|
|
8581
8804
|
import { createServer as createServer2 } from "http";
|
|
8582
8805
|
import { readFile as readFile16 } from "fs/promises";
|
|
@@ -8852,6 +9075,14 @@ function renderGallery(snapshot, opts = {}) {
|
|
|
8852
9075
|
.tray > button { padding:5px 11px; font-size:12.5px; }
|
|
8853
9076
|
@media (max-width: 900px) { .sheet.review-host, .sheet.compare-host { width:100vw; } }
|
|
8854
9077
|
.drawer .gen { display:flex; gap:8px; flex-wrap:wrap; margin-top:12px; }
|
|
9078
|
+
.pair { display:grid; grid-template-columns:1fr 1fr; gap:8px; margin:8px 0 10px; }
|
|
9079
|
+
.pair figure { margin:0; border:1px solid var(--line); background:var(--panel-deep); background-image:var(--checker);
|
|
9080
|
+
background-size:12px 12px; background-position:0 0,6px 6px; display:grid; place-items:center; padding:8px; min-height:96px; }
|
|
9081
|
+
.pair figure img { image-rendering:pixelated; display:block; max-width:100%; }
|
|
9082
|
+
.pair figcaption { font:11px/1.4 var(--mono); color:var(--dim); background:var(--panel); padding:2px 6px; margin-top:6px; }
|
|
9083
|
+
.card .pen { position:absolute; right:6px; top:6px; background:var(--panel); color:var(--accent-soft); border:1px solid var(--accent);
|
|
9084
|
+
font:700 11px/1 var(--mono); padding:3px 5px; }
|
|
9085
|
+
.hand-actions { display:flex; gap:8px; flex-wrap:wrap; margin-top:8px; }
|
|
8855
9086
|
.drawer .gen button.primary { padding:7px 14px; }
|
|
8856
9087
|
@media (max-width: 720px) {
|
|
8857
9088
|
.bar, .totals, .chips, .style, footer { padding-inline:14px; }
|
|
@@ -8902,6 +9133,7 @@ function renderGallery(snapshot, opts = {}) {
|
|
|
8902
9133
|
</footer>
|
|
8903
9134
|
<div id="drawer-host"></div>
|
|
8904
9135
|
<div id="dialog-host"></div>
|
|
9136
|
+
<datalist id="view-options"><option value="low top-down"><option value="high top-down"><option value="side"><option value="sidescroller"></datalist>
|
|
8905
9137
|
<script>
|
|
8906
9138
|
const INITIAL = ${data};
|
|
8907
9139
|
const SESSION = ${session};
|
|
@@ -9297,7 +9529,8 @@ function clearFilters() {
|
|
|
9297
9529
|
|
|
9298
9530
|
function thumb(item) {
|
|
9299
9531
|
const cell = el('div', 'cell');
|
|
9300
|
-
|
|
9532
|
+
// A hand edit is what ships, so it is what the card shows.
|
|
9533
|
+
const shown = item.edit && item.edit.url && item.editStatus === 'edited' ? [item.edit] : item.outputs.filter((o) => o.url);
|
|
9301
9534
|
if (!shown.length) {
|
|
9302
9535
|
cell.classList.add('none');
|
|
9303
9536
|
cell.append(el('span', null, item.state === 'missing' ? 'not generated yet'
|
|
@@ -9348,6 +9581,9 @@ function card(item) {
|
|
|
9348
9581
|
body.append(meta);
|
|
9349
9582
|
const cell = thumb(item);
|
|
9350
9583
|
if (slot >= 0) cell.append(el('span', 'slot', String(slot + 1)));
|
|
9584
|
+
if (item.editStatus === 'edited' || item.editStatus === 'regenerated-since') {
|
|
9585
|
+
const pen = el('span', 'pen', '\u270E'); pen.title = 'hand-edited'; cell.append(pen);
|
|
9586
|
+
}
|
|
9351
9587
|
c.append(cell, body);
|
|
9352
9588
|
c.onclick = (e) => { if (e.shiftKey) toggleCompare(item.id); else openItem(item.id); };
|
|
9353
9589
|
return c;
|
|
@@ -9674,6 +9910,78 @@ function openCompare() {
|
|
|
9674
9910
|
close.focus({ preventScroll: true });
|
|
9675
9911
|
}
|
|
9676
9912
|
|
|
9913
|
+
// ---- hand edits -------------------------------------------------------------
|
|
9914
|
+
|
|
9915
|
+
const EDIT_STATUS_TEXT = {
|
|
9916
|
+
same: 'A copy of the generated art, not changed yet. Open it in your editor and save.',
|
|
9917
|
+
edited: 'Differs from the generated art. mount and pack place this file; the generated file stays as the record.',
|
|
9918
|
+
'regenerated-since': 'The generated art changed after this edit was saved \u2014 the edit is based on an older generation.',
|
|
9919
|
+
missing: 'The declared file is not on disk.',
|
|
9920
|
+
};
|
|
9921
|
+
async function postHandEdit(item, action, extra) {
|
|
9922
|
+
const pr = projectOf(item);
|
|
9923
|
+
const body = { action, assetId: item.assetId, styleId: item.styleId, expectedSha256: pr.manifestSha256, ...extra };
|
|
9924
|
+
if (item.project) body.project = item.project;
|
|
9925
|
+
snap = await postEdit(body);
|
|
9926
|
+
}
|
|
9927
|
+
function handEditSection(item) {
|
|
9928
|
+
const canStart = EDITABLE && item.declared && item.asset && projectOf(item)?.manifestSha256 &&
|
|
9929
|
+
item.outputs.length === 1 && item.outputs[0].exists && item.outputs[0].mediaType === 'image/png';
|
|
9930
|
+
if (!item.edit && !canStart) return null;
|
|
9931
|
+
const s = el('section', 'meta');
|
|
9932
|
+
s.append(el('h3', null, 'Hand edit'));
|
|
9933
|
+
if (item.edit) {
|
|
9934
|
+
const pair = el('div', 'pair');
|
|
9935
|
+
const shared = displayScale(item.width, item.height, 200, 160);
|
|
9936
|
+
const fig = (out, label) => {
|
|
9937
|
+
const f = el('figure');
|
|
9938
|
+
if (out && out.url) {
|
|
9939
|
+
const img = el('img'); img.src = out.url; img.alt = label;
|
|
9940
|
+
if (shared >= 1) { img.width = item.width * shared; img.height = item.height * shared; }
|
|
9941
|
+
f.append(img);
|
|
9942
|
+
} else f.append(el('span', 'state-dim', 'not on disk'));
|
|
9943
|
+
f.append(el('figcaption', null, label));
|
|
9944
|
+
return f;
|
|
9945
|
+
};
|
|
9946
|
+
pair.append(fig(item.outputs[0], 'generated'), fig(item.edit, 'edit'));
|
|
9947
|
+
s.append(pair);
|
|
9948
|
+
const dl = el('dl');
|
|
9949
|
+
const tone = item.editStatus === 'edited' ? 'ok' : item.editStatus === 'same' ? 'dim' : 'warn';
|
|
9950
|
+
const st = el('span', 'state-' + tone); st.append(el('i', 'dot ' + tone), document.createTextNode(' ' + item.editStatus));
|
|
9951
|
+
row(dl, 'status', st);
|
|
9952
|
+
row(dl, 'why', EDIT_STATUS_TEXT[item.editStatus] || '');
|
|
9953
|
+
row(dl, 'file', item.edit.path, { mono: true, copy: item.edit.absolutePath });
|
|
9954
|
+
if (item.edit.sha256) row(dl, 'sha256', item.edit.sha256.slice(0, 16) + '\u2026', { mono: true, copy: item.edit.sha256 });
|
|
9955
|
+
row(dl, 'saved', fmtWhen(item.edit.modifiedAt));
|
|
9956
|
+
s.append(dl);
|
|
9957
|
+
} else {
|
|
9958
|
+
s.append(el('div', 'state-dim', 'Touch it up in your own editor. The generated file stays untouched; the edit is a sibling file the manifest points at, and mount and pack place that instead.'));
|
|
9959
|
+
}
|
|
9960
|
+
if (EDITABLE && item.declared && projectOf(item)?.manifestSha256) {
|
|
9961
|
+
const acts = el('div', 'hand-actions');
|
|
9962
|
+
const msg = el('span', 'msg');
|
|
9963
|
+
const go = async (label, action, extra, done) => {
|
|
9964
|
+
const b = el('button', label === 'Detach edit' ? null : 'primary', label); b.type = 'button';
|
|
9965
|
+
b.onclick = async () => {
|
|
9966
|
+
b.disabled = true; msg.className = 'msg'; msg.textContent = '\u2026';
|
|
9967
|
+
try { await postHandEdit(item, action, extra); ui.notice = { id: item.id, text: done }; render(); }
|
|
9968
|
+
catch (err) { b.disabled = false; msg.className = 'msg bad'; msg.textContent = err.message + (err.status === 409 ? ' \u2014 press Refresh.' : ''); }
|
|
9969
|
+
};
|
|
9970
|
+
acts.append(b);
|
|
9971
|
+
};
|
|
9972
|
+
if (item.edit) {
|
|
9973
|
+
go('Open in editor', 'start-edit', { open: true }, 'Opened ' + item.edit.path + ' in your editor. Save there, then Refresh.');
|
|
9974
|
+
go('Detach edit', 'detach-edit', {}, 'Detached. The file is still at ' + item.edit.path + '; the generated art is placed again.');
|
|
9975
|
+
} else if (canStart) {
|
|
9976
|
+
go('Edit by hand', 'start-edit', { open: true }, 'Created the edit file and opened it in your editor. Save there, then Refresh to see it here.');
|
|
9977
|
+
go('Create edit file only', 'start-edit', { open: false }, 'Created the edit file and declared it in the manifest.');
|
|
9978
|
+
}
|
|
9979
|
+
acts.append(msg);
|
|
9980
|
+
s.append(acts);
|
|
9981
|
+
}
|
|
9982
|
+
return s;
|
|
9983
|
+
}
|
|
9984
|
+
|
|
9677
9985
|
// ---- editing (only when the server minted a session) ---------------------
|
|
9678
9986
|
|
|
9679
9987
|
// Styles a parent edit reaches: every descendant (via extends) that does not
|
|
@@ -9693,9 +10001,18 @@ function inheritorsOf(style, field) {
|
|
|
9693
10001
|
}
|
|
9694
10002
|
return out;
|
|
9695
10003
|
}
|
|
10004
|
+
// noBackground reaches the request only for pixflux and non-PixelLab
|
|
10005
|
+
// providers; elsewhere a change is recorded but alters nothing.
|
|
10006
|
+
const fieldReaches = (field, style) =>
|
|
10007
|
+
field !== 'noBackground' || style.generator === 'pixflux' || style.provider !== 'pixellab';
|
|
9696
10008
|
function blastRadius(style, fields) {
|
|
9697
|
-
const
|
|
9698
|
-
|
|
10009
|
+
const byId = new Map(snap.styles.filter((x) => x.project === style.project).map((x) => [x.id, x]));
|
|
10010
|
+
const styleIds = new Set();
|
|
10011
|
+
for (const field of fields) {
|
|
10012
|
+
for (const id of [style.id, ...inheritorsOf(style, field)]) {
|
|
10013
|
+
if (fieldReaches(field, byId.get(id) || style)) styleIds.add(id);
|
|
10014
|
+
}
|
|
10015
|
+
}
|
|
9699
10016
|
const affected = snap.items.filter((i) => i.project === style.project && i.declared && styleIds.has(i.styleId));
|
|
9700
10017
|
const units = new Map();
|
|
9701
10018
|
for (const i of affected) if (i.estimatedCost !== null) units.set(i.costUnit, (units.get(i.costUnit) || 0) + i.estimatedCost);
|
|
@@ -9720,6 +10037,11 @@ function styleForm(style) {
|
|
|
9720
10037
|
const prefix = el('input'); prefix.type = 'text'; prefix.value = style.promptPrefix; prefix.placeholder = 'none';
|
|
9721
10038
|
const suffix = el('input'); suffix.type = 'text'; suffix.value = style.promptSuffix; suffix.placeholder = 'none';
|
|
9722
10039
|
const palette = el('input'); palette.type = 'text'; palette.value = style.palette.join(', '); palette.placeholder = '#rrggbb, #rrggbb \u2014 leave empty for no forced palette';
|
|
10040
|
+
const view = el('input'); view.type = 'text'; view.value = style.view || ''; view.placeholder = 'provider default';
|
|
10041
|
+
view.setAttribute('list', 'view-options');
|
|
10042
|
+
const noBg = el('select');
|
|
10043
|
+
noBg.append(new Option(style.extends ? 'inherit from ' + style.extends : 'default (on)', 'default'), new Option('on \u2014 strip the generated background', 'on'), new Option('off \u2014 keep the background (scenes, banners)', 'off'));
|
|
10044
|
+
noBg.value = own('noBackground') ? (style.noBackground ? 'on' : 'off') : 'default';
|
|
9723
10045
|
const swatches = el('div', 'pal');
|
|
9724
10046
|
const drawSwatches = () => {
|
|
9725
10047
|
swatches.textContent = '';
|
|
@@ -9732,12 +10054,21 @@ function styleForm(style) {
|
|
|
9732
10054
|
field('palette', palette, 'Forced colours where the provider supports them (pixflux). ' + provenance('palette')),
|
|
9733
10055
|
swatches,
|
|
9734
10056
|
);
|
|
10057
|
+
const row = el('div', 'row');
|
|
10058
|
+
row.append(
|
|
10059
|
+
field('view', view, 'PixelLab accepts low top-down, high top-down, side; Retro Diffusion reads sidescroller. ' + provenance('view')),
|
|
10060
|
+
field('background', noBg, (fieldReaches('noBackground', style) ? 'Sent for this style. ' : 'Not sent for ' + style.provider + ' ' + style.generator + '; recorded only. ') + provenance('noBackground')),
|
|
10061
|
+
);
|
|
10062
|
+
form.append(row);
|
|
9735
10063
|
const blast = el('div', 'blast');
|
|
9736
10064
|
const changed = () => {
|
|
9737
10065
|
const out = [];
|
|
9738
10066
|
if (prefix.value !== style.promptPrefix) out.push('promptPrefix');
|
|
9739
10067
|
if (suffix.value !== style.promptSuffix) out.push('promptSuffix');
|
|
9740
10068
|
if (parsePalette(palette.value).join(',') !== style.palette.map((c) => c.toLowerCase()).join(',')) out.push('palette');
|
|
10069
|
+
if (view.value.trim() !== (style.view || '')) out.push('view');
|
|
10070
|
+
const bgNow = own('noBackground') ? (style.noBackground ? 'on' : 'off') : 'default';
|
|
10071
|
+
if (noBg.value !== bgNow) out.push('noBackground');
|
|
9741
10072
|
return out;
|
|
9742
10073
|
};
|
|
9743
10074
|
const updateBlast = () => {
|
|
@@ -9745,6 +10076,11 @@ function styleForm(style) {
|
|
|
9745
10076
|
drawSwatches();
|
|
9746
10077
|
if (!fields.length) { blast.className = 'blast'; blast.textContent = 'No changes yet. A style change alters the request for every asset that uses it.'; return; }
|
|
9747
10078
|
const r = blastRadius(style, fields);
|
|
10079
|
+
if (!r.assets) {
|
|
10080
|
+
blast.className = 'blast';
|
|
10081
|
+
blast.textContent = 'Recorded in the manifest only: nothing in ' + style.id + ' sends this field, so no request changes.';
|
|
10082
|
+
return;
|
|
10083
|
+
}
|
|
9748
10084
|
blast.className = 'blast hot';
|
|
9749
10085
|
blast.textContent = '';
|
|
9750
10086
|
const others = r.styles.filter((id) => id !== style.id);
|
|
@@ -9754,7 +10090,8 @@ function styleForm(style) {
|
|
|
9754
10090
|
: ' in ' + style.id + '.'));
|
|
9755
10091
|
blast.append(document.createTextNode(' Generated ones become stale; regenerating all of them is about ' + (r.cost || 'nothing') + '. Nothing is spent until you generate.'));
|
|
9756
10092
|
};
|
|
9757
|
-
for (const input of [prefix, suffix, palette]) input.addEventListener('input', updateBlast);
|
|
10093
|
+
for (const input of [prefix, suffix, palette, view]) input.addEventListener('input', updateBlast);
|
|
10094
|
+
noBg.addEventListener('change', updateBlast);
|
|
9758
10095
|
updateBlast();
|
|
9759
10096
|
form.append(blast);
|
|
9760
10097
|
const actions = el('div', 'actions');
|
|
@@ -9773,6 +10110,8 @@ function styleForm(style) {
|
|
|
9773
10110
|
if (fields.includes('promptPrefix')) patch.promptPrefix = prefix.value;
|
|
9774
10111
|
if (fields.includes('promptSuffix')) patch.promptSuffix = suffix.value;
|
|
9775
10112
|
if (fields.includes('palette')) patch.palette = colors;
|
|
10113
|
+
if (fields.includes('view')) patch.view = view.value.trim();
|
|
10114
|
+
if (fields.includes('noBackground')) patch.noBackground = noBg.value === 'default' ? null : noBg.value === 'on';
|
|
9776
10115
|
save.disabled = true; msg.className = 'msg'; msg.textContent = 'saving\u2026';
|
|
9777
10116
|
const r = blastRadius(style, fields);
|
|
9778
10117
|
try {
|
|
@@ -9781,7 +10120,9 @@ function styleForm(style) {
|
|
|
9781
10120
|
snap = await postEdit(body);
|
|
9782
10121
|
ui.editing = null;
|
|
9783
10122
|
ui.notice = { id: 'style:' + (style.project || '') + ':' + style.id,
|
|
9784
|
-
text:
|
|
10123
|
+
text: r.assets
|
|
10124
|
+
? 'Saved. The request for ' + r.assets + (r.assets === 1 ? ' asset' : ' assets') + ' changed' + (r.cost ? ' \u2014 about ' + r.cost + ' to regenerate.' : '.') + ' Nothing is spent until you generate.'
|
|
10125
|
+
: 'Saved. Recorded in the manifest; no request changed.' };
|
|
9785
10126
|
render();
|
|
9786
10127
|
} catch (err) {
|
|
9787
10128
|
save.disabled = false;
|
|
@@ -10132,6 +10473,9 @@ function renderDrawer() {
|
|
|
10132
10473
|
body.append(s);
|
|
10133
10474
|
}
|
|
10134
10475
|
|
|
10476
|
+
const hand = handEditSection(item);
|
|
10477
|
+
if (hand) body.append(hand);
|
|
10478
|
+
|
|
10135
10479
|
if (item.revision || item.revisionParentKey) {
|
|
10136
10480
|
const { s, dl } = section('Lineage');
|
|
10137
10481
|
row(dl, 'mode', item.revision ? item.revision.mode : (item.asset && item.asset.revision && item.asset.revision.mode));
|
|
@@ -10599,7 +10943,7 @@ async function serveGallery(opts) {
|
|
|
10599
10943
|
});
|
|
10600
10944
|
opts.onReady?.(url);
|
|
10601
10945
|
if (opts.open !== false && process.platform === "darwin") {
|
|
10602
|
-
|
|
10946
|
+
spawn3("open", [url], { stdio: "ignore", detached: true }).unref();
|
|
10603
10947
|
}
|
|
10604
10948
|
return {
|
|
10605
10949
|
url,
|
|
@@ -10613,14 +10957,14 @@ async function serveGallery(opts) {
|
|
|
10613
10957
|
|
|
10614
10958
|
// src/gallery/generate.ts
|
|
10615
10959
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
10616
|
-
import { z as
|
|
10617
|
-
var GenerateRequestSchema =
|
|
10618
|
-
project:
|
|
10619
|
-
keys:
|
|
10960
|
+
import { z as z7 } from "zod";
|
|
10961
|
+
var GenerateRequestSchema = z7.object({
|
|
10962
|
+
project: z7.string().min(1).optional(),
|
|
10963
|
+
keys: z7.array(z7.string().min(1)).min(1).max(500),
|
|
10620
10964
|
/** Regenerate work that is up to date, as `gen --force` does. */
|
|
10621
|
-
force:
|
|
10965
|
+
force: z7.boolean().optional(),
|
|
10622
10966
|
/** Advance in-flight, review, or selected work without submitting anything. */
|
|
10623
|
-
resume:
|
|
10967
|
+
resume: z7.boolean().optional()
|
|
10624
10968
|
}).strict();
|
|
10625
10969
|
var GenerateRequestError = class extends Error {
|
|
10626
10970
|
constructor(message6, status = 400) {
|
|
@@ -10858,15 +11202,15 @@ function createGenerateHandlers(opts) {
|
|
|
10858
11202
|
|
|
10859
11203
|
// src/pipeline/salvage.ts
|
|
10860
11204
|
import { readFile as readFile17 } from "fs/promises";
|
|
10861
|
-
import { existsSync as
|
|
10862
|
-
import
|
|
11205
|
+
import { existsSync as existsSync18 } from "fs";
|
|
11206
|
+
import path23 from "path";
|
|
10863
11207
|
function providerClaimId(provider, objectId) {
|
|
10864
11208
|
return `${encodeURIComponent(provider)}:${encodeURIComponent(objectId)}`;
|
|
10865
11209
|
}
|
|
10866
11210
|
async function loadClaims(lockPaths, opts = {}) {
|
|
10867
11211
|
const claimed = /* @__PURE__ */ new Set();
|
|
10868
11212
|
for (const p of lockPaths) {
|
|
10869
|
-
if (!
|
|
11213
|
+
if (!existsSync18(p)) throw new Error(`Claim lockfile not found: ${p}`);
|
|
10870
11214
|
let parsed;
|
|
10871
11215
|
try {
|
|
10872
11216
|
parsed = parseLock(JSON.parse(await readFile17(p, "utf8")));
|
|
@@ -10917,20 +11261,20 @@ function matchStyleByPattern(prompt, manifest) {
|
|
|
10917
11261
|
return null;
|
|
10918
11262
|
}
|
|
10919
11263
|
async function loadSiblingManifests(ownManifestPath, workspaceManifestPaths, claimPaths) {
|
|
10920
|
-
const own =
|
|
11264
|
+
const own = path23.resolve(ownManifestPath);
|
|
10921
11265
|
const siblingManifestPaths = [
|
|
10922
11266
|
.../* @__PURE__ */ new Set([
|
|
10923
11267
|
...workspaceManifestPaths,
|
|
10924
|
-
...claimPaths.map((c) =>
|
|
11268
|
+
...claimPaths.map((c) => path23.join(path23.dirname(path23.resolve(c)), "pixelkiln.manifest.json"))
|
|
10925
11269
|
])
|
|
10926
11270
|
];
|
|
10927
11271
|
const siblings = [];
|
|
10928
11272
|
for (const siblingManifestPath of siblingManifestPaths) {
|
|
10929
|
-
if (
|
|
10930
|
-
if (!
|
|
11273
|
+
if (path23.resolve(siblingManifestPath) === own) continue;
|
|
11274
|
+
if (!existsSync18(siblingManifestPath)) continue;
|
|
10931
11275
|
try {
|
|
10932
11276
|
const { manifest } = await loadManifest(siblingManifestPath);
|
|
10933
|
-
siblings.push({ label:
|
|
11277
|
+
siblings.push({ label: path23.basename(path23.dirname(siblingManifestPath)), manifest });
|
|
10934
11278
|
} catch {
|
|
10935
11279
|
}
|
|
10936
11280
|
}
|
|
@@ -11149,15 +11493,15 @@ async function workspaceStatus(ws, dir) {
|
|
|
11149
11493
|
}
|
|
11150
11494
|
|
|
11151
11495
|
// src/pipeline/cache-health.ts
|
|
11152
|
-
import { existsSync as
|
|
11496
|
+
import { existsSync as existsSync19 } from "fs";
|
|
11153
11497
|
import { readFile as readFile18, readdir as readdir2, rm as rm8 } from "fs/promises";
|
|
11154
|
-
import
|
|
11498
|
+
import path24 from "path";
|
|
11155
11499
|
async function inspectCaches(lock, lockPath, options = {}) {
|
|
11156
|
-
if (options.prune && !
|
|
11157
|
-
throw new Error(`Refusing to prune without an existing lockfile at ${
|
|
11500
|
+
if (options.prune && !existsSync19(lockPath)) {
|
|
11501
|
+
throw new Error(`Refusing to prune without an existing lockfile at ${path24.resolve(lockPath)}`);
|
|
11158
11502
|
}
|
|
11159
|
-
const contentDir =
|
|
11160
|
-
const remotePath =
|
|
11503
|
+
const contentDir = path24.resolve(path24.dirname(lockPath), ".pixelkiln", "cache");
|
|
11504
|
+
const remotePath = path24.resolve(cachePathFor(lockPath));
|
|
11161
11505
|
const referenced = new Set(
|
|
11162
11506
|
Object.values(lock.entries).flatMap((entry) => entry.outputs.map((output) => output.sha256))
|
|
11163
11507
|
);
|
|
@@ -11171,7 +11515,7 @@ async function inspectCaches(lock, lockPath, options = {}) {
|
|
|
11171
11515
|
]);
|
|
11172
11516
|
for (const name of names) {
|
|
11173
11517
|
try {
|
|
11174
|
-
await rm8(
|
|
11518
|
+
await rm8(path24.join(contentDir, name), { force: true });
|
|
11175
11519
|
removed.contentFiles++;
|
|
11176
11520
|
} catch {
|
|
11177
11521
|
}
|
|
@@ -11198,7 +11542,7 @@ async function inspectCaches(lock, lockPath, options = {}) {
|
|
|
11198
11542
|
async function inspectContentCache(contentDir, referenced) {
|
|
11199
11543
|
const report = {
|
|
11200
11544
|
path: contentDir,
|
|
11201
|
-
exists:
|
|
11545
|
+
exists: existsSync19(contentDir),
|
|
11202
11546
|
files: 0,
|
|
11203
11547
|
bytes: 0,
|
|
11204
11548
|
valid: 0,
|
|
@@ -11219,7 +11563,7 @@ async function inspectContentCache(contentDir, referenced) {
|
|
|
11219
11563
|
continue;
|
|
11220
11564
|
}
|
|
11221
11565
|
report.files++;
|
|
11222
|
-
const file =
|
|
11566
|
+
const file = path24.join(contentDir, entry.name);
|
|
11223
11567
|
const mediaType = mediaTypeFromExtension(entry.name);
|
|
11224
11568
|
const expected = mediaType ? entry.name.slice(0, -4) : "";
|
|
11225
11569
|
let bytes;
|
|
@@ -11262,7 +11606,7 @@ async function inspectContentCache(contentDir, referenced) {
|
|
|
11262
11606
|
async function inspectRemoteHashCache(remotePath) {
|
|
11263
11607
|
const report = {
|
|
11264
11608
|
path: remotePath,
|
|
11265
|
-
exists:
|
|
11609
|
+
exists: existsSync19(remotePath),
|
|
11266
11610
|
entries: 0,
|
|
11267
11611
|
valid: 0,
|
|
11268
11612
|
invalidIds: [],
|
|
@@ -11542,8 +11886,8 @@ function isRecord2(value) {
|
|
|
11542
11886
|
}
|
|
11543
11887
|
|
|
11544
11888
|
// src/pick/salvage-server.ts
|
|
11545
|
-
import { mkdir as
|
|
11546
|
-
import
|
|
11889
|
+
import { mkdir as mkdir7, writeFile as writeFile9, readFile as readFile19 } from "fs/promises";
|
|
11890
|
+
import path25 from "path";
|
|
11547
11891
|
|
|
11548
11892
|
// src/pick/salvage-sheet.ts
|
|
11549
11893
|
var escapeHtml3 = (s) => s.replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
@@ -11708,7 +12052,7 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
11708
12052
|
});
|
|
11709
12053
|
const html = renderSalvageSheet(orphans, {
|
|
11710
12054
|
styleId: ctx.styleId,
|
|
11711
|
-
importDir:
|
|
12055
|
+
importDir: path25.relative(process.cwd(), ctx.importDir) || "."
|
|
11712
12056
|
});
|
|
11713
12057
|
const byId = new Map(orphans.map((o) => [o.id, o]));
|
|
11714
12058
|
const existingTags = new Map(orphans.map((o) => [o.id, o.tags]));
|
|
@@ -11737,13 +12081,14 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
11737
12081
|
if (!buf.subarray(0, 8).equals(PNG_SIGNATURE2)) throw new Error("not a PNG");
|
|
11738
12082
|
decodePng(buf);
|
|
11739
12083
|
const assetId = idFromPrompt(orphan.prompt, taken);
|
|
11740
|
-
const rel =
|
|
11741
|
-
const outFile =
|
|
11742
|
-
await
|
|
12084
|
+
const rel = path25.join("_salvaged", `${assetId}.png`);
|
|
12085
|
+
const outFile = path25.resolve(ctx.importDir, rel);
|
|
12086
|
+
await mkdir7(path25.dirname(outFile), { recursive: true });
|
|
11743
12087
|
await writeFile9(outFile, buf);
|
|
11744
12088
|
ctx.manifest.assets[assetId] = {
|
|
11745
12089
|
prompt: orphan.prompt,
|
|
11746
12090
|
promptByStyle: {},
|
|
12091
|
+
sourceByStyle: {},
|
|
11747
12092
|
category: "_salvaged",
|
|
11748
12093
|
file: rel,
|
|
11749
12094
|
tags: ["salvaged"],
|
|
@@ -11771,7 +12116,7 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
11771
12116
|
sourceUrl: durableSource,
|
|
11772
12117
|
sourceUrls: durableSource ? [{ url: durableSource }] : [],
|
|
11773
12118
|
outputs: [{
|
|
11774
|
-
path: portableOutputPath(outFile,
|
|
12119
|
+
path: portableOutputPath(outFile, path25.dirname(ctx.manifestPath)),
|
|
11775
12120
|
sha256: sha256(buf)
|
|
11776
12121
|
}],
|
|
11777
12122
|
submittedAt: orphan.createdAt,
|
|
@@ -11805,51 +12150,51 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
11805
12150
|
}
|
|
11806
12151
|
|
|
11807
12152
|
// src/recipes.ts
|
|
11808
|
-
import
|
|
11809
|
-
import { existsSync as
|
|
12153
|
+
import path26 from "path";
|
|
12154
|
+
import { existsSync as existsSync20 } from "fs";
|
|
11810
12155
|
import { readdir as readdir3, readFile as readFile20 } from "fs/promises";
|
|
11811
12156
|
import { fileURLToPath } from "url";
|
|
11812
|
-
import { z as
|
|
12157
|
+
import { z as z8 } from "zod";
|
|
11813
12158
|
var SHA256_RE = /^[0-9a-f]{64}$/;
|
|
11814
12159
|
var RECIPE_ID_RE = /^[a-z0-9][a-z0-9-]*\/[a-z0-9][a-z0-9-]*$/;
|
|
11815
12160
|
var VERSION_RE = /^\d+\.\d+\.\d+$/;
|
|
11816
12161
|
var RECIPE_FILE = "pixelkiln.recipe.json";
|
|
11817
12162
|
var RECIPE_DIR_TOKEN = "{{recipeDir}}";
|
|
11818
|
-
var PortablePathSchema =
|
|
11819
|
-
if (value.includes("\\") ||
|
|
12163
|
+
var PortablePathSchema = z8.string().min(1).refine((value) => {
|
|
12164
|
+
if (value.includes("\\") || path26.posix.isAbsolute(value)) return false;
|
|
11820
12165
|
const parts = value.split("/");
|
|
11821
12166
|
return !parts.includes("") && !parts.includes(".") && !parts.includes("..");
|
|
11822
12167
|
}, "expected a portable relative path without '.', '..', or backslashes");
|
|
11823
|
-
var RecipeBindingSchema =
|
|
11824
|
-
nodeId:
|
|
11825
|
-
input:
|
|
12168
|
+
var RecipeBindingSchema = z8.object({
|
|
12169
|
+
nodeId: z8.string().min(1),
|
|
12170
|
+
input: z8.string().min(1)
|
|
11826
12171
|
}).strict();
|
|
11827
|
-
var RecipeSchema =
|
|
11828
|
-
$schema:
|
|
11829
|
-
format:
|
|
11830
|
-
schemaVersion:
|
|
11831
|
-
id:
|
|
11832
|
-
version:
|
|
11833
|
-
provider:
|
|
11834
|
-
summary:
|
|
11835
|
-
files:
|
|
12172
|
+
var RecipeSchema = z8.object({
|
|
12173
|
+
$schema: z8.string().url().optional(),
|
|
12174
|
+
format: z8.literal("pixelkiln-recipe"),
|
|
12175
|
+
schemaVersion: z8.literal(1),
|
|
12176
|
+
id: z8.string().regex(RECIPE_ID_RE, "expected provider/name"),
|
|
12177
|
+
version: z8.string().regex(VERSION_RE, "expected x.y.z"),
|
|
12178
|
+
provider: z8.string().regex(/^[a-z0-9][a-z0-9-]*$/),
|
|
12179
|
+
summary: z8.string().min(1).max(240),
|
|
12180
|
+
files: z8.array(z8.object({
|
|
11836
12181
|
path: PortablePathSchema,
|
|
11837
|
-
role:
|
|
11838
|
-
sha256:
|
|
12182
|
+
role: z8.enum(["workflow", "reference"]),
|
|
12183
|
+
sha256: z8.string().regex(SHA256_RE)
|
|
11839
12184
|
}).strict()).default([]),
|
|
11840
|
-
models:
|
|
12185
|
+
models: z8.array(z8.object({
|
|
11841
12186
|
path: PortablePathSchema,
|
|
11842
|
-
sha256:
|
|
11843
|
-
source:
|
|
11844
|
-
license:
|
|
12187
|
+
sha256: z8.string().regex(SHA256_RE),
|
|
12188
|
+
source: z8.string().url(),
|
|
12189
|
+
license: z8.string().min(1)
|
|
11845
12190
|
}).strict()).default([]),
|
|
11846
|
-
styleId:
|
|
12191
|
+
styleId: z8.string().regex(/^[a-z0-9][a-z0-9-]*$/),
|
|
11847
12192
|
style: StyleSchema,
|
|
11848
|
-
workflow:
|
|
12193
|
+
workflow: z8.object({
|
|
11849
12194
|
path: PortablePathSchema,
|
|
11850
|
-
outputNodeId:
|
|
11851
|
-
numImages:
|
|
11852
|
-
bindings:
|
|
12195
|
+
outputNodeId: z8.string().min(1),
|
|
12196
|
+
numImages: z8.number().int().min(1).max(16),
|
|
12197
|
+
bindings: z8.object({
|
|
11853
12198
|
prompt: RecipeBindingSchema,
|
|
11854
12199
|
width: RecipeBindingSchema.optional(),
|
|
11855
12200
|
height: RecipeBindingSchema.optional(),
|
|
@@ -11860,67 +12205,67 @@ var RecipeSchema = z7.object({
|
|
|
11860
12205
|
strength: RecipeBindingSchema.optional()
|
|
11861
12206
|
}).strict()
|
|
11862
12207
|
}).strict().optional(),
|
|
11863
|
-
quality:
|
|
11864
|
-
stage:
|
|
11865
|
-
workingCanvas:
|
|
11866
|
-
width:
|
|
11867
|
-
height:
|
|
12208
|
+
quality: z8.object({
|
|
12209
|
+
stage: z8.enum(["composition-source", "production-candidate"]),
|
|
12210
|
+
workingCanvas: z8.object({
|
|
12211
|
+
width: z8.number().int().min(16),
|
|
12212
|
+
height: z8.number().int().min(16)
|
|
11868
12213
|
}).strict(),
|
|
11869
|
-
recommendedNativeSize:
|
|
11870
|
-
min:
|
|
11871
|
-
max:
|
|
12214
|
+
recommendedNativeSize: z8.object({
|
|
12215
|
+
min: z8.number().int().min(1),
|
|
12216
|
+
max: z8.number().int().min(1)
|
|
11872
12217
|
}).strict(),
|
|
11873
|
-
paletteColors:
|
|
11874
|
-
min:
|
|
11875
|
-
max:
|
|
12218
|
+
paletteColors: z8.object({
|
|
12219
|
+
min: z8.number().int().min(1),
|
|
12220
|
+
max: z8.number().int().min(1)
|
|
11876
12221
|
}).strict(),
|
|
11877
|
-
background:
|
|
11878
|
-
checks:
|
|
12222
|
+
background: z8.enum(["opaque", "transparent", "full-bleed"]),
|
|
12223
|
+
checks: z8.array(z8.enum([
|
|
11879
12224
|
"prompt-coverage",
|
|
11880
12225
|
"native-grid",
|
|
11881
12226
|
"final-palette",
|
|
11882
12227
|
"alpha-edge",
|
|
11883
12228
|
"human-1x"
|
|
11884
12229
|
])).min(1),
|
|
11885
|
-
notes:
|
|
12230
|
+
notes: z8.array(z8.string().min(1)).default([])
|
|
11886
12231
|
}).strict(),
|
|
11887
|
-
integrity:
|
|
11888
|
-
algorithm:
|
|
11889
|
-
digest:
|
|
12232
|
+
integrity: z8.object({
|
|
12233
|
+
algorithm: z8.literal("sha256"),
|
|
12234
|
+
digest: z8.string().regex(SHA256_RE)
|
|
11890
12235
|
}).strict()
|
|
11891
12236
|
}).strict().superRefine((recipe, context) => {
|
|
11892
12237
|
if (recipe.style.provider && recipe.style.provider !== recipe.provider) {
|
|
11893
12238
|
context.addIssue({
|
|
11894
|
-
code:
|
|
12239
|
+
code: z8.ZodIssueCode.custom,
|
|
11895
12240
|
path: ["style", "provider"],
|
|
11896
12241
|
message: "must match the recipe provider"
|
|
11897
12242
|
});
|
|
11898
12243
|
}
|
|
11899
12244
|
const filePaths = recipe.files.map((file) => file.path);
|
|
11900
12245
|
if (new Set(filePaths).size !== filePaths.length) {
|
|
11901
|
-
context.addIssue({ code:
|
|
12246
|
+
context.addIssue({ code: z8.ZodIssueCode.custom, path: ["files"], message: "duplicate file path" });
|
|
11902
12247
|
}
|
|
11903
12248
|
const modelPaths = recipe.models.map((model) => model.path);
|
|
11904
12249
|
if (new Set(modelPaths).size !== modelPaths.length) {
|
|
11905
|
-
context.addIssue({ code:
|
|
12250
|
+
context.addIssue({ code: z8.ZodIssueCode.custom, path: ["models"], message: "duplicate model path" });
|
|
11906
12251
|
}
|
|
11907
12252
|
if (filePaths.includes(RECIPE_FILE)) {
|
|
11908
12253
|
context.addIssue({
|
|
11909
|
-
code:
|
|
12254
|
+
code: z8.ZodIssueCode.custom,
|
|
11910
12255
|
path: ["files"],
|
|
11911
12256
|
message: `${RECIPE_FILE} cannot hash itself`
|
|
11912
12257
|
});
|
|
11913
12258
|
}
|
|
11914
12259
|
if (recipe.quality.recommendedNativeSize.min > recipe.quality.recommendedNativeSize.max) {
|
|
11915
12260
|
context.addIssue({
|
|
11916
|
-
code:
|
|
12261
|
+
code: z8.ZodIssueCode.custom,
|
|
11917
12262
|
path: ["quality", "recommendedNativeSize"],
|
|
11918
12263
|
message: "min cannot exceed max"
|
|
11919
12264
|
});
|
|
11920
12265
|
}
|
|
11921
12266
|
if (recipe.quality.paletteColors.min > recipe.quality.paletteColors.max) {
|
|
11922
12267
|
context.addIssue({
|
|
11923
|
-
code:
|
|
12268
|
+
code: z8.ZodIssueCode.custom,
|
|
11924
12269
|
path: ["quality", "paletteColors"],
|
|
11925
12270
|
message: "min cannot exceed max"
|
|
11926
12271
|
});
|
|
@@ -11929,7 +12274,7 @@ var RecipeSchema = z7.object({
|
|
|
11929
12274
|
(file) => file.path === recipe.workflow.path && file.role === "workflow"
|
|
11930
12275
|
)) {
|
|
11931
12276
|
context.addIssue({
|
|
11932
|
-
code:
|
|
12277
|
+
code: z8.ZodIssueCode.custom,
|
|
11933
12278
|
path: ["workflow", "path"],
|
|
11934
12279
|
message: "must name a file whose role is workflow"
|
|
11935
12280
|
});
|
|
@@ -11937,7 +12282,7 @@ var RecipeSchema = z7.object({
|
|
|
11937
12282
|
if (recipe.provider === "comfyui") {
|
|
11938
12283
|
if (!recipe.workflow) {
|
|
11939
12284
|
context.addIssue({
|
|
11940
|
-
code:
|
|
12285
|
+
code: z8.ZodIssueCode.custom,
|
|
11941
12286
|
path: ["workflow"],
|
|
11942
12287
|
message: "ComfyUI recipes require workflow metadata"
|
|
11943
12288
|
});
|
|
@@ -11946,7 +12291,7 @@ var RecipeSchema = z7.object({
|
|
|
11946
12291
|
const options = recipe.style.providerOptions.comfyui;
|
|
11947
12292
|
if (!options || typeof options !== "object" || Array.isArray(options)) {
|
|
11948
12293
|
context.addIssue({
|
|
11949
|
-
code:
|
|
12294
|
+
code: z8.ZodIssueCode.custom,
|
|
11950
12295
|
path: ["style", "providerOptions", "comfyui"],
|
|
11951
12296
|
message: "ComfyUI recipes require ComfyUI provider options"
|
|
11952
12297
|
});
|
|
@@ -11960,7 +12305,7 @@ var RecipeSchema = z7.object({
|
|
|
11960
12305
|
};
|
|
11961
12306
|
if (JSON.stringify(canonical3(options)) !== JSON.stringify(canonical3(expected))) {
|
|
11962
12307
|
context.addIssue({
|
|
11963
|
-
code:
|
|
12308
|
+
code: z8.ZodIssueCode.custom,
|
|
11964
12309
|
path: ["style", "providerOptions", "comfyui"],
|
|
11965
12310
|
message: "must match workflow metadata and use {{recipeDir}} for workflowFile"
|
|
11966
12311
|
});
|
|
@@ -11992,7 +12337,7 @@ function message4(error) {
|
|
|
11992
12337
|
return error instanceof Error ? error.message : String(error);
|
|
11993
12338
|
}
|
|
11994
12339
|
async function readRecipeFile(recipePath, bundled) {
|
|
11995
|
-
const absolute =
|
|
12340
|
+
const absolute = path26.resolve(recipePath);
|
|
11996
12341
|
let raw;
|
|
11997
12342
|
try {
|
|
11998
12343
|
raw = JSON.parse(await readFile20(absolute, "utf8"));
|
|
@@ -12006,13 +12351,13 @@ async function readRecipeFile(recipePath, bundled) {
|
|
|
12006
12351
|
` + parsed.error.issues.map((issue) => ` ${issue.path.join(".") || "$"}: ${issue.message}`).join("\n")
|
|
12007
12352
|
);
|
|
12008
12353
|
}
|
|
12009
|
-
return { recipe: parsed.data, path: absolute, dir:
|
|
12354
|
+
return { recipe: parsed.data, path: absolute, dir: path26.dirname(absolute), bundled };
|
|
12010
12355
|
}
|
|
12011
12356
|
async function walkRecipeFiles(root) {
|
|
12012
|
-
if (!
|
|
12357
|
+
if (!existsSync20(root)) return [];
|
|
12013
12358
|
const out = [];
|
|
12014
12359
|
for (const entry of await readdir3(root, { withFileTypes: true })) {
|
|
12015
|
-
const resolved =
|
|
12360
|
+
const resolved = path26.join(root, entry.name);
|
|
12016
12361
|
if (entry.isDirectory()) out.push(...await walkRecipeFiles(resolved));
|
|
12017
12362
|
else if (entry.isFile() && entry.name === RECIPE_FILE) out.push(resolved);
|
|
12018
12363
|
}
|
|
@@ -12023,7 +12368,7 @@ function bundledRecipeRoot() {
|
|
|
12023
12368
|
}
|
|
12024
12369
|
async function listBundledRecipes(root = bundledRecipeRoot()) {
|
|
12025
12370
|
const recipes = await Promise.all(
|
|
12026
|
-
(await walkRecipeFiles(
|
|
12371
|
+
(await walkRecipeFiles(path26.resolve(root))).map((file) => readRecipeFile(file, true))
|
|
12027
12372
|
);
|
|
12028
12373
|
return recipes.sort(
|
|
12029
12374
|
(a, b) => a.recipe.id.localeCompare(b.recipe.id) || compareVersions(b.recipe.version, a.recipe.version)
|
|
@@ -12046,9 +12391,9 @@ function parseSelector(selector) {
|
|
|
12046
12391
|
return { id: match[1], version: match[2] };
|
|
12047
12392
|
}
|
|
12048
12393
|
async function resolveRecipe(target, bundledRoot = bundledRecipeRoot()) {
|
|
12049
|
-
const local =
|
|
12050
|
-
if (
|
|
12051
|
-
const recipePath =
|
|
12394
|
+
const local = path26.resolve(target);
|
|
12395
|
+
if (existsSync20(local)) {
|
|
12396
|
+
const recipePath = path26.basename(local) === RECIPE_FILE ? local : path26.join(local, RECIPE_FILE);
|
|
12052
12397
|
return readRecipeFile(recipePath, false);
|
|
12053
12398
|
}
|
|
12054
12399
|
if (target.startsWith(".") || target.startsWith("/") || target.includes("\\")) {
|
|
@@ -12062,8 +12407,8 @@ async function resolveRecipe(target, bundledRoot = bundledRecipeRoot()) {
|
|
|
12062
12407
|
return matches[0];
|
|
12063
12408
|
}
|
|
12064
12409
|
async function verifyFile(root, file) {
|
|
12065
|
-
const absolute =
|
|
12066
|
-
if (!
|
|
12410
|
+
const absolute = path26.resolve(root, ...file.path.split("/"));
|
|
12411
|
+
if (!existsSync20(absolute)) {
|
|
12067
12412
|
return { path: file.path, expectedSha256: file.sha256, actualSha256: null, status: "missing" };
|
|
12068
12413
|
}
|
|
12069
12414
|
const actualSha256 = await sha256File(absolute);
|
|
@@ -12078,7 +12423,7 @@ async function verifyRecipe(target, options = {}) {
|
|
|
12078
12423
|
const loaded = await resolveRecipe(target, options.bundledRoot);
|
|
12079
12424
|
const actualIntegrity = recipeDigest(loaded.recipe);
|
|
12080
12425
|
const files = await Promise.all(loaded.recipe.files.map((file) => verifyFile(loaded.dir, file)));
|
|
12081
|
-
const modelRoot = options.modelRoot ?
|
|
12426
|
+
const modelRoot = options.modelRoot ? path26.resolve(options.modelRoot) : null;
|
|
12082
12427
|
const models = modelRoot ? await Promise.all(loaded.recipe.models.map(async (model) => ({
|
|
12083
12428
|
...await verifyFile(modelRoot, model),
|
|
12084
12429
|
source: model.source,
|
|
@@ -12112,7 +12457,7 @@ async function verifyRecipe(target, options = {}) {
|
|
|
12112
12457
|
};
|
|
12113
12458
|
}
|
|
12114
12459
|
function renderStyle(style, destination, cwd) {
|
|
12115
|
-
const portableDir =
|
|
12460
|
+
const portableDir = path26.relative(cwd, destination).split(path26.sep).join("/") || ".";
|
|
12116
12461
|
const replace = (value) => {
|
|
12117
12462
|
if (typeof value === "string") return value.replaceAll(RECIPE_DIR_TOKEN, portableDir);
|
|
12118
12463
|
if (Array.isArray(value)) return value.map(replace);
|
|
@@ -12124,24 +12469,24 @@ function renderStyle(style, destination, cwd) {
|
|
|
12124
12469
|
return replace(style);
|
|
12125
12470
|
}
|
|
12126
12471
|
async function installRecipe(target, options = {}) {
|
|
12127
|
-
const cwd =
|
|
12472
|
+
const cwd = path26.resolve(options.cwd ?? process.cwd());
|
|
12128
12473
|
const loaded = await resolveRecipe(target, options.bundledRoot);
|
|
12129
12474
|
const verification = await verifyRecipe(loaded.path);
|
|
12130
12475
|
if (!verification.ok) {
|
|
12131
12476
|
throw new Error(`Recipe ${loaded.recipe.id}@${loaded.recipe.version} failed integrity verification.`);
|
|
12132
12477
|
}
|
|
12133
|
-
const destination = options.out ?
|
|
12478
|
+
const destination = options.out ? path26.resolve(cwd, options.out) : path26.join(cwd, "pixelkiln-recipes", ...loaded.recipe.id.split("/"), loaded.recipe.version);
|
|
12134
12479
|
const sources = [
|
|
12135
12480
|
{ path: RECIPE_FILE, source: loaded.path },
|
|
12136
|
-
...loaded.recipe.files.map((file) => ({ path: file.path, source:
|
|
12481
|
+
...loaded.recipe.files.map((file) => ({ path: file.path, source: path26.join(loaded.dir, ...file.path.split("/")) }))
|
|
12137
12482
|
];
|
|
12138
12483
|
const files = await Promise.all(sources.map(async (file) => ({
|
|
12139
|
-
path:
|
|
12484
|
+
path: path26.join(destination, ...file.path.split("/")),
|
|
12140
12485
|
data: await readFile20(file.source)
|
|
12141
12486
|
})));
|
|
12142
12487
|
if (!options.force) {
|
|
12143
12488
|
for (const file of files) {
|
|
12144
|
-
if (!
|
|
12489
|
+
if (!existsSync20(file.path)) continue;
|
|
12145
12490
|
const current = await readFile20(file.path);
|
|
12146
12491
|
if (!current.equals(file.data)) {
|
|
12147
12492
|
throw new Error(
|
|
@@ -12165,15 +12510,15 @@ async function installRecipe(target, options = {}) {
|
|
|
12165
12510
|
}
|
|
12166
12511
|
|
|
12167
12512
|
// src/pipeline/quality-regression.ts
|
|
12168
|
-
import
|
|
12169
|
-
import { existsSync as
|
|
12513
|
+
import path27 from "path";
|
|
12514
|
+
import { existsSync as existsSync21 } from "fs";
|
|
12170
12515
|
import { readFile as readFile21 } from "fs/promises";
|
|
12171
|
-
import { z as
|
|
12516
|
+
import { z as z9 } from "zod";
|
|
12172
12517
|
var SHA256_RE2 = /^[0-9a-f]{64}$/;
|
|
12173
12518
|
var HEX_RE = /^#[0-9a-f]{6}$/;
|
|
12174
12519
|
var MAX_REDMEAN_DISTANCE = 765;
|
|
12175
|
-
var QualityPathSchema =
|
|
12176
|
-
(value) => !
|
|
12520
|
+
var QualityPathSchema = z9.string().min(1).refine(
|
|
12521
|
+
(value) => !path27.posix.isAbsolute(value) && !value.includes("\\"),
|
|
12177
12522
|
"expected a portable relative path"
|
|
12178
12523
|
);
|
|
12179
12524
|
var DEFAULT_QUALITY_TOLERANCES = {
|
|
@@ -12186,53 +12531,53 @@ var DEFAULT_QUALITY_TOLERANCES = {
|
|
|
12186
12531
|
maxEdgeContrastDrop: 0.03,
|
|
12187
12532
|
maxIsolatedPixelIncrease: 5e-3
|
|
12188
12533
|
};
|
|
12189
|
-
var QualityToleranceSchema =
|
|
12190
|
-
requireExactHash:
|
|
12191
|
-
maxColorCountIncrease:
|
|
12192
|
-
maxNewColors:
|
|
12193
|
-
maxTransparencyDelta:
|
|
12194
|
-
maxPartialAlphaIncrease:
|
|
12195
|
-
maxEdgeDensityDelta:
|
|
12196
|
-
maxEdgeContrastDrop:
|
|
12197
|
-
maxIsolatedPixelIncrease:
|
|
12534
|
+
var QualityToleranceSchema = z9.object({
|
|
12535
|
+
requireExactHash: z9.boolean(),
|
|
12536
|
+
maxColorCountIncrease: z9.number().int().min(0),
|
|
12537
|
+
maxNewColors: z9.number().int().min(0),
|
|
12538
|
+
maxTransparencyDelta: z9.number().min(0).max(1),
|
|
12539
|
+
maxPartialAlphaIncrease: z9.number().min(0).max(1),
|
|
12540
|
+
maxEdgeDensityDelta: z9.number().min(0).max(1),
|
|
12541
|
+
maxEdgeContrastDrop: z9.number().min(0).max(1),
|
|
12542
|
+
maxIsolatedPixelIncrease: z9.number().min(0).max(1)
|
|
12198
12543
|
}).strict();
|
|
12199
|
-
var ImageQualityMetricsSchema =
|
|
12200
|
-
sha256:
|
|
12201
|
-
width:
|
|
12202
|
-
height:
|
|
12203
|
-
colorCount:
|
|
12204
|
-
colors:
|
|
12205
|
-
transparency:
|
|
12206
|
-
partialAlpha:
|
|
12207
|
-
edgeDensity:
|
|
12208
|
-
meanEdgeContrast:
|
|
12209
|
-
isolatedPixelRatio:
|
|
12544
|
+
var ImageQualityMetricsSchema = z9.object({
|
|
12545
|
+
sha256: z9.string().regex(SHA256_RE2),
|
|
12546
|
+
width: z9.number().int().min(1),
|
|
12547
|
+
height: z9.number().int().min(1),
|
|
12548
|
+
colorCount: z9.number().int().min(0),
|
|
12549
|
+
colors: z9.array(z9.string().regex(HEX_RE)),
|
|
12550
|
+
transparency: z9.number().min(0).max(1),
|
|
12551
|
+
partialAlpha: z9.number().min(0).max(1),
|
|
12552
|
+
edgeDensity: z9.number().min(0).max(1),
|
|
12553
|
+
meanEdgeContrast: z9.number().min(0).max(1),
|
|
12554
|
+
isolatedPixelRatio: z9.number().min(0).max(1)
|
|
12210
12555
|
}).strict();
|
|
12211
|
-
var QualityBaselineCaseSchema =
|
|
12212
|
-
id:
|
|
12556
|
+
var QualityBaselineCaseSchema = z9.object({
|
|
12557
|
+
id: z9.string().regex(/^[a-z0-9][a-z0-9/_-]*$/),
|
|
12213
12558
|
file: QualityPathSchema,
|
|
12214
|
-
record:
|
|
12559
|
+
record: z9.object({
|
|
12215
12560
|
path: QualityPathSchema,
|
|
12216
|
-
sha256:
|
|
12561
|
+
sha256: z9.string().regex(SHA256_RE2)
|
|
12217
12562
|
}).strict().optional(),
|
|
12218
12563
|
expected: ImageQualityMetricsSchema,
|
|
12219
12564
|
tolerances: QualityToleranceSchema
|
|
12220
12565
|
}).strict();
|
|
12221
|
-
var QualityBaselineSchema =
|
|
12222
|
-
$schema:
|
|
12223
|
-
format:
|
|
12224
|
-
schemaVersion:
|
|
12225
|
-
cases:
|
|
12566
|
+
var QualityBaselineSchema = z9.object({
|
|
12567
|
+
$schema: z9.string().url().optional(),
|
|
12568
|
+
format: z9.literal("pixelkiln-quality-baseline"),
|
|
12569
|
+
schemaVersion: z9.literal(1),
|
|
12570
|
+
cases: z9.array(QualityBaselineCaseSchema).min(1)
|
|
12226
12571
|
}).strict().superRefine((baseline, context) => {
|
|
12227
12572
|
const ids = baseline.cases.map((entry) => entry.id);
|
|
12228
12573
|
if (new Set(ids).size !== ids.length) {
|
|
12229
|
-
context.addIssue({ code:
|
|
12574
|
+
context.addIssue({ code: z9.ZodIssueCode.custom, path: ["cases"], message: "duplicate case id" });
|
|
12230
12575
|
}
|
|
12231
12576
|
});
|
|
12232
|
-
var QualityInputSchema =
|
|
12233
|
-
id:
|
|
12234
|
-
path:
|
|
12235
|
-
record:
|
|
12577
|
+
var QualityInputSchema = z9.object({
|
|
12578
|
+
id: z9.string().regex(/^[a-z0-9][a-z0-9/_-]*$/),
|
|
12579
|
+
path: z9.string().min(1),
|
|
12580
|
+
record: z9.string().min(1).optional(),
|
|
12236
12581
|
tolerances: QualityToleranceSchema.partial().optional()
|
|
12237
12582
|
}).strict();
|
|
12238
12583
|
function message5(error) {
|
|
@@ -12320,7 +12665,7 @@ function measureDecodedImageQuality(png, digest2) {
|
|
|
12320
12665
|
};
|
|
12321
12666
|
}
|
|
12322
12667
|
async function measureImageQuality(file) {
|
|
12323
|
-
const absolute =
|
|
12668
|
+
const absolute = path27.resolve(file);
|
|
12324
12669
|
let bytes;
|
|
12325
12670
|
try {
|
|
12326
12671
|
bytes = await readFile21(absolute);
|
|
@@ -12334,7 +12679,7 @@ async function measureImageQuality(file) {
|
|
|
12334
12679
|
}
|
|
12335
12680
|
}
|
|
12336
12681
|
function resolveQualityInputs(raw, inputsFilePath) {
|
|
12337
|
-
const parsed =
|
|
12682
|
+
const parsed = z9.array(QualityInputSchema).min(1).safeParse(raw);
|
|
12338
12683
|
if (!parsed.success) {
|
|
12339
12684
|
throw new Error(
|
|
12340
12685
|
"--inputs must be a non-empty JSON array of quality cases:\n" + parsed.error.issues.map((issue) => ` ${issue.path.join(".") || "$"}: ${issue.message}`).join("\n")
|
|
@@ -12342,11 +12687,11 @@ function resolveQualityInputs(raw, inputsFilePath) {
|
|
|
12342
12687
|
}
|
|
12343
12688
|
const ids = parsed.data.map((entry) => entry.id);
|
|
12344
12689
|
if (new Set(ids).size !== ids.length) throw new Error("--inputs contains a duplicate quality case id.");
|
|
12345
|
-
const root =
|
|
12690
|
+
const root = path27.dirname(path27.resolve(inputsFilePath));
|
|
12346
12691
|
return parsed.data.map((entry) => ({
|
|
12347
12692
|
id: entry.id,
|
|
12348
|
-
file:
|
|
12349
|
-
...entry.record ? { record:
|
|
12693
|
+
file: path27.resolve(root, entry.path),
|
|
12694
|
+
...entry.record ? { record: path27.resolve(root, entry.record) } : {},
|
|
12350
12695
|
tolerances: QualityToleranceSchema.parse({
|
|
12351
12696
|
...DEFAULT_QUALITY_TOLERANCES,
|
|
12352
12697
|
...entry.tolerances
|
|
@@ -12354,7 +12699,7 @@ function resolveQualityInputs(raw, inputsFilePath) {
|
|
|
12354
12699
|
}));
|
|
12355
12700
|
}
|
|
12356
12701
|
function portableRelative2(from, to) {
|
|
12357
|
-
return
|
|
12702
|
+
return path27.relative(from, path27.resolve(to)).split(path27.sep).join("/") || ".";
|
|
12358
12703
|
}
|
|
12359
12704
|
async function verifyRecordForImage(recordPath, imagePath) {
|
|
12360
12705
|
const verification = await verifyArtifactBundle(recordPath);
|
|
@@ -12369,20 +12714,20 @@ async function verifyRecordForImage(recordPath, imagePath) {
|
|
|
12369
12714
|
if (record.kind !== "refine" || options === null || typeof options !== "object" || options.schema !== "pixelkiln-quality") {
|
|
12370
12715
|
throw new Error(`Quality record ${recordPath} is not a PixelKiln refinement record.`);
|
|
12371
12716
|
}
|
|
12372
|
-
const outputs = record.outputs.map((output) =>
|
|
12373
|
-
if (!outputs.includes(
|
|
12717
|
+
const outputs = record.outputs.map((output) => path27.resolve(path27.dirname(recordPath), output.path));
|
|
12718
|
+
if (!outputs.includes(path27.resolve(imagePath))) {
|
|
12374
12719
|
throw new Error(`Quality record ${recordPath} does not own ${imagePath}.`);
|
|
12375
12720
|
}
|
|
12376
12721
|
}
|
|
12377
12722
|
async function snapshotQualityBaseline(inputs, baselinePath, options = {}) {
|
|
12378
12723
|
if (!inputs.length) throw new Error("Cannot snapshot an empty quality baseline.");
|
|
12379
|
-
const absolute =
|
|
12724
|
+
const absolute = path27.resolve(baselinePath);
|
|
12380
12725
|
for (const input of inputs) {
|
|
12381
|
-
if (
|
|
12726
|
+
if (path27.resolve(input.file) === absolute || input.record && path27.resolve(input.record) === absolute) {
|
|
12382
12727
|
throw new Error(`Quality baseline output would overwrite an input: ${absolute}.`);
|
|
12383
12728
|
}
|
|
12384
12729
|
}
|
|
12385
|
-
const root =
|
|
12730
|
+
const root = path27.dirname(absolute);
|
|
12386
12731
|
const cases = [];
|
|
12387
12732
|
for (const input of [...inputs].sort((a, b) => a.id.localeCompare(b.id))) {
|
|
12388
12733
|
const expected = await measureImageQuality(input.file);
|
|
@@ -12409,7 +12754,7 @@ async function snapshotQualityBaseline(inputs, baselinePath, options = {}) {
|
|
|
12409
12754
|
cases
|
|
12410
12755
|
});
|
|
12411
12756
|
const data = Buffer.from(JSON.stringify(baseline, null, 2) + "\n");
|
|
12412
|
-
if (
|
|
12757
|
+
if (existsSync21(absolute) && !options.force) {
|
|
12413
12758
|
const current = await readFile21(absolute);
|
|
12414
12759
|
if (!current.equals(data)) {
|
|
12415
12760
|
throw new Error(`Quality baseline already exists with different content: ${absolute}. Pass --force to replace it.`);
|
|
@@ -12419,7 +12764,7 @@ async function snapshotQualityBaseline(inputs, baselinePath, options = {}) {
|
|
|
12419
12764
|
return { baseline, path: absolute, changed: result.changed.length > 0 };
|
|
12420
12765
|
}
|
|
12421
12766
|
async function readQualityBaseline(baselinePath) {
|
|
12422
|
-
const absolute =
|
|
12767
|
+
const absolute = path27.resolve(baselinePath);
|
|
12423
12768
|
let raw;
|
|
12424
12769
|
try {
|
|
12425
12770
|
raw = JSON.parse(await readFile21(absolute, "utf8"));
|
|
@@ -12492,12 +12837,12 @@ function regressionViolations(expected, actual, tolerances) {
|
|
|
12492
12837
|
return { violations, warnings };
|
|
12493
12838
|
}
|
|
12494
12839
|
async function checkQualityBaseline(baselinePath) {
|
|
12495
|
-
const absolute =
|
|
12840
|
+
const absolute = path27.resolve(baselinePath);
|
|
12496
12841
|
const baseline = await readQualityBaseline(absolute);
|
|
12497
|
-
const root =
|
|
12842
|
+
const root = path27.dirname(absolute);
|
|
12498
12843
|
const cases = [];
|
|
12499
12844
|
for (const entry of baseline.cases) {
|
|
12500
|
-
const file =
|
|
12845
|
+
const file = path27.resolve(root, entry.file);
|
|
12501
12846
|
let actual = null;
|
|
12502
12847
|
const violations = [];
|
|
12503
12848
|
const warnings = [];
|
|
@@ -12510,7 +12855,7 @@ async function checkQualityBaseline(baselinePath) {
|
|
|
12510
12855
|
violations.push(message5(error));
|
|
12511
12856
|
}
|
|
12512
12857
|
if (entry.record) {
|
|
12513
|
-
const recordPath =
|
|
12858
|
+
const recordPath = path27.resolve(root, entry.record.path);
|
|
12514
12859
|
try {
|
|
12515
12860
|
const recordHash = await sha256File(recordPath);
|
|
12516
12861
|
if (recordHash !== entry.record.sha256) violations.push("quality record hash changed");
|
|
@@ -12596,13 +12941,13 @@ async function runGallery(initial, reload, args, access2) {
|
|
|
12596
12941
|
};
|
|
12597
12942
|
const loadProject = async (project) => {
|
|
12598
12943
|
const ctx = await access2.loadProject(project);
|
|
12599
|
-
const dir =
|
|
12944
|
+
const dir = path28.dirname(ctx.loaded.path);
|
|
12600
12945
|
const declared = readEnvFiles(dir);
|
|
12601
12946
|
const credentialNames = new Set(availableProviders().flatMap((id) => providerCredentialEnvs(providerFactory(id))));
|
|
12602
12947
|
for (const [name, value] of Object.entries(declared)) {
|
|
12603
12948
|
if (credentialNames.has(name) && name in process.env && process.env[name] !== value) {
|
|
12604
12949
|
throw new Error(
|
|
12605
|
-
`${
|
|
12950
|
+
`${path28.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.`
|
|
12606
12951
|
);
|
|
12607
12952
|
}
|
|
12608
12953
|
}
|
|
@@ -12628,7 +12973,7 @@ async function runGallery(initial, reload, args, access2) {
|
|
|
12628
12973
|
args.edit,
|
|
12629
12974
|
budget ? describeBudget(budget) : null
|
|
12630
12975
|
),
|
|
12631
|
-
...args.edit ? { edit: createGalleryEditHandler({ manifestFor: access2.manifestFor, reload, onProgress: log }) } : {},
|
|
12976
|
+
...args.edit ? { edit: createGalleryEditHandler({ manifestFor: access2.manifestFor, loadProject: access2.loadProject, reload, onProgress: log }) } : {},
|
|
12632
12977
|
...budget ? { generate: createGenerateHandlers({ loadProject, providerFor, budget, reload, onProgress: log }) } : {}
|
|
12633
12978
|
});
|
|
12634
12979
|
await new Promise((resolve) => {
|
|
@@ -12644,11 +12989,11 @@ async function runGallery(initial, reload, args, access2) {
|
|
|
12644
12989
|
log("\n gallery closed");
|
|
12645
12990
|
}
|
|
12646
12991
|
async function provenanceFile(id, file) {
|
|
12647
|
-
const absolute =
|
|
12992
|
+
const absolute = path28.resolve(file);
|
|
12648
12993
|
return {
|
|
12649
12994
|
id,
|
|
12650
12995
|
path: absolute,
|
|
12651
|
-
sha256:
|
|
12996
|
+
sha256: existsSync22(absolute) ? await sha256File(absolute) : null,
|
|
12652
12997
|
included: true
|
|
12653
12998
|
};
|
|
12654
12999
|
}
|
|
@@ -12723,6 +13068,7 @@ var COMMANDS = [
|
|
|
12723
13068
|
"balance",
|
|
12724
13069
|
"status",
|
|
12725
13070
|
"gallery",
|
|
13071
|
+
"edit",
|
|
12726
13072
|
"quality",
|
|
12727
13073
|
"refine",
|
|
12728
13074
|
"recipe",
|
|
@@ -12737,6 +13083,7 @@ var WORKSPACE_SUBCOMMANDS = ["add", "remove", "list", "status", "claims"];
|
|
|
12737
13083
|
var REFINE_SUBCOMMANDS = ["run", "approve", "check"];
|
|
12738
13084
|
var RECIPE_SUBCOMMANDS = ["list", "inspect", "install", "verify"];
|
|
12739
13085
|
var QUALITY_SUBCOMMANDS = ["snapshot", "check"];
|
|
13086
|
+
var EDIT_SUBCOMMANDS = ["start", "detach"];
|
|
12740
13087
|
function parseArgs(argv) {
|
|
12741
13088
|
const [command = "help"] = argv;
|
|
12742
13089
|
if (!COMMANDS.includes(command)) {
|
|
@@ -12794,6 +13141,12 @@ function parseArgs(argv) {
|
|
|
12794
13141
|
}
|
|
12795
13142
|
rest = rest.slice(1);
|
|
12796
13143
|
}
|
|
13144
|
+
} else if (command === "edit") {
|
|
13145
|
+
subcommand = rest[0]?.startsWith("-") || rest[0] === void 0 ? "start" : rest[0];
|
|
13146
|
+
if (!EDIT_SUBCOMMANDS.includes(subcommand)) {
|
|
13147
|
+
throw new Error(`Unknown edit subcommand "${subcommand}". Known: ${EDIT_SUBCOMMANDS.join(", ")}`);
|
|
13148
|
+
}
|
|
13149
|
+
if (rest[0] === subcommand) rest = rest.slice(1);
|
|
12797
13150
|
} else if (command === "refine") {
|
|
12798
13151
|
subcommand = rest[0]?.startsWith("-") || rest[0] === void 0 ? "run" : rest[0];
|
|
12799
13152
|
if (!REFINE_SUBCOMMANDS.includes(subcommand)) {
|
|
@@ -12907,7 +13260,7 @@ function parseArgs(argv) {
|
|
|
12907
13260
|
return {
|
|
12908
13261
|
command,
|
|
12909
13262
|
manifest,
|
|
12910
|
-
lock: get("--lock") ??
|
|
13263
|
+
lock: get("--lock") ?? path28.join(path28.dirname(path28.resolve(manifest)), "pixelkiln.lock.json"),
|
|
12911
13264
|
explicitLock: get("--lock"),
|
|
12912
13265
|
styles: list("--style"),
|
|
12913
13266
|
assets: list("--only"),
|
|
@@ -12988,6 +13341,9 @@ Commands
|
|
|
12988
13341
|
tag Push manifest tags to the objects upstream (free).
|
|
12989
13342
|
balance Show the provider's remaining balance.
|
|
12990
13343
|
status Summarise the lockfile.
|
|
13344
|
+
edit Hand-edit an asset in your own editor: copies the generated PNG to
|
|
13345
|
+
<outDir>/edits/, declares it as the asset's source, and opens it.
|
|
13346
|
+
edit detach places the generated art again (the file is kept).
|
|
12991
13347
|
gallery Open a local read-only gallery of every generation and its
|
|
12992
13348
|
provenance: prompt, provider, cost, outputs, lineage, quality.
|
|
12993
13349
|
--workspace <catalog> shows every registered project at once;
|
|
@@ -13030,7 +13386,7 @@ Options
|
|
|
13030
13386
|
and the gallery snapshot (no server)
|
|
13031
13387
|
--check plan/audit/cache: exit nonzero when the selected state is unsafe
|
|
13032
13388
|
--yes, -y Skip the confirmation prompt
|
|
13033
|
-
--no-open Do not auto-open the browser
|
|
13389
|
+
--no-open Do not auto-open the browser (pick, salvage, gallery) or editor (edit)
|
|
13034
13390
|
--edit gallery: allow manifest edits from the page (never spends)
|
|
13035
13391
|
--tag Also push tags upstream after fetch
|
|
13036
13392
|
--claims a.json,b Other projects' lockfiles (salvage; required if account is shared)
|
|
@@ -13047,6 +13403,8 @@ Examples
|
|
|
13047
13403
|
pixelkiln gallery --style heybud-premium
|
|
13048
13404
|
pixelkiln gallery --workspace pixelkiln.workspace.json
|
|
13049
13405
|
pixelkiln gallery --edit --budget 80
|
|
13406
|
+
pixelkiln edit --style base --only anvil
|
|
13407
|
+
PIXELKILN_EDITOR="open -a Aseprite" pixelkiln edit --only anvil --style base
|
|
13050
13408
|
pixelkiln adopt --tag
|
|
13051
13409
|
pixelkiln pack --style heybud-premium
|
|
13052
13410
|
pixelkiln pack --inputs sprites.json --out dist/sheet # no manifest needed
|
|
@@ -13123,6 +13481,15 @@ function printResumeActions(specs, lock) {
|
|
|
13123
13481
|
}
|
|
13124
13482
|
return actions.length;
|
|
13125
13483
|
}
|
|
13484
|
+
function manifestSources(manifest, styleId) {
|
|
13485
|
+
const sources = {};
|
|
13486
|
+
for (const [assetId, asset] of Object.entries(manifest.assets)) {
|
|
13487
|
+
if (asset.styles.length && !asset.styles.includes(styleId)) continue;
|
|
13488
|
+
const source = asset.sourceByStyle[styleId] ?? asset.source;
|
|
13489
|
+
if (source) sources[assetId] = source;
|
|
13490
|
+
}
|
|
13491
|
+
return sources;
|
|
13492
|
+
}
|
|
13126
13493
|
function manifestProviderIds(manifest) {
|
|
13127
13494
|
return [...new Set(
|
|
13128
13495
|
Object.values(manifest.styles).map((style) => style.provider ?? manifest.provider)
|
|
@@ -13196,10 +13563,10 @@ async function confirm(question, auto) {
|
|
|
13196
13563
|
return answer === "y" || answer === "yes";
|
|
13197
13564
|
}
|
|
13198
13565
|
async function requireCompleteWorkspaceClaims(workspacePath) {
|
|
13199
|
-
if (!
|
|
13566
|
+
if (!existsSync22(workspacePath)) {
|
|
13200
13567
|
throw new Error(`Workspace catalog not found: ${workspacePath}`);
|
|
13201
13568
|
}
|
|
13202
|
-
const dir =
|
|
13569
|
+
const dir = path28.dirname(path28.resolve(workspacePath));
|
|
13203
13570
|
const ws = await loadWorkspace(workspacePath);
|
|
13204
13571
|
const diagnostics = validateWorkspace(ws, dir);
|
|
13205
13572
|
const errors = diagnostics.filter((d) => d.level === "error");
|
|
@@ -13231,15 +13598,15 @@ async function main() {
|
|
|
13231
13598
|
if (!args.out) throw new Error("quality snapshot needs --out <pixelkiln.quality.json>.");
|
|
13232
13599
|
let raw;
|
|
13233
13600
|
try {
|
|
13234
|
-
raw = JSON.parse(await readFile22(
|
|
13601
|
+
raw = JSON.parse(await readFile22(path28.resolve(args.inputs), "utf8"));
|
|
13235
13602
|
} catch (error) {
|
|
13236
13603
|
throw new Error(
|
|
13237
|
-
`Could not read quality inputs ${
|
|
13604
|
+
`Could not read quality inputs ${path28.resolve(args.inputs)}: ${error instanceof Error ? error.message : String(error)}`,
|
|
13238
13605
|
{ cause: error }
|
|
13239
13606
|
);
|
|
13240
13607
|
}
|
|
13241
13608
|
const inputs = resolveQualityInputs(raw, args.inputs);
|
|
13242
|
-
if (
|
|
13609
|
+
if (path28.resolve(args.inputs) === path28.resolve(args.out)) {
|
|
13243
13610
|
throw new Error("quality snapshot --out must not overwrite its --inputs file.");
|
|
13244
13611
|
}
|
|
13245
13612
|
const result = await snapshotQualityBaseline(inputs, args.out, { force: args.force });
|
|
@@ -13252,7 +13619,7 @@ async function main() {
|
|
|
13252
13619
|
}, null, 2));
|
|
13253
13620
|
} else {
|
|
13254
13621
|
log(
|
|
13255
|
-
` ${result.changed ? "wrote" : "unchanged"} ${
|
|
13622
|
+
` ${result.changed ? "wrote" : "unchanged"} ${path28.relative(process.cwd(), result.path)} (${result.baseline.cases.length} case(s))`
|
|
13256
13623
|
);
|
|
13257
13624
|
log(` Review the tolerances, commit the baseline, then run quality check in CI.`);
|
|
13258
13625
|
}
|
|
@@ -13347,14 +13714,14 @@ async function main() {
|
|
|
13347
13714
|
}, null, 2));
|
|
13348
13715
|
} else {
|
|
13349
13716
|
log(` installed ${result.recipe.id}@${result.recipe.version}`);
|
|
13350
|
-
log(` ${
|
|
13717
|
+
log(` ${path28.relative(process.cwd(), result.destination) || "."}`);
|
|
13351
13718
|
log(`
|
|
13352
13719
|
Add this entry under your manifest's styles object:`);
|
|
13353
13720
|
log(JSON.stringify({ [result.styleId]: result.style }, null, 2));
|
|
13354
13721
|
if (result.recipe.models.length) {
|
|
13355
13722
|
log(`
|
|
13356
13723
|
Models are not downloaded automatically. Verify them with:`);
|
|
13357
|
-
log(` pixelkiln recipe verify ${
|
|
13724
|
+
log(` pixelkiln recipe verify ${path28.relative(process.cwd(), result.destination)} --model-root <ComfyUI/models>`);
|
|
13358
13725
|
}
|
|
13359
13726
|
}
|
|
13360
13727
|
return;
|
|
@@ -13388,12 +13755,12 @@ async function main() {
|
|
|
13388
13755
|
` recovered ${result2.detection.columns}x${result2.detection.rows} native grid (${result2.detection.confidence}: ${result2.detection.consensus})`
|
|
13389
13756
|
);
|
|
13390
13757
|
log(` enforced ${result2.palette.length}-color palette without dithering`);
|
|
13391
|
-
log(` wrote ${
|
|
13392
|
-
log(` quality record: ${
|
|
13758
|
+
log(` wrote ${path28.relative(process.cwd(), result2.output)}`);
|
|
13759
|
+
log(` quality record: ${path28.relative(process.cwd(), result2.record)}`);
|
|
13393
13760
|
log(`
|
|
13394
13761
|
Automated checks passed. Human 1\xD7 review is still required:`);
|
|
13395
13762
|
log(
|
|
13396
|
-
` pixelkiln refine approve --from ${
|
|
13763
|
+
` pixelkiln refine approve --from ${path28.relative(process.cwd(), result2.record)} --reviewer "Your Name"`
|
|
13397
13764
|
);
|
|
13398
13765
|
}
|
|
13399
13766
|
return;
|
|
@@ -13421,7 +13788,7 @@ async function main() {
|
|
|
13421
13788
|
if (args.json) log(JSON.stringify(result2, null, 2));
|
|
13422
13789
|
else {
|
|
13423
13790
|
log(
|
|
13424
|
-
result2.options.frameSet ? ` approved ${result2.outputs.length} frames in ${
|
|
13791
|
+
result2.options.frameSet ? ` approved ${result2.outputs.length} frames in ${path28.relative(process.cwd(), result2.record)} by ${args.reviewer.trim()}` : ` approved ${path28.relative(process.cwd(), result2.output)} by ${args.reviewer.trim()}`
|
|
13425
13792
|
);
|
|
13426
13793
|
}
|
|
13427
13794
|
return;
|
|
@@ -13430,14 +13797,14 @@ async function main() {
|
|
|
13430
13797
|
if (args.json) {
|
|
13431
13798
|
log(JSON.stringify(result, null, 2));
|
|
13432
13799
|
} else {
|
|
13433
|
-
log(` ${result.safe ? "release-ready" : "not release-ready"}: ${
|
|
13800
|
+
log(` ${result.safe ? "release-ready" : "not release-ready"}: ${path28.relative(process.cwd(), result.output || result.record)}`);
|
|
13434
13801
|
for (const reason of result.reasons) log(` ${reason}`);
|
|
13435
13802
|
}
|
|
13436
13803
|
if (!result.safe) process.exitCode = 1;
|
|
13437
13804
|
return;
|
|
13438
13805
|
}
|
|
13439
13806
|
if (args.command === "balance") {
|
|
13440
|
-
loadEnvFiles(
|
|
13807
|
+
loadEnvFiles(path28.dirname(path28.resolve(args.manifest)));
|
|
13441
13808
|
loadEnvFiles(process.cwd());
|
|
13442
13809
|
const loaded2 = await loadManifest(args.manifest);
|
|
13443
13810
|
const selectedProvider = accountProviderId(loaded2.manifest, args.provider, "balance");
|
|
@@ -13450,31 +13817,31 @@ async function main() {
|
|
|
13450
13817
|
}
|
|
13451
13818
|
if (args.command === "init") {
|
|
13452
13819
|
if (!args.from) throw new Error("init needs --from <dir> pointing at your existing PNGs.");
|
|
13453
|
-
const root =
|
|
13454
|
-
if (!
|
|
13820
|
+
const root = path28.resolve(args.from);
|
|
13821
|
+
if (!existsSync22(root)) throw new Error(`No directory at ${root}`);
|
|
13455
13822
|
const generator = args.generator ?? "map";
|
|
13456
13823
|
if (generator !== "1dir" && generator !== "map") {
|
|
13457
13824
|
throw new Error(`--generator must be "1dir" or "map", got "${args.generator}".`);
|
|
13458
13825
|
}
|
|
13459
|
-
const target =
|
|
13826
|
+
const target = path28.resolve(args.out ?? "pixelkiln.manifest.json");
|
|
13460
13827
|
const { assets, skipped } = await scanAssets(root, { exclude: args.exclude });
|
|
13461
13828
|
if (!assets.length) throw new Error(`No PNGs found under ${root}`);
|
|
13462
13829
|
const manifest = buildManifest(
|
|
13463
|
-
args.name ??
|
|
13830
|
+
args.name ?? path28.basename(path28.dirname(target)),
|
|
13464
13831
|
args.styles[0] ?? "base",
|
|
13465
13832
|
generator,
|
|
13466
|
-
|
|
13833
|
+
path28.relative(path28.dirname(target), root) || ".",
|
|
13467
13834
|
assets
|
|
13468
13835
|
);
|
|
13469
13836
|
await writeManifestFile(target, manifest);
|
|
13470
|
-
log(` scanned ${assets.length} PNG(s) under ${
|
|
13837
|
+
log(` scanned ${assets.length} PNG(s) under ${path28.relative(process.cwd(), root)}`);
|
|
13471
13838
|
if (skipped.length) log(` skipped ${skipped.length} unreadable file(s)`);
|
|
13472
|
-
log(` wrote ${
|
|
13839
|
+
log(` wrote ${path28.relative(process.cwd(), target)}`);
|
|
13473
13840
|
log(`
|
|
13474
13841
|
Prompts are intentionally empty. To recover the real ones from your`);
|
|
13475
13842
|
log(` PixelLab account instead of inventing them:`);
|
|
13476
13843
|
log(`
|
|
13477
|
-
pixelkiln adopt --manifest ${
|
|
13844
|
+
pixelkiln adopt --manifest ${path28.relative(process.cwd(), target)} --write-prompts
|
|
13478
13845
|
`);
|
|
13479
13846
|
return;
|
|
13480
13847
|
}
|
|
@@ -13522,10 +13889,10 @@ async function main() {
|
|
|
13522
13889
|
if (args.primaryOnly || args.outputRoles.length) {
|
|
13523
13890
|
throw new Error("--primary-only and --output-role require manifest-driven pack");
|
|
13524
13891
|
}
|
|
13525
|
-
const raw = JSON.parse(await readFile22(
|
|
13892
|
+
const raw = JSON.parse(await readFile22(path28.resolve(args.inputs), "utf8"));
|
|
13526
13893
|
const inputs = resolvePackInputs(raw, args.inputs);
|
|
13527
13894
|
const { png, atlas, skipped, sources } = packSprites(inputs, { columns: args.columns });
|
|
13528
|
-
const base =
|
|
13895
|
+
const base = path28.resolve(args.out.replace(/\.png$/, ""));
|
|
13529
13896
|
const outputs = [
|
|
13530
13897
|
{ path: `${base}.png`, data: png },
|
|
13531
13898
|
{ path: `${base}.json`, data: JSON.stringify(atlas, null, 2) + "\n" }
|
|
@@ -13538,17 +13905,17 @@ async function main() {
|
|
|
13538
13905
|
log(
|
|
13539
13906
|
` ${atlas.frames.length} sprite(s), ${atlas.sheet.width}x${atlas.sheet.height} in ${atlas.columns} column(s) \u2014 ${(png.length / 1024).toFixed(1)} KB`
|
|
13540
13907
|
);
|
|
13541
|
-
log(` ${
|
|
13908
|
+
log(` ${path28.relative(process.cwd(), base)}.png + .json + .pixelkiln.json`);
|
|
13542
13909
|
for (const s of skipped) log(` skipped ${s.id}: ${s.reason}`);
|
|
13543
13910
|
return;
|
|
13544
13911
|
}
|
|
13545
13912
|
if (args.command === "workspace") {
|
|
13546
|
-
const workspacePath =
|
|
13547
|
-
const dir =
|
|
13913
|
+
const workspacePath = path28.resolve(args.workspace ?? "pixelkiln.workspace.json");
|
|
13914
|
+
const dir = path28.dirname(workspacePath);
|
|
13548
13915
|
if (args.subcommand === "add") {
|
|
13549
|
-
const manifestPath =
|
|
13916
|
+
const manifestPath = path28.resolve(args.target);
|
|
13550
13917
|
const loadedTarget = await loadManifest(manifestPath);
|
|
13551
|
-
const lockPath = args.explicitLock ?
|
|
13918
|
+
const lockPath = args.explicitLock ? path28.resolve(args.explicitLock) : path28.join(path28.dirname(manifestPath), "pixelkiln.lock.json");
|
|
13552
13919
|
const ws = await loadWorkspace(workspacePath);
|
|
13553
13920
|
const id = args.name ?? loadedTarget.manifest.name;
|
|
13554
13921
|
if (ws.projects.some((p) => p.id === id)) {
|
|
@@ -13568,10 +13935,10 @@ async function main() {
|
|
|
13568
13935
|
...args.account ? { account: args.account } : {}
|
|
13569
13936
|
};
|
|
13570
13937
|
await saveWorkspace(workspacePath, { version: 1, projects: [...ws.projects, project] });
|
|
13571
|
-
log(` registered "${id}" in ${
|
|
13938
|
+
log(` registered "${id}" in ${path28.relative(process.cwd(), workspacePath)}`);
|
|
13572
13939
|
log(` manifest: ${project.manifest}`);
|
|
13573
13940
|
log(` lock: ${project.lock}`);
|
|
13574
|
-
if (!
|
|
13941
|
+
if (!existsSync22(lockPath)) {
|
|
13575
13942
|
log(
|
|
13576
13943
|
` warning: no lockfile there yet \u2014 this project contributes no claims until one is generated`
|
|
13577
13944
|
);
|
|
@@ -13580,7 +13947,7 @@ async function main() {
|
|
|
13580
13947
|
}
|
|
13581
13948
|
if (args.subcommand === "remove") {
|
|
13582
13949
|
const ws = await loadWorkspace(workspacePath);
|
|
13583
|
-
const resolvedTarget =
|
|
13950
|
+
const resolvedTarget = path28.resolve(args.target);
|
|
13584
13951
|
const match = ws.projects.find(
|
|
13585
13952
|
(p) => p.id === args.target || resolveProject(dir, p).manifestPath === resolvedTarget
|
|
13586
13953
|
);
|
|
@@ -13591,11 +13958,11 @@ async function main() {
|
|
|
13591
13958
|
version: 1,
|
|
13592
13959
|
projects: ws.projects.filter((p) => p !== match)
|
|
13593
13960
|
});
|
|
13594
|
-
log(` removed "${match.id}" from ${
|
|
13961
|
+
log(` removed "${match.id}" from ${path28.relative(process.cwd(), workspacePath)}`);
|
|
13595
13962
|
return;
|
|
13596
13963
|
}
|
|
13597
13964
|
if (args.subcommand === "list") {
|
|
13598
|
-
if (!
|
|
13965
|
+
if (!existsSync22(workspacePath)) {
|
|
13599
13966
|
throw new Error(`Workspace catalog not found: ${workspacePath}`);
|
|
13600
13967
|
}
|
|
13601
13968
|
const ws = await loadWorkspace(workspacePath);
|
|
@@ -13603,9 +13970,9 @@ async function main() {
|
|
|
13603
13970
|
if (args.json) {
|
|
13604
13971
|
log(JSON.stringify({ version: 1, workspace: workspacePath, projects: ws.projects, diagnostics }, null, 2));
|
|
13605
13972
|
} else if (!ws.projects.length) {
|
|
13606
|
-
log(` no projects registered in ${
|
|
13973
|
+
log(` no projects registered in ${path28.relative(process.cwd(), workspacePath)}`);
|
|
13607
13974
|
} else {
|
|
13608
|
-
log(` ${ws.projects.length} project(s) in ${
|
|
13975
|
+
log(` ${ws.projects.length} project(s) in ${path28.relative(process.cwd(), workspacePath)}:`);
|
|
13609
13976
|
for (const p of ws.projects) {
|
|
13610
13977
|
log(` ${p.id.padEnd(24)} ${p.manifest.padEnd(40)} (${p.provider}${p.account ? `, ${p.account}` : ""})`);
|
|
13611
13978
|
}
|
|
@@ -13615,7 +13982,7 @@ async function main() {
|
|
|
13615
13982
|
return;
|
|
13616
13983
|
}
|
|
13617
13984
|
if (args.subcommand === "status") {
|
|
13618
|
-
if (!
|
|
13985
|
+
if (!existsSync22(workspacePath)) {
|
|
13619
13986
|
throw new Error(`Workspace catalog not found: ${workspacePath}`);
|
|
13620
13987
|
}
|
|
13621
13988
|
const ws = await loadWorkspace(workspacePath);
|
|
@@ -13623,7 +13990,7 @@ async function main() {
|
|
|
13623
13990
|
if (args.json) {
|
|
13624
13991
|
log(JSON.stringify({ ...report, workspace: workspacePath }, null, 2));
|
|
13625
13992
|
} else {
|
|
13626
|
-
log(` workspace: ${
|
|
13993
|
+
log(` workspace: ${path28.relative(process.cwd(), workspacePath)}`);
|
|
13627
13994
|
for (const p of report.projects) {
|
|
13628
13995
|
if (p.error) {
|
|
13629
13996
|
log(`
|
|
@@ -13670,8 +14037,8 @@ async function main() {
|
|
|
13670
14037
|
}
|
|
13671
14038
|
}
|
|
13672
14039
|
if (args.command === "gallery" && args.workspace) {
|
|
13673
|
-
const workspacePath =
|
|
13674
|
-
if (!
|
|
14040
|
+
const workspacePath = path28.resolve(args.workspace);
|
|
14041
|
+
if (!existsSync22(workspacePath)) {
|
|
13675
14042
|
throw new Error(`Workspace catalog not found: ${workspacePath}`);
|
|
13676
14043
|
}
|
|
13677
14044
|
const filter = { styles: args.styles, assets: args.assets };
|
|
@@ -13691,7 +14058,7 @@ async function main() {
|
|
|
13691
14058
|
const registered = async (projectId) => {
|
|
13692
14059
|
const project = (await loadWorkspace(workspacePath)).projects.find((candidate) => candidate.id === projectId);
|
|
13693
14060
|
if (!projectId || !project) throw new Error(`unknown workspace project "${projectId ?? ""}"`);
|
|
13694
|
-
return resolveProject(
|
|
14061
|
+
return resolveProject(path28.dirname(workspacePath), project);
|
|
13695
14062
|
};
|
|
13696
14063
|
await runGallery(initial, build, args, {
|
|
13697
14064
|
manifestFor: async (projectId) => (await registered(projectId)).manifestPath,
|
|
@@ -13706,14 +14073,14 @@ async function main() {
|
|
|
13706
14073
|
});
|
|
13707
14074
|
return;
|
|
13708
14075
|
}
|
|
13709
|
-
if (!
|
|
14076
|
+
if (!existsSync22(path28.resolve(args.manifest))) {
|
|
13710
14077
|
throw new Error(
|
|
13711
|
-
`No manifest at ${
|
|
14078
|
+
`No manifest at ${path28.resolve(args.manifest)}. Pass --manifest, or run \`pixelkiln init --from <dir>\`.`
|
|
13712
14079
|
);
|
|
13713
14080
|
}
|
|
13714
|
-
const manifestDir =
|
|
14081
|
+
const manifestDir = path28.dirname(path28.resolve(args.manifest));
|
|
13715
14082
|
const envFiles2 = [...loadEnvFiles(manifestDir)];
|
|
13716
|
-
if (
|
|
14083
|
+
if (path28.resolve(process.cwd()) !== manifestDir) envFiles2.push(...loadEnvFiles(process.cwd()));
|
|
13717
14084
|
const loaded = await loadManifest(args.manifest);
|
|
13718
14085
|
let specs = await resolveSpecs(loaded, {
|
|
13719
14086
|
styles: args.styles,
|
|
@@ -13786,7 +14153,7 @@ async function main() {
|
|
|
13786
14153
|
});
|
|
13787
14154
|
};
|
|
13788
14155
|
await runGallery(await build(), reload, args, {
|
|
13789
|
-
manifestFor: () =>
|
|
14156
|
+
manifestFor: () => path28.resolve(args.manifest),
|
|
13790
14157
|
loadProject: async () => {
|
|
13791
14158
|
const freshLoaded = await loadManifest(args.manifest);
|
|
13792
14159
|
const freshSpecs = await resolveSpecs(freshLoaded);
|
|
@@ -13797,6 +14164,29 @@ async function main() {
|
|
|
13797
14164
|
});
|
|
13798
14165
|
return;
|
|
13799
14166
|
}
|
|
14167
|
+
if (args.command === "edit") {
|
|
14168
|
+
if (!args.assets.length) throw new Error("edit needs --only <asset id> (and --style when the asset is in several styles).");
|
|
14169
|
+
if (specs.length !== 1) {
|
|
14170
|
+
throw new Error(
|
|
14171
|
+
specs.length ? `edit works on one asset in one style; --only ${args.assets.join(",")} resolves to ${specs.length}: ` + specs.map((spec2) => `${spec2.styleId}/${spec2.assetId}`).join(", ") + ". Add --style." : "edit matched no asset."
|
|
14172
|
+
);
|
|
14173
|
+
}
|
|
14174
|
+
const spec = specs[0];
|
|
14175
|
+
if (args.subcommand === "detach") {
|
|
14176
|
+
const result = await detachHandEdit(loaded, spec);
|
|
14177
|
+
log(result.changed ? ` detached the hand edit for ${spec.styleId}/${spec.assetId}; the generated art is placed again (the edit file is kept)` : ` ${spec.styleId}/${spec.assetId} has no hand edit`);
|
|
14178
|
+
return;
|
|
14179
|
+
}
|
|
14180
|
+
const started = await startHandEdit(loaded, lock, spec);
|
|
14181
|
+
log(` ${started.created ? "created" : "found"} ${started.source}${started.declared ? " and declared it as the asset's source" : ""}`);
|
|
14182
|
+
if (args.noOpen) {
|
|
14183
|
+
log(` edit it with your own tool, then run pixelkiln plan; mount and pack place it in place of the generated art`);
|
|
14184
|
+
} else {
|
|
14185
|
+
const command = openInEditor(started.editPath);
|
|
14186
|
+
log(` opened with: ${command}${process.env.PIXELKILN_EDITOR ? "" : " (set PIXELKILN_EDITOR to choose the program)"}`);
|
|
14187
|
+
}
|
|
14188
|
+
return;
|
|
14189
|
+
}
|
|
13800
14190
|
const plan = await buildPlan(specs, lock, { force: args.force });
|
|
13801
14191
|
if (args.command === "refine") {
|
|
13802
14192
|
const pathOwnedFlags = [
|
|
@@ -13838,7 +14228,7 @@ async function main() {
|
|
|
13838
14228
|
log(` ${item.state.padEnd(16)} ${item.key} ${item.reason}`);
|
|
13839
14229
|
if (item.state === "needs-approval") {
|
|
13840
14230
|
log(
|
|
13841
|
-
` pixelkiln refine approve --from ${
|
|
14231
|
+
` pixelkiln refine approve --from ${path28.relative(process.cwd(), item.record)} --reviewer "Your Name"`
|
|
13842
14232
|
);
|
|
13843
14233
|
}
|
|
13844
14234
|
}
|
|
@@ -13929,7 +14319,7 @@ async function main() {
|
|
|
13929
14319
|
let intact = true;
|
|
13930
14320
|
for (const output of entry.outputs) {
|
|
13931
14321
|
const file = resolveOutputPath(output.path, item.spec.root);
|
|
13932
|
-
if (!
|
|
14322
|
+
if (!existsSync22(file) || await sha256File(file) !== output.sha256) {
|
|
13933
14323
|
intact = false;
|
|
13934
14324
|
break;
|
|
13935
14325
|
}
|
|
@@ -13987,7 +14377,7 @@ async function main() {
|
|
|
13987
14377
|
if (args.primaryOnly && args.outputRoles.length) {
|
|
13988
14378
|
throw new Error("pack accepts either --primary-only or --output-role, not both");
|
|
13989
14379
|
}
|
|
13990
|
-
const manifestDir2 =
|
|
14380
|
+
const manifestDir2 = path28.dirname(path28.resolve(args.manifest));
|
|
13991
14381
|
const styleIds = args.styles.length ? args.styles : Object.keys(loaded.manifest.styles);
|
|
13992
14382
|
for (const styleId of styleIds) {
|
|
13993
14383
|
const packagingSpecs = await resolveSpecs(loaded, { styles: [styleId] });
|
|
@@ -13996,10 +14386,11 @@ async function main() {
|
|
|
13996
14386
|
columns: args.columns,
|
|
13997
14387
|
outputRoles: args.outputRoles,
|
|
13998
14388
|
primaryOnly: args.primaryOnly,
|
|
13999
|
-
sourceOverrides: qualitySources
|
|
14389
|
+
sourceOverrides: qualitySources,
|
|
14390
|
+
sources: manifestSources(loaded.manifest, styleId)
|
|
14000
14391
|
});
|
|
14001
14392
|
const style = loaded.manifest.styles[styleId];
|
|
14002
|
-
const base = args.out ?
|
|
14393
|
+
const base = args.out ? path28.resolve(args.out.replace(/\.png$/, "")) : path28.resolve(manifestDir2, style.outDir, `${styleId}-sheet`);
|
|
14003
14394
|
const outputs = [
|
|
14004
14395
|
{ path: `${base}.png`, data: png },
|
|
14005
14396
|
{ path: `${base}.json`, data: JSON.stringify(atlas, null, 2) + "\n" }
|
|
@@ -14029,13 +14420,13 @@ async function main() {
|
|
|
14029
14420
|
log(
|
|
14030
14421
|
` ${styleId} \u2014 ${atlas.frames.length} sprite(s), ${atlas.sheet.width}x${atlas.sheet.height} in ${atlas.columns} column(s)`
|
|
14031
14422
|
);
|
|
14032
|
-
log(` ${
|
|
14423
|
+
log(` ${path28.relative(process.cwd(), base)}.png + .json + .pixelkiln.json`);
|
|
14033
14424
|
for (const s of skipped) log(` skipped ${s.id}: ${s.reason}`);
|
|
14034
14425
|
}
|
|
14035
14426
|
return;
|
|
14036
14427
|
}
|
|
14037
14428
|
if (args.command === "mount") {
|
|
14038
|
-
const manifestDir2 =
|
|
14429
|
+
const manifestDir2 = path28.dirname(path28.resolve(args.manifest));
|
|
14039
14430
|
const styleIds = args.styles.length ? args.styles : Object.keys(loaded.manifest.styles);
|
|
14040
14431
|
for (const styleId of styleIds) {
|
|
14041
14432
|
const style = loaded.manifest.styles[styleId];
|
|
@@ -14050,10 +14441,11 @@ async function main() {
|
|
|
14050
14441
|
const cells = {};
|
|
14051
14442
|
const sources = {};
|
|
14052
14443
|
const outputRoles = {};
|
|
14444
|
+
const styleSources = manifestSources(loaded.manifest, styleId);
|
|
14053
14445
|
for (const [assetId, asset] of Object.entries(loaded.manifest.assets)) {
|
|
14054
14446
|
if (!asset.cell) continue;
|
|
14055
14447
|
cells[assetId] = asset.cell;
|
|
14056
|
-
if (
|
|
14448
|
+
if (styleSources[assetId]) sources[assetId] = styleSources[assetId];
|
|
14057
14449
|
if (asset.outputRole) outputRoles[assetId] = asset.outputRole;
|
|
14058
14450
|
}
|
|
14059
14451
|
const packagingSpecs = await resolveSpecs(loaded, { styles: [styleId] });
|
|
@@ -14073,7 +14465,7 @@ async function main() {
|
|
|
14073
14465
|
sources,
|
|
14074
14466
|
outputRoles
|
|
14075
14467
|
);
|
|
14076
|
-
const out =
|
|
14468
|
+
const out = path28.resolve(manifestDir2, style.mount.out);
|
|
14077
14469
|
const metadata = out.replace(/\.png$/, "") + ".json";
|
|
14078
14470
|
const companion = out.replace(/\.png$/, "") + ".pixelkiln.json";
|
|
14079
14471
|
const outputs = [
|
|
@@ -14093,7 +14485,7 @@ async function main() {
|
|
|
14093
14485
|
await provenanceFile("$lock", args.lock),
|
|
14094
14486
|
...qualityRecords,
|
|
14095
14487
|
...artifactSources.filter(
|
|
14096
|
-
(source) => source.id !== "$base" ||
|
|
14488
|
+
(source) => source.id !== "$base" || path28.resolve(source.path) !== out
|
|
14097
14489
|
)
|
|
14098
14490
|
],
|
|
14099
14491
|
options: {
|
|
@@ -14106,7 +14498,7 @@ async function main() {
|
|
|
14106
14498
|
log(
|
|
14107
14499
|
` ${styleId} \u2014 ${atlas.frames.length} cell(s) into ${atlas.sheet.width}x${atlas.sheet.height}` + (overBase ? ` over ${style.mount.base}` : " (new sheet)")
|
|
14108
14500
|
);
|
|
14109
|
-
log(` ${
|
|
14501
|
+
log(` ${path28.relative(process.cwd(), out)} + atlas/provenance JSON`);
|
|
14110
14502
|
for (const s of skipped) log(` skipped ${s.id}: ${s.reason}`);
|
|
14111
14503
|
}
|
|
14112
14504
|
return;
|
|
@@ -14162,7 +14554,7 @@ async function main() {
|
|
|
14162
14554
|
}
|
|
14163
14555
|
if (args.command === "export") {
|
|
14164
14556
|
const format = args.format ?? "generic";
|
|
14165
|
-
const manifestDir2 =
|
|
14557
|
+
const manifestDir2 = path28.dirname(path28.resolve(args.manifest));
|
|
14166
14558
|
const selected = specs.filter((spec) => {
|
|
14167
14559
|
if (spec.generator !== "tiles") return false;
|
|
14168
14560
|
if (args.styles.length && !args.styles.includes(spec.styleId)) return false;
|
|
@@ -14178,12 +14570,12 @@ async function main() {
|
|
|
14178
14570
|
for (const spec of selected) {
|
|
14179
14571
|
const entry = lock.entries[lockKey(spec.styleId, spec.assetId)];
|
|
14180
14572
|
const style = loaded.manifest.styles[spec.styleId];
|
|
14181
|
-
const defaultBase =
|
|
14182
|
-
const base = args.out ?
|
|
14573
|
+
const defaultBase = path28.resolve(manifestDir2, style.outDir, `${spec.assetId}-tileset`);
|
|
14574
|
+
const base = args.out ? path28.resolve(args.out.replace(/\.(?:png|json|tsj|tres)$/i, "")) : defaultBase;
|
|
14183
14575
|
const result = exportTileset(entry, spec, {
|
|
14184
14576
|
format,
|
|
14185
14577
|
manifestDir: manifestDir2,
|
|
14186
|
-
imageName:
|
|
14578
|
+
imageName: path28.basename(`${base}.png`),
|
|
14187
14579
|
columns: args.columns
|
|
14188
14580
|
});
|
|
14189
14581
|
const outputs = [
|
|
@@ -14201,7 +14593,7 @@ async function main() {
|
|
|
14201
14593
|
asset: spec.assetId,
|
|
14202
14594
|
columns: args.columns ?? null,
|
|
14203
14595
|
format,
|
|
14204
|
-
image:
|
|
14596
|
+
image: path28.basename(`${base}.png`),
|
|
14205
14597
|
providerRules: result.generic.providerRules,
|
|
14206
14598
|
style: spec.styleId,
|
|
14207
14599
|
tileType: spec.tileType ?? null
|
|
@@ -14211,7 +14603,7 @@ async function main() {
|
|
|
14211
14603
|
` ${spec.styleId}/${spec.assetId} \u2014 ${result.generic.tiles.length} tile(s), ${result.generic.sheet.width}x${result.generic.sheet.height} (${format})`
|
|
14212
14604
|
);
|
|
14213
14605
|
log(
|
|
14214
|
-
` ${
|
|
14606
|
+
` ${path28.relative(process.cwd(), base)}.png + ${path28.basename(base)}${result.extension} + .pixelkiln.json`
|
|
14215
14607
|
);
|
|
14216
14608
|
}
|
|
14217
14609
|
return;
|
|
@@ -14261,12 +14653,12 @@ async function main() {
|
|
|
14261
14653
|
tagged ${n} object(s) upstream`);
|
|
14262
14654
|
}
|
|
14263
14655
|
if (args.writePrompts) {
|
|
14264
|
-
const { filled, stillEmpty } = await writePromptsBack(
|
|
14656
|
+
const { filled, stillEmpty } = await writePromptsBack(path28.resolve(args.manifest), lock, {
|
|
14265
14657
|
onProgress: log,
|
|
14266
14658
|
provider: provider.id,
|
|
14267
14659
|
assetIds: specs.map((spec) => spec.assetId)
|
|
14268
14660
|
});
|
|
14269
|
-
log(` recovered ${filled} prompt(s) into ${
|
|
14661
|
+
log(` recovered ${filled} prompt(s) into ${path28.relative(process.cwd(), args.manifest)}`);
|
|
14270
14662
|
const reloaded = await loadManifest(args.manifest);
|
|
14271
14663
|
const rebased = (await resolveSpecs(reloaded, {
|
|
14272
14664
|
styles: args.styles,
|
|
@@ -14295,12 +14687,12 @@ async function main() {
|
|
|
14295
14687
|
const provider = providerFor(selectedAccountProvider);
|
|
14296
14688
|
const jsonMode = args.dryRun && args.json;
|
|
14297
14689
|
const diag = jsonMode ? (msg = "") => console.error(msg) : log;
|
|
14298
|
-
const ownLock =
|
|
14690
|
+
const ownLock = path28.resolve(args.lock);
|
|
14299
14691
|
let workspaceProjects = [];
|
|
14300
14692
|
let workspaceDir = "";
|
|
14301
14693
|
if (args.workspace) {
|
|
14302
|
-
const workspacePath =
|
|
14303
|
-
workspaceDir =
|
|
14694
|
+
const workspacePath = path28.resolve(args.workspace);
|
|
14695
|
+
workspaceDir = path28.dirname(workspacePath);
|
|
14304
14696
|
const complete = await requireCompleteWorkspaceClaims(workspacePath);
|
|
14305
14697
|
workspaceProjects = complete.ws.projects;
|
|
14306
14698
|
for (const d of complete.diagnostics) diag(` WARN ${d.id}: ${d.message}`);
|
|
@@ -14309,12 +14701,12 @@ async function main() {
|
|
|
14309
14701
|
const lockPaths = [
|
|
14310
14702
|
.../* @__PURE__ */ new Set([
|
|
14311
14703
|
...workspaceLockPaths,
|
|
14312
|
-
...
|
|
14313
|
-
...args.claims.map((c) =>
|
|
14704
|
+
...existsSync22(ownLock) ? [ownLock] : [],
|
|
14705
|
+
...args.claims.map((c) => path28.resolve(c))
|
|
14314
14706
|
])
|
|
14315
14707
|
];
|
|
14316
14708
|
diag(` claim set (${lockPaths.length} lockfile(s)):`);
|
|
14317
|
-
for (const p of lockPaths) diag(` ${
|
|
14709
|
+
for (const p of lockPaths) diag(` ${path28.relative(process.cwd(), p)}`);
|
|
14318
14710
|
if (!args.claims.length && !args.workspace) {
|
|
14319
14711
|
diag(
|
|
14320
14712
|
`
|
|
@@ -14322,7 +14714,7 @@ async function main() {
|
|
|
14322
14714
|
pass every other project's lockfile via --claims a.json,b.json, or
|
|
14323
14715
|
register every project in a workspace catalog and pass --workspace.`
|
|
14324
14716
|
);
|
|
14325
|
-
} else if (args.workspace && !workspaceProjects.some((p) => resolveProject(workspaceDir, p).manifestPath ===
|
|
14717
|
+
} else if (args.workspace && !workspaceProjects.some((p) => resolveProject(workspaceDir, p).manifestPath === path28.resolve(args.manifest))) {
|
|
14326
14718
|
diag(
|
|
14327
14719
|
`
|
|
14328
14720
|
This project's manifest is not registered in the workspace catalog. Its own
|
|
@@ -14406,7 +14798,7 @@ async function main() {
|
|
|
14406
14798
|
manifestPath: loaded.path,
|
|
14407
14799
|
manifest: accountManifest,
|
|
14408
14800
|
styleId,
|
|
14409
|
-
importDir:
|
|
14801
|
+
importDir: path28.resolve(loaded.root, style.outDir),
|
|
14410
14802
|
lock,
|
|
14411
14803
|
lockPath: args.lock
|
|
14412
14804
|
},
|