@spectratools/graphic-designer-cli 0.4.0 → 0.7.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 +32 -2
- package/dist/cli.js +728 -61
- package/dist/index.d.ts +115 -5
- package/dist/index.js +751 -61
- package/dist/qa.d.ts +14 -3
- package/dist/qa.js +263 -12
- package/dist/renderer.d.ts +1 -1
- package/dist/renderer.js +466 -54
- package/dist/{spec.schema-BUTof436.d.ts → spec.schema-BeFz_nk1.d.ts} +1820 -193
- package/dist/spec.schema.d.ts +1 -1
- package/dist/spec.schema.js +96 -9
- package/package.json +1 -1
package/dist/qa.d.ts
CHANGED
|
@@ -1,15 +1,24 @@
|
|
|
1
|
-
import { R as RenderMetadata, D as DesignSpec } from './spec.schema-
|
|
1
|
+
import { R as RenderMetadata, D as DesignSpec } from './spec.schema-BeFz_nk1.js';
|
|
2
2
|
import 'zod';
|
|
3
3
|
import '@napi-rs/canvas';
|
|
4
4
|
|
|
5
5
|
type QaSeverity = 'error' | 'warning';
|
|
6
6
|
type QaIssue = {
|
|
7
|
-
code: 'DIMENSIONS_MISMATCH' | 'ELEMENT_CLIPPED' | 'ELEMENT_OVERLAP' | 'LOW_CONTRAST' | 'FOOTER_SPACING' | 'TEXT_TRUNCATED' | 'MISSING_LAYOUT' | 'DRAW_OUT_OF_BOUNDS';
|
|
7
|
+
code: 'DIMENSIONS_MISMATCH' | 'ELEMENT_CLIPPED' | 'ELEMENT_OVERLAP' | 'LOW_CONTRAST' | 'FOOTER_SPACING' | 'TEXT_TRUNCATED' | 'MISSING_LAYOUT' | 'DRAW_OUT_OF_BOUNDS' | 'REFERENCE_MISMATCH';
|
|
8
8
|
severity: QaSeverity;
|
|
9
9
|
message: string;
|
|
10
10
|
elementId?: string;
|
|
11
11
|
details?: Record<string, number | string | boolean>;
|
|
12
12
|
};
|
|
13
|
+
type QaReferenceResult = {
|
|
14
|
+
similarity: number;
|
|
15
|
+
verdict: 'match' | 'close' | 'mismatch';
|
|
16
|
+
regions: Array<{
|
|
17
|
+
label: string;
|
|
18
|
+
similarity: number;
|
|
19
|
+
description?: string;
|
|
20
|
+
}>;
|
|
21
|
+
};
|
|
13
22
|
type QaReport = {
|
|
14
23
|
pass: boolean;
|
|
15
24
|
checkedAt: string;
|
|
@@ -27,6 +36,7 @@ type QaReport = {
|
|
|
27
36
|
footerSpacingPx?: number;
|
|
28
37
|
};
|
|
29
38
|
issues: QaIssue[];
|
|
39
|
+
reference?: QaReferenceResult;
|
|
30
40
|
};
|
|
31
41
|
/**
|
|
32
42
|
* Read and parse a sidecar `.meta.json` file produced by
|
|
@@ -57,6 +67,7 @@ declare function runQa(options: {
|
|
|
57
67
|
imagePath: string;
|
|
58
68
|
spec: DesignSpec;
|
|
59
69
|
metadata?: RenderMetadata;
|
|
70
|
+
referencePath?: string;
|
|
60
71
|
}): Promise<QaReport>;
|
|
61
72
|
|
|
62
|
-
export { type QaIssue, type QaReport, type QaSeverity, readMetadata, runQa };
|
|
73
|
+
export { type QaIssue, type QaReferenceResult, type QaReport, type QaSeverity, readMetadata, runQa };
|
package/dist/qa.js
CHANGED
|
@@ -1,7 +1,154 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
|
+
var __esm = (fn, res) => function __init() {
|
|
4
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
5
|
+
};
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
// src/compare.ts
|
|
12
|
+
var compare_exports = {};
|
|
13
|
+
__export(compare_exports, {
|
|
14
|
+
compareImages: () => compareImages
|
|
15
|
+
});
|
|
16
|
+
import sharp from "sharp";
|
|
17
|
+
function clampUnit(value) {
|
|
18
|
+
if (value < 0) {
|
|
19
|
+
return 0;
|
|
20
|
+
}
|
|
21
|
+
if (value > 1) {
|
|
22
|
+
return 1;
|
|
23
|
+
}
|
|
24
|
+
return value;
|
|
25
|
+
}
|
|
26
|
+
function toRegionLabel(row, column) {
|
|
27
|
+
const letter = String.fromCharCode(65 + row);
|
|
28
|
+
return `${letter}${column + 1}`;
|
|
29
|
+
}
|
|
30
|
+
function validateGrid(grid) {
|
|
31
|
+
if (!Number.isInteger(grid) || grid <= 0) {
|
|
32
|
+
throw new Error(`Invalid grid value "${grid}". Expected a positive integer.`);
|
|
33
|
+
}
|
|
34
|
+
if (grid > 26) {
|
|
35
|
+
throw new Error(`Invalid grid value "${grid}". Maximum supported grid is 26.`);
|
|
36
|
+
}
|
|
37
|
+
return grid;
|
|
38
|
+
}
|
|
39
|
+
function validateThreshold(threshold) {
|
|
40
|
+
if (!Number.isFinite(threshold) || threshold < 0 || threshold > 1) {
|
|
41
|
+
throw new Error(`Invalid threshold value "${threshold}". Expected a number between 0 and 1.`);
|
|
42
|
+
}
|
|
43
|
+
return threshold;
|
|
44
|
+
}
|
|
45
|
+
async function readDimensions(path) {
|
|
46
|
+
const metadata = await sharp(path).metadata();
|
|
47
|
+
if (!metadata.width || !metadata.height) {
|
|
48
|
+
throw new Error(`Unable to read image dimensions for "${path}".`);
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
width: metadata.width,
|
|
52
|
+
height: metadata.height
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
async function normalizeToRaw(path, width, height) {
|
|
56
|
+
const normalized = await sharp(path).rotate().resize(width, height, {
|
|
57
|
+
fit: "contain",
|
|
58
|
+
position: "centre",
|
|
59
|
+
background: { r: 0, g: 0, b: 0, alpha: 0 }
|
|
60
|
+
}).ensureAlpha().raw().toBuffer({ resolveWithObject: true });
|
|
61
|
+
return {
|
|
62
|
+
data: normalized.data,
|
|
63
|
+
width: normalized.info.width,
|
|
64
|
+
height: normalized.info.height
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function scorePixelDifference(a, b, offset) {
|
|
68
|
+
const redDiff = Math.abs(a.data[offset] - b.data[offset]);
|
|
69
|
+
const greenDiff = Math.abs(a.data[offset + 1] - b.data[offset + 1]);
|
|
70
|
+
const blueDiff = Math.abs(a.data[offset + 2] - b.data[offset + 2]);
|
|
71
|
+
const alphaDiff = Math.abs(a.data[offset + 3] - b.data[offset + 3]);
|
|
72
|
+
const rgbDelta = (redDiff + greenDiff + blueDiff) / (3 * 255);
|
|
73
|
+
const alphaDelta = alphaDiff / 255;
|
|
74
|
+
return rgbDelta * 0.75 + alphaDelta * 0.25;
|
|
75
|
+
}
|
|
76
|
+
async function compareImages(target, rendered, options = {}) {
|
|
77
|
+
const grid = validateGrid(options.grid ?? DEFAULT_GRID);
|
|
78
|
+
const threshold = validateThreshold(options.threshold ?? DEFAULT_THRESHOLD);
|
|
79
|
+
const closeThreshold = clampUnit(threshold - (options.closeMargin ?? DEFAULT_CLOSE_MARGIN));
|
|
80
|
+
const targetDimensions = await readDimensions(target);
|
|
81
|
+
const renderedDimensions = await readDimensions(rendered);
|
|
82
|
+
const normalizedWidth = Math.max(targetDimensions.width, renderedDimensions.width);
|
|
83
|
+
const normalizedHeight = Math.max(targetDimensions.height, renderedDimensions.height);
|
|
84
|
+
const [targetImage, renderedImage] = await Promise.all([
|
|
85
|
+
normalizeToRaw(target, normalizedWidth, normalizedHeight),
|
|
86
|
+
normalizeToRaw(rendered, normalizedWidth, normalizedHeight)
|
|
87
|
+
]);
|
|
88
|
+
const regionDiffSums = new Array(grid * grid).fill(0);
|
|
89
|
+
const regionCounts = new Array(grid * grid).fill(0);
|
|
90
|
+
let totalDiff = 0;
|
|
91
|
+
for (let y = 0; y < normalizedHeight; y += 1) {
|
|
92
|
+
const row = Math.min(Math.floor(y * grid / normalizedHeight), grid - 1);
|
|
93
|
+
for (let x = 0; x < normalizedWidth; x += 1) {
|
|
94
|
+
const column = Math.min(Math.floor(x * grid / normalizedWidth), grid - 1);
|
|
95
|
+
const regionIndex = row * grid + column;
|
|
96
|
+
const offset = (y * normalizedWidth + x) * 4;
|
|
97
|
+
const diff = scorePixelDifference(targetImage, renderedImage, offset);
|
|
98
|
+
totalDiff += diff;
|
|
99
|
+
regionDiffSums[regionIndex] += diff;
|
|
100
|
+
regionCounts[regionIndex] += 1;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
const pixelCount = normalizedWidth * normalizedHeight;
|
|
104
|
+
const similarity = clampUnit(1 - totalDiff / pixelCount);
|
|
105
|
+
const regions = [];
|
|
106
|
+
for (let row = 0; row < grid; row += 1) {
|
|
107
|
+
for (let column = 0; column < grid; column += 1) {
|
|
108
|
+
const regionIndex = row * grid + column;
|
|
109
|
+
const regionCount = regionCounts[regionIndex];
|
|
110
|
+
const regionSimilarity = regionCount > 0 ? clampUnit(1 - regionDiffSums[regionIndex] / regionCount) : 1;
|
|
111
|
+
regions.push({
|
|
112
|
+
label: toRegionLabel(row, column),
|
|
113
|
+
row,
|
|
114
|
+
column,
|
|
115
|
+
similarity: regionSimilarity
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
const verdict = similarity >= threshold ? "match" : similarity >= closeThreshold ? "close" : "mismatch";
|
|
120
|
+
return {
|
|
121
|
+
targetPath: target,
|
|
122
|
+
renderedPath: rendered,
|
|
123
|
+
targetDimensions,
|
|
124
|
+
renderedDimensions,
|
|
125
|
+
normalizedDimensions: {
|
|
126
|
+
width: normalizedWidth,
|
|
127
|
+
height: normalizedHeight
|
|
128
|
+
},
|
|
129
|
+
dimensionMismatch: targetDimensions.width !== renderedDimensions.width || targetDimensions.height !== renderedDimensions.height,
|
|
130
|
+
grid,
|
|
131
|
+
threshold,
|
|
132
|
+
closeThreshold,
|
|
133
|
+
similarity,
|
|
134
|
+
verdict,
|
|
135
|
+
regions
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
var DEFAULT_GRID, DEFAULT_THRESHOLD, DEFAULT_CLOSE_MARGIN;
|
|
139
|
+
var init_compare = __esm({
|
|
140
|
+
"src/compare.ts"() {
|
|
141
|
+
"use strict";
|
|
142
|
+
DEFAULT_GRID = 3;
|
|
143
|
+
DEFAULT_THRESHOLD = 0.8;
|
|
144
|
+
DEFAULT_CLOSE_MARGIN = 0.1;
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
|
|
1
148
|
// src/qa.ts
|
|
2
149
|
import { readFile } from "fs/promises";
|
|
3
150
|
import { resolve } from "path";
|
|
4
|
-
import
|
|
151
|
+
import sharp2 from "sharp";
|
|
5
152
|
|
|
6
153
|
// src/code-style.ts
|
|
7
154
|
var CARBON_SURROUND_COLOR = "rgba(171, 184, 195, 1)";
|
|
@@ -448,6 +595,15 @@ var drawGradientRectSchema = z2.object({
|
|
|
448
595
|
radius: z2.number().min(0).max(256).default(0),
|
|
449
596
|
opacity: z2.number().min(0).max(1).default(1)
|
|
450
597
|
}).strict();
|
|
598
|
+
var drawGridSchema = z2.object({
|
|
599
|
+
type: z2.literal("grid"),
|
|
600
|
+
spacing: z2.number().min(5).max(200).default(40),
|
|
601
|
+
color: colorHexSchema2.default("#1E2D4A"),
|
|
602
|
+
width: z2.number().min(0.1).max(4).default(0.5),
|
|
603
|
+
opacity: z2.number().min(0).max(1).default(0.2),
|
|
604
|
+
offsetX: z2.number().default(0),
|
|
605
|
+
offsetY: z2.number().default(0)
|
|
606
|
+
}).strict();
|
|
451
607
|
var drawCommandSchema = z2.discriminatedUnion("type", [
|
|
452
608
|
drawRectSchema,
|
|
453
609
|
drawCircleSchema,
|
|
@@ -456,7 +612,8 @@ var drawCommandSchema = z2.discriminatedUnion("type", [
|
|
|
456
612
|
drawBezierSchema,
|
|
457
613
|
drawPathSchema,
|
|
458
614
|
drawBadgeSchema,
|
|
459
|
-
drawGradientRectSchema
|
|
615
|
+
drawGradientRectSchema,
|
|
616
|
+
drawGridSchema
|
|
460
617
|
]);
|
|
461
618
|
var defaultCanvas = {
|
|
462
619
|
width: 1200,
|
|
@@ -520,10 +677,26 @@ var cardElementSchema = z2.object({
|
|
|
520
677
|
tone: z2.enum(["neutral", "accent", "success", "warning", "error"]).default("neutral"),
|
|
521
678
|
icon: z2.string().min(1).max(64).optional()
|
|
522
679
|
}).strict();
|
|
680
|
+
var flowNodeShadowSchema = z2.object({
|
|
681
|
+
color: colorHexSchema2.optional(),
|
|
682
|
+
blur: z2.number().min(0).max(64).default(8),
|
|
683
|
+
offsetX: z2.number().min(-32).max(32).default(0),
|
|
684
|
+
offsetY: z2.number().min(-32).max(32).default(0),
|
|
685
|
+
opacity: z2.number().min(0).max(1).default(0.3)
|
|
686
|
+
}).strict();
|
|
523
687
|
var flowNodeElementSchema = z2.object({
|
|
524
688
|
type: z2.literal("flow-node"),
|
|
525
689
|
id: z2.string().min(1).max(120),
|
|
526
|
-
shape: z2.enum([
|
|
690
|
+
shape: z2.enum([
|
|
691
|
+
"box",
|
|
692
|
+
"rounded-box",
|
|
693
|
+
"diamond",
|
|
694
|
+
"circle",
|
|
695
|
+
"pill",
|
|
696
|
+
"cylinder",
|
|
697
|
+
"parallelogram",
|
|
698
|
+
"hexagon"
|
|
699
|
+
]).default("rounded-box"),
|
|
527
700
|
label: z2.string().min(1).max(200),
|
|
528
701
|
sublabel: z2.string().min(1).max(300).optional(),
|
|
529
702
|
sublabelColor: colorHexSchema2.optional(),
|
|
@@ -543,20 +716,35 @@ var flowNodeElementSchema = z2.object({
|
|
|
543
716
|
badgeText: z2.string().min(1).max(32).optional(),
|
|
544
717
|
badgeColor: colorHexSchema2.optional(),
|
|
545
718
|
badgeBackground: colorHexSchema2.optional(),
|
|
546
|
-
badgePosition: z2.enum(["top", "inside-top"]).default("inside-top")
|
|
719
|
+
badgePosition: z2.enum(["top", "inside-top"]).default("inside-top"),
|
|
720
|
+
shadow: flowNodeShadowSchema.optional()
|
|
547
721
|
}).strict();
|
|
722
|
+
var anchorHintSchema = z2.union([
|
|
723
|
+
z2.enum(["top", "bottom", "left", "right", "center"]),
|
|
724
|
+
z2.object({
|
|
725
|
+
x: z2.number().min(-1).max(1),
|
|
726
|
+
y: z2.number().min(-1).max(1)
|
|
727
|
+
}).strict()
|
|
728
|
+
]);
|
|
548
729
|
var connectionElementSchema = z2.object({
|
|
549
730
|
type: z2.literal("connection"),
|
|
550
731
|
from: z2.string().min(1).max(120),
|
|
551
732
|
to: z2.string().min(1).max(120),
|
|
552
733
|
style: z2.enum(["solid", "dashed", "dotted"]).default("solid"),
|
|
734
|
+
strokeStyle: z2.enum(["solid", "dashed", "dotted"]).default("solid"),
|
|
553
735
|
arrow: z2.enum(["end", "start", "both", "none"]).default("end"),
|
|
554
736
|
label: z2.string().min(1).max(200).optional(),
|
|
555
737
|
labelPosition: z2.enum(["start", "middle", "end"]).default("middle"),
|
|
556
738
|
color: colorHexSchema2.optional(),
|
|
557
|
-
width: z2.number().min(0.5).max(
|
|
739
|
+
width: z2.number().min(0.5).max(10).optional(),
|
|
740
|
+
strokeWidth: z2.number().min(0.5).max(10).default(2),
|
|
558
741
|
arrowSize: z2.number().min(4).max(32).optional(),
|
|
559
|
-
|
|
742
|
+
arrowPlacement: z2.enum(["endpoint", "boundary"]).default("endpoint"),
|
|
743
|
+
opacity: z2.number().min(0).max(1).default(1),
|
|
744
|
+
routing: z2.enum(["auto", "orthogonal", "curve", "arc"]).default("auto"),
|
|
745
|
+
tension: z2.number().min(0.1).max(0.8).default(0.35),
|
|
746
|
+
fromAnchor: anchorHintSchema.optional(),
|
|
747
|
+
toAnchor: anchorHintSchema.optional()
|
|
560
748
|
}).strict();
|
|
561
749
|
var codeBlockStyleSchema = z2.object({
|
|
562
750
|
paddingVertical: z2.number().min(0).max(128).default(56),
|
|
@@ -625,6 +813,10 @@ var elementSchema = z2.discriminatedUnion("type", [
|
|
|
625
813
|
shapeElementSchema,
|
|
626
814
|
imageElementSchema
|
|
627
815
|
]);
|
|
816
|
+
var diagramCenterSchema = z2.object({
|
|
817
|
+
x: z2.number(),
|
|
818
|
+
y: z2.number()
|
|
819
|
+
}).strict();
|
|
628
820
|
var autoLayoutConfigSchema = z2.object({
|
|
629
821
|
mode: z2.literal("auto"),
|
|
630
822
|
algorithm: z2.enum(["layered", "stress", "force", "radial", "box"]).default("layered"),
|
|
@@ -640,7 +832,9 @@ var autoLayoutConfigSchema = z2.object({
|
|
|
640
832
|
/** Compaction strategy for radial layout. Only relevant when algorithm is 'radial'. */
|
|
641
833
|
radialCompaction: z2.enum(["none", "radial", "wedge"]).optional(),
|
|
642
834
|
/** Sort strategy for radial layout node ordering. Only relevant when algorithm is 'radial'. */
|
|
643
|
-
radialSortBy: z2.enum(["id", "connections"]).optional()
|
|
835
|
+
radialSortBy: z2.enum(["id", "connections"]).optional(),
|
|
836
|
+
/** Explicit center used by curve/arc connection routing. */
|
|
837
|
+
diagramCenter: diagramCenterSchema.optional()
|
|
644
838
|
}).strict();
|
|
645
839
|
var gridLayoutConfigSchema = z2.object({
|
|
646
840
|
mode: z2.literal("grid"),
|
|
@@ -648,13 +842,17 @@ var gridLayoutConfigSchema = z2.object({
|
|
|
648
842
|
gap: z2.number().int().min(0).max(256).default(24),
|
|
649
843
|
cardMinHeight: z2.number().int().min(32).max(4096).optional(),
|
|
650
844
|
cardMaxHeight: z2.number().int().min(32).max(4096).optional(),
|
|
651
|
-
equalHeight: z2.boolean().default(false)
|
|
845
|
+
equalHeight: z2.boolean().default(false),
|
|
846
|
+
/** Explicit center used by curve/arc connection routing. */
|
|
847
|
+
diagramCenter: diagramCenterSchema.optional()
|
|
652
848
|
}).strict();
|
|
653
849
|
var stackLayoutConfigSchema = z2.object({
|
|
654
850
|
mode: z2.literal("stack"),
|
|
655
851
|
direction: z2.enum(["vertical", "horizontal"]).default("vertical"),
|
|
656
852
|
gap: z2.number().int().min(0).max(256).default(24),
|
|
657
|
-
alignment: z2.enum(["start", "center", "end", "stretch"]).default("stretch")
|
|
853
|
+
alignment: z2.enum(["start", "center", "end", "stretch"]).default("stretch"),
|
|
854
|
+
/** Explicit center used by curve/arc connection routing. */
|
|
855
|
+
diagramCenter: diagramCenterSchema.optional()
|
|
658
856
|
}).strict();
|
|
659
857
|
var manualPositionSchema = z2.object({
|
|
660
858
|
x: z2.number().int(),
|
|
@@ -664,7 +862,9 @@ var manualPositionSchema = z2.object({
|
|
|
664
862
|
}).strict();
|
|
665
863
|
var manualLayoutConfigSchema = z2.object({
|
|
666
864
|
mode: z2.literal("manual"),
|
|
667
|
-
positions: z2.record(z2.string().min(1), manualPositionSchema).default({})
|
|
865
|
+
positions: z2.record(z2.string().min(1), manualPositionSchema).default({}),
|
|
866
|
+
/** Explicit center used by curve/arc connection routing. */
|
|
867
|
+
diagramCenter: diagramCenterSchema.optional()
|
|
668
868
|
}).strict();
|
|
669
869
|
var layoutConfigSchema = z2.discriminatedUnion("mode", [
|
|
670
870
|
autoLayoutConfigSchema,
|
|
@@ -716,6 +916,31 @@ var canvasSchema = z2.object({
|
|
|
716
916
|
padding: z2.number().int().min(0).max(256).default(defaultCanvas.padding)
|
|
717
917
|
}).strict();
|
|
718
918
|
var themeInputSchema = z2.union([builtInThemeSchema, themeSchema]);
|
|
919
|
+
var diagramPositionSchema = z2.object({
|
|
920
|
+
x: z2.number(),
|
|
921
|
+
y: z2.number(),
|
|
922
|
+
width: z2.number().positive(),
|
|
923
|
+
height: z2.number().positive()
|
|
924
|
+
}).strict();
|
|
925
|
+
var diagramElementSchema = z2.discriminatedUnion("type", [
|
|
926
|
+
flowNodeElementSchema,
|
|
927
|
+
connectionElementSchema
|
|
928
|
+
]);
|
|
929
|
+
var diagramLayoutSchema = z2.object({
|
|
930
|
+
mode: z2.enum(["manual", "auto"]).default("manual"),
|
|
931
|
+
positions: z2.record(z2.string(), diagramPositionSchema).optional(),
|
|
932
|
+
diagramCenter: diagramCenterSchema.optional()
|
|
933
|
+
}).strict();
|
|
934
|
+
var diagramSpecSchema = z2.object({
|
|
935
|
+
version: z2.literal(1),
|
|
936
|
+
canvas: z2.object({
|
|
937
|
+
width: z2.number().int().min(320).max(4096).default(1200),
|
|
938
|
+
height: z2.number().int().min(180).max(4096).default(675)
|
|
939
|
+
}).default({ width: 1200, height: 675 }),
|
|
940
|
+
theme: themeSchema.optional(),
|
|
941
|
+
elements: z2.array(diagramElementSchema).min(1),
|
|
942
|
+
layout: diagramLayoutSchema.default({ mode: "manual" })
|
|
943
|
+
}).strict();
|
|
719
944
|
var designSpecSchema = z2.object({
|
|
720
945
|
version: z2.literal(2).default(2),
|
|
721
946
|
canvas: canvasSchema.default(defaultCanvas),
|
|
@@ -789,7 +1014,7 @@ async function runQa(options) {
|
|
|
789
1014
|
const imagePath = resolve(options.imagePath);
|
|
790
1015
|
const expectedSafeFrame = deriveSafeFrame(spec);
|
|
791
1016
|
const expectedCanvas = canvasRect(spec);
|
|
792
|
-
const imageMetadata = await
|
|
1017
|
+
const imageMetadata = await sharp2(imagePath).metadata();
|
|
793
1018
|
const issues = [];
|
|
794
1019
|
const expectedScale = options.metadata?.canvas.scale ?? resolveRenderScale(spec);
|
|
795
1020
|
const expectedWidth = spec.canvas.width * expectedScale;
|
|
@@ -940,6 +1165,31 @@ async function runQa(options) {
|
|
|
940
1165
|
});
|
|
941
1166
|
}
|
|
942
1167
|
}
|
|
1168
|
+
let referenceResult;
|
|
1169
|
+
if (options.referencePath) {
|
|
1170
|
+
const { compareImages: compareImages2 } = await Promise.resolve().then(() => (init_compare(), compare_exports));
|
|
1171
|
+
const comparison = await compareImages2(options.referencePath, imagePath);
|
|
1172
|
+
referenceResult = {
|
|
1173
|
+
similarity: comparison.similarity,
|
|
1174
|
+
verdict: comparison.verdict,
|
|
1175
|
+
regions: comparison.regions.map((region) => ({
|
|
1176
|
+
label: region.label,
|
|
1177
|
+
similarity: region.similarity
|
|
1178
|
+
}))
|
|
1179
|
+
};
|
|
1180
|
+
if (comparison.verdict === "mismatch") {
|
|
1181
|
+
const severity = comparison.similarity < 0.5 ? "error" : "warning";
|
|
1182
|
+
issues.push({
|
|
1183
|
+
code: "REFERENCE_MISMATCH",
|
|
1184
|
+
severity,
|
|
1185
|
+
message: `Reference image comparison ${severity === "error" ? "failed" : "warned"}: similarity ${comparison.similarity.toFixed(4)} with verdict "${comparison.verdict}".`,
|
|
1186
|
+
details: {
|
|
1187
|
+
similarity: comparison.similarity,
|
|
1188
|
+
verdict: comparison.verdict
|
|
1189
|
+
}
|
|
1190
|
+
});
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
943
1193
|
const footerSpacingPx = options.metadata?.layout.elements ? (() => {
|
|
944
1194
|
const footer = options.metadata.layout.elements.find((element) => element.id === "footer");
|
|
945
1195
|
if (!footer) {
|
|
@@ -972,7 +1222,8 @@ async function runQa(options) {
|
|
|
972
1222
|
...imageMetadata.height !== void 0 ? { height: imageMetadata.height } : {},
|
|
973
1223
|
...footerSpacingPx !== void 0 ? { footerSpacingPx } : {}
|
|
974
1224
|
},
|
|
975
|
-
issues
|
|
1225
|
+
issues,
|
|
1226
|
+
...referenceResult ? { reference: referenceResult } : {}
|
|
976
1227
|
};
|
|
977
1228
|
}
|
|
978
1229
|
export {
|
package/dist/renderer.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { i as DEFAULT_GENERATOR_VERSION, O as LayoutSnapshot, a as Rect, R as RenderMetadata, S as RenderResult, d as RenderedElement, _ as WrittenArtifacts, a1 as computeSpecHash, ak as inferSidecarPath, an as renderDesign, ap as writeRenderArtifacts } from './spec.schema-BeFz_nk1.js';
|
|
2
2
|
import 'zod';
|
|
3
3
|
import '@napi-rs/canvas';
|