@designfever/web-review-kit 0.5.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.
Files changed (42) hide show
  1. package/.env.sample +29 -0
  2. package/README.md +36 -10
  3. package/dist/chunk-AB5B6O77.js +584 -0
  4. package/dist/chunk-AB5B6O77.js.map +1 -0
  5. package/dist/{chunk-TWCSIBMY.js → chunk-RPVLRULC.js} +736 -201
  6. package/dist/chunk-RPVLRULC.js.map +1 -0
  7. package/dist/image.types-BmzkFSPX.d.cts +71 -0
  8. package/dist/image.types-BmzkFSPX.d.ts +71 -0
  9. package/dist/index.cjs +1251 -202
  10. package/dist/index.cjs.map +1 -1
  11. package/dist/index.d.cts +27 -3
  12. package/dist/index.d.ts +27 -3
  13. package/dist/index.js +50 -3
  14. package/dist/index.js.map +1 -1
  15. package/dist/react-shell.cjs +10135 -4070
  16. package/dist/react-shell.cjs.map +1 -1
  17. package/dist/react-shell.d.cts +37 -4
  18. package/dist/react-shell.d.ts +37 -4
  19. package/dist/react-shell.js +9258 -3707
  20. package/dist/react-shell.js.map +1 -1
  21. package/dist/token-Dt-ZH-YO.d.cts +88 -0
  22. package/dist/token-nJXPPdYX.d.ts +88 -0
  23. package/dist/{types-RvVa5ns-.d.cts → types-DT9Z66mV.d.cts} +14 -1
  24. package/dist/{types-RvVa5ns-.d.ts → types-DT9Z66mV.d.ts} +14 -1
  25. package/dist/vite.cjs +1116 -5
  26. package/dist/vite.cjs.map +1 -1
  27. package/dist/vite.d.cts +45 -1
  28. package/dist/vite.d.ts +45 -1
  29. package/dist/vite.js +800 -5
  30. package/dist/vite.js.map +1 -1
  31. package/docs/README.md +11 -5
  32. package/docs/adapters.md +126 -0
  33. package/docs/adaptor.sample.ts +13 -1
  34. package/docs/architecture.md +6 -1
  35. package/docs/code-review-0.6.0.md +232 -0
  36. package/docs/db-setup.md +4 -2
  37. package/docs/figma-image-mvp-0.7.0.md +327 -0
  38. package/docs/installation.md +44 -8
  39. package/docs/release-notes-0.6.0.md +108 -0
  40. package/docs/release-notes-0.7.0.md +128 -0
  41. package/package.json +6 -2
  42. package/dist/chunk-TWCSIBMY.js.map +0 -1
package/dist/vite.cjs CHANGED
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __export = (target, all) => {
7
9
  for (var name in all)
@@ -15,23 +17,1110 @@ var __copyProps = (to, from, except, desc) => {
15
17
  }
16
18
  return to;
17
19
  };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
18
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
29
 
20
30
  // src/vite.ts
21
31
  var vite_exports = {};
22
32
  __export(vite_exports, {
33
+ collectReviewFigmaReleaseSnapshot: () => collectReviewFigmaReleaseSnapshot,
34
+ createReviewFigmaImageApiUrl: () => createReviewFigmaImageApiUrl,
35
+ createReviewFigmaImagesSnapshot: () => createReviewFigmaImagesSnapshot,
36
+ createReviewFigmaReleaseSnapshot: () => createReviewFigmaReleaseSnapshot,
37
+ readReviewFigmaServerToken: () => readReviewFigmaServerToken,
38
+ renderReviewFigmaImage: () => renderReviewFigmaImage,
39
+ renderReviewFigmaServerImage: () => renderReviewFigmaServerImage,
40
+ requireReviewFigmaServerToken: () => requireReviewFigmaServerToken,
23
41
  reviewDataLocator: () => reviewDataLocator,
42
+ reviewFigmaImageStore: () => reviewFigmaImageStore,
24
43
  reviewSourceLocator: () => reviewSourceLocator
25
44
  });
26
45
  module.exports = __toCommonJS(vite_exports);
46
+
47
+ // src/vite/figma-image-store.ts
48
+ var import_node_path3 = __toESM(require("path"), 1);
49
+ var import_vite = require("vite");
50
+
51
+ // src/vite/figma-asset.ts
52
+ function parseReviewFigmaImageFormat(value) {
53
+ return value === "webp" || value === "png" || value === "jpg" ? value : void 0;
54
+ }
55
+ function getStoreRenderFormat(renderFormat, imageFormat) {
56
+ if (renderFormat === "jpg" || renderFormat === "png") return renderFormat;
57
+ if (imageFormat === "jpg") return "jpg";
58
+ return "png";
59
+ }
60
+ function getReviewFigmaImageFormatFromMimeType(mimeType) {
61
+ if (mimeType === "image/webp") return "webp";
62
+ if (mimeType === "image/png") return "png";
63
+ if (mimeType === "image/jpeg") return "jpg";
64
+ return null;
65
+ }
66
+ function normalizeImageMimeType(value) {
67
+ const mimeType = value?.split(";")[0]?.trim().toLowerCase();
68
+ if (mimeType === "image/jpg") return "image/jpeg";
69
+ if (mimeType === "image/jpeg" || mimeType === "image/png" || mimeType === "image/webp") {
70
+ return mimeType;
71
+ }
72
+ return null;
73
+ }
74
+ function createReviewFigmaAssetStorageKey(id, imageFormat) {
75
+ return `${id}.${getReviewFigmaAssetExtension(imageFormat)}`;
76
+ }
77
+ function createReviewFigmaAssetUrl(assetEndpoint, storageKey) {
78
+ return `${assetEndpoint}/${encodeURIComponent(storageKey)}`;
79
+ }
80
+ function getReviewFigmaAssetStorageKeyFromPathname(pathname, assetEndpoint) {
81
+ try {
82
+ const storageKey = decodeURIComponent(
83
+ pathname.slice(assetEndpoint.length + 1)
84
+ );
85
+ return isSafeReviewFigmaAssetStorageKey(storageKey) ? storageKey : null;
86
+ } catch {
87
+ return null;
88
+ }
89
+ }
90
+ function isSafeReviewFigmaAssetStorageKey(value) {
91
+ return /^figma_[a-z0-9_]+\.(webp|png|jpg)$/.test(value);
92
+ }
93
+ function getReviewFigmaAssetExtension(format) {
94
+ return format === "jpg" ? "jpg" : format;
95
+ }
96
+ function getReviewFigmaAssetMimeType(storageKey) {
97
+ if (storageKey.endsWith(".jpg")) return "image/jpeg";
98
+ if (storageKey.endsWith(".webp")) return "image/webp";
99
+ return "image/png";
100
+ }
101
+
102
+ // src/figma/parse.ts
103
+ var FIGMA_NODE_REF_SEPARATOR = "->";
104
+ function parseReviewFigmaNodeRef(value) {
105
+ if (typeof value !== "string") return normalizeReviewFigmaNodeRef(value);
106
+ const input = value.trim();
107
+ if (!input) return null;
108
+ return parseReviewFigmaNodeRefValue(input) ?? parseReviewFigmaUrl(input);
109
+ }
110
+ function requireReviewFigmaNodeRef(value) {
111
+ const ref = parseReviewFigmaNodeRef(value);
112
+ if (!ref) {
113
+ throw new Error("A Figma node link or fileKey->nodeId value is required.");
114
+ }
115
+ return ref;
116
+ }
117
+ function parseReviewFigmaNodeRefValue(value) {
118
+ const [fileKey, nodeId, extra] = value.split(FIGMA_NODE_REF_SEPARATOR).map((part) => part.trim());
119
+ if (!fileKey || !nodeId || extra !== void 0) return null;
120
+ return normalizeReviewFigmaNodeRef({ fileKey, nodeId });
121
+ }
122
+ function parseReviewFigmaUrl(value) {
123
+ let url;
124
+ try {
125
+ url = new URL(value);
126
+ } catch {
127
+ return null;
128
+ }
129
+ const fileKey = getFigmaFileKey(url);
130
+ const nodeId = normalizeFigmaNodeId(url.searchParams.get("node-id"));
131
+ if (!fileKey || !nodeId) return null;
132
+ return { fileKey, nodeId, sourceUrl: value };
133
+ }
134
+ function getFigmaFileKey(url) {
135
+ if (!/(^|\.)figma\.com$/i.test(url.hostname)) return null;
136
+ const parts = url.pathname.split("/").filter(Boolean);
137
+ const fileKeyIndex = parts.findIndex(
138
+ (part) => ["design", "file", "proto", "board"].includes(part)
139
+ );
140
+ const fileKey = fileKeyIndex >= 0 ? parts[fileKeyIndex + 1] : null;
141
+ return normalizeFigmaFileKey(fileKey);
142
+ }
143
+ function normalizeReviewFigmaNodeRef(value) {
144
+ const fileKey = normalizeFigmaFileKey(value?.fileKey);
145
+ const nodeId = normalizeFigmaNodeId(value?.nodeId);
146
+ if (!fileKey || !nodeId) return null;
147
+ const sourceUrl = normalizeOptionalString(value?.sourceUrl);
148
+ return sourceUrl ? { fileKey, nodeId, sourceUrl } : { fileKey, nodeId };
149
+ }
150
+ function normalizeFigmaFileKey(value) {
151
+ return normalizeOptionalString(value)?.replace(/[?#].*$/, "") ?? null;
152
+ }
153
+ function normalizeFigmaNodeId(value) {
154
+ const nodeId = normalizeOptionalString(value);
155
+ if (!nodeId) return null;
156
+ if (nodeId.includes(":")) return nodeId;
157
+ return nodeId.replace(/-/g, ":");
158
+ }
159
+ function normalizeOptionalString(value) {
160
+ if (typeof value !== "string") return null;
161
+ return value.trim() || null;
162
+ }
163
+
164
+ // src/figma/token.ts
165
+ var DEFAULT_REVIEW_FIGMA_TOKEN_ENV_KEY = "FIGMA_TOKEN";
166
+ var REVIEW_FIGMA_TOKEN_MISSING_CODE = "DFWR_FIGMA_TOKEN_MISSING";
167
+ var ReviewFigmaTokenError = class extends Error {
168
+ constructor(envKey = DEFAULT_REVIEW_FIGMA_TOKEN_ENV_KEY) {
169
+ super(
170
+ `Figma image rendering requires server env ${envKey}. Set ${envKey} in the dev/server environment; do not expose it as VITE_${envKey}.`
171
+ );
172
+ this.code = REVIEW_FIGMA_TOKEN_MISSING_CODE;
173
+ this.name = "ReviewFigmaTokenError";
174
+ this.envKey = envKey;
175
+ }
176
+ };
177
+ function readReviewFigmaToken(options = {}) {
178
+ if (options.enabled === false) return null;
179
+ const envKey = options.envKey ?? DEFAULT_REVIEW_FIGMA_TOKEN_ENV_KEY;
180
+ return normalizeReviewFigmaToken(options.token ?? options.env?.[envKey]);
181
+ }
182
+ function requireReviewFigmaToken(options = {}) {
183
+ const envKey = options.envKey ?? DEFAULT_REVIEW_FIGMA_TOKEN_ENV_KEY;
184
+ const token = readReviewFigmaToken(options);
185
+ if (!token) throw new ReviewFigmaTokenError(envKey);
186
+ return token;
187
+ }
188
+ function normalizeReviewFigmaToken(value) {
189
+ if (typeof value !== "string") return null;
190
+ return value.trim() || null;
191
+ }
192
+
193
+ // src/figma/render.ts
194
+ var DEFAULT_FIGMA_API_BASE_URL = "https://api.figma.com";
195
+ async function renderReviewFigmaImage(options) {
196
+ const token = requireReviewFigmaToken({ token: options.token });
197
+ const ref = requireReviewFigmaNodeRef(options.figmaUrl);
198
+ const renderFormat = options.format ?? "png";
199
+ const requestUrl = createReviewFigmaImageApiUrl({
200
+ apiBaseUrl: options.apiBaseUrl,
201
+ fileKey: ref.fileKey,
202
+ nodeId: ref.nodeId,
203
+ format: renderFormat,
204
+ scale: options.scale,
205
+ useAbsoluteBounds: options.useAbsoluteBounds
206
+ });
207
+ const fetchImage = options.fetch ?? globalThis.fetch;
208
+ if (!fetchImage) throw new Error("Figma image rendering requires fetch.");
209
+ const response = await fetchImage(requestUrl, {
210
+ headers: {
211
+ "X-Figma-Token": token
212
+ },
213
+ signal: options.signal
214
+ });
215
+ const body = await response.json().catch(() => null);
216
+ if (!response.ok) {
217
+ throw new Error(
218
+ body?.err || `Figma image render failed with ${response.status}`
219
+ );
220
+ }
221
+ const imageUrl = body?.images?.[ref.nodeId];
222
+ if (!imageUrl) {
223
+ throw new Error(`Figma image render returned no URL for ${ref.nodeId}.`);
224
+ }
225
+ return {
226
+ fileKey: ref.fileKey,
227
+ nodeId: ref.nodeId,
228
+ figmaUrl: typeof options.figmaUrl === "string" ? options.figmaUrl : options.figmaUrl.sourceUrl,
229
+ imageUrl,
230
+ renderFormat
231
+ };
232
+ }
233
+ function createReviewFigmaImageApiUrl({
234
+ apiBaseUrl = DEFAULT_FIGMA_API_BASE_URL,
235
+ fileKey,
236
+ nodeId,
237
+ format = "png",
238
+ scale,
239
+ useAbsoluteBounds
240
+ }) {
241
+ const url = new URL(`/v1/images/${encodeURIComponent(fileKey)}`, apiBaseUrl);
242
+ url.searchParams.set("ids", nodeId);
243
+ url.searchParams.set("format", format);
244
+ if (typeof scale === "number" && Number.isFinite(scale) && scale > 0) {
245
+ url.searchParams.set("scale", String(scale));
246
+ }
247
+ if (useAbsoluteBounds !== void 0) {
248
+ url.searchParams.set("use_absolute_bounds", String(useAbsoluteBounds));
249
+ }
250
+ return url.toString();
251
+ }
252
+
253
+ // src/figma/image.store.ts
254
+ var DEFAULT_REVIEW_FIGMA_IMAGE_STORE_ENDPOINT = "/__dfwr/figma-images";
255
+ function getReviewFigmaImageTargetKey(target) {
256
+ return JSON.stringify(normalizeReviewFigmaImageTarget(target));
257
+ }
258
+ function getReviewFigmaImageMimeType(format) {
259
+ if (format === "jpg") return "image/jpeg";
260
+ if (format === "png") return "image/png";
261
+ return "image/webp";
262
+ }
263
+ function normalizeReviewFigmaImageTarget(target) {
264
+ if (target.type === "figma-node") {
265
+ return {
266
+ type: target.type,
267
+ projectId: target.projectId,
268
+ fileKey: target.fileKey,
269
+ nodeId: target.nodeId
270
+ };
271
+ }
272
+ return {
273
+ type: target.type,
274
+ projectId: target.projectId,
275
+ pageUrl: target.pageUrl,
276
+ slot: target.slot ?? "",
277
+ viewport: target.viewport ? {
278
+ label: target.viewport.label ?? "",
279
+ width: target.viewport.width ?? null,
280
+ height: target.viewport.height ?? null,
281
+ scope: target.viewport.scope ?? ""
282
+ } : null
283
+ };
284
+ }
285
+
286
+ // src/vite/figma-image-store.server.ts
287
+ var import_promises2 = require("fs/promises");
288
+ var import_node_path2 = __toESM(require("path"), 1);
289
+
290
+ // src/figma/image.snapshot.ts
291
+ function createReviewFigmaImagesSnapshot(images, options = {}) {
292
+ const targetKeys = options.targets?.length ? new Set(options.targets.map(getReviewFigmaImageTargetKey)) : null;
293
+ return images.filter((image) => {
294
+ if (options.projectId && image.projectId !== options.projectId) {
295
+ return false;
296
+ }
297
+ if (targetKeys && !targetKeys.has(getReviewFigmaImageTargetKey(image.target))) {
298
+ return false;
299
+ }
300
+ return true;
301
+ }).map(cloneReviewFigmaImage).sort(compareReviewFigmaSnapshotImages);
302
+ }
303
+ function createReviewFigmaReleaseSnapshot({
304
+ images,
305
+ projectId,
306
+ releaseId,
307
+ label,
308
+ createdAt,
309
+ targets
310
+ }) {
311
+ return {
312
+ version: 1,
313
+ projectId,
314
+ ...releaseId ? { releaseId } : null,
315
+ ...label ? { label } : null,
316
+ createdAt: createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
317
+ figmaImagesSnapshot: createReviewFigmaImagesSnapshot(images, {
318
+ projectId,
319
+ targets
320
+ })
321
+ };
322
+ }
323
+ async function collectReviewFigmaReleaseSnapshot({
324
+ store,
325
+ targets,
326
+ ...snapshotOptions
327
+ }) {
328
+ const imagesByTarget = await Promise.all(
329
+ targets.map((target) => store.listImages(target))
330
+ );
331
+ return createReviewFigmaReleaseSnapshot({
332
+ ...snapshotOptions,
333
+ targets,
334
+ images: dedupeReviewFigmaImages(imagesByTarget.flat())
335
+ });
336
+ }
337
+ function dedupeReviewFigmaImages(images) {
338
+ return Array.from(new Map(images.map((image) => [image.id, image])).values());
339
+ }
340
+ function cloneReviewFigmaImage(image) {
341
+ return {
342
+ ...image,
343
+ target: cloneReviewFigmaImageTarget(image.target)
344
+ };
345
+ }
346
+ function cloneReviewFigmaImageTarget(target) {
347
+ if (target.type === "figma-node") {
348
+ return {
349
+ type: target.type,
350
+ projectId: target.projectId,
351
+ fileKey: target.fileKey,
352
+ nodeId: target.nodeId
353
+ };
354
+ }
355
+ return {
356
+ type: target.type,
357
+ projectId: target.projectId,
358
+ pageUrl: target.pageUrl,
359
+ slot: target.slot,
360
+ viewport: target.viewport ? {
361
+ label: target.viewport.label,
362
+ width: target.viewport.width,
363
+ height: target.viewport.height,
364
+ scope: target.viewport.scope
365
+ } : void 0
366
+ };
367
+ }
368
+ function compareReviewFigmaSnapshotImages(a, b) {
369
+ return a.projectId.localeCompare(b.projectId) || getReviewFigmaImageTargetKey(a.target).localeCompare(
370
+ getReviewFigmaImageTargetKey(b.target)
371
+ ) || a.order - b.order || a.createdAt.localeCompare(b.createdAt) || a.id.localeCompare(b.id);
372
+ }
373
+
374
+ // src/vite/figma-image-store.image.ts
375
+ var import_promises = require("fs/promises");
376
+ var import_node_path = __toESM(require("path"), 1);
377
+ async function readReviewFigmaNodeName({
378
+ apiBaseUrl,
379
+ enabled,
380
+ env,
381
+ envKey,
382
+ fetchOption,
383
+ fileKey,
384
+ nodeId,
385
+ token
386
+ }) {
387
+ const explicitToken = typeof token === "string" ? token.trim() : token;
388
+ const figmaToken = explicitToken || requireReviewFigmaToken({
389
+ token: null,
390
+ env,
391
+ envKey: envKey ?? DEFAULT_REVIEW_FIGMA_TOKEN_ENV_KEY,
392
+ enabled
393
+ });
394
+ const fetchNode = fetchOption ?? globalThis.fetch;
395
+ if (!fetchNode) throw new Error("Figma node name lookup requires fetch.");
396
+ const response = await fetchNode(
397
+ createReviewFigmaNodeApiUrl({ apiBaseUrl, fileKey, nodeId }),
398
+ {
399
+ headers: {
400
+ "X-Figma-Token": figmaToken
401
+ }
402
+ }
403
+ );
404
+ const body = await response.json().catch(() => null);
405
+ if (!response.ok) {
406
+ throw new Error(body?.err || `Figma node lookup failed with ${response.status}`);
407
+ }
408
+ const nodes = body?.nodes;
409
+ const node = nodes?.[nodeId] ?? Object.values(nodes ?? {})[0];
410
+ return normalizeOptionalText(node?.document?.name);
411
+ }
412
+ function createReviewFigmaNodeApiUrl({
413
+ apiBaseUrl = "https://api.figma.com",
414
+ fileKey,
415
+ nodeId
416
+ }) {
417
+ const url = new URL(
418
+ `/v1/files/${encodeURIComponent(fileKey)}/nodes`,
419
+ apiBaseUrl
420
+ );
421
+ url.searchParams.set("ids", nodeId);
422
+ return url.toString();
423
+ }
424
+ async function createReviewFigmaImage({
425
+ assetDir,
426
+ assetEndpoint,
427
+ currentImages,
428
+ env,
429
+ input,
430
+ options
431
+ }) {
432
+ const ref = parseReviewFigmaNodeRef(input.figmaUrl);
433
+ if (!ref) {
434
+ throw new Error("A Figma node copy link or fileKey->nodeId value is required.");
435
+ }
436
+ const id = createReviewFigmaImageId();
437
+ const explicitLabel = normalizeOptionalText(input.label);
438
+ if (input.asset) {
439
+ const cachedAsset2 = await cacheReviewFigmaProvidedImageAsset({
440
+ assetDir,
441
+ assetEndpoint,
442
+ id,
443
+ asset: input.asset,
444
+ options
445
+ });
446
+ const now2 = (/* @__PURE__ */ new Date()).toISOString();
447
+ const order2 = typeof input.order === "number" && Number.isFinite(input.order) ? input.order : getNextImageOrder(currentImages, input.target);
448
+ return {
449
+ id,
450
+ projectId: input.target.projectId,
451
+ target: input.target,
452
+ figmaUrl: input.figmaUrl,
453
+ fileKey: ref.fileKey,
454
+ nodeId: ref.nodeId,
455
+ imageUrl: cachedAsset2.imageUrl,
456
+ imageFormat: cachedAsset2.imageFormat,
457
+ mimeType: cachedAsset2.mimeType,
458
+ label: explicitLabel,
459
+ order: order2,
460
+ storageKey: cachedAsset2.storageKey,
461
+ width: input.asset.width,
462
+ height: input.asset.height,
463
+ byteSize: cachedAsset2.byteSize,
464
+ createdAt: now2,
465
+ updatedAt: now2
466
+ };
467
+ }
468
+ const nodeLabelPromise = explicitLabel ? Promise.resolve(void 0) : readReviewFigmaNodeName({
469
+ apiBaseUrl: options.apiBaseUrl,
470
+ enabled: options.enabled,
471
+ env,
472
+ envKey: options.envKey,
473
+ fetchOption: options.fetch,
474
+ fileKey: ref.fileKey,
475
+ nodeId: ref.nodeId,
476
+ token: options.token
477
+ }).catch(() => void 0);
478
+ const targetImageFormat = input.imageFormat ?? options.imageFormat ?? "webp";
479
+ const renderFormat = getStoreRenderFormat(options.renderFormat, targetImageFormat);
480
+ const explicitToken = typeof options.token === "string" ? options.token.trim() : options.token;
481
+ const rendered = await renderReviewFigmaImage({
482
+ figmaUrl: input.figmaUrl,
483
+ format: renderFormat,
484
+ scale: options.renderScale,
485
+ useAbsoluteBounds: options.useAbsoluteBounds,
486
+ apiBaseUrl: options.apiBaseUrl,
487
+ fetch: options.fetch,
488
+ token: explicitToken || requireReviewFigmaToken({
489
+ token: null,
490
+ env,
491
+ envKey: options.envKey ?? DEFAULT_REVIEW_FIGMA_TOKEN_ENV_KEY,
492
+ enabled: options.enabled
493
+ })
494
+ });
495
+ const cachedAsset = await cacheReviewFigmaImageAsset({
496
+ assetDir,
497
+ assetEndpoint,
498
+ id,
499
+ imageUrl: rendered.imageUrl,
500
+ options,
501
+ renderFormat,
502
+ targetImageFormat
503
+ });
504
+ const imageFormat = cachedAsset?.imageFormat ?? (renderFormat === "jpg" ? "jpg" : "png");
505
+ const now = (/* @__PURE__ */ new Date()).toISOString();
506
+ const order = typeof input.order === "number" && Number.isFinite(input.order) ? input.order : getNextImageOrder(currentImages, input.target);
507
+ const nodeLabel = await nodeLabelPromise;
508
+ return {
509
+ id,
510
+ projectId: input.target.projectId,
511
+ target: input.target,
512
+ figmaUrl: input.figmaUrl,
513
+ fileKey: rendered.fileKey,
514
+ nodeId: rendered.nodeId,
515
+ imageUrl: cachedAsset?.imageUrl ?? rendered.imageUrl,
516
+ imageFormat,
517
+ mimeType: cachedAsset?.mimeType ?? getReviewFigmaImageMimeType(imageFormat),
518
+ label: explicitLabel ?? nodeLabel,
519
+ order,
520
+ storageKey: cachedAsset?.storageKey,
521
+ byteSize: cachedAsset?.byteSize,
522
+ createdAt: now,
523
+ updatedAt: now
524
+ };
525
+ }
526
+ async function cacheReviewFigmaProvidedImageAsset({
527
+ assetDir,
528
+ assetEndpoint,
529
+ id,
530
+ asset,
531
+ options
532
+ }) {
533
+ const decodedAsset = decodeReviewFigmaImageAsset(asset);
534
+ const storageKey = createReviewFigmaAssetStorageKey(
535
+ id,
536
+ decodedAsset.imageFormat
537
+ );
538
+ await (0, import_promises.mkdir)(assetDir, { recursive: true });
539
+ await (0, import_promises.writeFile)(import_node_path.default.join(assetDir, storageKey), decodedAsset.data);
540
+ return {
541
+ imageUrl: createReviewFigmaAssetUrl(assetEndpoint, storageKey),
542
+ imageFormat: decodedAsset.imageFormat,
543
+ mimeType: decodedAsset.mimeType,
544
+ storageKey,
545
+ byteSize: decodedAsset.data.byteLength
546
+ };
547
+ }
548
+ function decodeReviewFigmaImageAsset(asset) {
549
+ const mimeType = normalizeImageMimeType(asset.mimeType);
550
+ if (!mimeType) throw new Error("Unsupported Figma image asset MIME type.");
551
+ const imageFormat = getReviewFigmaImageFormatFromMimeType(mimeType) ?? asset.imageFormat;
552
+ const match = /^data:([^;,]+);base64,([a-zA-Z0-9+/=\s]+)$/.exec(
553
+ asset.dataUrl
554
+ );
555
+ if (!match) throw new Error("Valid Figma image asset data URL is required.");
556
+ const dataUrlMimeType = normalizeImageMimeType(match[1]);
557
+ if (dataUrlMimeType && dataUrlMimeType !== mimeType) {
558
+ throw new Error("Figma image asset MIME type mismatch.");
559
+ }
560
+ return {
561
+ data: Buffer.from(match[2].replace(/\s/g, ""), "base64"),
562
+ imageFormat,
563
+ mimeType
564
+ };
565
+ }
566
+ async function cacheReviewFigmaImageAsset({
567
+ assetDir,
568
+ assetEndpoint,
569
+ id,
570
+ imageUrl,
571
+ options,
572
+ renderFormat,
573
+ targetImageFormat
574
+ }) {
575
+ if (options.cacheAssets === false) return null;
576
+ const asset = await downloadReviewFigmaImageAsset({
577
+ fetchOption: options.fetch,
578
+ imageUrl,
579
+ renderFormat,
580
+ targetImageFormat,
581
+ transformAsset: options.transformAsset
582
+ });
583
+ const storageKey = createReviewFigmaAssetStorageKey(id, asset.imageFormat);
584
+ await (0, import_promises.mkdir)(assetDir, { recursive: true });
585
+ await (0, import_promises.writeFile)(import_node_path.default.join(assetDir, storageKey), asset.data);
586
+ return {
587
+ imageUrl: createReviewFigmaAssetUrl(assetEndpoint, storageKey),
588
+ imageFormat: asset.imageFormat,
589
+ mimeType: asset.mimeType,
590
+ storageKey,
591
+ byteSize: asset.data.byteLength
592
+ };
593
+ }
594
+ async function downloadReviewFigmaImageAsset({
595
+ fetchOption,
596
+ imageUrl,
597
+ renderFormat,
598
+ targetImageFormat,
599
+ transformAsset
600
+ }) {
601
+ const fetchImage = fetchOption ?? globalThis.fetch;
602
+ if (!fetchImage) throw new Error("Figma image caching requires fetch.");
603
+ const response = await fetchImage(imageUrl);
604
+ if (!response.ok) {
605
+ throw new Error(`Figma image download failed with ${response.status}`);
606
+ }
607
+ const sourceMimeType = normalizeImageMimeType(response.headers.get("content-type")) ?? getReviewFigmaImageMimeType(renderFormat === "jpg" ? "jpg" : "png");
608
+ const sourceImageFormat = getReviewFigmaImageFormatFromMimeType(sourceMimeType) ?? (renderFormat === "jpg" ? "jpg" : "png");
609
+ const sourceData = new Uint8Array(await response.arrayBuffer());
610
+ const transformed = transformAsset ? await transformAsset({
611
+ data: sourceData,
612
+ imageFormat: sourceImageFormat,
613
+ mimeType: sourceMimeType,
614
+ targetFormat: targetImageFormat
615
+ }) : null;
616
+ const imageFormat = transformed?.imageFormat ?? sourceImageFormat;
617
+ const mimeType = normalizeImageMimeType(transformed?.mimeType) ?? getReviewFigmaImageMimeType(imageFormat);
618
+ const data = createBufferFromImageData(transformed?.data ?? sourceData);
619
+ return { data, imageFormat, mimeType };
620
+ }
621
+ async function deleteReviewFigmaImageAsset(assetDir, storageKey) {
622
+ if (!storageKey) return;
623
+ if (!isSafeReviewFigmaAssetStorageKey(storageKey)) return;
624
+ await (0, import_promises.rm)(import_node_path.default.join(assetDir, storageKey), { force: true }).catch(() => null);
625
+ }
626
+ function createBufferFromImageData(data) {
627
+ return data instanceof ArrayBuffer ? Buffer.from(new Uint8Array(data)) : Buffer.from(data);
628
+ }
629
+ function listImagesForTarget(images, target) {
630
+ const targetKey = getReviewFigmaImageTargetKey(target);
631
+ return images.filter((image) => getReviewFigmaImageTargetKey(image.target) === targetKey).sort(compareReviewFigmaImages);
632
+ }
633
+ function reorderReviewFigmaImages(images, input) {
634
+ const targetKey = getReviewFigmaImageTargetKey(input.target);
635
+ const orderById = new Map(input.imageIds.map((id, index) => [id, index]));
636
+ const targetImages = listImagesForTarget(images, input.target);
637
+ const nextTargetImages = targetImages.map((image) => ({
638
+ ...image,
639
+ order: orderById.get(image.id) ?? input.imageIds.length + image.order,
640
+ updatedAt: orderById.has(image.id) ? (/* @__PURE__ */ new Date()).toISOString() : image.updatedAt
641
+ })).sort(compareReviewFigmaImages);
642
+ const nextTargetImageById = new Map(
643
+ nextTargetImages.map((image, index) => [image.id, { ...image, order: index }])
644
+ );
645
+ const allImages = images.map(
646
+ (image) => getReviewFigmaImageTargetKey(image.target) === targetKey ? nextTargetImageById.get(image.id) ?? image : image
647
+ );
648
+ return {
649
+ allImages,
650
+ targetImages: listImagesForTarget(allImages, input.target)
651
+ };
652
+ }
653
+ function updateReviewFigmaImage(images, id, patch) {
654
+ const index = images.findIndex((image) => image.id === id);
655
+ if (index < 0) return null;
656
+ const nextImage = {
657
+ ...images[index],
658
+ label: patch.label === void 0 ? images[index].label : normalizeOptionalText(patch.label),
659
+ order: typeof patch.order === "number" && Number.isFinite(patch.order) ? patch.order : images[index].order,
660
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
661
+ };
662
+ const nextImages = [...images];
663
+ nextImages[index] = nextImage;
664
+ return { image: nextImage, images: nextImages };
665
+ }
666
+ async function readReviewFigmaImageStoreFile(dataFile) {
667
+ try {
668
+ const raw = await (0, import_promises.readFile)(dataFile, "utf8");
669
+ const parsed = JSON.parse(raw);
670
+ return {
671
+ version: 1,
672
+ images: Array.isArray(parsed.images) ? parsed.images.flatMap((image) => isReviewFigmaImage(image) ? [image] : []) : []
673
+ };
674
+ } catch (error) {
675
+ if (isNodeError(error) && error.code === "ENOENT") {
676
+ return { version: 1, images: [] };
677
+ }
678
+ throw error;
679
+ }
680
+ }
681
+ async function writeReviewFigmaImageStoreFile(dataFile, data) {
682
+ await (0, import_promises.mkdir)(import_node_path.default.dirname(dataFile), { recursive: true });
683
+ await (0, import_promises.writeFile)(
684
+ dataFile,
685
+ `${JSON.stringify({ version: 1, images: data.images }, null, 2)}
686
+ `,
687
+ "utf8"
688
+ );
689
+ }
690
+ function getNextImageOrder(images, target) {
691
+ const targetImages = listImagesForTarget(images, target);
692
+ return targetImages.length ? Math.max(...targetImages.map((image) => image.order)) + 1 : 0;
693
+ }
694
+ function compareReviewFigmaImages(a, b) {
695
+ return a.order - b.order || a.createdAt.localeCompare(b.createdAt);
696
+ }
697
+ function createReviewFigmaImageId() {
698
+ return `figma_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
699
+ }
700
+ function isReviewFigmaImage(value) {
701
+ if (!value || typeof value !== "object") return false;
702
+ const image = value;
703
+ return typeof image.id === "string" && typeof image.projectId === "string" && typeof image.figmaUrl === "string" && typeof image.fileKey === "string" && typeof image.nodeId === "string" && typeof image.imageUrl === "string" && typeof image.order === "number" && typeof image.createdAt === "string" && typeof image.updatedAt === "string";
704
+ }
705
+ function isNodeError(error) {
706
+ if (!error || typeof error !== "object") return false;
707
+ return "code" in error;
708
+ }
709
+ function normalizeOptionalText(value) {
710
+ if (typeof value !== "string") return void 0;
711
+ return value.trim() || void 0;
712
+ }
713
+
714
+ // src/vite/figma-image-store.server.ts
715
+ async function handleReviewFigmaImageStoreRequest({
716
+ dataFile,
717
+ assetDir,
718
+ assetEndpoint,
719
+ endpoint,
720
+ method,
721
+ options,
722
+ pathname,
723
+ requestUrl,
724
+ body,
725
+ env
726
+ }) {
727
+ if (method === "OPTIONS") return { status: 204, body: null };
728
+ if ((method === "GET" || method === "POST") && pathname === `${endpoint}/snapshot`) {
729
+ const input = parseReleaseSnapshotInput(body, requestUrl, options.projectId);
730
+ if (!input) return jsonError(400, "valid snapshot input is required.");
731
+ if (options.projectId && input.projectId !== options.projectId) {
732
+ return jsonError(403, "snapshot project is not allowed.");
733
+ }
734
+ if (input.targets.some((target) => !isAllowedProjectTarget(target, options.projectId))) {
735
+ return jsonError(403, "snapshot target project is not allowed.");
736
+ }
737
+ const data = await readReviewFigmaImageStoreFile(dataFile);
738
+ return {
739
+ status: 200,
740
+ body: createReviewFigmaReleaseSnapshot({
741
+ images: data.images,
742
+ projectId: input.projectId,
743
+ releaseId: input.releaseId,
744
+ label: input.label,
745
+ targets: input.targets.length > 0 ? input.targets : void 0
746
+ })
747
+ };
748
+ }
749
+ if (method === "GET" && pathname === endpoint) {
750
+ const target = parseTargetParam(requestUrl.searchParams.get("target"));
751
+ if (!target) return jsonError(400, "target query is required.");
752
+ if (!isAllowedProjectTarget(target, options.projectId)) {
753
+ return { status: 200, body: [] };
754
+ }
755
+ const data = await readReviewFigmaImageStoreFile(dataFile);
756
+ return { status: 200, body: listImagesForTarget(data.images, target) };
757
+ }
758
+ if (method === "POST" && pathname === endpoint) {
759
+ const input = parseAddImageInput(body);
760
+ if (!input) return jsonError(400, "valid add image input is required.");
761
+ if (!isAllowedProjectTarget(input.target, options.projectId)) {
762
+ return jsonError(403, "target project is not allowed.");
763
+ }
764
+ const data = await readReviewFigmaImageStoreFile(dataFile);
765
+ const image = await createReviewFigmaImage({
766
+ assetDir,
767
+ assetEndpoint,
768
+ currentImages: data.images,
769
+ env,
770
+ input,
771
+ options
772
+ });
773
+ data.images = [image, ...data.images];
774
+ await writeReviewFigmaImageStoreFile(dataFile, data);
775
+ return { status: 201, body: image };
776
+ }
777
+ if (method === "PATCH" && pathname === `${endpoint}/reorder`) {
778
+ const input = parseReorderImagesInput(body);
779
+ if (!input) return jsonError(400, "valid reorder input is required.");
780
+ if (!isAllowedProjectTarget(input.target, options.projectId)) {
781
+ return jsonError(403, "target project is not allowed.");
782
+ }
783
+ const data = await readReviewFigmaImageStoreFile(dataFile);
784
+ const images = reorderReviewFigmaImages(data.images, input);
785
+ data.images = images.allImages;
786
+ await writeReviewFigmaImageStoreFile(dataFile, data);
787
+ return { status: 200, body: images.targetImages };
788
+ }
789
+ const id = getEndpointItemId(pathname, endpoint);
790
+ if (id && method === "PATCH") {
791
+ const patch = parseUpdateImageInput(body);
792
+ if (!patch) return jsonError(400, "valid update patch is required.");
793
+ const data = await readReviewFigmaImageStoreFile(dataFile);
794
+ const result = updateReviewFigmaImage(data.images, id, patch);
795
+ if (!result) return jsonError(404, `Figma image not found: ${id}`);
796
+ if (!isAllowedProjectTarget(result.image.target, options.projectId)) {
797
+ return jsonError(403, "target project is not allowed.");
798
+ }
799
+ data.images = result.images;
800
+ await writeReviewFigmaImageStoreFile(dataFile, data);
801
+ return { status: 200, body: result.image };
802
+ }
803
+ if (id && method === "DELETE") {
804
+ const data = await readReviewFigmaImageStoreFile(dataFile);
805
+ const image = data.images.find((item) => item.id === id);
806
+ if (!image) return jsonError(404, `Figma image not found: ${id}`);
807
+ if (!isAllowedProjectTarget(image.target, options.projectId)) {
808
+ return jsonError(403, "target project is not allowed.");
809
+ }
810
+ data.images = data.images.filter((item) => item.id !== id);
811
+ await writeReviewFigmaImageStoreFile(dataFile, data);
812
+ await deleteReviewFigmaImageAsset(assetDir, image.storageKey);
813
+ return { status: 200, body: { ok: true } };
814
+ }
815
+ return jsonError(405, "method not allowed.");
816
+ }
817
+ async function readJsonRequestBody(req) {
818
+ if (req.method === "GET" || req.method === "DELETE") return null;
819
+ const chunks = [];
820
+ for await (const chunk of req) {
821
+ chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
822
+ }
823
+ const raw = Buffer.concat(chunks).toString("utf8").trim();
824
+ if (!raw) return null;
825
+ return JSON.parse(raw);
826
+ }
827
+ function sendJson(res, status, body) {
828
+ res.statusCode = status;
829
+ res.setHeader("Content-Type", "application/json; charset=utf-8");
830
+ if (status === 204) {
831
+ res.end();
832
+ return;
833
+ }
834
+ res.end(JSON.stringify(body ?? null));
835
+ }
836
+ async function sendReviewFigmaAsset(res, assetDir, assetEndpoint, pathname) {
837
+ const storageKey = getReviewFigmaAssetStorageKeyFromPathname(pathname, assetEndpoint);
838
+ if (!storageKey) {
839
+ sendPlainText(res, 400, "Invalid Figma image asset path.");
840
+ return;
841
+ }
842
+ try {
843
+ const data = await (0, import_promises2.readFile)(import_node_path2.default.join(assetDir, storageKey));
844
+ res.statusCode = 200;
845
+ res.setHeader("Content-Type", getReviewFigmaAssetMimeType(storageKey));
846
+ res.setHeader("Cache-Control", "private, max-age=31536000, immutable");
847
+ res.end(data);
848
+ } catch (error) {
849
+ if (isNodeError(error) && error.code === "ENOENT") {
850
+ sendPlainText(res, 404, "Figma image asset not found.");
851
+ return;
852
+ }
853
+ sendPlainText(
854
+ res,
855
+ 500,
856
+ error instanceof Error ? error.message : "Figma image asset request failed."
857
+ );
858
+ }
859
+ }
860
+ function sendPlainText(res, status, body) {
861
+ res.statusCode = status;
862
+ res.setHeader("Content-Type", "text/plain; charset=utf-8");
863
+ res.end(body);
864
+ }
865
+ function parseTargetParam(value) {
866
+ if (!value) return null;
867
+ try {
868
+ return parseReviewFigmaImageTarget(JSON.parse(value));
869
+ } catch {
870
+ return null;
871
+ }
872
+ }
873
+ function parseAddImageInput(value) {
874
+ if (!value || typeof value !== "object") return null;
875
+ const input = value;
876
+ const target = parseReviewFigmaImageTarget(input.target);
877
+ if (!target || typeof input.figmaUrl !== "string") return null;
878
+ return {
879
+ target,
880
+ figmaUrl: input.figmaUrl,
881
+ label: typeof input.label === "string" ? input.label : void 0,
882
+ order: typeof input.order === "number" ? input.order : void 0,
883
+ imageFormat: parseReviewFigmaImageFormat(input.imageFormat),
884
+ asset: parseAddImageAssetInput(input.asset)
885
+ };
886
+ }
887
+ function parseAddImageAssetInput(value) {
888
+ if (!value || typeof value !== "object") return void 0;
889
+ const input = value;
890
+ const imageFormat = parseReviewFigmaImageFormat(input.imageFormat);
891
+ if (!imageFormat || typeof input.dataUrl !== "string" || typeof input.mimeType !== "string") {
892
+ return void 0;
893
+ }
894
+ return {
895
+ dataUrl: input.dataUrl,
896
+ imageFormat,
897
+ mimeType: input.mimeType,
898
+ byteSize: typeof input.byteSize === "number" ? input.byteSize : void 0,
899
+ width: typeof input.width === "number" ? input.width : void 0,
900
+ height: typeof input.height === "number" ? input.height : void 0
901
+ };
902
+ }
903
+ function parseUpdateImageInput(value) {
904
+ if (!value || typeof value !== "object") return null;
905
+ const input = value;
906
+ return {
907
+ label: typeof input.label === "string" ? input.label : void 0,
908
+ order: typeof input.order === "number" ? input.order : void 0
909
+ };
910
+ }
911
+ function parseReorderImagesInput(value) {
912
+ if (!value || typeof value !== "object") return null;
913
+ const input = value;
914
+ const target = parseReviewFigmaImageTarget(input.target);
915
+ if (!target || !Array.isArray(input.imageIds)) return null;
916
+ return {
917
+ target,
918
+ imageIds: input.imageIds.filter((id) => typeof id === "string")
919
+ };
920
+ }
921
+ function parseReleaseSnapshotInput(value, requestUrl, fallbackProjectId) {
922
+ const input = value && typeof value === "object" ? value : null;
923
+ const projectId = normalizeOptionalText(
924
+ typeof input?.projectId === "string" ? input.projectId : requestUrl.searchParams.get("projectId") ?? fallbackProjectId
925
+ );
926
+ if (!projectId) return null;
927
+ return {
928
+ projectId,
929
+ releaseId: normalizeOptionalText(
930
+ typeof input?.releaseId === "string" ? input.releaseId : requestUrl.searchParams.get("releaseId")
931
+ ),
932
+ label: normalizeOptionalText(
933
+ typeof input?.label === "string" ? input.label : requestUrl.searchParams.get("label")
934
+ ),
935
+ targets: parseReleaseSnapshotTargets(input?.targets, requestUrl)
936
+ };
937
+ }
938
+ function parseReleaseSnapshotTargets(value, requestUrl) {
939
+ const bodyTargets = Array.isArray(value) ? value.flatMap((target) => {
940
+ const parsed = parseReviewFigmaImageTarget(target);
941
+ return parsed ? [parsed] : [];
942
+ }) : [];
943
+ const queryTargets = requestUrl.searchParams.getAll("target").flatMap((target) => {
944
+ const parsed = parseTargetParam(target);
945
+ return parsed ? [parsed] : [];
946
+ });
947
+ const targetByKey = new Map(
948
+ [...bodyTargets, ...queryTargets].map((target) => [
949
+ getReviewFigmaImageTargetKey(target),
950
+ target
951
+ ])
952
+ );
953
+ return Array.from(targetByKey.values());
954
+ }
955
+ function parseReviewFigmaImageTarget(value) {
956
+ if (!value || typeof value !== "object") return null;
957
+ const target = value;
958
+ if (target.type === "route") {
959
+ if (typeof target.projectId !== "string" || typeof target.pageUrl !== "string") {
960
+ return null;
961
+ }
962
+ return {
963
+ type: "route",
964
+ projectId: target.projectId,
965
+ pageUrl: target.pageUrl,
966
+ viewport: target.viewport && typeof target.viewport === "object" ? target.viewport : void 0,
967
+ slot: typeof target.slot === "string" ? target.slot : void 0
968
+ };
969
+ }
970
+ if (target.type === "figma-node") {
971
+ if (typeof target.projectId !== "string" || typeof target.fileKey !== "string" || typeof target.nodeId !== "string") {
972
+ return null;
973
+ }
974
+ return {
975
+ type: "figma-node",
976
+ projectId: target.projectId,
977
+ fileKey: target.fileKey,
978
+ nodeId: target.nodeId
979
+ };
980
+ }
981
+ return null;
982
+ }
983
+ function normalizeEndpoint(endpoint) {
984
+ const normalized = endpoint.trim().replace(/\/+$/, "");
985
+ return normalized.startsWith("/") ? normalized : `/${normalized}`;
986
+ }
987
+ function getEndpointItemId(pathname, endpoint) {
988
+ if (!pathname.startsWith(`${endpoint}/`)) return null;
989
+ const value = pathname.slice(endpoint.length + 1);
990
+ if (!value || value.includes("/")) return null;
991
+ return decodeURIComponent(value);
992
+ }
993
+ function isAllowedProjectTarget(target, projectId) {
994
+ return !projectId || target.projectId === projectId;
995
+ }
996
+ function jsonError(status, error) {
997
+ return { status, body: { error } };
998
+ }
999
+
1000
+ // src/vite/figma-image-store.ts
1001
+ var readReviewFigmaServerToken = (options = {}) => readReviewFigmaToken({
1002
+ token: options.token,
1003
+ env: options.env ?? getServerEnv(),
1004
+ envKey: options.envKey ?? DEFAULT_REVIEW_FIGMA_TOKEN_ENV_KEY,
1005
+ enabled: options.enabled
1006
+ });
1007
+ var requireReviewFigmaServerToken = (options = {}) => requireReviewFigmaToken({
1008
+ token: options.token,
1009
+ env: options.env ?? getServerEnv(),
1010
+ envKey: options.envKey ?? DEFAULT_REVIEW_FIGMA_TOKEN_ENV_KEY,
1011
+ enabled: options.enabled
1012
+ });
1013
+ var renderReviewFigmaServerImage = (options) => {
1014
+ const { token, env, envKey, enabled, ...renderOptions } = options;
1015
+ const explicitToken = typeof token === "string" ? token.trim() : token;
1016
+ return renderReviewFigmaImage({
1017
+ ...renderOptions,
1018
+ token: explicitToken || requireReviewFigmaServerToken({ env, envKey, enabled })
1019
+ });
1020
+ };
1021
+ var reviewFigmaImageStore = (options = {}) => {
1022
+ let root = "";
1023
+ let dataFile = "";
1024
+ let assetDir = "";
1025
+ let env = {};
1026
+ const enabled = options.enabled ?? true;
1027
+ const endpoint = normalizeEndpoint(
1028
+ options.endpoint ?? DEFAULT_REVIEW_FIGMA_IMAGE_STORE_ENDPOINT
1029
+ );
1030
+ const assetEndpoint = normalizeEndpoint(
1031
+ options.assetEndpoint ?? `${endpoint}/assets`
1032
+ );
1033
+ return {
1034
+ name: "df-web-review-kit-figma-image-store",
1035
+ apply: "serve",
1036
+ configResolved(config) {
1037
+ root = config.root;
1038
+ dataFile = import_node_path3.default.resolve(
1039
+ root,
1040
+ options.dataFile ?? ".df-review/figma-images.json"
1041
+ );
1042
+ assetDir = options.assetDir ? import_node_path3.default.resolve(root, options.assetDir) : import_node_path3.default.join(import_node_path3.default.dirname(dataFile), "figma-assets");
1043
+ env = {
1044
+ ...(0, import_vite.loadEnv)(config.mode, config.envDir, ""),
1045
+ ...getServerEnv(),
1046
+ ...options.env ?? {}
1047
+ };
1048
+ },
1049
+ configureServer(server) {
1050
+ if (!enabled) return;
1051
+ server.middlewares.use(async (req, res, next) => {
1052
+ const requestUrl = new URL(req.url ?? "/", "http://localhost");
1053
+ const pathname = requestUrl.pathname;
1054
+ if (pathname.startsWith(`${assetEndpoint}/`)) {
1055
+ await sendReviewFigmaAsset(res, assetDir, assetEndpoint, pathname);
1056
+ return;
1057
+ }
1058
+ if (pathname !== endpoint && !pathname.startsWith(`${endpoint}/`)) {
1059
+ next();
1060
+ return;
1061
+ }
1062
+ try {
1063
+ const response = await handleReviewFigmaImageStoreRequest({
1064
+ dataFile,
1065
+ assetDir,
1066
+ assetEndpoint,
1067
+ endpoint,
1068
+ options,
1069
+ env,
1070
+ pathname,
1071
+ requestUrl,
1072
+ method: req.method ?? "GET",
1073
+ body: await readJsonRequestBody(req)
1074
+ });
1075
+ sendJson(res, response.status, response.body);
1076
+ } catch (error) {
1077
+ sendJson(res, 500, {
1078
+ error: error instanceof Error ? error.message : "Figma image store request failed."
1079
+ });
1080
+ }
1081
+ });
1082
+ }
1083
+ };
1084
+ };
1085
+ function getServerEnv() {
1086
+ const runtime = globalThis;
1087
+ return runtime.process?.env ?? {};
1088
+ }
1089
+
1090
+ // src/vite.ts
27
1091
  var VIRTUAL_JSX_DEV_RUNTIME_ID = "\0@designfever/web-review-kit/source-locator/jsx-dev-runtime";
1092
+ var REVIEW_SOURCE_ENV_DEFINE_KEYS = [
1093
+ ["__DF_WRK_REVIEW_SOURCE_ROOT__", "VITE_REVIEW_SOURCE_ROOT"],
1094
+ ["__DF_WRK_REVIEW_SOURCE_EDITOR__", "VITE_REVIEW_SOURCE_EDITOR"],
1095
+ [
1096
+ "__DF_WRK_REVIEW_SOURCE_URL_TEMPLATE__",
1097
+ "VITE_REVIEW_SOURCE_URL_TEMPLATE"
1098
+ ]
1099
+ ];
1100
+ var createReviewSourceEnvReplacements = (env = {}) => {
1101
+ return Object.fromEntries(
1102
+ REVIEW_SOURCE_ENV_DEFINE_KEYS.map(([defineKey, envKey]) => [
1103
+ defineKey,
1104
+ JSON.stringify(env[envKey] ?? "")
1105
+ ])
1106
+ );
1107
+ };
1108
+ var injectReviewSourceEnv = (code, replacements) => {
1109
+ let nextCode = code;
1110
+ for (const [defineKey, value] of Object.entries(replacements)) {
1111
+ nextCode = nextCode.split(`typeof ${defineKey}`).join(`typeof ${value}`).split(`: ${defineKey}`).join(`: ${value}`);
1112
+ }
1113
+ return nextCode === code ? null : nextCode;
1114
+ };
28
1115
  var reviewSourceLocator = (options = {}) => {
29
1116
  let runtimeOptions = createRuntimeOptions(options);
1117
+ let sourceEnvReplacements = createReviewSourceEnvReplacements();
30
1118
  return {
31
1119
  name: "df-web-review-kit-source-locator",
32
1120
  enforce: "pre",
33
1121
  configResolved(config) {
34
1122
  runtimeOptions = createRuntimeOptions(options, config);
1123
+ sourceEnvReplacements = createReviewSourceEnvReplacements(config.env);
35
1124
  },
36
1125
  resolveId(id, importer) {
37
1126
  if (!runtimeOptions.enabled) return null;
@@ -42,12 +1131,17 @@ var reviewSourceLocator = (options = {}) => {
42
1131
  load(id) {
43
1132
  if (id !== VIRTUAL_JSX_DEV_RUNTIME_ID) return null;
44
1133
  return createJsxDevRuntime(runtimeOptions);
1134
+ },
1135
+ transform(code) {
1136
+ const injectedCode = injectReviewSourceEnv(code, sourceEnvReplacements);
1137
+ return injectedCode ? { code: injectedCode, map: null } : null;
45
1138
  }
46
1139
  };
47
1140
  };
48
1141
  var reviewDataLocator = (options = {}) => {
49
1142
  let root = normalizePath(options.root ?? "");
50
1143
  let enabled = options.enabled ?? false;
1144
+ let sourceEnvReplacements = createReviewSourceEnvReplacements();
51
1145
  const include = (options.include ?? []).map(createRuntimeMatcher);
52
1146
  const exclude = (options.exclude ?? ["node_modules", "dist"]).map(
53
1147
  createRuntimeMatcher
@@ -62,26 +1156,34 @@ var reviewDataLocator = (options = {}) => {
62
1156
  configResolved(config) {
63
1157
  root = normalizePath(options.root ?? config.root ?? "");
64
1158
  enabled = options.enabled ?? config.command === "serve";
1159
+ sourceEnvReplacements = createReviewSourceEnvReplacements(config.env);
65
1160
  },
66
1161
  transform(code, id) {
1162
+ const envInjectedCode = injectReviewSourceEnv(
1163
+ code,
1164
+ sourceEnvReplacements
1165
+ );
1166
+ const inputCode = envInjectedCode ?? code;
67
1167
  if (!enabled) return null;
68
1168
  const file = normalizePath(id.split("?")[0]);
69
1169
  const relativeFile = root && file.startsWith(root + "/") ? file.slice(root.length + 1) : file;
70
1170
  if (include.length > 0 && !include.some((m) => matchesPath(m, file, relativeFile)))
71
- return null;
72
- if (exclude.some((m) => matchesPath(m, file, relativeFile))) return null;
1171
+ return envInjectedCode ? { code: envInjectedCode, map: null } : null;
1172
+ if (exclude.some((m) => matchesPath(m, file, relativeFile))) {
1173
+ return envInjectedCode ? { code: envInjectedCode, map: null } : null;
1174
+ }
73
1175
  const sourceFile = (options.filePath ?? "relative") === "absolute" ? file : relativeFile;
74
1176
  const regex = new RegExp(componentSource, "g");
75
1177
  let changed = false;
76
- const out = code.replace(
1178
+ const out = inputCode.replace(
77
1179
  regex,
78
1180
  (_match, pre, comp, quote, name, offset) => {
79
- const line = code.slice(0, offset + pre.length).split("\n").length;
1181
+ const line = inputCode.slice(0, offset + pre.length).split("\n").length;
80
1182
  changed = true;
81
1183
  return `${pre}${JSON.stringify(fileKey)}: ${JSON.stringify(sourceFile)}, ${JSON.stringify(lineKey)}: ${line}, ${comp}${quote}${name}${quote}`;
82
1184
  }
83
1185
  );
84
- return changed ? { code: out, map: null } : null;
1186
+ return changed || envInjectedCode ? { code: out, map: null } : null;
85
1187
  }
86
1188
  };
87
1189
  };
@@ -230,7 +1332,16 @@ function normalizePath(value) {
230
1332
  }
231
1333
  // Annotate the CommonJS export names for ESM import in node:
232
1334
  0 && (module.exports = {
1335
+ collectReviewFigmaReleaseSnapshot,
1336
+ createReviewFigmaImageApiUrl,
1337
+ createReviewFigmaImagesSnapshot,
1338
+ createReviewFigmaReleaseSnapshot,
1339
+ readReviewFigmaServerToken,
1340
+ renderReviewFigmaImage,
1341
+ renderReviewFigmaServerImage,
1342
+ requireReviewFigmaServerToken,
233
1343
  reviewDataLocator,
1344
+ reviewFigmaImageStore,
234
1345
  reviewSourceLocator
235
1346
  });
236
1347
  //# sourceMappingURL=vite.cjs.map