@spectratools/graphic-designer-cli 0.4.0 → 0.6.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 +555 -60
- package/dist/index.d.ts +105 -5
- package/dist/index.js +578 -60
- package/dist/qa.d.ts +14 -3
- package/dist/qa.js +242 -11
- package/dist/renderer.d.ts +1 -1
- package/dist/renderer.js +293 -53
- package/dist/{spec.schema-BUTof436.d.ts → spec.schema-Dm_wOLTd.d.ts} +1375 -114
- package/dist/spec.schema.d.ts +1 -1
- package/dist/spec.schema.js +75 -8
- 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-Dm_wOLTd.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)";
|
|
@@ -520,10 +667,26 @@ var cardElementSchema = z2.object({
|
|
|
520
667
|
tone: z2.enum(["neutral", "accent", "success", "warning", "error"]).default("neutral"),
|
|
521
668
|
icon: z2.string().min(1).max(64).optional()
|
|
522
669
|
}).strict();
|
|
670
|
+
var flowNodeShadowSchema = z2.object({
|
|
671
|
+
color: colorHexSchema2.optional(),
|
|
672
|
+
blur: z2.number().min(0).max(64).default(8),
|
|
673
|
+
offsetX: z2.number().min(-32).max(32).default(0),
|
|
674
|
+
offsetY: z2.number().min(-32).max(32).default(0),
|
|
675
|
+
opacity: z2.number().min(0).max(1).default(0.3)
|
|
676
|
+
}).strict();
|
|
523
677
|
var flowNodeElementSchema = z2.object({
|
|
524
678
|
type: z2.literal("flow-node"),
|
|
525
679
|
id: z2.string().min(1).max(120),
|
|
526
|
-
shape: z2.enum([
|
|
680
|
+
shape: z2.enum([
|
|
681
|
+
"box",
|
|
682
|
+
"rounded-box",
|
|
683
|
+
"diamond",
|
|
684
|
+
"circle",
|
|
685
|
+
"pill",
|
|
686
|
+
"cylinder",
|
|
687
|
+
"parallelogram",
|
|
688
|
+
"hexagon"
|
|
689
|
+
]).default("rounded-box"),
|
|
527
690
|
label: z2.string().min(1).max(200),
|
|
528
691
|
sublabel: z2.string().min(1).max(300).optional(),
|
|
529
692
|
sublabelColor: colorHexSchema2.optional(),
|
|
@@ -543,20 +706,25 @@ var flowNodeElementSchema = z2.object({
|
|
|
543
706
|
badgeText: z2.string().min(1).max(32).optional(),
|
|
544
707
|
badgeColor: colorHexSchema2.optional(),
|
|
545
708
|
badgeBackground: colorHexSchema2.optional(),
|
|
546
|
-
badgePosition: z2.enum(["top", "inside-top"]).default("inside-top")
|
|
709
|
+
badgePosition: z2.enum(["top", "inside-top"]).default("inside-top"),
|
|
710
|
+
shadow: flowNodeShadowSchema.optional()
|
|
547
711
|
}).strict();
|
|
548
712
|
var connectionElementSchema = z2.object({
|
|
549
713
|
type: z2.literal("connection"),
|
|
550
714
|
from: z2.string().min(1).max(120),
|
|
551
715
|
to: z2.string().min(1).max(120),
|
|
552
716
|
style: z2.enum(["solid", "dashed", "dotted"]).default("solid"),
|
|
717
|
+
strokeStyle: z2.enum(["solid", "dashed", "dotted"]).default("solid"),
|
|
553
718
|
arrow: z2.enum(["end", "start", "both", "none"]).default("end"),
|
|
554
719
|
label: z2.string().min(1).max(200).optional(),
|
|
555
720
|
labelPosition: z2.enum(["start", "middle", "end"]).default("middle"),
|
|
556
721
|
color: colorHexSchema2.optional(),
|
|
557
|
-
width: z2.number().min(0.5).max(
|
|
722
|
+
width: z2.number().min(0.5).max(10).optional(),
|
|
723
|
+
strokeWidth: z2.number().min(0.5).max(10).default(2),
|
|
558
724
|
arrowSize: z2.number().min(4).max(32).optional(),
|
|
559
|
-
opacity: z2.number().min(0).max(1).default(1)
|
|
725
|
+
opacity: z2.number().min(0).max(1).default(1),
|
|
726
|
+
routing: z2.enum(["auto", "orthogonal", "curve", "arc"]).default("auto"),
|
|
727
|
+
tension: z2.number().min(0.1).max(0.8).default(0.35)
|
|
560
728
|
}).strict();
|
|
561
729
|
var codeBlockStyleSchema = z2.object({
|
|
562
730
|
paddingVertical: z2.number().min(0).max(128).default(56),
|
|
@@ -625,6 +793,10 @@ var elementSchema = z2.discriminatedUnion("type", [
|
|
|
625
793
|
shapeElementSchema,
|
|
626
794
|
imageElementSchema
|
|
627
795
|
]);
|
|
796
|
+
var diagramCenterSchema = z2.object({
|
|
797
|
+
x: z2.number(),
|
|
798
|
+
y: z2.number()
|
|
799
|
+
}).strict();
|
|
628
800
|
var autoLayoutConfigSchema = z2.object({
|
|
629
801
|
mode: z2.literal("auto"),
|
|
630
802
|
algorithm: z2.enum(["layered", "stress", "force", "radial", "box"]).default("layered"),
|
|
@@ -640,7 +812,9 @@ var autoLayoutConfigSchema = z2.object({
|
|
|
640
812
|
/** Compaction strategy for radial layout. Only relevant when algorithm is 'radial'. */
|
|
641
813
|
radialCompaction: z2.enum(["none", "radial", "wedge"]).optional(),
|
|
642
814
|
/** Sort strategy for radial layout node ordering. Only relevant when algorithm is 'radial'. */
|
|
643
|
-
radialSortBy: z2.enum(["id", "connections"]).optional()
|
|
815
|
+
radialSortBy: z2.enum(["id", "connections"]).optional(),
|
|
816
|
+
/** Explicit center used by curve/arc connection routing. */
|
|
817
|
+
diagramCenter: diagramCenterSchema.optional()
|
|
644
818
|
}).strict();
|
|
645
819
|
var gridLayoutConfigSchema = z2.object({
|
|
646
820
|
mode: z2.literal("grid"),
|
|
@@ -648,13 +822,17 @@ var gridLayoutConfigSchema = z2.object({
|
|
|
648
822
|
gap: z2.number().int().min(0).max(256).default(24),
|
|
649
823
|
cardMinHeight: z2.number().int().min(32).max(4096).optional(),
|
|
650
824
|
cardMaxHeight: z2.number().int().min(32).max(4096).optional(),
|
|
651
|
-
equalHeight: z2.boolean().default(false)
|
|
825
|
+
equalHeight: z2.boolean().default(false),
|
|
826
|
+
/** Explicit center used by curve/arc connection routing. */
|
|
827
|
+
diagramCenter: diagramCenterSchema.optional()
|
|
652
828
|
}).strict();
|
|
653
829
|
var stackLayoutConfigSchema = z2.object({
|
|
654
830
|
mode: z2.literal("stack"),
|
|
655
831
|
direction: z2.enum(["vertical", "horizontal"]).default("vertical"),
|
|
656
832
|
gap: z2.number().int().min(0).max(256).default(24),
|
|
657
|
-
alignment: z2.enum(["start", "center", "end", "stretch"]).default("stretch")
|
|
833
|
+
alignment: z2.enum(["start", "center", "end", "stretch"]).default("stretch"),
|
|
834
|
+
/** Explicit center used by curve/arc connection routing. */
|
|
835
|
+
diagramCenter: diagramCenterSchema.optional()
|
|
658
836
|
}).strict();
|
|
659
837
|
var manualPositionSchema = z2.object({
|
|
660
838
|
x: z2.number().int(),
|
|
@@ -664,7 +842,9 @@ var manualPositionSchema = z2.object({
|
|
|
664
842
|
}).strict();
|
|
665
843
|
var manualLayoutConfigSchema = z2.object({
|
|
666
844
|
mode: z2.literal("manual"),
|
|
667
|
-
positions: z2.record(z2.string().min(1), manualPositionSchema).default({})
|
|
845
|
+
positions: z2.record(z2.string().min(1), manualPositionSchema).default({}),
|
|
846
|
+
/** Explicit center used by curve/arc connection routing. */
|
|
847
|
+
diagramCenter: diagramCenterSchema.optional()
|
|
668
848
|
}).strict();
|
|
669
849
|
var layoutConfigSchema = z2.discriminatedUnion("mode", [
|
|
670
850
|
autoLayoutConfigSchema,
|
|
@@ -716,6 +896,31 @@ var canvasSchema = z2.object({
|
|
|
716
896
|
padding: z2.number().int().min(0).max(256).default(defaultCanvas.padding)
|
|
717
897
|
}).strict();
|
|
718
898
|
var themeInputSchema = z2.union([builtInThemeSchema, themeSchema]);
|
|
899
|
+
var diagramPositionSchema = z2.object({
|
|
900
|
+
x: z2.number(),
|
|
901
|
+
y: z2.number(),
|
|
902
|
+
width: z2.number().positive(),
|
|
903
|
+
height: z2.number().positive()
|
|
904
|
+
}).strict();
|
|
905
|
+
var diagramElementSchema = z2.discriminatedUnion("type", [
|
|
906
|
+
flowNodeElementSchema,
|
|
907
|
+
connectionElementSchema
|
|
908
|
+
]);
|
|
909
|
+
var diagramLayoutSchema = z2.object({
|
|
910
|
+
mode: z2.enum(["manual", "auto"]).default("manual"),
|
|
911
|
+
positions: z2.record(z2.string(), diagramPositionSchema).optional(),
|
|
912
|
+
diagramCenter: diagramCenterSchema.optional()
|
|
913
|
+
}).strict();
|
|
914
|
+
var diagramSpecSchema = z2.object({
|
|
915
|
+
version: z2.literal(1),
|
|
916
|
+
canvas: z2.object({
|
|
917
|
+
width: z2.number().int().min(320).max(4096).default(1200),
|
|
918
|
+
height: z2.number().int().min(180).max(4096).default(675)
|
|
919
|
+
}).default({ width: 1200, height: 675 }),
|
|
920
|
+
theme: themeSchema.optional(),
|
|
921
|
+
elements: z2.array(diagramElementSchema).min(1),
|
|
922
|
+
layout: diagramLayoutSchema.default({ mode: "manual" })
|
|
923
|
+
}).strict();
|
|
719
924
|
var designSpecSchema = z2.object({
|
|
720
925
|
version: z2.literal(2).default(2),
|
|
721
926
|
canvas: canvasSchema.default(defaultCanvas),
|
|
@@ -789,7 +994,7 @@ async function runQa(options) {
|
|
|
789
994
|
const imagePath = resolve(options.imagePath);
|
|
790
995
|
const expectedSafeFrame = deriveSafeFrame(spec);
|
|
791
996
|
const expectedCanvas = canvasRect(spec);
|
|
792
|
-
const imageMetadata = await
|
|
997
|
+
const imageMetadata = await sharp2(imagePath).metadata();
|
|
793
998
|
const issues = [];
|
|
794
999
|
const expectedScale = options.metadata?.canvas.scale ?? resolveRenderScale(spec);
|
|
795
1000
|
const expectedWidth = spec.canvas.width * expectedScale;
|
|
@@ -940,6 +1145,31 @@ async function runQa(options) {
|
|
|
940
1145
|
});
|
|
941
1146
|
}
|
|
942
1147
|
}
|
|
1148
|
+
let referenceResult;
|
|
1149
|
+
if (options.referencePath) {
|
|
1150
|
+
const { compareImages: compareImages2 } = await Promise.resolve().then(() => (init_compare(), compare_exports));
|
|
1151
|
+
const comparison = await compareImages2(options.referencePath, imagePath);
|
|
1152
|
+
referenceResult = {
|
|
1153
|
+
similarity: comparison.similarity,
|
|
1154
|
+
verdict: comparison.verdict,
|
|
1155
|
+
regions: comparison.regions.map((region) => ({
|
|
1156
|
+
label: region.label,
|
|
1157
|
+
similarity: region.similarity
|
|
1158
|
+
}))
|
|
1159
|
+
};
|
|
1160
|
+
if (comparison.verdict === "mismatch") {
|
|
1161
|
+
const severity = comparison.similarity < 0.5 ? "error" : "warning";
|
|
1162
|
+
issues.push({
|
|
1163
|
+
code: "REFERENCE_MISMATCH",
|
|
1164
|
+
severity,
|
|
1165
|
+
message: `Reference image comparison ${severity === "error" ? "failed" : "warned"}: similarity ${comparison.similarity.toFixed(4)} with verdict "${comparison.verdict}".`,
|
|
1166
|
+
details: {
|
|
1167
|
+
similarity: comparison.similarity,
|
|
1168
|
+
verdict: comparison.verdict
|
|
1169
|
+
}
|
|
1170
|
+
});
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
943
1173
|
const footerSpacingPx = options.metadata?.layout.elements ? (() => {
|
|
944
1174
|
const footer = options.metadata.layout.elements.find((element) => element.id === "footer");
|
|
945
1175
|
if (!footer) {
|
|
@@ -972,7 +1202,8 @@ async function runQa(options) {
|
|
|
972
1202
|
...imageMetadata.height !== void 0 ? { height: imageMetadata.height } : {},
|
|
973
1203
|
...footerSpacingPx !== void 0 ? { footerSpacingPx } : {}
|
|
974
1204
|
},
|
|
975
|
-
issues
|
|
1205
|
+
issues,
|
|
1206
|
+
...referenceResult ? { reference: referenceResult } : {}
|
|
976
1207
|
};
|
|
977
1208
|
}
|
|
978
1209
|
export {
|
package/dist/renderer.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { h as DEFAULT_GENERATOR_VERSION,
|
|
1
|
+
export { h as DEFAULT_GENERATOR_VERSION, N as LayoutSnapshot, a as Rect, R as RenderMetadata, Q as RenderResult, d as RenderedElement, Z as WrittenArtifacts, a0 as computeSpecHash, aj as inferSidecarPath, am as renderDesign, ao as writeRenderArtifacts } from './spec.schema-Dm_wOLTd.js';
|
|
2
2
|
import 'zod';
|
|
3
3
|
import '@napi-rs/canvas';
|