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/index.cjs
CHANGED
|
@@ -164,6 +164,7 @@ __export(index_exports, {
|
|
|
164
164
|
fontOutputPaths: () => fontOutputPaths,
|
|
165
165
|
formatCost: () => formatCost,
|
|
166
166
|
frameSetFps: () => frameSetFps,
|
|
167
|
+
galleryContentSecurityPolicy: () => galleryContentSecurityPolicy,
|
|
167
168
|
galleryMediaId: () => galleryMediaId,
|
|
168
169
|
galleryMediaRoute: () => galleryMediaRoute,
|
|
169
170
|
generateFont: () => generateFont,
|
|
@@ -15946,6 +15947,15 @@ var SkeletonAnimationRequestSchema = import_zod10.z.object({
|
|
|
15946
15947
|
/** Replace an existing `keypointsFile` with `set`. */
|
|
15947
15948
|
overwrite: import_zod10.z.boolean().optional()
|
|
15948
15949
|
}).strict();
|
|
15950
|
+
var SkeletonKeypointsUpdateSchema = import_zod10.z.object({
|
|
15951
|
+
action: import_zod10.z.literal("update-skeleton-keypoints"),
|
|
15952
|
+
project: import_zod10.z.string().min(1).optional(),
|
|
15953
|
+
styleId: import_zod10.z.string().min(1),
|
|
15954
|
+
assetId: import_zod10.z.string().min(1),
|
|
15955
|
+
/** The file's SHA-256 when the page loaded it; anything else on disk is a concurrent edit. */
|
|
15956
|
+
expectedSha256: import_zod10.z.string().regex(/^[0-9a-f]{64}$/),
|
|
15957
|
+
set: SkeletonSetSchema
|
|
15958
|
+
}).strict();
|
|
15949
15959
|
async function createSkeletonAnimation(manifestPath, request) {
|
|
15950
15960
|
const root = import_node_path29.default.dirname(import_node_path29.default.resolve(manifestPath));
|
|
15951
15961
|
const file = import_node_path29.default.resolve(root, request.keypointsFile);
|
|
@@ -15991,6 +16001,26 @@ async function createSkeletonAnimation(manifestPath, request) {
|
|
|
15991
16001
|
throw error;
|
|
15992
16002
|
}
|
|
15993
16003
|
}
|
|
16004
|
+
async function updateSkeletonKeypoints(ctx, request) {
|
|
16005
|
+
const key = lockKey(request.styleId, request.assetId);
|
|
16006
|
+
const spec = ctx.specs.find((candidate) => lockKey(candidate.styleId, candidate.assetId) === key);
|
|
16007
|
+
if (!spec) throw new ManifestEditError(`"${key}" is not declared by the manifest`);
|
|
16008
|
+
const file = spec.revision?.mode === "animate-skeleton" ? spec.revision.keypointsFile : void 0;
|
|
16009
|
+
if (!file) throw new ManifestEditError(`${key} is not an animate-skeleton revision`);
|
|
16010
|
+
const relative = import_node_path29.default.relative(ctx.loaded.root, file);
|
|
16011
|
+
if (relative.startsWith("..") || import_node_path29.default.isAbsolute(relative)) {
|
|
16012
|
+
throw new ManifestEditError(`${key}'s keypoints file is outside the project; edit it by hand`);
|
|
16013
|
+
}
|
|
16014
|
+
const current = (0, import_node_fs25.existsSync)(file) ? sha256(await (0, import_promises22.readFile)(file)) : null;
|
|
16015
|
+
if (current !== request.expectedSha256) {
|
|
16016
|
+
throw Object.assign(
|
|
16017
|
+
new Error(`${relative.split(import_node_path29.default.sep).join("/")} changed on disk since this page loaded it; press Refresh and edit again`),
|
|
16018
|
+
{ status: 409 }
|
|
16019
|
+
);
|
|
16020
|
+
}
|
|
16021
|
+
await (0, import_promises22.writeFile)(file, JSON.stringify(request.set, null, 2) + "\n");
|
|
16022
|
+
return relative.split(import_node_path29.default.sep).join("/");
|
|
16023
|
+
}
|
|
15994
16024
|
|
|
15995
16025
|
// src/gallery/edit.ts
|
|
15996
16026
|
var Base64 = import_zod11.z.string().regex(/^[A-Za-z0-9+/]*={0,2}$/).max(Math.ceil(MAX_HAND_EDIT_BYTES / 3) * 4);
|
|
@@ -16067,6 +16097,18 @@ function createGalleryEditHandler(opts) {
|
|
|
16067
16097
|
}
|
|
16068
16098
|
return opts.reload();
|
|
16069
16099
|
}
|
|
16100
|
+
const poses = SkeletonKeypointsUpdateSchema.safeParse(body);
|
|
16101
|
+
if (poses.success) {
|
|
16102
|
+
if (!opts.loadProject) throw new ManifestEditError("pose edits are not available in this gallery");
|
|
16103
|
+
const written = await updateSkeletonKeypoints(await opts.loadProject(poses.data.project), poses.data);
|
|
16104
|
+
log(` poses saved: ${written} (${poses.data.styleId}/${poses.data.assetId} is stale until generated again)`);
|
|
16105
|
+
return opts.reload();
|
|
16106
|
+
}
|
|
16107
|
+
if (body?.action === "update-skeleton-keypoints") {
|
|
16108
|
+
throw new ManifestEditError(
|
|
16109
|
+
"invalid pose edit: " + poses.error.issues.slice(0, 3).map((issue) => `${issue.path.join(".")}: ${issue.message}`).join("; ")
|
|
16110
|
+
);
|
|
16111
|
+
}
|
|
16070
16112
|
const skeleton = SkeletonAnimationRequestSchema.safeParse(body);
|
|
16071
16113
|
if (skeleton.success) {
|
|
16072
16114
|
let manifestPath2;
|
|
@@ -16440,7 +16482,8 @@ async function buildGallerySnapshot(opts) {
|
|
|
16440
16482
|
mirrorOfKey: spec.mirror ? lockKey(spec.styleId, spec.mirror.sourceAssetId) : entry?.mirror ? lockKey(spec.styleId, entry.mirror.sourceAssetId) : null,
|
|
16441
16483
|
skeleton: spec.revision?.mode === "animate-skeleton" && spec.revision.keypointsFile ? {
|
|
16442
16484
|
keypointsFile: portableOutputPath(spec.revision.keypointsFile, root),
|
|
16443
|
-
set: spec.revision.skeleton ?? null
|
|
16485
|
+
set: spec.revision.skeleton ?? null,
|
|
16486
|
+
sha256: spec.revision.keypointsSha256 ?? null
|
|
16444
16487
|
} : null,
|
|
16445
16488
|
character: describeCharacter(spec, entry),
|
|
16446
16489
|
outputs,
|
|
@@ -16728,12 +16771,47 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
16728
16771
|
|
|
16729
16772
|
// src/gallery/page.ts
|
|
16730
16773
|
var import_node_fs27 = require("fs");
|
|
16774
|
+
|
|
16775
|
+
// src/gallery/client-bundle.ts
|
|
16776
|
+
var import_node_module = require("module");
|
|
16777
|
+
var import_node_url2 = require("url");
|
|
16778
|
+
function bundleGalleryClient(entry = (0, import_node_url2.fileURLToPath)(new URL("./client/src/main.ts", importMetaUrl))) {
|
|
16779
|
+
const esbuild = (0, import_node_module.createRequire)(importMetaUrl)("esbuild");
|
|
16780
|
+
const result = esbuild.buildSync({
|
|
16781
|
+
entryPoints: [entry],
|
|
16782
|
+
bundle: true,
|
|
16783
|
+
format: "iife",
|
|
16784
|
+
target: "es2020",
|
|
16785
|
+
platform: "browser",
|
|
16786
|
+
write: false,
|
|
16787
|
+
legalComments: "none",
|
|
16788
|
+
charset: "utf8"
|
|
16789
|
+
});
|
|
16790
|
+
return result.outputFiles[0].text;
|
|
16791
|
+
}
|
|
16792
|
+
|
|
16793
|
+
// src/gallery/page.ts
|
|
16794
|
+
function galleryContentSecurityPolicy(nonce) {
|
|
16795
|
+
return [
|
|
16796
|
+
"default-src 'self'",
|
|
16797
|
+
`script-src 'nonce-${nonce}'`,
|
|
16798
|
+
`style-src 'nonce-${nonce}'`,
|
|
16799
|
+
"img-src 'self' data: blob:",
|
|
16800
|
+
"frame-src 'self'",
|
|
16801
|
+
"connect-src 'self'",
|
|
16802
|
+
"object-src 'none'",
|
|
16803
|
+
"base-uri 'none'",
|
|
16804
|
+
"form-action 'none'",
|
|
16805
|
+
"frame-ancestors 'none'"
|
|
16806
|
+
].join("; ");
|
|
16807
|
+
}
|
|
16731
16808
|
var CLIENT_DIR = new URL("./client/", importMetaUrl);
|
|
16732
16809
|
var client;
|
|
16733
16810
|
function clientAssets() {
|
|
16811
|
+
const prebuilt = new URL("gallery.js", CLIENT_DIR);
|
|
16734
16812
|
client ??= {
|
|
16735
16813
|
css: (0, import_node_fs27.readFileSync)(new URL("gallery.css", CLIENT_DIR), "utf8"),
|
|
16736
|
-
js: (0, import_node_fs27.
|
|
16814
|
+
js: (0, import_node_fs27.existsSync)(prebuilt) ? (0, import_node_fs27.readFileSync)(prebuilt, "utf8") : bundleGalleryClient()
|
|
16737
16815
|
};
|
|
16738
16816
|
return client;
|
|
16739
16817
|
}
|
|
@@ -16745,6 +16823,7 @@ function renderGallery(snapshot, opts = {}) {
|
|
|
16745
16823
|
const editor = JSON.stringify(Boolean(opts.editor && opts.session));
|
|
16746
16824
|
const title = `${snapshot.project?.name ?? "workspace"} | pixelkiln`;
|
|
16747
16825
|
const { css, js } = clientAssets();
|
|
16826
|
+
const nonce = opts.nonce ? ` nonce="${escapeHtml2(opts.nonce)}"` : "";
|
|
16748
16827
|
return `<!doctype html>
|
|
16749
16828
|
<html lang="en">
|
|
16750
16829
|
<head>
|
|
@@ -16752,7 +16831,7 @@ function renderGallery(snapshot, opts = {}) {
|
|
|
16752
16831
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
16753
16832
|
<title>${escapeHtml2(title)}</title>
|
|
16754
16833
|
<link rel="icon" href="data:,">
|
|
16755
|
-
<style>
|
|
16834
|
+
<style${nonce}>
|
|
16756
16835
|
${css}</style>
|
|
16757
16836
|
</head>
|
|
16758
16837
|
<body>
|
|
@@ -16794,7 +16873,7 @@ ${css}</style>
|
|
|
16794
16873
|
<div id="drawer-host"></div>
|
|
16795
16874
|
<div id="dialog-host"></div>
|
|
16796
16875
|
<datalist id="view-options"><option value="low top-down"><option value="high top-down"><option value="side"><option value="sidescroller"></datalist>
|
|
16797
|
-
<script>
|
|
16876
|
+
<script${nonce}>
|
|
16798
16877
|
const INITIAL = ${data};
|
|
16799
16878
|
const SESSION = ${session};
|
|
16800
16879
|
const EDITABLE = ${editable};
|
|
@@ -17287,6 +17366,10 @@ async function serveGallery(opts) {
|
|
|
17287
17366
|
}
|
|
17288
17367
|
}
|
|
17289
17368
|
if (req.method !== "GET" && req.method !== "HEAD") return fail(405, "the gallery is read-only");
|
|
17369
|
+
if (url2.pathname === "/api/skeleton") {
|
|
17370
|
+
if (!opts.skeleton) return fail(404, "skeleton estimates need the gallery started with --edit");
|
|
17371
|
+
return json(200, opts.skeleton.status());
|
|
17372
|
+
}
|
|
17290
17373
|
if (url2.pathname === "/api/jobs") {
|
|
17291
17374
|
if (!opts.generate) return fail(404, "generation is off");
|
|
17292
17375
|
return json(200, opts.generate.status());
|
|
@@ -17320,12 +17403,15 @@ async function serveGallery(opts) {
|
|
|
17320
17403
|
try {
|
|
17321
17404
|
if (url2.pathname === "/") {
|
|
17322
17405
|
const { snapshot } = await load();
|
|
17406
|
+
const nonce = (0, import_node_crypto8.randomBytes)(16).toString("base64");
|
|
17323
17407
|
res.writeHead(200, {
|
|
17324
17408
|
"Content-Type": "text/html; charset=utf-8",
|
|
17325
17409
|
"Cache-Control": "no-store",
|
|
17326
|
-
"X-Content-Type-Options": "nosniff"
|
|
17410
|
+
"X-Content-Type-Options": "nosniff",
|
|
17411
|
+
"Content-Security-Policy": galleryContentSecurityPolicy(nonce)
|
|
17327
17412
|
});
|
|
17328
17413
|
res.end(renderGallery(snapshot, {
|
|
17414
|
+
nonce,
|
|
17329
17415
|
...session ? { session } : {},
|
|
17330
17416
|
editable: Boolean(opts.edit),
|
|
17331
17417
|
generation: Boolean(opts.generate),
|
|
@@ -18499,6 +18585,7 @@ async function workspaceStatus(ws, dir) {
|
|
|
18499
18585
|
fontOutputPaths,
|
|
18500
18586
|
formatCost,
|
|
18501
18587
|
frameSetFps,
|
|
18588
|
+
galleryContentSecurityPolicy,
|
|
18502
18589
|
galleryMediaId,
|
|
18503
18590
|
galleryMediaRoute,
|
|
18504
18591
|
generateFont,
|