@spectratools/graphic-designer-cli 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +624 -455
- package/dist/index.d.ts +156 -2
- package/dist/index.js +624 -455
- package/dist/publish/index.d.ts +38 -9
- package/dist/publish/index.js +62 -130
- package/dist/qa.d.ts +25 -1
- package/dist/qa.js +121 -41
- package/dist/renderer.d.ts +1 -1
- package/dist/renderer.js +627 -389
- package/dist/{spec.schema-BxXBTOn-.d.ts → spec.schema-BUTof436.d.ts} +775 -463
- package/dist/spec.schema.d.ts +1 -1
- package/dist/spec.schema.js +85 -4
- package/package.json +3 -2
package/dist/publish/index.d.ts
CHANGED
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
maxRetries: number;
|
|
3
|
-
baseMs: number;
|
|
4
|
-
maxMs: number;
|
|
5
|
-
}
|
|
1
|
+
import { RetryOptions } from '@spectratools/cli-shared/middleware';
|
|
6
2
|
|
|
7
3
|
type GitHubPublishOptions = {
|
|
8
4
|
imagePath: string;
|
|
@@ -12,19 +8,37 @@ type GitHubPublishOptions = {
|
|
|
12
8
|
branch?: string;
|
|
13
9
|
token?: string;
|
|
14
10
|
commitMessage?: string;
|
|
15
|
-
retryPolicy?:
|
|
11
|
+
retryPolicy?: RetryOptions;
|
|
16
12
|
};
|
|
17
13
|
type GitHubPublishResult = {
|
|
18
14
|
target: 'github';
|
|
19
15
|
repo: string;
|
|
20
16
|
branch: string;
|
|
21
|
-
attempts: number;
|
|
22
17
|
files: Array<{
|
|
23
18
|
path: string;
|
|
24
19
|
htmlUrl?: string;
|
|
25
20
|
sha?: string;
|
|
26
21
|
}>;
|
|
27
22
|
};
|
|
23
|
+
/**
|
|
24
|
+
* Publish rendered design artifacts to a GitHub repository.
|
|
25
|
+
*
|
|
26
|
+
* Uploads the PNG image and sidecar `.meta.json` metadata to the specified
|
|
27
|
+
* repository via the GitHub Contents API. Files are placed under
|
|
28
|
+
* {@link GitHubPublishOptions.pathPrefix} (defaults to `"artifacts"`). If a
|
|
29
|
+
* file already exists at the destination it is updated in-place (its SHA is
|
|
30
|
+
* fetched first for the update).
|
|
31
|
+
*
|
|
32
|
+
* Requires a GitHub token — either passed via
|
|
33
|
+
* {@link GitHubPublishOptions.token} or resolved from the `GITHUB_TOKEN`
|
|
34
|
+
* environment variable.
|
|
35
|
+
*
|
|
36
|
+
* @param options - Publish configuration including file paths, target repo,
|
|
37
|
+
* branch, commit message, and optional retry policy.
|
|
38
|
+
* @returns A {@link GitHubPublishResult} with the repo, branch, and per-file
|
|
39
|
+
* metadata (path, SHA, HTML URL).
|
|
40
|
+
* @throws When no GitHub token is available or the API request fails.
|
|
41
|
+
*/
|
|
28
42
|
declare function publishToGitHub(options: GitHubPublishOptions): Promise<GitHubPublishResult>;
|
|
29
43
|
|
|
30
44
|
type GistPublishOptions = {
|
|
@@ -35,15 +49,30 @@ type GistPublishOptions = {
|
|
|
35
49
|
filenamePrefix?: string;
|
|
36
50
|
public?: boolean;
|
|
37
51
|
token?: string;
|
|
38
|
-
retryPolicy?:
|
|
52
|
+
retryPolicy?: RetryOptions;
|
|
39
53
|
};
|
|
40
54
|
type GistPublishResult = {
|
|
41
55
|
target: 'gist';
|
|
42
56
|
gistId: string;
|
|
43
57
|
htmlUrl: string;
|
|
44
|
-
attempts: number;
|
|
45
58
|
files: string[];
|
|
46
59
|
};
|
|
60
|
+
/**
|
|
61
|
+
* Publish rendered design artifacts to a GitHub Gist.
|
|
62
|
+
*
|
|
63
|
+
* Creates (or updates, when {@link GistPublishOptions.gistId} is set) a gist
|
|
64
|
+
* containing the PNG image as a base64 text file, the sidecar `.meta.json`
|
|
65
|
+
* metadata, and a Markdown README with an embedded preview.
|
|
66
|
+
*
|
|
67
|
+
* Requires a GitHub token — either passed via {@link GistPublishOptions.token}
|
|
68
|
+
* or resolved from the `GITHUB_TOKEN` environment variable.
|
|
69
|
+
*
|
|
70
|
+
* @param options - Publish configuration including file paths, gist settings,
|
|
71
|
+
* and optional retry policy.
|
|
72
|
+
* @returns A {@link GistPublishResult} with the gist ID, URL, attempt count,
|
|
73
|
+
* and list of created file names.
|
|
74
|
+
* @throws When no GitHub token is available or the API request fails.
|
|
75
|
+
*/
|
|
47
76
|
declare function publishToGist(options: GistPublishOptions): Promise<GistPublishResult>;
|
|
48
77
|
|
|
49
78
|
export { type GistPublishOptions, type GistPublishResult, type GitHubPublishOptions, type GitHubPublishResult, publishToGist, publishToGitHub };
|
package/dist/publish/index.js
CHANGED
|
@@ -1,38 +1,8 @@
|
|
|
1
1
|
// src/publish/github.ts
|
|
2
2
|
import { readFile } from "fs/promises";
|
|
3
3
|
import { basename, posix } from "path";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
var DEFAULT_RETRY_POLICY = {
|
|
7
|
-
maxRetries: 3,
|
|
8
|
-
baseMs: 500,
|
|
9
|
-
maxMs: 4e3
|
|
10
|
-
};
|
|
11
|
-
function sleep(ms) {
|
|
12
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
13
|
-
}
|
|
14
|
-
async function withRetry(operation, policy = DEFAULT_RETRY_POLICY) {
|
|
15
|
-
let attempt = 0;
|
|
16
|
-
let lastError;
|
|
17
|
-
while (attempt <= policy.maxRetries) {
|
|
18
|
-
try {
|
|
19
|
-
const value = await operation();
|
|
20
|
-
return { value, attempts: attempt + 1 };
|
|
21
|
-
} catch (error) {
|
|
22
|
-
lastError = error;
|
|
23
|
-
if (attempt >= policy.maxRetries) {
|
|
24
|
-
break;
|
|
25
|
-
}
|
|
26
|
-
const backoff = Math.min(policy.baseMs * 2 ** attempt, policy.maxMs);
|
|
27
|
-
const jitter = Math.floor(Math.random() * 125);
|
|
28
|
-
await sleep(backoff + jitter);
|
|
29
|
-
attempt += 1;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
throw lastError;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// src/publish/github.ts
|
|
4
|
+
import { withRetry } from "@spectratools/cli-shared/middleware";
|
|
5
|
+
import { HttpError, createHttpClient } from "@spectratools/cli-shared/utils";
|
|
36
6
|
function requireGitHubToken(token) {
|
|
37
7
|
const resolved = token ?? process.env.GITHUB_TOKEN;
|
|
38
8
|
if (!resolved) {
|
|
@@ -40,56 +10,18 @@ function requireGitHubToken(token) {
|
|
|
40
10
|
}
|
|
41
11
|
return resolved;
|
|
42
12
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
});
|
|
54
|
-
if (!response.ok) {
|
|
55
|
-
const text = await response.text();
|
|
56
|
-
throw new Error(
|
|
57
|
-
`GitHub API ${path} failed (${response.status}): ${text || response.statusText}`
|
|
58
|
-
);
|
|
59
|
-
}
|
|
60
|
-
return await response.json();
|
|
61
|
-
}, retryPolicy);
|
|
62
|
-
}
|
|
63
|
-
async function githubJsonMaybe(path, token, retryPolicy) {
|
|
64
|
-
const { value, attempts } = await withRetry(async () => {
|
|
65
|
-
const response = await fetch(`https://api.github.com${path}`, {
|
|
66
|
-
headers: {
|
|
67
|
-
Accept: "application/vnd.github+json",
|
|
68
|
-
Authorization: `Bearer ${token}`,
|
|
69
|
-
"User-Agent": "spectratools-graphic-designer"
|
|
70
|
-
}
|
|
71
|
-
});
|
|
72
|
-
if (response.status === 404) {
|
|
73
|
-
return { found: false };
|
|
74
|
-
}
|
|
75
|
-
if (!response.ok) {
|
|
76
|
-
const text = await response.text();
|
|
77
|
-
throw new Error(
|
|
78
|
-
`GitHub API ${path} failed (${response.status}): ${text || response.statusText}`
|
|
79
|
-
);
|
|
80
|
-
}
|
|
81
|
-
const json = await response.json();
|
|
82
|
-
return { found: true, value: json };
|
|
83
|
-
}, retryPolicy);
|
|
84
|
-
if (!value.found) {
|
|
85
|
-
return { found: false, attempts };
|
|
13
|
+
var DEFAULT_RETRY = {
|
|
14
|
+
maxRetries: 3,
|
|
15
|
+
baseMs: 500,
|
|
16
|
+
maxMs: 4e3
|
|
17
|
+
};
|
|
18
|
+
var github = createHttpClient({
|
|
19
|
+
baseUrl: "https://api.github.com",
|
|
20
|
+
defaultHeaders: {
|
|
21
|
+
Accept: "application/vnd.github+json",
|
|
22
|
+
"User-Agent": "spectratools-graphic-designer"
|
|
86
23
|
}
|
|
87
|
-
|
|
88
|
-
found: true,
|
|
89
|
-
value: value.value,
|
|
90
|
-
attempts
|
|
91
|
-
};
|
|
92
|
-
}
|
|
24
|
+
});
|
|
93
25
|
function parseRepo(repo) {
|
|
94
26
|
const [owner, name] = repo.split("/");
|
|
95
27
|
if (!owner || !name) {
|
|
@@ -105,9 +37,25 @@ function normalizePath(pathPrefix, filename) {
|
|
|
105
37
|
const trimmed = (pathPrefix ?? "artifacts").replace(/^\/+|\/+$/gu, "");
|
|
106
38
|
return posix.join(trimmed, filename);
|
|
107
39
|
}
|
|
40
|
+
async function githubJsonMaybe(path, token, retry) {
|
|
41
|
+
return withRetry(async () => {
|
|
42
|
+
try {
|
|
43
|
+
const value = await github.request(path, {
|
|
44
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
45
|
+
});
|
|
46
|
+
return { found: true, value };
|
|
47
|
+
} catch (err) {
|
|
48
|
+
if (err instanceof HttpError && err.status === 404) {
|
|
49
|
+
return { found: false };
|
|
50
|
+
}
|
|
51
|
+
throw err;
|
|
52
|
+
}
|
|
53
|
+
}, retry);
|
|
54
|
+
}
|
|
108
55
|
async function publishToGitHub(options) {
|
|
109
56
|
const token = requireGitHubToken(options.token);
|
|
110
57
|
const branch = options.branch ?? "main";
|
|
58
|
+
const retry = options.retryPolicy ?? DEFAULT_RETRY;
|
|
111
59
|
const commitMessage = options.commitMessage ?? "chore(graphic-designer): publish deterministic artifacts";
|
|
112
60
|
const [imageBuffer, metadataBuffer] = await Promise.all([
|
|
113
61
|
readFile(options.imagePath),
|
|
@@ -125,16 +73,10 @@ async function publishToGitHub(options) {
|
|
|
125
73
|
content: metadataBuffer.toString("base64")
|
|
126
74
|
}
|
|
127
75
|
];
|
|
128
|
-
let totalAttempts = 0;
|
|
129
76
|
const files = [];
|
|
130
77
|
for (const upload of uploads) {
|
|
131
78
|
const existingPath = `${toApiContentPath(options.repo, upload.destination)}?ref=${encodeURIComponent(branch)}`;
|
|
132
|
-
const existing = await githubJsonMaybe(
|
|
133
|
-
existingPath,
|
|
134
|
-
token,
|
|
135
|
-
options.retryPolicy
|
|
136
|
-
);
|
|
137
|
-
totalAttempts += existing.attempts;
|
|
79
|
+
const existing = await githubJsonMaybe(existingPath, token, retry);
|
|
138
80
|
const body = {
|
|
139
81
|
message: `${commitMessage} (${basename(upload.sourcePath)})`,
|
|
140
82
|
content: upload.content,
|
|
@@ -142,27 +84,24 @@ async function publishToGitHub(options) {
|
|
|
142
84
|
sha: existing.value?.sha
|
|
143
85
|
};
|
|
144
86
|
const putPath = toApiContentPath(options.repo, upload.destination);
|
|
145
|
-
const published = await
|
|
146
|
-
putPath,
|
|
147
|
-
{
|
|
87
|
+
const published = await withRetry(
|
|
88
|
+
() => github.request(putPath, {
|
|
148
89
|
method: "PUT",
|
|
149
|
-
body
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
90
|
+
body,
|
|
91
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
92
|
+
}),
|
|
93
|
+
retry
|
|
153
94
|
);
|
|
154
|
-
totalAttempts += published.attempts;
|
|
155
95
|
files.push({
|
|
156
96
|
path: upload.destination,
|
|
157
|
-
...published.
|
|
158
|
-
...published.
|
|
97
|
+
...published.content?.sha ? { sha: published.content.sha } : {},
|
|
98
|
+
...published.content?.html_url ? { htmlUrl: published.content.html_url } : {}
|
|
159
99
|
});
|
|
160
100
|
}
|
|
161
101
|
return {
|
|
162
102
|
target: "github",
|
|
163
103
|
repo: options.repo,
|
|
164
104
|
branch,
|
|
165
|
-
attempts: totalAttempts,
|
|
166
105
|
files
|
|
167
106
|
};
|
|
168
107
|
}
|
|
@@ -170,6 +109,8 @@ async function publishToGitHub(options) {
|
|
|
170
109
|
// src/publish/gist.ts
|
|
171
110
|
import { readFile as readFile2 } from "fs/promises";
|
|
172
111
|
import { basename as basename2 } from "path";
|
|
112
|
+
import { withRetry as withRetry2 } from "@spectratools/cli-shared/middleware";
|
|
113
|
+
import { createHttpClient as createHttpClient2 } from "@spectratools/cli-shared/utils";
|
|
173
114
|
function requireGitHubToken2(token) {
|
|
174
115
|
const resolved = token ?? process.env.GITHUB_TOKEN;
|
|
175
116
|
if (!resolved) {
|
|
@@ -177,28 +118,21 @@ function requireGitHubToken2(token) {
|
|
|
177
118
|
}
|
|
178
119
|
return resolved;
|
|
179
120
|
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
const text = await response.text();
|
|
193
|
-
throw new Error(
|
|
194
|
-
`GitHub Gist API ${path} failed (${response.status}): ${text || response.statusText}`
|
|
195
|
-
);
|
|
196
|
-
}
|
|
197
|
-
return await response.json();
|
|
198
|
-
}, retryPolicy);
|
|
199
|
-
}
|
|
121
|
+
var DEFAULT_RETRY2 = {
|
|
122
|
+
maxRetries: 3,
|
|
123
|
+
baseMs: 500,
|
|
124
|
+
maxMs: 4e3
|
|
125
|
+
};
|
|
126
|
+
var github2 = createHttpClient2({
|
|
127
|
+
baseUrl: "https://api.github.com",
|
|
128
|
+
defaultHeaders: {
|
|
129
|
+
Accept: "application/vnd.github+json",
|
|
130
|
+
"User-Agent": "spectratools-graphic-designer"
|
|
131
|
+
}
|
|
132
|
+
});
|
|
200
133
|
async function publishToGist(options) {
|
|
201
134
|
const token = requireGitHubToken2(options.token);
|
|
135
|
+
const retry = options.retryPolicy ?? DEFAULT_RETRY2;
|
|
202
136
|
const [imageBuffer, metadataBuffer] = await Promise.all([
|
|
203
137
|
readFile2(options.imagePath),
|
|
204
138
|
readFile2(options.metadataPath)
|
|
@@ -236,21 +170,19 @@ async function publishToGist(options) {
|
|
|
236
170
|
};
|
|
237
171
|
const endpoint = options.gistId ? `/gists/${options.gistId}` : "/gists";
|
|
238
172
|
const method = options.gistId ? "PATCH" : "POST";
|
|
239
|
-
const published = await
|
|
240
|
-
endpoint,
|
|
241
|
-
{
|
|
173
|
+
const published = await withRetry2(
|
|
174
|
+
() => github2.request(endpoint, {
|
|
242
175
|
method,
|
|
243
|
-
body:
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
176
|
+
body: payload,
|
|
177
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
178
|
+
}),
|
|
179
|
+
retry
|
|
247
180
|
);
|
|
248
181
|
return {
|
|
249
182
|
target: "gist",
|
|
250
|
-
gistId: published.
|
|
251
|
-
htmlUrl: published.
|
|
252
|
-
|
|
253
|
-
files: Object.keys(published.value.files ?? payload.files)
|
|
183
|
+
gistId: published.id,
|
|
184
|
+
htmlUrl: published.html_url,
|
|
185
|
+
files: Object.keys(published.files ?? payload.files)
|
|
254
186
|
};
|
|
255
187
|
}
|
|
256
188
|
export {
|
package/dist/qa.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { R as RenderMetadata, D as DesignSpec } from './spec.schema-
|
|
1
|
+
import { R as RenderMetadata, D as DesignSpec } from './spec.schema-BUTof436.js';
|
|
2
2
|
import 'zod';
|
|
3
3
|
import '@napi-rs/canvas';
|
|
4
4
|
|
|
@@ -28,7 +28,31 @@ type QaReport = {
|
|
|
28
28
|
};
|
|
29
29
|
issues: QaIssue[];
|
|
30
30
|
};
|
|
31
|
+
/**
|
|
32
|
+
* Read and parse a sidecar `.meta.json` file produced by
|
|
33
|
+
* {@link writeRenderArtifacts}.
|
|
34
|
+
*
|
|
35
|
+
* @param path - Absolute or relative path to the `.meta.json` sidecar file.
|
|
36
|
+
* @returns The parsed {@link RenderMetadata} object.
|
|
37
|
+
*/
|
|
31
38
|
declare function readMetadata(path: string): Promise<RenderMetadata>;
|
|
39
|
+
/**
|
|
40
|
+
* Run quality-assurance checks on a rendered design image.
|
|
41
|
+
*
|
|
42
|
+
* Validates the rendered PNG against the source spec and optional metadata.
|
|
43
|
+
* Checks include: dimension matching, element clipping against the safe frame,
|
|
44
|
+
* element overlap detection, WCAG contrast ratio, footer spacing, text
|
|
45
|
+
* truncation, and draw-command bounds.
|
|
46
|
+
*
|
|
47
|
+
* @param options - QA configuration.
|
|
48
|
+
* @param options.imagePath - Path to the rendered PNG image to validate.
|
|
49
|
+
* @param options.spec - The {@link DesignSpec} used to produce the image.
|
|
50
|
+
* @param options.metadata - Optional {@link RenderMetadata} from the render
|
|
51
|
+
* pass. When provided, layout-level checks (overlap, clipping, contrast) are
|
|
52
|
+
* performed against the element positions recorded in the metadata.
|
|
53
|
+
* @returns A {@link QaReport} summarising whether the image passes and listing
|
|
54
|
+
* any issues found.
|
|
55
|
+
*/
|
|
32
56
|
declare function runQa(options: {
|
|
33
57
|
imagePath: string;
|
|
34
58
|
spec: DesignSpec;
|
package/dist/qa.js
CHANGED
|
@@ -62,7 +62,98 @@ import { z as z2 } from "zod";
|
|
|
62
62
|
|
|
63
63
|
// src/themes/builtin.ts
|
|
64
64
|
import { z } from "zod";
|
|
65
|
-
|
|
65
|
+
|
|
66
|
+
// src/utils/color.ts
|
|
67
|
+
function parseChannel(hex, offset) {
|
|
68
|
+
return Number.parseInt(hex.slice(offset, offset + 2), 16);
|
|
69
|
+
}
|
|
70
|
+
function parseHexColor(hexColor) {
|
|
71
|
+
const normalized = hexColor.startsWith("#") ? hexColor.slice(1) : hexColor;
|
|
72
|
+
if (normalized.length !== 6 && normalized.length !== 8) {
|
|
73
|
+
throw new Error(`Unsupported color format: ${hexColor}`);
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
r: parseChannel(normalized, 0),
|
|
77
|
+
g: parseChannel(normalized, 2),
|
|
78
|
+
b: parseChannel(normalized, 4)
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
var rgbaRegex = /^rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*([01](?:\.\d+)?|0?\.\d+)\s*)?\)$/;
|
|
82
|
+
var hexColorRegex = /^#(?:[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/;
|
|
83
|
+
function toHex(n) {
|
|
84
|
+
return n.toString(16).padStart(2, "0");
|
|
85
|
+
}
|
|
86
|
+
function parseRgbaToHex(color) {
|
|
87
|
+
const match = rgbaRegex.exec(color);
|
|
88
|
+
if (!match) {
|
|
89
|
+
throw new Error(`Invalid rgb/rgba color: ${color}`);
|
|
90
|
+
}
|
|
91
|
+
const r = Number.parseInt(match[1], 10);
|
|
92
|
+
const g = Number.parseInt(match[2], 10);
|
|
93
|
+
const b = Number.parseInt(match[3], 10);
|
|
94
|
+
if (r > 255 || g > 255 || b > 255) {
|
|
95
|
+
throw new Error(`RGB channel values must be 0-255, got: ${color}`);
|
|
96
|
+
}
|
|
97
|
+
if (match[4] !== void 0) {
|
|
98
|
+
const a = Number.parseFloat(match[4]);
|
|
99
|
+
if (a < 0 || a > 1) {
|
|
100
|
+
throw new Error(`Alpha value must be 0-1, got: ${a}`);
|
|
101
|
+
}
|
|
102
|
+
const alphaByte = Math.round(a * 255);
|
|
103
|
+
return `#${toHex(r)}${toHex(g)}${toHex(b)}${toHex(alphaByte)}`;
|
|
104
|
+
}
|
|
105
|
+
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
|
|
106
|
+
}
|
|
107
|
+
function isRgbaColor(color) {
|
|
108
|
+
return rgbaRegex.test(color);
|
|
109
|
+
}
|
|
110
|
+
function isHexColor(color) {
|
|
111
|
+
return hexColorRegex.test(color);
|
|
112
|
+
}
|
|
113
|
+
function normalizeColor(color) {
|
|
114
|
+
if (isHexColor(color)) {
|
|
115
|
+
return color;
|
|
116
|
+
}
|
|
117
|
+
if (isRgbaColor(color)) {
|
|
118
|
+
return parseRgbaToHex(color);
|
|
119
|
+
}
|
|
120
|
+
throw new Error(`Expected #RRGGBB, #RRGGBBAA, rgb(), or rgba() color, got: ${color}`);
|
|
121
|
+
}
|
|
122
|
+
function srgbToLinear(channel) {
|
|
123
|
+
const normalized = channel / 255;
|
|
124
|
+
if (normalized <= 0.03928) {
|
|
125
|
+
return normalized / 12.92;
|
|
126
|
+
}
|
|
127
|
+
return ((normalized + 0.055) / 1.055) ** 2.4;
|
|
128
|
+
}
|
|
129
|
+
function relativeLuminance(hexColor) {
|
|
130
|
+
const normalized = isRgbaColor(hexColor) ? parseRgbaToHex(hexColor) : hexColor;
|
|
131
|
+
const rgb = parseHexColor(normalized);
|
|
132
|
+
const r = srgbToLinear(rgb.r);
|
|
133
|
+
const g = srgbToLinear(rgb.g);
|
|
134
|
+
const b = srgbToLinear(rgb.b);
|
|
135
|
+
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
136
|
+
}
|
|
137
|
+
function contrastRatio(foreground, background) {
|
|
138
|
+
const fg = relativeLuminance(foreground);
|
|
139
|
+
const bg = relativeLuminance(background);
|
|
140
|
+
const lighter = Math.max(fg, bg);
|
|
141
|
+
const darker = Math.min(fg, bg);
|
|
142
|
+
return (lighter + 0.05) / (darker + 0.05);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// src/themes/builtin.ts
|
|
146
|
+
var colorHexSchema = z.string().refine(
|
|
147
|
+
(v) => {
|
|
148
|
+
try {
|
|
149
|
+
normalizeColor(v);
|
|
150
|
+
return true;
|
|
151
|
+
} catch {
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
{ message: "Expected #RRGGBB, #RRGGBBAA, rgb(), or rgba() color" }
|
|
156
|
+
).transform((v) => normalizeColor(v));
|
|
66
157
|
var fontFamilySchema = z.string().min(1).max(120);
|
|
67
158
|
var codeThemeSchema = z.object({
|
|
68
159
|
background: colorHexSchema,
|
|
@@ -235,7 +326,17 @@ var builtInThemes = {
|
|
|
235
326
|
var defaultTheme = builtInThemes.dark;
|
|
236
327
|
|
|
237
328
|
// src/spec.schema.ts
|
|
238
|
-
var colorHexSchema2 = z2.string().
|
|
329
|
+
var colorHexSchema2 = z2.string().refine(
|
|
330
|
+
(v) => {
|
|
331
|
+
try {
|
|
332
|
+
normalizeColor(v);
|
|
333
|
+
return true;
|
|
334
|
+
} catch {
|
|
335
|
+
return false;
|
|
336
|
+
}
|
|
337
|
+
},
|
|
338
|
+
{ message: "Expected #RRGGBB, #RRGGBBAA, rgb(), or rgba() color" }
|
|
339
|
+
).transform((v) => normalizeColor(v));
|
|
239
340
|
var gradientStopSchema = z2.object({
|
|
240
341
|
offset: z2.number().min(0).max(1),
|
|
241
342
|
color: colorHexSchema2
|
|
@@ -426,6 +527,9 @@ var flowNodeElementSchema = z2.object({
|
|
|
426
527
|
label: z2.string().min(1).max(200),
|
|
427
528
|
sublabel: z2.string().min(1).max(300).optional(),
|
|
428
529
|
sublabelColor: colorHexSchema2.optional(),
|
|
530
|
+
sublabel2: z2.string().min(1).max(300).optional(),
|
|
531
|
+
sublabel2Color: colorHexSchema2.optional(),
|
|
532
|
+
sublabel2FontSize: z2.number().min(8).max(32).optional(),
|
|
429
533
|
labelColor: colorHexSchema2.optional(),
|
|
430
534
|
labelFontSize: z2.number().min(10).max(48).optional(),
|
|
431
535
|
color: colorHexSchema2.optional(),
|
|
@@ -434,7 +538,12 @@ var flowNodeElementSchema = z2.object({
|
|
|
434
538
|
cornerRadius: z2.number().min(0).max(64).optional(),
|
|
435
539
|
width: z2.number().int().min(40).max(800).optional(),
|
|
436
540
|
height: z2.number().int().min(30).max(600).optional(),
|
|
437
|
-
|
|
541
|
+
fillOpacity: z2.number().min(0).max(1).default(1),
|
|
542
|
+
opacity: z2.number().min(0).max(1).default(1),
|
|
543
|
+
badgeText: z2.string().min(1).max(32).optional(),
|
|
544
|
+
badgeColor: colorHexSchema2.optional(),
|
|
545
|
+
badgeBackground: colorHexSchema2.optional(),
|
|
546
|
+
badgePosition: z2.enum(["top", "inside-top"]).default("inside-top")
|
|
438
547
|
}).strict();
|
|
439
548
|
var connectionElementSchema = z2.object({
|
|
440
549
|
type: z2.literal("connection"),
|
|
@@ -523,7 +632,15 @@ var autoLayoutConfigSchema = z2.object({
|
|
|
523
632
|
nodeSpacing: z2.number().int().min(0).max(512).default(80),
|
|
524
633
|
rankSpacing: z2.number().int().min(0).max(512).default(120),
|
|
525
634
|
edgeRouting: z2.enum(["orthogonal", "polyline", "spline"]).default("polyline"),
|
|
526
|
-
aspectRatio: z2.number().min(0.5).max(3).optional()
|
|
635
|
+
aspectRatio: z2.number().min(0.5).max(3).optional(),
|
|
636
|
+
/** ID of the root node for radial layout. Only relevant when algorithm is 'radial'. */
|
|
637
|
+
radialRoot: z2.string().min(1).max(120).optional(),
|
|
638
|
+
/** Fixed radius in pixels for radial layout. Only relevant when algorithm is 'radial'. */
|
|
639
|
+
radialRadius: z2.number().positive().optional(),
|
|
640
|
+
/** Compaction strategy for radial layout. Only relevant when algorithm is 'radial'. */
|
|
641
|
+
radialCompaction: z2.enum(["none", "radial", "wedge"]).optional(),
|
|
642
|
+
/** Sort strategy for radial layout node ordering. Only relevant when algorithm is 'radial'. */
|
|
643
|
+
radialSortBy: z2.enum(["id", "connections"]).optional()
|
|
527
644
|
}).strict();
|
|
528
645
|
var gridLayoutConfigSchema = z2.object({
|
|
529
646
|
mode: z2.literal("grid"),
|
|
@@ -627,43 +744,6 @@ function parseDesignSpec(input) {
|
|
|
627
744
|
return designSpecSchema.parse(input);
|
|
628
745
|
}
|
|
629
746
|
|
|
630
|
-
// src/utils/color.ts
|
|
631
|
-
function parseChannel(hex, offset) {
|
|
632
|
-
return Number.parseInt(hex.slice(offset, offset + 2), 16);
|
|
633
|
-
}
|
|
634
|
-
function parseHexColor(hexColor) {
|
|
635
|
-
const normalized = hexColor.startsWith("#") ? hexColor.slice(1) : hexColor;
|
|
636
|
-
if (normalized.length !== 6 && normalized.length !== 8) {
|
|
637
|
-
throw new Error(`Unsupported color format: ${hexColor}`);
|
|
638
|
-
}
|
|
639
|
-
return {
|
|
640
|
-
r: parseChannel(normalized, 0),
|
|
641
|
-
g: parseChannel(normalized, 2),
|
|
642
|
-
b: parseChannel(normalized, 4)
|
|
643
|
-
};
|
|
644
|
-
}
|
|
645
|
-
function srgbToLinear(channel) {
|
|
646
|
-
const normalized = channel / 255;
|
|
647
|
-
if (normalized <= 0.03928) {
|
|
648
|
-
return normalized / 12.92;
|
|
649
|
-
}
|
|
650
|
-
return ((normalized + 0.055) / 1.055) ** 2.4;
|
|
651
|
-
}
|
|
652
|
-
function relativeLuminance(hexColor) {
|
|
653
|
-
const rgb = parseHexColor(hexColor);
|
|
654
|
-
const r = srgbToLinear(rgb.r);
|
|
655
|
-
const g = srgbToLinear(rgb.g);
|
|
656
|
-
const b = srgbToLinear(rgb.b);
|
|
657
|
-
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
658
|
-
}
|
|
659
|
-
function contrastRatio(foreground, background) {
|
|
660
|
-
const fg = relativeLuminance(foreground);
|
|
661
|
-
const bg = relativeLuminance(background);
|
|
662
|
-
const lighter = Math.max(fg, bg);
|
|
663
|
-
const darker = Math.min(fg, bg);
|
|
664
|
-
return (lighter + 0.05) / (darker + 0.05);
|
|
665
|
-
}
|
|
666
|
-
|
|
667
747
|
// src/qa.ts
|
|
668
748
|
function rectWithin(outer, inner) {
|
|
669
749
|
return inner.x >= outer.x && inner.y >= outer.y && inner.x + inner.width <= outer.x + outer.width && inner.y + inner.height <= outer.y + outer.height;
|
package/dist/renderer.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { h as DEFAULT_GENERATOR_VERSION, z as LayoutSnapshot, a as Rect, R as RenderMetadata, J as RenderResult, d as RenderedElement, W as WrittenArtifacts, X as computeSpecHash, a9 as inferSidecarPath, ab as renderDesign, ad as writeRenderArtifacts } from './spec.schema-
|
|
1
|
+
export { h as DEFAULT_GENERATOR_VERSION, z as LayoutSnapshot, a as Rect, R as RenderMetadata, J as RenderResult, d as RenderedElement, W as WrittenArtifacts, X as computeSpecHash, a9 as inferSidecarPath, ab as renderDesign, ad as writeRenderArtifacts } from './spec.schema-BUTof436.js';
|
|
2
2
|
import 'zod';
|
|
3
3
|
import '@napi-rs/canvas';
|