pixelkiln 0.70.0 → 0.71.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +165 -54
- package/dist/cli.js.map +1 -1
- package/dist/client/gallery.css +16 -1
- package/dist/client/gallery.js +3325 -2586
- package/dist/index.cjs +92 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +29 -2
- package/dist/index.d.ts +29 -2
- package/dist/index.js +97 -11
- package/dist/index.js.map +1 -1
- package/docs/CLI.md +11 -0
- package/docs/REVISIONS.md +14 -5
- package/package.json +3 -2
- package/skills/pixelkiln/references/revisions.md +3 -2
package/dist/cli.js
CHANGED
|
@@ -67,7 +67,7 @@ function exitCodeFor(err) {
|
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
// src/cli/main.ts
|
|
70
|
-
import { existsSync as
|
|
70
|
+
import { existsSync as existsSync36 } from "fs";
|
|
71
71
|
import { readFile as readFile32 } from "fs/promises";
|
|
72
72
|
|
|
73
73
|
// src/cli/args.ts
|
|
@@ -108,7 +108,8 @@ var VALUE_FLAGS = [
|
|
|
108
108
|
"--description",
|
|
109
109
|
"--weight",
|
|
110
110
|
"--glyph-px",
|
|
111
|
-
"--frames"
|
|
111
|
+
"--frames",
|
|
112
|
+
"--estimate-limit"
|
|
112
113
|
];
|
|
113
114
|
var BOOL_FLAGS = [
|
|
114
115
|
"--force",
|
|
@@ -430,7 +431,8 @@ function parseArgs(argv) {
|
|
|
430
431
|
description: get("--description"),
|
|
431
432
|
weight: get("--weight"),
|
|
432
433
|
glyphPx: numberOption("--glyph-px", { min: 8, max: 64, integer: true }),
|
|
433
|
-
frames: numberOption("--frames", { min: 3, max: 15, integer: true })
|
|
434
|
+
frames: numberOption("--frames", { min: 3, max: 15, integer: true }),
|
|
435
|
+
estimateLimit: numberOption("--estimate-limit", { min: 0, max: 1e3, integer: true })
|
|
434
436
|
};
|
|
435
437
|
}
|
|
436
438
|
|
|
@@ -543,6 +545,7 @@ Options
|
|
|
543
545
|
--no-open Do not auto-open the browser (pick, salvage, gallery) or editor (edit)
|
|
544
546
|
--edit gallery: allow manifest edits from the page (never spends)
|
|
545
547
|
--no-editor gallery: do not offer or serve the in-browser editor
|
|
548
|
+
--estimate-limit <n> gallery: PixelLab skeleton estimates the page may make (default 10)
|
|
546
549
|
--tag Also push tags upstream after fetch
|
|
547
550
|
--refresh fetch: re-download and replace files whose object changed
|
|
548
551
|
upstream (e.g. edited in PixelLab's editor); no generation
|
|
@@ -14373,7 +14376,7 @@ async function runEdit(args) {
|
|
|
14373
14376
|
|
|
14374
14377
|
// src/cli/commands/gallery.ts
|
|
14375
14378
|
import path33 from "path";
|
|
14376
|
-
import { existsSync as
|
|
14379
|
+
import { existsSync as existsSync25 } from "fs";
|
|
14377
14380
|
|
|
14378
14381
|
// src/gallery/snapshot.ts
|
|
14379
14382
|
import { createHash as createHash3 } from "crypto";
|
|
@@ -14416,6 +14419,16 @@ var SkeletonAnimationRequestSchema = z8.object({
|
|
|
14416
14419
|
/** Replace an existing `keypointsFile` with `set`. */
|
|
14417
14420
|
overwrite: z8.boolean().optional()
|
|
14418
14421
|
}).strict();
|
|
14422
|
+
var SkeletonKeypointsUpdateSchema = z8.object({
|
|
14423
|
+
action: z8.literal("update-skeleton-keypoints"),
|
|
14424
|
+
project: z8.string().min(1).optional(),
|
|
14425
|
+
styleId: z8.string().min(1),
|
|
14426
|
+
assetId: z8.string().min(1),
|
|
14427
|
+
/** The file's SHA-256 when the page loaded it; anything else on disk is a concurrent edit. */
|
|
14428
|
+
expectedSha256: z8.string().regex(/^[0-9a-f]{64}$/),
|
|
14429
|
+
set: SkeletonSetSchema
|
|
14430
|
+
}).strict();
|
|
14431
|
+
var DEFAULT_ESTIMATE_LIMIT = 10;
|
|
14419
14432
|
function skeletonSourceFile(spec) {
|
|
14420
14433
|
if (spec.source) return path26.resolve(spec.root, spec.source);
|
|
14421
14434
|
if (spec.quality && existsSync18(spec.quality.outFile)) return spec.quality.outFile;
|
|
@@ -14424,7 +14437,12 @@ function skeletonSourceFile(spec) {
|
|
|
14424
14437
|
function createGallerySkeletonHandlers(opts) {
|
|
14425
14438
|
const log2 = opts.onProgress ?? (() => {
|
|
14426
14439
|
});
|
|
14440
|
+
const limit = opts.limit ?? DEFAULT_ESTIMATE_LIMIT;
|
|
14441
|
+
let used = 0;
|
|
14442
|
+
const spent = {};
|
|
14443
|
+
const status = () => ({ used, limit, spent: { ...spent } });
|
|
14427
14444
|
return {
|
|
14445
|
+
status,
|
|
14428
14446
|
async estimate(body) {
|
|
14429
14447
|
const parsed = SkeletonEstimateRequestSchema.safeParse(body);
|
|
14430
14448
|
if (!parsed.success) throw new ManifestEditError(`invalid estimate request: ${parsed.error.issues[0]?.message}`);
|
|
@@ -14440,16 +14458,33 @@ function createGallerySkeletonHandlers(opts) {
|
|
|
14440
14458
|
if (!metadata) throw new ManifestEditError(`${path26.basename(file)} is not a readable PNG or JPEG`);
|
|
14441
14459
|
const problem = estimateSkeletonSizeProblem(metadata.width, metadata.height);
|
|
14442
14460
|
if (problem) throw new ManifestEditError(`${key} is ${problem}`);
|
|
14461
|
+
if (used >= limit) {
|
|
14462
|
+
throw Object.assign(
|
|
14463
|
+
new Error(`this gallery has made its ${limit} skeleton estimate${limit === 1 ? "" : "s"}; restart it with --estimate-limit to allow more`),
|
|
14464
|
+
{ status: 429 }
|
|
14465
|
+
);
|
|
14466
|
+
}
|
|
14443
14467
|
const client2 = (opts.client ?? clientFromEnv)();
|
|
14468
|
+
used++;
|
|
14444
14469
|
const res = await client2.estimateSkeleton({ image: { base64: bytes.toString("base64"), format: metadata.format } });
|
|
14445
|
-
|
|
14470
|
+
const billed = billedAmount(res.usage);
|
|
14471
|
+
if (billed) spent[billed.unit] = (spent[billed.unit] ?? 0) + billed.amount;
|
|
14472
|
+
log2(` estimated a skeleton for ${key} (PixelLab estimate-skeleton, ${used}/${limit} this session${billed ? `, billed ${billed.amount} ${billed.unit}` : ""})`);
|
|
14446
14473
|
return {
|
|
14447
14474
|
set: scaffoldSkeletonSet(res.keypoints, request.frames ?? DEFAULT_SCAFFOLD_FRAMES),
|
|
14448
|
-
source: path26.relative(ctx.loaded.root, file).split(path26.sep).join("/")
|
|
14475
|
+
source: path26.relative(ctx.loaded.root, file).split(path26.sep).join("/"),
|
|
14476
|
+
session: status()
|
|
14449
14477
|
};
|
|
14450
14478
|
}
|
|
14451
14479
|
};
|
|
14452
14480
|
}
|
|
14481
|
+
function billedAmount(usage) {
|
|
14482
|
+
if (!usage || typeof usage !== "object") return null;
|
|
14483
|
+
const { type } = usage;
|
|
14484
|
+
if (type !== "usd" && type !== "generations") return null;
|
|
14485
|
+
const amount = usage[type];
|
|
14486
|
+
return typeof amount === "number" && Number.isFinite(amount) ? { unit: type, amount } : null;
|
|
14487
|
+
}
|
|
14453
14488
|
async function createSkeletonAnimation(manifestPath, request) {
|
|
14454
14489
|
const root = path26.dirname(path26.resolve(manifestPath));
|
|
14455
14490
|
const file = path26.resolve(root, request.keypointsFile);
|
|
@@ -14495,6 +14530,26 @@ async function createSkeletonAnimation(manifestPath, request) {
|
|
|
14495
14530
|
throw error;
|
|
14496
14531
|
}
|
|
14497
14532
|
}
|
|
14533
|
+
async function updateSkeletonKeypoints(ctx, request) {
|
|
14534
|
+
const key = lockKey(request.styleId, request.assetId);
|
|
14535
|
+
const spec = ctx.specs.find((candidate) => lockKey(candidate.styleId, candidate.assetId) === key);
|
|
14536
|
+
if (!spec) throw new ManifestEditError(`"${key}" is not declared by the manifest`);
|
|
14537
|
+
const file = spec.revision?.mode === "animate-skeleton" ? spec.revision.keypointsFile : void 0;
|
|
14538
|
+
if (!file) throw new ManifestEditError(`${key} is not an animate-skeleton revision`);
|
|
14539
|
+
const relative = path26.relative(ctx.loaded.root, file);
|
|
14540
|
+
if (relative.startsWith("..") || path26.isAbsolute(relative)) {
|
|
14541
|
+
throw new ManifestEditError(`${key}'s keypoints file is outside the project; edit it by hand`);
|
|
14542
|
+
}
|
|
14543
|
+
const current = existsSync18(file) ? sha256(await readFile17(file)) : null;
|
|
14544
|
+
if (current !== request.expectedSha256) {
|
|
14545
|
+
throw Object.assign(
|
|
14546
|
+
new Error(`${relative.split(path26.sep).join("/")} changed on disk since this page loaded it; press Refresh and edit again`),
|
|
14547
|
+
{ status: 409 }
|
|
14548
|
+
);
|
|
14549
|
+
}
|
|
14550
|
+
await writeFile12(file, JSON.stringify(request.set, null, 2) + "\n");
|
|
14551
|
+
return relative.split(path26.sep).join("/");
|
|
14552
|
+
}
|
|
14498
14553
|
|
|
14499
14554
|
// src/gallery/edit.ts
|
|
14500
14555
|
var Base64 = z9.string().regex(/^[A-Za-z0-9+/]*={0,2}$/).max(Math.ceil(MAX_HAND_EDIT_BYTES / 3) * 4);
|
|
@@ -14571,6 +14626,18 @@ function createGalleryEditHandler(opts) {
|
|
|
14571
14626
|
}
|
|
14572
14627
|
return opts.reload();
|
|
14573
14628
|
}
|
|
14629
|
+
const poses = SkeletonKeypointsUpdateSchema.safeParse(body);
|
|
14630
|
+
if (poses.success) {
|
|
14631
|
+
if (!opts.loadProject) throw new ManifestEditError("pose edits are not available in this gallery");
|
|
14632
|
+
const written = await updateSkeletonKeypoints(await opts.loadProject(poses.data.project), poses.data);
|
|
14633
|
+
log2(` poses saved: ${written} (${poses.data.styleId}/${poses.data.assetId} is stale until generated again)`);
|
|
14634
|
+
return opts.reload();
|
|
14635
|
+
}
|
|
14636
|
+
if (body?.action === "update-skeleton-keypoints") {
|
|
14637
|
+
throw new ManifestEditError(
|
|
14638
|
+
"invalid pose edit: " + poses.error.issues.slice(0, 3).map((issue) => `${issue.path.join(".")}: ${issue.message}`).join("; ")
|
|
14639
|
+
);
|
|
14640
|
+
}
|
|
14574
14641
|
const skeleton = SkeletonAnimationRequestSchema.safeParse(body);
|
|
14575
14642
|
if (skeleton.success) {
|
|
14576
14643
|
let manifestPath2;
|
|
@@ -15086,7 +15153,8 @@ async function buildGallerySnapshot(opts) {
|
|
|
15086
15153
|
mirrorOfKey: spec.mirror ? lockKey(spec.styleId, spec.mirror.sourceAssetId) : entry?.mirror ? lockKey(spec.styleId, entry.mirror.sourceAssetId) : null,
|
|
15087
15154
|
skeleton: spec.revision?.mode === "animate-skeleton" && spec.revision.keypointsFile ? {
|
|
15088
15155
|
keypointsFile: portableOutputPath(spec.revision.keypointsFile, root),
|
|
15089
|
-
set: spec.revision.skeleton ?? null
|
|
15156
|
+
set: spec.revision.skeleton ?? null,
|
|
15157
|
+
sha256: spec.revision.keypointsSha256 ?? null
|
|
15090
15158
|
} : null,
|
|
15091
15159
|
character: describeCharacter(spec, entry),
|
|
15092
15160
|
outputs,
|
|
@@ -15378,13 +15446,48 @@ import { createServer as createServer2 } from "http";
|
|
|
15378
15446
|
import { readFile as readFile19 } from "fs/promises";
|
|
15379
15447
|
|
|
15380
15448
|
// src/gallery/page.ts
|
|
15381
|
-
import { readFileSync as readFileSync4 } from "fs";
|
|
15449
|
+
import { existsSync as existsSync21, readFileSync as readFileSync4 } from "fs";
|
|
15450
|
+
|
|
15451
|
+
// src/gallery/client-bundle.ts
|
|
15452
|
+
import { createRequire } from "module";
|
|
15453
|
+
import { fileURLToPath } from "url";
|
|
15454
|
+
function bundleGalleryClient(entry = fileURLToPath(new URL("./client/src/main.ts", import.meta.url))) {
|
|
15455
|
+
const esbuild = createRequire(import.meta.url)("esbuild");
|
|
15456
|
+
const result = esbuild.buildSync({
|
|
15457
|
+
entryPoints: [entry],
|
|
15458
|
+
bundle: true,
|
|
15459
|
+
format: "iife",
|
|
15460
|
+
target: "es2020",
|
|
15461
|
+
platform: "browser",
|
|
15462
|
+
write: false,
|
|
15463
|
+
legalComments: "none",
|
|
15464
|
+
charset: "utf8"
|
|
15465
|
+
});
|
|
15466
|
+
return result.outputFiles[0].text;
|
|
15467
|
+
}
|
|
15468
|
+
|
|
15469
|
+
// src/gallery/page.ts
|
|
15470
|
+
function galleryContentSecurityPolicy(nonce) {
|
|
15471
|
+
return [
|
|
15472
|
+
"default-src 'self'",
|
|
15473
|
+
`script-src 'nonce-${nonce}'`,
|
|
15474
|
+
`style-src 'nonce-${nonce}'`,
|
|
15475
|
+
"img-src 'self' data: blob:",
|
|
15476
|
+
"frame-src 'self'",
|
|
15477
|
+
"connect-src 'self'",
|
|
15478
|
+
"object-src 'none'",
|
|
15479
|
+
"base-uri 'none'",
|
|
15480
|
+
"form-action 'none'",
|
|
15481
|
+
"frame-ancestors 'none'"
|
|
15482
|
+
].join("; ");
|
|
15483
|
+
}
|
|
15382
15484
|
var CLIENT_DIR = new URL("./client/", import.meta.url);
|
|
15383
15485
|
var client;
|
|
15384
15486
|
function clientAssets() {
|
|
15487
|
+
const prebuilt = new URL("gallery.js", CLIENT_DIR);
|
|
15385
15488
|
client ??= {
|
|
15386
15489
|
css: readFileSync4(new URL("gallery.css", CLIENT_DIR), "utf8"),
|
|
15387
|
-
js:
|
|
15490
|
+
js: existsSync21(prebuilt) ? readFileSync4(prebuilt, "utf8") : bundleGalleryClient()
|
|
15388
15491
|
};
|
|
15389
15492
|
return client;
|
|
15390
15493
|
}
|
|
@@ -15396,6 +15499,7 @@ function renderGallery(snapshot, opts = {}) {
|
|
|
15396
15499
|
const editor = JSON.stringify(Boolean(opts.editor && opts.session));
|
|
15397
15500
|
const title = `${snapshot.project?.name ?? "workspace"} | pixelkiln`;
|
|
15398
15501
|
const { css, js } = clientAssets();
|
|
15502
|
+
const nonce = opts.nonce ? ` nonce="${escapeHtml2(opts.nonce)}"` : "";
|
|
15399
15503
|
return `<!doctype html>
|
|
15400
15504
|
<html lang="en">
|
|
15401
15505
|
<head>
|
|
@@ -15403,7 +15507,7 @@ function renderGallery(snapshot, opts = {}) {
|
|
|
15403
15507
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
15404
15508
|
<title>${escapeHtml2(title)}</title>
|
|
15405
15509
|
<link rel="icon" href="data:,">
|
|
15406
|
-
<style>
|
|
15510
|
+
<style${nonce}>
|
|
15407
15511
|
${css}</style>
|
|
15408
15512
|
</head>
|
|
15409
15513
|
<body>
|
|
@@ -15445,7 +15549,7 @@ ${css}</style>
|
|
|
15445
15549
|
<div id="drawer-host"></div>
|
|
15446
15550
|
<div id="dialog-host"></div>
|
|
15447
15551
|
<datalist id="view-options"><option value="low top-down"><option value="high top-down"><option value="side"><option value="sidescroller"></datalist>
|
|
15448
|
-
<script>
|
|
15552
|
+
<script${nonce}>
|
|
15449
15553
|
const INITIAL = ${data};
|
|
15450
15554
|
const SESSION = ${session};
|
|
15451
15555
|
const EDITABLE = ${editable};
|
|
@@ -15641,6 +15745,10 @@ async function serveGallery(opts) {
|
|
|
15641
15745
|
}
|
|
15642
15746
|
}
|
|
15643
15747
|
if (req.method !== "GET" && req.method !== "HEAD") return fail(405, "the gallery is read-only");
|
|
15748
|
+
if (url2.pathname === "/api/skeleton") {
|
|
15749
|
+
if (!opts.skeleton) return fail(404, "skeleton estimates need the gallery started with --edit");
|
|
15750
|
+
return json(200, opts.skeleton.status());
|
|
15751
|
+
}
|
|
15644
15752
|
if (url2.pathname === "/api/jobs") {
|
|
15645
15753
|
if (!opts.generate) return fail(404, "generation is off");
|
|
15646
15754
|
return json(200, opts.generate.status());
|
|
@@ -15674,12 +15782,15 @@ async function serveGallery(opts) {
|
|
|
15674
15782
|
try {
|
|
15675
15783
|
if (url2.pathname === "/") {
|
|
15676
15784
|
const { snapshot } = await load();
|
|
15785
|
+
const nonce = randomBytes(16).toString("base64");
|
|
15677
15786
|
res.writeHead(200, {
|
|
15678
15787
|
"Content-Type": "text/html; charset=utf-8",
|
|
15679
15788
|
"Cache-Control": "no-store",
|
|
15680
|
-
"X-Content-Type-Options": "nosniff"
|
|
15789
|
+
"X-Content-Type-Options": "nosniff",
|
|
15790
|
+
"Content-Security-Policy": galleryContentSecurityPolicy(nonce)
|
|
15681
15791
|
});
|
|
15682
15792
|
res.end(renderGallery(snapshot, {
|
|
15793
|
+
nonce,
|
|
15683
15794
|
...session ? { session } : {},
|
|
15684
15795
|
editable: Boolean(opts.edit),
|
|
15685
15796
|
generation: Boolean(opts.generate),
|
|
@@ -16158,12 +16269,12 @@ function ownedReferenceImageId(spec, lock, providerId) {
|
|
|
16158
16269
|
}
|
|
16159
16270
|
|
|
16160
16271
|
// src/pick/server.ts
|
|
16161
|
-
import { existsSync as
|
|
16272
|
+
import { existsSync as existsSync23 } from "fs";
|
|
16162
16273
|
import { readFile as readFile21 } from "fs/promises";
|
|
16163
16274
|
|
|
16164
16275
|
// src/pipeline/init.ts
|
|
16165
16276
|
import { readdir, readFile as readFile20, writeFile as writeFile13 } from "fs/promises";
|
|
16166
|
-
import { existsSync as
|
|
16277
|
+
import { existsSync as existsSync22 } from "fs";
|
|
16167
16278
|
import path30 from "path";
|
|
16168
16279
|
function pngSize(buf) {
|
|
16169
16280
|
if (buf.length < 24) return null;
|
|
@@ -16257,7 +16368,7 @@ function mode(values) {
|
|
|
16257
16368
|
return [...counts.entries()].sort((a, b) => b[1] - a[1])[0]?.[0] ?? 64;
|
|
16258
16369
|
}
|
|
16259
16370
|
async function writeManifestFile(target, manifest) {
|
|
16260
|
-
if (
|
|
16371
|
+
if (existsSync22(target)) {
|
|
16261
16372
|
throw new Error(`${target} already exists; refusing to overwrite an existing manifest.`);
|
|
16262
16373
|
}
|
|
16263
16374
|
await writeFile13(target, JSON.stringify(manifest, null, 2) + "\n");
|
|
@@ -16673,7 +16784,7 @@ async function currentArt(entry, spec) {
|
|
|
16673
16784
|
const previous = primaryOutput({ ...entry, outputs: entry.supersededOutputs });
|
|
16674
16785
|
if (!previous) return null;
|
|
16675
16786
|
const file = resolveOutputPath(previous.path, spec.root);
|
|
16676
|
-
if (!
|
|
16787
|
+
if (!existsSync23(file)) return null;
|
|
16677
16788
|
const contentType = previous.mediaType ?? mediaTypeFromExtension(file);
|
|
16678
16789
|
if (!contentType) return null;
|
|
16679
16790
|
let width = entry.width;
|
|
@@ -17181,7 +17292,7 @@ import path32 from "path";
|
|
|
17181
17292
|
|
|
17182
17293
|
// src/editor/install.ts
|
|
17183
17294
|
import { createHash as createHash4 } from "crypto";
|
|
17184
|
-
import { existsSync as
|
|
17295
|
+
import { existsSync as existsSync24 } from "fs";
|
|
17185
17296
|
import { mkdir as mkdir11, readFile as readFile22, rename as rename9, rm as rm10, stat as stat3, writeFile as writeFile14 } from "fs/promises";
|
|
17186
17297
|
import os2 from "os";
|
|
17187
17298
|
import path31 from "path";
|
|
@@ -17280,7 +17391,7 @@ async function editorStatus(opts = {}) {
|
|
|
17280
17391
|
for (const [name, expected] of Object.entries(pin.files)) {
|
|
17281
17392
|
totalBytes += expected.bytes;
|
|
17282
17393
|
const file = path31.join(dir, name);
|
|
17283
|
-
const info =
|
|
17394
|
+
const info = existsSync24(file) ? await stat3(file).catch(() => null) : null;
|
|
17284
17395
|
if (info && info.size === expected.bytes && await sha256Of(file) === expected.sha256) {
|
|
17285
17396
|
installedBytes += expected.bytes;
|
|
17286
17397
|
} else {
|
|
@@ -17534,7 +17645,7 @@ async function serveUntilStopped(initial, reload, args, access2) {
|
|
|
17534
17645
|
...args.edit ? { edit: createGalleryEditHandler({ manifestFor: access2.manifestFor, loadProject: access2.loadProject, reload, onProgress: log }) } : {},
|
|
17535
17646
|
...budget ? { generate: createGenerateHandlers({ loadProject, providerFor, budget, reload, onProgress: log }) } : {},
|
|
17536
17647
|
// Estimating acts on the author's behalf and feeds a manifest edit, so it follows the write gate.
|
|
17537
|
-
...args.edit ? { skeleton: createGallerySkeletonHandlers({ loadProject, onProgress: log }) } : {},
|
|
17648
|
+
...args.edit ? { skeleton: createGallerySkeletonHandlers({ loadProject, limit: args.estimateLimit, onProgress: log }) } : {},
|
|
17538
17649
|
// The editor exists to write hand edits back, so it follows the write gate.
|
|
17539
17650
|
...args.edit && !args.noEditor ? { editor: createGalleryEditorHandlers({ onProgress: log }) } : {}
|
|
17540
17651
|
});
|
|
@@ -17553,7 +17664,7 @@ async function serveUntilStopped(initial, reload, args, access2) {
|
|
|
17553
17664
|
async function runGallery(args) {
|
|
17554
17665
|
if (args.workspace) {
|
|
17555
17666
|
const workspacePath = path33.resolve(args.workspace);
|
|
17556
|
-
if (!
|
|
17667
|
+
if (!existsSync25(workspacePath)) {
|
|
17557
17668
|
throw new Error(`Workspace catalog not found: ${workspacePath}`);
|
|
17558
17669
|
}
|
|
17559
17670
|
const filter2 = { styles: args.styles, assets: args.assets };
|
|
@@ -17907,14 +18018,14 @@ async function runRestoreGeneration(args) {
|
|
|
17907
18018
|
}
|
|
17908
18019
|
|
|
17909
18020
|
// src/cli/commands/housekeeping.ts
|
|
17910
|
-
import { existsSync as
|
|
18021
|
+
import { existsSync as existsSync27 } from "fs";
|
|
17911
18022
|
|
|
17912
18023
|
// src/pipeline/cache-health.ts
|
|
17913
|
-
import { existsSync as
|
|
18024
|
+
import { existsSync as existsSync26 } from "fs";
|
|
17914
18025
|
import { readFile as readFile24, readdir as readdir2, rm as rm11 } from "fs/promises";
|
|
17915
18026
|
import path34 from "path";
|
|
17916
18027
|
async function inspectCaches(lock, lockPath, options = {}) {
|
|
17917
|
-
if (options.prune && !
|
|
18028
|
+
if (options.prune && !existsSync26(lockPath)) {
|
|
17918
18029
|
throw new Error(`Refusing to prune without an existing lockfile at ${path34.resolve(lockPath)}`);
|
|
17919
18030
|
}
|
|
17920
18031
|
const contentDir = path34.resolve(path34.dirname(lockPath), ".pixelkiln", "cache");
|
|
@@ -17957,7 +18068,7 @@ async function inspectCaches(lock, lockPath, options = {}) {
|
|
|
17957
18068
|
async function inspectContentCache(contentDir, referenced) {
|
|
17958
18069
|
const report = {
|
|
17959
18070
|
path: contentDir,
|
|
17960
|
-
exists:
|
|
18071
|
+
exists: existsSync26(contentDir),
|
|
17961
18072
|
files: 0,
|
|
17962
18073
|
bytes: 0,
|
|
17963
18074
|
valid: 0,
|
|
@@ -18021,7 +18132,7 @@ async function inspectContentCache(contentDir, referenced) {
|
|
|
18021
18132
|
async function inspectRemoteHashCache(remotePath) {
|
|
18022
18133
|
const report = {
|
|
18023
18134
|
path: remotePath,
|
|
18024
|
-
exists:
|
|
18135
|
+
exists: existsSync26(remotePath),
|
|
18025
18136
|
entries: 0,
|
|
18026
18137
|
valid: 0,
|
|
18027
18138
|
invalidIds: [],
|
|
@@ -18094,7 +18205,7 @@ async function runAccept(args) {
|
|
|
18094
18205
|
let intact = true;
|
|
18095
18206
|
for (const output of entry.outputs) {
|
|
18096
18207
|
const file = resolveOutputPath(output.path, item.spec.root);
|
|
18097
|
-
if (!
|
|
18208
|
+
if (!existsSync27(file) || await sha256File(file) !== output.sha256) {
|
|
18098
18209
|
intact = false;
|
|
18099
18210
|
break;
|
|
18100
18211
|
}
|
|
@@ -18150,11 +18261,11 @@ This drops their provenance from the lockfile. The art on disk is`);
|
|
|
18150
18261
|
|
|
18151
18262
|
// src/cli/commands/init.ts
|
|
18152
18263
|
import path35 from "path";
|
|
18153
|
-
import { existsSync as
|
|
18264
|
+
import { existsSync as existsSync28 } from "fs";
|
|
18154
18265
|
async function runInit(args) {
|
|
18155
18266
|
if (!args.from) throw new Error("init needs --from <dir> pointing at your existing PNGs.");
|
|
18156
18267
|
const root = path35.resolve(args.from);
|
|
18157
|
-
if (!
|
|
18268
|
+
if (!existsSync28(root)) throw new Error(`No directory at ${root}`);
|
|
18158
18269
|
const generator = args.generator ?? "map";
|
|
18159
18270
|
if (generator !== "1dir" && generator !== "map") {
|
|
18160
18271
|
throw new Error(`--generator must be "1dir" or "map", got "${args.generator}".`);
|
|
@@ -18182,7 +18293,7 @@ async function runInit(args) {
|
|
|
18182
18293
|
}
|
|
18183
18294
|
|
|
18184
18295
|
// src/pipeline/doctor.ts
|
|
18185
|
-
import { constants, existsSync as
|
|
18296
|
+
import { constants, existsSync as existsSync29 } from "fs";
|
|
18186
18297
|
import { access, stat as stat4 } from "fs/promises";
|
|
18187
18298
|
import path36 from "path";
|
|
18188
18299
|
async function doctor(loaded, specs, lock, lockPath, opts = {}) {
|
|
@@ -18254,7 +18365,7 @@ async function doctor(loaded, specs, lock, lockPath, opts = {}) {
|
|
|
18254
18365
|
cacheDir,
|
|
18255
18366
|
cacheFileName(output.sha256, output.mediaType ?? MediaType.PNG)
|
|
18256
18367
|
);
|
|
18257
|
-
if (
|
|
18368
|
+
if (existsSync29(cached) && await sha256File(cached) === output.sha256) {
|
|
18258
18369
|
hasCache = true;
|
|
18259
18370
|
break;
|
|
18260
18371
|
}
|
|
@@ -18358,7 +18469,7 @@ async function nearestExistingDirectory(start) {
|
|
|
18358
18469
|
|
|
18359
18470
|
// src/pipeline/audit.ts
|
|
18360
18471
|
import { readFile as readFile25 } from "fs/promises";
|
|
18361
|
-
import { existsSync as
|
|
18472
|
+
import { existsSync as existsSync30 } from "fs";
|
|
18362
18473
|
import path37 from "path";
|
|
18363
18474
|
function paletteDistance(asset, reference) {
|
|
18364
18475
|
if (!asset.length || !reference.length) return 0;
|
|
@@ -18395,7 +18506,7 @@ async function auditStyle(loaded, specs, styleId, lock) {
|
|
|
18395
18506
|
const unreadable = [];
|
|
18396
18507
|
for (const spec of mine) {
|
|
18397
18508
|
for (const output of resolveSpecOutputs(spec, lock, loaded.root)) {
|
|
18398
|
-
if (!
|
|
18509
|
+
if (!existsSync30(output.absolutePath)) {
|
|
18399
18510
|
missing.push(output.id);
|
|
18400
18511
|
continue;
|
|
18401
18512
|
}
|
|
@@ -18425,7 +18536,7 @@ async function auditStyle(loaded, specs, styleId, lock) {
|
|
|
18425
18536
|
const refPalettes = [];
|
|
18426
18537
|
for (const img of style.styleImages) {
|
|
18427
18538
|
const abs = path37.resolve(loaded.root, img.path);
|
|
18428
|
-
if (!
|
|
18539
|
+
if (!existsSync30(abs)) continue;
|
|
18429
18540
|
try {
|
|
18430
18541
|
refPalettes.push(extractPalette(decodePng(await readFile25(abs)), 12));
|
|
18431
18542
|
} catch {
|
|
@@ -19597,7 +19708,7 @@ function sheetFormat(args) {
|
|
|
19597
19708
|
import path41 from "path";
|
|
19598
19709
|
|
|
19599
19710
|
// src/pixellab-utils.ts
|
|
19600
|
-
import { existsSync as
|
|
19711
|
+
import { existsSync as existsSync31 } from "fs";
|
|
19601
19712
|
import { mkdir as mkdir12, readFile as readFile27, writeFile as writeFile15 } from "fs/promises";
|
|
19602
19713
|
import path40 from "path";
|
|
19603
19714
|
var UNZOOM_MIN_SIDE = 256;
|
|
@@ -19609,7 +19720,7 @@ function defaultUnzoomOut(input) {
|
|
|
19609
19720
|
async function unzoomFile(client2, input, opts = {}) {
|
|
19610
19721
|
const out = path40.resolve(opts.out ?? defaultUnzoomOut(input));
|
|
19611
19722
|
if (path40.resolve(input) === out) throw new Error("unzoom will not overwrite its own input; pass a different --out");
|
|
19612
|
-
if (
|
|
19723
|
+
if (existsSync31(out) && !opts.force) throw new Error(`${out} already exists; pass --force to replace it`);
|
|
19613
19724
|
const bytes = await readFile27(input);
|
|
19614
19725
|
const metadata = imageMetadata(bytes);
|
|
19615
19726
|
if (!metadata) throw new Error(`${input} is not a readable PNG or JPEG`);
|
|
@@ -19638,7 +19749,7 @@ async function generateFont(client2, opts) {
|
|
|
19638
19749
|
const { ttf, atlas } = fontOutputPaths(opts.out);
|
|
19639
19750
|
if (!opts.force) {
|
|
19640
19751
|
for (const file of [ttf, atlas]) {
|
|
19641
|
-
if (
|
|
19752
|
+
if (existsSync31(file)) throw new Error(`${file} already exists; pass --force to replace it`);
|
|
19642
19753
|
}
|
|
19643
19754
|
}
|
|
19644
19755
|
const sleep3 = opts.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
@@ -19752,7 +19863,7 @@ import { readFile as readFile29 } from "fs/promises";
|
|
|
19752
19863
|
|
|
19753
19864
|
// src/pipeline/quality-regression.ts
|
|
19754
19865
|
import path42 from "path";
|
|
19755
|
-
import { existsSync as
|
|
19866
|
+
import { existsSync as existsSync32 } from "fs";
|
|
19756
19867
|
import { readFile as readFile28 } from "fs/promises";
|
|
19757
19868
|
import { z as z11 } from "zod";
|
|
19758
19869
|
var SHA256_RE = /^[0-9a-f]{64}$/;
|
|
@@ -19995,7 +20106,7 @@ async function snapshotQualityBaseline(inputs, baselinePath, options = {}) {
|
|
|
19995
20106
|
cases
|
|
19996
20107
|
});
|
|
19997
20108
|
const data = Buffer.from(JSON.stringify(baseline, null, 2) + "\n");
|
|
19998
|
-
if (
|
|
20109
|
+
if (existsSync32(absolute) && !options.force) {
|
|
19999
20110
|
const current = await readFile28(absolute);
|
|
20000
20111
|
if (!current.equals(data)) {
|
|
20001
20112
|
throw new OverwriteRefusedError(
|
|
@@ -20192,9 +20303,9 @@ import path45 from "path";
|
|
|
20192
20303
|
|
|
20193
20304
|
// src/recipes.ts
|
|
20194
20305
|
import path44 from "path";
|
|
20195
|
-
import { existsSync as
|
|
20306
|
+
import { existsSync as existsSync33 } from "fs";
|
|
20196
20307
|
import { readdir as readdir3, readFile as readFile30 } from "fs/promises";
|
|
20197
|
-
import { fileURLToPath } from "url";
|
|
20308
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
20198
20309
|
import { z as z12 } from "zod";
|
|
20199
20310
|
var SHA256_RE2 = /^[0-9a-f]{64}$/;
|
|
20200
20311
|
var RECIPE_ID_RE = /^[a-z0-9][a-z0-9-]*\/[a-z0-9][a-z0-9-]*$/;
|
|
@@ -20395,7 +20506,7 @@ async function readRecipeFile(recipePath, bundled) {
|
|
|
20395
20506
|
return { recipe: parsed.data, authored: raw, path: absolute, dir: path44.dirname(absolute), bundled };
|
|
20396
20507
|
}
|
|
20397
20508
|
async function walkRecipeFiles(root) {
|
|
20398
|
-
if (!
|
|
20509
|
+
if (!existsSync33(root)) return [];
|
|
20399
20510
|
const out = [];
|
|
20400
20511
|
for (const entry of await readdir3(root, { withFileTypes: true })) {
|
|
20401
20512
|
const resolved = path44.join(root, entry.name);
|
|
@@ -20405,7 +20516,7 @@ async function walkRecipeFiles(root) {
|
|
|
20405
20516
|
return out;
|
|
20406
20517
|
}
|
|
20407
20518
|
function bundledRecipeRoot() {
|
|
20408
|
-
return
|
|
20519
|
+
return fileURLToPath2(new URL("../recipes/", import.meta.url));
|
|
20409
20520
|
}
|
|
20410
20521
|
async function listBundledRecipes(root = bundledRecipeRoot()) {
|
|
20411
20522
|
const recipes = await Promise.all(
|
|
@@ -20433,7 +20544,7 @@ function parseSelector(selector) {
|
|
|
20433
20544
|
}
|
|
20434
20545
|
async function resolveRecipe(target, bundledRoot = bundledRecipeRoot()) {
|
|
20435
20546
|
const local = path44.resolve(target);
|
|
20436
|
-
if (
|
|
20547
|
+
if (existsSync33(local)) {
|
|
20437
20548
|
const recipePath = path44.basename(local) === RECIPE_FILE ? local : path44.join(local, RECIPE_FILE);
|
|
20438
20549
|
return readRecipeFile(recipePath, false);
|
|
20439
20550
|
}
|
|
@@ -20449,7 +20560,7 @@ async function resolveRecipe(target, bundledRoot = bundledRecipeRoot()) {
|
|
|
20449
20560
|
}
|
|
20450
20561
|
async function verifyFile(root, file) {
|
|
20451
20562
|
const absolute = path44.resolve(root, ...file.path.split("/"));
|
|
20452
|
-
if (!
|
|
20563
|
+
if (!existsSync33(absolute)) {
|
|
20453
20564
|
return { path: file.path, expectedSha256: file.sha256, actualSha256: null, status: "missing" };
|
|
20454
20565
|
}
|
|
20455
20566
|
const actualSha256 = await sha256File(absolute);
|
|
@@ -20527,7 +20638,7 @@ async function installRecipe(target, options = {}) {
|
|
|
20527
20638
|
})));
|
|
20528
20639
|
if (!options.force) {
|
|
20529
20640
|
for (const file of files) {
|
|
20530
|
-
if (!
|
|
20641
|
+
if (!existsSync33(file.path)) continue;
|
|
20531
20642
|
const current = await readFile30(file.path);
|
|
20532
20643
|
if (!current.equals(file.data)) {
|
|
20533
20644
|
throw new OverwriteRefusedError(
|
|
@@ -20767,7 +20878,7 @@ async function runRefine(args) {
|
|
|
20767
20878
|
|
|
20768
20879
|
// src/cli/commands/skeleton.ts
|
|
20769
20880
|
import path47 from "path";
|
|
20770
|
-
import { existsSync as
|
|
20881
|
+
import { existsSync as existsSync34 } from "fs";
|
|
20771
20882
|
import { readFile as readFile31, writeFile as writeFile16 } from "fs/promises";
|
|
20772
20883
|
|
|
20773
20884
|
// src/skeleton-preview.ts
|
|
@@ -20879,7 +20990,7 @@ var Canvas = class {
|
|
|
20879
20990
|
async function runEstimateSkeleton(args) {
|
|
20880
20991
|
const image = args.target;
|
|
20881
20992
|
if (!image) throw new UsageError("estimate-skeleton needs an image path: pixelkiln estimate-skeleton <image> [--out keypoints.json]");
|
|
20882
|
-
if (args.out &&
|
|
20993
|
+
if (args.out && existsSync34(path47.resolve(args.out)) && !args.force) {
|
|
20883
20994
|
throw new UsageError(`${args.out} already exists; pass --force to replace it`);
|
|
20884
20995
|
}
|
|
20885
20996
|
loadEnvFiles(path47.dirname(path47.resolve(args.manifest)));
|
|
@@ -20925,7 +21036,7 @@ async function runSkeletonPreview(args) {
|
|
|
20925
21036
|
sourceFile = args.from ? path47.resolve(args.from) : spec.revision.sourceFile;
|
|
20926
21037
|
label = `${spec.styleId}/${spec.assetId}`;
|
|
20927
21038
|
}
|
|
20928
|
-
if (!
|
|
21039
|
+
if (!existsSync34(keypointsFile)) throw new UsageError(`Keypoints file does not exist yet: ${keypointsFile}`);
|
|
20929
21040
|
let json;
|
|
20930
21041
|
try {
|
|
20931
21042
|
json = JSON.parse(await readFile31(keypointsFile, "utf8"));
|
|
@@ -20935,7 +21046,7 @@ async function runSkeletonPreview(args) {
|
|
|
20935
21046
|
const set = parseSkeletonSet(json, keypointsFile);
|
|
20936
21047
|
let source;
|
|
20937
21048
|
if (sourceFile) {
|
|
20938
|
-
if (!
|
|
21049
|
+
if (!existsSync34(sourceFile)) {
|
|
20939
21050
|
log(` source image does not exist yet, drawing on a plain ground: ${sourceFile}`);
|
|
20940
21051
|
} else {
|
|
20941
21052
|
const bytes = await readFile31(sourceFile);
|
|
@@ -20993,7 +21104,7 @@ async function runTools(args) {
|
|
|
20993
21104
|
|
|
20994
21105
|
// src/cli/commands/workspace.ts
|
|
20995
21106
|
import path48 from "path";
|
|
20996
|
-
import { existsSync as
|
|
21107
|
+
import { existsSync as existsSync35 } from "fs";
|
|
20997
21108
|
async function runWorkspace(args) {
|
|
20998
21109
|
const workspacePath = path48.resolve(args.workspace ?? "pixelkiln.workspace.json");
|
|
20999
21110
|
const dir = path48.dirname(workspacePath);
|
|
@@ -21023,7 +21134,7 @@ async function runWorkspace(args) {
|
|
|
21023
21134
|
log(` registered "${id}" in ${path48.relative(process.cwd(), workspacePath)}`);
|
|
21024
21135
|
log(` manifest: ${project.manifest}`);
|
|
21025
21136
|
log(` lock: ${project.lock}`);
|
|
21026
|
-
if (!
|
|
21137
|
+
if (!existsSync35(lockPath)) {
|
|
21027
21138
|
log(
|
|
21028
21139
|
` warning: no lockfile there yet; this project contributes no claims until one is generated`
|
|
21029
21140
|
);
|
|
@@ -21047,7 +21158,7 @@ async function runWorkspace(args) {
|
|
|
21047
21158
|
return;
|
|
21048
21159
|
}
|
|
21049
21160
|
if (args.subcommand === "list") {
|
|
21050
|
-
if (!
|
|
21161
|
+
if (!existsSync35(workspacePath)) {
|
|
21051
21162
|
throw new Error(`Workspace catalog not found: ${workspacePath}`);
|
|
21052
21163
|
}
|
|
21053
21164
|
const ws = await loadWorkspace(workspacePath);
|
|
@@ -21067,7 +21178,7 @@ async function runWorkspace(args) {
|
|
|
21067
21178
|
return;
|
|
21068
21179
|
}
|
|
21069
21180
|
if (args.subcommand === "status") {
|
|
21070
|
-
if (!
|
|
21181
|
+
if (!existsSync35(workspacePath)) {
|
|
21071
21182
|
throw new Error(`Workspace catalog not found: ${workspacePath}`);
|
|
21072
21183
|
}
|
|
21073
21184
|
const ws = await loadWorkspace(workspacePath);
|
|
@@ -21129,7 +21240,7 @@ async function runHelp() {
|
|
|
21129
21240
|
async function runVersion() {
|
|
21130
21241
|
for (const up of ["../package.json", "../../package.json"]) {
|
|
21131
21242
|
const url = new URL(up, import.meta.url);
|
|
21132
|
-
if (!
|
|
21243
|
+
if (!existsSync36(url)) continue;
|
|
21133
21244
|
const pkg = JSON.parse(await readFile32(url, "utf8"));
|
|
21134
21245
|
if (pkg.name === "pixelkiln") {
|
|
21135
21246
|
log(`${pkg.name} ${pkg.version}`);
|