@sxl-studio/storybook-addon 1.1.1 → 1.1.2
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/CHANGELOG.md +7 -0
- package/README.md +3 -2
- package/dist/index.d.ts +1 -1
- package/dist/manager.js +329 -284
- package/dist/preset.js +35 -2
- package/package.json +10 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
- **Embed debug:** `parameters.sxl.debugFigmaEmbed` now works in runtime. The panel logs resolved embed diagnostics and iframe load/error events when enabled.
|
|
6
|
+
- **Token status compatibility:** `sxl.tokenStatus` now maps to `tokensBool` in runtime (deprecated but supported), so documented override behavior is consistent.
|
|
7
|
+
- **CSP merge hardening:** `mergeSxlFigmaFrameSrcHeader` now merges into an existing `frame-src` directive instead of appending raw strings.
|
|
8
|
+
- **Quality gates:** added ESLint pipeline and unit tests for entry resolution, composition loading fallback chain, and Storybook preset behavior.
|
|
9
|
+
|
|
3
10
|
## 1.1.1
|
|
4
11
|
|
|
5
12
|
- **Registry matching:** removed the behavior where a **single** registry entry was applied to **every** story. Matching now always uses the same story-context heuristic (or explicit `sxl.component` / `sxl.figmaNodeId`). Unrelated stories show the “no Figma integration” state.
|
package/README.md
CHANGED
|
@@ -175,13 +175,14 @@ export const Default = {
|
|
|
175
175
|
| `sxl.figmaNodeId` | `string` | Match entry by Figma node ID |
|
|
176
176
|
| `sxl.figmaUrl` | `string` | Direct Figma URL (no registry needed) |
|
|
177
177
|
| `sxl.description` | `string` | Override description |
|
|
178
|
-
| `sxl.
|
|
178
|
+
| `sxl.tokensBool` | `"true" \| "false"` | Explicit override for Tokens badge |
|
|
179
|
+
| `sxl.tokenStatus` | `"assigned" \| "partial" \| "none"` | Deprecated compatibility override; internally mapped to `tokensBool` |
|
|
179
180
|
| `sxl.readiness` | `"complete" \| "ready-for-dev" \| "in-progress" \| "backlog"` | Override readiness |
|
|
180
181
|
| `sxl.compositionSources` | `Record<string, string>` | Map repo-relative composition paths → raw JSON (recommended) |
|
|
181
182
|
| `sxl.compositionFetchBaseUrl` | `string` | Base URL to `fetch()` composition by path (e.g. static dir) |
|
|
182
183
|
| `sxl.compositionDevProxyPrefix` | `string` | Same-origin prefix (e.g. Vite proxy) so fetches avoid CORS to private Git |
|
|
183
184
|
| `sxl.resolveComposition` | `(path) => Promise<string \| undefined>` | Custom loader for composition file content |
|
|
184
|
-
| `sxl.debugFigmaEmbed` | `boolean` | Log embed
|
|
185
|
+
| `sxl.debugFigmaEmbed` | `boolean` | Log embed diagnostics (`resolved`, `iframe-load`, `iframe-error`) to the console |
|
|
185
186
|
|
|
186
187
|
## Composition JSON (repo file, not inlined in diff)
|
|
187
188
|
|
package/dist/index.d.ts
CHANGED
|
@@ -92,7 +92,7 @@ type SxlStoryParameters = {
|
|
|
92
92
|
figmaUrl?: string;
|
|
93
93
|
/** Direct description (overrides registry). */
|
|
94
94
|
description?: string;
|
|
95
|
-
/**
|
|
95
|
+
/** @deprecated Prefer `tokensBool`; kept for backward compatibility and mapped to `tokensBool` internally. */
|
|
96
96
|
tokenStatus?: SxlTokenStatus;
|
|
97
97
|
tokensBool?: SxlTokensBool;
|
|
98
98
|
/** Override readiness per-story. */
|
package/dist/manager.js
CHANGED
|
@@ -12,123 +12,6 @@ var SXL_TOKENS_URL_PREFIX = "/sxl-tokens";
|
|
|
12
12
|
import React2, { useCallback as useCallback3, useEffect, useState } from "react";
|
|
13
13
|
import { useParameter, useStorybookState } from "@storybook/manager-api";
|
|
14
14
|
|
|
15
|
-
// src/convert.ts
|
|
16
|
-
function fromDiffCodeConnect(data) {
|
|
17
|
-
if (!data || typeof data !== "object") {
|
|
18
|
-
return { version: 1, figmaFileKey: "", entries: [] };
|
|
19
|
-
}
|
|
20
|
-
const d = data;
|
|
21
|
-
const fileKey = typeof d.$figmaFileKey === "string" ? d.$figmaFileKey : "";
|
|
22
|
-
const fileName = typeof d.$figmaFileName === "string" ? d.$figmaFileName : void 0;
|
|
23
|
-
const repository = d.repository && typeof d.repository === "object" ? d.repository : void 0;
|
|
24
|
-
const isV2 = Array.isArray(d.components);
|
|
25
|
-
const entries = isV2 ? convertV2Components(d.components, fileKey) : convertV1Entries(Array.isArray(d.entries) ? d.entries : [], fileKey);
|
|
26
|
-
return {
|
|
27
|
-
version: 1,
|
|
28
|
-
figmaFileKey: fileKey,
|
|
29
|
-
figmaFileName: fileName,
|
|
30
|
-
repository: repository ? {
|
|
31
|
-
...typeof repository.url === "string" ? { url: repository.url } : {},
|
|
32
|
-
...typeof repository.documentationUrl === "string" ? { documentationUrl: repository.documentationUrl } : {}
|
|
33
|
-
} : void 0,
|
|
34
|
-
entries
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
function tokensBoolFromStorybook(sb) {
|
|
38
|
-
if (sb.tokensReady === true) return "true";
|
|
39
|
-
return "false";
|
|
40
|
-
}
|
|
41
|
-
function convertV2Components(components, fileKey) {
|
|
42
|
-
return components.filter((c) => c.linked === true).map((c) => {
|
|
43
|
-
const nodeId = String(c.nodeId ?? "");
|
|
44
|
-
const sb = c.storybook ?? {};
|
|
45
|
-
const cc = c.codeConnect ?? {};
|
|
46
|
-
const figmaUrl = typeof sb.figmaUrl === "string" && sb.figmaUrl.trim() ? sb.figmaUrl.trim() : fileKey ? `https://www.figma.com/design/${fileKey}?node-id=${nodeId.replace(":", "-")}` : void 0;
|
|
47
|
-
const statusRaw = typeof sb.status === "string" ? sb.status : void 0;
|
|
48
|
-
const readiness = statusRaw === "complete" || statusRaw === "ready-for-dev" || statusRaw === "in-progress" || statusRaw === "backlog" ? statusRaw : void 0;
|
|
49
|
-
const files = parseFiles(Array.isArray(cc.files) ? cc.files : []);
|
|
50
|
-
const componentApi = parseComponentApi(sb.componentApi);
|
|
51
|
-
return {
|
|
52
|
-
nodeId,
|
|
53
|
-
displayName: String(c.displayName ?? c.name ?? ""),
|
|
54
|
-
description: sb.description ? String(sb.description) : void 0,
|
|
55
|
-
figmaUrl,
|
|
56
|
-
designEmbed: sb.designEmbed === true,
|
|
57
|
-
compositionJson: sb.compositionJson === true,
|
|
58
|
-
metadata: sb.metadata === true,
|
|
59
|
-
updatedAt: typeof c.updatedAt === "string" ? c.updatedAt : void 0,
|
|
60
|
-
importPath: typeof cc.importPath === "string" ? cc.importPath : void 0,
|
|
61
|
-
snippetTemplate: typeof cc.snippetTemplate === "string" ? cc.snippetTemplate : void 0,
|
|
62
|
-
files: files.length > 0 ? files : void 0,
|
|
63
|
-
compositionFilePath: typeof sb.compositionFilePath === "string" ? sb.compositionFilePath : void 0,
|
|
64
|
-
compositionSnapshot: typeof sb.compositionSnapshot === "string" ? sb.compositionSnapshot : void 0,
|
|
65
|
-
compositionName: typeof sb.compositionName === "string" ? sb.compositionName : void 0,
|
|
66
|
-
componentApi,
|
|
67
|
-
meta: {
|
|
68
|
-
tokensBool: tokensBoolFromStorybook(sb),
|
|
69
|
-
...readiness ? { readiness } : {}
|
|
70
|
-
}
|
|
71
|
-
};
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
function parseComponentApi(raw) {
|
|
75
|
-
if (!raw || typeof raw !== "object") return void 0;
|
|
76
|
-
const o = raw;
|
|
77
|
-
if (!Array.isArray(o.properties)) return void 0;
|
|
78
|
-
const properties = o.properties.filter((p) => !!p && typeof p === "object").map((p) => ({
|
|
79
|
-
name: String(p.name ?? ""),
|
|
80
|
-
kind: String(p.kind ?? ""),
|
|
81
|
-
...p.defaultValue !== void 0 ? { defaultValue: p.defaultValue } : {},
|
|
82
|
-
...Array.isArray(p.options) ? { options: p.options.map(String) } : {}
|
|
83
|
-
}));
|
|
84
|
-
return properties.length ? { properties } : void 0;
|
|
85
|
-
}
|
|
86
|
-
function convertV1Entries(rawEntries, fileKey) {
|
|
87
|
-
return rawEntries.filter((e) => {
|
|
88
|
-
if (!e || typeof e !== "object") return false;
|
|
89
|
-
const o = e;
|
|
90
|
-
return o.linked === true && !!o.binding;
|
|
91
|
-
}).map((e) => {
|
|
92
|
-
const b = e.binding;
|
|
93
|
-
const sb = b.storybook ?? {};
|
|
94
|
-
const nodeId = String(e.nodeId ?? "");
|
|
95
|
-
const figmaUrl = typeof sb.figmaUrl === "string" && sb.figmaUrl.trim() ? sb.figmaUrl.trim() : fileKey ? `https://www.figma.com/design/${fileKey}?node-id=${nodeId.replace(":", "-")}` : void 0;
|
|
96
|
-
const statusRaw = typeof sb.status === "string" ? sb.status : void 0;
|
|
97
|
-
const readiness = statusRaw === "complete" || statusRaw === "ready-for-dev" || statusRaw === "in-progress" || statusRaw === "backlog" ? statusRaw : void 0;
|
|
98
|
-
const files = parseFiles(Array.isArray(b.files) ? b.files : []);
|
|
99
|
-
const componentApi = parseComponentApi(sb.componentApi);
|
|
100
|
-
return {
|
|
101
|
-
nodeId,
|
|
102
|
-
displayName: String(b.displayName ?? e.nodeName ?? ""),
|
|
103
|
-
description: sb.description ? String(sb.description) : void 0,
|
|
104
|
-
figmaUrl,
|
|
105
|
-
designEmbed: sb.designEmbed === true,
|
|
106
|
-
compositionJson: sb.compositionJson === true,
|
|
107
|
-
metadata: sb.metadata === true,
|
|
108
|
-
updatedAt: typeof e.updatedAt === "string" ? e.updatedAt : void 0,
|
|
109
|
-
importPath: typeof b.importPath === "string" ? b.importPath : void 0,
|
|
110
|
-
snippetTemplate: typeof b.snippetTemplate === "string" ? b.snippetTemplate : void 0,
|
|
111
|
-
files: files.length > 0 ? files : void 0,
|
|
112
|
-
compositionFilePath: typeof sb.compositionFilePath === "string" ? sb.compositionFilePath : void 0,
|
|
113
|
-
compositionSnapshot: typeof sb.compositionSnapshot === "string" ? sb.compositionSnapshot : void 0,
|
|
114
|
-
compositionName: typeof sb.compositionName === "string" ? sb.compositionName : void 0,
|
|
115
|
-
componentApi,
|
|
116
|
-
meta: {
|
|
117
|
-
tokensBool: tokensBoolFromStorybook(sb),
|
|
118
|
-
...readiness ? { readiness } : {}
|
|
119
|
-
}
|
|
120
|
-
};
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
function parseFiles(raw) {
|
|
124
|
-
return raw.filter((f) => !!f && typeof f === "object").map((f) => ({
|
|
125
|
-
framework: String(f.framework ?? ""),
|
|
126
|
-
filePath: String(f.filePath ?? ""),
|
|
127
|
-
...f.componentName ? { componentName: String(f.componentName) } : {},
|
|
128
|
-
...f.importPath ? { importPath: String(f.importPath) } : {}
|
|
129
|
-
}));
|
|
130
|
-
}
|
|
131
|
-
|
|
132
15
|
// src/compositionLoad.ts
|
|
133
16
|
function normalizeCompositionPath(p) {
|
|
134
17
|
return p.trim().replace(/\\/g, "/").replace(/^\/+/, "");
|
|
@@ -278,28 +161,304 @@ async function loadCompositionJsonText(path, legacyInline, params) {
|
|
|
278
161
|
};
|
|
279
162
|
}
|
|
280
163
|
}
|
|
281
|
-
const sxlPresetUrls = candidateRelativePaths(p).map((rel) => joinFetchUrl(SXL_TOKENS_URL_PREFIX, rel));
|
|
282
|
-
const sxlPresetHit = await fetchFirstOk(sxlPresetUrls);
|
|
283
|
-
if (sxlPresetHit) {
|
|
284
|
-
return { text: sxlPresetHit.text, source: "fetch" };
|
|
164
|
+
const sxlPresetUrls = candidateRelativePaths(p).map((rel) => joinFetchUrl(SXL_TOKENS_URL_PREFIX, rel));
|
|
165
|
+
const sxlPresetHit = await fetchFirstOk(sxlPresetUrls);
|
|
166
|
+
if (sxlPresetHit) {
|
|
167
|
+
return { text: sxlPresetHit.text, source: "fetch" };
|
|
168
|
+
}
|
|
169
|
+
const repoUrl = extractRepositoryUrl(params);
|
|
170
|
+
if (repoUrl) {
|
|
171
|
+
const gitlabCandidates = buildGitlabRawCandidates(repoUrl, p);
|
|
172
|
+
const hit = await fetchFirstOk(gitlabCandidates);
|
|
173
|
+
if (hit) {
|
|
174
|
+
return { text: hit.text, source: "fetch" };
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
const localCandidates = candidateRelativePaths(p).map((rel) => `/${rel}`);
|
|
178
|
+
const localHit = await fetchFirstOk(localCandidates);
|
|
179
|
+
if (localHit) {
|
|
180
|
+
return { text: localHit.text, source: "fetch" };
|
|
181
|
+
}
|
|
182
|
+
return {
|
|
183
|
+
error: "Composition file is not inlined and auto-fetch failed. Use compositionSources, compositionFetchBaseUrl + staticDirs, resolveComposition(), or compositionDevProxyPrefix (same-origin proxy; private Git hosts usually block CORS) \u2014 see @sxl-studio/storybook-addon README."
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// src/convert.ts
|
|
188
|
+
function fromDiffCodeConnect(data) {
|
|
189
|
+
if (!data || typeof data !== "object") {
|
|
190
|
+
return { version: 1, figmaFileKey: "", entries: [] };
|
|
191
|
+
}
|
|
192
|
+
const d = data;
|
|
193
|
+
const fileKey = typeof d.$figmaFileKey === "string" ? d.$figmaFileKey : "";
|
|
194
|
+
const fileName = typeof d.$figmaFileName === "string" ? d.$figmaFileName : void 0;
|
|
195
|
+
const repository = d.repository && typeof d.repository === "object" ? d.repository : void 0;
|
|
196
|
+
const isV2 = Array.isArray(d.components);
|
|
197
|
+
const entries = isV2 ? convertV2Components(d.components, fileKey) : convertV1Entries(Array.isArray(d.entries) ? d.entries : [], fileKey);
|
|
198
|
+
return {
|
|
199
|
+
version: 1,
|
|
200
|
+
figmaFileKey: fileKey,
|
|
201
|
+
figmaFileName: fileName,
|
|
202
|
+
repository: repository ? {
|
|
203
|
+
...typeof repository.url === "string" ? { url: repository.url } : {},
|
|
204
|
+
...typeof repository.documentationUrl === "string" ? { documentationUrl: repository.documentationUrl } : {}
|
|
205
|
+
} : void 0,
|
|
206
|
+
entries
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
function tokensBoolFromStorybook(sb) {
|
|
210
|
+
if (sb.tokensReady === true) return "true";
|
|
211
|
+
return "false";
|
|
212
|
+
}
|
|
213
|
+
function convertV2Components(components, fileKey) {
|
|
214
|
+
return components.filter((c) => c.linked === true).map((c) => {
|
|
215
|
+
const nodeId = String(c.nodeId ?? "");
|
|
216
|
+
const sb = c.storybook ?? {};
|
|
217
|
+
const cc = c.codeConnect ?? {};
|
|
218
|
+
const figmaUrl = typeof sb.figmaUrl === "string" && sb.figmaUrl.trim() ? sb.figmaUrl.trim() : fileKey ? `https://www.figma.com/design/${fileKey}?node-id=${nodeId.replace(":", "-")}` : void 0;
|
|
219
|
+
const statusRaw = typeof sb.status === "string" ? sb.status : void 0;
|
|
220
|
+
const readiness = statusRaw === "complete" || statusRaw === "ready-for-dev" || statusRaw === "in-progress" || statusRaw === "backlog" ? statusRaw : void 0;
|
|
221
|
+
const files = parseFiles(Array.isArray(cc.files) ? cc.files : []);
|
|
222
|
+
const componentApi = parseComponentApi(sb.componentApi);
|
|
223
|
+
return {
|
|
224
|
+
nodeId,
|
|
225
|
+
displayName: String(c.displayName ?? c.name ?? ""),
|
|
226
|
+
description: sb.description ? String(sb.description) : void 0,
|
|
227
|
+
figmaUrl,
|
|
228
|
+
designEmbed: sb.designEmbed === true,
|
|
229
|
+
compositionJson: sb.compositionJson === true,
|
|
230
|
+
metadata: sb.metadata === true,
|
|
231
|
+
updatedAt: typeof c.updatedAt === "string" ? c.updatedAt : void 0,
|
|
232
|
+
importPath: typeof cc.importPath === "string" ? cc.importPath : void 0,
|
|
233
|
+
snippetTemplate: typeof cc.snippetTemplate === "string" ? cc.snippetTemplate : void 0,
|
|
234
|
+
files: files.length > 0 ? files : void 0,
|
|
235
|
+
compositionFilePath: typeof sb.compositionFilePath === "string" ? sb.compositionFilePath : void 0,
|
|
236
|
+
compositionSnapshot: typeof sb.compositionSnapshot === "string" ? sb.compositionSnapshot : void 0,
|
|
237
|
+
compositionName: typeof sb.compositionName === "string" ? sb.compositionName : void 0,
|
|
238
|
+
componentApi,
|
|
239
|
+
meta: {
|
|
240
|
+
tokensBool: tokensBoolFromStorybook(sb),
|
|
241
|
+
...readiness ? { readiness } : {}
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
function parseComponentApi(raw) {
|
|
247
|
+
if (!raw || typeof raw !== "object") return void 0;
|
|
248
|
+
const o = raw;
|
|
249
|
+
if (!Array.isArray(o.properties)) return void 0;
|
|
250
|
+
const properties = o.properties.filter((p) => !!p && typeof p === "object").map((p) => ({
|
|
251
|
+
name: String(p.name ?? ""),
|
|
252
|
+
kind: String(p.kind ?? ""),
|
|
253
|
+
...p.defaultValue !== void 0 ? { defaultValue: p.defaultValue } : {},
|
|
254
|
+
...Array.isArray(p.options) ? { options: p.options.map(String) } : {}
|
|
255
|
+
}));
|
|
256
|
+
return properties.length ? { properties } : void 0;
|
|
257
|
+
}
|
|
258
|
+
function convertV1Entries(rawEntries, fileKey) {
|
|
259
|
+
return rawEntries.filter((e) => {
|
|
260
|
+
if (!e || typeof e !== "object") return false;
|
|
261
|
+
const o = e;
|
|
262
|
+
return o.linked === true && !!o.binding;
|
|
263
|
+
}).map((e) => {
|
|
264
|
+
const b = e.binding;
|
|
265
|
+
const sb = b.storybook ?? {};
|
|
266
|
+
const nodeId = String(e.nodeId ?? "");
|
|
267
|
+
const figmaUrl = typeof sb.figmaUrl === "string" && sb.figmaUrl.trim() ? sb.figmaUrl.trim() : fileKey ? `https://www.figma.com/design/${fileKey}?node-id=${nodeId.replace(":", "-")}` : void 0;
|
|
268
|
+
const statusRaw = typeof sb.status === "string" ? sb.status : void 0;
|
|
269
|
+
const readiness = statusRaw === "complete" || statusRaw === "ready-for-dev" || statusRaw === "in-progress" || statusRaw === "backlog" ? statusRaw : void 0;
|
|
270
|
+
const files = parseFiles(Array.isArray(b.files) ? b.files : []);
|
|
271
|
+
const componentApi = parseComponentApi(sb.componentApi);
|
|
272
|
+
return {
|
|
273
|
+
nodeId,
|
|
274
|
+
displayName: String(b.displayName ?? e.nodeName ?? ""),
|
|
275
|
+
description: sb.description ? String(sb.description) : void 0,
|
|
276
|
+
figmaUrl,
|
|
277
|
+
designEmbed: sb.designEmbed === true,
|
|
278
|
+
compositionJson: sb.compositionJson === true,
|
|
279
|
+
metadata: sb.metadata === true,
|
|
280
|
+
updatedAt: typeof e.updatedAt === "string" ? e.updatedAt : void 0,
|
|
281
|
+
importPath: typeof b.importPath === "string" ? b.importPath : void 0,
|
|
282
|
+
snippetTemplate: typeof b.snippetTemplate === "string" ? b.snippetTemplate : void 0,
|
|
283
|
+
files: files.length > 0 ? files : void 0,
|
|
284
|
+
compositionFilePath: typeof sb.compositionFilePath === "string" ? sb.compositionFilePath : void 0,
|
|
285
|
+
compositionSnapshot: typeof sb.compositionSnapshot === "string" ? sb.compositionSnapshot : void 0,
|
|
286
|
+
compositionName: typeof sb.compositionName === "string" ? sb.compositionName : void 0,
|
|
287
|
+
componentApi,
|
|
288
|
+
meta: {
|
|
289
|
+
tokensBool: tokensBoolFromStorybook(sb),
|
|
290
|
+
...readiness ? { readiness } : {}
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
function parseFiles(raw) {
|
|
296
|
+
return raw.filter((f) => !!f && typeof f === "object").map((f) => ({
|
|
297
|
+
framework: String(f.framework ?? ""),
|
|
298
|
+
filePath: String(f.filePath ?? ""),
|
|
299
|
+
...f.componentName ? { componentName: String(f.componentName) } : {},
|
|
300
|
+
...f.importPath ? { importPath: String(f.importPath) } : {}
|
|
301
|
+
}));
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// src/entryResolve.ts
|
|
305
|
+
function getRegistryFigmaFileKey(raw) {
|
|
306
|
+
if (!raw || typeof raw !== "object") return "";
|
|
307
|
+
const fk = raw.figmaFileKey;
|
|
308
|
+
return typeof fk === "string" ? fk.trim() : "";
|
|
309
|
+
}
|
|
310
|
+
function buildFigmaDesignUrlFromFileKey(fileKey, nodeId) {
|
|
311
|
+
if (!fileKey || !nodeId) return void 0;
|
|
312
|
+
return `https://www.figma.com/design/${fileKey}?node-id=${String(nodeId).replace(/:/g, "-")}`;
|
|
313
|
+
}
|
|
314
|
+
function tokenStatusToTokensBool(status) {
|
|
315
|
+
if (status === "assigned") return "true";
|
|
316
|
+
if (status === "partial" || status === "none") return "false";
|
|
317
|
+
return void 0;
|
|
318
|
+
}
|
|
319
|
+
function normalizeRegistry(raw) {
|
|
320
|
+
if (!raw || typeof raw !== "object") return null;
|
|
321
|
+
const obj = raw;
|
|
322
|
+
if (Array.isArray(obj.components)) {
|
|
323
|
+
return fromDiffCodeConnect(raw);
|
|
324
|
+
}
|
|
325
|
+
const entries = Array.isArray(obj.entries) ? obj.entries : [];
|
|
326
|
+
if (entries.length === 0) return null;
|
|
327
|
+
const first = entries[0];
|
|
328
|
+
if (first && "binding" in first && "linked" in first) {
|
|
329
|
+
return fromDiffCodeConnect(raw);
|
|
330
|
+
}
|
|
331
|
+
if (first && "nodeId" in first && "displayName" in first) {
|
|
332
|
+
return raw;
|
|
333
|
+
}
|
|
334
|
+
return null;
|
|
335
|
+
}
|
|
336
|
+
function resolveEntry(params, ctx) {
|
|
337
|
+
if (!params) return { status: "no-registry" };
|
|
338
|
+
const registry = normalizeRegistry(params.registry);
|
|
339
|
+
const tokensOverride = params.tokensBool ?? tokenStatusToTokensBool(params.tokenStatus);
|
|
340
|
+
if (params.figmaUrl || params.description) {
|
|
341
|
+
return {
|
|
342
|
+
status: "found",
|
|
343
|
+
entry: {
|
|
344
|
+
nodeId: params.figmaNodeId ?? "",
|
|
345
|
+
displayName: params.component ?? params.componentName ?? "",
|
|
346
|
+
description: params.description,
|
|
347
|
+
figmaUrl: params.figmaUrl,
|
|
348
|
+
designEmbed: params.designEmbed ?? !!params.figmaUrl,
|
|
349
|
+
compositionJson: params.compositionJson,
|
|
350
|
+
metadata: params.metadata,
|
|
351
|
+
meta: {
|
|
352
|
+
tokensBool: tokensOverride,
|
|
353
|
+
readiness: params.readiness,
|
|
354
|
+
tokenStatus: params.tokenStatus
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
if (!registry) return { status: "no-registry" };
|
|
360
|
+
let found;
|
|
361
|
+
if (params.figmaNodeId) {
|
|
362
|
+
found = registry.entries.find((e) => e.nodeId === params.figmaNodeId);
|
|
285
363
|
}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
if (hit) {
|
|
291
|
-
return { text: hit.text, source: "fetch" };
|
|
364
|
+
if (!found) {
|
|
365
|
+
const name = (params.component ?? params.componentName ?? "").toLowerCase();
|
|
366
|
+
if (name) {
|
|
367
|
+
found = registry.entries.find((e) => e.displayName.toLowerCase() === name);
|
|
292
368
|
}
|
|
293
369
|
}
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
if (localHit) {
|
|
297
|
-
return { text: localHit.text, source: "fetch" };
|
|
370
|
+
if (!found) {
|
|
371
|
+
found = matchByStoryContext(registry.entries, ctx);
|
|
298
372
|
}
|
|
373
|
+
if (!found) {
|
|
374
|
+
const hint = extractComponentName(ctx.title) || ctx.name || "";
|
|
375
|
+
return { status: "no-match", componentHint: hint };
|
|
376
|
+
}
|
|
377
|
+
const rootFileKey = getRegistryFigmaFileKey(params.registry);
|
|
378
|
+
const figmaUrlResolved = found.figmaUrl ?? buildFigmaDesignUrlFromFileKey(rootFileKey, found.nodeId);
|
|
299
379
|
return {
|
|
300
|
-
|
|
380
|
+
status: "found",
|
|
381
|
+
entry: {
|
|
382
|
+
...found,
|
|
383
|
+
figmaUrl: figmaUrlResolved,
|
|
384
|
+
meta: {
|
|
385
|
+
...found.meta,
|
|
386
|
+
tokensBool: tokensOverride ?? found.meta?.tokensBool ?? tokenStatusToTokensBool(found.meta?.tokenStatus),
|
|
387
|
+
tokenStatus: params.tokenStatus ?? found.meta?.tokenStatus,
|
|
388
|
+
readiness: params.readiness ?? found.meta?.readiness
|
|
389
|
+
},
|
|
390
|
+
description: params.description ?? found.description,
|
|
391
|
+
compositionJson: params.compositionJson ?? found.compositionJson,
|
|
392
|
+
metadata: params.metadata ?? found.metadata,
|
|
393
|
+
designEmbed: params.designEmbed ?? found.designEmbed,
|
|
394
|
+
compositionFilePath: found.compositionFilePath,
|
|
395
|
+
compositionSnapshot: found.compositionSnapshot,
|
|
396
|
+
componentApi: found.componentApi
|
|
397
|
+
}
|
|
301
398
|
};
|
|
302
399
|
}
|
|
400
|
+
function resolveTokensBool(entry, params) {
|
|
401
|
+
const explicit = params?.tokensBool ?? tokenStatusToTokensBool(params?.tokenStatus);
|
|
402
|
+
if (explicit) return explicit;
|
|
403
|
+
const fromEntry = entry.meta?.tokensBool ?? tokenStatusToTokensBool(entry.meta?.tokenStatus);
|
|
404
|
+
if (fromEntry) return fromEntry;
|
|
405
|
+
return "false";
|
|
406
|
+
}
|
|
407
|
+
function matchByStoryContext(entries, ctx) {
|
|
408
|
+
const tokens = [
|
|
409
|
+
extractComponentName(ctx.title),
|
|
410
|
+
ctx.name,
|
|
411
|
+
ctx.storyId,
|
|
412
|
+
ctx.importPath,
|
|
413
|
+
fileStem(ctx.importPath)
|
|
414
|
+
].filter(Boolean).map(norm);
|
|
415
|
+
if (tokens.length === 0) return void 0;
|
|
416
|
+
let best;
|
|
417
|
+
let bestScore = 0;
|
|
418
|
+
let tie = false;
|
|
419
|
+
for (const entry of entries) {
|
|
420
|
+
const sc = score(entry, tokens);
|
|
421
|
+
if (sc > bestScore) {
|
|
422
|
+
bestScore = sc;
|
|
423
|
+
best = entry;
|
|
424
|
+
tie = false;
|
|
425
|
+
} else if (sc === bestScore && sc > 0) {
|
|
426
|
+
tie = true;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
if (tie || bestScore < 60) return void 0;
|
|
430
|
+
return best;
|
|
431
|
+
}
|
|
432
|
+
function score(entry, tokens) {
|
|
433
|
+
const display = norm(entry.displayName);
|
|
434
|
+
const imprt = norm(entry.importPath ?? "");
|
|
435
|
+
const stems = (entry.files ?? []).map((f) => norm(fileStem(f.filePath))).filter(Boolean);
|
|
436
|
+
let s = 0;
|
|
437
|
+
for (const t of tokens) {
|
|
438
|
+
if (!t) continue;
|
|
439
|
+
if (display && t === display) s += 120;
|
|
440
|
+
else if (display && t.includes(display)) s += 70;
|
|
441
|
+
else if (display && display.includes(t)) s += 65;
|
|
442
|
+
if (imprt && t.includes(imprt)) s += 55;
|
|
443
|
+
for (const stem of stems) {
|
|
444
|
+
if (stem && t.includes(stem)) s += 30;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
return s;
|
|
448
|
+
}
|
|
449
|
+
function extractComponentName(title) {
|
|
450
|
+
if (!title) return "";
|
|
451
|
+
const parts = title.split("/");
|
|
452
|
+
return parts[parts.length - 1].trim();
|
|
453
|
+
}
|
|
454
|
+
function norm(v) {
|
|
455
|
+
return v.toLowerCase().replace(/[^a-z0-9]+/g, "");
|
|
456
|
+
}
|
|
457
|
+
function fileStem(p) {
|
|
458
|
+
const f = (p || "").split("/").pop() ?? "";
|
|
459
|
+
const d = f.lastIndexOf(".");
|
|
460
|
+
return d > 0 ? f.slice(0, d) : f;
|
|
461
|
+
}
|
|
303
462
|
|
|
304
463
|
// src/components/JsonHighlighted.tsx
|
|
305
464
|
import React from "react";
|
|
@@ -3642,154 +3801,12 @@ var JsonHighlighted = ({ code }) => {
|
|
|
3642
3801
|
};
|
|
3643
3802
|
|
|
3644
3803
|
// src/components/SxlPanel.tsx
|
|
3645
|
-
function getRegistryFigmaFileKey(raw) {
|
|
3646
|
-
if (!raw || typeof raw !== "object") return "";
|
|
3647
|
-
const fk = raw.figmaFileKey;
|
|
3648
|
-
return typeof fk === "string" ? fk.trim() : "";
|
|
3649
|
-
}
|
|
3650
|
-
function buildFigmaDesignUrlFromFileKey(fileKey, nodeId) {
|
|
3651
|
-
if (!fileKey || !nodeId) return void 0;
|
|
3652
|
-
return `https://www.figma.com/design/${fileKey}?node-id=${String(nodeId).replace(/:/g, "-")}`;
|
|
3653
|
-
}
|
|
3654
3804
|
var READINESS_BADGE = {
|
|
3655
3805
|
backlog: { label: "Backlog", bg: "#111827", fg: "#f9fafb" },
|
|
3656
3806
|
"in-progress": { label: "In Progress", bg: "#ea580c", fg: "#ffffff" },
|
|
3657
3807
|
"ready-for-dev": { label: "Ready for Dev", bg: "#16a34a", fg: "#ffffff" },
|
|
3658
3808
|
complete: { label: "Completed", bg: "#9ca3af", fg: "#111827" }
|
|
3659
3809
|
};
|
|
3660
|
-
function normalizeRegistry(raw) {
|
|
3661
|
-
if (!raw || typeof raw !== "object") return null;
|
|
3662
|
-
const obj = raw;
|
|
3663
|
-
if (Array.isArray(obj.components)) {
|
|
3664
|
-
return fromDiffCodeConnect(raw);
|
|
3665
|
-
}
|
|
3666
|
-
const entries = Array.isArray(obj.entries) ? obj.entries : [];
|
|
3667
|
-
if (entries.length === 0) return null;
|
|
3668
|
-
const first = entries[0];
|
|
3669
|
-
if (first && "binding" in first && "linked" in first) {
|
|
3670
|
-
return fromDiffCodeConnect(raw);
|
|
3671
|
-
}
|
|
3672
|
-
if (first && "nodeId" in first && "displayName" in first) {
|
|
3673
|
-
return raw;
|
|
3674
|
-
}
|
|
3675
|
-
return null;
|
|
3676
|
-
}
|
|
3677
|
-
function resolveEntry(params, ctx) {
|
|
3678
|
-
if (!params) return { status: "no-registry" };
|
|
3679
|
-
const registry = normalizeRegistry(params.registry);
|
|
3680
|
-
if (params.figmaUrl || params.description) {
|
|
3681
|
-
return {
|
|
3682
|
-
status: "found",
|
|
3683
|
-
entry: {
|
|
3684
|
-
nodeId: params.figmaNodeId ?? "",
|
|
3685
|
-
displayName: params.component ?? params.componentName ?? "",
|
|
3686
|
-
description: params.description,
|
|
3687
|
-
figmaUrl: params.figmaUrl,
|
|
3688
|
-
designEmbed: params.designEmbed ?? !!params.figmaUrl,
|
|
3689
|
-
compositionJson: params.compositionJson,
|
|
3690
|
-
metadata: params.metadata,
|
|
3691
|
-
meta: {
|
|
3692
|
-
tokensBool: params.tokensBool,
|
|
3693
|
-
readiness: params.readiness
|
|
3694
|
-
}
|
|
3695
|
-
}
|
|
3696
|
-
};
|
|
3697
|
-
}
|
|
3698
|
-
if (!registry) return { status: "no-registry" };
|
|
3699
|
-
let found;
|
|
3700
|
-
if (params.figmaNodeId) {
|
|
3701
|
-
found = registry.entries.find((e) => e.nodeId === params.figmaNodeId);
|
|
3702
|
-
}
|
|
3703
|
-
if (!found) {
|
|
3704
|
-
const name = (params.component ?? params.componentName ?? "").toLowerCase();
|
|
3705
|
-
if (name) {
|
|
3706
|
-
found = registry.entries.find((e) => e.displayName.toLowerCase() === name);
|
|
3707
|
-
}
|
|
3708
|
-
}
|
|
3709
|
-
if (!found) {
|
|
3710
|
-
found = matchByStoryContext(registry.entries, ctx);
|
|
3711
|
-
}
|
|
3712
|
-
if (!found) {
|
|
3713
|
-
const hint = extractComponentName(ctx.title) || ctx.name || "";
|
|
3714
|
-
return { status: "no-match", componentHint: hint };
|
|
3715
|
-
}
|
|
3716
|
-
const rootFileKey = getRegistryFigmaFileKey(params.registry);
|
|
3717
|
-
const figmaUrlResolved = found.figmaUrl ?? buildFigmaDesignUrlFromFileKey(rootFileKey, found.nodeId);
|
|
3718
|
-
return {
|
|
3719
|
-
status: "found",
|
|
3720
|
-
entry: {
|
|
3721
|
-
...found,
|
|
3722
|
-
figmaUrl: figmaUrlResolved,
|
|
3723
|
-
meta: {
|
|
3724
|
-
...found.meta,
|
|
3725
|
-
tokensBool: params.tokensBool ?? found.meta?.tokensBool,
|
|
3726
|
-
readiness: params.readiness ?? found.meta?.readiness
|
|
3727
|
-
},
|
|
3728
|
-
description: params.description ?? found.description,
|
|
3729
|
-
compositionJson: params.compositionJson ?? found.compositionJson,
|
|
3730
|
-
metadata: params.metadata ?? found.metadata,
|
|
3731
|
-
designEmbed: params.designEmbed ?? found.designEmbed,
|
|
3732
|
-
compositionFilePath: found.compositionFilePath,
|
|
3733
|
-
compositionSnapshot: found.compositionSnapshot,
|
|
3734
|
-
componentApi: found.componentApi
|
|
3735
|
-
}
|
|
3736
|
-
};
|
|
3737
|
-
}
|
|
3738
|
-
function matchByStoryContext(entries, ctx) {
|
|
3739
|
-
const tokens = [
|
|
3740
|
-
extractComponentName(ctx.title),
|
|
3741
|
-
ctx.name,
|
|
3742
|
-
ctx.storyId,
|
|
3743
|
-
ctx.importPath,
|
|
3744
|
-
fileStem(ctx.importPath)
|
|
3745
|
-
].filter(Boolean).map(norm);
|
|
3746
|
-
if (tokens.length === 0) return void 0;
|
|
3747
|
-
let best;
|
|
3748
|
-
let bestScore = 0;
|
|
3749
|
-
let tie = false;
|
|
3750
|
-
for (const entry of entries) {
|
|
3751
|
-
const sc = score(entry, tokens);
|
|
3752
|
-
if (sc > bestScore) {
|
|
3753
|
-
bestScore = sc;
|
|
3754
|
-
best = entry;
|
|
3755
|
-
tie = false;
|
|
3756
|
-
} else if (sc === bestScore && sc > 0) {
|
|
3757
|
-
tie = true;
|
|
3758
|
-
}
|
|
3759
|
-
}
|
|
3760
|
-
if (tie || bestScore < 60) return void 0;
|
|
3761
|
-
return best;
|
|
3762
|
-
}
|
|
3763
|
-
function score(entry, tokens) {
|
|
3764
|
-
const display = norm(entry.displayName);
|
|
3765
|
-
const imprt = norm(entry.importPath ?? "");
|
|
3766
|
-
const stems = (entry.files ?? []).map((f) => norm(fileStem(f.filePath))).filter(Boolean);
|
|
3767
|
-
let s = 0;
|
|
3768
|
-
for (const t of tokens) {
|
|
3769
|
-
if (!t) continue;
|
|
3770
|
-
if (display && t === display) s += 120;
|
|
3771
|
-
else if (display && t.includes(display)) s += 70;
|
|
3772
|
-
else if (display && display.includes(t)) s += 65;
|
|
3773
|
-
if (imprt && t.includes(imprt)) s += 55;
|
|
3774
|
-
for (const stem of stems) {
|
|
3775
|
-
if (stem && t.includes(stem)) s += 30;
|
|
3776
|
-
}
|
|
3777
|
-
}
|
|
3778
|
-
return s;
|
|
3779
|
-
}
|
|
3780
|
-
function extractComponentName(title) {
|
|
3781
|
-
if (!title) return "";
|
|
3782
|
-
const parts = title.split("/");
|
|
3783
|
-
return parts[parts.length - 1].trim();
|
|
3784
|
-
}
|
|
3785
|
-
function norm(v) {
|
|
3786
|
-
return v.toLowerCase().replace(/[^a-z0-9]+/g, "");
|
|
3787
|
-
}
|
|
3788
|
-
function fileStem(p) {
|
|
3789
|
-
const f = (p || "").split("/").pop() ?? "";
|
|
3790
|
-
const d = f.lastIndexOf(".");
|
|
3791
|
-
return d > 0 ? f.slice(0, d) : f;
|
|
3792
|
-
}
|
|
3793
3810
|
function buildEmbedUrl(figmaUrl) {
|
|
3794
3811
|
if (figmaUrl.includes("figma.com/embed")) return figmaUrl;
|
|
3795
3812
|
return `https://www.figma.com/embed?embed_host=share&url=${encodeURIComponent(figmaUrl)}`;
|
|
@@ -3818,12 +3835,6 @@ function formatDate(iso) {
|
|
|
3818
3835
|
return iso;
|
|
3819
3836
|
}
|
|
3820
3837
|
}
|
|
3821
|
-
function resolveTokensBool(entry, params) {
|
|
3822
|
-
const p = params?.tokensBool ?? entry.meta?.tokensBool;
|
|
3823
|
-
if (p === "true" || p === "false") return p;
|
|
3824
|
-
if (entry.meta?.tokenStatus === "assigned") return "true";
|
|
3825
|
-
return "false";
|
|
3826
|
-
}
|
|
3827
3838
|
function formatJsonSnapshot(raw) {
|
|
3828
3839
|
if (!raw) return "";
|
|
3829
3840
|
try {
|
|
@@ -3832,6 +3843,10 @@ function formatJsonSnapshot(raw) {
|
|
|
3832
3843
|
return raw;
|
|
3833
3844
|
}
|
|
3834
3845
|
}
|
|
3846
|
+
function logFigmaEmbedDebug(enabled, event, payload) {
|
|
3847
|
+
if (!enabled) return;
|
|
3848
|
+
console.debug("[SXL Studio addon] Figma embed", { event, ...payload });
|
|
3849
|
+
}
|
|
3835
3850
|
var TokensBadge = ({ value }) => {
|
|
3836
3851
|
const on = value === "true";
|
|
3837
3852
|
return /* @__PURE__ */ React2.createElement(
|
|
@@ -3945,6 +3960,8 @@ var SxlPanel = () => {
|
|
|
3945
3960
|
const entryForRender = result.status === "found" ? result.entry : null;
|
|
3946
3961
|
const showEmbed = !!entryForRender?.figmaUrl && entryForRender.designEmbed !== false;
|
|
3947
3962
|
const renderEmbedSection = entryForRender?.designEmbed === true || !!entryForRender?.figmaUrl;
|
|
3963
|
+
const debugFigmaEmbed = params?.debugFigmaEmbed === true;
|
|
3964
|
+
const embedUrl = showEmbed && entryForRender?.figmaUrl ? buildEmbedUrl(entryForRender.figmaUrl) : void 0;
|
|
3948
3965
|
useEffect(() => {
|
|
3949
3966
|
if (!entryForRender) return;
|
|
3950
3967
|
if (entryForRender.designEmbed === true && !entryForRender.figmaUrl) {
|
|
@@ -3953,7 +3970,23 @@ var SxlPanel = () => {
|
|
|
3953
3970
|
{ nodeId: entryForRender.nodeId, displayName: entryForRender.displayName }
|
|
3954
3971
|
);
|
|
3955
3972
|
}
|
|
3956
|
-
|
|
3973
|
+
logFigmaEmbedDebug(debugFigmaEmbed, "resolved", {
|
|
3974
|
+
nodeId: entryForRender.nodeId,
|
|
3975
|
+
displayName: entryForRender.displayName,
|
|
3976
|
+
designEmbed: entryForRender.designEmbed ?? null,
|
|
3977
|
+
figmaUrl: entryForRender.figmaUrl ?? null,
|
|
3978
|
+
embedUrl: embedUrl ?? null,
|
|
3979
|
+
storyId: ctx.storyId || null
|
|
3980
|
+
});
|
|
3981
|
+
}, [
|
|
3982
|
+
debugFigmaEmbed,
|
|
3983
|
+
embedUrl,
|
|
3984
|
+
ctx.storyId,
|
|
3985
|
+
entryForRender?.designEmbed,
|
|
3986
|
+
entryForRender?.figmaUrl,
|
|
3987
|
+
entryForRender?.nodeId,
|
|
3988
|
+
entryForRender?.displayName
|
|
3989
|
+
]);
|
|
3957
3990
|
if (result.status === "no-registry") {
|
|
3958
3991
|
return React2.createElement(NoRegistryState, null);
|
|
3959
3992
|
}
|
|
@@ -3971,16 +4004,28 @@ var SxlPanel = () => {
|
|
|
3971
4004
|
"iframe",
|
|
3972
4005
|
{
|
|
3973
4006
|
title: "Figma embed",
|
|
3974
|
-
src:
|
|
4007
|
+
src: embedUrl,
|
|
3975
4008
|
style: iframe,
|
|
3976
4009
|
allowFullScreen: true,
|
|
3977
4010
|
allow: "clipboard-write; fullscreen",
|
|
3978
|
-
referrerPolicy: "no-referrer-when-downgrade"
|
|
4011
|
+
referrerPolicy: "no-referrer-when-downgrade",
|
|
4012
|
+
onLoad: () => {
|
|
4013
|
+
logFigmaEmbedDebug(debugFigmaEmbed, "iframe-load", {
|
|
4014
|
+
nodeId: entry.nodeId,
|
|
4015
|
+
embedUrl: embedUrl ?? null
|
|
4016
|
+
});
|
|
4017
|
+
},
|
|
4018
|
+
onError: () => {
|
|
4019
|
+
logFigmaEmbedDebug(debugFigmaEmbed, "iframe-error", {
|
|
4020
|
+
nodeId: entry.nodeId,
|
|
4021
|
+
embedUrl: embedUrl ?? null
|
|
4022
|
+
});
|
|
4023
|
+
}
|
|
3979
4024
|
}
|
|
3980
4025
|
), /* @__PURE__ */ React2.createElement("div", { style: embedFooter }, /* @__PURE__ */ React2.createElement(
|
|
3981
4026
|
"a",
|
|
3982
4027
|
{
|
|
3983
|
-
href:
|
|
4028
|
+
href: embedUrl,
|
|
3984
4029
|
target: "_blank",
|
|
3985
4030
|
rel: "noopener noreferrer",
|
|
3986
4031
|
style: { ...link, marginRight: "12px" }
|
package/dist/preset.js
CHANGED
|
@@ -166,9 +166,16 @@ function staticDirs(entries, options) {
|
|
|
166
166
|
return [...e, { from: root, to: `/${mount}` }];
|
|
167
167
|
}
|
|
168
168
|
function mergeSxlFigmaFrameSrcHeader(config) {
|
|
169
|
-
const
|
|
169
|
+
const requiredSources = [
|
|
170
|
+
"'self'",
|
|
171
|
+
"https://www.figma.com",
|
|
172
|
+
"https://*.figma.com",
|
|
173
|
+
"data:",
|
|
174
|
+
"blob:"
|
|
175
|
+
];
|
|
176
|
+
const directive = `frame-src ${requiredSources.join(" ")}`;
|
|
170
177
|
const prev = config.server?.headers?.["Content-Security-Policy"];
|
|
171
|
-
const merged = typeof prev === "string" && prev.trim() ?
|
|
178
|
+
const merged = typeof prev === "string" && prev.trim() ? mergeCspFrameSrcDirective(prev, requiredSources) : directive;
|
|
172
179
|
return {
|
|
173
180
|
...config,
|
|
174
181
|
server: {
|
|
@@ -180,6 +187,32 @@ function mergeSxlFigmaFrameSrcHeader(config) {
|
|
|
180
187
|
}
|
|
181
188
|
};
|
|
182
189
|
}
|
|
190
|
+
function mergeCspFrameSrcDirective(policy, requiredSources) {
|
|
191
|
+
const directives = policy.split(";").map((d) => d.trim()).filter(Boolean);
|
|
192
|
+
let hasFrameSrc = false;
|
|
193
|
+
const mergedDirectives = directives.map((directive) => {
|
|
194
|
+
const parts = directive.split(/\s+/).filter(Boolean);
|
|
195
|
+
if (parts.length === 0) return directive;
|
|
196
|
+
const name = parts[0].toLowerCase();
|
|
197
|
+
if (name !== "frame-src") return directive;
|
|
198
|
+
hasFrameSrc = true;
|
|
199
|
+
const existing = parts.slice(1);
|
|
200
|
+
const next = [...existing];
|
|
201
|
+
const seen = new Set(existing.map((v) => v.toLowerCase()));
|
|
202
|
+
for (const src of requiredSources) {
|
|
203
|
+
const key = src.toLowerCase();
|
|
204
|
+
if (!seen.has(key)) {
|
|
205
|
+
seen.add(key);
|
|
206
|
+
next.push(src);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return `frame-src ${next.join(" ")}`;
|
|
210
|
+
});
|
|
211
|
+
if (!hasFrameSrc) {
|
|
212
|
+
mergedDirectives.push(`frame-src ${requiredSources.join(" ")}`);
|
|
213
|
+
}
|
|
214
|
+
return mergedDirectives.join("; ");
|
|
215
|
+
}
|
|
183
216
|
export {
|
|
184
217
|
managerEntries,
|
|
185
218
|
mergeSxlFigmaFrameSrcHeader,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sxl-studio/storybook-addon",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.2",
|
|
4
4
|
"description": "Storybook addon for SXL Studio — displays Figma Embed, component info and design token status for linked components",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -25,7 +25,9 @@
|
|
|
25
25
|
"scripts": {
|
|
26
26
|
"build": "tsup",
|
|
27
27
|
"dev": "tsup --watch",
|
|
28
|
-
"lint": "
|
|
28
|
+
"lint": "eslint \"src/**/*.{ts,tsx}\" --max-warnings=0",
|
|
29
|
+
"typecheck": "tsc --noEmit",
|
|
30
|
+
"test": "node --import tsx --test src/**/*.test.ts",
|
|
29
31
|
"prepublishOnly": "npm run build",
|
|
30
32
|
"push": "npm run build && npm publish"
|
|
31
33
|
},
|
|
@@ -44,14 +46,19 @@
|
|
|
44
46
|
"storybook": "^8.0.0 || ^9.0.0 || ^10.0.0"
|
|
45
47
|
},
|
|
46
48
|
"devDependencies": {
|
|
49
|
+
"@eslint/js": "^9.27.0",
|
|
47
50
|
"@storybook/components": "^8.6.14",
|
|
48
51
|
"@storybook/manager-api": "^8.6.14",
|
|
49
52
|
"@types/node": "^22.19.17",
|
|
50
53
|
"@types/react": "^18.0.0",
|
|
54
|
+
"eslint": "^9.27.0",
|
|
55
|
+
"globals": "^15.15.0",
|
|
51
56
|
"react": "^18.0.0",
|
|
52
57
|
"storybook": "^8.0.0",
|
|
58
|
+
"tsx": "^4.19.2",
|
|
53
59
|
"tsup": "^8.5.1",
|
|
54
|
-
"typescript": "^5.7.0"
|
|
60
|
+
"typescript": "^5.7.0",
|
|
61
|
+
"typescript-eslint": "^8.33.1"
|
|
55
62
|
},
|
|
56
63
|
"storybook": {
|
|
57
64
|
"preset": "./preset.js",
|