pixelkiln 0.15.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/PROVIDERS.md +9 -2
- package/README.md +5 -2
- package/dist/cli.js +730 -254
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +628 -161
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +566 -96
- package/dist/index.d.ts +566 -96
- package/dist/index.js +613 -150
- package/dist/index.js.map +1 -1
- package/docs/AGENTS.md +4 -1
- package/docs/ARCHITECTURE.md +20 -0
- package/docs/CLI.md +12 -6
- package/docs/COMFYUI.md +68 -1
- package/docs/GETTING_STARTED.md +8 -1
- package/docs/LIBRARY.md +20 -0
- package/docs/MANIFEST.md +48 -3
- package/docs/PROVIDER_BENCHMARK.md +21 -0
- package/docs/README.md +1 -0
- package/docs/RECIPES.md +16 -0
- package/docs/REVISIONS.md +172 -0
- package/package.json +1 -1
- package/recipes/comfyui/pixel-art-xl-img2img/1.0.0/pixelkiln.recipe.json +87 -0
- package/recipes/comfyui/pixel-art-xl-img2img/1.0.0/workflow-api.json +101 -0
- package/schema/manifest.schema.json +31 -0
- package/schema/recipe.schema.json +55 -4
- package/skills/pixelkiln/SKILL.md +5 -0
- package/skills/pixelkiln/references/comfyui.md +6 -4
- package/skills/pixelkiln/references/recipes.md +6 -0
- package/skills/pixelkiln/references/revisions.md +30 -0
package/dist/cli.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
-
import
|
|
5
|
-
import { existsSync as
|
|
6
|
-
import { readFile as
|
|
4
|
+
import path24 from "path";
|
|
5
|
+
import { existsSync as existsSync19 } from "fs";
|
|
6
|
+
import { readFile as readFile18 } from "fs/promises";
|
|
7
7
|
|
|
8
8
|
// src/env.ts
|
|
9
9
|
import { readFileSync, existsSync } from "fs";
|
|
@@ -114,10 +114,10 @@ import { createReadStream } from "fs";
|
|
|
114
114
|
function sha256(data) {
|
|
115
115
|
return createHash("sha256").update(data).digest("hex");
|
|
116
116
|
}
|
|
117
|
-
async function sha256File(
|
|
117
|
+
async function sha256File(path25) {
|
|
118
118
|
return new Promise((resolve, reject) => {
|
|
119
119
|
const hash = createHash("sha256");
|
|
120
|
-
const stream = createReadStream(
|
|
120
|
+
const stream = createReadStream(path25);
|
|
121
121
|
stream.on("data", (chunk2) => hash.update(chunk2));
|
|
122
122
|
stream.on("error", reject);
|
|
123
123
|
stream.on("end", () => resolve(hash.digest("hex")));
|
|
@@ -148,7 +148,14 @@ function specHash(spec, styleImageHashes, providerOptionIdentity = spec.provider
|
|
|
148
148
|
tileView: spec.tileView,
|
|
149
149
|
tileFeature: spec.tileFeature,
|
|
150
150
|
outlineMode: spec.outlineMode,
|
|
151
|
-
styleImages: styleImageHashes
|
|
151
|
+
styleImages: styleImageHashes,
|
|
152
|
+
revision: spec.revision ? {
|
|
153
|
+
mode: spec.revision.mode,
|
|
154
|
+
from: spec.revision.sourceAssetId,
|
|
155
|
+
sourceSha256: spec.revision.sourceSha256,
|
|
156
|
+
maskSha256: spec.revision.maskSha256,
|
|
157
|
+
strength: spec.revision.strength
|
|
158
|
+
} : void 0
|
|
152
159
|
})
|
|
153
160
|
);
|
|
154
161
|
}
|
|
@@ -623,6 +630,37 @@ var ComfyUIClient = class {
|
|
|
623
630
|
}
|
|
624
631
|
return promptId;
|
|
625
632
|
}
|
|
633
|
+
async uploadImage(bytes, sha2562, format) {
|
|
634
|
+
const extension = format === "jpeg" ? "jpg" : "png";
|
|
635
|
+
const form = new FormData();
|
|
636
|
+
form.append(
|
|
637
|
+
"image",
|
|
638
|
+
new Blob([new Uint8Array(bytes)], { type: format === "jpeg" ? "image/jpeg" : "image/png" }),
|
|
639
|
+
`${sha2562}.${extension}`
|
|
640
|
+
);
|
|
641
|
+
form.append("type", "input");
|
|
642
|
+
form.append("subfolder", "pixelkiln");
|
|
643
|
+
form.append("overwrite", "true");
|
|
644
|
+
const url = this.endpoint("upload/image");
|
|
645
|
+
const response = await this.request(url, { method: "POST", body: form });
|
|
646
|
+
const text = await response.text();
|
|
647
|
+
let value = null;
|
|
648
|
+
try {
|
|
649
|
+
value = text ? JSON.parse(text) : null;
|
|
650
|
+
} catch {
|
|
651
|
+
value = null;
|
|
652
|
+
}
|
|
653
|
+
if (!response.ok) {
|
|
654
|
+
throw new Error(
|
|
655
|
+
`ComfyUI image upload failed (${response.status})` + (apiError(value) ? `: ${apiError(value)}` : "")
|
|
656
|
+
);
|
|
657
|
+
}
|
|
658
|
+
if (!isObject(value) || typeof value.name !== "string" || !value.name) {
|
|
659
|
+
throw new Error("ComfyUI image upload returned an invalid response");
|
|
660
|
+
}
|
|
661
|
+
const subfolder = typeof value.subfolder === "string" ? value.subfolder : "";
|
|
662
|
+
return subfolder ? `${subfolder.replace(/\/+$/, "")}/${value.name}` : value.name;
|
|
663
|
+
}
|
|
626
664
|
async history(promptId) {
|
|
627
665
|
return this.json(`history/${encodeURIComponent(promptId)}`);
|
|
628
666
|
}
|
|
@@ -718,11 +756,15 @@ var ComfyUIProvider = class _ComfyUIProvider {
|
|
|
718
756
|
supports(generator) {
|
|
719
757
|
return generator === "map";
|
|
720
758
|
}
|
|
759
|
+
supportsRevision(_mode) {
|
|
760
|
+
return true;
|
|
761
|
+
}
|
|
721
762
|
estimate(spec) {
|
|
722
763
|
return { unit: "free", amount: 0, candidates: resolvedOptions(spec).numImages };
|
|
723
764
|
}
|
|
724
765
|
validate(spec, styleImages) {
|
|
725
766
|
const options = resolvedOptions(spec);
|
|
767
|
+
if (spec.source && !spec.revision) return;
|
|
726
768
|
if (spec.width < 16 || spec.height < 16 || spec.width > 4096 || spec.height > 4096) {
|
|
727
769
|
throw new Error("ComfyUI output dimensions must be between 16 and 4096 pixels");
|
|
728
770
|
}
|
|
@@ -735,6 +777,31 @@ var ComfyUIProvider = class _ComfyUIProvider {
|
|
|
735
777
|
if (spec.seed != null && !options.bindings.seed) {
|
|
736
778
|
throw new Error("ComfyUI requires bindings.seed when the style declares a seed");
|
|
737
779
|
}
|
|
780
|
+
if (!options.bindings.width !== !options.bindings.height) {
|
|
781
|
+
throw new Error("ComfyUI width and height bindings must be declared together");
|
|
782
|
+
}
|
|
783
|
+
if (!spec.revision && (!options.bindings.width || !options.bindings.height)) {
|
|
784
|
+
throw new Error("ComfyUI text-to-image generation requires width and height bindings");
|
|
785
|
+
}
|
|
786
|
+
if (options.numImages > 1 && !options.bindings.batchSize) {
|
|
787
|
+
throw new Error("ComfyUI numImages greater than 1 requires bindings.batchSize");
|
|
788
|
+
}
|
|
789
|
+
if (spec.revision) {
|
|
790
|
+
if (!options.bindings.sourceImage) {
|
|
791
|
+
throw new Error("ComfyUI revisions require bindings.sourceImage");
|
|
792
|
+
}
|
|
793
|
+
if (spec.revision.mode === "inpaint" && !options.bindings.maskImage) {
|
|
794
|
+
throw new Error("ComfyUI inpaint revisions require bindings.maskImage");
|
|
795
|
+
}
|
|
796
|
+
if (spec.revision.strength != null && !options.bindings.strength) {
|
|
797
|
+
throw new Error("ComfyUI revision strength requires bindings.strength");
|
|
798
|
+
}
|
|
799
|
+
if ((!options.bindings.width || !options.bindings.height) && spec.revision.sourceWidth != null && spec.revision.sourceHeight != null && (spec.width !== spec.revision.sourceWidth || spec.height !== spec.revision.sourceHeight)) {
|
|
800
|
+
throw new Error(
|
|
801
|
+
"ComfyUI revisions without width/height bindings must keep the source dimensions"
|
|
802
|
+
);
|
|
803
|
+
}
|
|
804
|
+
}
|
|
738
805
|
validateWorkflowBindings(options.workflow, options);
|
|
739
806
|
}
|
|
740
807
|
rateLimit() {
|
|
@@ -745,14 +812,61 @@ var ComfyUIProvider = class _ComfyUIProvider {
|
|
|
745
812
|
const options = resolvedOptions(spec);
|
|
746
813
|
const workflow = structuredClone(options.workflow);
|
|
747
814
|
setBinding(workflow, options.bindings.prompt, spec.prompt);
|
|
748
|
-
setBinding(workflow, options.bindings.width, spec.width);
|
|
749
|
-
setBinding(workflow, options.bindings.height, spec.height);
|
|
750
|
-
|
|
815
|
+
if (options.bindings.width) setBinding(workflow, options.bindings.width, spec.width);
|
|
816
|
+
if (options.bindings.height) setBinding(workflow, options.bindings.height, spec.height);
|
|
817
|
+
if (options.bindings.batchSize) {
|
|
818
|
+
setBinding(workflow, options.bindings.batchSize, options.numImages);
|
|
819
|
+
}
|
|
751
820
|
if (spec.seed != null && options.bindings.seed) {
|
|
752
821
|
setBinding(workflow, options.bindings.seed, spec.seed);
|
|
753
822
|
}
|
|
823
|
+
if (spec.revision) {
|
|
824
|
+
if (!spec.revision.sourceSha256 || !spec.revision.sourceFormat) {
|
|
825
|
+
throw new Error("ComfyUI revision source is not ready");
|
|
826
|
+
}
|
|
827
|
+
const sourceBytes = await readFile(spec.revision.sourceFile);
|
|
828
|
+
if (sha256(sourceBytes) !== spec.revision.sourceSha256) {
|
|
829
|
+
throw new Error("ComfyUI revision source changed before upload");
|
|
830
|
+
}
|
|
831
|
+
const uploadedSource = await this.client.uploadImage(
|
|
832
|
+
sourceBytes,
|
|
833
|
+
spec.revision.sourceSha256,
|
|
834
|
+
spec.revision.sourceFormat
|
|
835
|
+
);
|
|
836
|
+
setBinding(workflow, options.bindings.sourceImage, uploadedSource);
|
|
837
|
+
if (spec.revision.maskFile) {
|
|
838
|
+
if (!spec.revision.maskSha256 || !spec.revision.maskFormat) {
|
|
839
|
+
throw new Error("ComfyUI revision mask is not ready");
|
|
840
|
+
}
|
|
841
|
+
const maskBytes = await readFile(spec.revision.maskFile);
|
|
842
|
+
if (sha256(maskBytes) !== spec.revision.maskSha256) {
|
|
843
|
+
throw new Error("ComfyUI revision mask changed before upload");
|
|
844
|
+
}
|
|
845
|
+
const uploadedMask = await this.client.uploadImage(
|
|
846
|
+
maskBytes,
|
|
847
|
+
spec.revision.maskSha256,
|
|
848
|
+
spec.revision.maskFormat
|
|
849
|
+
);
|
|
850
|
+
setBinding(workflow, options.bindings.maskImage, uploadedMask);
|
|
851
|
+
}
|
|
852
|
+
if (spec.revision.strength != null) {
|
|
853
|
+
setBinding(workflow, options.bindings.strength, spec.revision.strength);
|
|
854
|
+
}
|
|
855
|
+
}
|
|
754
856
|
const promptId = await this.client.submit(workflow);
|
|
755
|
-
return {
|
|
857
|
+
return {
|
|
858
|
+
jobId: encodeJob(promptId, options.outputNodeId),
|
|
859
|
+
...spec.revision ? {
|
|
860
|
+
metadata: {
|
|
861
|
+
revision: {
|
|
862
|
+
mode: spec.revision.mode,
|
|
863
|
+
sourceAssetId: spec.revision.sourceAssetId,
|
|
864
|
+
sourceSha256: spec.revision.sourceSha256,
|
|
865
|
+
...spec.revision.maskSha256 ? { maskSha256: spec.revision.maskSha256 } : {}
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
} : {}
|
|
869
|
+
};
|
|
756
870
|
}
|
|
757
871
|
async poll(jobId, _generator, context) {
|
|
758
872
|
const { promptId, outputNodeId } = decodeJob(jobId);
|
|
@@ -827,15 +941,27 @@ function parseOptions(value) {
|
|
|
827
941
|
throw new Error("ComfyUI numImages must be a whole number from 1 to 16");
|
|
828
942
|
}
|
|
829
943
|
if (!isObject(value.bindings)) throw new Error("ComfyUI bindings must be an object");
|
|
830
|
-
const bindingKeys = /* @__PURE__ */ new Set([
|
|
944
|
+
const bindingKeys = /* @__PURE__ */ new Set([
|
|
945
|
+
"prompt",
|
|
946
|
+
"width",
|
|
947
|
+
"height",
|
|
948
|
+
"batchSize",
|
|
949
|
+
"seed",
|
|
950
|
+
"sourceImage",
|
|
951
|
+
"maskImage",
|
|
952
|
+
"strength"
|
|
953
|
+
]);
|
|
831
954
|
const extraBindings = Object.keys(value.bindings).filter((key) => !bindingKeys.has(key));
|
|
832
955
|
if (extraBindings.length) throw new Error(`Unknown ComfyUI binding(s): ${extraBindings.join(", ")}`);
|
|
833
956
|
const bindings = {
|
|
834
957
|
prompt: parseBinding(value.bindings.prompt, "prompt"),
|
|
835
|
-
width: parseBinding(value.bindings.width, "width"),
|
|
836
|
-
height: parseBinding(value.bindings.height, "height"),
|
|
837
|
-
batchSize: parseBinding(value.bindings.batchSize, "batchSize"),
|
|
838
|
-
...value.bindings.seed == null ? {} : { seed: parseBinding(value.bindings.seed, "seed") }
|
|
958
|
+
...value.bindings.width == null ? {} : { width: parseBinding(value.bindings.width, "width") },
|
|
959
|
+
...value.bindings.height == null ? {} : { height: parseBinding(value.bindings.height, "height") },
|
|
960
|
+
...value.bindings.batchSize == null ? {} : { batchSize: parseBinding(value.bindings.batchSize, "batchSize") },
|
|
961
|
+
...value.bindings.seed == null ? {} : { seed: parseBinding(value.bindings.seed, "seed") },
|
|
962
|
+
...value.bindings.sourceImage == null ? {} : { sourceImage: parseBinding(value.bindings.sourceImage, "sourceImage") },
|
|
963
|
+
...value.bindings.maskImage == null ? {} : { maskImage: parseBinding(value.bindings.maskImage, "maskImage") },
|
|
964
|
+
...value.bindings.strength == null ? {} : { strength: parseBinding(value.bindings.strength, "strength") }
|
|
839
965
|
};
|
|
840
966
|
const workflow = value.workflow == null ? void 0 : parseWorkflow(value.workflow, workflowFile);
|
|
841
967
|
const workflowSha256 = value.workflowSha256 == null ? void 0 : requiredString(value.workflowSha256, "workflowSha256");
|
|
@@ -928,7 +1054,16 @@ function comfyMetadata(spec, promptId, outputNodeId, images) {
|
|
|
928
1054
|
outputCount: images.length,
|
|
929
1055
|
workflowFile: options.workflowFile,
|
|
930
1056
|
workflowSha256: options.workflowSha256,
|
|
931
|
-
files: images.map((image) => ({ ...image }))
|
|
1057
|
+
files: images.map((image) => ({ ...image })),
|
|
1058
|
+
...spec.revision ? {
|
|
1059
|
+
revision: {
|
|
1060
|
+
mode: spec.revision.mode,
|
|
1061
|
+
sourceAssetId: spec.revision.sourceAssetId,
|
|
1062
|
+
sourceSha256: spec.revision.sourceSha256,
|
|
1063
|
+
...spec.revision.maskSha256 ? { maskSha256: spec.revision.maskSha256 } : {},
|
|
1064
|
+
...spec.revision.strength == null ? {} : { strength: spec.revision.strength }
|
|
1065
|
+
}
|
|
1066
|
+
} : {}
|
|
932
1067
|
};
|
|
933
1068
|
}
|
|
934
1069
|
function encodeJob(promptId, outputNodeId) {
|
|
@@ -1129,11 +1264,11 @@ var PixelLabClient = class {
|
|
|
1129
1264
|
* common than the failure mode of retrying (a duplicate object), and a
|
|
1130
1265
|
* duplicate is visible and free to delete whereas a silent gap is neither.
|
|
1131
1266
|
*/
|
|
1132
|
-
async request(
|
|
1267
|
+
async request(path25, init, attempt = 0) {
|
|
1133
1268
|
const auth = this.apiKey.startsWith("Bearer ") ? this.apiKey : `Bearer ${this.apiKey}`;
|
|
1134
1269
|
let res;
|
|
1135
1270
|
try {
|
|
1136
|
-
res = await fetch(`${BASE}${
|
|
1271
|
+
res = await fetch(`${BASE}${path25}`, {
|
|
1137
1272
|
...init,
|
|
1138
1273
|
signal: init?.signal ?? AbortSignal.timeout(this.timeoutMs),
|
|
1139
1274
|
headers: {
|
|
@@ -1145,24 +1280,24 @@ var PixelLabClient = class {
|
|
|
1145
1280
|
} catch (err) {
|
|
1146
1281
|
if (attempt < MAX_RETRIES) {
|
|
1147
1282
|
await sleep(backoffMs(attempt));
|
|
1148
|
-
return this.request(
|
|
1283
|
+
return this.request(path25, init, attempt + 1);
|
|
1149
1284
|
}
|
|
1150
1285
|
throw err;
|
|
1151
1286
|
}
|
|
1152
1287
|
if (!res.ok && shouldRetry(res.status) && attempt < MAX_RETRIES) {
|
|
1153
1288
|
const waitMs = retryAfterMs(res.headers.get("retry-after")) ?? backoffMs(attempt);
|
|
1154
1289
|
await sleep(waitMs);
|
|
1155
|
-
return this.request(
|
|
1290
|
+
return this.request(path25, init, attempt + 1);
|
|
1156
1291
|
}
|
|
1157
1292
|
const text = await res.text();
|
|
1158
1293
|
if (!res.ok) {
|
|
1159
|
-
throw new PixelLabError(`${init?.method ?? "GET"} ${
|
|
1294
|
+
throw new PixelLabError(`${init?.method ?? "GET"} ${path25} \u2192 ${res.status}`, res.status, text);
|
|
1160
1295
|
}
|
|
1161
1296
|
if (!text) return {};
|
|
1162
1297
|
try {
|
|
1163
1298
|
return JSON.parse(text);
|
|
1164
1299
|
} catch {
|
|
1165
|
-
throw new Error(`${init?.method ?? "GET"} ${
|
|
1300
|
+
throw new Error(`${init?.method ?? "GET"} ${path25} returned invalid JSON`);
|
|
1166
1301
|
}
|
|
1167
1302
|
}
|
|
1168
1303
|
async balance() {
|
|
@@ -1369,6 +1504,7 @@ import { z as z2 } from "zod";
|
|
|
1369
1504
|
var MediaTypeSchema = z2.enum(["image/png", "image/gif"]);
|
|
1370
1505
|
var GeneratorSchema = z2.enum(["1dir", "map", "pixflux", "tiles", "animation"]);
|
|
1371
1506
|
var GridConfidenceSchema = z2.enum(["low", "medium", "high"]);
|
|
1507
|
+
var RevisionModeSchema = z2.enum(["image-to-image", "inpaint", "outpaint"]);
|
|
1372
1508
|
function tileVariationCount(descriptions) {
|
|
1373
1509
|
return Math.max(1, descriptions) * 4;
|
|
1374
1510
|
}
|
|
@@ -1419,6 +1555,31 @@ var QualityProfileSchema = z2.object({
|
|
|
1419
1555
|
(profile) => new Set(profile.palette.map((color) => color.replace(/^#/, "").toLowerCase())).size === profile.palette.length,
|
|
1420
1556
|
{ message: "quality palette colors must be unique", path: ["palette"] }
|
|
1421
1557
|
);
|
|
1558
|
+
var RevisionSchema = z2.object({
|
|
1559
|
+
/** What kind of controlled change the provider workflow performs. */
|
|
1560
|
+
mode: RevisionModeSchema,
|
|
1561
|
+
/** Parent asset id in the same style. */
|
|
1562
|
+
from: z2.string().min(1),
|
|
1563
|
+
/** Manifest-relative black/white PNG. Required only for masked inpainting. */
|
|
1564
|
+
mask: z2.string().min(1).optional(),
|
|
1565
|
+
/** Provider-neutral edit strength. The active adapter must bind it explicitly. */
|
|
1566
|
+
strength: z2.number().min(0).max(1).optional()
|
|
1567
|
+
}).strict().superRefine((revision, context) => {
|
|
1568
|
+
if (revision.mode === "inpaint" && !revision.mask) {
|
|
1569
|
+
context.addIssue({
|
|
1570
|
+
code: z2.ZodIssueCode.custom,
|
|
1571
|
+
message: "inpaint revisions require a mask",
|
|
1572
|
+
path: ["mask"]
|
|
1573
|
+
});
|
|
1574
|
+
}
|
|
1575
|
+
if (revision.mode !== "inpaint" && revision.mask) {
|
|
1576
|
+
context.addIssue({
|
|
1577
|
+
code: z2.ZodIssueCode.custom,
|
|
1578
|
+
message: `${revision.mode} revisions do not accept a mask`,
|
|
1579
|
+
path: ["mask"]
|
|
1580
|
+
});
|
|
1581
|
+
}
|
|
1582
|
+
});
|
|
1422
1583
|
var StyleSchema = z2.object({
|
|
1423
1584
|
/** Generation backend for this style. Omit to inherit the manifest default. */
|
|
1424
1585
|
provider: z2.string().min(1).optional(),
|
|
@@ -1584,6 +1745,8 @@ var AssetSchema = z2.object({
|
|
|
1584
1745
|
* that wasn't. `prompt` still records what was asked for.
|
|
1585
1746
|
*/
|
|
1586
1747
|
source: z2.string().optional(),
|
|
1748
|
+
/** Controlled generation derived from another asset in the same style. */
|
|
1749
|
+
revision: RevisionSchema.optional(),
|
|
1587
1750
|
/**
|
|
1588
1751
|
* Generated output role to place in `cell` when this asset expands to
|
|
1589
1752
|
* several files. Omit for ordinary single-output assets. A structural set
|
|
@@ -1604,7 +1767,15 @@ var AssetSchema = z2.object({
|
|
|
1604
1767
|
* style would invalidate every other style's already-generated art.
|
|
1605
1768
|
*/
|
|
1606
1769
|
promptByStyle: z2.record(z2.string()).default({})
|
|
1607
|
-
}).strict()
|
|
1770
|
+
}).strict().superRefine((asset, context) => {
|
|
1771
|
+
if (asset.source && asset.revision) {
|
|
1772
|
+
context.addIssue({
|
|
1773
|
+
code: z2.ZodIssueCode.custom,
|
|
1774
|
+
message: "source and revision are mutually exclusive",
|
|
1775
|
+
path: ["revision"]
|
|
1776
|
+
});
|
|
1777
|
+
}
|
|
1778
|
+
});
|
|
1608
1779
|
var ManifestSchema = z2.object({
|
|
1609
1780
|
$schema: z2.string().optional(),
|
|
1610
1781
|
name: z2.string(),
|
|
@@ -1625,6 +1796,14 @@ var LockEntrySchema = z2.object({
|
|
|
1625
1796
|
prompt: z2.string(),
|
|
1626
1797
|
width: z2.number().int(),
|
|
1627
1798
|
height: z2.number().int(),
|
|
1799
|
+
/** Explicit parent/input lineage for a revision generation. */
|
|
1800
|
+
revision: z2.object({
|
|
1801
|
+
mode: RevisionModeSchema,
|
|
1802
|
+
sourceAssetId: z2.string().min(1),
|
|
1803
|
+
sourceSha256: z2.string().regex(/^[0-9a-f]{64}$/),
|
|
1804
|
+
maskSha256: z2.string().regex(/^[0-9a-f]{64}$/).optional(),
|
|
1805
|
+
strength: z2.number().min(0).max(1).optional()
|
|
1806
|
+
}).strict().nullable().default(null),
|
|
1628
1807
|
/** Set at submit time, before the request is awaited, so a crash is recoverable. */
|
|
1629
1808
|
jobId: z2.string().nullable().default(null),
|
|
1630
1809
|
/** For `1dir`: the multi-candidate parent object awaiting selection. */
|
|
@@ -2032,9 +2211,9 @@ var RetroDiffusionClient = class {
|
|
|
2032
2211
|
}
|
|
2033
2212
|
return balance;
|
|
2034
2213
|
}
|
|
2035
|
-
async call(
|
|
2214
|
+
async call(path25, init = {}) {
|
|
2036
2215
|
if (!this.token) throw new Error("RD_API_KEY is not set");
|
|
2037
|
-
const response = await this.request(`${this.baseUrl}${
|
|
2216
|
+
const response = await this.request(`${this.baseUrl}${path25}`, {
|
|
2038
2217
|
...init,
|
|
2039
2218
|
headers: {
|
|
2040
2219
|
"Content-Type": "application/json",
|
|
@@ -2908,7 +3087,36 @@ ${issues}`);
|
|
|
2908
3087
|
unknownReferences.push(`assets.${assetId}.promptByStyle: unknown style "${styleId}"`);
|
|
2909
3088
|
}
|
|
2910
3089
|
}
|
|
3090
|
+
if (asset.revision) {
|
|
3091
|
+
if (!parsed.data.assets[asset.revision.from]) {
|
|
3092
|
+
unknownReferences.push(
|
|
3093
|
+
`assets.${assetId}.revision.from: unknown asset "${asset.revision.from}"`
|
|
3094
|
+
);
|
|
3095
|
+
} else if (asset.revision.from === assetId) {
|
|
3096
|
+
unknownReferences.push(`assets.${assetId}.revision.from: an asset cannot revise itself`);
|
|
3097
|
+
}
|
|
3098
|
+
}
|
|
2911
3099
|
}
|
|
3100
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
3101
|
+
const visited = /* @__PURE__ */ new Set();
|
|
3102
|
+
const visitRevision = (assetId, chain) => {
|
|
3103
|
+
if (visited.has(assetId)) return;
|
|
3104
|
+
if (visiting.has(assetId)) {
|
|
3105
|
+
const start = chain.indexOf(assetId);
|
|
3106
|
+
unknownReferences.push(
|
|
3107
|
+
`assets.${assetId}.revision.from: revision cycle ${[...chain.slice(start), assetId].join(" -> ")}`
|
|
3108
|
+
);
|
|
3109
|
+
return;
|
|
3110
|
+
}
|
|
3111
|
+
visiting.add(assetId);
|
|
3112
|
+
const parent = parsed.data.assets[assetId]?.revision?.from;
|
|
3113
|
+
if (parent && parent !== assetId && parsed.data.assets[parent]) {
|
|
3114
|
+
visitRevision(parent, [...chain, assetId]);
|
|
3115
|
+
}
|
|
3116
|
+
visiting.delete(assetId);
|
|
3117
|
+
visited.add(assetId);
|
|
3118
|
+
};
|
|
3119
|
+
for (const assetId of Object.keys(parsed.data.assets)) visitRevision(assetId, []);
|
|
2912
3120
|
if (unknownReferences.length) {
|
|
2913
3121
|
throw new Error(`Manifest at ${abs} is invalid:
|
|
2914
3122
|
${unknownReferences.map((i) => ` ${i}`).join("\n")}`);
|
|
@@ -2944,6 +3152,15 @@ async function resolveSpecs(loaded, filter) {
|
|
|
2944
3152
|
throw new Error(`Unknown asset "${unknownAsset}".`);
|
|
2945
3153
|
}
|
|
2946
3154
|
}
|
|
3155
|
+
const requestedAssetIds = new Set(filter?.assets?.length ? filter.assets : Object.keys(manifest.assets));
|
|
3156
|
+
const resolutionAssetIds = new Set(requestedAssetIds);
|
|
3157
|
+
for (const assetId of [...requestedAssetIds]) {
|
|
3158
|
+
let current = manifest.assets[assetId]?.revision?.from;
|
|
3159
|
+
while (current && !resolutionAssetIds.has(current)) {
|
|
3160
|
+
resolutionAssetIds.add(current);
|
|
3161
|
+
current = manifest.assets[current]?.revision?.from;
|
|
3162
|
+
}
|
|
3163
|
+
}
|
|
2947
3164
|
const styleImageCache = /* @__PURE__ */ new Map();
|
|
2948
3165
|
async function loadStyleImage(rel) {
|
|
2949
3166
|
const abs = path4.resolve(root, rel);
|
|
@@ -2975,8 +3192,13 @@ async function resolveSpecs(loaded, filter) {
|
|
|
2975
3192
|
styleImageHashes.push(loadedImage.hash);
|
|
2976
3193
|
styleImageDimensions.push(loadedImage);
|
|
2977
3194
|
}
|
|
3195
|
+
const resolvedImages = style.styleImages.map((image) => {
|
|
3196
|
+
const hit = styleImageCache.get(path4.resolve(root, image.path));
|
|
3197
|
+
return { base64: hit.base64, width: hit.width, height: hit.height, format: hit.format };
|
|
3198
|
+
});
|
|
3199
|
+
const styleSpecs = /* @__PURE__ */ new Map();
|
|
2978
3200
|
for (const [assetId, asset] of Object.entries(manifest.assets)) {
|
|
2979
|
-
if (
|
|
3201
|
+
if (!resolutionAssetIds.has(assetId)) continue;
|
|
2980
3202
|
if (asset.styles.length && !asset.styles.includes(styleId)) continue;
|
|
2981
3203
|
const generator = style.generator;
|
|
2982
3204
|
if (!activeProvider.supports(generator)) {
|
|
@@ -3056,18 +3278,69 @@ async function resolveSpecs(loaded, filter) {
|
|
|
3056
3278
|
} : {},
|
|
3057
3279
|
tags,
|
|
3058
3280
|
source: asset.source,
|
|
3059
|
-
|
|
3281
|
+
// Revision identity is attached after every dependency in this style
|
|
3282
|
+
// has a concrete output target. Finalization below computes the hash.
|
|
3283
|
+
specHash: ""
|
|
3060
3284
|
};
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3285
|
+
styleSpecs.set(assetId, resolved);
|
|
3286
|
+
}
|
|
3287
|
+
const finalized = /* @__PURE__ */ new Set();
|
|
3288
|
+
const finalize = async (assetId) => {
|
|
3289
|
+
const resolved = styleSpecs.get(assetId);
|
|
3290
|
+
if (!resolved) {
|
|
3291
|
+
throw new Error(
|
|
3292
|
+
`Asset "${assetId}" is not available in style "${styleId}"; revision parents must participate in the same style.`
|
|
3293
|
+
);
|
|
3294
|
+
}
|
|
3295
|
+
if (finalized.has(assetId)) return resolved;
|
|
3296
|
+
const asset = manifest.assets[assetId];
|
|
3297
|
+
if (asset.revision) {
|
|
3298
|
+
if (!activeProvider.supportsRevision?.(asset.revision.mode)) {
|
|
3299
|
+
throw new Error(
|
|
3300
|
+
`Provider "${activeProvider.id}" does not support ${asset.revision.mode} revisions`
|
|
3301
|
+
);
|
|
3302
|
+
}
|
|
3303
|
+
const sourceSpec = await finalize(asset.revision.from);
|
|
3304
|
+
const sourceFile = sourceSpec.quality?.outFile ?? (sourceSpec.source ? path4.resolve(root, sourceSpec.source) : sourceSpec.outFile);
|
|
3305
|
+
const sourceImage = await optionalRevisionImage(sourceFile, "revision source");
|
|
3306
|
+
const maskFile = asset.revision.mask ? path4.resolve(root, asset.revision.mask) : void 0;
|
|
3307
|
+
const maskImage = maskFile ? await optionalRevisionImage(maskFile, "revision mask", "png") : null;
|
|
3308
|
+
if (sourceImage && maskImage && (sourceImage.width !== maskImage.width || sourceImage.height !== maskImage.height)) {
|
|
3309
|
+
throw new Error(
|
|
3310
|
+
`Revision mask for ${styleId}/${assetId} is ${maskImage.width}x${maskImage.height}; source ${asset.revision.from} is ${sourceImage.width}x${sourceImage.height}`
|
|
3311
|
+
);
|
|
3312
|
+
}
|
|
3313
|
+
resolved.revision = {
|
|
3314
|
+
mode: asset.revision.mode,
|
|
3315
|
+
sourceAssetId: asset.revision.from,
|
|
3316
|
+
sourceFile,
|
|
3317
|
+
sourceSha256: sourceImage?.hash ?? null,
|
|
3318
|
+
sourceWidth: sourceImage?.width ?? null,
|
|
3319
|
+
sourceHeight: sourceImage?.height ?? null,
|
|
3320
|
+
sourceFormat: sourceImage?.format ?? null,
|
|
3321
|
+
sourceSpec,
|
|
3322
|
+
...maskFile ? {
|
|
3323
|
+
maskFile,
|
|
3324
|
+
maskSha256: maskImage?.hash ?? null,
|
|
3325
|
+
maskWidth: maskImage?.width ?? null,
|
|
3326
|
+
maskHeight: maskImage?.height ?? null,
|
|
3327
|
+
maskFormat: maskImage?.format ?? null
|
|
3328
|
+
} : {},
|
|
3329
|
+
...asset.revision.strength == null ? {} : { strength: asset.revision.strength }
|
|
3330
|
+
};
|
|
3331
|
+
}
|
|
3332
|
+
resolved.specHash = specHash(resolved, styleImageHashes, providerOptionIdentity);
|
|
3065
3333
|
activeProvider.validate?.(resolved, resolvedImages);
|
|
3066
3334
|
const estimate = validateCostEstimate(activeProvider.id, activeProvider.estimate(resolved));
|
|
3067
3335
|
resolved.cost = estimate.amount;
|
|
3068
3336
|
resolved.costUnit = estimate.unit;
|
|
3069
3337
|
resolved.candidates = estimate.candidates;
|
|
3070
|
-
|
|
3338
|
+
finalized.add(assetId);
|
|
3339
|
+
return resolved;
|
|
3340
|
+
};
|
|
3341
|
+
for (const assetId of Object.keys(manifest.assets)) {
|
|
3342
|
+
if (!requestedAssetIds.has(assetId) || !styleSpecs.has(assetId)) continue;
|
|
3343
|
+
specs.push(await finalize(assetId));
|
|
3071
3344
|
}
|
|
3072
3345
|
}
|
|
3073
3346
|
const byOutput = /* @__PURE__ */ new Map();
|
|
@@ -3089,6 +3362,16 @@ function pngPath(file) {
|
|
|
3089
3362
|
const extension = path4.extname(file);
|
|
3090
3363
|
return extension ? `${file.slice(0, -extension.length)}.png` : `${file}.png`;
|
|
3091
3364
|
}
|
|
3365
|
+
async function optionalRevisionImage(file, label, requiredFormat) {
|
|
3366
|
+
if (!existsSync3(file)) return null;
|
|
3367
|
+
const bytes = await readFile2(file);
|
|
3368
|
+
const metadata = imageMetadata(bytes);
|
|
3369
|
+
if (!metadata) throw new Error(`${label} is not a readable PNG or JPEG: ${file}`);
|
|
3370
|
+
if (requiredFormat && metadata.format !== requiredFormat) {
|
|
3371
|
+
throw new Error(`${label} must be ${requiredFormat.toUpperCase()}: ${file}`);
|
|
3372
|
+
}
|
|
3373
|
+
return { hash: sha256(bytes), ...metadata };
|
|
3374
|
+
}
|
|
3092
3375
|
async function resolveStyleImages(loaded, styleId) {
|
|
3093
3376
|
const style = loaded.manifest.styles[styleId];
|
|
3094
3377
|
if (!style) throw new Error(`Unknown style "${styleId}"`);
|
|
@@ -3677,8 +3960,8 @@ function mountStyle(lock, styleId, manifestDir, mount, cells, sources = {}, outp
|
|
|
3677
3960
|
}
|
|
3678
3961
|
|
|
3679
3962
|
// src/pipeline/plan.ts
|
|
3680
|
-
import { existsSync as
|
|
3681
|
-
import
|
|
3963
|
+
import { existsSync as existsSync8 } from "fs";
|
|
3964
|
+
import path13 from "path";
|
|
3682
3965
|
|
|
3683
3966
|
// src/pipeline/quality-profile.ts
|
|
3684
3967
|
import { existsSync as existsSync6 } from "fs";
|
|
@@ -4831,6 +5114,83 @@ async function requireApprovedQualitySources(specs, lock, assetIds) {
|
|
|
4831
5114
|
return sources;
|
|
4832
5115
|
}
|
|
4833
5116
|
|
|
5117
|
+
// src/pipeline/revision.ts
|
|
5118
|
+
import { existsSync as existsSync7 } from "fs";
|
|
5119
|
+
import path12 from "path";
|
|
5120
|
+
async function inspectRevisionReadiness(spec, lock) {
|
|
5121
|
+
if (!spec.revision) return null;
|
|
5122
|
+
return inspectRevision(spec, lock, /* @__PURE__ */ new Set());
|
|
5123
|
+
}
|
|
5124
|
+
async function inspectRevision(spec, lock, seen) {
|
|
5125
|
+
const revision = spec.revision;
|
|
5126
|
+
if (!revision) return { ready: true, reason: "asset has no revision inputs" };
|
|
5127
|
+
const dependency = await inspectParent(revision.sourceSpec, lock, seen);
|
|
5128
|
+
if (!dependency.ready) {
|
|
5129
|
+
return {
|
|
5130
|
+
ready: false,
|
|
5131
|
+
reason: `revision source ${spec.styleId}/${revision.sourceAssetId} is not ready: ${dependency.reason}`
|
|
5132
|
+
};
|
|
5133
|
+
}
|
|
5134
|
+
if (!revision.sourceSha256 || !existsSync7(revision.sourceFile)) {
|
|
5135
|
+
return { ready: false, reason: `revision source is missing: ${revision.sourceFile}` };
|
|
5136
|
+
}
|
|
5137
|
+
if (await sha256File(revision.sourceFile) !== revision.sourceSha256) {
|
|
5138
|
+
return { ready: false, reason: "revision source changed after the manifest was resolved" };
|
|
5139
|
+
}
|
|
5140
|
+
if (revision.maskFile) {
|
|
5141
|
+
if (!revision.maskSha256 || !existsSync7(revision.maskFile)) {
|
|
5142
|
+
return { ready: false, reason: `revision mask is missing: ${revision.maskFile}` };
|
|
5143
|
+
}
|
|
5144
|
+
if (await sha256File(revision.maskFile) !== revision.maskSha256) {
|
|
5145
|
+
return { ready: false, reason: "revision mask changed after the manifest was resolved" };
|
|
5146
|
+
}
|
|
5147
|
+
}
|
|
5148
|
+
return { ready: true, reason: "revision inputs are current" };
|
|
5149
|
+
}
|
|
5150
|
+
async function requireRevisionReady(spec, lock) {
|
|
5151
|
+
const inspection = await inspectRevisionReadiness(spec, lock);
|
|
5152
|
+
if (inspection && !inspection.ready) throw new Error(inspection.reason);
|
|
5153
|
+
}
|
|
5154
|
+
async function inspectParent(spec, lock, seen) {
|
|
5155
|
+
const key = lockKey(spec.styleId, spec.assetId);
|
|
5156
|
+
if (seen.has(key)) return { ready: false, reason: `revision dependency cycle at ${key}` };
|
|
5157
|
+
const nextSeen = new Set(seen);
|
|
5158
|
+
nextSeen.add(key);
|
|
5159
|
+
if (spec.revision) {
|
|
5160
|
+
const inputs = await inspectRevision(spec, lock, nextSeen);
|
|
5161
|
+
if (!inputs.ready) return inputs;
|
|
5162
|
+
}
|
|
5163
|
+
if (spec.quality) {
|
|
5164
|
+
const quality = await inspectQualityProfile(spec, lock);
|
|
5165
|
+
return quality?.state === "approved" ? { ready: true, reason: "approved quality output is current" } : {
|
|
5166
|
+
ready: false,
|
|
5167
|
+
reason: `quality output is ${quality?.state ?? "missing"}` + (quality?.reason ? ` (${quality.reason})` : "")
|
|
5168
|
+
};
|
|
5169
|
+
}
|
|
5170
|
+
if (spec.source) {
|
|
5171
|
+
const source = path12.resolve(spec.root, spec.source);
|
|
5172
|
+
return existsSync7(source) ? { ready: true, reason: "declared source is present" } : { ready: false, reason: `declared source is missing: ${spec.source}` };
|
|
5173
|
+
}
|
|
5174
|
+
const entry = lock.entries[key];
|
|
5175
|
+
if (!entry) return { ready: false, reason: "parent is not in the lockfile" };
|
|
5176
|
+
if (entry.specHash !== spec.specHash) {
|
|
5177
|
+
return { ready: false, reason: "parent generation spec is stale" };
|
|
5178
|
+
}
|
|
5179
|
+
if (entry.status !== "downloaded") {
|
|
5180
|
+
return { ready: false, reason: `parent is ${entry.status}, not downloaded` };
|
|
5181
|
+
}
|
|
5182
|
+
if (!entry.outputs.length) return { ready: false, reason: "parent has no recorded output" };
|
|
5183
|
+
for (let index = 0; index < entry.outputs.length; index++) {
|
|
5184
|
+
const output = entry.outputs[index];
|
|
5185
|
+
const file = currentEntryOutputPath(entry, spec, index);
|
|
5186
|
+
if (!existsSync7(file)) return { ready: false, reason: `parent output is missing: ${file}` };
|
|
5187
|
+
if (await sha256File(file) !== output.sha256) {
|
|
5188
|
+
return { ready: false, reason: `parent output was modified: ${file}` };
|
|
5189
|
+
}
|
|
5190
|
+
}
|
|
5191
|
+
return { ready: true, reason: "parent output is current" };
|
|
5192
|
+
}
|
|
5193
|
+
|
|
4834
5194
|
// src/pipeline/plan.ts
|
|
4835
5195
|
async function anyOutputModified(entry, spec) {
|
|
4836
5196
|
for (let index = 0; index < entry.outputs.length; index++) {
|
|
@@ -4846,11 +5206,15 @@ async function buildPlan(specs, lock, opts = {}) {
|
|
|
4846
5206
|
const entry = lock.entries[key];
|
|
4847
5207
|
let state;
|
|
4848
5208
|
let reason;
|
|
4849
|
-
|
|
5209
|
+
const revision = await inspectRevisionReadiness(spec, lock);
|
|
5210
|
+
if (revision && !revision.ready) {
|
|
5211
|
+
state = "blocked";
|
|
5212
|
+
reason = revision.reason;
|
|
5213
|
+
} else if (opts.force) {
|
|
4850
5214
|
state = "missing";
|
|
4851
5215
|
reason = "--force";
|
|
4852
5216
|
} else if (!entry && spec.source) {
|
|
4853
|
-
if (
|
|
5217
|
+
if (existsSync8(path13.resolve(spec.root, spec.source))) {
|
|
4854
5218
|
state = "ok";
|
|
4855
5219
|
reason = `placed from ${spec.source}; not generated`;
|
|
4856
5220
|
} else {
|
|
@@ -4858,7 +5222,7 @@ async function buildPlan(specs, lock, opts = {}) {
|
|
|
4858
5222
|
reason = `declared source is missing: ${spec.source}`;
|
|
4859
5223
|
}
|
|
4860
5224
|
} else if (!entry) {
|
|
4861
|
-
if (
|
|
5225
|
+
if (existsSync8(spec.outFile)) {
|
|
4862
5226
|
state = "untracked";
|
|
4863
5227
|
reason = "file on disk, no provenance recorded";
|
|
4864
5228
|
} else {
|
|
@@ -4877,9 +5241,9 @@ async function buildPlan(specs, lock, opts = {}) {
|
|
|
4877
5241
|
} else if (entry.status !== "downloaded") {
|
|
4878
5242
|
state = "in-flight";
|
|
4879
5243
|
reason = `awaiting ${entry.status}`;
|
|
4880
|
-
} else if (entry.outputs.length === 0 || entry.outputs.some((_output, index) => !
|
|
5244
|
+
} else if (entry.outputs.length === 0 || entry.outputs.some((_output, index) => !existsSync8(currentEntryOutputPath(entry, spec, index)))) {
|
|
4881
5245
|
state = "orphaned";
|
|
4882
|
-
reason = entry.outputs.length === 0 ? "no recorded output files" : `missing on disk: ${entry.outputs.filter((_output, index) => !
|
|
5246
|
+
reason = entry.outputs.length === 0 ? "no recorded output files" : `missing on disk: ${entry.outputs.filter((_output, index) => !existsSync8(currentEntryOutputPath(entry, spec, index))).map((o) => o.role ?? "asset").join(", ")}`;
|
|
4883
5247
|
} else if (await anyOutputModified(entry, spec)) {
|
|
4884
5248
|
state = "orphaned";
|
|
4885
5249
|
reason = "output modified since download";
|
|
@@ -4936,6 +5300,7 @@ function summarize(plan) {
|
|
|
4936
5300
|
orphaned: 0,
|
|
4937
5301
|
"in-flight": 0,
|
|
4938
5302
|
recoverable: 0,
|
|
5303
|
+
blocked: 0,
|
|
4939
5304
|
failed: 0
|
|
4940
5305
|
};
|
|
4941
5306
|
for (const item of plan.items) counts[item.state]++;
|
|
@@ -5008,6 +5373,7 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
|
|
|
5008
5373
|
await waitForSlot();
|
|
5009
5374
|
const since = Date.now() - lastSubmitAt;
|
|
5010
5375
|
if (since < spacing) await sleep2(spacing - since);
|
|
5376
|
+
await requireRevisionReady(spec, lock);
|
|
5011
5377
|
upsert(lock, key, {
|
|
5012
5378
|
styleId: spec.styleId,
|
|
5013
5379
|
assetId: spec.assetId,
|
|
@@ -5017,6 +5383,13 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
|
|
|
5017
5383
|
prompt: spec.prompt,
|
|
5018
5384
|
width: spec.width,
|
|
5019
5385
|
height: spec.height,
|
|
5386
|
+
revision: spec.revision ? {
|
|
5387
|
+
mode: spec.revision.mode,
|
|
5388
|
+
sourceAssetId: spec.revision.sourceAssetId,
|
|
5389
|
+
sourceSha256: spec.revision.sourceSha256,
|
|
5390
|
+
...spec.revision.maskSha256 ? { maskSha256: spec.revision.maskSha256 } : {},
|
|
5391
|
+
...spec.revision.strength == null ? {} : { strength: spec.revision.strength }
|
|
5392
|
+
} : null,
|
|
5020
5393
|
status: "pending",
|
|
5021
5394
|
jobId: null,
|
|
5022
5395
|
objectId: null,
|
|
@@ -5142,9 +5515,9 @@ async function poll(provider, lock, lockPath, opts = {}) {
|
|
|
5142
5515
|
}
|
|
5143
5516
|
|
|
5144
5517
|
// src/pipeline/fetch.ts
|
|
5145
|
-
import { existsSync as
|
|
5518
|
+
import { existsSync as existsSync9 } from "fs";
|
|
5146
5519
|
import { mkdir as mkdir3, readFile as readFile7, rename as rename3, rm as rm4, writeFile as writeFile3 } from "fs/promises";
|
|
5147
|
-
import
|
|
5520
|
+
import path14 from "path";
|
|
5148
5521
|
|
|
5149
5522
|
// src/source-url.ts
|
|
5150
5523
|
var SENSITIVE_QUERY_KEYS = /* @__PURE__ */ new Set([
|
|
@@ -5190,13 +5563,13 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
5190
5563
|
const result = { downloaded: 0, skipped: 0, failed: 0 };
|
|
5191
5564
|
const specByKey = new Map(specs.map((s) => [lockKey(s.styleId, s.assetId), s]));
|
|
5192
5565
|
normalizeLockOutputPaths(lock, specs);
|
|
5193
|
-
const cacheDir = opts.cacheDir === false ? null :
|
|
5566
|
+
const cacheDir = opts.cacheDir === false ? null : path14.resolve(opts.cacheDir ?? path14.join(path14.dirname(lockPath), ".pixelkiln", "cache"));
|
|
5194
5567
|
const pending = Object.entries(lock.entries).filter(([key, e]) => {
|
|
5195
5568
|
if (e.provider !== provider.id || !specByKey.has(key)) return false;
|
|
5196
5569
|
if (e.status === "selected" || e.status === "download-failed") return true;
|
|
5197
5570
|
if (!opts.repair || e.status !== "downloaded") return false;
|
|
5198
5571
|
const spec = specByKey.get(key);
|
|
5199
|
-
return e.outputs.length === 0 || !spec || e.outputs.some((output) => !
|
|
5572
|
+
return e.outputs.length === 0 || !spec || e.outputs.some((output) => !existsSync9(resolveOutputPath(output.path, spec.root)));
|
|
5200
5573
|
});
|
|
5201
5574
|
const concurrency = Math.min(Math.max(1, opts.concurrency ?? 8), Math.max(1, pending.length));
|
|
5202
5575
|
let cursor = 0;
|
|
@@ -5233,7 +5606,7 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
5233
5606
|
(o) => source.role ? o.role === source.role : !o.role && sources.length === 1
|
|
5234
5607
|
);
|
|
5235
5608
|
const target = recorded ? resolveOutputPath(recorded.path, spec.root) : expectedOutputPath(spec, source.role, index, sources.length, source.mediaType);
|
|
5236
|
-
if (
|
|
5609
|
+
if (existsSync9(target)) {
|
|
5237
5610
|
if (!recorded) {
|
|
5238
5611
|
throw new Error(`refusing to overwrite untracked output ${target}`);
|
|
5239
5612
|
}
|
|
@@ -5254,7 +5627,7 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
5254
5627
|
const expectedMediaType = source.mediaType ?? recorded?.mediaType ?? MediaType.PNG;
|
|
5255
5628
|
let buf = recorded && cacheDir ? await readCachedMedia(cacheDir, recorded.sha256, expectedMediaType) : null;
|
|
5256
5629
|
if (buf) {
|
|
5257
|
-
log2(` cached ${
|
|
5630
|
+
log2(` cached ${path14.relative(process.cwd(), target)}`);
|
|
5258
5631
|
} else {
|
|
5259
5632
|
if (!source.url) {
|
|
5260
5633
|
throw new Error(`no source URL or cached bytes remain for ${source.role ?? "asset"}`);
|
|
@@ -5272,7 +5645,7 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
5272
5645
|
);
|
|
5273
5646
|
}
|
|
5274
5647
|
if (cacheDir) await cacheMedia(cacheDir, buf, mediaType);
|
|
5275
|
-
await mkdir3(
|
|
5648
|
+
await mkdir3(path14.dirname(target), { recursive: true });
|
|
5276
5649
|
const tmp = `${target}.pixelkiln.tmp`;
|
|
5277
5650
|
await writeFile3(tmp, buf);
|
|
5278
5651
|
outputs.push({
|
|
@@ -5284,7 +5657,7 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
5284
5657
|
upsert(lock, key, { outputs: mergeOutputs(entry.outputs, outputs) });
|
|
5285
5658
|
await saveLock(lockPath, lock);
|
|
5286
5659
|
await rename3(tmp, target);
|
|
5287
|
-
log2(` wrote ${
|
|
5660
|
+
log2(` wrote ${path14.relative(process.cwd(), target)}`);
|
|
5288
5661
|
}
|
|
5289
5662
|
const persistentSources = sources.filter((source) => shouldPersistSourceUrl(source.url));
|
|
5290
5663
|
upsert(lock, key, {
|
|
@@ -5318,8 +5691,8 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
|
|
|
5318
5691
|
return result;
|
|
5319
5692
|
}
|
|
5320
5693
|
async function readCachedMedia(cacheDir, hash, mediaType) {
|
|
5321
|
-
const file =
|
|
5322
|
-
if (!
|
|
5694
|
+
const file = path14.join(cacheDir, cacheFileName(hash, mediaType));
|
|
5695
|
+
if (!existsSync9(file)) return null;
|
|
5323
5696
|
try {
|
|
5324
5697
|
const buf = await readFile7(file);
|
|
5325
5698
|
if (sha256(buf) !== hash) return null;
|
|
@@ -5332,7 +5705,7 @@ async function readCachedMedia(cacheDir, hash, mediaType) {
|
|
|
5332
5705
|
async function cacheMedia(cacheDir, buf, mediaType, knownHash) {
|
|
5333
5706
|
const hash = knownHash ?? sha256(buf);
|
|
5334
5707
|
validateMedia(buf, mediaType);
|
|
5335
|
-
const file =
|
|
5708
|
+
const file = path14.join(cacheDir, cacheFileName(hash, mediaType));
|
|
5336
5709
|
if (await readCachedMedia(cacheDir, hash, mediaType)) return;
|
|
5337
5710
|
await mkdir3(cacheDir, { recursive: true });
|
|
5338
5711
|
const tmp = `${file}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
@@ -5340,7 +5713,7 @@ async function cacheMedia(cacheDir, buf, mediaType, knownHash) {
|
|
|
5340
5713
|
try {
|
|
5341
5714
|
await rename3(tmp, file);
|
|
5342
5715
|
} catch (err) {
|
|
5343
|
-
if (!
|
|
5716
|
+
if (!existsSync9(file)) throw err;
|
|
5344
5717
|
} finally {
|
|
5345
5718
|
await rm4(tmp, { force: true });
|
|
5346
5719
|
}
|
|
@@ -5373,9 +5746,9 @@ async function pushTags(provider, specs, lock, opts = {}) {
|
|
|
5373
5746
|
}
|
|
5374
5747
|
|
|
5375
5748
|
// src/pipeline/doctor.ts
|
|
5376
|
-
import { constants, existsSync as
|
|
5749
|
+
import { constants, existsSync as existsSync10 } from "fs";
|
|
5377
5750
|
import { access, stat as stat2 } from "fs/promises";
|
|
5378
|
-
import
|
|
5751
|
+
import path15 from "path";
|
|
5379
5752
|
async function doctor(loaded, specs, lock, lockPath, opts = {}) {
|
|
5380
5753
|
const checks = [];
|
|
5381
5754
|
const add = (id, level, message5) => checks.push({ id, level, message: message5 });
|
|
@@ -5386,8 +5759,8 @@ async function doctor(loaded, specs, lock, lockPath, opts = {}) {
|
|
|
5386
5759
|
);
|
|
5387
5760
|
add("lock", "ok", `${Object.keys(lock.entries).length} valid v2 lock entr(ies)`);
|
|
5388
5761
|
const dirs = [...new Set(specs.flatMap((spec) => [
|
|
5389
|
-
|
|
5390
|
-
...spec.quality ? [
|
|
5762
|
+
path15.dirname(spec.outFile),
|
|
5763
|
+
...spec.quality ? [path15.dirname(spec.quality.outFile)] : []
|
|
5391
5764
|
]))];
|
|
5392
5765
|
const unwritable = [];
|
|
5393
5766
|
for (const dir of dirs) {
|
|
@@ -5410,7 +5783,7 @@ async function doctor(loaded, specs, lock, lockPath, opts = {}) {
|
|
|
5410
5783
|
for (let index = 0; index < entry.outputs.length; index++) {
|
|
5411
5784
|
const output = entry.outputs[index];
|
|
5412
5785
|
const spec = specByKey.get(key);
|
|
5413
|
-
const absolute = spec ? currentEntryOutputPath(entry, spec, index) : resolveOutputPath(output.path,
|
|
5786
|
+
const absolute = spec ? currentEntryOutputPath(entry, spec, index) : resolveOutputPath(output.path, path15.dirname(lockPath));
|
|
5414
5787
|
const owner = outputOwners.get(absolute);
|
|
5415
5788
|
if (owner && owner !== key) duplicateOutputs.push(`${output.path} (${owner}, ${key})`);
|
|
5416
5789
|
outputOwners.set(absolute, key);
|
|
@@ -5425,7 +5798,7 @@ async function doctor(loaded, specs, lock, lockPath, opts = {}) {
|
|
|
5425
5798
|
} else {
|
|
5426
5799
|
add("lock-outputs", "ok", `${outputOwners.size} recorded output path(s) are uniquely owned`);
|
|
5427
5800
|
}
|
|
5428
|
-
const cacheDir =
|
|
5801
|
+
const cacheDir = path15.join(path15.dirname(lockPath), ".pixelkiln", "cache");
|
|
5429
5802
|
const stranded = [];
|
|
5430
5803
|
const invalidState = [];
|
|
5431
5804
|
const staleMapUrls = [];
|
|
@@ -5441,11 +5814,11 @@ async function doctor(loaded, specs, lock, lockPath, opts = {}) {
|
|
|
5441
5814
|
const hasSource = Boolean(entry.sourceUrl || entry.sourceUrls?.length);
|
|
5442
5815
|
let hasCache = false;
|
|
5443
5816
|
for (const output of entry.outputs) {
|
|
5444
|
-
const cached =
|
|
5817
|
+
const cached = path15.join(
|
|
5445
5818
|
cacheDir,
|
|
5446
5819
|
cacheFileName(output.sha256, output.mediaType ?? MediaType.PNG)
|
|
5447
5820
|
);
|
|
5448
|
-
if (
|
|
5821
|
+
if (existsSync10(cached) && await sha256File(cached) === output.sha256) {
|
|
5449
5822
|
hasCache = true;
|
|
5450
5823
|
break;
|
|
5451
5824
|
}
|
|
@@ -5533,20 +5906,20 @@ async function nearestExistingDirectory(start) {
|
|
|
5533
5906
|
if ((await stat2(current)).isDirectory()) return current;
|
|
5534
5907
|
} catch {
|
|
5535
5908
|
}
|
|
5536
|
-
const parent =
|
|
5909
|
+
const parent = path15.dirname(current);
|
|
5537
5910
|
if (parent === current) return current;
|
|
5538
5911
|
current = parent;
|
|
5539
5912
|
}
|
|
5540
5913
|
}
|
|
5541
5914
|
|
|
5542
5915
|
// src/pipeline/adopt.ts
|
|
5543
|
-
import { existsSync as
|
|
5916
|
+
import { existsSync as existsSync12 } from "fs";
|
|
5544
5917
|
import { readFile as readFile9, writeFile as writeFile5 } from "fs/promises";
|
|
5545
|
-
import
|
|
5918
|
+
import path16 from "path";
|
|
5546
5919
|
|
|
5547
5920
|
// src/cache.ts
|
|
5548
5921
|
import { mkdir as mkdir4, readFile as readFile8, rename as rename4, rm as rm5, writeFile as writeFile4 } from "fs/promises";
|
|
5549
|
-
import { existsSync as
|
|
5922
|
+
import { existsSync as existsSync11 } from "fs";
|
|
5550
5923
|
import pathModule from "path";
|
|
5551
5924
|
import { z as z3 } from "zod";
|
|
5552
5925
|
var HashCacheSchema = z3.object({
|
|
@@ -5561,10 +5934,10 @@ function isSha256Hash(value) {
|
|
|
5561
5934
|
function parseCache(value) {
|
|
5562
5935
|
return HashCacheSchema.parse(value);
|
|
5563
5936
|
}
|
|
5564
|
-
async function loadCache(
|
|
5565
|
-
if (!
|
|
5937
|
+
async function loadCache(path25) {
|
|
5938
|
+
if (!existsSync11(path25)) return { version: 1, hashes: {} };
|
|
5566
5939
|
try {
|
|
5567
|
-
const parsed = HashCacheSchema.safeParse(JSON.parse(await readFile8(
|
|
5940
|
+
const parsed = HashCacheSchema.safeParse(JSON.parse(await readFile8(path25, "utf8")));
|
|
5568
5941
|
if (!parsed.success) return { version: 1, hashes: {} };
|
|
5569
5942
|
return {
|
|
5570
5943
|
version: 1,
|
|
@@ -5576,18 +5949,18 @@ async function loadCache(path24) {
|
|
|
5576
5949
|
return { version: 1, hashes: {} };
|
|
5577
5950
|
}
|
|
5578
5951
|
}
|
|
5579
|
-
async function saveCache(
|
|
5952
|
+
async function saveCache(path25, cache) {
|
|
5580
5953
|
const sorted = {};
|
|
5581
5954
|
for (const key of Object.keys(cache.hashes).sort()) {
|
|
5582
5955
|
const hash = cache.hashes[key];
|
|
5583
5956
|
if (!isSha256Hash(hash)) throw new Error(`Refusing to cache invalid SHA-256 for ${key}`);
|
|
5584
5957
|
sorted[key] = hash;
|
|
5585
5958
|
}
|
|
5586
|
-
await mkdir4(pathModule.dirname(pathModule.resolve(
|
|
5587
|
-
const tmp = `${
|
|
5959
|
+
await mkdir4(pathModule.dirname(pathModule.resolve(path25)), { recursive: true });
|
|
5960
|
+
const tmp = `${path25}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
5588
5961
|
try {
|
|
5589
5962
|
await writeFile4(tmp, JSON.stringify({ version: 1, hashes: sorted }, null, 2) + "\n");
|
|
5590
|
-
await rename4(tmp,
|
|
5963
|
+
await rename4(tmp, path25);
|
|
5591
5964
|
} finally {
|
|
5592
5965
|
await rm5(tmp, { force: true });
|
|
5593
5966
|
}
|
|
@@ -5618,7 +5991,7 @@ async function adopt(provider, specs, lock, lockPath, opts = {}) {
|
|
|
5618
5991
|
const localByHash = /* @__PURE__ */ new Map();
|
|
5619
5992
|
const unmatchedLocal = [];
|
|
5620
5993
|
for (const spec of specs) {
|
|
5621
|
-
if (!
|
|
5994
|
+
if (!existsSync12(spec.outFile)) {
|
|
5622
5995
|
unmatchedLocal.push(`${spec.styleId}/${spec.assetId} (no file at ${spec.outFile})`);
|
|
5623
5996
|
continue;
|
|
5624
5997
|
}
|
|
@@ -5781,7 +6154,13 @@ function renderSheet(groups) {
|
|
|
5781
6154
|
...g,
|
|
5782
6155
|
styleId: escapeHtml(g.styleId),
|
|
5783
6156
|
assetId: escapeHtml(g.assetId),
|
|
5784
|
-
prompt: escapeHtml(g.prompt)
|
|
6157
|
+
prompt: escapeHtml(g.prompt),
|
|
6158
|
+
...g.revision ? {
|
|
6159
|
+
revision: {
|
|
6160
|
+
...g.revision,
|
|
6161
|
+
sourceAssetId: escapeHtml(g.revision.sourceAssetId)
|
|
6162
|
+
}
|
|
6163
|
+
} : {}
|
|
5785
6164
|
}));
|
|
5786
6165
|
const data = JSON.stringify(safe).replace(/<\//g, "<\\/");
|
|
5787
6166
|
return `<!doctype html>
|
|
@@ -5822,6 +6201,18 @@ function renderSheet(groups) {
|
|
|
5822
6201
|
color:var(--accent-soft); border:1px solid var(--accent); border-radius:0; padding:2px 6px; }
|
|
5823
6202
|
.aid { font-weight:650; font-family:ui-monospace,SFMono-Regular,Menlo,monospace; }
|
|
5824
6203
|
.prompt { flex:1; min-width:280px; color:var(--dim); font-size:12.5px; max-width:80ch; }
|
|
6204
|
+
.review { display:grid; grid-template-columns:minmax(180px,280px) minmax(0,1fr); gap:16px;
|
|
6205
|
+
align-items:start; }
|
|
6206
|
+
.review.no-context { display:block; }
|
|
6207
|
+
.context { border:1px solid var(--line); padding:8px; background:var(--panel-deep);
|
|
6208
|
+
min-width:0; }
|
|
6209
|
+
.context-label { display:flex; justify-content:space-between; gap:8px; margin-bottom:7px;
|
|
6210
|
+
color:var(--dim); font-size:10.5px; font-variant-numeric:tabular-nums; }
|
|
6211
|
+
.context img { display:block; width:100%; height:auto; max-height:420px; object-fit:contain;
|
|
6212
|
+
image-rendering:pixelated;
|
|
6213
|
+
background-image:linear-gradient(45deg,#0000 25%,#7f7f7f22 25%,#7f7f7f22 75%,#0000 75%),
|
|
6214
|
+
linear-gradient(45deg,#0000 25%,#7f7f7f22 25%,#7f7f7f22 75%,#0000 75%);
|
|
6215
|
+
background-size:12px 12px; background-position:0 0,6px 6px; }
|
|
5825
6216
|
.frames { display:flex; flex-wrap:wrap; gap:10px; max-width:100%; overflow-x:auto; }
|
|
5826
6217
|
.cand { border:2px solid var(--line-strong); border-radius:0; padding:7px; background:var(--panel-deep);
|
|
5827
6218
|
cursor:pointer; display:flex; flex:0 0 auto; flex-direction:column; align-items:center; gap:5px;
|
|
@@ -5848,6 +6239,10 @@ function renderSheet(groups) {
|
|
|
5848
6239
|
.prompt { min-width:100%; }
|
|
5849
6240
|
.cand img.preview { max-width:calc(100vw - 50px); }
|
|
5850
6241
|
}
|
|
6242
|
+
@media (max-width:900px) {
|
|
6243
|
+
.review { grid-template-columns:1fr; }
|
|
6244
|
+
.context { max-width:280px; }
|
|
6245
|
+
}
|
|
5851
6246
|
</style>
|
|
5852
6247
|
</head>
|
|
5853
6248
|
<body>
|
|
@@ -5890,8 +6285,26 @@ GROUPS.forEach((g, gi) => {
|
|
|
5890
6285
|
'<div class="ghead"><span class="style">' + g.styleId + '</span>' +
|
|
5891
6286
|
'<span class="aid">' + g.assetId + '</span>' +
|
|
5892
6287
|
'<span class="prompt">' + g.prompt + '</span></div>' +
|
|
5893
|
-
'<div class="
|
|
6288
|
+
'<div class="review' + (g.revision ? '' : ' no-context') + '">' +
|
|
6289
|
+
'<div class="frames"></div></div>';
|
|
6290
|
+
const review = el.querySelector('.review');
|
|
5894
6291
|
const frames = el.querySelector('.frames');
|
|
6292
|
+
if (g.revision) {
|
|
6293
|
+
const context = document.createElement('aside');
|
|
6294
|
+
context.className = 'context';
|
|
6295
|
+
const label = document.createElement('div');
|
|
6296
|
+
label.className = 'context-label';
|
|
6297
|
+
const identity = document.createElement('span');
|
|
6298
|
+
identity.textContent = g.revision.mode.toUpperCase() + ' \xB7 SOURCE ' + g.revision.sourceAssetId;
|
|
6299
|
+
const dimensions = document.createElement('span');
|
|
6300
|
+
dimensions.textContent = g.revision.width + '\xD7' + g.revision.height;
|
|
6301
|
+
label.append(identity, dimensions);
|
|
6302
|
+
const source = document.createElement('img');
|
|
6303
|
+
source.src = g.revision.sourceUrl;
|
|
6304
|
+
source.alt = 'Revision source ' + g.revision.sourceAssetId;
|
|
6305
|
+
context.append(label, source);
|
|
6306
|
+
review.insertBefore(context, frames);
|
|
6307
|
+
}
|
|
5895
6308
|
g.frameUrls.forEach((url, i) => {
|
|
5896
6309
|
const c = document.createElement('button');
|
|
5897
6310
|
c.type = 'button';
|
|
@@ -6028,11 +6441,28 @@ document.getElementById('submit').onclick = async () => {
|
|
|
6028
6441
|
// src/pick/review-server.ts
|
|
6029
6442
|
import { createServer } from "http";
|
|
6030
6443
|
import { spawn } from "child_process";
|
|
6444
|
+
import { readFile as readFile10 } from "fs/promises";
|
|
6031
6445
|
function serveReviewPage(opts) {
|
|
6032
6446
|
const log2 = opts.onProgress ?? (() => {
|
|
6033
6447
|
});
|
|
6034
6448
|
return new Promise((resolve, reject) => {
|
|
6035
6449
|
const server = createServer(async (req, res) => {
|
|
6450
|
+
if (req.method === "GET" && req.url && opts.assets?.has(req.url)) {
|
|
6451
|
+
try {
|
|
6452
|
+
const asset = opts.assets.get(req.url);
|
|
6453
|
+
const bytes = await readFile10(asset.path);
|
|
6454
|
+
res.writeHead(200, {
|
|
6455
|
+
"Content-Type": asset.contentType,
|
|
6456
|
+
"Cache-Control": "no-store",
|
|
6457
|
+
"X-Content-Type-Options": "nosniff"
|
|
6458
|
+
});
|
|
6459
|
+
res.end(bytes);
|
|
6460
|
+
} catch {
|
|
6461
|
+
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
6462
|
+
res.end("review asset is unavailable");
|
|
6463
|
+
}
|
|
6464
|
+
return;
|
|
6465
|
+
}
|
|
6036
6466
|
if (req.method === "GET" && (req.url === "/" || req.url?.startsWith("/?"))) {
|
|
6037
6467
|
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
6038
6468
|
res.end(opts.html);
|
|
@@ -6096,13 +6526,25 @@ async function runPicker(provider, lock, lockPath, opts = {}) {
|
|
|
6096
6526
|
const log2 = opts.onProgress ?? (() => {
|
|
6097
6527
|
});
|
|
6098
6528
|
const selectedKeys = opts.keys ? new Set(opts.keys) : null;
|
|
6529
|
+
const specByKey = new Map(
|
|
6530
|
+
(opts.specs ?? []).map((spec) => [lockKey(spec.styleId, spec.assetId), spec])
|
|
6531
|
+
);
|
|
6532
|
+
const reviewAssets = /* @__PURE__ */ new Map();
|
|
6099
6533
|
const groups = [];
|
|
6100
6534
|
for (const [key, entry] of Object.entries(lock.entries)) {
|
|
6101
6535
|
if (entry.provider !== provider.id || selectedKeys && !selectedKeys.has(key)) continue;
|
|
6102
6536
|
if (entry.status !== "review" || !entry.reviewObjectId) continue;
|
|
6103
6537
|
try {
|
|
6104
|
-
const
|
|
6538
|
+
const spec = specByKey.get(key);
|
|
6539
|
+
const state = await provider.poll(entry.reviewObjectId, entry.generator, { spec });
|
|
6105
6540
|
if (state.status !== "review" || !state.candidateUrls.length) continue;
|
|
6541
|
+
const sourceRoute = spec?.revision ? `/revision-source/${encodeURIComponent(String(groups.length))}` : null;
|
|
6542
|
+
if (sourceRoute && spec?.revision) {
|
|
6543
|
+
reviewAssets.set(sourceRoute, {
|
|
6544
|
+
path: spec.revision.sourceFile,
|
|
6545
|
+
contentType: spec.revision.sourceFormat === "jpeg" ? "image/jpeg" : "image/png"
|
|
6546
|
+
});
|
|
6547
|
+
}
|
|
6106
6548
|
groups.push({
|
|
6107
6549
|
key,
|
|
6108
6550
|
assetId: entry.assetId,
|
|
@@ -6111,7 +6553,16 @@ async function runPicker(provider, lock, lockPath, opts = {}) {
|
|
|
6111
6553
|
reviewObjectId: entry.reviewObjectId,
|
|
6112
6554
|
frameUrls: state.candidateUrls,
|
|
6113
6555
|
width: entry.width,
|
|
6114
|
-
height: entry.height
|
|
6556
|
+
height: entry.height,
|
|
6557
|
+
...sourceRoute && spec?.revision && spec.revision.sourceWidth && spec.revision.sourceHeight ? {
|
|
6558
|
+
revision: {
|
|
6559
|
+
mode: spec.revision.mode,
|
|
6560
|
+
sourceAssetId: spec.revision.sourceAssetId,
|
|
6561
|
+
sourceUrl: sourceRoute,
|
|
6562
|
+
width: spec.revision.sourceWidth,
|
|
6563
|
+
height: spec.revision.sourceHeight
|
|
6564
|
+
}
|
|
6565
|
+
} : {}
|
|
6115
6566
|
});
|
|
6116
6567
|
} catch (err) {
|
|
6117
6568
|
log2(` could not load candidates for ${key}: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -6125,6 +6576,7 @@ async function runPicker(provider, lock, lockPath, opts = {}) {
|
|
|
6125
6576
|
port: opts.port,
|
|
6126
6577
|
open: opts.open,
|
|
6127
6578
|
onProgress: log2,
|
|
6579
|
+
assets: reviewAssets,
|
|
6128
6580
|
onReady: (url) => {
|
|
6129
6581
|
log2(`
|
|
6130
6582
|
${groups.length} asset(s) awaiting selection: ${url}`);
|
|
@@ -6170,9 +6622,9 @@ async function runPicker(provider, lock, lockPath, opts = {}) {
|
|
|
6170
6622
|
}
|
|
6171
6623
|
|
|
6172
6624
|
// src/pipeline/init.ts
|
|
6173
|
-
import { readdir, readFile as
|
|
6174
|
-
import { existsSync as
|
|
6175
|
-
import
|
|
6625
|
+
import { readdir, readFile as readFile11, writeFile as writeFile6 } from "fs/promises";
|
|
6626
|
+
import { existsSync as existsSync13 } from "fs";
|
|
6627
|
+
import path17 from "path";
|
|
6176
6628
|
function pngSize(buf) {
|
|
6177
6629
|
if (buf.length < 24) return null;
|
|
6178
6630
|
const isPng = buf.readUInt32BE(0) === 2303741511 && buf.readUInt32BE(4) === 218765834;
|
|
@@ -6186,7 +6638,7 @@ function slugify(value) {
|
|
|
6186
6638
|
async function walk(dir, exclude) {
|
|
6187
6639
|
const out = [];
|
|
6188
6640
|
for (const entry of await readdir(dir, { withFileTypes: true })) {
|
|
6189
|
-
const full =
|
|
6641
|
+
const full = path17.join(dir, entry.name);
|
|
6190
6642
|
if (exclude.some((x) => full.includes(x))) continue;
|
|
6191
6643
|
if (entry.isDirectory()) out.push(...await walk(full, exclude));
|
|
6192
6644
|
else if (entry.name.toLowerCase().endsWith(".png")) out.push(full);
|
|
@@ -6200,14 +6652,14 @@ async function scanAssets(root, opts = {}) {
|
|
|
6200
6652
|
const skipped = [];
|
|
6201
6653
|
const seen = /* @__PURE__ */ new Map();
|
|
6202
6654
|
for (const file of files.sort()) {
|
|
6203
|
-
const size = pngSize(await
|
|
6655
|
+
const size = pngSize(await readFile11(file));
|
|
6204
6656
|
if (!size) {
|
|
6205
6657
|
skipped.push(`${file} (not a readable PNG)`);
|
|
6206
6658
|
continue;
|
|
6207
6659
|
}
|
|
6208
|
-
const rel =
|
|
6209
|
-
const category =
|
|
6210
|
-
let id = slugify(
|
|
6660
|
+
const rel = path17.relative(root, file);
|
|
6661
|
+
const category = path17.dirname(rel) === "." ? "" : path17.dirname(rel);
|
|
6662
|
+
let id = slugify(path17.basename(rel));
|
|
6211
6663
|
if (seen.has(id)) {
|
|
6212
6664
|
const n = seen.get(id) + 1;
|
|
6213
6665
|
seen.set(id, n);
|
|
@@ -6262,26 +6714,26 @@ function mode(values) {
|
|
|
6262
6714
|
return [...counts.entries()].sort((a, b) => b[1] - a[1])[0]?.[0] ?? 64;
|
|
6263
6715
|
}
|
|
6264
6716
|
async function writeManifestFile(target, manifest) {
|
|
6265
|
-
if (
|
|
6717
|
+
if (existsSync13(target)) {
|
|
6266
6718
|
throw new Error(`${target} already exists \u2014 refusing to overwrite an existing manifest.`);
|
|
6267
6719
|
}
|
|
6268
6720
|
await writeFile6(target, JSON.stringify(manifest, null, 2) + "\n");
|
|
6269
6721
|
}
|
|
6270
6722
|
|
|
6271
6723
|
// src/pipeline/salvage.ts
|
|
6272
|
-
import { readFile as
|
|
6273
|
-
import { existsSync as
|
|
6274
|
-
import
|
|
6724
|
+
import { readFile as readFile12 } from "fs/promises";
|
|
6725
|
+
import { existsSync as existsSync14 } from "fs";
|
|
6726
|
+
import path18 from "path";
|
|
6275
6727
|
function providerClaimId(provider, objectId) {
|
|
6276
6728
|
return `${encodeURIComponent(provider)}:${encodeURIComponent(objectId)}`;
|
|
6277
6729
|
}
|
|
6278
6730
|
async function loadClaims(lockPaths, opts = {}) {
|
|
6279
6731
|
const claimed = /* @__PURE__ */ new Set();
|
|
6280
6732
|
for (const p of lockPaths) {
|
|
6281
|
-
if (!
|
|
6733
|
+
if (!existsSync14(p)) throw new Error(`Claim lockfile not found: ${p}`);
|
|
6282
6734
|
let parsed;
|
|
6283
6735
|
try {
|
|
6284
|
-
parsed = parseLock(JSON.parse(await
|
|
6736
|
+
parsed = parseLock(JSON.parse(await readFile12(p, "utf8")));
|
|
6285
6737
|
} catch {
|
|
6286
6738
|
throw new Error(`Claim lockfile is malformed: ${p}`);
|
|
6287
6739
|
}
|
|
@@ -6329,20 +6781,20 @@ function matchStyleByPattern(prompt, manifest) {
|
|
|
6329
6781
|
return null;
|
|
6330
6782
|
}
|
|
6331
6783
|
async function loadSiblingManifests(ownManifestPath, workspaceManifestPaths, claimPaths) {
|
|
6332
|
-
const own =
|
|
6784
|
+
const own = path18.resolve(ownManifestPath);
|
|
6333
6785
|
const siblingManifestPaths = [
|
|
6334
6786
|
.../* @__PURE__ */ new Set([
|
|
6335
6787
|
...workspaceManifestPaths,
|
|
6336
|
-
...claimPaths.map((c) =>
|
|
6788
|
+
...claimPaths.map((c) => path18.join(path18.dirname(path18.resolve(c)), "pixelkiln.manifest.json"))
|
|
6337
6789
|
])
|
|
6338
6790
|
];
|
|
6339
6791
|
const siblings = [];
|
|
6340
6792
|
for (const siblingManifestPath of siblingManifestPaths) {
|
|
6341
|
-
if (
|
|
6342
|
-
if (!
|
|
6793
|
+
if (path18.resolve(siblingManifestPath) === own) continue;
|
|
6794
|
+
if (!existsSync14(siblingManifestPath)) continue;
|
|
6343
6795
|
try {
|
|
6344
6796
|
const { manifest } = await loadManifest(siblingManifestPath);
|
|
6345
|
-
siblings.push({ label:
|
|
6797
|
+
siblings.push({ label: path18.basename(path18.dirname(siblingManifestPath)), manifest });
|
|
6346
6798
|
} catch {
|
|
6347
6799
|
}
|
|
6348
6800
|
}
|
|
@@ -6458,9 +6910,9 @@ async function applyTags(provider, decisions, existing, opts = {}) {
|
|
|
6458
6910
|
}
|
|
6459
6911
|
|
|
6460
6912
|
// src/workspace.ts
|
|
6461
|
-
import { mkdir as mkdir5, readFile as
|
|
6462
|
-
import { existsSync as
|
|
6463
|
-
import
|
|
6913
|
+
import { mkdir as mkdir5, readFile as readFile13, rename as rename5, rm as rm6, writeFile as writeFile7 } from "fs/promises";
|
|
6914
|
+
import { existsSync as existsSync15 } from "fs";
|
|
6915
|
+
import path19 from "path";
|
|
6464
6916
|
import { z as z4 } from "zod";
|
|
6465
6917
|
var WorkspaceProjectSchema = z4.object({
|
|
6466
6918
|
id: z4.string().min(1),
|
|
@@ -6485,10 +6937,10 @@ ${parsed.error.issues.slice(0, 5).map((i) => ` ${i.path.join(".")}: ${i.message
|
|
|
6485
6937
|
);
|
|
6486
6938
|
}
|
|
6487
6939
|
async function loadWorkspace(workspacePath) {
|
|
6488
|
-
if (!
|
|
6940
|
+
if (!existsSync15(workspacePath)) return { version: 1, projects: [] };
|
|
6489
6941
|
let raw;
|
|
6490
6942
|
try {
|
|
6491
|
-
raw = JSON.parse(await
|
|
6943
|
+
raw = JSON.parse(await readFile13(workspacePath, "utf8"));
|
|
6492
6944
|
} catch (err) {
|
|
6493
6945
|
throw new Error(
|
|
6494
6946
|
`Workspace catalog at ${workspacePath} is malformed:
|
|
@@ -6502,7 +6954,7 @@ async function saveWorkspace(workspacePath, ws) {
|
|
|
6502
6954
|
version: 1,
|
|
6503
6955
|
projects: [...ws.projects].sort((a, b) => a.id.localeCompare(b.id))
|
|
6504
6956
|
};
|
|
6505
|
-
await mkdir5(
|
|
6957
|
+
await mkdir5(path19.dirname(path19.resolve(workspacePath)), { recursive: true });
|
|
6506
6958
|
const tmp = `${workspacePath}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
6507
6959
|
try {
|
|
6508
6960
|
await writeFile7(tmp, JSON.stringify(sorted, null, 2) + "\n");
|
|
@@ -6512,12 +6964,12 @@ async function saveWorkspace(workspacePath, ws) {
|
|
|
6512
6964
|
}
|
|
6513
6965
|
}
|
|
6514
6966
|
function toPortablePath(dir, absolute) {
|
|
6515
|
-
return
|
|
6967
|
+
return path19.relative(dir, absolute).split(path19.sep).join("/");
|
|
6516
6968
|
}
|
|
6517
6969
|
function resolveProject(dir, project) {
|
|
6518
6970
|
return {
|
|
6519
|
-
manifestPath:
|
|
6520
|
-
lockPath:
|
|
6971
|
+
manifestPath: path19.resolve(dir, project.manifest.split("/").join(path19.sep)),
|
|
6972
|
+
lockPath: path19.resolve(dir, project.lock.split("/").join(path19.sep))
|
|
6521
6973
|
};
|
|
6522
6974
|
}
|
|
6523
6975
|
function validateWorkspace(ws, dir) {
|
|
@@ -6530,21 +6982,21 @@ function validateWorkspace(ws, dir) {
|
|
|
6530
6982
|
const { manifestPath, lockPath } = resolveProject(dir, project);
|
|
6531
6983
|
lockOwners.set(lockPath, [...lockOwners.get(lockPath) ?? [], project.id]);
|
|
6532
6984
|
manifestOwners.set(manifestPath, [...manifestOwners.get(manifestPath) ?? [], project.id]);
|
|
6533
|
-
if (
|
|
6985
|
+
if (path19.isAbsolute(project.manifest) || path19.isAbsolute(project.lock)) {
|
|
6534
6986
|
diagnostics.push({
|
|
6535
6987
|
id: "absolute-path",
|
|
6536
6988
|
level: "warning",
|
|
6537
6989
|
message: `project "${project.id}" stores an absolute path \u2014 the catalog will not resolve correctly if this tree is cloned or moved elsewhere`
|
|
6538
6990
|
});
|
|
6539
6991
|
}
|
|
6540
|
-
if (!
|
|
6992
|
+
if (!existsSync15(manifestPath)) {
|
|
6541
6993
|
diagnostics.push({
|
|
6542
6994
|
id: "missing-manifest",
|
|
6543
6995
|
level: "error",
|
|
6544
6996
|
message: `project "${project.id}" manifest not found: ${manifestPath}`
|
|
6545
6997
|
});
|
|
6546
6998
|
}
|
|
6547
|
-
if (!
|
|
6999
|
+
if (!existsSync15(lockPath)) {
|
|
6548
7000
|
diagnostics.push({
|
|
6549
7001
|
id: "missing-lock",
|
|
6550
7002
|
level: "error",
|
|
@@ -6620,6 +7072,7 @@ function emptyStateCounts() {
|
|
|
6620
7072
|
orphaned: 0,
|
|
6621
7073
|
"in-flight": 0,
|
|
6622
7074
|
recoverable: 0,
|
|
7075
|
+
blocked: 0,
|
|
6623
7076
|
failed: 0
|
|
6624
7077
|
};
|
|
6625
7078
|
}
|
|
@@ -6693,15 +7146,15 @@ async function workspaceStatus(ws, dir) {
|
|
|
6693
7146
|
}
|
|
6694
7147
|
|
|
6695
7148
|
// src/pipeline/cache-health.ts
|
|
6696
|
-
import { existsSync as
|
|
6697
|
-
import { readFile as
|
|
6698
|
-
import
|
|
7149
|
+
import { existsSync as existsSync16 } from "fs";
|
|
7150
|
+
import { readFile as readFile14, readdir as readdir2, rm as rm7 } from "fs/promises";
|
|
7151
|
+
import path20 from "path";
|
|
6699
7152
|
async function inspectCaches(lock, lockPath, options = {}) {
|
|
6700
|
-
if (options.prune && !
|
|
6701
|
-
throw new Error(`Refusing to prune without an existing lockfile at ${
|
|
7153
|
+
if (options.prune && !existsSync16(lockPath)) {
|
|
7154
|
+
throw new Error(`Refusing to prune without an existing lockfile at ${path20.resolve(lockPath)}`);
|
|
6702
7155
|
}
|
|
6703
|
-
const contentDir =
|
|
6704
|
-
const remotePath =
|
|
7156
|
+
const contentDir = path20.resolve(path20.dirname(lockPath), ".pixelkiln", "cache");
|
|
7157
|
+
const remotePath = path20.resolve(cachePathFor(lockPath));
|
|
6705
7158
|
const referenced = new Set(
|
|
6706
7159
|
Object.values(lock.entries).flatMap((entry) => entry.outputs.map((output) => output.sha256))
|
|
6707
7160
|
);
|
|
@@ -6715,7 +7168,7 @@ async function inspectCaches(lock, lockPath, options = {}) {
|
|
|
6715
7168
|
]);
|
|
6716
7169
|
for (const name of names) {
|
|
6717
7170
|
try {
|
|
6718
|
-
await rm7(
|
|
7171
|
+
await rm7(path20.join(contentDir, name), { force: true });
|
|
6719
7172
|
removed.contentFiles++;
|
|
6720
7173
|
} catch {
|
|
6721
7174
|
}
|
|
@@ -6724,7 +7177,7 @@ async function inspectCaches(lock, lockPath, options = {}) {
|
|
|
6724
7177
|
await saveCache(remotePath, { version: 1, hashes: {} });
|
|
6725
7178
|
removed.resetRemoteHashCache = true;
|
|
6726
7179
|
} else if (remoteHashes.invalidIds.length) {
|
|
6727
|
-
const cache = parseCache(JSON.parse(await
|
|
7180
|
+
const cache = parseCache(JSON.parse(await readFile14(remotePath, "utf8")));
|
|
6728
7181
|
for (const id of remoteHashes.invalidIds) delete cache.hashes[id];
|
|
6729
7182
|
removed.remoteHashEntries = remoteHashes.invalidIds.length;
|
|
6730
7183
|
await saveCache(remotePath, cache);
|
|
@@ -6742,7 +7195,7 @@ async function inspectCaches(lock, lockPath, options = {}) {
|
|
|
6742
7195
|
async function inspectContentCache(contentDir, referenced) {
|
|
6743
7196
|
const report = {
|
|
6744
7197
|
path: contentDir,
|
|
6745
|
-
exists:
|
|
7198
|
+
exists: existsSync16(contentDir),
|
|
6746
7199
|
files: 0,
|
|
6747
7200
|
bytes: 0,
|
|
6748
7201
|
valid: 0,
|
|
@@ -6763,12 +7216,12 @@ async function inspectContentCache(contentDir, referenced) {
|
|
|
6763
7216
|
continue;
|
|
6764
7217
|
}
|
|
6765
7218
|
report.files++;
|
|
6766
|
-
const file =
|
|
7219
|
+
const file = path20.join(contentDir, entry.name);
|
|
6767
7220
|
const mediaType = mediaTypeFromExtension(entry.name);
|
|
6768
7221
|
const expected = mediaType ? entry.name.slice(0, -4) : "";
|
|
6769
7222
|
let bytes;
|
|
6770
7223
|
try {
|
|
6771
|
-
bytes = await
|
|
7224
|
+
bytes = await readFile14(file);
|
|
6772
7225
|
report.bytes += bytes.length;
|
|
6773
7226
|
} catch (err) {
|
|
6774
7227
|
report.invalid.push({
|
|
@@ -6806,7 +7259,7 @@ async function inspectContentCache(contentDir, referenced) {
|
|
|
6806
7259
|
async function inspectRemoteHashCache(remotePath) {
|
|
6807
7260
|
const report = {
|
|
6808
7261
|
path: remotePath,
|
|
6809
|
-
exists:
|
|
7262
|
+
exists: existsSync16(remotePath),
|
|
6810
7263
|
entries: 0,
|
|
6811
7264
|
valid: 0,
|
|
6812
7265
|
invalidIds: [],
|
|
@@ -6815,7 +7268,7 @@ async function inspectRemoteHashCache(remotePath) {
|
|
|
6815
7268
|
if (!report.exists) return report;
|
|
6816
7269
|
let cache;
|
|
6817
7270
|
try {
|
|
6818
|
-
cache = parseCache(JSON.parse(await
|
|
7271
|
+
cache = parseCache(JSON.parse(await readFile14(remotePath, "utf8")));
|
|
6819
7272
|
} catch (err) {
|
|
6820
7273
|
report.error = err instanceof Error ? err.message : String(err);
|
|
6821
7274
|
return report;
|
|
@@ -7086,8 +7539,8 @@ function isRecord2(value) {
|
|
|
7086
7539
|
}
|
|
7087
7540
|
|
|
7088
7541
|
// src/pick/salvage-server.ts
|
|
7089
|
-
import { mkdir as mkdir6, writeFile as writeFile8, readFile as
|
|
7090
|
-
import
|
|
7542
|
+
import { mkdir as mkdir6, writeFile as writeFile8, readFile as readFile15 } from "fs/promises";
|
|
7543
|
+
import path21 from "path";
|
|
7091
7544
|
|
|
7092
7545
|
// src/pick/salvage-sheet.ts
|
|
7093
7546
|
var escapeHtml2 = (s) => s.replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
@@ -7252,7 +7705,7 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
7252
7705
|
});
|
|
7253
7706
|
const html = renderSalvageSheet(orphans, {
|
|
7254
7707
|
styleId: ctx.styleId,
|
|
7255
|
-
importDir:
|
|
7708
|
+
importDir: path21.relative(process.cwd(), ctx.importDir) || "."
|
|
7256
7709
|
});
|
|
7257
7710
|
const byId = new Map(orphans.map((o) => [o.id, o]));
|
|
7258
7711
|
const existingTags = new Map(orphans.map((o) => [o.id, o.tags]));
|
|
@@ -7281,9 +7734,9 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
7281
7734
|
if (!buf.subarray(0, 8).equals(PNG_SIGNATURE2)) throw new Error("not a PNG");
|
|
7282
7735
|
decodePng(buf);
|
|
7283
7736
|
const assetId = idFromPrompt(orphan.prompt, taken);
|
|
7284
|
-
const rel =
|
|
7285
|
-
const outFile =
|
|
7286
|
-
await mkdir6(
|
|
7737
|
+
const rel = path21.join("_salvaged", `${assetId}.png`);
|
|
7738
|
+
const outFile = path21.resolve(ctx.importDir, rel);
|
|
7739
|
+
await mkdir6(path21.dirname(outFile), { recursive: true });
|
|
7287
7740
|
await writeFile8(outFile, buf);
|
|
7288
7741
|
ctx.manifest.assets[assetId] = {
|
|
7289
7742
|
prompt: orphan.prompt,
|
|
@@ -7314,7 +7767,7 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
7314
7767
|
sourceUrl: durableSource,
|
|
7315
7768
|
sourceUrls: durableSource ? [{ url: durableSource }] : [],
|
|
7316
7769
|
outputs: [{
|
|
7317
|
-
path: portableOutputPath(outFile,
|
|
7770
|
+
path: portableOutputPath(outFile, path21.dirname(ctx.manifestPath)),
|
|
7318
7771
|
sha256: sha256(buf)
|
|
7319
7772
|
}],
|
|
7320
7773
|
submittedAt: orphan.createdAt,
|
|
@@ -7338,7 +7791,7 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
7338
7791
|
}
|
|
7339
7792
|
}
|
|
7340
7793
|
await applyTags(provider, decisions, existingTags, { onProgress: log2 });
|
|
7341
|
-
const raw = JSON.parse(await
|
|
7794
|
+
const raw = JSON.parse(await readFile15(ctx.manifestPath, "utf8"));
|
|
7342
7795
|
for (const id of importedAssetIds) raw.assets[id] = ctx.manifest.assets[id];
|
|
7343
7796
|
await writeFile8(ctx.manifestPath, JSON.stringify(raw, null, 2) + "\n");
|
|
7344
7797
|
await saveLock(ctx.lockPath, ctx.lock);
|
|
@@ -7348,9 +7801,9 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
|
|
|
7348
7801
|
}
|
|
7349
7802
|
|
|
7350
7803
|
// src/recipes.ts
|
|
7351
|
-
import
|
|
7352
|
-
import { existsSync as
|
|
7353
|
-
import { readdir as readdir3, readFile as
|
|
7804
|
+
import path22 from "path";
|
|
7805
|
+
import { existsSync as existsSync17 } from "fs";
|
|
7806
|
+
import { readdir as readdir3, readFile as readFile16 } from "fs/promises";
|
|
7354
7807
|
import { fileURLToPath } from "url";
|
|
7355
7808
|
import { z as z5 } from "zod";
|
|
7356
7809
|
var SHA256_RE = /^[0-9a-f]{64}$/;
|
|
@@ -7359,7 +7812,7 @@ var VERSION_RE = /^\d+\.\d+\.\d+$/;
|
|
|
7359
7812
|
var RECIPE_FILE = "pixelkiln.recipe.json";
|
|
7360
7813
|
var RECIPE_DIR_TOKEN = "{{recipeDir}}";
|
|
7361
7814
|
var PortablePathSchema = z5.string().min(1).refine((value) => {
|
|
7362
|
-
if (value.includes("\\") ||
|
|
7815
|
+
if (value.includes("\\") || path22.posix.isAbsolute(value)) return false;
|
|
7363
7816
|
const parts = value.split("/");
|
|
7364
7817
|
return !parts.includes("") && !parts.includes(".") && !parts.includes("..");
|
|
7365
7818
|
}, "expected a portable relative path without '.', '..', or backslashes");
|
|
@@ -7394,10 +7847,13 @@ var RecipeSchema = z5.object({
|
|
|
7394
7847
|
numImages: z5.number().int().min(1).max(16),
|
|
7395
7848
|
bindings: z5.object({
|
|
7396
7849
|
prompt: RecipeBindingSchema,
|
|
7397
|
-
width: RecipeBindingSchema,
|
|
7398
|
-
height: RecipeBindingSchema,
|
|
7399
|
-
batchSize: RecipeBindingSchema,
|
|
7400
|
-
seed: RecipeBindingSchema.optional()
|
|
7850
|
+
width: RecipeBindingSchema.optional(),
|
|
7851
|
+
height: RecipeBindingSchema.optional(),
|
|
7852
|
+
batchSize: RecipeBindingSchema.optional(),
|
|
7853
|
+
seed: RecipeBindingSchema.optional(),
|
|
7854
|
+
sourceImage: RecipeBindingSchema.optional(),
|
|
7855
|
+
maskImage: RecipeBindingSchema.optional(),
|
|
7856
|
+
strength: RecipeBindingSchema.optional()
|
|
7401
7857
|
}).strict()
|
|
7402
7858
|
}).strict().optional(),
|
|
7403
7859
|
quality: z5.object({
|
|
@@ -7532,10 +7988,10 @@ function message3(error) {
|
|
|
7532
7988
|
return error instanceof Error ? error.message : String(error);
|
|
7533
7989
|
}
|
|
7534
7990
|
async function readRecipeFile(recipePath, bundled) {
|
|
7535
|
-
const absolute =
|
|
7991
|
+
const absolute = path22.resolve(recipePath);
|
|
7536
7992
|
let raw;
|
|
7537
7993
|
try {
|
|
7538
|
-
raw = JSON.parse(await
|
|
7994
|
+
raw = JSON.parse(await readFile16(absolute, "utf8"));
|
|
7539
7995
|
} catch (error) {
|
|
7540
7996
|
throw new Error(`Could not read recipe ${absolute}: ${message3(error)}`, { cause: error });
|
|
7541
7997
|
}
|
|
@@ -7546,13 +8002,13 @@ async function readRecipeFile(recipePath, bundled) {
|
|
|
7546
8002
|
` + parsed.error.issues.map((issue) => ` ${issue.path.join(".") || "$"}: ${issue.message}`).join("\n")
|
|
7547
8003
|
);
|
|
7548
8004
|
}
|
|
7549
|
-
return { recipe: parsed.data, path: absolute, dir:
|
|
8005
|
+
return { recipe: parsed.data, path: absolute, dir: path22.dirname(absolute), bundled };
|
|
7550
8006
|
}
|
|
7551
8007
|
async function walkRecipeFiles(root) {
|
|
7552
|
-
if (!
|
|
8008
|
+
if (!existsSync17(root)) return [];
|
|
7553
8009
|
const out = [];
|
|
7554
8010
|
for (const entry of await readdir3(root, { withFileTypes: true })) {
|
|
7555
|
-
const resolved =
|
|
8011
|
+
const resolved = path22.join(root, entry.name);
|
|
7556
8012
|
if (entry.isDirectory()) out.push(...await walkRecipeFiles(resolved));
|
|
7557
8013
|
else if (entry.isFile() && entry.name === RECIPE_FILE) out.push(resolved);
|
|
7558
8014
|
}
|
|
@@ -7563,7 +8019,7 @@ function bundledRecipeRoot() {
|
|
|
7563
8019
|
}
|
|
7564
8020
|
async function listBundledRecipes(root = bundledRecipeRoot()) {
|
|
7565
8021
|
const recipes = await Promise.all(
|
|
7566
|
-
(await walkRecipeFiles(
|
|
8022
|
+
(await walkRecipeFiles(path22.resolve(root))).map((file) => readRecipeFile(file, true))
|
|
7567
8023
|
);
|
|
7568
8024
|
return recipes.sort(
|
|
7569
8025
|
(a, b) => a.recipe.id.localeCompare(b.recipe.id) || compareVersions(b.recipe.version, a.recipe.version)
|
|
@@ -7586,9 +8042,9 @@ function parseSelector(selector) {
|
|
|
7586
8042
|
return { id: match[1], version: match[2] };
|
|
7587
8043
|
}
|
|
7588
8044
|
async function resolveRecipe(target, bundledRoot = bundledRecipeRoot()) {
|
|
7589
|
-
const local =
|
|
7590
|
-
if (
|
|
7591
|
-
const recipePath =
|
|
8045
|
+
const local = path22.resolve(target);
|
|
8046
|
+
if (existsSync17(local)) {
|
|
8047
|
+
const recipePath = path22.basename(local) === RECIPE_FILE ? local : path22.join(local, RECIPE_FILE);
|
|
7592
8048
|
return readRecipeFile(recipePath, false);
|
|
7593
8049
|
}
|
|
7594
8050
|
if (target.startsWith(".") || target.startsWith("/") || target.includes("\\")) {
|
|
@@ -7602,8 +8058,8 @@ async function resolveRecipe(target, bundledRoot = bundledRecipeRoot()) {
|
|
|
7602
8058
|
return matches[0];
|
|
7603
8059
|
}
|
|
7604
8060
|
async function verifyFile(root, file) {
|
|
7605
|
-
const absolute =
|
|
7606
|
-
if (!
|
|
8061
|
+
const absolute = path22.resolve(root, ...file.path.split("/"));
|
|
8062
|
+
if (!existsSync17(absolute)) {
|
|
7607
8063
|
return { path: file.path, expectedSha256: file.sha256, actualSha256: null, status: "missing" };
|
|
7608
8064
|
}
|
|
7609
8065
|
const actualSha256 = await sha256File(absolute);
|
|
@@ -7618,7 +8074,7 @@ async function verifyRecipe(target, options = {}) {
|
|
|
7618
8074
|
const loaded = await resolveRecipe(target, options.bundledRoot);
|
|
7619
8075
|
const actualIntegrity = recipeDigest(loaded.recipe);
|
|
7620
8076
|
const files = await Promise.all(loaded.recipe.files.map((file) => verifyFile(loaded.dir, file)));
|
|
7621
|
-
const modelRoot = options.modelRoot ?
|
|
8077
|
+
const modelRoot = options.modelRoot ? path22.resolve(options.modelRoot) : null;
|
|
7622
8078
|
const models = modelRoot ? await Promise.all(loaded.recipe.models.map(async (model) => ({
|
|
7623
8079
|
...await verifyFile(modelRoot, model),
|
|
7624
8080
|
source: model.source,
|
|
@@ -7652,7 +8108,7 @@ async function verifyRecipe(target, options = {}) {
|
|
|
7652
8108
|
};
|
|
7653
8109
|
}
|
|
7654
8110
|
function renderStyle(style, destination, cwd) {
|
|
7655
|
-
const portableDir =
|
|
8111
|
+
const portableDir = path22.relative(cwd, destination).split(path22.sep).join("/") || ".";
|
|
7656
8112
|
const replace = (value) => {
|
|
7657
8113
|
if (typeof value === "string") return value.replaceAll(RECIPE_DIR_TOKEN, portableDir);
|
|
7658
8114
|
if (Array.isArray(value)) return value.map(replace);
|
|
@@ -7664,25 +8120,25 @@ function renderStyle(style, destination, cwd) {
|
|
|
7664
8120
|
return replace(style);
|
|
7665
8121
|
}
|
|
7666
8122
|
async function installRecipe(target, options = {}) {
|
|
7667
|
-
const cwd =
|
|
8123
|
+
const cwd = path22.resolve(options.cwd ?? process.cwd());
|
|
7668
8124
|
const loaded = await resolveRecipe(target, options.bundledRoot);
|
|
7669
8125
|
const verification = await verifyRecipe(loaded.path);
|
|
7670
8126
|
if (!verification.ok) {
|
|
7671
8127
|
throw new Error(`Recipe ${loaded.recipe.id}@${loaded.recipe.version} failed integrity verification.`);
|
|
7672
8128
|
}
|
|
7673
|
-
const destination = options.out ?
|
|
8129
|
+
const destination = options.out ? path22.resolve(cwd, options.out) : path22.join(cwd, "pixelkiln-recipes", ...loaded.recipe.id.split("/"), loaded.recipe.version);
|
|
7674
8130
|
const sources = [
|
|
7675
8131
|
{ path: RECIPE_FILE, source: loaded.path },
|
|
7676
|
-
...loaded.recipe.files.map((file) => ({ path: file.path, source:
|
|
8132
|
+
...loaded.recipe.files.map((file) => ({ path: file.path, source: path22.join(loaded.dir, ...file.path.split("/")) }))
|
|
7677
8133
|
];
|
|
7678
8134
|
const files = await Promise.all(sources.map(async (file) => ({
|
|
7679
|
-
path:
|
|
7680
|
-
data: await
|
|
8135
|
+
path: path22.join(destination, ...file.path.split("/")),
|
|
8136
|
+
data: await readFile16(file.source)
|
|
7681
8137
|
})));
|
|
7682
8138
|
if (!options.force) {
|
|
7683
8139
|
for (const file of files) {
|
|
7684
|
-
if (!
|
|
7685
|
-
const current = await
|
|
8140
|
+
if (!existsSync17(file.path)) continue;
|
|
8141
|
+
const current = await readFile16(file.path);
|
|
7686
8142
|
if (!current.equals(file.data)) {
|
|
7687
8143
|
throw new Error(
|
|
7688
8144
|
`Recipe destination has local changes: ${file.path}. Choose another --out or pass --force to replace declared recipe files.`
|
|
@@ -7705,15 +8161,15 @@ async function installRecipe(target, options = {}) {
|
|
|
7705
8161
|
}
|
|
7706
8162
|
|
|
7707
8163
|
// src/pipeline/quality-regression.ts
|
|
7708
|
-
import
|
|
7709
|
-
import { existsSync as
|
|
7710
|
-
import { readFile as
|
|
8164
|
+
import path23 from "path";
|
|
8165
|
+
import { existsSync as existsSync18 } from "fs";
|
|
8166
|
+
import { readFile as readFile17 } from "fs/promises";
|
|
7711
8167
|
import { z as z6 } from "zod";
|
|
7712
8168
|
var SHA256_RE2 = /^[0-9a-f]{64}$/;
|
|
7713
8169
|
var HEX_RE = /^#[0-9a-f]{6}$/;
|
|
7714
8170
|
var MAX_REDMEAN_DISTANCE = 765;
|
|
7715
8171
|
var QualityPathSchema = z6.string().min(1).refine(
|
|
7716
|
-
(value) => !
|
|
8172
|
+
(value) => !path23.posix.isAbsolute(value) && !value.includes("\\"),
|
|
7717
8173
|
"expected a portable relative path"
|
|
7718
8174
|
);
|
|
7719
8175
|
var DEFAULT_QUALITY_TOLERANCES = {
|
|
@@ -7860,10 +8316,10 @@ function measureDecodedImageQuality(png, digest2) {
|
|
|
7860
8316
|
};
|
|
7861
8317
|
}
|
|
7862
8318
|
async function measureImageQuality(file) {
|
|
7863
|
-
const absolute =
|
|
8319
|
+
const absolute = path23.resolve(file);
|
|
7864
8320
|
let bytes;
|
|
7865
8321
|
try {
|
|
7866
|
-
bytes = await
|
|
8322
|
+
bytes = await readFile17(absolute);
|
|
7867
8323
|
} catch (error) {
|
|
7868
8324
|
throw new Error(`Cannot read quality image ${absolute}: ${message4(error)}`, { cause: error });
|
|
7869
8325
|
}
|
|
@@ -7882,11 +8338,11 @@ function resolveQualityInputs(raw, inputsFilePath) {
|
|
|
7882
8338
|
}
|
|
7883
8339
|
const ids = parsed.data.map((entry) => entry.id);
|
|
7884
8340
|
if (new Set(ids).size !== ids.length) throw new Error("--inputs contains a duplicate quality case id.");
|
|
7885
|
-
const root =
|
|
8341
|
+
const root = path23.dirname(path23.resolve(inputsFilePath));
|
|
7886
8342
|
return parsed.data.map((entry) => ({
|
|
7887
8343
|
id: entry.id,
|
|
7888
|
-
file:
|
|
7889
|
-
...entry.record ? { record:
|
|
8344
|
+
file: path23.resolve(root, entry.path),
|
|
8345
|
+
...entry.record ? { record: path23.resolve(root, entry.record) } : {},
|
|
7890
8346
|
tolerances: QualityToleranceSchema.parse({
|
|
7891
8347
|
...DEFAULT_QUALITY_TOLERANCES,
|
|
7892
8348
|
...entry.tolerances
|
|
@@ -7894,7 +8350,7 @@ function resolveQualityInputs(raw, inputsFilePath) {
|
|
|
7894
8350
|
}));
|
|
7895
8351
|
}
|
|
7896
8352
|
function portableRelative2(from, to) {
|
|
7897
|
-
return
|
|
8353
|
+
return path23.relative(from, path23.resolve(to)).split(path23.sep).join("/") || ".";
|
|
7898
8354
|
}
|
|
7899
8355
|
async function verifyRecordForImage(recordPath, imagePath) {
|
|
7900
8356
|
const verification = await verifyArtifactBundle(recordPath);
|
|
@@ -7909,20 +8365,20 @@ async function verifyRecordForImage(recordPath, imagePath) {
|
|
|
7909
8365
|
if (record.kind !== "refine" || options === null || typeof options !== "object" || options.schema !== "pixelkiln-quality") {
|
|
7910
8366
|
throw new Error(`Quality record ${recordPath} is not a PixelKiln refinement record.`);
|
|
7911
8367
|
}
|
|
7912
|
-
const outputs = record.outputs.map((output) =>
|
|
7913
|
-
if (!outputs.includes(
|
|
8368
|
+
const outputs = record.outputs.map((output) => path23.resolve(path23.dirname(recordPath), output.path));
|
|
8369
|
+
if (!outputs.includes(path23.resolve(imagePath))) {
|
|
7914
8370
|
throw new Error(`Quality record ${recordPath} does not own ${imagePath}.`);
|
|
7915
8371
|
}
|
|
7916
8372
|
}
|
|
7917
8373
|
async function snapshotQualityBaseline(inputs, baselinePath, options = {}) {
|
|
7918
8374
|
if (!inputs.length) throw new Error("Cannot snapshot an empty quality baseline.");
|
|
7919
|
-
const absolute =
|
|
8375
|
+
const absolute = path23.resolve(baselinePath);
|
|
7920
8376
|
for (const input of inputs) {
|
|
7921
|
-
if (
|
|
8377
|
+
if (path23.resolve(input.file) === absolute || input.record && path23.resolve(input.record) === absolute) {
|
|
7922
8378
|
throw new Error(`Quality baseline output would overwrite an input: ${absolute}.`);
|
|
7923
8379
|
}
|
|
7924
8380
|
}
|
|
7925
|
-
const root =
|
|
8381
|
+
const root = path23.dirname(absolute);
|
|
7926
8382
|
const cases = [];
|
|
7927
8383
|
for (const input of [...inputs].sort((a, b) => a.id.localeCompare(b.id))) {
|
|
7928
8384
|
const expected = await measureImageQuality(input.file);
|
|
@@ -7949,8 +8405,8 @@ async function snapshotQualityBaseline(inputs, baselinePath, options = {}) {
|
|
|
7949
8405
|
cases
|
|
7950
8406
|
});
|
|
7951
8407
|
const data = Buffer.from(JSON.stringify(baseline, null, 2) + "\n");
|
|
7952
|
-
if (
|
|
7953
|
-
const current = await
|
|
8408
|
+
if (existsSync18(absolute) && !options.force) {
|
|
8409
|
+
const current = await readFile17(absolute);
|
|
7954
8410
|
if (!current.equals(data)) {
|
|
7955
8411
|
throw new Error(`Quality baseline already exists with different content: ${absolute}. Pass --force to replace it.`);
|
|
7956
8412
|
}
|
|
@@ -7959,10 +8415,10 @@ async function snapshotQualityBaseline(inputs, baselinePath, options = {}) {
|
|
|
7959
8415
|
return { baseline, path: absolute, changed: result.changed.length > 0 };
|
|
7960
8416
|
}
|
|
7961
8417
|
async function readQualityBaseline(baselinePath) {
|
|
7962
|
-
const absolute =
|
|
8418
|
+
const absolute = path23.resolve(baselinePath);
|
|
7963
8419
|
let raw;
|
|
7964
8420
|
try {
|
|
7965
|
-
raw = JSON.parse(await
|
|
8421
|
+
raw = JSON.parse(await readFile17(absolute, "utf8"));
|
|
7966
8422
|
} catch (error) {
|
|
7967
8423
|
throw new Error(`Could not read quality baseline ${absolute}: ${message4(error)}`, { cause: error });
|
|
7968
8424
|
}
|
|
@@ -8032,12 +8488,12 @@ function regressionViolations(expected, actual, tolerances) {
|
|
|
8032
8488
|
return { violations, warnings };
|
|
8033
8489
|
}
|
|
8034
8490
|
async function checkQualityBaseline(baselinePath) {
|
|
8035
|
-
const absolute =
|
|
8491
|
+
const absolute = path23.resolve(baselinePath);
|
|
8036
8492
|
const baseline = await readQualityBaseline(absolute);
|
|
8037
|
-
const root =
|
|
8493
|
+
const root = path23.dirname(absolute);
|
|
8038
8494
|
const cases = [];
|
|
8039
8495
|
for (const entry of baseline.cases) {
|
|
8040
|
-
const file =
|
|
8496
|
+
const file = path23.resolve(root, entry.file);
|
|
8041
8497
|
let actual = null;
|
|
8042
8498
|
const violations = [];
|
|
8043
8499
|
const warnings = [];
|
|
@@ -8050,7 +8506,7 @@ async function checkQualityBaseline(baselinePath) {
|
|
|
8050
8506
|
violations.push(message4(error));
|
|
8051
8507
|
}
|
|
8052
8508
|
if (entry.record) {
|
|
8053
|
-
const recordPath =
|
|
8509
|
+
const recordPath = path23.resolve(root, entry.record.path);
|
|
8054
8510
|
try {
|
|
8055
8511
|
const recordHash = await sha256File(recordPath);
|
|
8056
8512
|
if (recordHash !== entry.record.sha256) violations.push("quality record hash changed");
|
|
@@ -8088,11 +8544,11 @@ async function checkQualityBaseline(baselinePath) {
|
|
|
8088
8544
|
// src/cli.ts
|
|
8089
8545
|
var log = (msg = "") => console.log(msg);
|
|
8090
8546
|
async function provenanceFile(id, file) {
|
|
8091
|
-
const absolute =
|
|
8547
|
+
const absolute = path24.resolve(file);
|
|
8092
8548
|
return {
|
|
8093
8549
|
id,
|
|
8094
8550
|
path: absolute,
|
|
8095
|
-
sha256:
|
|
8551
|
+
sha256: existsSync19(absolute) ? await sha256File(absolute) : null,
|
|
8096
8552
|
included: true
|
|
8097
8553
|
};
|
|
8098
8554
|
}
|
|
@@ -8349,7 +8805,7 @@ function parseArgs(argv) {
|
|
|
8349
8805
|
return {
|
|
8350
8806
|
command,
|
|
8351
8807
|
manifest,
|
|
8352
|
-
lock: get("--lock") ??
|
|
8808
|
+
lock: get("--lock") ?? path24.join(path24.dirname(path24.resolve(manifest)), "pixelkiln.lock.json"),
|
|
8353
8809
|
explicitLock: get("--lock"),
|
|
8354
8810
|
styles: list("--style"),
|
|
8355
8811
|
assets: list("--only"),
|
|
@@ -8497,7 +8953,17 @@ Examples
|
|
|
8497
8953
|
`;
|
|
8498
8954
|
function printPlan(plan) {
|
|
8499
8955
|
const counts = summarize(plan);
|
|
8500
|
-
const order = [
|
|
8956
|
+
const order = [
|
|
8957
|
+
"blocked",
|
|
8958
|
+
"missing",
|
|
8959
|
+
"untracked",
|
|
8960
|
+
"stale",
|
|
8961
|
+
"failed",
|
|
8962
|
+
"recoverable",
|
|
8963
|
+
"in-flight",
|
|
8964
|
+
"orphaned",
|
|
8965
|
+
"ok"
|
|
8966
|
+
];
|
|
8501
8967
|
for (const state of order) {
|
|
8502
8968
|
const items = plan.items.filter((i) => i.state === state);
|
|
8503
8969
|
if (!items.length) continue;
|
|
@@ -8510,7 +8976,7 @@ function printPlan(plan) {
|
|
|
8510
8976
|
}
|
|
8511
8977
|
log(
|
|
8512
8978
|
`
|
|
8513
|
-
totals: ${counts.ok} ok \xB7 ${counts.missing} missing \xB7 ${counts.untracked} untracked \xB7 ${counts.stale} stale \xB7 ${counts["in-flight"]} in-flight \xB7 ${counts.orphaned} orphaned \xB7 ${counts.recoverable} recoverable \xB7 ${counts.failed} failed`
|
|
8979
|
+
totals: ${counts.ok} ok \xB7 ${counts.blocked} blocked \xB7 ${counts.missing} missing \xB7 ${counts.untracked} untracked \xB7 ${counts.stale} stale \xB7 ${counts["in-flight"]} in-flight \xB7 ${counts.orphaned} orphaned \xB7 ${counts.recoverable} recoverable \xB7 ${counts.failed} failed`
|
|
8514
8980
|
);
|
|
8515
8981
|
if (plan.actionable.length) {
|
|
8516
8982
|
for (const group of plan.groups) {
|
|
@@ -8604,10 +9070,10 @@ async function confirm(question, auto) {
|
|
|
8604
9070
|
return answer === "y" || answer === "yes";
|
|
8605
9071
|
}
|
|
8606
9072
|
async function requireCompleteWorkspaceClaims(workspacePath) {
|
|
8607
|
-
if (!
|
|
9073
|
+
if (!existsSync19(workspacePath)) {
|
|
8608
9074
|
throw new Error(`Workspace catalog not found: ${workspacePath}`);
|
|
8609
9075
|
}
|
|
8610
|
-
const dir =
|
|
9076
|
+
const dir = path24.dirname(path24.resolve(workspacePath));
|
|
8611
9077
|
const ws = await loadWorkspace(workspacePath);
|
|
8612
9078
|
const diagnostics = validateWorkspace(ws, dir);
|
|
8613
9079
|
const errors = diagnostics.filter((d) => d.level === "error");
|
|
@@ -8628,7 +9094,7 @@ async function main() {
|
|
|
8628
9094
|
}
|
|
8629
9095
|
if (args.command === "--version" || args.command === "-v") {
|
|
8630
9096
|
const pkg = JSON.parse(
|
|
8631
|
-
await
|
|
9097
|
+
await readFile18(new URL("../package.json", import.meta.url), "utf8")
|
|
8632
9098
|
);
|
|
8633
9099
|
log(`${pkg.name} ${pkg.version}`);
|
|
8634
9100
|
return;
|
|
@@ -8639,15 +9105,15 @@ async function main() {
|
|
|
8639
9105
|
if (!args.out) throw new Error("quality snapshot needs --out <pixelkiln.quality.json>.");
|
|
8640
9106
|
let raw;
|
|
8641
9107
|
try {
|
|
8642
|
-
raw = JSON.parse(await
|
|
9108
|
+
raw = JSON.parse(await readFile18(path24.resolve(args.inputs), "utf8"));
|
|
8643
9109
|
} catch (error) {
|
|
8644
9110
|
throw new Error(
|
|
8645
|
-
`Could not read quality inputs ${
|
|
9111
|
+
`Could not read quality inputs ${path24.resolve(args.inputs)}: ${error instanceof Error ? error.message : String(error)}`,
|
|
8646
9112
|
{ cause: error }
|
|
8647
9113
|
);
|
|
8648
9114
|
}
|
|
8649
9115
|
const inputs = resolveQualityInputs(raw, args.inputs);
|
|
8650
|
-
if (
|
|
9116
|
+
if (path24.resolve(args.inputs) === path24.resolve(args.out)) {
|
|
8651
9117
|
throw new Error("quality snapshot --out must not overwrite its --inputs file.");
|
|
8652
9118
|
}
|
|
8653
9119
|
const result = await snapshotQualityBaseline(inputs, args.out, { force: args.force });
|
|
@@ -8660,7 +9126,7 @@ async function main() {
|
|
|
8660
9126
|
}, null, 2));
|
|
8661
9127
|
} else {
|
|
8662
9128
|
log(
|
|
8663
|
-
` ${result.changed ? "wrote" : "unchanged"} ${
|
|
9129
|
+
` ${result.changed ? "wrote" : "unchanged"} ${path24.relative(process.cwd(), result.path)} (${result.baseline.cases.length} case(s))`
|
|
8664
9130
|
);
|
|
8665
9131
|
log(` Review the tolerances, commit the baseline, then run quality check in CI.`);
|
|
8666
9132
|
}
|
|
@@ -8755,14 +9221,14 @@ async function main() {
|
|
|
8755
9221
|
}, null, 2));
|
|
8756
9222
|
} else {
|
|
8757
9223
|
log(` installed ${result.recipe.id}@${result.recipe.version}`);
|
|
8758
|
-
log(` ${
|
|
9224
|
+
log(` ${path24.relative(process.cwd(), result.destination) || "."}`);
|
|
8759
9225
|
log(`
|
|
8760
9226
|
Add this entry under your manifest's styles object:`);
|
|
8761
9227
|
log(JSON.stringify({ [result.styleId]: result.style }, null, 2));
|
|
8762
9228
|
if (result.recipe.models.length) {
|
|
8763
9229
|
log(`
|
|
8764
9230
|
Models are not downloaded automatically. Verify them with:`);
|
|
8765
|
-
log(` pixelkiln recipe verify ${
|
|
9231
|
+
log(` pixelkiln recipe verify ${path24.relative(process.cwd(), result.destination)} --model-root <ComfyUI/models>`);
|
|
8766
9232
|
}
|
|
8767
9233
|
}
|
|
8768
9234
|
return;
|
|
@@ -8796,12 +9262,12 @@ async function main() {
|
|
|
8796
9262
|
` recovered ${result2.detection.columns}x${result2.detection.rows} native grid (${result2.detection.confidence}: ${result2.detection.consensus})`
|
|
8797
9263
|
);
|
|
8798
9264
|
log(` enforced ${result2.palette.length}-color palette without dithering`);
|
|
8799
|
-
log(` wrote ${
|
|
8800
|
-
log(` quality record: ${
|
|
9265
|
+
log(` wrote ${path24.relative(process.cwd(), result2.output)}`);
|
|
9266
|
+
log(` quality record: ${path24.relative(process.cwd(), result2.record)}`);
|
|
8801
9267
|
log(`
|
|
8802
9268
|
Automated checks passed. Human 1\xD7 review is still required:`);
|
|
8803
9269
|
log(
|
|
8804
|
-
` pixelkiln refine approve --from ${
|
|
9270
|
+
` pixelkiln refine approve --from ${path24.relative(process.cwd(), result2.record)} --reviewer "Your Name"`
|
|
8805
9271
|
);
|
|
8806
9272
|
}
|
|
8807
9273
|
return;
|
|
@@ -8821,21 +9287,21 @@ async function main() {
|
|
|
8821
9287
|
note: args.note
|
|
8822
9288
|
});
|
|
8823
9289
|
if (args.json) log(JSON.stringify(result2, null, 2));
|
|
8824
|
-
else log(` approved ${
|
|
9290
|
+
else log(` approved ${path24.relative(process.cwd(), result2.output)} by ${args.reviewer.trim()}`);
|
|
8825
9291
|
return;
|
|
8826
9292
|
}
|
|
8827
9293
|
const result = await checkQualityRecord(args.from);
|
|
8828
9294
|
if (args.json) {
|
|
8829
9295
|
log(JSON.stringify(result, null, 2));
|
|
8830
9296
|
} else {
|
|
8831
|
-
log(` ${result.safe ? "release-ready" : "not release-ready"}: ${
|
|
9297
|
+
log(` ${result.safe ? "release-ready" : "not release-ready"}: ${path24.relative(process.cwd(), result.output || result.record)}`);
|
|
8832
9298
|
for (const reason of result.reasons) log(` ${reason}`);
|
|
8833
9299
|
}
|
|
8834
9300
|
if (!result.safe) process.exitCode = 1;
|
|
8835
9301
|
return;
|
|
8836
9302
|
}
|
|
8837
9303
|
if (args.command === "balance") {
|
|
8838
|
-
loadEnvFiles(
|
|
9304
|
+
loadEnvFiles(path24.dirname(path24.resolve(args.manifest)));
|
|
8839
9305
|
loadEnvFiles(process.cwd());
|
|
8840
9306
|
const loaded2 = await loadManifest(args.manifest);
|
|
8841
9307
|
const selectedProvider = accountProviderId(loaded2.manifest, args.provider, "balance");
|
|
@@ -8848,31 +9314,31 @@ async function main() {
|
|
|
8848
9314
|
}
|
|
8849
9315
|
if (args.command === "init") {
|
|
8850
9316
|
if (!args.from) throw new Error("init needs --from <dir> pointing at your existing PNGs.");
|
|
8851
|
-
const root =
|
|
8852
|
-
if (!
|
|
9317
|
+
const root = path24.resolve(args.from);
|
|
9318
|
+
if (!existsSync19(root)) throw new Error(`No directory at ${root}`);
|
|
8853
9319
|
const generator = args.generator ?? "map";
|
|
8854
9320
|
if (generator !== "1dir" && generator !== "map") {
|
|
8855
9321
|
throw new Error(`--generator must be "1dir" or "map", got "${args.generator}".`);
|
|
8856
9322
|
}
|
|
8857
|
-
const target =
|
|
9323
|
+
const target = path24.resolve(args.out ?? "pixelkiln.manifest.json");
|
|
8858
9324
|
const { assets, skipped } = await scanAssets(root, { exclude: args.exclude });
|
|
8859
9325
|
if (!assets.length) throw new Error(`No PNGs found under ${root}`);
|
|
8860
9326
|
const manifest = buildManifest(
|
|
8861
|
-
args.name ??
|
|
9327
|
+
args.name ?? path24.basename(path24.dirname(target)),
|
|
8862
9328
|
args.styles[0] ?? "base",
|
|
8863
9329
|
generator,
|
|
8864
|
-
|
|
9330
|
+
path24.relative(path24.dirname(target), root) || ".",
|
|
8865
9331
|
assets
|
|
8866
9332
|
);
|
|
8867
9333
|
await writeManifestFile(target, manifest);
|
|
8868
|
-
log(` scanned ${assets.length} PNG(s) under ${
|
|
9334
|
+
log(` scanned ${assets.length} PNG(s) under ${path24.relative(process.cwd(), root)}`);
|
|
8869
9335
|
if (skipped.length) log(` skipped ${skipped.length} unreadable file(s)`);
|
|
8870
|
-
log(` wrote ${
|
|
9336
|
+
log(` wrote ${path24.relative(process.cwd(), target)}`);
|
|
8871
9337
|
log(`
|
|
8872
9338
|
Prompts are intentionally empty. To recover the real ones from your`);
|
|
8873
9339
|
log(` PixelLab account instead of inventing them:`);
|
|
8874
9340
|
log(`
|
|
8875
|
-
pixelkiln adopt --manifest ${
|
|
9341
|
+
pixelkiln adopt --manifest ${path24.relative(process.cwd(), target)} --write-prompts
|
|
8876
9342
|
`);
|
|
8877
9343
|
return;
|
|
8878
9344
|
}
|
|
@@ -8920,10 +9386,10 @@ async function main() {
|
|
|
8920
9386
|
if (args.primaryOnly || args.outputRoles.length) {
|
|
8921
9387
|
throw new Error("--primary-only and --output-role require manifest-driven pack");
|
|
8922
9388
|
}
|
|
8923
|
-
const raw = JSON.parse(await
|
|
9389
|
+
const raw = JSON.parse(await readFile18(path24.resolve(args.inputs), "utf8"));
|
|
8924
9390
|
const inputs = resolvePackInputs(raw, args.inputs);
|
|
8925
9391
|
const { png, atlas, skipped, sources } = packSprites(inputs, { columns: args.columns });
|
|
8926
|
-
const base =
|
|
9392
|
+
const base = path24.resolve(args.out.replace(/\.png$/, ""));
|
|
8927
9393
|
const outputs = [
|
|
8928
9394
|
{ path: `${base}.png`, data: png },
|
|
8929
9395
|
{ path: `${base}.json`, data: JSON.stringify(atlas, null, 2) + "\n" }
|
|
@@ -8936,17 +9402,17 @@ async function main() {
|
|
|
8936
9402
|
log(
|
|
8937
9403
|
` ${atlas.frames.length} sprite(s), ${atlas.sheet.width}x${atlas.sheet.height} in ${atlas.columns} column(s) \u2014 ${(png.length / 1024).toFixed(1)} KB`
|
|
8938
9404
|
);
|
|
8939
|
-
log(` ${
|
|
9405
|
+
log(` ${path24.relative(process.cwd(), base)}.png + .json + .pixelkiln.json`);
|
|
8940
9406
|
for (const s of skipped) log(` skipped ${s.id}: ${s.reason}`);
|
|
8941
9407
|
return;
|
|
8942
9408
|
}
|
|
8943
9409
|
if (args.command === "workspace") {
|
|
8944
|
-
const workspacePath =
|
|
8945
|
-
const dir =
|
|
9410
|
+
const workspacePath = path24.resolve(args.workspace ?? "pixelkiln.workspace.json");
|
|
9411
|
+
const dir = path24.dirname(workspacePath);
|
|
8946
9412
|
if (args.subcommand === "add") {
|
|
8947
|
-
const manifestPath =
|
|
9413
|
+
const manifestPath = path24.resolve(args.target);
|
|
8948
9414
|
const loadedTarget = await loadManifest(manifestPath);
|
|
8949
|
-
const lockPath = args.explicitLock ?
|
|
9415
|
+
const lockPath = args.explicitLock ? path24.resolve(args.explicitLock) : path24.join(path24.dirname(manifestPath), "pixelkiln.lock.json");
|
|
8950
9416
|
const ws = await loadWorkspace(workspacePath);
|
|
8951
9417
|
const id = args.name ?? loadedTarget.manifest.name;
|
|
8952
9418
|
if (ws.projects.some((p) => p.id === id)) {
|
|
@@ -8966,10 +9432,10 @@ async function main() {
|
|
|
8966
9432
|
...args.account ? { account: args.account } : {}
|
|
8967
9433
|
};
|
|
8968
9434
|
await saveWorkspace(workspacePath, { version: 1, projects: [...ws.projects, project] });
|
|
8969
|
-
log(` registered "${id}" in ${
|
|
9435
|
+
log(` registered "${id}" in ${path24.relative(process.cwd(), workspacePath)}`);
|
|
8970
9436
|
log(` manifest: ${project.manifest}`);
|
|
8971
9437
|
log(` lock: ${project.lock}`);
|
|
8972
|
-
if (!
|
|
9438
|
+
if (!existsSync19(lockPath)) {
|
|
8973
9439
|
log(
|
|
8974
9440
|
` warning: no lockfile there yet \u2014 this project contributes no claims until one is generated`
|
|
8975
9441
|
);
|
|
@@ -8978,7 +9444,7 @@ async function main() {
|
|
|
8978
9444
|
}
|
|
8979
9445
|
if (args.subcommand === "remove") {
|
|
8980
9446
|
const ws = await loadWorkspace(workspacePath);
|
|
8981
|
-
const resolvedTarget =
|
|
9447
|
+
const resolvedTarget = path24.resolve(args.target);
|
|
8982
9448
|
const match = ws.projects.find(
|
|
8983
9449
|
(p) => p.id === args.target || resolveProject(dir, p).manifestPath === resolvedTarget
|
|
8984
9450
|
);
|
|
@@ -8989,11 +9455,11 @@ async function main() {
|
|
|
8989
9455
|
version: 1,
|
|
8990
9456
|
projects: ws.projects.filter((p) => p !== match)
|
|
8991
9457
|
});
|
|
8992
|
-
log(` removed "${match.id}" from ${
|
|
9458
|
+
log(` removed "${match.id}" from ${path24.relative(process.cwd(), workspacePath)}`);
|
|
8993
9459
|
return;
|
|
8994
9460
|
}
|
|
8995
9461
|
if (args.subcommand === "list") {
|
|
8996
|
-
if (!
|
|
9462
|
+
if (!existsSync19(workspacePath)) {
|
|
8997
9463
|
throw new Error(`Workspace catalog not found: ${workspacePath}`);
|
|
8998
9464
|
}
|
|
8999
9465
|
const ws = await loadWorkspace(workspacePath);
|
|
@@ -9001,9 +9467,9 @@ async function main() {
|
|
|
9001
9467
|
if (args.json) {
|
|
9002
9468
|
log(JSON.stringify({ version: 1, workspace: workspacePath, projects: ws.projects, diagnostics }, null, 2));
|
|
9003
9469
|
} else if (!ws.projects.length) {
|
|
9004
|
-
log(` no projects registered in ${
|
|
9470
|
+
log(` no projects registered in ${path24.relative(process.cwd(), workspacePath)}`);
|
|
9005
9471
|
} else {
|
|
9006
|
-
log(` ${ws.projects.length} project(s) in ${
|
|
9472
|
+
log(` ${ws.projects.length} project(s) in ${path24.relative(process.cwd(), workspacePath)}:`);
|
|
9007
9473
|
for (const p of ws.projects) {
|
|
9008
9474
|
log(` ${p.id.padEnd(24)} ${p.manifest.padEnd(40)} (${p.provider}${p.account ? `, ${p.account}` : ""})`);
|
|
9009
9475
|
}
|
|
@@ -9013,7 +9479,7 @@ async function main() {
|
|
|
9013
9479
|
return;
|
|
9014
9480
|
}
|
|
9015
9481
|
if (args.subcommand === "status") {
|
|
9016
|
-
if (!
|
|
9482
|
+
if (!existsSync19(workspacePath)) {
|
|
9017
9483
|
throw new Error(`Workspace catalog not found: ${workspacePath}`);
|
|
9018
9484
|
}
|
|
9019
9485
|
const ws = await loadWorkspace(workspacePath);
|
|
@@ -9021,7 +9487,7 @@ async function main() {
|
|
|
9021
9487
|
if (args.json) {
|
|
9022
9488
|
log(JSON.stringify({ ...report, workspace: workspacePath }, null, 2));
|
|
9023
9489
|
} else {
|
|
9024
|
-
log(` workspace: ${
|
|
9490
|
+
log(` workspace: ${path24.relative(process.cwd(), workspacePath)}`);
|
|
9025
9491
|
for (const p of report.projects) {
|
|
9026
9492
|
if (p.error) {
|
|
9027
9493
|
log(`
|
|
@@ -9067,14 +9533,14 @@ async function main() {
|
|
|
9067
9533
|
return;
|
|
9068
9534
|
}
|
|
9069
9535
|
}
|
|
9070
|
-
if (!
|
|
9536
|
+
if (!existsSync19(path24.resolve(args.manifest))) {
|
|
9071
9537
|
throw new Error(
|
|
9072
|
-
`No manifest at ${
|
|
9538
|
+
`No manifest at ${path24.resolve(args.manifest)}. Pass --manifest, or run \`pixelkiln init --from <dir>\`.`
|
|
9073
9539
|
);
|
|
9074
9540
|
}
|
|
9075
|
-
const manifestDir =
|
|
9541
|
+
const manifestDir = path24.dirname(path24.resolve(args.manifest));
|
|
9076
9542
|
const envFiles = [...loadEnvFiles(manifestDir)];
|
|
9077
|
-
if (
|
|
9543
|
+
if (path24.resolve(process.cwd()) !== manifestDir) envFiles.push(...loadEnvFiles(process.cwd()));
|
|
9078
9544
|
const loaded = await loadManifest(args.manifest);
|
|
9079
9545
|
let specs = await resolveSpecs(loaded, {
|
|
9080
9546
|
styles: args.styles,
|
|
@@ -9167,7 +9633,7 @@ async function main() {
|
|
|
9167
9633
|
log(` ${item.state.padEnd(16)} ${item.key} ${item.reason}`);
|
|
9168
9634
|
if (item.state === "needs-approval") {
|
|
9169
9635
|
log(
|
|
9170
|
-
` pixelkiln refine approve --from ${
|
|
9636
|
+
` pixelkiln refine approve --from ${path24.relative(process.cwd(), item.record)} --reviewer "Your Name"`
|
|
9171
9637
|
);
|
|
9172
9638
|
}
|
|
9173
9639
|
}
|
|
@@ -9225,10 +9691,19 @@ async function main() {
|
|
|
9225
9691
|
actionable: group.actionable.map((item) => item.key)
|
|
9226
9692
|
})),
|
|
9227
9693
|
actionable: plan.actionable.map((i) => i.key),
|
|
9228
|
-
items: plan.items.map(({ key, state, reason, quality }) => ({
|
|
9694
|
+
items: plan.items.map(({ key, state, reason, quality, spec }) => ({
|
|
9229
9695
|
key,
|
|
9230
9696
|
state,
|
|
9231
9697
|
reason,
|
|
9698
|
+
...spec.revision ? {
|
|
9699
|
+
revision: {
|
|
9700
|
+
mode: spec.revision.mode,
|
|
9701
|
+
from: spec.revision.sourceAssetId,
|
|
9702
|
+
sourceSha256: spec.revision.sourceSha256,
|
|
9703
|
+
...spec.revision.maskSha256 ? { maskSha256: spec.revision.maskSha256 } : {},
|
|
9704
|
+
...spec.revision.strength == null ? {} : { strength: spec.revision.strength }
|
|
9705
|
+
}
|
|
9706
|
+
} : {},
|
|
9232
9707
|
...quality ? { quality: { state: quality.state, reason: quality.reason } } : {}
|
|
9233
9708
|
}))
|
|
9234
9709
|
}, null, 2));
|
|
@@ -9249,7 +9724,7 @@ async function main() {
|
|
|
9249
9724
|
let intact = true;
|
|
9250
9725
|
for (const output of entry.outputs) {
|
|
9251
9726
|
const file = resolveOutputPath(output.path, item.spec.root);
|
|
9252
|
-
if (!
|
|
9727
|
+
if (!existsSync19(file) || await sha256File(file) !== output.sha256) {
|
|
9253
9728
|
intact = false;
|
|
9254
9729
|
break;
|
|
9255
9730
|
}
|
|
@@ -9307,7 +9782,7 @@ async function main() {
|
|
|
9307
9782
|
if (args.primaryOnly && args.outputRoles.length) {
|
|
9308
9783
|
throw new Error("pack accepts either --primary-only or --output-role, not both");
|
|
9309
9784
|
}
|
|
9310
|
-
const manifestDir2 =
|
|
9785
|
+
const manifestDir2 = path24.dirname(path24.resolve(args.manifest));
|
|
9311
9786
|
const styleIds = args.styles.length ? args.styles : Object.keys(loaded.manifest.styles);
|
|
9312
9787
|
for (const styleId of styleIds) {
|
|
9313
9788
|
const packagingSpecs = await resolveSpecs(loaded, { styles: [styleId] });
|
|
@@ -9319,7 +9794,7 @@ async function main() {
|
|
|
9319
9794
|
sourceOverrides: qualitySources
|
|
9320
9795
|
});
|
|
9321
9796
|
const style = loaded.manifest.styles[styleId];
|
|
9322
|
-
const base = args.out ?
|
|
9797
|
+
const base = args.out ? path24.resolve(args.out.replace(/\.png$/, "")) : path24.resolve(manifestDir2, style.outDir, `${styleId}-sheet`);
|
|
9323
9798
|
const outputs = [
|
|
9324
9799
|
{ path: `${base}.png`, data: png },
|
|
9325
9800
|
{ path: `${base}.json`, data: JSON.stringify(atlas, null, 2) + "\n" }
|
|
@@ -9346,13 +9821,13 @@ async function main() {
|
|
|
9346
9821
|
log(
|
|
9347
9822
|
` ${styleId} \u2014 ${atlas.frames.length} sprite(s), ${atlas.sheet.width}x${atlas.sheet.height} in ${atlas.columns} column(s)`
|
|
9348
9823
|
);
|
|
9349
|
-
log(` ${
|
|
9824
|
+
log(` ${path24.relative(process.cwd(), base)}.png + .json + .pixelkiln.json`);
|
|
9350
9825
|
for (const s of skipped) log(` skipped ${s.id}: ${s.reason}`);
|
|
9351
9826
|
}
|
|
9352
9827
|
return;
|
|
9353
9828
|
}
|
|
9354
9829
|
if (args.command === "mount") {
|
|
9355
|
-
const manifestDir2 =
|
|
9830
|
+
const manifestDir2 = path24.dirname(path24.resolve(args.manifest));
|
|
9356
9831
|
const styleIds = args.styles.length ? args.styles : Object.keys(loaded.manifest.styles);
|
|
9357
9832
|
for (const styleId of styleIds) {
|
|
9358
9833
|
const style = loaded.manifest.styles[styleId];
|
|
@@ -9389,7 +9864,7 @@ async function main() {
|
|
|
9389
9864
|
sources,
|
|
9390
9865
|
outputRoles
|
|
9391
9866
|
);
|
|
9392
|
-
const out =
|
|
9867
|
+
const out = path24.resolve(manifestDir2, style.mount.out);
|
|
9393
9868
|
const metadata = out.replace(/\.png$/, "") + ".json";
|
|
9394
9869
|
const companion = out.replace(/\.png$/, "") + ".pixelkiln.json";
|
|
9395
9870
|
const outputs = [
|
|
@@ -9406,7 +9881,7 @@ async function main() {
|
|
|
9406
9881
|
await provenanceFile("$lock", args.lock),
|
|
9407
9882
|
...qualityRecords,
|
|
9408
9883
|
...artifactSources.filter(
|
|
9409
|
-
(source) => source.id !== "$base" ||
|
|
9884
|
+
(source) => source.id !== "$base" || path24.resolve(source.path) !== out
|
|
9410
9885
|
)
|
|
9411
9886
|
],
|
|
9412
9887
|
options: {
|
|
@@ -9419,7 +9894,7 @@ async function main() {
|
|
|
9419
9894
|
log(
|
|
9420
9895
|
` ${styleId} \u2014 ${atlas.frames.length} cell(s) into ${atlas.sheet.width}x${atlas.sheet.height}` + (overBase ? ` over ${style.mount.base}` : " (new sheet)")
|
|
9421
9896
|
);
|
|
9422
|
-
log(` ${
|
|
9897
|
+
log(` ${path24.relative(process.cwd(), out)} + atlas/provenance JSON`);
|
|
9423
9898
|
for (const s of skipped) log(` skipped ${s.id}: ${s.reason}`);
|
|
9424
9899
|
}
|
|
9425
9900
|
return;
|
|
@@ -9475,7 +9950,7 @@ async function main() {
|
|
|
9475
9950
|
}
|
|
9476
9951
|
if (args.command === "export") {
|
|
9477
9952
|
const format = args.format ?? "generic";
|
|
9478
|
-
const manifestDir2 =
|
|
9953
|
+
const manifestDir2 = path24.dirname(path24.resolve(args.manifest));
|
|
9479
9954
|
const selected = specs.filter((spec) => {
|
|
9480
9955
|
if (spec.generator !== "tiles") return false;
|
|
9481
9956
|
if (args.styles.length && !args.styles.includes(spec.styleId)) return false;
|
|
@@ -9491,12 +9966,12 @@ async function main() {
|
|
|
9491
9966
|
for (const spec of selected) {
|
|
9492
9967
|
const entry = lock.entries[lockKey(spec.styleId, spec.assetId)];
|
|
9493
9968
|
const style = loaded.manifest.styles[spec.styleId];
|
|
9494
|
-
const defaultBase =
|
|
9495
|
-
const base = args.out ?
|
|
9969
|
+
const defaultBase = path24.resolve(manifestDir2, style.outDir, `${spec.assetId}-tileset`);
|
|
9970
|
+
const base = args.out ? path24.resolve(args.out.replace(/\.(?:png|json|tsj|tres)$/i, "")) : defaultBase;
|
|
9496
9971
|
const result = exportTileset(entry, spec, {
|
|
9497
9972
|
format,
|
|
9498
9973
|
manifestDir: manifestDir2,
|
|
9499
|
-
imageName:
|
|
9974
|
+
imageName: path24.basename(`${base}.png`),
|
|
9500
9975
|
columns: args.columns
|
|
9501
9976
|
});
|
|
9502
9977
|
const outputs = [
|
|
@@ -9514,7 +9989,7 @@ async function main() {
|
|
|
9514
9989
|
asset: spec.assetId,
|
|
9515
9990
|
columns: args.columns ?? null,
|
|
9516
9991
|
format,
|
|
9517
|
-
image:
|
|
9992
|
+
image: path24.basename(`${base}.png`),
|
|
9518
9993
|
providerRules: result.generic.providerRules,
|
|
9519
9994
|
style: spec.styleId,
|
|
9520
9995
|
tileType: spec.tileType ?? null
|
|
@@ -9524,7 +9999,7 @@ async function main() {
|
|
|
9524
9999
|
` ${spec.styleId}/${spec.assetId} \u2014 ${result.generic.tiles.length} tile(s), ${result.generic.sheet.width}x${result.generic.sheet.height} (${format})`
|
|
9525
10000
|
);
|
|
9526
10001
|
log(
|
|
9527
|
-
` ${
|
|
10002
|
+
` ${path24.relative(process.cwd(), base)}.png + ${path24.basename(base)}${result.extension} + .pixelkiln.json`
|
|
9528
10003
|
);
|
|
9529
10004
|
}
|
|
9530
10005
|
return;
|
|
@@ -9574,12 +10049,12 @@ async function main() {
|
|
|
9574
10049
|
tagged ${n} object(s) upstream`);
|
|
9575
10050
|
}
|
|
9576
10051
|
if (args.writePrompts) {
|
|
9577
|
-
const { filled, stillEmpty } = await writePromptsBack(
|
|
10052
|
+
const { filled, stillEmpty } = await writePromptsBack(path24.resolve(args.manifest), lock, {
|
|
9578
10053
|
onProgress: log,
|
|
9579
10054
|
provider: provider.id,
|
|
9580
10055
|
assetIds: specs.map((spec) => spec.assetId)
|
|
9581
10056
|
});
|
|
9582
|
-
log(` recovered ${filled} prompt(s) into ${
|
|
10057
|
+
log(` recovered ${filled} prompt(s) into ${path24.relative(process.cwd(), args.manifest)}`);
|
|
9583
10058
|
const reloaded = await loadManifest(args.manifest);
|
|
9584
10059
|
const rebased = (await resolveSpecs(reloaded, {
|
|
9585
10060
|
styles: args.styles,
|
|
@@ -9608,12 +10083,12 @@ async function main() {
|
|
|
9608
10083
|
const provider = providerFor(selectedAccountProvider);
|
|
9609
10084
|
const jsonMode = args.dryRun && args.json;
|
|
9610
10085
|
const diag = jsonMode ? (msg = "") => console.error(msg) : log;
|
|
9611
|
-
const ownLock =
|
|
10086
|
+
const ownLock = path24.resolve(args.lock);
|
|
9612
10087
|
let workspaceProjects = [];
|
|
9613
10088
|
let workspaceDir = "";
|
|
9614
10089
|
if (args.workspace) {
|
|
9615
|
-
const workspacePath =
|
|
9616
|
-
workspaceDir =
|
|
10090
|
+
const workspacePath = path24.resolve(args.workspace);
|
|
10091
|
+
workspaceDir = path24.dirname(workspacePath);
|
|
9617
10092
|
const complete = await requireCompleteWorkspaceClaims(workspacePath);
|
|
9618
10093
|
workspaceProjects = complete.ws.projects;
|
|
9619
10094
|
for (const d of complete.diagnostics) diag(` WARN ${d.id}: ${d.message}`);
|
|
@@ -9622,12 +10097,12 @@ async function main() {
|
|
|
9622
10097
|
const lockPaths = [
|
|
9623
10098
|
.../* @__PURE__ */ new Set([
|
|
9624
10099
|
...workspaceLockPaths,
|
|
9625
|
-
...
|
|
9626
|
-
...args.claims.map((c) =>
|
|
10100
|
+
...existsSync19(ownLock) ? [ownLock] : [],
|
|
10101
|
+
...args.claims.map((c) => path24.resolve(c))
|
|
9627
10102
|
])
|
|
9628
10103
|
];
|
|
9629
10104
|
diag(` claim set (${lockPaths.length} lockfile(s)):`);
|
|
9630
|
-
for (const p of lockPaths) diag(` ${
|
|
10105
|
+
for (const p of lockPaths) diag(` ${path24.relative(process.cwd(), p)}`);
|
|
9631
10106
|
if (!args.claims.length && !args.workspace) {
|
|
9632
10107
|
diag(
|
|
9633
10108
|
`
|
|
@@ -9635,7 +10110,7 @@ async function main() {
|
|
|
9635
10110
|
pass every other project's lockfile via --claims a.json,b.json, or
|
|
9636
10111
|
register every project in a workspace catalog and pass --workspace.`
|
|
9637
10112
|
);
|
|
9638
|
-
} else if (args.workspace && !workspaceProjects.some((p) => resolveProject(workspaceDir, p).manifestPath ===
|
|
10113
|
+
} else if (args.workspace && !workspaceProjects.some((p) => resolveProject(workspaceDir, p).manifestPath === path24.resolve(args.manifest))) {
|
|
9639
10114
|
diag(
|
|
9640
10115
|
`
|
|
9641
10116
|
This project's manifest is not registered in the workspace catalog. Its own
|
|
@@ -9719,7 +10194,7 @@ async function main() {
|
|
|
9719
10194
|
manifestPath: loaded.path,
|
|
9720
10195
|
manifest: accountManifest,
|
|
9721
10196
|
styleId,
|
|
9722
|
-
importDir:
|
|
10197
|
+
importDir: path24.resolve(loaded.root, style.outDir),
|
|
9723
10198
|
lock,
|
|
9724
10199
|
lockPath: args.lock
|
|
9725
10200
|
},
|
|
@@ -9929,7 +10404,8 @@ async function main() {
|
|
|
9929
10404
|
port: args.port,
|
|
9930
10405
|
open: !args.noOpen,
|
|
9931
10406
|
onProgress: log,
|
|
9932
|
-
keys: providerSpecsForRun.map((spec) => lockKey(spec.styleId, spec.assetId))
|
|
10407
|
+
keys: providerSpecsForRun.map((spec) => lockKey(spec.styleId, spec.assetId)),
|
|
10408
|
+
specs: providerSpecsForRun
|
|
9933
10409
|
});
|
|
9934
10410
|
total.selected += res.selected;
|
|
9935
10411
|
total.skipped += res.skipped;
|