makeables 0.9.0 → 0.10.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/README.md +12 -0
- package/dist/{chunk-RFIQNWNF.js → chunk-G6OTYITI.js} +1119 -495
- package/dist/chunk-SZTWKHNA.js +95 -0
- package/dist/chunk-TRIRNJSG.js +389 -0
- package/dist/cli.js +558 -354
- package/dist/kit-YXOYCN6F.js +17 -0
- package/dist/{mcp-HS2XZXSO.js → mcp-VS5XYXVM.js} +5 -3
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,4 +1,24 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
addKitMember,
|
|
4
|
+
addToSheet,
|
|
5
|
+
checkSheet,
|
|
6
|
+
exportKit,
|
|
7
|
+
exportSheetPdf,
|
|
8
|
+
importKit,
|
|
9
|
+
kitCover,
|
|
10
|
+
newSheet,
|
|
11
|
+
packStickerSheet,
|
|
12
|
+
readKit,
|
|
13
|
+
readSheet,
|
|
14
|
+
sheetPreviewPng,
|
|
15
|
+
sheetReport
|
|
16
|
+
} from "./chunk-TRIRNJSG.js";
|
|
17
|
+
import {
|
|
18
|
+
getSkill,
|
|
19
|
+
skills,
|
|
20
|
+
version
|
|
21
|
+
} from "./chunk-SZTWKHNA.js";
|
|
2
22
|
import {
|
|
3
23
|
CloudError,
|
|
4
24
|
InputError,
|
|
@@ -9,61 +29,53 @@ import {
|
|
|
9
29
|
cardStyles,
|
|
10
30
|
checkSubmittedSvg,
|
|
11
31
|
connectAccount,
|
|
12
|
-
|
|
32
|
+
createStickerDesign,
|
|
13
33
|
defaultStickerMaterial,
|
|
14
34
|
designCatalog,
|
|
15
35
|
designProfile,
|
|
16
36
|
draftFromPackage,
|
|
17
37
|
findCardStyle,
|
|
18
38
|
findDesign,
|
|
19
|
-
getSkill,
|
|
20
39
|
hashBytes,
|
|
21
40
|
jsonBytes,
|
|
41
|
+
kitDocumentSchema,
|
|
22
42
|
listCardDesigns,
|
|
23
43
|
logout,
|
|
24
44
|
newBadge,
|
|
25
45
|
newCard,
|
|
26
46
|
openBrowser,
|
|
27
|
-
packSheet,
|
|
28
47
|
packageJson,
|
|
29
|
-
placeNear,
|
|
30
|
-
prepareSheetPrint,
|
|
31
48
|
readBadge,
|
|
32
49
|
readPackageJson,
|
|
33
50
|
readPackageZip,
|
|
34
51
|
readSubmission,
|
|
35
52
|
removeBadgeLayer,
|
|
36
53
|
renderBadge,
|
|
54
|
+
renderDraftFace,
|
|
37
55
|
setBadgeLayer,
|
|
38
|
-
sheetPrintPdf,
|
|
39
|
-
sheetSizes,
|
|
40
|
-
sheetSpec,
|
|
41
|
-
sheetTypeIds,
|
|
42
|
-
sheetTypes,
|
|
43
56
|
side,
|
|
44
|
-
skills,
|
|
45
57
|
startStudio,
|
|
46
58
|
stickerCut,
|
|
47
59
|
stickerCutSvg,
|
|
48
60
|
stickerMaterialSchema,
|
|
49
|
-
stickerSheetSchema,
|
|
50
61
|
stickerStyles,
|
|
51
62
|
studioRequest,
|
|
52
63
|
submitDesign,
|
|
53
64
|
summarizeBadgeLayers,
|
|
54
65
|
svgNodeSchema,
|
|
55
66
|
svgNodeXml,
|
|
56
|
-
version,
|
|
57
67
|
writeAtomic
|
|
58
|
-
} from "./chunk-
|
|
68
|
+
} from "./chunk-G6OTYITI.js";
|
|
59
69
|
|
|
60
70
|
// src/cli.ts
|
|
61
71
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
62
|
-
import {
|
|
63
|
-
import {
|
|
64
|
-
import {
|
|
72
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
73
|
+
import { constants as constants4 } from "fs";
|
|
74
|
+
import { access, lstat as lstat4, open as open4, readFile as readFile7 } from "fs/promises";
|
|
75
|
+
import { dirname as dirname5, extname as extname4, resolve as resolve7 } from "path";
|
|
65
76
|
import { parseArgs } from "util";
|
|
66
77
|
import sharp8 from "sharp";
|
|
78
|
+
import { ZodError } from "zod";
|
|
67
79
|
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
68
80
|
|
|
69
81
|
// src/card-import.ts
|
|
@@ -274,6 +286,199 @@ async function extractImage(file, out) {
|
|
|
274
286
|
return { path, bytes: bytes.length };
|
|
275
287
|
}
|
|
276
288
|
|
|
289
|
+
// src/kit-extract.ts
|
|
290
|
+
import { mkdir, mkdtemp, rename, rm, writeFile as writeFile2 } from "fs/promises";
|
|
291
|
+
import { dirname, join, resolve as resolve2 } from "path";
|
|
292
|
+
import sharp2 from "sharp";
|
|
293
|
+
import { z } from "zod";
|
|
294
|
+
var regionSchema = z.object({
|
|
295
|
+
x: z.number().int().nonnegative(),
|
|
296
|
+
y: z.number().int().nonnegative(),
|
|
297
|
+
w: z.number().int().positive(),
|
|
298
|
+
h: z.number().int().positive(),
|
|
299
|
+
name: z.string().trim().min(1).max(60).optional()
|
|
300
|
+
}).strict();
|
|
301
|
+
var optionsSchema = z.object({
|
|
302
|
+
name: z.string().trim().min(1).max(60).default("Artwork kit"),
|
|
303
|
+
gap: z.number().int().min(0).max(40).default(12),
|
|
304
|
+
tolerance: z.number().int().min(0).max(150).default(40),
|
|
305
|
+
minArea: z.number().int().min(1).max(1e5).default(64),
|
|
306
|
+
regions: z.array(regionSchema).min(1).max(24).optional()
|
|
307
|
+
}).strict();
|
|
308
|
+
function dilate(mask, w, h, radius) {
|
|
309
|
+
const horizontal = new Uint8Array(mask.length), out = new Uint8Array(mask.length);
|
|
310
|
+
for (let y = 0; y < h; y++) {
|
|
311
|
+
let count = 0;
|
|
312
|
+
for (let x = -radius; x < w; x++) {
|
|
313
|
+
if (x + radius < w) count += mask[y * w + x + radius];
|
|
314
|
+
if (x - radius - 1 >= 0) count -= mask[y * w + x - radius - 1];
|
|
315
|
+
if (x >= 0) horizontal[y * w + x] = count > 0 ? 1 : 0;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
for (let x = 0; x < w; x++) {
|
|
319
|
+
let count = 0;
|
|
320
|
+
for (let y = -radius; y < h; y++) {
|
|
321
|
+
if (y + radius < h) count += horizontal[(y + radius) * w + x];
|
|
322
|
+
if (y - radius - 1 >= 0) count -= horizontal[(y - radius - 1) * w + x];
|
|
323
|
+
if (y >= 0) out[y * w + x] = count > 0 ? 1 : 0;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
return out;
|
|
327
|
+
}
|
|
328
|
+
function artworkRegions(foreground, w, h, gap, minArea) {
|
|
329
|
+
const mask = dilate(foreground, w, h, gap), queue = new Int32Array(mask.length);
|
|
330
|
+
const regions = [];
|
|
331
|
+
for (let start = 0; start < mask.length; start++) {
|
|
332
|
+
if (!mask[start]) continue;
|
|
333
|
+
let head = 0, tail = 1, x0 = w, y0 = h, x1 = -1, y1 = -1, area = 0;
|
|
334
|
+
queue[0] = start;
|
|
335
|
+
mask[start] = 0;
|
|
336
|
+
while (head < tail) {
|
|
337
|
+
const p = queue[head++], x = p % w, y = Math.floor(p / w);
|
|
338
|
+
if (foreground[p]) {
|
|
339
|
+
x0 = Math.min(x0, x);
|
|
340
|
+
x1 = Math.max(x1, x);
|
|
341
|
+
y0 = Math.min(y0, y);
|
|
342
|
+
y1 = Math.max(y1, y);
|
|
343
|
+
area++;
|
|
344
|
+
}
|
|
345
|
+
for (const n of [
|
|
346
|
+
x > 0 ? p - 1 : -1,
|
|
347
|
+
x < w - 1 ? p + 1 : -1,
|
|
348
|
+
y > 0 ? p - w : -1,
|
|
349
|
+
y < h - 1 ? p + w : -1
|
|
350
|
+
])
|
|
351
|
+
if (n >= 0 && mask[n]) {
|
|
352
|
+
mask[n] = 0;
|
|
353
|
+
queue[tail++] = n;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
if (area >= minArea)
|
|
357
|
+
regions.push({
|
|
358
|
+
x: Math.max(0, x0 - 2),
|
|
359
|
+
y: Math.max(0, y0 - 2),
|
|
360
|
+
w: Math.min(w - 1, x1 + 2) - Math.max(0, x0 - 2) + 1,
|
|
361
|
+
h: Math.min(h - 1, y1 + 2) - Math.max(0, y0 - 2) + 1
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
return regions.sort((a, b) => a.y - b.y || a.x - b.x);
|
|
365
|
+
}
|
|
366
|
+
function removeOuterBackground(rgba, w, h, background, tolerance) {
|
|
367
|
+
const result = new Uint8Array(rgba), seen = new Uint8Array(w * h), queue = new Int32Array(w * h);
|
|
368
|
+
let head = 0, tail = 0;
|
|
369
|
+
const add = (p) => {
|
|
370
|
+
if (seen[p]) return;
|
|
371
|
+
seen[p] = 1;
|
|
372
|
+
if (rgba[p * 4 + 3] < 16 || [0, 1, 2].reduce((n, c) => n + Math.abs(rgba[p * 4 + c] - background[c]), 0) <= tolerance)
|
|
373
|
+
queue[tail++] = p;
|
|
374
|
+
};
|
|
375
|
+
for (let x = 0; x < w; x++) {
|
|
376
|
+
add(x);
|
|
377
|
+
add((h - 1) * w + x);
|
|
378
|
+
}
|
|
379
|
+
for (let y = 0; y < h; y++) {
|
|
380
|
+
add(y * w);
|
|
381
|
+
add(y * w + w - 1);
|
|
382
|
+
}
|
|
383
|
+
while (head < tail) {
|
|
384
|
+
const p = queue[head++], x = p % w, y = Math.floor(p / w);
|
|
385
|
+
result[p * 4 + 3] = 0;
|
|
386
|
+
if (x) add(p - 1);
|
|
387
|
+
if (x < w - 1) add(p + 1);
|
|
388
|
+
if (y) add(p - w);
|
|
389
|
+
if (y < h - 1) add(p + w);
|
|
390
|
+
}
|
|
391
|
+
return result;
|
|
392
|
+
}
|
|
393
|
+
async function extractKit(file, out, input) {
|
|
394
|
+
const options = optionsSchema.parse(input);
|
|
395
|
+
const { data, info } = await sharp2(resolve2(file), { limitInputPixels: 24e6 }).rotate().ensureAlpha().toColourspace("srgb").raw().toBuffer({ resolveWithObject: true });
|
|
396
|
+
const w = info.width, h = info.height;
|
|
397
|
+
const corners = [0, w - 1, (h - 1) * w, w * h - 1];
|
|
398
|
+
const transparentBackground = corners.every((p) => data[p * 4 + 3] < 16);
|
|
399
|
+
const background = [0, 1, 2].map(
|
|
400
|
+
(c) => Math.round(corners.reduce((n, p) => n + data[p * 4 + c], 0) / 4)
|
|
401
|
+
);
|
|
402
|
+
const foreground = new Uint8Array(w * h);
|
|
403
|
+
for (let p = 0; p < foreground.length; p++)
|
|
404
|
+
foreground[p] = data[p * 4 + 3] >= 16 && (transparentBackground || [0, 1, 2].reduce((n, c) => n + Math.abs(data[p * 4 + c] - background[c]), 0) > options.tolerance) ? 1 : 0;
|
|
405
|
+
const regions = options.regions ?? artworkRegions(foreground, w, h, options.gap, options.minArea);
|
|
406
|
+
if (!regions.length || regions.length > 24)
|
|
407
|
+
throw new InputError("Choose 1\u201324 regions using --params, or adjust gap/minArea.");
|
|
408
|
+
if (regions.some((r) => r.x + r.w > w || r.y + r.h > h))
|
|
409
|
+
throw new InputError("Every region must fit inside the image.");
|
|
410
|
+
const target = resolve2(out), temporary = await mkdtemp(join(dirname(target), ".makeables-extract-"));
|
|
411
|
+
try {
|
|
412
|
+
await mkdir(join(temporary, "pieces"));
|
|
413
|
+
const kit = {
|
|
414
|
+
format: "makeables-kit",
|
|
415
|
+
version: 1,
|
|
416
|
+
name: options.name,
|
|
417
|
+
items: [],
|
|
418
|
+
sheets: []
|
|
419
|
+
};
|
|
420
|
+
const views = [];
|
|
421
|
+
for (const [index, region] of regions.entries()) {
|
|
422
|
+
const id = `piece-${String(index + 1).padStart(2, "0")}`, name = region.name ?? `Piece ${index + 1}`;
|
|
423
|
+
const crop = await sharp2(data, { raw: { width: w, height: h, channels: 4 } }).extract({ left: region.x, top: region.y, width: region.w, height: region.h }).raw().toBuffer();
|
|
424
|
+
const pixels = transparentBackground ? crop : removeOuterBackground(crop, region.w, region.h, background, options.tolerance);
|
|
425
|
+
const png = await sharp2(pixels, { raw: { width: region.w, height: region.h, channels: 4 } }).png().toBuffer();
|
|
426
|
+
const design = createStickerDesign();
|
|
427
|
+
design.sticker = { ...defaultStickerMaterial, finish: "vinyl", sparkle: 0, holography: 0 };
|
|
428
|
+
design.name = name;
|
|
429
|
+
design.source = id;
|
|
430
|
+
design.assets = {
|
|
431
|
+
artwork: {
|
|
432
|
+
source: "image",
|
|
433
|
+
name,
|
|
434
|
+
data: `data:image/png;base64,${png.toString("base64")}`,
|
|
435
|
+
sourcePixels: { width: region.w, height: region.h }
|
|
436
|
+
}
|
|
437
|
+
};
|
|
438
|
+
design.front = {
|
|
439
|
+
background: "#00000000",
|
|
440
|
+
layers: [
|
|
441
|
+
{
|
|
442
|
+
id: "artwork",
|
|
443
|
+
kind: "image",
|
|
444
|
+
asset: "artwork",
|
|
445
|
+
name,
|
|
446
|
+
x: 64,
|
|
447
|
+
y: 64,
|
|
448
|
+
w: 896,
|
|
449
|
+
h: 896,
|
|
450
|
+
fit: "contain",
|
|
451
|
+
opacity: 1,
|
|
452
|
+
radius: 0
|
|
453
|
+
}
|
|
454
|
+
]
|
|
455
|
+
};
|
|
456
|
+
design.back = { background: "#00000000", layers: [] };
|
|
457
|
+
await writeFile2(
|
|
458
|
+
join(temporary, `pieces/${id}.json`),
|
|
459
|
+
JSON.stringify(badgeDesignSchema.parse(design), null, 2)
|
|
460
|
+
);
|
|
461
|
+
kit.items.push({ id, file: `pieces/${id}.json` });
|
|
462
|
+
views.push({ name, bytes: png });
|
|
463
|
+
}
|
|
464
|
+
await writeFile2(join(temporary, "kit.json"), JSON.stringify(kit, null, 2));
|
|
465
|
+
await writeFile2(join(temporary, "contact.png"), await kitCover(options.name, views));
|
|
466
|
+
await mkdir(target);
|
|
467
|
+
await rename(temporary, target);
|
|
468
|
+
return {
|
|
469
|
+
path: join(target, "kit.json"),
|
|
470
|
+
preview: join(target, "contact.png"),
|
|
471
|
+
items: kit.items.length,
|
|
472
|
+
regions,
|
|
473
|
+
source: { width: w, height: h },
|
|
474
|
+
notice: "Review the contact proof. Raster lettering remains part of each image; preserve new text as native layers."
|
|
475
|
+
};
|
|
476
|
+
} catch (error) {
|
|
477
|
+
await rm(temporary, { recursive: true, force: true });
|
|
478
|
+
throw error;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
277
482
|
// src/presentation.ts
|
|
278
483
|
import { stripVTControlCharacters } from "util";
|
|
279
484
|
function machineOutput(json) {
|
|
@@ -298,11 +503,11 @@ function banner(version2) {
|
|
|
298
503
|
// src/publish.ts
|
|
299
504
|
import { createHash as createHash2, randomUUID } from "crypto";
|
|
300
505
|
import { constants } from "fs";
|
|
301
|
-
import { appendFile, link, mkdir, open, readFile as readFile3, rename, rm } from "fs/promises";
|
|
506
|
+
import { appendFile, link, mkdir as mkdir2, open, readFile as readFile3, rename as rename2, rm as rm2 } from "fs/promises";
|
|
302
507
|
import { homedir } from "os";
|
|
303
|
-
import { dirname, join, resolve as
|
|
304
|
-
import
|
|
305
|
-
import { z } from "zod";
|
|
508
|
+
import { dirname as dirname2, join as join2, resolve as resolve3 } from "path";
|
|
509
|
+
import sharp3 from "sharp";
|
|
510
|
+
import { z as z2 } from "zod";
|
|
306
511
|
async function saveBundle(url, out) {
|
|
307
512
|
const state = await studioRequest(url, "/call", {
|
|
308
513
|
name: "badge_inspect",
|
|
@@ -323,7 +528,7 @@ async function saveBundle(url, out) {
|
|
|
323
528
|
result.error?.message ?? "The editor could not export a bundle. Refresh it after saving the current draft."
|
|
324
529
|
);
|
|
325
530
|
const pkg = await readPackageJson(result.data);
|
|
326
|
-
const path =
|
|
531
|
+
const path = resolve3(out);
|
|
327
532
|
const file = await open(path, "wx", 384);
|
|
328
533
|
try {
|
|
329
534
|
await file.writeFile(`${JSON.stringify(packageJson(pkg))}
|
|
@@ -344,48 +549,48 @@ async function saveBundle(url, out) {
|
|
|
344
549
|
published: false
|
|
345
550
|
};
|
|
346
551
|
}
|
|
347
|
-
var receiptSchema =
|
|
348
|
-
id:
|
|
349
|
-
version:
|
|
350
|
-
state:
|
|
351
|
-
url:
|
|
552
|
+
var receiptSchema = z2.object({
|
|
553
|
+
id: z2.string().uuid(),
|
|
554
|
+
version: z2.number().int().positive(),
|
|
555
|
+
state: z2.enum(["published", "withdrawn"]),
|
|
556
|
+
url: z2.string()
|
|
352
557
|
});
|
|
353
558
|
|
|
354
559
|
// src/reference-image.ts
|
|
355
560
|
import { spawnSync } from "child_process";
|
|
356
561
|
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "fs";
|
|
357
562
|
import { tmpdir } from "os";
|
|
358
|
-
import { join as
|
|
563
|
+
import { join as join3, resolve as resolve4 } from "path";
|
|
359
564
|
import { DOMParser as DOMParser2, XMLSerializer } from "@xmldom/xmldom";
|
|
360
|
-
import
|
|
361
|
-
import { z as
|
|
565
|
+
import sharp4 from "sharp";
|
|
566
|
+
import { z as z3 } from "zod";
|
|
362
567
|
var W = 1200;
|
|
363
568
|
var H = 756;
|
|
364
569
|
var N = W * H;
|
|
365
|
-
var optionSchema =
|
|
366
|
-
crop:
|
|
367
|
-
patch:
|
|
368
|
-
"edge-band":
|
|
369
|
-
"bg-tol":
|
|
370
|
-
title:
|
|
371
|
-
desc:
|
|
372
|
-
credit:
|
|
373
|
-
tones:
|
|
374
|
-
ink:
|
|
375
|
-
raster:
|
|
376
|
-
points:
|
|
377
|
-
box:
|
|
378
|
-
rows:
|
|
379
|
-
cols:
|
|
380
|
-
dark:
|
|
381
|
-
colors:
|
|
382
|
-
at:
|
|
383
|
-
up:
|
|
384
|
-
k:
|
|
385
|
-
fill:
|
|
386
|
-
top:
|
|
387
|
-
upright:
|
|
388
|
-
"ref-box":
|
|
570
|
+
var optionSchema = z3.object({
|
|
571
|
+
crop: z3.string().optional(),
|
|
572
|
+
patch: z3.array(z3.string()).default([]),
|
|
573
|
+
"edge-band": z3.coerce.number().min(0).max(1e3).optional(),
|
|
574
|
+
"bg-tol": z3.coerce.number().min(0).max(765).optional(),
|
|
575
|
+
title: z3.string().min(1).max(200).optional(),
|
|
576
|
+
desc: z3.string().max(5e3).optional(),
|
|
577
|
+
credit: z3.string().max(5e3).optional(),
|
|
578
|
+
tones: z3.coerce.number().int().min(2).max(12).optional(),
|
|
579
|
+
ink: z3.coerce.number().min(0).max(1).optional(),
|
|
580
|
+
raster: z3.boolean().optional(),
|
|
581
|
+
points: z3.array(z3.string()).max(100).optional(),
|
|
582
|
+
box: z3.string().optional(),
|
|
583
|
+
rows: z3.string().optional(),
|
|
584
|
+
cols: z3.string().optional(),
|
|
585
|
+
dark: z3.coerce.number().min(0).max(765).optional(),
|
|
586
|
+
colors: z3.coerce.number().int().min(1).max(12).optional(),
|
|
587
|
+
at: z3.string().optional(),
|
|
588
|
+
up: z3.coerce.number().int().min(1).max(8).optional(),
|
|
589
|
+
k: z3.coerce.number().min(0).max(1).optional(),
|
|
590
|
+
fill: z3.string().regex(/^#[a-fA-F0-9]{6}$/).optional(),
|
|
591
|
+
top: z3.enum(["left", "right"]).optional(),
|
|
592
|
+
upright: z3.enum(["left", "right"]).optional(),
|
|
593
|
+
"ref-box": z3.string().optional()
|
|
389
594
|
}).strict();
|
|
390
595
|
function fail(message) {
|
|
391
596
|
throw new InputError(message);
|
|
@@ -404,7 +609,7 @@ function validBox(values, width, height) {
|
|
|
404
609
|
}
|
|
405
610
|
var lumOf = (r, g, b) => 0.299 * r + 0.587 * g + 0.114 * b;
|
|
406
611
|
async function detect(file) {
|
|
407
|
-
const { data, info } = await
|
|
612
|
+
const { data, info } = await sharp4(file, { limitInputPixels: 24e6 }).toColourspace("srgb").removeAlpha().raw().toBuffer({ resolveWithObject: true });
|
|
408
613
|
const { width: w, height: h } = info;
|
|
409
614
|
const px = (x, y) => {
|
|
410
615
|
const i = (y * w + x) * 3;
|
|
@@ -480,7 +685,7 @@ async function detect(file) {
|
|
|
480
685
|
async function prepare(file, o) {
|
|
481
686
|
if (!o.crop || !o.out) fail('prepare needs --out <dir> and --params with {"crop":"x,y,w,h"}.');
|
|
482
687
|
const [cx, cy, cw, ch] = nums(o.crop, 4, "crop");
|
|
483
|
-
const meta = await
|
|
688
|
+
const meta = await sharp4(file, { limitInputPixels: 24e6 }).metadata();
|
|
484
689
|
validBox([cx, cy, cw, ch], meta.width ?? 0, meta.height ?? 0);
|
|
485
690
|
if (Math.abs(cw / ch / (W / H) - 1) > 0.03)
|
|
486
691
|
fail(
|
|
@@ -493,12 +698,12 @@ async function prepare(file, o) {
|
|
|
493
698
|
if (feather < 0 || ![0, 1].includes(flop))
|
|
494
699
|
fail("Patch feather must be nonnegative and flop must be 0 or 1.");
|
|
495
700
|
}
|
|
496
|
-
const out =
|
|
701
|
+
const out = resolve4(o.out);
|
|
497
702
|
mkdirSync(out);
|
|
498
703
|
const layers = [];
|
|
499
704
|
for (const spec of o.patch) {
|
|
500
705
|
const [dx, dy, pw, ph, sx, sy, feather = 6, flop = 0] = nums(spec, 6, "patch");
|
|
501
|
-
const patch = await
|
|
706
|
+
const patch = await sharp4(file).extract({ left: sx, top: sy, width: pw, height: ph }).flop(!!flop).toColourspace("srgb").removeAlpha().raw().toBuffer();
|
|
502
707
|
const rgba = Buffer.alloc(pw * ph * 4);
|
|
503
708
|
for (let y = 0; y < ph; y++)
|
|
504
709
|
for (let x = 0; x < pw; x++) {
|
|
@@ -510,10 +715,10 @@ async function prepare(file, o) {
|
|
|
510
715
|
}
|
|
511
716
|
layers.push({ input: rgba, raw: { width: pw, height: ph, channels: 4 }, left: dx, top: dy });
|
|
512
717
|
}
|
|
513
|
-
const patched = await
|
|
514
|
-
const card = await
|
|
718
|
+
const patched = await sharp4(file).removeAlpha().composite(layers).png().toBuffer();
|
|
719
|
+
const card = await sharp4(patched).extract({ left: cx, top: cy, width: cw, height: ch }).toColourspace("srgb").removeAlpha().raw().toBuffer({ resolveWithObject: true });
|
|
515
720
|
const px = card.data, CW = card.info.width, CH = card.info.height;
|
|
516
|
-
const src = await
|
|
721
|
+
const src = await sharp4(file, { limitInputPixels: 24e6 }).toColourspace("srgb").removeAlpha().raw().toBuffer({ resolveWithObject: true });
|
|
517
722
|
const sp = (x, y) => {
|
|
518
723
|
const i = (y * src.info.width + x) * 3;
|
|
519
724
|
return [src.data[i], src.data[i + 1], src.data[i + 2]];
|
|
@@ -553,14 +758,14 @@ async function prepare(file, o) {
|
|
|
553
758
|
if (y > 0) queue.push(i - CW);
|
|
554
759
|
if (y < CH - 1) queue.push(i + CW);
|
|
555
760
|
}
|
|
556
|
-
const cardPng = await
|
|
557
|
-
const resized =
|
|
558
|
-
await resized.clone().png().toFile(
|
|
559
|
-
await resized.clone().jpeg({ quality: 84, mozjpeg: true }).toFile(
|
|
761
|
+
const cardPng = await sharp4(px, { raw: card.info }).png().toBuffer();
|
|
762
|
+
const resized = sharp4(cardPng).resize(W, H, { fit: "fill", kernel: "lanczos3" });
|
|
763
|
+
await resized.clone().png().toFile(join3(out, "card.png"));
|
|
764
|
+
await resized.clone().jpeg({ quality: 84, mozjpeg: true }).toFile(join3(out, "card.jpg"));
|
|
560
765
|
const rgb = await resized.clone().toColourspace("srgb").raw().toBuffer();
|
|
561
766
|
const g = await resized.clone().toColourspace("b-w").raw().toBuffer();
|
|
562
|
-
writeFileSync(
|
|
563
|
-
writeFileSync(
|
|
767
|
+
writeFileSync(join3(out, "rgb.raw"), rgb);
|
|
768
|
+
writeFileSync(join3(out, "lum.raw"), g);
|
|
564
769
|
const GW = W * 2, GH = H * 2, big = Buffer.alloc(GW * GH);
|
|
565
770
|
for (let y = 0; y < GH; y++) {
|
|
566
771
|
const fy = Math.min(H - 1, Math.max(0, (y + 0.5) / 2 - 0.5)), y0 = Math.floor(fy), y1 = Math.min(H - 1, y0 + 1), ty = fy - y0;
|
|
@@ -572,14 +777,14 @@ async function prepare(file, o) {
|
|
|
572
777
|
);
|
|
573
778
|
}
|
|
574
779
|
}
|
|
575
|
-
writeFileSync(
|
|
780
|
+
writeFileSync(join3(out, "ink.pgm"), Buffer.concat([Buffer.from(`P5
|
|
576
781
|
${GW} ${GH}
|
|
577
782
|
255
|
|
578
783
|
`), big]));
|
|
579
784
|
const hex = paper.map((v) => Math.round(v).toString(16).padStart(2, "0")).join("");
|
|
580
785
|
return {
|
|
581
786
|
out,
|
|
582
|
-
preview:
|
|
787
|
+
preview: join3(out, "card.png"),
|
|
583
788
|
paper: `#${hex}`,
|
|
584
789
|
patches: o.patch.length,
|
|
585
790
|
filledEdgePixels: filled,
|
|
@@ -604,10 +809,10 @@ function potrace(input, extra) {
|
|
|
604
809
|
}
|
|
605
810
|
var esc = (s) => String(s).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
606
811
|
function vectorize(dir, o) {
|
|
607
|
-
dir =
|
|
812
|
+
dir = resolve4(dir);
|
|
608
813
|
if (!o.out || !o.title) fail("vectorize needs --out <file.svg> and --title");
|
|
609
814
|
const K = Number(o.tones ?? 7);
|
|
610
|
-
const lum = readFileSync(
|
|
815
|
+
const lum = readFileSync(join3(dir, "lum.raw")), rgb = readFileSync(join3(dir, "rgb.raw"));
|
|
611
816
|
if (lum.length !== N || rgb.length !== N * 3)
|
|
612
817
|
fail("Expected an intact directory created by image prepare.");
|
|
613
818
|
let c = [...Array(K)].map((_, i) => 30 + i * 200 / (K - 1));
|
|
@@ -657,11 +862,11 @@ function vectorize(dir, o) {
|
|
|
657
862
|
const L = used.length;
|
|
658
863
|
const inkK = o.ink !== void 0 ? Number(o.ink) : L > 1 ? (c[used[0]] + c[used[1]]) / 2 / 255 : 0.25;
|
|
659
864
|
const groups = [];
|
|
660
|
-
const maskFile =
|
|
865
|
+
const maskFile = join3(dir, "mask.pbm");
|
|
661
866
|
for (let j = o.raster ? 0 : L - 2; j >= 0; j--) {
|
|
662
867
|
let traced;
|
|
663
868
|
if (j === 0) {
|
|
664
|
-
traced = potrace(
|
|
869
|
+
traced = potrace(join3(dir, "ink.pgm"), [
|
|
665
870
|
"-k",
|
|
666
871
|
inkK.toFixed(4),
|
|
667
872
|
"-t",
|
|
@@ -684,7 +889,7 @@ ${W} ${H}
|
|
|
684
889
|
if (traced) groups.push(`<g transform="${traced.tr}" fill="${colors[j]}">${traced.paths}</g>`);
|
|
685
890
|
}
|
|
686
891
|
const base = o.raster ? `<rect width="${W}" height="${H}" fill="${colors[L - 1]}"/>
|
|
687
|
-
<image width="${W}" height="${H}" preserveAspectRatio="none" href="data:image/jpeg;base64,${readFileSync(
|
|
892
|
+
<image width="${W}" height="${H}" preserveAspectRatio="none" href="data:image/jpeg;base64,${readFileSync(join3(dir, "card.jpg")).toString("base64")}"/>` : `<rect width="${W}" height="${H}" fill="${colors[L - 1]}"/>`;
|
|
688
893
|
const title = esc(o.title), desc = esc(o.desc ?? `${o.title} card artwork.`);
|
|
689
894
|
const meta = esc(o.credit ?? "Original artwork traced from a reference image.");
|
|
690
895
|
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${W}" height="${H}" viewBox="0 0 ${W} ${H}" role="img" aria-label="${desc}">
|
|
@@ -697,9 +902,9 @@ ${groups.join("\n")}
|
|
|
697
902
|
`;
|
|
698
903
|
const checked = checkSubmittedSvg(svg);
|
|
699
904
|
if (!checked.ok) fail(checked.error);
|
|
700
|
-
writeFileSync(
|
|
905
|
+
writeFileSync(resolve4(o.out), svg, { flag: "wx" });
|
|
701
906
|
return {
|
|
702
|
-
out:
|
|
907
|
+
out: resolve4(o.out),
|
|
703
908
|
bytes: Buffer.byteLength(svg),
|
|
704
909
|
mode: o.raster ? "raster background + vector ink" : "pure vector",
|
|
705
910
|
tones: colors,
|
|
@@ -707,7 +912,7 @@ ${groups.join("\n")}
|
|
|
707
912
|
};
|
|
708
913
|
}
|
|
709
914
|
async function rawImage(file) {
|
|
710
|
-
const { data, info } = await
|
|
915
|
+
const { data, info } = await sharp4(file, { limitInputPixels: 24e6 }).toColourspace("srgb").removeAlpha().raw().toBuffer({ resolveWithObject: true });
|
|
711
916
|
const px = (x, y) => {
|
|
712
917
|
if (!Number.isInteger(x) || !Number.isInteger(y) || x < 0 || y < 0 || x >= info.width || y >= info.height)
|
|
713
918
|
fail("Pixel coordinates are outside the image.");
|
|
@@ -806,14 +1011,14 @@ async function glyphs(file, o) {
|
|
|
806
1011
|
if (!o.box || !o.at) fail("glyphs needs --box x,y,w,h and --at X,Y");
|
|
807
1012
|
const [bx, by, bw, bh] = nums(o.box, 4, "box");
|
|
808
1013
|
const [ax, ay] = nums(o.at, 2, "at");
|
|
809
|
-
const meta = await
|
|
1014
|
+
const meta = await sharp4(file, { limitInputPixels: 24e6 }).metadata();
|
|
810
1015
|
validBox([bx, by, bw, bh], meta.width ?? 0, meta.height ?? 0);
|
|
811
1016
|
const up = Number(o.up ?? 4);
|
|
812
1017
|
if (bw * bh * up * up > 24e6)
|
|
813
1018
|
fail("Trace region is too large; lower up or choose a smaller box.");
|
|
814
|
-
const { data, info } = await
|
|
815
|
-
const temp = mkdtempSync(
|
|
816
|
-
const pgm =
|
|
1019
|
+
const { data, info } = await sharp4(file).extract({ left: bx, top: by, width: bw, height: bh }).greyscale().removeAlpha().resize({ width: bw * up, kernel: "lanczos3" }).raw().toBuffer({ resolveWithObject: true });
|
|
1020
|
+
const temp = mkdtempSync(join3(tmpdir(), "makeables-glyphs-"));
|
|
1021
|
+
const pgm = join3(temp, "glyphs.pgm");
|
|
817
1022
|
writeFileSync(pgm, Buffer.concat([Buffer.from(`P5
|
|
818
1023
|
${info.width} ${info.height}
|
|
819
1024
|
255
|
|
@@ -832,12 +1037,12 @@ ${info.width} ${info.height}
|
|
|
832
1037
|
const paths = [...svg.matchAll(/ d="([^"]+)"/g)].map((m) => m[1].replace(/\s+/g, " ")).join(" ");
|
|
833
1038
|
if (!tr || !paths) fail("nothing dark enough to trace in that box; lower --k or check the box");
|
|
834
1039
|
const snippet = `<g transform="translate(${ax} ${ay}) scale(${+(1 / up).toFixed(6)})"><path transform="${tr}" fill="${o.fill ?? "#0a0a0a"}" fill-rule="evenodd" d="${paths}"/></g>`;
|
|
835
|
-
if (o.out) writeFileSync(
|
|
1040
|
+
if (o.out) writeFileSync(resolve4(o.out), snippet, { flag: "wx" });
|
|
836
1041
|
return {
|
|
837
1042
|
box: { x: bx, y: by, w: bw, h: bh },
|
|
838
1043
|
at: { x: ax, y: ay },
|
|
839
1044
|
bytes: snippet.length,
|
|
840
|
-
out: o.out ?
|
|
1045
|
+
out: o.out ? resolve4(o.out) : null,
|
|
841
1046
|
snippet: o.out ? void 0 : snippet
|
|
842
1047
|
};
|
|
843
1048
|
}
|
|
@@ -859,28 +1064,28 @@ function rotate(file, o) {
|
|
|
859
1064
|
const svg = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="${W}" height="${H}" viewBox="0 0 ${W} ${H}"><g transform="${transform}"><g${inherited}>${body}</g></g></svg>`;
|
|
860
1065
|
const checked = checkSubmittedSvg(svg);
|
|
861
1066
|
if (!checked.ok) fail(checked.error);
|
|
862
|
-
writeFileSync(
|
|
863
|
-
return { out:
|
|
1067
|
+
writeFileSync(resolve4(o.out), svg, { flag: "wx" });
|
|
1068
|
+
return { out: resolve4(o.out), transform, bytes: Buffer.byteLength(svg) };
|
|
864
1069
|
}
|
|
865
1070
|
async function compare(file, ref, o) {
|
|
866
1071
|
if (!o.out) fail("compare needs --out <file.png>");
|
|
867
|
-
let card =
|
|
1072
|
+
let card = sharp4(readFileSync(file));
|
|
868
1073
|
if (o.upright) card = card.rotate(o.upright === "left" ? 90 : -90);
|
|
869
1074
|
const cardPng = await card.png().toBuffer();
|
|
870
|
-
let reference =
|
|
1075
|
+
let reference = sharp4(ref);
|
|
871
1076
|
if (o["ref-box"]) {
|
|
872
1077
|
const [x, y, w, h] = nums(o["ref-box"], 4, "ref-box");
|
|
873
1078
|
reference = reference.extract({ left: x, top: y, width: w, height: h });
|
|
874
1079
|
}
|
|
875
1080
|
const height = 900;
|
|
876
|
-
const a = await
|
|
877
|
-
const b = await
|
|
1081
|
+
const a = await sharp4(cardPng).resize({ height }).png().toBuffer({ resolveWithObject: true });
|
|
1082
|
+
const b = await sharp4(await reference.png().toBuffer()).resize({ height }).png().toBuffer({ resolveWithObject: true });
|
|
878
1083
|
const gap = 24, width = a.info.width + b.info.width + gap * 3;
|
|
879
|
-
await
|
|
1084
|
+
await sharp4({ create: { width, height: height + gap * 2, channels: 3, background: "#ffffff" } }).composite([
|
|
880
1085
|
{ input: a.data, left: gap, top: gap },
|
|
881
1086
|
{ input: b.data, left: a.info.width + gap * 2, top: gap }
|
|
882
|
-
]).png().toFile(
|
|
883
|
-
return { out:
|
|
1087
|
+
]).png().toFile(resolve4(o.out));
|
|
1088
|
+
return { out: resolve4(o.out), left: "card", right: "reference" };
|
|
884
1089
|
}
|
|
885
1090
|
var imageTools = [
|
|
886
1091
|
"detect",
|
|
@@ -900,7 +1105,7 @@ async function referenceImage(command, file, options, out, reference) {
|
|
|
900
1105
|
parsed.error.issues.map((issue) => `${issue.path.join(".")}: ${issue.message}`).join("; ")
|
|
901
1106
|
);
|
|
902
1107
|
const o = { ...parsed.data, out };
|
|
903
|
-
if (out && existsSync(
|
|
1108
|
+
if (out && existsSync(resolve4(out))) fail("Output already exists. Choose a new --out path.");
|
|
904
1109
|
if (!existsSync(file)) fail("Input file does not exist.");
|
|
905
1110
|
switch (command) {
|
|
906
1111
|
case "detect":
|
|
@@ -928,175 +1133,6 @@ async function referenceImage(command, file, options, out, reference) {
|
|
|
928
1133
|
}
|
|
929
1134
|
}
|
|
930
1135
|
|
|
931
|
-
// src/sheet.ts
|
|
932
|
-
import { readFile as readFile4 } from "fs/promises";
|
|
933
|
-
import { resolve as resolve4 } from "path";
|
|
934
|
-
import { deflateSync } from "zlib";
|
|
935
|
-
import { createCanvas } from "@napi-rs/canvas";
|
|
936
|
-
import sharp4 from "sharp";
|
|
937
|
-
async function readSheet(file) {
|
|
938
|
-
const value = JSON.parse(await readFile4(resolve4(file), "utf8"));
|
|
939
|
-
const parsed = stickerSheetSchema.safeParse(value);
|
|
940
|
-
if (!parsed.success)
|
|
941
|
-
throw new InputError(
|
|
942
|
-
`Invalid sheet: ${parsed.error.issues.map((issue) => `${issue.path.join(".")}: ${issue.message}`).join("; ")}`
|
|
943
|
-
);
|
|
944
|
-
return parsed.data;
|
|
945
|
-
}
|
|
946
|
-
function newSheet(type, size, name) {
|
|
947
|
-
if (typeof type !== "string" || !sheetTypeIds.includes(type))
|
|
948
|
-
throw new InputError(`--type must be one of ${sheetTypeIds.join(", ")}.`);
|
|
949
|
-
const sheet = createStickerSheet(
|
|
950
|
-
type,
|
|
951
|
-
typeof name === "string" ? name : void 0
|
|
952
|
-
);
|
|
953
|
-
if (size !== void 0) {
|
|
954
|
-
if (typeof size !== "string" || !(size in sheetSizes))
|
|
955
|
-
throw new InputError(`--size must be one of ${Object.keys(sheetSizes).join(", ")}.`);
|
|
956
|
-
sheet.size = size;
|
|
957
|
-
}
|
|
958
|
-
return parse(sheet);
|
|
959
|
-
}
|
|
960
|
-
function parse(sheet) {
|
|
961
|
-
const parsed = stickerSheetSchema.safeParse(sheet);
|
|
962
|
-
if (!parsed.success)
|
|
963
|
-
throw new InputError(parsed.error.issues.map((issue) => issue.message).join("; "));
|
|
964
|
-
return parsed.data;
|
|
965
|
-
}
|
|
966
|
-
async function readSticker(source) {
|
|
967
|
-
const style2 = stickerStyles.find((entry) => entry.source === source);
|
|
968
|
-
if (style2) return badgeDesignSchema.parse(structuredClone(style2));
|
|
969
|
-
const design = badgeDesignSchema.parse(JSON.parse(await readFile4(resolve4(source), "utf8")));
|
|
970
|
-
if (design.product !== "sticker") throw new InputError(`${source} is not a sticker design.`);
|
|
971
|
-
return design;
|
|
972
|
-
}
|
|
973
|
-
var artCache = /* @__PURE__ */ new Map();
|
|
974
|
-
function flatArt(design) {
|
|
975
|
-
const key = JSON.stringify(design);
|
|
976
|
-
let art = artCache.get(key);
|
|
977
|
-
if (!art) {
|
|
978
|
-
art = renderBadge(design, { face: "front", person: {} }).then(async ({ png }) => {
|
|
979
|
-
const { data, info } = await sharp4(png).ensureAlpha().raw().toBuffer({ resolveWithObject: true });
|
|
980
|
-
return {
|
|
981
|
-
width: info.width,
|
|
982
|
-
height: info.height,
|
|
983
|
-
rgba: new Uint8ClampedArray(data.buffer, data.byteOffset, data.length)
|
|
984
|
-
};
|
|
985
|
-
});
|
|
986
|
-
artCache.set(key, art);
|
|
987
|
-
}
|
|
988
|
-
return art;
|
|
989
|
-
}
|
|
990
|
-
async function sheetArt(sheet) {
|
|
991
|
-
return new Map(
|
|
992
|
-
await Promise.all(
|
|
993
|
-
sheet.items.map(async (item) => [item.id, await flatArt(item.design)])
|
|
994
|
-
)
|
|
995
|
-
);
|
|
996
|
-
}
|
|
997
|
-
function bounds(sheet) {
|
|
998
|
-
const spec = sheetSpec(sheet);
|
|
999
|
-
return { widthMm: spec.widthMm ?? 0, heightMm: spec.heightMm ?? 0 };
|
|
1000
|
-
}
|
|
1001
|
-
async function addToSheet(sheet, source, widthMm) {
|
|
1002
|
-
const type = sheetTypes[sheet.type];
|
|
1003
|
-
if (type.cut === "die-cut" && sheet.items.length)
|
|
1004
|
-
throw new InputError("A single sticker file holds one sticker. Start another sheet.");
|
|
1005
|
-
const width = widthMm === void 0 ? type.stickerWidthMm : Number(widthMm);
|
|
1006
|
-
if (!Number.isFinite(width) || width < 5 || width > 300)
|
|
1007
|
-
throw new InputError("--width-mm must be a number from 5 to 300.");
|
|
1008
|
-
const design = await readSticker(source);
|
|
1009
|
-
const base = (design.source ?? "sticker").replace(/[^a-z0-9-]/g, "").slice(0, 40) || "sticker";
|
|
1010
|
-
let id = base;
|
|
1011
|
-
for (let n = 2; sheet.items.some((item2) => item2.id === id); n++) id = `${base}-${n}`;
|
|
1012
|
-
const spec = sheetSpec(sheet);
|
|
1013
|
-
const item = {
|
|
1014
|
-
id,
|
|
1015
|
-
design,
|
|
1016
|
-
x: (spec.widthMm ?? width) / 2,
|
|
1017
|
-
y: (spec.heightMm ?? width) / 2,
|
|
1018
|
-
rotation: 0,
|
|
1019
|
-
widthMm: width
|
|
1020
|
-
};
|
|
1021
|
-
const next = parse({ ...sheet, items: [...sheet.items, item] });
|
|
1022
|
-
if (type.cut === "die-cut") return { sheet: next, item, placed: true };
|
|
1023
|
-
const print = prepareSheetPrint(next, await sheetArt(next));
|
|
1024
|
-
const moved = placeNear(
|
|
1025
|
-
spec,
|
|
1026
|
-
bounds(next),
|
|
1027
|
-
next.items.map((entry) => ({ item: entry, shape: print.shapes.get(entry.id) })),
|
|
1028
|
-
id,
|
|
1029
|
-
[item.x, item.y]
|
|
1030
|
-
);
|
|
1031
|
-
if (moved) next.items[next.items.length - 1] = moved;
|
|
1032
|
-
return { sheet: next, item: moved ?? item, placed: Boolean(moved) };
|
|
1033
|
-
}
|
|
1034
|
-
async function packStickerSheet(sheet) {
|
|
1035
|
-
if (sheetTypes[sheet.type].cut === "die-cut") return { sheet, missing: [] };
|
|
1036
|
-
const print = prepareSheetPrint(sheet, await sheetArt(sheet));
|
|
1037
|
-
const packed = packSheet(
|
|
1038
|
-
sheetSpec(sheet),
|
|
1039
|
-
bounds(sheet),
|
|
1040
|
-
sheet.items.map((item) => ({ item, shape: print.shapes.get(item.id) })),
|
|
1041
|
-
sheet.hero
|
|
1042
|
-
);
|
|
1043
|
-
return { sheet: parse({ ...sheet, items: packed.items }), missing: packed.missing };
|
|
1044
|
-
}
|
|
1045
|
-
async function checkSheet(sheet) {
|
|
1046
|
-
return prepareSheetPrint(sheet, await sheetArt(sheet));
|
|
1047
|
-
}
|
|
1048
|
-
function sheetReport(print) {
|
|
1049
|
-
return {
|
|
1050
|
-
printable: print.printable,
|
|
1051
|
-
widthMm: Number(print.widthMm.toFixed(2)),
|
|
1052
|
-
heightMm: Number(print.heightMm.toFixed(2)),
|
|
1053
|
-
stickers: print.items.length,
|
|
1054
|
-
violations: print.violations,
|
|
1055
|
-
issues: print.issues.map(({ id, issue }) => ({ id, ...issue }))
|
|
1056
|
-
};
|
|
1057
|
-
}
|
|
1058
|
-
async function exportSheetPdf(sheet, print) {
|
|
1059
|
-
return sheetPrintPdf(sheet, print, async (bytes) => new Uint8Array(deflateSync(bytes)));
|
|
1060
|
-
}
|
|
1061
|
-
function sheetPreviewPng(print, dpi = 150) {
|
|
1062
|
-
const scale = dpi / 25.4;
|
|
1063
|
-
const canvas = createCanvas(
|
|
1064
|
-
Math.round(print.widthMm * scale),
|
|
1065
|
-
Math.round(print.heightMm * scale)
|
|
1066
|
-
);
|
|
1067
|
-
const context = canvas.getContext("2d");
|
|
1068
|
-
context.fillStyle = "#f4f3ef";
|
|
1069
|
-
context.fillRect(0, 0, canvas.width, canvas.height);
|
|
1070
|
-
for (const item of print.pdfItems) {
|
|
1071
|
-
const art = createCanvas(item.art.width, item.art.height);
|
|
1072
|
-
const data = art.getContext("2d").createImageData(item.art.width, item.art.height);
|
|
1073
|
-
data.data.set(item.art.rgba);
|
|
1074
|
-
art.getContext("2d").putImageData(data, 0, 0);
|
|
1075
|
-
context.save();
|
|
1076
|
-
context.translate(item.x * scale, item.y * scale);
|
|
1077
|
-
context.rotate(item.rotation * Math.PI / 180);
|
|
1078
|
-
context.drawImage(
|
|
1079
|
-
art,
|
|
1080
|
-
-item.widthMm / 2 * scale,
|
|
1081
|
-
-item.heightMm / 2 * scale,
|
|
1082
|
-
item.widthMm * scale,
|
|
1083
|
-
item.heightMm * scale
|
|
1084
|
-
);
|
|
1085
|
-
context.restore();
|
|
1086
|
-
context.strokeStyle = "#ff00ff";
|
|
1087
|
-
context.lineWidth = Math.max(1, 0.2 * scale);
|
|
1088
|
-
context.beginPath();
|
|
1089
|
-
for (const loop of item.cut) {
|
|
1090
|
-
for (const [i, [x, y]] of loop.entries())
|
|
1091
|
-
if (i) context.lineTo(x * scale, y * scale);
|
|
1092
|
-
else context.moveTo(x * scale, y * scale);
|
|
1093
|
-
context.closePath();
|
|
1094
|
-
}
|
|
1095
|
-
context.stroke();
|
|
1096
|
-
}
|
|
1097
|
-
return canvas.toBuffer("image/png");
|
|
1098
|
-
}
|
|
1099
|
-
|
|
1100
1136
|
// src/sticker-cut.ts
|
|
1101
1137
|
import sharp5 from "sharp";
|
|
1102
1138
|
async function stickerPrintCut(design) {
|
|
@@ -1114,7 +1150,7 @@ async function stickerPrintCut(design) {
|
|
|
1114
1150
|
}
|
|
1115
1151
|
|
|
1116
1152
|
// src/sticker-material.ts
|
|
1117
|
-
import { readFile as
|
|
1153
|
+
import { readFile as readFile4, stat as stat3 } from "fs/promises";
|
|
1118
1154
|
import { basename as basename2 } from "path";
|
|
1119
1155
|
import sharp6 from "sharp";
|
|
1120
1156
|
async function bindStickerMap(design, layerId, path) {
|
|
@@ -1124,7 +1160,7 @@ async function bindStickerMap(design, layerId, path) {
|
|
|
1124
1160
|
const info = await stat3(path);
|
|
1125
1161
|
if (!info.isFile() || info.size > 4e6)
|
|
1126
1162
|
throw new InputError("Use an optical image below 4 MB.");
|
|
1127
|
-
const bytes = await
|
|
1163
|
+
const bytes = await readFile4(path);
|
|
1128
1164
|
const meta = await sharp6(bytes, { limitInputPixels: 16777216 }).metadata();
|
|
1129
1165
|
if (!meta.width || !meta.height || meta.width > 4096 || meta.height > 4096 || !["png", "webp"].includes(meta.format ?? ""))
|
|
1130
1166
|
throw new InputError("Use an RGB PNG or WebP optical map up to 4096 \xD7 4096.");
|
|
@@ -1150,13 +1186,48 @@ async function bindStickerMap(design, layerId, path) {
|
|
|
1150
1186
|
});
|
|
1151
1187
|
}
|
|
1152
1188
|
|
|
1189
|
+
// src/trace.ts
|
|
1190
|
+
import { constants as constants2 } from "fs";
|
|
1191
|
+
import { open as open2 } from "fs/promises";
|
|
1192
|
+
async function appendTrace(path, event) {
|
|
1193
|
+
const file = await open2(
|
|
1194
|
+
path,
|
|
1195
|
+
constants2.O_APPEND | constants2.O_CREAT | constants2.O_WRONLY | constants2.O_NOFOLLOW,
|
|
1196
|
+
384
|
|
1197
|
+
);
|
|
1198
|
+
try {
|
|
1199
|
+
if (!(await file.stat()).isFile()) throw new Error("Choose a regular trace file.");
|
|
1200
|
+
await file.chmod(384);
|
|
1201
|
+
await file.writeFile(`${JSON.stringify(event)}
|
|
1202
|
+
`);
|
|
1203
|
+
} finally {
|
|
1204
|
+
await file.close();
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
function compactReceipt(value) {
|
|
1208
|
+
if (Array.isArray(value)) return value.map(compactReceipt);
|
|
1209
|
+
if (value && typeof value === "object")
|
|
1210
|
+
return Object.fromEntries(
|
|
1211
|
+
Object.entries(value).map(([key, entry]) => [
|
|
1212
|
+
key,
|
|
1213
|
+
key === "design" && entry && typeof entry === "object" ? {
|
|
1214
|
+
name: "name" in entry ? entry.name : void 0,
|
|
1215
|
+
product: "product" in entry ? entry.product : "badge",
|
|
1216
|
+
omitted: true
|
|
1217
|
+
} : compactReceipt(entry)
|
|
1218
|
+
])
|
|
1219
|
+
);
|
|
1220
|
+
if (typeof value === "string" && value.length > 2e3) return "[omitted; inspect the saved file]";
|
|
1221
|
+
return value;
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1153
1224
|
// src/wallet.ts
|
|
1154
1225
|
import { spawn } from "child_process";
|
|
1155
1226
|
import { createHash as createHash3 } from "crypto";
|
|
1156
1227
|
import { existsSync as existsSync2 } from "fs";
|
|
1157
|
-
import { chmod as chmod2, lstat as lstat3, mkdir as
|
|
1228
|
+
import { chmod as chmod2, lstat as lstat3, mkdir as mkdir4, mkdtemp as mkdtemp3, readFile as readFile6, rename as rename4, rm as rm5, writeFile as writeFile4 } from "fs/promises";
|
|
1158
1229
|
import { homedir as homedir3, tmpdir as tmpdir2 } from "os";
|
|
1159
|
-
import { dirname as
|
|
1230
|
+
import { dirname as dirname4, extname as extname3, join as join7, resolve as resolve6 } from "path";
|
|
1160
1231
|
import sharp7 from "sharp";
|
|
1161
1232
|
|
|
1162
1233
|
// src/wallet-release.json
|
|
@@ -1178,18 +1249,18 @@ var wallet_release_default = {
|
|
|
1178
1249
|
|
|
1179
1250
|
// src/wallet-storage.ts
|
|
1180
1251
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
1181
|
-
import { constants as
|
|
1182
|
-
import { chmod, lstat, mkdir as
|
|
1252
|
+
import { constants as constants3 } from "fs";
|
|
1253
|
+
import { chmod, lstat, mkdir as mkdir3, open as open3, rename as rename3, rm as rm3 } from "fs/promises";
|
|
1183
1254
|
import { homedir as homedir2 } from "os";
|
|
1184
|
-
import { dirname as
|
|
1255
|
+
import { dirname as dirname3, join as join4, resolve as resolve5 } from "path";
|
|
1185
1256
|
function walletHome() {
|
|
1186
1257
|
return resolve5(
|
|
1187
|
-
process.env.MAKEABLES_WALLET_HOME ??
|
|
1258
|
+
process.env.MAKEABLES_WALLET_HOME ?? join4(homedir2(), "Library", "Application Support", "Makeables", "Wallet")
|
|
1188
1259
|
);
|
|
1189
1260
|
}
|
|
1190
1261
|
async function privateDirectory(path) {
|
|
1191
1262
|
const absolute = resolve5(path);
|
|
1192
|
-
const parent =
|
|
1263
|
+
const parent = dirname3(absolute);
|
|
1193
1264
|
if (parent !== absolute) {
|
|
1194
1265
|
try {
|
|
1195
1266
|
const info = await lstat(parent);
|
|
@@ -1200,14 +1271,14 @@ async function privateDirectory(path) {
|
|
|
1200
1271
|
await privateDirectory(parent);
|
|
1201
1272
|
}
|
|
1202
1273
|
}
|
|
1203
|
-
await
|
|
1274
|
+
await mkdir3(absolute, { recursive: true, mode: 448 });
|
|
1204
1275
|
if (!(await lstat(absolute)).isDirectory())
|
|
1205
1276
|
throw new InputError("Wallet storage must be a directory, not a symbolic link.");
|
|
1206
1277
|
await chmod(absolute, 448);
|
|
1207
1278
|
return absolute;
|
|
1208
1279
|
}
|
|
1209
1280
|
async function readPrivateJson(path) {
|
|
1210
|
-
const handle = await
|
|
1281
|
+
const handle = await open3(path, constants3.O_RDONLY | constants3.O_NOFOLLOW);
|
|
1211
1282
|
try {
|
|
1212
1283
|
const info = await handle.stat();
|
|
1213
1284
|
if (!info.isFile() || info.size > 1e6)
|
|
@@ -1218,9 +1289,9 @@ async function readPrivateJson(path) {
|
|
|
1218
1289
|
}
|
|
1219
1290
|
}
|
|
1220
1291
|
async function writePrivateJson(path, value) {
|
|
1221
|
-
const temporary =
|
|
1292
|
+
const temporary = join4(dirname3(path), `.${randomUUID2()}.json`);
|
|
1222
1293
|
try {
|
|
1223
|
-
const handle = await
|
|
1294
|
+
const handle = await open3(temporary, "wx", 384);
|
|
1224
1295
|
try {
|
|
1225
1296
|
await handle.writeFile(`${JSON.stringify(value)}
|
|
1226
1297
|
`);
|
|
@@ -1228,15 +1299,15 @@ async function writePrivateJson(path, value) {
|
|
|
1228
1299
|
} finally {
|
|
1229
1300
|
await handle.close();
|
|
1230
1301
|
}
|
|
1231
|
-
await
|
|
1302
|
+
await rename3(temporary, path);
|
|
1232
1303
|
} finally {
|
|
1233
|
-
await
|
|
1304
|
+
await rm3(temporary, { force: true });
|
|
1234
1305
|
}
|
|
1235
1306
|
}
|
|
1236
1307
|
|
|
1237
1308
|
// src/wallet-submission.ts
|
|
1238
|
-
import { mkdtemp, readFile as
|
|
1239
|
-
import { join as
|
|
1309
|
+
import { mkdtemp as mkdtemp2, readFile as readFile5, rm as rm4, writeFile as writeFile3 } from "fs/promises";
|
|
1310
|
+
import { join as join5 } from "path";
|
|
1240
1311
|
var uuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
1241
1312
|
function submissionId(input) {
|
|
1242
1313
|
if (uuid.test(input)) return input.toLowerCase();
|
|
@@ -1254,7 +1325,7 @@ function submissionId(input) {
|
|
|
1254
1325
|
return id.toLowerCase();
|
|
1255
1326
|
}
|
|
1256
1327
|
async function downloadWalletSubmission(input) {
|
|
1257
|
-
const
|
|
1328
|
+
const started2 = performance.now();
|
|
1258
1329
|
const id = submissionId(input);
|
|
1259
1330
|
const response = await fetch(`https://makeables.dev/api/submissions/${id}/files?download=1`, {
|
|
1260
1331
|
redirect: "error",
|
|
@@ -1284,16 +1355,16 @@ async function downloadWalletSubmission(input) {
|
|
|
1284
1355
|
const pkg = await readPackageZip(bytes);
|
|
1285
1356
|
if (pkg.manifest.product !== "card")
|
|
1286
1357
|
throw new InputError("Wallet artwork requires a card submission.");
|
|
1287
|
-
const directory = await
|
|
1288
|
-
|
|
1289
|
-
await privateDirectory(
|
|
1358
|
+
const directory = await mkdtemp2(
|
|
1359
|
+
join5(
|
|
1360
|
+
await privateDirectory(join5(await privateDirectory(walletHome()), "submissions")),
|
|
1290
1361
|
"download-"
|
|
1291
1362
|
)
|
|
1292
1363
|
);
|
|
1293
1364
|
try {
|
|
1294
|
-
const path =
|
|
1295
|
-
await
|
|
1296
|
-
const saved = await
|
|
1365
|
+
const path = join5(directory, `${id}.makeables.zip`);
|
|
1366
|
+
await writeFile3(path, bytes, { mode: 384, flag: "wx" });
|
|
1367
|
+
const saved = await readFile5(path);
|
|
1297
1368
|
const sha256 = await hashBytes(saved);
|
|
1298
1369
|
if (saved.length !== bytes.length || sha256 !== await hashBytes(bytes))
|
|
1299
1370
|
throw new Error("Saved submission checksum does not match.");
|
|
@@ -1304,34 +1375,34 @@ async function downloadWalletSubmission(input) {
|
|
|
1304
1375
|
submissionId: id,
|
|
1305
1376
|
title: pkg.manifest.title,
|
|
1306
1377
|
downloadVerified: true,
|
|
1307
|
-
elapsedMs: Math.round(performance.now() -
|
|
1378
|
+
elapsedMs: Math.round(performance.now() - started2)
|
|
1308
1379
|
};
|
|
1309
1380
|
} catch (error) {
|
|
1310
|
-
await
|
|
1381
|
+
await rm4(directory, { recursive: true, force: true });
|
|
1311
1382
|
throw error;
|
|
1312
1383
|
}
|
|
1313
1384
|
}
|
|
1314
1385
|
|
|
1315
1386
|
// src/wallet-targets.ts
|
|
1316
1387
|
import { lstat as lstat2 } from "fs/promises";
|
|
1317
|
-
import { join as
|
|
1388
|
+
import { join as join6 } from "path";
|
|
1318
1389
|
import lockfile from "proper-lockfile";
|
|
1319
|
-
import { z as
|
|
1320
|
-
var alias =
|
|
1321
|
-
var label =
|
|
1390
|
+
import { z as z4 } from "zod";
|
|
1391
|
+
var alias = z4.string().regex(/^[a-z][a-z0-9-]{0,47}$/).refine((value) => !/\d{5}/.test(value));
|
|
1392
|
+
var label = z4.string().trim().min(1).max(60).refine(
|
|
1322
1393
|
(value) => !/\d{5}/.test(value) && [...value].every(
|
|
1323
1394
|
(character) => character.charCodeAt(0) >= 32 && character.charCodeAt(0) !== 127
|
|
1324
1395
|
)
|
|
1325
1396
|
);
|
|
1326
|
-
var targetSchema =
|
|
1397
|
+
var targetSchema = z4.object({
|
|
1327
1398
|
target: alias,
|
|
1328
1399
|
label,
|
|
1329
|
-
last4:
|
|
1330
|
-
identitySource:
|
|
1331
|
-
backup:
|
|
1332
|
-
createdAt:
|
|
1400
|
+
last4: z4.string().regex(/^\d{4}$/).optional(),
|
|
1401
|
+
identitySource: z4.literal("user-confirmed"),
|
|
1402
|
+
backup: z4.string().min(1),
|
|
1403
|
+
createdAt: z4.string().datetime()
|
|
1333
1404
|
}).strict();
|
|
1334
|
-
var registrySchema =
|
|
1405
|
+
var registrySchema = z4.object({ version: z4.literal(1), targets: z4.array(targetSchema).max(100) }).strict();
|
|
1335
1406
|
function targetIdentity(values, key = "remember") {
|
|
1336
1407
|
const result = targetSchema.pick({ target: true, label: true, last4: true }).safeParse({
|
|
1337
1408
|
target: values[key],
|
|
@@ -1345,12 +1416,12 @@ function targetIdentity(values, key = "remember") {
|
|
|
1345
1416
|
return result.data;
|
|
1346
1417
|
}
|
|
1347
1418
|
function registryPath() {
|
|
1348
|
-
return
|
|
1419
|
+
return join6(walletHome(), "targets", "registry.json");
|
|
1349
1420
|
}
|
|
1350
1421
|
async function walletTargets() {
|
|
1351
1422
|
const path = registryPath();
|
|
1352
1423
|
try {
|
|
1353
|
-
for (const parent of [walletHome(),
|
|
1424
|
+
for (const parent of [walletHome(), join6(walletHome(), "targets")]) {
|
|
1354
1425
|
if (!(await lstat2(parent)).isDirectory())
|
|
1355
1426
|
throw new InputError("Invalid Wallet target storage.");
|
|
1356
1427
|
}
|
|
@@ -1373,7 +1444,7 @@ function targetChoices(targets) {
|
|
|
1373
1444
|
}));
|
|
1374
1445
|
}
|
|
1375
1446
|
async function withWalletTargets(action) {
|
|
1376
|
-
const directory = await privateDirectory(
|
|
1447
|
+
const directory = await privateDirectory(join6(await privateDirectory(walletHome()), "targets"));
|
|
1377
1448
|
const release = await lockfile.lock(directory, { realpath: false, retries: 0 });
|
|
1378
1449
|
try {
|
|
1379
1450
|
return await action(await walletTargets(), async (targets) => {
|
|
@@ -1402,9 +1473,9 @@ var WalletError = class extends Error {
|
|
|
1402
1473
|
};
|
|
1403
1474
|
function installation() {
|
|
1404
1475
|
const platform = `${process.platform}-${process.arch}`;
|
|
1405
|
-
const cache = process.env.MAKEABLES_WALLET_CACHE ??
|
|
1476
|
+
const cache = process.env.MAKEABLES_WALLET_CACHE ?? join7(homedir3(), "Library", "Caches", "Makeables", "wallet");
|
|
1406
1477
|
const root = resolve6(cache, `${wallet_release_default.version}-${platform}`);
|
|
1407
|
-
return { platform, root, executable:
|
|
1478
|
+
return { platform, root, executable: join7(root, "makeables-wallet", "makeables-wallet") };
|
|
1408
1479
|
}
|
|
1409
1480
|
async function execute(command, args, progress) {
|
|
1410
1481
|
return new Promise((accept, reject) => {
|
|
@@ -1535,25 +1606,25 @@ async function setupWallet(progress) {
|
|
|
1535
1606
|
const bytes = Buffer.concat(chunks);
|
|
1536
1607
|
if (createHash3("sha256").update(bytes).digest("hex") !== artifact.sha256)
|
|
1537
1608
|
throw new Error("Wallet companion checksum did not match. Nothing was installed.");
|
|
1538
|
-
await
|
|
1539
|
-
const stage = await
|
|
1609
|
+
await mkdir4(dirname4(root), { recursive: true, mode: 448 });
|
|
1610
|
+
const stage = await mkdtemp3(join7(dirname4(root), ".install-"));
|
|
1540
1611
|
try {
|
|
1541
|
-
const archive =
|
|
1542
|
-
await
|
|
1612
|
+
const archive = join7(stage, "companion.tar.gz");
|
|
1613
|
+
await writeFile4(archive, bytes, { mode: 384, flag: "wx" });
|
|
1543
1614
|
const members = (await execute("tar", ["-tzf", archive])).trim().split("\n");
|
|
1544
1615
|
if (members.some(
|
|
1545
1616
|
(path) => !path.startsWith("makeables-wallet/") || path.split("/").includes("..")
|
|
1546
1617
|
))
|
|
1547
1618
|
throw new Error("Unexpected companion archive contents.");
|
|
1548
1619
|
await execute("tar", ["-xzf", archive, "-C", stage]);
|
|
1549
|
-
await
|
|
1550
|
-
await chmod2(
|
|
1551
|
-
const info = await invoke(
|
|
1620
|
+
await rm5(archive);
|
|
1621
|
+
await chmod2(join7(stage, "makeables-wallet", "makeables-wallet"), 448);
|
|
1622
|
+
const info = await invoke(join7(stage, "makeables-wallet", "makeables-wallet"), ["version"]);
|
|
1552
1623
|
if (info.version !== wallet_release_default.version || info.protocol !== wallet_release_default.protocol)
|
|
1553
1624
|
throw new Error("Companion version does not match the installed CLI.");
|
|
1554
|
-
await
|
|
1625
|
+
await rename4(stage, root);
|
|
1555
1626
|
} finally {
|
|
1556
|
-
await
|
|
1627
|
+
await rm5(stage, { recursive: true, force: true });
|
|
1557
1628
|
}
|
|
1558
1629
|
return walletDoctor();
|
|
1559
1630
|
}
|
|
@@ -1571,7 +1642,7 @@ async function walletArtwork(file, side2 = "front") {
|
|
|
1571
1642
|
if (!path) throw new InputError("This package has no back artwork.");
|
|
1572
1643
|
source = Buffer.from(pkg.files[path]);
|
|
1573
1644
|
} else {
|
|
1574
|
-
source = await
|
|
1645
|
+
source = await readFile6(input);
|
|
1575
1646
|
if (extname3(input).toLowerCase() === ".svg") {
|
|
1576
1647
|
const result = checkSubmittedSvg(source.toString("utf8"));
|
|
1577
1648
|
if (!result.ok) throw new InputError(result.error);
|
|
@@ -1588,15 +1659,15 @@ async function preparedArtwork(directory) {
|
|
|
1588
1659
|
"cardBackgroundCombined@2x.png",
|
|
1589
1660
|
"cardBackgroundCombined.pdf"
|
|
1590
1661
|
]) {
|
|
1591
|
-
const file =
|
|
1662
|
+
const file = join7(path, name);
|
|
1592
1663
|
const info = await lstat3(file);
|
|
1593
1664
|
if (!info.isFile() || info.size === 0 || info.size > 16 * 1024 * 1024)
|
|
1594
1665
|
throw new InputError(`Invalid prepared asset: ${name}. Run wallet prep again.`);
|
|
1595
1666
|
const signature = Buffer.from(name.endsWith(".pdf") ? "%PDF-" : "\x89PNG\r\n\n", "latin1");
|
|
1596
|
-
if (!(await
|
|
1667
|
+
if (!(await readFile6(file)).subarray(0, signature.length).equals(signature))
|
|
1597
1668
|
throw new InputError(`Invalid prepared asset: ${name}. Run wallet prep again.`);
|
|
1598
1669
|
}
|
|
1599
|
-
return { path, preview:
|
|
1670
|
+
return { path, preview: join7(path, "cardBackgroundCombined@3x.png") };
|
|
1600
1671
|
}
|
|
1601
1672
|
async function walletCommand(command, values, progress) {
|
|
1602
1673
|
if (values.remember) {
|
|
@@ -1630,8 +1701,8 @@ async function walletCommand(command, values, progress) {
|
|
|
1630
1701
|
return runWalletCommand(command, values, progress);
|
|
1631
1702
|
}
|
|
1632
1703
|
async function runWalletCommand(command, values, progress) {
|
|
1633
|
-
const
|
|
1634
|
-
const elapsed = () => Math.round(performance.now() -
|
|
1704
|
+
const started2 = performance.now();
|
|
1705
|
+
const elapsed = () => Math.round(performance.now() - started2);
|
|
1635
1706
|
let latestPhase;
|
|
1636
1707
|
const report = (phase, message) => {
|
|
1637
1708
|
latestPhase = phase;
|
|
@@ -1806,7 +1877,7 @@ async function runWalletCommand(command, values, progress) {
|
|
|
1806
1877
|
}
|
|
1807
1878
|
}
|
|
1808
1879
|
if (command === "wallet prep" || applying || installing) {
|
|
1809
|
-
const work = await
|
|
1880
|
+
const work = await mkdtemp3(join7(tmpdir2(), "makeables-wallet-"));
|
|
1810
1881
|
let phase = "prepare";
|
|
1811
1882
|
let deviceWriteAttempted = false;
|
|
1812
1883
|
let persistent;
|
|
@@ -1819,18 +1890,18 @@ async function runWalletCommand(command, values, progress) {
|
|
|
1819
1890
|
} else {
|
|
1820
1891
|
report("prepare", "Preparing the complete card artwork.");
|
|
1821
1892
|
const png = await walletArtwork(need("file"), values.side === "back" ? "back" : "front");
|
|
1822
|
-
const raster =
|
|
1823
|
-
await
|
|
1893
|
+
const raster = join7(work, "artwork.png");
|
|
1894
|
+
await writeFile4(raster, png, { mode: 384 });
|
|
1824
1895
|
if (command === "wallet prep") {
|
|
1825
1896
|
target = resolve6(need("out"));
|
|
1826
1897
|
} else if (installing && !applying) {
|
|
1827
1898
|
const root = await privateDirectory(
|
|
1828
|
-
|
|
1899
|
+
join7(await privateDirectory(walletHome()), "prepared")
|
|
1829
1900
|
);
|
|
1830
|
-
persistent = await
|
|
1831
|
-
target =
|
|
1901
|
+
persistent = await mkdtemp3(join7(root, "install-"));
|
|
1902
|
+
target = join7(persistent, "assets");
|
|
1832
1903
|
} else {
|
|
1833
|
-
target =
|
|
1904
|
+
target = join7(work, "assets");
|
|
1834
1905
|
}
|
|
1835
1906
|
prepared = await invoke(
|
|
1836
1907
|
executable,
|
|
@@ -1916,8 +1987,8 @@ async function runWalletCommand(command, values, progress) {
|
|
|
1916
1987
|
}
|
|
1917
1988
|
throw error;
|
|
1918
1989
|
} finally {
|
|
1919
|
-
await
|
|
1920
|
-
if (persistent && phase === "prepare") await
|
|
1990
|
+
await rm5(work, { recursive: true, force: true });
|
|
1991
|
+
if (persistent && phase === "prepare") await rm5(persistent, { recursive: true, force: true });
|
|
1921
1992
|
}
|
|
1922
1993
|
}
|
|
1923
1994
|
throw new InputError("Unknown Wallet command.");
|
|
@@ -1968,6 +2039,14 @@ function walletSummary(result) {
|
|
|
1968
2039
|
|
|
1969
2040
|
// src/cli.ts
|
|
1970
2041
|
var commands = [
|
|
2042
|
+
"kit new --name <title> --out <kit.json>",
|
|
2043
|
+
"kit extract --file <artwork-sheet.png> --out <new-directory> [--params <json|@file>]",
|
|
2044
|
+
"kit add --file <kit.json> --ref <design|package> --id <piece-id>",
|
|
2045
|
+
"kit sheet --file <kit.json> --ref <sheet.json> --id <sheet-id>",
|
|
2046
|
+
"kit inspect --file <kit.json|kit.makeables.zip>",
|
|
2047
|
+
"kit render --file <kit.json|kit.makeables.zip> --out <mockup.png> [--overwrite]",
|
|
2048
|
+
"kit export --file <kit.json|kit.makeables.zip> --out <kit.makeables.zip> [--overwrite]",
|
|
2049
|
+
"kit import --file <kit.makeables.zip> --out <new-directory>",
|
|
1971
2050
|
"designs list",
|
|
1972
2051
|
"new card --design <slug> | --style <id> --out <new-file> [--dry-run]",
|
|
1973
2052
|
"material set --file <design.json> --params <json|@file>",
|
|
@@ -1975,7 +2054,7 @@ var commands = [
|
|
|
1975
2054
|
"new badge --style <id> --out <new-file> [--dry-run]",
|
|
1976
2055
|
"new sticker --style <id> --out <new-file> [--dry-run]",
|
|
1977
2056
|
"layer list --file <design.json>",
|
|
1978
|
-
"layer add --file <design.json> --sticker <petdex:slug|path> [--side front|back]",
|
|
2057
|
+
"layer add --file <design.json> --sticker <petdex:slug|path> [--side front|back] [--relative-to document|cwd]",
|
|
1979
2058
|
"layer set --file <design.json> --id <layer> [--side front|back] [--x --y --rotation] [--params <json|@file>]",
|
|
1980
2059
|
"layer remove --file <design.json> --id <layer> [--side front|back]",
|
|
1981
2060
|
"cut --file <sticker.json> [--out <cut.svg>] [--overwrite]",
|
|
@@ -1984,7 +2063,7 @@ var commands = [
|
|
|
1984
2063
|
"sheet pack --file <sheet.json>",
|
|
1985
2064
|
"sheet check --file <sheet.json>",
|
|
1986
2065
|
"sheet export --file <sheet.json> --out <sheet.pdf> [--preview <proof.png>] [--overwrite]",
|
|
1987
|
-
"render --file <design.json|design.makeables.json> --out <png> [--side front|back] [--photo <image>] [--name --role --number] [--overwrite]",
|
|
2066
|
+
"render --file <design.json|design.makeables.json> --out <png> [--appearance flat|material] [--side front|back] [--photo <image>] [--name --role --number] [--overwrite]",
|
|
1988
2067
|
"validate --file <design.json|artwork.svg> [--out <preview.png>]",
|
|
1989
2068
|
"new card --file <artwork.svg|image.png> --out <card.json> [--name <title>]",
|
|
1990
2069
|
...imageTools.map(
|
|
@@ -2027,9 +2106,22 @@ var commands = [
|
|
|
2027
2106
|
"schema"
|
|
2028
2107
|
];
|
|
2029
2108
|
var nextSteps = ["makeables skills get core", "makeables doctor"];
|
|
2109
|
+
var compact = false;
|
|
2110
|
+
var stdoutClosed = false;
|
|
2111
|
+
var traceFile;
|
|
2112
|
+
var traceCommand = "invalid";
|
|
2113
|
+
var traceCode;
|
|
2114
|
+
var elapsedStartup = process.uptime() * 1e3;
|
|
2115
|
+
var startedAt = new Date(Date.now() - elapsedStartup).toISOString();
|
|
2116
|
+
var started = performance.now() - elapsedStartup;
|
|
2117
|
+
process.stdout.on("error", (error) => {
|
|
2118
|
+
if (error.code === "EPIPE") stdoutClosed = true;
|
|
2119
|
+
else throw error;
|
|
2120
|
+
});
|
|
2030
2121
|
function emit(data, next, human, json) {
|
|
2122
|
+
if (stdoutClosed) return;
|
|
2031
2123
|
process.stdout.write(
|
|
2032
|
-
json ? `${JSON.stringify({ ok: true, version, data, nextSteps: next })}
|
|
2124
|
+
json ? `${JSON.stringify({ ok: true, version, data: compact ? compactReceipt(data) : data, nextSteps: next })}
|
|
2033
2125
|
` : `${human}
|
|
2034
2126
|
`
|
|
2035
2127
|
);
|
|
@@ -2042,7 +2134,7 @@ async function newPath(file) {
|
|
|
2042
2134
|
} catch (error) {
|
|
2043
2135
|
if (error.code !== "ENOENT") throw error;
|
|
2044
2136
|
}
|
|
2045
|
-
await access(
|
|
2137
|
+
await access(dirname5(path), constants4.W_OK);
|
|
2046
2138
|
return path;
|
|
2047
2139
|
}
|
|
2048
2140
|
async function run() {
|
|
@@ -2100,10 +2192,16 @@ async function run() {
|
|
|
2100
2192
|
type: { type: "string" },
|
|
2101
2193
|
size: { type: "string" },
|
|
2102
2194
|
"width-mm": { type: "string" },
|
|
2103
|
-
preview: { type: "string" }
|
|
2195
|
+
preview: { type: "string" },
|
|
2196
|
+
appearance: { type: "string" },
|
|
2197
|
+
"relative-to": { type: "string" },
|
|
2198
|
+
compact: { type: "boolean" },
|
|
2199
|
+
trace: { type: "string" }
|
|
2104
2200
|
}
|
|
2105
2201
|
});
|
|
2106
2202
|
const json = machineOutput(Boolean(values.json));
|
|
2203
|
+
compact = Boolean(values.compact);
|
|
2204
|
+
traceFile = values.trace;
|
|
2107
2205
|
if (values.version) {
|
|
2108
2206
|
emit({ version }, nextSteps, version, json);
|
|
2109
2207
|
return;
|
|
@@ -2116,7 +2214,7 @@ async function run() {
|
|
|
2116
2214
|
emit(
|
|
2117
2215
|
{
|
|
2118
2216
|
commands: helpCommands,
|
|
2119
|
-
flags: ["--json", "--help", "--version"],
|
|
2217
|
+
flags: ["--json", "--help", "--version", "--compact", "--trace <local.jsonl>"],
|
|
2120
2218
|
exitCodes: { success: 0, invalidInput: 2, systemFailure: 1 }
|
|
2121
2219
|
},
|
|
2122
2220
|
nextSteps,
|
|
@@ -2132,6 +2230,14 @@ From a built checkout: npm run studio -- <command>`,
|
|
|
2132
2230
|
}
|
|
2133
2231
|
const command = ["skills get", "studio call"].includes(positionals.slice(0, 2).join(" ")) ? positionals.slice(0, 2).join(" ") : positionals.join(" ");
|
|
2134
2232
|
const allowed = {
|
|
2233
|
+
"kit new": ["name", "out"],
|
|
2234
|
+
"kit extract": ["file", "out", "params"],
|
|
2235
|
+
"kit add": ["file", "ref", "id"],
|
|
2236
|
+
"kit sheet": ["file", "ref", "id"],
|
|
2237
|
+
"kit inspect": ["file"],
|
|
2238
|
+
"kit render": ["file", "out", "overwrite"],
|
|
2239
|
+
"kit export": ["file", "out", "overwrite"],
|
|
2240
|
+
"kit import": ["file", "out"],
|
|
2135
2241
|
...Object.fromEntries(
|
|
2136
2242
|
imageTools.map((tool) => [`image ${tool}`, ["file", "out", "params", "ref"]])
|
|
2137
2243
|
),
|
|
@@ -2210,7 +2316,7 @@ From a built checkout: npm run studio -- <command>`,
|
|
|
2210
2316
|
"material map": ["file", "id", "ref"],
|
|
2211
2317
|
"new badge": ["style", "out", "dry-run"],
|
|
2212
2318
|
"layer list": ["file"],
|
|
2213
|
-
"layer add": ["file", "sticker", "side"],
|
|
2319
|
+
"layer add": ["file", "sticker", "side", "relative-to"],
|
|
2214
2320
|
"layer set": ["file", "id", "x", "y", "rotation", "side", "params"],
|
|
2215
2321
|
"layer remove": ["file", "id", "side"],
|
|
2216
2322
|
cut: ["file", "out", "overwrite"],
|
|
@@ -2219,17 +2325,90 @@ From a built checkout: npm run studio -- <command>`,
|
|
|
2219
2325
|
"sheet pack": ["file"],
|
|
2220
2326
|
"sheet check": ["file"],
|
|
2221
2327
|
"sheet export": ["file", "out", "preview", "overwrite"],
|
|
2222
|
-
render: ["file", "out", "overwrite", "side", "photo", "name", "role", "number"],
|
|
2328
|
+
render: ["file", "out", "overwrite", "side", "photo", "name", "role", "number", "appearance"],
|
|
2223
2329
|
validate: ["file", "out"]
|
|
2224
2330
|
};
|
|
2225
2331
|
if (!(command in allowed)) throw new InputError(`Unknown command: ${command}`);
|
|
2332
|
+
traceCommand = command;
|
|
2226
2333
|
for (const flag of Object.keys(values))
|
|
2227
|
-
if (!["json", "help", "version", ...allowed[command]].includes(flag))
|
|
2334
|
+
if (!["json", "help", "version", "compact", "trace", ...allowed[command]].includes(flag))
|
|
2228
2335
|
throw new InputError(`--${flag} is not supported by ${command}`);
|
|
2229
2336
|
const progress = (data) => process.stderr.write(`${JSON.stringify(data)}
|
|
2230
2337
|
`);
|
|
2231
2338
|
if (values.product && !["card", "badge", "sticker"].includes(values.product))
|
|
2232
2339
|
throw new InputError("--product must be card, badge or sticker.");
|
|
2340
|
+
if (command.startsWith("kit ")) {
|
|
2341
|
+
const required = (value, flag) => {
|
|
2342
|
+
if (!value) throw new InputError(`Supply --${flag}.`);
|
|
2343
|
+
return value;
|
|
2344
|
+
};
|
|
2345
|
+
let result2;
|
|
2346
|
+
if (command === "kit extract") {
|
|
2347
|
+
result2 = await extractKit(
|
|
2348
|
+
required(values.file, "file"),
|
|
2349
|
+
required(values.out, "out"),
|
|
2350
|
+
values.params ? await readParams(values.params) : {}
|
|
2351
|
+
);
|
|
2352
|
+
} else if (command === "kit new") {
|
|
2353
|
+
const path2 = await newPath(required(values.out, "out"));
|
|
2354
|
+
const kit = kitDocumentSchema.parse({
|
|
2355
|
+
format: "makeables-kit",
|
|
2356
|
+
version: 1,
|
|
2357
|
+
name: required(values.name, "name"),
|
|
2358
|
+
items: [],
|
|
2359
|
+
sheets: []
|
|
2360
|
+
});
|
|
2361
|
+
await writeAtomic(path2, JSON.stringify(kit, null, 2), true);
|
|
2362
|
+
result2 = { path: path2, kit };
|
|
2363
|
+
} else if (command === "kit add" || command === "kit sheet") {
|
|
2364
|
+
const path2 = required(values.file, "file");
|
|
2365
|
+
const kit = await addKitMember(
|
|
2366
|
+
path2,
|
|
2367
|
+
required(values.ref, "ref"),
|
|
2368
|
+
required(values.id, "id"),
|
|
2369
|
+
command === "kit add" ? "items" : "sheets"
|
|
2370
|
+
);
|
|
2371
|
+
await writeAtomic(path2, JSON.stringify(kit, null, 2));
|
|
2372
|
+
result2 = { path: resolve7(path2), items: kit.items.length, sheets: kit.sheets.length };
|
|
2373
|
+
} else if (command === "kit import") {
|
|
2374
|
+
result2 = await importKit(required(values.file, "file"), required(values.out, "out"));
|
|
2375
|
+
} else if (command === "kit inspect" && !required(values.file, "file").toLowerCase().endsWith(".zip")) {
|
|
2376
|
+
const value = JSON.parse(await readFile7(required(values.file, "file"), "utf8"));
|
|
2377
|
+
if (value.format === "makeables-kit") result2 = await readKit(required(values.file, "file"));
|
|
2378
|
+
else {
|
|
2379
|
+
const pkg = await readSubmission(required(values.file, "file"));
|
|
2380
|
+
if (pkg.manifest.product !== "kit")
|
|
2381
|
+
throw new InputError("Choose a kit document or package.");
|
|
2382
|
+
result2 = pkg.manifest;
|
|
2383
|
+
}
|
|
2384
|
+
} else {
|
|
2385
|
+
const { pkg, bytes } = await exportKit(required(values.file, "file"));
|
|
2386
|
+
if (command === "kit inspect") result2 = pkg.manifest;
|
|
2387
|
+
else {
|
|
2388
|
+
const out = resolve7(required(values.out, "out"));
|
|
2389
|
+
const rendering = command === "kit render";
|
|
2390
|
+
if (!out.endsWith(rendering ? ".png" : ".zip"))
|
|
2391
|
+
throw new InputError(rendering ? "--out must end in .png." : "--out must end in .zip.");
|
|
2392
|
+
const handle2 = await open4(
|
|
2393
|
+
values.overwrite ? out : await newPath(out),
|
|
2394
|
+
values.overwrite ? "w" : "wx"
|
|
2395
|
+
);
|
|
2396
|
+
try {
|
|
2397
|
+
await handle2.writeFile(rendering ? pkg.files[pkg.manifest.front] : bytes);
|
|
2398
|
+
} finally {
|
|
2399
|
+
await handle2.close();
|
|
2400
|
+
}
|
|
2401
|
+
result2 = {
|
|
2402
|
+
path: out,
|
|
2403
|
+
product: "kit",
|
|
2404
|
+
title: pkg.manifest.title,
|
|
2405
|
+
bytes: rendering ? pkg.files[pkg.manifest.front].length : bytes.length
|
|
2406
|
+
};
|
|
2407
|
+
}
|
|
2408
|
+
}
|
|
2409
|
+
emit(result2, ["makeables skills get kits"], "Kit ready.", json);
|
|
2410
|
+
return;
|
|
2411
|
+
}
|
|
2233
2412
|
if (command.startsWith("wallet ")) {
|
|
2234
2413
|
const walletProgress = (data) => {
|
|
2235
2414
|
if (json) return progress(data);
|
|
@@ -2395,7 +2574,7 @@ Run makeables skills get core for setup.`,
|
|
|
2395
2574
|
}
|
|
2396
2575
|
if (command === "mcp") {
|
|
2397
2576
|
try {
|
|
2398
|
-
const { runMcp } = await import("./mcp-
|
|
2577
|
+
const { runMcp } = await import("./mcp-VS5XYXVM.js");
|
|
2399
2578
|
await runMcp(values.root ?? process.cwd());
|
|
2400
2579
|
} catch (error) {
|
|
2401
2580
|
process.stderr.write(
|
|
@@ -2454,7 +2633,7 @@ Keep this process running while you design.`,
|
|
|
2454
2633
|
const info = await lstat4(path2);
|
|
2455
2634
|
if (!info.isFile() || info.size > 12e6)
|
|
2456
2635
|
throw new InputError("Expected a params file below 12 MB.");
|
|
2457
|
-
params = JSON.parse(await
|
|
2636
|
+
params = JSON.parse(await readFile7(path2, "utf8"));
|
|
2458
2637
|
} else if (values.params) params = JSON.parse(values.params);
|
|
2459
2638
|
}
|
|
2460
2639
|
const result2 = await studioRequest(
|
|
@@ -2537,7 +2716,7 @@ ${styles.map((entry) => `${column(entry.id ?? "", 25)}${entry.name}`).join("\n")
|
|
|
2537
2716
|
const path2 = values.out ? await newPath(values.out) : null;
|
|
2538
2717
|
const dryRun = Boolean(values["dry-run"]);
|
|
2539
2718
|
if (path2 && !dryRun) {
|
|
2540
|
-
const handle2 = await
|
|
2719
|
+
const handle2 = await open4(path2, "wx", 384);
|
|
2541
2720
|
try {
|
|
2542
2721
|
await handle2.writeFile(`${JSON.stringify(design, null, 2)}
|
|
2543
2722
|
`);
|
|
@@ -2561,13 +2740,13 @@ ${dryRun ? "Dry run. No files changed." : "2 faces \xB7 editable layers"}`,
|
|
|
2561
2740
|
}
|
|
2562
2741
|
if (!values.file) throw new InputError("Supply --file to validate a document.");
|
|
2563
2742
|
const path = resolve7(values.file);
|
|
2564
|
-
const handle = await
|
|
2743
|
+
const handle = await open4(path, "r");
|
|
2565
2744
|
let input;
|
|
2566
2745
|
try {
|
|
2567
2746
|
const info = await handle.stat();
|
|
2568
2747
|
if (!info.isFile() || info.size > 2 * 1024 * 1024)
|
|
2569
2748
|
throw new InputError("Expected a JSON file smaller than 2 MB.");
|
|
2570
|
-
input = JSON.parse(await
|
|
2749
|
+
input = JSON.parse(await readFile7(handle, "utf8"));
|
|
2571
2750
|
} finally {
|
|
2572
2751
|
await handle.close();
|
|
2573
2752
|
}
|
|
@@ -2586,7 +2765,7 @@ Schema and layout constraints passed.`,
|
|
|
2586
2765
|
}
|
|
2587
2766
|
async function readParams(value) {
|
|
2588
2767
|
return JSON.parse(
|
|
2589
|
-
value.startsWith("@") ? await
|
|
2768
|
+
value.startsWith("@") ? await readFile7(resolve7(value.slice(1)), "utf8") : value
|
|
2590
2769
|
);
|
|
2591
2770
|
}
|
|
2592
2771
|
async function cardCommand(command, values, json) {
|
|
@@ -2667,7 +2846,7 @@ ${path ?? `No file written. Add --out ${kind}.json.`}`,
|
|
|
2667
2846
|
const path = resolve7(need(file, "file"));
|
|
2668
2847
|
const design = await readBadge(path);
|
|
2669
2848
|
const raw = need(values.params, "params");
|
|
2670
|
-
const input = JSON.parse(raw.startsWith("@") ? await
|
|
2849
|
+
const input = JSON.parse(raw.startsWith("@") ? await readFile7(raw.slice(1), "utf8") : raw);
|
|
2671
2850
|
if (design.product === "sticker") {
|
|
2672
2851
|
const patch2 = stickerMaterialSchema.partial().strict().safeParse(input);
|
|
2673
2852
|
if (!patch2.success)
|
|
@@ -2747,11 +2926,13 @@ ${path ?? `No file written. Add --out ${kind}.json.`}`,
|
|
|
2747
2926
|
if (command === "layer add") {
|
|
2748
2927
|
const path = need(file, "file");
|
|
2749
2928
|
const face = side(values.side);
|
|
2929
|
+
if (values["relative-to"] && !["cwd", "document"].includes(String(values["relative-to"])))
|
|
2930
|
+
throw new InputError("--relative-to must be document or cwd.");
|
|
2750
2931
|
const design = await addBadgeSticker(
|
|
2751
2932
|
await readBadge(path),
|
|
2752
2933
|
face,
|
|
2753
2934
|
need(values.sticker, "sticker"),
|
|
2754
|
-
|
|
2935
|
+
values["relative-to"] === "cwd" ? process.cwd() : dirname5(resolve7(path))
|
|
2755
2936
|
);
|
|
2756
2937
|
await writeAtomic(path, `${JSON.stringify(design, null, 2)}
|
|
2757
2938
|
`);
|
|
@@ -2766,7 +2947,7 @@ ${path ?? `No file written. Add --out ${kind}.json.`}`,
|
|
|
2766
2947
|
}
|
|
2767
2948
|
if (command.startsWith("sheet ")) {
|
|
2768
2949
|
const write = async (target, bytes) => {
|
|
2769
|
-
const handle = await
|
|
2950
|
+
const handle = await open4(target, values.overwrite ? "w" : "wx", 420);
|
|
2770
2951
|
try {
|
|
2771
2952
|
await handle.writeFile(bytes);
|
|
2772
2953
|
} finally {
|
|
@@ -2870,7 +3051,7 @@ ${path ?? `No file written. Add --out ${kind}.json.`}`,
|
|
|
2870
3051
|
const target = out && (values.overwrite ? out : await newPath(out));
|
|
2871
3052
|
const { cut, svg } = await stickerPrintCut(design);
|
|
2872
3053
|
if (target) {
|
|
2873
|
-
const handle = await
|
|
3054
|
+
const handle = await open4(target, values.overwrite ? "w" : "wx", 420);
|
|
2874
3055
|
try {
|
|
2875
3056
|
await handle.writeFile(svg);
|
|
2876
3057
|
} finally {
|
|
@@ -2902,6 +3083,9 @@ ${path ?? `No file written. Add --out ${kind}.json.`}`,
|
|
|
2902
3083
|
return true;
|
|
2903
3084
|
}
|
|
2904
3085
|
if (command === "render") {
|
|
3086
|
+
if (values.appearance && !["flat", "material"].includes(String(values.appearance)))
|
|
3087
|
+
throw new InputError("--appearance must be flat or material.");
|
|
3088
|
+
const appearance = values.appearance;
|
|
2905
3089
|
const path = need(file, "file");
|
|
2906
3090
|
const out = resolve7(need(values.out, "out"));
|
|
2907
3091
|
if (!out.endsWith(".png")) throw new InputError("--out must end in .png.");
|
|
@@ -2910,7 +3094,7 @@ ${path ?? `No file written. Add --out ${kind}.json.`}`,
|
|
|
2910
3094
|
if (number !== void 0 && !Number.isInteger(number))
|
|
2911
3095
|
throw new InputError("--number must be an integer.");
|
|
2912
3096
|
const face = side(values.side);
|
|
2913
|
-
const value = JSON.parse(await
|
|
3097
|
+
const value = JSON.parse(await readFile7(resolve7(path), "utf8"));
|
|
2914
3098
|
const pkg = value?.format === "makeables-package" ? await readPackageJson(value) : void 0;
|
|
2915
3099
|
if (pkg && ["photo", "name", "role", "number"].some((key) => values[key] !== void 0))
|
|
2916
3100
|
throw new InputError(
|
|
@@ -2922,11 +3106,12 @@ ${path ?? `No file written. Add --out ${kind}.json.`}`,
|
|
|
2922
3106
|
if (pkg && !previewPath) throw new InputError("This package has no back preview.");
|
|
2923
3107
|
const card = design.product === "card" || design.product === "sticker";
|
|
2924
3108
|
const { png, person, samplePortrait } = pkg && previewPath ? {
|
|
2925
|
-
png: Buffer.from(pkg.files[previewPath]),
|
|
3109
|
+
png: appearance && draft ? await renderDraftFace(draft, face, appearance) : Buffer.from(pkg.files[previewPath]),
|
|
2926
3110
|
person: draft?.participant,
|
|
2927
3111
|
samplePortrait: false
|
|
2928
3112
|
} : await renderBadge(design, {
|
|
2929
3113
|
face,
|
|
3114
|
+
appearance,
|
|
2930
3115
|
photo: typeof values.photo === "string" ? values.photo : void 0,
|
|
2931
3116
|
person: {
|
|
2932
3117
|
...typeof values.name === "string" ? { name: values.name } : {},
|
|
@@ -2934,7 +3119,7 @@ ${path ?? `No file written. Add --out ${kind}.json.`}`,
|
|
|
2934
3119
|
...number === void 0 ? {} : { number }
|
|
2935
3120
|
}
|
|
2936
3121
|
});
|
|
2937
|
-
const handle = await
|
|
3122
|
+
const handle = await open4(target, values.overwrite ? "w" : "wx", 420);
|
|
2938
3123
|
try {
|
|
2939
3124
|
await handle.writeFile(png);
|
|
2940
3125
|
} finally {
|
|
@@ -2949,12 +3134,13 @@ ${path ?? `No file written. Add --out ${kind}.json.`}`,
|
|
|
2949
3134
|
side: face,
|
|
2950
3135
|
width,
|
|
2951
3136
|
height,
|
|
3137
|
+
appearance: appearance ?? (pkg && design.product === "sticker" ? "material" : "flat"),
|
|
2952
3138
|
...card ? {} : { person, samplePortrait }
|
|
2953
3139
|
},
|
|
2954
3140
|
[
|
|
2955
3141
|
`Open ${target} to review.`,
|
|
2956
3142
|
...portrait ? ["Pass --photo <image> to use the person's portrait."] : [],
|
|
2957
|
-
...pkg && design.product === "sticker" ? ["The transparent PNG includes the sticker material."] : ["This is flat artwork. Use an available integrated preview for live materials."]
|
|
3143
|
+
...(appearance === "material" || pkg && !appearance) && design.product === "sticker" ? ["The transparent PNG includes the sticker material."] : ["This is flat artwork. Use an available integrated preview for live materials."]
|
|
2958
3144
|
],
|
|
2959
3145
|
`${style("Rendered", "1", !json)} ${face} ${target}${portrait ? "\n note: sample portrait; pass --photo" : ""}`,
|
|
2960
3146
|
json
|
|
@@ -2974,7 +3160,7 @@ ${path ?? `No file written. Add --out ${kind}.json.`}`,
|
|
|
2974
3160
|
if (command === "validate") {
|
|
2975
3161
|
const path = resolve7(need(file, "file"));
|
|
2976
3162
|
if (extname4(path).toLowerCase() === ".svg") {
|
|
2977
|
-
const source = await
|
|
3163
|
+
const source = await readFile7(path, "utf8");
|
|
2978
3164
|
const result2 = checkSubmittedSvg(source);
|
|
2979
3165
|
if (!result2.ok) throw new InputError(result2.error);
|
|
2980
3166
|
const preview = values.out ? await newPath(need(values.out, "out")) : void 0;
|
|
@@ -2995,7 +3181,7 @@ ${path ?? `No file written. Add --out ${kind}.json.`}`,
|
|
|
2995
3181
|
return true;
|
|
2996
3182
|
}
|
|
2997
3183
|
if (values.out) throw new InputError("--out is only used to preview SVG validation.");
|
|
2998
|
-
const input = JSON.parse(await
|
|
3184
|
+
const input = JSON.parse(await readFile7(path, "utf8"));
|
|
2999
3185
|
const result = badgeDesignSchema.safeParse(input);
|
|
3000
3186
|
if (!result.success)
|
|
3001
3187
|
throw new InputError(
|
|
@@ -3013,7 +3199,7 @@ ${path ?? `No file written. Add --out ${kind}.json.`}`,
|
|
|
3013
3199
|
return false;
|
|
3014
3200
|
}
|
|
3015
3201
|
run().catch((error) => {
|
|
3016
|
-
const userError = error instanceof InputError || error instanceof WalletError && error.details.code === "INVALID_INPUT" || error instanceof SyntaxError || [
|
|
3202
|
+
const userError = error instanceof InputError || error instanceof ZodError || error instanceof WalletError && error.details.code === "INVALID_INPUT" || error instanceof SyntaxError || [
|
|
3017
3203
|
"ERR_PARSE_ARGS_UNKNOWN_OPTION",
|
|
3018
3204
|
"ERR_PARSE_ARGS_INVALID_OPTION_VALUE",
|
|
3019
3205
|
"ERR_PARSE_ARGS_UNEXPECTED_POSITIONAL",
|
|
@@ -3032,10 +3218,28 @@ run().catch((error) => {
|
|
|
3032
3218
|
},
|
|
3033
3219
|
nextSteps
|
|
3034
3220
|
};
|
|
3035
|
-
|
|
3221
|
+
traceCode = payload.error.code;
|
|
3222
|
+
if (!stdoutClosed && machineOutput(process.argv.includes("--json")))
|
|
3036
3223
|
process.stdout.write(`${JSON.stringify(payload)}
|
|
3037
3224
|
`);
|
|
3038
3225
|
process.stderr.write(`makeables: ${error.message}
|
|
3039
3226
|
`);
|
|
3040
3227
|
process.exitCode = exitCode;
|
|
3228
|
+
}).finally(async () => {
|
|
3229
|
+
if (!traceFile) return;
|
|
3230
|
+
try {
|
|
3231
|
+
const supplied = process.env.MAKEABLES_TRACE_SESSION;
|
|
3232
|
+
await appendTrace(resolve7(traceFile), {
|
|
3233
|
+
sessionId: supplied && /^[a-zA-Z0-9_-]{1,80}$/.test(supplied) ? supplied : randomUUID3(),
|
|
3234
|
+
version,
|
|
3235
|
+
command: traceCommand,
|
|
3236
|
+
startedAt,
|
|
3237
|
+
endedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3238
|
+
durationMs: Math.round(performance.now() - started),
|
|
3239
|
+
ok: !traceCode,
|
|
3240
|
+
...traceCode ? { code: traceCode } : {}
|
|
3241
|
+
});
|
|
3242
|
+
} catch {
|
|
3243
|
+
process.stderr.write("makeables: Could not write the optional local trace.\n");
|
|
3244
|
+
}
|
|
3041
3245
|
});
|