@designfever/web-review-kit 0.7.0 → 0.7.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/dist/vite.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  DEFAULT_REVIEW_FIGMA_IMAGE_STORE_ENDPOINT,
3
3
  DEFAULT_REVIEW_FIGMA_TOKEN_ENV_KEY,
4
+ ReviewFigmaTokenError,
4
5
  collectReviewFigmaReleaseSnapshot,
5
6
  createReviewFigmaAssetStorageKey,
6
7
  createReviewFigmaAssetUrl,
@@ -20,7 +21,7 @@ import {
20
21
  readReviewFigmaToken,
21
22
  renderReviewFigmaImage,
22
23
  requireReviewFigmaToken
23
- } from "./chunk-AB5B6O77.js";
24
+ } from "./chunk-K26WLRRP.js";
24
25
 
25
26
  // src/vite/figma-image-store.ts
26
27
  import path3 from "path";
@@ -33,6 +34,23 @@ import path2 from "path";
33
34
  // src/vite/figma-image-store.image.ts
34
35
  import { mkdir, readFile, rm, writeFile } from "fs/promises";
35
36
  import path from "path";
37
+ function requireReviewFigmaRequestToken({
38
+ enabled,
39
+ env,
40
+ envKey,
41
+ requestToken,
42
+ token
43
+ }) {
44
+ const tokenEnvKey = envKey ?? DEFAULT_REVIEW_FIGMA_TOKEN_ENV_KEY;
45
+ const figmaToken = readReviewFigmaToken({ token, env, envKey: tokenEnvKey, enabled }) || readReviewFigmaToken({
46
+ token: requestToken,
47
+ env: {},
48
+ envKey: tokenEnvKey,
49
+ enabled
50
+ });
51
+ if (!figmaToken) throw new ReviewFigmaTokenError(tokenEnvKey);
52
+ return figmaToken;
53
+ }
36
54
  async function readReviewFigmaNodeName({
37
55
  apiBaseUrl,
38
56
  enabled,
@@ -41,14 +59,15 @@ async function readReviewFigmaNodeName({
41
59
  fetchOption,
42
60
  fileKey,
43
61
  nodeId,
44
- token
62
+ token,
63
+ requestToken
45
64
  }) {
46
- const explicitToken = typeof token === "string" ? token.trim() : token;
47
- const figmaToken = explicitToken || requireReviewFigmaToken({
48
- token: null,
65
+ const figmaToken = requireReviewFigmaRequestToken({
66
+ enabled,
49
67
  env,
50
- envKey: envKey ?? DEFAULT_REVIEW_FIGMA_TOKEN_ENV_KEY,
51
- enabled
68
+ envKey,
69
+ requestToken,
70
+ token
52
71
  });
53
72
  const fetchNode = fetchOption ?? globalThis.fetch;
54
73
  if (!fetchNode) throw new Error("Figma node name lookup requires fetch.");
@@ -86,7 +105,8 @@ async function createReviewFigmaImage({
86
105
  currentImages,
87
106
  env,
88
107
  input,
89
- options
108
+ options,
109
+ requestToken
90
110
  }) {
91
111
  const ref = parseReviewFigmaNodeRef(input.figmaUrl);
92
112
  if (!ref) {
@@ -132,11 +152,11 @@ async function createReviewFigmaImage({
132
152
  fetchOption: options.fetch,
133
153
  fileKey: ref.fileKey,
134
154
  nodeId: ref.nodeId,
135
- token: options.token
155
+ token: options.token,
156
+ requestToken
136
157
  }).catch(() => void 0);
137
158
  const targetImageFormat = input.imageFormat ?? options.imageFormat ?? "webp";
138
159
  const renderFormat = getStoreRenderFormat(options.renderFormat, targetImageFormat);
139
- const explicitToken = typeof options.token === "string" ? options.token.trim() : options.token;
140
160
  const rendered = await renderReviewFigmaImage({
141
161
  figmaUrl: input.figmaUrl,
142
162
  format: renderFormat,
@@ -144,11 +164,12 @@ async function createReviewFigmaImage({
144
164
  useAbsoluteBounds: options.useAbsoluteBounds,
145
165
  apiBaseUrl: options.apiBaseUrl,
146
166
  fetch: options.fetch,
147
- token: explicitToken || requireReviewFigmaToken({
148
- token: null,
167
+ token: requireReviewFigmaRequestToken({
168
+ enabled: options.enabled,
149
169
  env,
150
- envKey: options.envKey ?? DEFAULT_REVIEW_FIGMA_TOKEN_ENV_KEY,
151
- enabled: options.enabled
170
+ envKey: options.envKey,
171
+ requestToken,
172
+ token: options.token
152
173
  })
153
174
  });
154
175
  const cachedAsset = await cacheReviewFigmaImageAsset({
@@ -381,7 +402,8 @@ async function handleReviewFigmaImageStoreRequest({
381
402
  pathname,
382
403
  requestUrl,
383
404
  body,
384
- env
405
+ env,
406
+ requestToken
385
407
  }) {
386
408
  if (method === "OPTIONS") return { status: 204, body: null };
387
409
  if ((method === "GET" || method === "POST") && pathname === `${endpoint}/snapshot`) {
@@ -427,7 +449,8 @@ async function handleReviewFigmaImageStoreRequest({
427
449
  currentImages: data.images,
428
450
  env,
429
451
  input,
430
- options
452
+ options,
453
+ requestToken
431
454
  });
432
455
  data.images = [image, ...data.images];
433
456
  await writeReviewFigmaImageStoreFile(dataFile, data);
@@ -729,7 +752,8 @@ var reviewFigmaImageStore = (options = {}) => {
729
752
  pathname,
730
753
  requestUrl,
731
754
  method: req.method ?? "GET",
732
- body: await readJsonRequestBody(req)
755
+ body: await readJsonRequestBody(req),
756
+ requestToken: readRequestFigmaToken(req)
733
757
  });
734
758
  sendJson(res, response.status, response.body);
735
759
  } catch (error) {
@@ -741,6 +765,11 @@ var reviewFigmaImageStore = (options = {}) => {
741
765
  }
742
766
  };
743
767
  };
768
+ function readRequestFigmaToken(req) {
769
+ const value = req.headers?.["x-figma-token"];
770
+ const token = Array.isArray(value) ? value[0] : value;
771
+ return typeof token === "string" ? token.trim() || null : null;
772
+ }
744
773
  function getServerEnv() {
745
774
  const runtime = globalThis;
746
775
  return runtime.process?.env ?? {};
package/dist/vite.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/vite/figma-image-store.ts","../src/vite/figma-image-store.server.ts","../src/vite/figma-image-store.image.ts","../src/vite.ts"],"sourcesContent":["import path from 'node:path';\nimport { loadEnv, type Plugin } from 'vite';\nimport type { ReviewFigmaImageFormat } from '../figma/image.types';\nimport {\n DEFAULT_REVIEW_FIGMA_IMAGE_STORE_ENDPOINT,\n} from '../figma/image.store';\nimport {\n DEFAULT_REVIEW_FIGMA_TOKEN_ENV_KEY,\n readReviewFigmaToken,\n requireReviewFigmaToken,\n type ReviewFigmaTokenEnv,\n} from '../figma/token';\nimport {\n renderReviewFigmaImage,\n type ReviewFigmaImageRenderOptions,\n type ReviewFigmaRenderFormat,\n} from '../figma/render';\nimport {\n handleReviewFigmaImageStoreRequest,\n normalizeEndpoint,\n readJsonRequestBody,\n sendJson,\n sendReviewFigmaAsset,\n} from './figma-image-store.server';\n\nexport interface ReviewFigmaImageStorePluginOptions\n extends ReviewFigmaServerTokenOptions {\n enabled?: boolean;\n projectId?: string;\n endpoint?: string;\n dataFile?: string;\n assetDir?: string;\n assetEndpoint?: string;\n cacheAssets?: boolean;\n imageFormat?: ReviewFigmaImageFormat;\n renderFormat?: ReviewFigmaRenderFormat;\n renderScale?: number;\n useAbsoluteBounds?: boolean;\n apiBaseUrl?: string;\n fetch?: typeof fetch;\n transformAsset?: ReviewFigmaImageAssetTransformer;\n}\n\nexport interface ReviewFigmaServerTokenOptions {\n token?: string | null;\n env?: ReviewFigmaTokenEnv;\n envKey?: string;\n enabled?: boolean;\n}\n\nexport type ReviewFigmaServerImageRenderOptions =\n Omit<ReviewFigmaImageRenderOptions, 'token'> &\n ReviewFigmaServerTokenOptions;\n\nexport type ReviewFigmaImageAssetTransformInput = {\n data: Uint8Array;\n imageFormat: ReviewFigmaImageFormat;\n mimeType: string;\n targetFormat: ReviewFigmaImageFormat;\n};\n\nexport type ReviewFigmaImageAssetTransformResult = {\n data: Uint8Array | ArrayBuffer;\n imageFormat: ReviewFigmaImageFormat;\n mimeType?: string;\n};\n\nexport type ReviewFigmaImageAssetTransformer = (\n input: ReviewFigmaImageAssetTransformInput\n) =>\n | ReviewFigmaImageAssetTransformResult\n | null\n | undefined\n | Promise<ReviewFigmaImageAssetTransformResult | null | undefined>;\n\nexport {\n createReviewFigmaImageApiUrl,\n renderReviewFigmaImage,\n} from '../figma/render';\nexport {\n collectReviewFigmaReleaseSnapshot,\n createReviewFigmaImagesSnapshot,\n createReviewFigmaReleaseSnapshot,\n} from '../figma/image.snapshot';\nexport type {\n ReviewFigmaImageRenderOptions,\n ReviewFigmaRenderFormat,\n ReviewFigmaRenderedImage,\n} from '../figma/render';\nexport type {\n CollectReviewFigmaReleaseSnapshotOptions,\n CreateReviewFigmaImagesSnapshotOptions,\n CreateReviewFigmaReleaseSnapshotOptions,\n ReviewFigmaImagesSnapshot,\n ReviewFigmaReleaseSnapshot,\n} from '../figma/image.snapshot';\n\nexport const readReviewFigmaServerToken = (\n options: ReviewFigmaServerTokenOptions = {}\n) =>\n readReviewFigmaToken({\n token: options.token,\n env: options.env ?? getServerEnv(),\n envKey: options.envKey ?? DEFAULT_REVIEW_FIGMA_TOKEN_ENV_KEY,\n enabled: options.enabled,\n });\n\nexport const requireReviewFigmaServerToken = (\n options: ReviewFigmaServerTokenOptions = {}\n) =>\n requireReviewFigmaToken({\n token: options.token,\n env: options.env ?? getServerEnv(),\n envKey: options.envKey ?? DEFAULT_REVIEW_FIGMA_TOKEN_ENV_KEY,\n enabled: options.enabled,\n });\n\nexport const renderReviewFigmaServerImage = (\n options: ReviewFigmaServerImageRenderOptions\n) => {\n const { token, env, envKey, enabled, ...renderOptions } = options;\n const explicitToken = typeof token === 'string' ? token.trim() : token;\n\n return renderReviewFigmaImage({\n ...renderOptions,\n token:\n explicitToken ||\n requireReviewFigmaServerToken({ env, envKey, enabled }),\n });\n};\n\nexport const reviewFigmaImageStore = (\n options: ReviewFigmaImageStorePluginOptions = {}\n): Plugin => {\n let root = '';\n let dataFile = '';\n let assetDir = '';\n let env: ReviewFigmaTokenEnv = {};\n const enabled = options.enabled ?? true;\n const endpoint = normalizeEndpoint(\n options.endpoint ?? DEFAULT_REVIEW_FIGMA_IMAGE_STORE_ENDPOINT\n );\n const assetEndpoint = normalizeEndpoint(\n options.assetEndpoint ?? `${endpoint}/assets`\n );\n\n return {\n name: 'df-web-review-kit-figma-image-store',\n apply: 'serve',\n configResolved(config) {\n root = config.root;\n dataFile = path.resolve(\n root,\n options.dataFile ?? '.df-review/figma-images.json'\n );\n assetDir = options.assetDir\n ? path.resolve(root, options.assetDir)\n : path.join(path.dirname(dataFile), 'figma-assets');\n env = {\n ...loadEnv(config.mode, config.envDir, ''),\n ...getServerEnv(),\n ...(options.env ?? {}),\n };\n },\n configureServer(server) {\n if (!enabled) return;\n\n server.middlewares.use(async (req, res, next) => {\n const requestUrl = new URL(req.url ?? '/', 'http://localhost');\n const pathname = requestUrl.pathname;\n if (pathname.startsWith(`${assetEndpoint}/`)) {\n await sendReviewFigmaAsset(res, assetDir, assetEndpoint, pathname);\n return;\n }\n if (pathname !== endpoint && !pathname.startsWith(`${endpoint}/`)) {\n next();\n return;\n }\n\n try {\n const response = await handleReviewFigmaImageStoreRequest({\n dataFile,\n assetDir,\n assetEndpoint,\n endpoint,\n options,\n env,\n pathname,\n requestUrl,\n method: req.method ?? 'GET',\n body: await readJsonRequestBody(req),\n });\n\n sendJson(res, response.status, response.body);\n } catch (error) {\n sendJson(res, 500, {\n error:\n error instanceof Error\n ? error.message\n : 'Figma image store request failed.',\n });\n }\n });\n },\n };\n};\n\nfunction getServerEnv(): ReviewFigmaTokenEnv {\n const runtime = globalThis as typeof globalThis & {\n process?: {\n env?: ReviewFigmaTokenEnv;\n };\n };\n\n return runtime.process?.env ?? {};\n}\n","import { readFile } from 'node:fs/promises';\nimport type { IncomingMessage, ServerResponse } from 'node:http';\nimport path from 'node:path';\nimport type {\n ReviewFigmaImage,\n ReviewFigmaImageTarget,\n AddReviewFigmaImageInput,\n} from '../figma/image.types';\nimport {\n getReviewFigmaImageTargetKey,\n type ReorderReviewFigmaImagesInput,\n} from '../figma/image.store';\nimport type { ReviewFigmaTokenEnv } from '../figma/token';\nimport { createReviewFigmaReleaseSnapshot } from '../figma/image.snapshot';\nimport {\n getReviewFigmaAssetMimeType,\n getReviewFigmaAssetStorageKeyFromPathname,\n parseReviewFigmaImageFormat,\n} from './figma-asset';\nimport type { ReviewFigmaImageStorePluginOptions } from './figma-image-store';\nimport {\n createReviewFigmaImage,\n deleteReviewFigmaImageAsset,\n isNodeError,\n listImagesForTarget,\n normalizeOptionalText,\n readReviewFigmaImageStoreFile,\n reorderReviewFigmaImages,\n updateReviewFigmaImage,\n writeReviewFigmaImageStoreFile,\n} from './figma-image-store.image';\n\ntype ReviewFigmaImageStoreResponse = {\n status: number;\n body: unknown;\n};\n\nexport async function handleReviewFigmaImageStoreRequest({\n dataFile,\n assetDir,\n assetEndpoint,\n endpoint,\n method,\n options,\n pathname,\n requestUrl,\n body,\n env,\n}: {\n dataFile: string;\n assetDir: string;\n assetEndpoint: string;\n endpoint: string;\n method: string;\n options: ReviewFigmaImageStorePluginOptions;\n env: ReviewFigmaTokenEnv;\n pathname: string;\n requestUrl: URL;\n body: unknown;\n}): Promise<ReviewFigmaImageStoreResponse> {\n if (method === 'OPTIONS') return { status: 204, body: null };\n\n if (\n (method === 'GET' || method === 'POST') &&\n pathname === `${endpoint}/snapshot`\n ) {\n const input = parseReleaseSnapshotInput(body, requestUrl, options.projectId);\n if (!input) return jsonError(400, 'valid snapshot input is required.');\n if (options.projectId && input.projectId !== options.projectId) {\n return jsonError(403, 'snapshot project is not allowed.');\n }\n if (input.targets.some((target) => !isAllowedProjectTarget(target, options.projectId))) {\n return jsonError(403, 'snapshot target project is not allowed.');\n }\n\n const data = await readReviewFigmaImageStoreFile(dataFile);\n return {\n status: 200,\n body: createReviewFigmaReleaseSnapshot({\n images: data.images,\n projectId: input.projectId,\n releaseId: input.releaseId,\n label: input.label,\n targets: input.targets.length > 0 ? input.targets : undefined,\n }),\n };\n }\n\n if (method === 'GET' && pathname === endpoint) {\n const target = parseTargetParam(requestUrl.searchParams.get('target'));\n if (!target) return jsonError(400, 'target query is required.');\n if (!isAllowedProjectTarget(target, options.projectId)) {\n return { status: 200, body: [] };\n }\n\n const data = await readReviewFigmaImageStoreFile(dataFile);\n return { status: 200, body: listImagesForTarget(data.images, target) };\n }\n\n if (method === 'POST' && pathname === endpoint) {\n const input = parseAddImageInput(body);\n if (!input) return jsonError(400, 'valid add image input is required.');\n if (!isAllowedProjectTarget(input.target, options.projectId)) {\n return jsonError(403, 'target project is not allowed.');\n }\n\n const data = await readReviewFigmaImageStoreFile(dataFile);\n const image = await createReviewFigmaImage({\n assetDir,\n assetEndpoint,\n currentImages: data.images,\n env,\n input,\n options,\n });\n data.images = [image, ...data.images];\n await writeReviewFigmaImageStoreFile(dataFile, data);\n\n return { status: 201, body: image };\n }\n\n if (method === 'PATCH' && pathname === `${endpoint}/reorder`) {\n const input = parseReorderImagesInput(body);\n if (!input) return jsonError(400, 'valid reorder input is required.');\n if (!isAllowedProjectTarget(input.target, options.projectId)) {\n return jsonError(403, 'target project is not allowed.');\n }\n\n const data = await readReviewFigmaImageStoreFile(dataFile);\n const images = reorderReviewFigmaImages(data.images, input);\n data.images = images.allImages;\n await writeReviewFigmaImageStoreFile(dataFile, data);\n\n return { status: 200, body: images.targetImages };\n }\n\n const id = getEndpointItemId(pathname, endpoint);\n if (id && method === 'PATCH') {\n const patch = parseUpdateImageInput(body);\n if (!patch) return jsonError(400, 'valid update patch is required.');\n\n const data = await readReviewFigmaImageStoreFile(dataFile);\n const result = updateReviewFigmaImage(data.images, id, patch);\n if (!result) return jsonError(404, `Figma image not found: ${id}`);\n if (!isAllowedProjectTarget(result.image.target, options.projectId)) {\n return jsonError(403, 'target project is not allowed.');\n }\n\n data.images = result.images;\n await writeReviewFigmaImageStoreFile(dataFile, data);\n\n return { status: 200, body: result.image };\n }\n\n if (id && method === 'DELETE') {\n const data = await readReviewFigmaImageStoreFile(dataFile);\n const image = data.images.find((item) => item.id === id);\n if (!image) return jsonError(404, `Figma image not found: ${id}`);\n if (!isAllowedProjectTarget(image.target, options.projectId)) {\n return jsonError(403, 'target project is not allowed.');\n }\n\n data.images = data.images.filter((item) => item.id !== id);\n await writeReviewFigmaImageStoreFile(dataFile, data);\n await deleteReviewFigmaImageAsset(assetDir, image.storageKey);\n\n return { status: 200, body: { ok: true } };\n }\n\n return jsonError(405, 'method not allowed.');\n}\n\nexport async function readJsonRequestBody(req: IncomingMessage) {\n if (req.method === 'GET' || req.method === 'DELETE') return null;\n\n const chunks: Buffer[] = [];\n for await (const chunk of req) {\n chunks.push(typeof chunk === 'string' ? Buffer.from(chunk) : chunk);\n }\n const raw = Buffer.concat(chunks).toString('utf8').trim();\n if (!raw) return null;\n\n return JSON.parse(raw);\n}\n\nexport function sendJson(res: ServerResponse, status: number, body: unknown) {\n res.statusCode = status;\n res.setHeader('Content-Type', 'application/json; charset=utf-8');\n if (status === 204) {\n res.end();\n return;\n }\n res.end(JSON.stringify(body ?? null));\n}\n\nexport async function sendReviewFigmaAsset(\n res: ServerResponse,\n assetDir: string,\n assetEndpoint: string,\n pathname: string\n) {\n const storageKey = getReviewFigmaAssetStorageKeyFromPathname(pathname, assetEndpoint);\n if (!storageKey) {\n sendPlainText(res, 400, 'Invalid Figma image asset path.');\n return;\n }\n\n try {\n const data = await readFile(path.join(assetDir, storageKey));\n res.statusCode = 200;\n res.setHeader('Content-Type', getReviewFigmaAssetMimeType(storageKey));\n res.setHeader('Cache-Control', 'private, max-age=31536000, immutable');\n res.end(data);\n } catch (error) {\n if (isNodeError(error) && error.code === 'ENOENT') {\n sendPlainText(res, 404, 'Figma image asset not found.');\n return;\n }\n sendPlainText(\n res,\n 500,\n error instanceof Error ? error.message : 'Figma image asset request failed.'\n );\n }\n}\n\nfunction sendPlainText(res: ServerResponse, status: number, body: string) {\n res.statusCode = status;\n res.setHeader('Content-Type', 'text/plain; charset=utf-8');\n res.end(body);\n}\n\nfunction parseTargetParam(value: string | null) {\n if (!value) return null;\n try {\n return parseReviewFigmaImageTarget(JSON.parse(value));\n } catch {\n return null;\n }\n}\n\nfunction parseAddImageInput(value: unknown): AddReviewFigmaImageInput | null {\n if (!value || typeof value !== 'object') return null;\n const input = value as Partial<AddReviewFigmaImageInput>;\n const target = parseReviewFigmaImageTarget(input.target);\n if (!target || typeof input.figmaUrl !== 'string') return null;\n\n return {\n target,\n figmaUrl: input.figmaUrl,\n label: typeof input.label === 'string' ? input.label : undefined,\n order: typeof input.order === 'number' ? input.order : undefined,\n imageFormat: parseReviewFigmaImageFormat(input.imageFormat),\n asset: parseAddImageAssetInput(input.asset),\n };\n}\n\nfunction parseAddImageAssetInput(\n value: unknown\n): AddReviewFigmaImageInput['asset'] {\n if (!value || typeof value !== 'object') return undefined;\n const input = value as Partial<NonNullable<AddReviewFigmaImageInput['asset']>>;\n const imageFormat = parseReviewFigmaImageFormat(input.imageFormat);\n if (\n !imageFormat ||\n typeof input.dataUrl !== 'string' ||\n typeof input.mimeType !== 'string'\n ) {\n return undefined;\n }\n\n return {\n dataUrl: input.dataUrl,\n imageFormat,\n mimeType: input.mimeType,\n byteSize: typeof input.byteSize === 'number' ? input.byteSize : undefined,\n width: typeof input.width === 'number' ? input.width : undefined,\n height: typeof input.height === 'number' ? input.height : undefined,\n };\n}\n\nfunction parseUpdateImageInput(value: unknown) {\n if (!value || typeof value !== 'object') return null;\n const input = value as { label?: unknown; order?: unknown };\n\n return {\n label: typeof input.label === 'string' ? input.label : undefined,\n order: typeof input.order === 'number' ? input.order : undefined,\n };\n}\n\nfunction parseReorderImagesInput(\n value: unknown\n): ReorderReviewFigmaImagesInput | null {\n if (!value || typeof value !== 'object') return null;\n const input = value as ReorderReviewFigmaImagesInput;\n const target = parseReviewFigmaImageTarget(input.target);\n if (!target || !Array.isArray(input.imageIds)) return null;\n\n return {\n target,\n imageIds: input.imageIds.filter((id) => typeof id === 'string'),\n };\n}\n\nfunction parseReleaseSnapshotInput(\n value: unknown,\n requestUrl: URL,\n fallbackProjectId: string | undefined\n): {\n projectId: string;\n releaseId?: string;\n label?: string;\n targets: ReviewFigmaImageTarget[];\n} | null {\n const input = value && typeof value === 'object'\n ? (value as Partial<{ projectId: unknown; releaseId: unknown; label: unknown; targets: unknown }>)\n : null;\n const projectId = normalizeOptionalText(\n typeof input?.projectId === 'string'\n ? input.projectId\n : requestUrl.searchParams.get('projectId') ?? fallbackProjectId\n );\n if (!projectId) return null;\n\n return {\n projectId,\n releaseId: normalizeOptionalText(\n typeof input?.releaseId === 'string'\n ? input.releaseId\n : requestUrl.searchParams.get('releaseId')\n ),\n label: normalizeOptionalText(\n typeof input?.label === 'string'\n ? input.label\n : requestUrl.searchParams.get('label')\n ),\n targets: parseReleaseSnapshotTargets(input?.targets, requestUrl),\n };\n}\n\nfunction parseReleaseSnapshotTargets(value: unknown, requestUrl: URL) {\n const bodyTargets = Array.isArray(value)\n ? value.flatMap((target) => {\n const parsed = parseReviewFigmaImageTarget(target);\n return parsed ? [parsed] : [];\n })\n : [];\n const queryTargets = requestUrl.searchParams\n .getAll('target')\n .flatMap((target) => {\n const parsed = parseTargetParam(target);\n return parsed ? [parsed] : [];\n });\n const targetByKey = new Map(\n [...bodyTargets, ...queryTargets].map((target) => [\n getReviewFigmaImageTargetKey(target),\n target,\n ])\n );\n\n return Array.from(targetByKey.values());\n}\n\nfunction parseReviewFigmaImageTarget(\n value: unknown\n): ReviewFigmaImageTarget | null {\n if (!value || typeof value !== 'object') return null;\n const target = value as ReviewFigmaImageTarget;\n if (target.type === 'route') {\n if (\n typeof target.projectId !== 'string' ||\n typeof target.pageUrl !== 'string'\n ) {\n return null;\n }\n\n return {\n type: 'route',\n projectId: target.projectId,\n pageUrl: target.pageUrl,\n viewport:\n target.viewport && typeof target.viewport === 'object'\n ? target.viewport\n : undefined,\n slot: typeof target.slot === 'string' ? target.slot : undefined,\n };\n }\n\n if (target.type === 'figma-node') {\n if (\n typeof target.projectId !== 'string' ||\n typeof target.fileKey !== 'string' ||\n typeof target.nodeId !== 'string'\n ) {\n return null;\n }\n\n return {\n type: 'figma-node',\n projectId: target.projectId,\n fileKey: target.fileKey,\n nodeId: target.nodeId,\n };\n }\n\n return null;\n}\n\nexport function normalizeEndpoint(endpoint: string) {\n const normalized = endpoint.trim().replace(/\\/+$/, '');\n return normalized.startsWith('/') ? normalized : `/${normalized}`;\n}\n\nfunction getEndpointItemId(pathname: string, endpoint: string) {\n if (!pathname.startsWith(`${endpoint}/`)) return null;\n const value = pathname.slice(endpoint.length + 1);\n if (!value || value.includes('/')) return null;\n return decodeURIComponent(value);\n}\n\nfunction isAllowedProjectTarget(\n target: ReviewFigmaImageTarget,\n projectId: string | undefined\n) {\n return !projectId || target.projectId === projectId;\n}\n\nfunction jsonError(status: number, error: string): ReviewFigmaImageStoreResponse {\n return { status, body: { error } };\n}\n","import { mkdir, readFile, rm, writeFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport type {\n ReviewFigmaImage,\n ReviewFigmaImageAssetInput,\n ReviewFigmaImageFormat,\n ReviewFigmaImageTarget,\n AddReviewFigmaImageInput,\n UpdateReviewFigmaImageInput,\n} from '../figma/image.types';\nimport {\n getReviewFigmaImageMimeType,\n getReviewFigmaImageTargetKey,\n type ReorderReviewFigmaImagesInput,\n} from '../figma/image.store';\nimport { parseReviewFigmaNodeRef } from '../figma/parse';\nimport {\n requireReviewFigmaToken,\n DEFAULT_REVIEW_FIGMA_TOKEN_ENV_KEY,\n type ReviewFigmaTokenEnv,\n} from '../figma/token';\nimport {\n renderReviewFigmaImage,\n type ReviewFigmaRenderFormat,\n} from '../figma/render';\nimport {\n createReviewFigmaAssetStorageKey,\n createReviewFigmaAssetUrl,\n getReviewFigmaImageFormatFromMimeType,\n getStoreRenderFormat,\n isSafeReviewFigmaAssetStorageKey,\n normalizeImageMimeType,\n} from './figma-asset';\nimport type { ReviewFigmaImageStorePluginOptions, ReviewFigmaImageAssetTransformer } from './figma-image-store';\n\nexport type ReviewFigmaImageStoreFile = {\n version: 1;\n images: ReviewFigmaImage[];\n};\n\nasync function readReviewFigmaNodeName({\n apiBaseUrl,\n enabled,\n env,\n envKey,\n fetchOption,\n fileKey,\n nodeId,\n token,\n}: {\n apiBaseUrl?: string;\n enabled?: boolean;\n env: ReviewFigmaTokenEnv;\n envKey?: string;\n fetchOption?: typeof fetch;\n fileKey: string;\n nodeId: string;\n token?: string | null;\n}) {\n const explicitToken = typeof token === 'string' ? token.trim() : token;\n const figmaToken =\n explicitToken ||\n requireReviewFigmaToken({\n token: null,\n env,\n envKey: envKey ?? DEFAULT_REVIEW_FIGMA_TOKEN_ENV_KEY,\n enabled,\n });\n const fetchNode = fetchOption ?? globalThis.fetch;\n if (!fetchNode) throw new Error('Figma node name lookup requires fetch.');\n\n type FigmaNodeResponse = {\n err?: string | null;\n nodes?: Record<string, { document?: { name?: string | null } | null } | null | undefined>;\n };\n\n const response = await fetchNode(\n createReviewFigmaNodeApiUrl({ apiBaseUrl, fileKey, nodeId }),\n {\n headers: {\n 'X-Figma-Token': figmaToken,\n },\n }\n );\n const body = (await response.json().catch(() => null)) as FigmaNodeResponse | null;\n\n if (!response.ok) {\n throw new Error(body?.err || `Figma node lookup failed with ${response.status}`);\n }\n\n const nodes = body?.nodes;\n const node = nodes?.[nodeId] ?? Object.values(nodes ?? {})[0];\n return normalizeOptionalText(node?.document?.name);\n}\n\nfunction createReviewFigmaNodeApiUrl({\n apiBaseUrl = 'https://api.figma.com',\n fileKey,\n nodeId,\n}: {\n apiBaseUrl?: string;\n fileKey: string;\n nodeId: string;\n}) {\n const url = new URL(\n `/v1/files/${encodeURIComponent(fileKey)}/nodes`,\n apiBaseUrl\n );\n url.searchParams.set('ids', nodeId);\n return url.toString();\n}\n\nexport async function createReviewFigmaImage({\n assetDir,\n assetEndpoint,\n currentImages,\n env,\n input,\n options,\n}: {\n assetDir: string;\n assetEndpoint: string;\n currentImages: ReviewFigmaImage[];\n env: ReviewFigmaTokenEnv;\n input: AddReviewFigmaImageInput;\n options: ReviewFigmaImageStorePluginOptions;\n}): Promise<ReviewFigmaImage> {\n const ref = parseReviewFigmaNodeRef(input.figmaUrl);\n if (!ref) {\n throw new Error('A Figma node copy link or fileKey->nodeId value is required.');\n }\n\n const id = createReviewFigmaImageId();\n const explicitLabel = normalizeOptionalText(input.label);\n if (input.asset) {\n const cachedAsset = await cacheReviewFigmaProvidedImageAsset({\n assetDir,\n assetEndpoint,\n id,\n asset: input.asset,\n options,\n });\n const now = new Date().toISOString();\n const order =\n typeof input.order === 'number' && Number.isFinite(input.order)\n ? input.order\n : getNextImageOrder(currentImages, input.target);\n\n return {\n id,\n projectId: input.target.projectId,\n target: input.target,\n figmaUrl: input.figmaUrl,\n fileKey: ref.fileKey,\n nodeId: ref.nodeId,\n imageUrl: cachedAsset.imageUrl,\n imageFormat: cachedAsset.imageFormat,\n mimeType: cachedAsset.mimeType,\n label: explicitLabel,\n order,\n storageKey: cachedAsset.storageKey,\n width: input.asset.width,\n height: input.asset.height,\n byteSize: cachedAsset.byteSize,\n createdAt: now,\n updatedAt: now,\n };\n }\n\n const nodeLabelPromise = explicitLabel\n ? Promise.resolve(undefined)\n : readReviewFigmaNodeName({\n apiBaseUrl: options.apiBaseUrl,\n enabled: options.enabled,\n env,\n envKey: options.envKey,\n fetchOption: options.fetch,\n fileKey: ref.fileKey,\n nodeId: ref.nodeId,\n token: options.token,\n }).catch(() => undefined);\n const targetImageFormat = input.imageFormat ?? options.imageFormat ?? 'webp';\n const renderFormat = getStoreRenderFormat(options.renderFormat, targetImageFormat);\n const explicitToken = typeof options.token === 'string' ? options.token.trim() : options.token;\n const rendered = await renderReviewFigmaImage({\n figmaUrl: input.figmaUrl,\n format: renderFormat,\n scale: options.renderScale,\n useAbsoluteBounds: options.useAbsoluteBounds,\n apiBaseUrl: options.apiBaseUrl,\n fetch: options.fetch,\n token:\n explicitToken ||\n requireReviewFigmaToken({\n token: null,\n env,\n envKey: options.envKey ?? DEFAULT_REVIEW_FIGMA_TOKEN_ENV_KEY,\n enabled: options.enabled,\n }),\n });\n const cachedAsset = await cacheReviewFigmaImageAsset({\n assetDir,\n assetEndpoint,\n id,\n imageUrl: rendered.imageUrl,\n options,\n renderFormat,\n targetImageFormat,\n });\n const imageFormat =\n cachedAsset?.imageFormat ?? (renderFormat === 'jpg' ? 'jpg' : 'png');\n const now = new Date().toISOString();\n const order =\n typeof input.order === 'number' && Number.isFinite(input.order)\n ? input.order\n : getNextImageOrder(currentImages, input.target);\n const nodeLabel = await nodeLabelPromise;\n\n return {\n id,\n projectId: input.target.projectId,\n target: input.target,\n figmaUrl: input.figmaUrl,\n fileKey: rendered.fileKey,\n nodeId: rendered.nodeId,\n imageUrl: cachedAsset?.imageUrl ?? rendered.imageUrl,\n imageFormat,\n mimeType: cachedAsset?.mimeType ?? getReviewFigmaImageMimeType(imageFormat),\n label: explicitLabel ?? nodeLabel,\n order,\n storageKey: cachedAsset?.storageKey,\n byteSize: cachedAsset?.byteSize,\n createdAt: now,\n updatedAt: now,\n };\n}\n\ntype CachedReviewFigmaImageAsset = {\n imageUrl: string;\n imageFormat: ReviewFigmaImageFormat;\n mimeType: string;\n storageKey: string;\n byteSize: number;\n};\n\nasync function cacheReviewFigmaProvidedImageAsset({\n assetDir,\n assetEndpoint,\n id,\n asset,\n options,\n}: {\n assetDir: string;\n assetEndpoint: string;\n id: string;\n asset: ReviewFigmaImageAssetInput;\n options: ReviewFigmaImageStorePluginOptions;\n}): Promise<CachedReviewFigmaImageAsset> {\n const decodedAsset = decodeReviewFigmaImageAsset(asset);\n const storageKey = createReviewFigmaAssetStorageKey(\n id,\n decodedAsset.imageFormat\n );\n\n await mkdir(assetDir, { recursive: true });\n await writeFile(path.join(assetDir, storageKey), decodedAsset.data);\n\n return {\n imageUrl: createReviewFigmaAssetUrl(assetEndpoint, storageKey),\n imageFormat: decodedAsset.imageFormat,\n mimeType: decodedAsset.mimeType,\n storageKey,\n byteSize: decodedAsset.data.byteLength,\n };\n}\n\nfunction decodeReviewFigmaImageAsset(asset: ReviewFigmaImageAssetInput) {\n const mimeType = normalizeImageMimeType(asset.mimeType);\n if (!mimeType) throw new Error('Unsupported Figma image asset MIME type.');\n\n const imageFormat =\n getReviewFigmaImageFormatFromMimeType(mimeType) ?? asset.imageFormat;\n const match = /^data:([^;,]+);base64,([a-zA-Z0-9+/=\\s]+)$/.exec(\n asset.dataUrl\n );\n if (!match) throw new Error('Valid Figma image asset data URL is required.');\n\n const dataUrlMimeType = normalizeImageMimeType(match[1]);\n if (dataUrlMimeType && dataUrlMimeType !== mimeType) {\n throw new Error('Figma image asset MIME type mismatch.');\n }\n\n return {\n data: Buffer.from(match[2].replace(/\\s/g, ''), 'base64'),\n imageFormat,\n mimeType,\n };\n}\n\nasync function cacheReviewFigmaImageAsset({\n assetDir,\n assetEndpoint,\n id,\n imageUrl,\n options,\n renderFormat,\n targetImageFormat,\n}: {\n assetDir: string;\n assetEndpoint: string;\n id: string;\n imageUrl: string;\n options: ReviewFigmaImageStorePluginOptions;\n renderFormat: Extract<ReviewFigmaRenderFormat, 'png' | 'jpg'>;\n targetImageFormat: ReviewFigmaImageFormat;\n}): Promise<CachedReviewFigmaImageAsset | null> {\n if (options.cacheAssets === false) return null;\n\n const asset = await downloadReviewFigmaImageAsset({\n fetchOption: options.fetch,\n imageUrl,\n renderFormat,\n targetImageFormat,\n transformAsset: options.transformAsset,\n });\n const storageKey = createReviewFigmaAssetStorageKey(id, asset.imageFormat);\n\n await mkdir(assetDir, { recursive: true });\n await writeFile(path.join(assetDir, storageKey), asset.data);\n\n return {\n imageUrl: createReviewFigmaAssetUrl(assetEndpoint, storageKey),\n imageFormat: asset.imageFormat,\n mimeType: asset.mimeType,\n storageKey,\n byteSize: asset.data.byteLength,\n };\n}\n\nasync function downloadReviewFigmaImageAsset({\n fetchOption,\n imageUrl,\n renderFormat,\n targetImageFormat,\n transformAsset,\n}: {\n fetchOption: typeof fetch | undefined;\n imageUrl: string;\n renderFormat: Extract<ReviewFigmaRenderFormat, 'png' | 'jpg'>;\n targetImageFormat: ReviewFigmaImageFormat;\n transformAsset: ReviewFigmaImageAssetTransformer | undefined;\n}) {\n const fetchImage = fetchOption ?? globalThis.fetch;\n if (!fetchImage) throw new Error('Figma image caching requires fetch.');\n\n const response = await fetchImage(imageUrl);\n if (!response.ok) {\n throw new Error(`Figma image download failed with ${response.status}`);\n }\n\n const sourceMimeType =\n normalizeImageMimeType(response.headers.get('content-type')) ??\n getReviewFigmaImageMimeType(renderFormat === 'jpg' ? 'jpg' : 'png');\n const sourceImageFormat =\n getReviewFigmaImageFormatFromMimeType(sourceMimeType) ??\n (renderFormat === 'jpg' ? 'jpg' : 'png');\n const sourceData = new Uint8Array(await response.arrayBuffer());\n const transformed = transformAsset\n ? await transformAsset({\n data: sourceData,\n imageFormat: sourceImageFormat,\n mimeType: sourceMimeType,\n targetFormat: targetImageFormat,\n })\n : null;\n const imageFormat = transformed?.imageFormat ?? sourceImageFormat;\n const mimeType =\n normalizeImageMimeType(transformed?.mimeType) ??\n getReviewFigmaImageMimeType(imageFormat);\n const data = createBufferFromImageData(transformed?.data ?? sourceData);\n\n return { data, imageFormat, mimeType };\n}\n\nexport async function deleteReviewFigmaImageAsset(\n assetDir: string,\n storageKey: string | undefined\n) {\n if (!storageKey) return;\n if (!isSafeReviewFigmaAssetStorageKey(storageKey)) return;\n await rm(path.join(assetDir, storageKey), { force: true }).catch(() => null);\n}\n\nfunction createBufferFromImageData(data: Uint8Array | ArrayBuffer) {\n return data instanceof ArrayBuffer\n ? Buffer.from(new Uint8Array(data))\n : Buffer.from(data);\n}\n\nexport function listImagesForTarget(\n images: ReviewFigmaImage[],\n target: ReviewFigmaImageTarget\n) {\n const targetKey = getReviewFigmaImageTargetKey(target);\n return images\n .filter((image) => getReviewFigmaImageTargetKey(image.target) === targetKey)\n .sort(compareReviewFigmaImages);\n}\n\nexport function reorderReviewFigmaImages(\n images: ReviewFigmaImage[],\n input: ReorderReviewFigmaImagesInput\n) {\n const targetKey = getReviewFigmaImageTargetKey(input.target);\n const orderById = new Map(input.imageIds.map((id, index) => [id, index]));\n const targetImages = listImagesForTarget(images, input.target);\n const nextTargetImages = targetImages\n .map((image) => ({\n ...image,\n order: orderById.get(image.id) ?? input.imageIds.length + image.order,\n updatedAt: orderById.has(image.id) ? new Date().toISOString() : image.updatedAt,\n }))\n .sort(compareReviewFigmaImages);\n const nextTargetImageById = new Map(\n nextTargetImages.map((image, index) => [image.id, { ...image, order: index }])\n );\n const allImages = images.map((image) =>\n getReviewFigmaImageTargetKey(image.target) === targetKey\n ? nextTargetImageById.get(image.id) ?? image\n : image\n );\n\n return {\n allImages,\n targetImages: listImagesForTarget(allImages, input.target),\n };\n}\n\nexport function updateReviewFigmaImage(\n images: ReviewFigmaImage[],\n id: string,\n patch: UpdateReviewFigmaImageInput\n) {\n const index = images.findIndex((image) => image.id === id);\n if (index < 0) return null;\n\n const nextImage: ReviewFigmaImage = {\n ...images[index],\n label:\n patch.label === undefined\n ? images[index].label\n : normalizeOptionalText(patch.label),\n order:\n typeof patch.order === 'number' && Number.isFinite(patch.order)\n ? patch.order\n : images[index].order,\n updatedAt: new Date().toISOString(),\n };\n const nextImages = [...images];\n nextImages[index] = nextImage;\n\n return { image: nextImage, images: nextImages };\n}\n\nexport async function readReviewFigmaImageStoreFile(\n dataFile: string\n): Promise<ReviewFigmaImageStoreFile> {\n try {\n const raw = await readFile(dataFile, 'utf8');\n const parsed = JSON.parse(raw) as Partial<ReviewFigmaImageStoreFile>;\n\n return {\n version: 1,\n images: Array.isArray(parsed.images)\n ? parsed.images.flatMap((image) => (isReviewFigmaImage(image) ? [image] : []))\n : [],\n };\n } catch (error) {\n if (isNodeError(error) && error.code === 'ENOENT') {\n return { version: 1, images: [] };\n }\n throw error;\n }\n}\n\nexport async function writeReviewFigmaImageStoreFile(\n dataFile: string,\n data: ReviewFigmaImageStoreFile\n) {\n await mkdir(path.dirname(dataFile), { recursive: true });\n await writeFile(\n dataFile,\n `${JSON.stringify({ version: 1, images: data.images }, null, 2)}\\n`,\n 'utf8'\n );\n}\n\nfunction getNextImageOrder(\n images: ReviewFigmaImage[],\n target: ReviewFigmaImageTarget\n) {\n const targetImages = listImagesForTarget(images, target);\n return targetImages.length\n ? Math.max(...targetImages.map((image) => image.order)) + 1\n : 0;\n}\n\nfunction compareReviewFigmaImages(a: ReviewFigmaImage, b: ReviewFigmaImage) {\n return a.order - b.order || a.createdAt.localeCompare(b.createdAt);\n}\n\nfunction createReviewFigmaImageId() {\n return `figma_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;\n}\n\nfunction isReviewFigmaImage(value: unknown): value is ReviewFigmaImage {\n if (!value || typeof value !== 'object') return false;\n const image = value as ReviewFigmaImage;\n\n return (\n typeof image.id === 'string' &&\n typeof image.projectId === 'string' &&\n typeof image.figmaUrl === 'string' &&\n typeof image.fileKey === 'string' &&\n typeof image.nodeId === 'string' &&\n typeof image.imageUrl === 'string' &&\n typeof image.order === 'number' &&\n typeof image.createdAt === 'string' &&\n typeof image.updatedAt === 'string'\n );\n}\n\nexport function isNodeError(error: unknown): error is { code?: string } {\n if (!error || typeof error !== 'object') return false;\n return 'code' in error;\n}\n\nexport function normalizeOptionalText(value: string | null | undefined) {\n if (typeof value !== 'string') return undefined;\n return value.trim() || undefined;\n}\n","import { type Plugin, type ResolvedConfig } from 'vite';\n\nexport * from './vite/figma-image-store';\n\ntype SourceLocatorPattern = string | RegExp;\n\nexport interface ReviewSourceLocatorOptions {\n enabled?: boolean;\n root?: string;\n include?: readonly SourceLocatorPattern[];\n exclude?: readonly SourceLocatorPattern[];\n filePath?: 'relative' | 'absolute';\n line?: boolean;\n column?: boolean;\n attributePrefix?: string;\n}\n\ntype RuntimeMatcher =\n | { type: 'path'; value: string }\n | { type: 'regex'; value: string; flags: string };\n\ntype RuntimeOptions = {\n enabled: boolean;\n root: string;\n include: RuntimeMatcher[];\n exclude: RuntimeMatcher[];\n filePath: 'relative' | 'absolute';\n line: boolean;\n column: boolean;\n fileAttribute: string;\n lineAttribute: string;\n columnAttribute: string;\n};\n\nconst VIRTUAL_JSX_DEV_RUNTIME_ID =\n '\\0@designfever/web-review-kit/source-locator/jsx-dev-runtime';\n\nconst REVIEW_SOURCE_ENV_DEFINE_KEYS = [\n ['__DF_WRK_REVIEW_SOURCE_ROOT__', 'VITE_REVIEW_SOURCE_ROOT'],\n ['__DF_WRK_REVIEW_SOURCE_EDITOR__', 'VITE_REVIEW_SOURCE_EDITOR'],\n [\n '__DF_WRK_REVIEW_SOURCE_URL_TEMPLATE__',\n 'VITE_REVIEW_SOURCE_URL_TEMPLATE',\n ],\n] as const;\n\ntype ReviewSourceEnvReplacements = Record<string, string>;\n\nconst createReviewSourceEnvReplacements = (\n env: ResolvedConfig['env'] = {}\n): ReviewSourceEnvReplacements => {\n return Object.fromEntries(\n REVIEW_SOURCE_ENV_DEFINE_KEYS.map(([defineKey, envKey]) => [\n defineKey,\n JSON.stringify(env[envKey] ?? ''),\n ])\n );\n};\n\nconst injectReviewSourceEnv = (\n code: string,\n replacements: ReviewSourceEnvReplacements\n) => {\n let nextCode = code;\n for (const [defineKey, value] of Object.entries(replacements)) {\n nextCode = nextCode\n .split(`typeof ${defineKey}`)\n .join(`typeof ${value}`)\n .split(`: ${defineKey}`)\n .join(`: ${value}`);\n }\n\n return nextCode === code ? null : nextCode;\n};\n\nexport const reviewSourceLocator = (\n options: ReviewSourceLocatorOptions = {}\n): Plugin => {\n let runtimeOptions = createRuntimeOptions(options);\n let sourceEnvReplacements = createReviewSourceEnvReplacements();\n\n return {\n name: 'df-web-review-kit-source-locator',\n enforce: 'pre',\n configResolved(config) {\n runtimeOptions = createRuntimeOptions(options, config);\n sourceEnvReplacements = createReviewSourceEnvReplacements(config.env);\n },\n resolveId(id, importer) {\n if (!runtimeOptions.enabled) return null;\n if (id !== 'react/jsx-dev-runtime') return null;\n if (importer === VIRTUAL_JSX_DEV_RUNTIME_ID) return null;\n\n return VIRTUAL_JSX_DEV_RUNTIME_ID;\n },\n load(id) {\n if (id !== VIRTUAL_JSX_DEV_RUNTIME_ID) return null;\n return createJsxDevRuntime(runtimeOptions);\n },\n transform(code) {\n const injectedCode = injectReviewSourceEnv(code, sourceEnvReplacements);\n return injectedCode ? { code: injectedCode, map: null } : null;\n },\n };\n};\n\nexport interface ReviewDataLocatorOptions {\n enabled?: boolean;\n root?: string;\n include?: readonly SourceLocatorPattern[];\n exclude?: readonly SourceLocatorPattern[];\n filePath?: 'relative' | 'absolute';\n /** 매칭할 component 이름 패턴. 기본은 `Section`으로 시작하는 이름. */\n componentPattern?: RegExp;\n fileAttribute?: string;\n lineAttribute?: string;\n}\n\n/**\n * page data 파일의 section 객체(`component: 'SectionXxx'`)에 출처 파일/라인을\n * `__wrkDataFile`/`__wrkDataLine` prop 으로 주입한다. 라인 보존을 위해 같은 줄에만 삽입한다.\n */\nexport const reviewDataLocator = (\n options: ReviewDataLocatorOptions = {}\n): Plugin => {\n let root = normalizePath(options.root ?? '');\n let enabled = options.enabled ?? false;\n let sourceEnvReplacements = createReviewSourceEnvReplacements();\n const include = (options.include ?? []).map(createRuntimeMatcher);\n const exclude = (options.exclude ?? ['node_modules', 'dist']).map(\n createRuntimeMatcher\n );\n const componentPattern = options.componentPattern ?? /Section[A-Za-z0-9_]*/;\n const fileKey = options.fileAttribute ?? '__wrkDataFile';\n const lineKey = options.lineAttribute ?? '__wrkDataLine';\n\n const componentSource = `(^|[\\\\n,{(\\\\[]\\\\s*)(component:\\\\s*)(['\"\\`])(${componentPattern.source})\\\\3`;\n\n return {\n name: 'df-web-review-kit-data-locator',\n enforce: 'pre',\n configResolved(config) {\n root = normalizePath(options.root ?? config.root ?? '');\n enabled = options.enabled ?? config.command === 'serve';\n sourceEnvReplacements = createReviewSourceEnvReplacements(config.env);\n },\n transform(code, id) {\n const envInjectedCode = injectReviewSourceEnv(\n code,\n sourceEnvReplacements\n );\n const inputCode = envInjectedCode ?? code;\n if (!enabled) return null;\n const file = normalizePath(id.split('?')[0]);\n const relativeFile =\n root && file.startsWith(root + '/') ? file.slice(root.length + 1) : file;\n if (\n include.length > 0 &&\n !include.some((m) => matchesPath(m, file, relativeFile))\n )\n return envInjectedCode ? { code: envInjectedCode, map: null } : null;\n if (exclude.some((m) => matchesPath(m, file, relativeFile))) {\n return envInjectedCode ? { code: envInjectedCode, map: null } : null;\n }\n\n const sourceFile =\n (options.filePath ?? 'relative') === 'absolute' ? file : relativeFile;\n const regex = new RegExp(componentSource, 'g');\n let changed = false;\n const out = inputCode.replace(\n regex,\n (\n _match,\n pre: string,\n comp: string,\n quote: string,\n name: string,\n offset: number\n ) => {\n const line = inputCode\n .slice(0, offset + pre.length)\n .split('\\n').length;\n changed = true;\n return `${pre}${JSON.stringify(fileKey)}: ${JSON.stringify(sourceFile)}, ${JSON.stringify(lineKey)}: ${line}, ${comp}${quote}${name}${quote}`;\n }\n );\n\n return changed || envInjectedCode ? { code: out, map: null } : null;\n },\n };\n};\n\nfunction matchesPath(\n matcher: RuntimeMatcher,\n absoluteFile: string,\n relativeFile: string\n) {\n if (matcher.type === 'regex') {\n const regex = new RegExp(matcher.value, matcher.flags);\n return regex.test(absoluteFile) || regex.test(relativeFile);\n }\n const target = matcher.value.startsWith('/') ? absoluteFile : relativeFile;\n return target === matcher.value || target.startsWith(matcher.value + '/') || target.includes('/' + matcher.value);\n}\n\nfunction createRuntimeOptions(\n options: ReviewSourceLocatorOptions,\n config?: ResolvedConfig\n): RuntimeOptions {\n const attributePrefix = (options.attributePrefix ?? 'data-wrk-source').replace(\n /-+$/,\n ''\n );\n const root = normalizePath(options.root ?? config?.root ?? '');\n const enabled = options.enabled ?? (config?.command === 'serve');\n\n return {\n enabled,\n root,\n include: (options.include ?? []).map(createRuntimeMatcher),\n exclude: (options.exclude ?? ['node_modules', 'dist']).map(\n createRuntimeMatcher\n ),\n filePath: options.filePath ?? 'relative',\n line: options.line ?? true,\n column: options.column ?? true,\n fileAttribute: `${attributePrefix}-file`,\n lineAttribute: `${attributePrefix}-line`,\n columnAttribute: `${attributePrefix}-column`,\n };\n}\n\nfunction createRuntimeMatcher(pattern: SourceLocatorPattern): RuntimeMatcher {\n if (pattern instanceof RegExp) {\n return { type: 'regex', value: pattern.source, flags: pattern.flags };\n }\n\n return { type: 'path', value: normalizePath(pattern).replace(/^\\.\\//, '') };\n}\n\nfunction normalizePath(value: string) {\n return value.replace(/\\\\/g, '/').replace(/\\/+$/, '');\n}\n\nfunction createJsxDevRuntime(options: RuntimeOptions) {\n return `\nimport { Fragment, jsxDEV as baseJsxDEV } from 'react/jsx-dev-runtime';\n\nconst OPTIONS = ${JSON.stringify(options)};\n\nexport { Fragment };\n\nexport function jsxDEV(type, props, key, isStaticChildren, source, self) {\n return baseJsxDEV(\n type,\n injectSourceProps(type, props, source),\n key,\n isStaticChildren,\n source,\n self\n );\n}\n\nfunction injectSourceProps(type, props, source) {\n if (typeof type !== 'string') return props;\n if (!source || typeof source.fileName !== 'string') return props;\n\n const sourceFile = getSourceFile(source.fileName);\n if (!sourceFile) return props;\n\n const nextProps = props ? { ...props } : {};\n if (nextProps[OPTIONS.fileAttribute] == null) {\n nextProps[OPTIONS.fileAttribute] = sourceFile;\n }\n if (OPTIONS.line && source.lineNumber != null && nextProps[OPTIONS.lineAttribute] == null) {\n nextProps[OPTIONS.lineAttribute] = String(source.lineNumber);\n }\n if (OPTIONS.column && source.columnNumber != null && nextProps[OPTIONS.columnAttribute] == null) {\n nextProps[OPTIONS.columnAttribute] = String(source.columnNumber);\n }\n\n return nextProps;\n}\n\nfunction getSourceFile(fileName) {\n const absoluteFile = normalizePath(fileName);\n const relativeFile = getRelativeFile(absoluteFile);\n\n if (OPTIONS.include.length > 0 && !matchesAny(OPTIONS.include, absoluteFile, relativeFile)) {\n return null;\n }\n if (matchesAny(OPTIONS.exclude, absoluteFile, relativeFile)) return null;\n\n return OPTIONS.filePath === 'absolute' ? absoluteFile : relativeFile;\n}\n\nfunction getRelativeFile(absoluteFile) {\n if (!OPTIONS.root) return absoluteFile;\n if (absoluteFile === OPTIONS.root) return '';\n if (absoluteFile.startsWith(OPTIONS.root + '/')) {\n return absoluteFile.slice(OPTIONS.root.length + 1);\n }\n\n return absoluteFile;\n}\n\nfunction matchesAny(patterns, absoluteFile, relativeFile) {\n return patterns.some((pattern) =>\n matchesPattern(pattern, absoluteFile, relativeFile)\n );\n}\n\nfunction matchesPattern(pattern, absoluteFile, relativeFile) {\n if (pattern.type === 'regex') {\n const regex = new RegExp(pattern.value, pattern.flags);\n return regex.test(absoluteFile) || regex.test(relativeFile);\n }\n\n const value = pattern.value;\n const target = isAbsolutePattern(value) ? absoluteFile : relativeFile;\n if (!value.includes('*')) {\n return target === value || target.startsWith(value + '/');\n }\n\n return globToRegExp(value).test(target);\n}\n\nfunction isAbsolutePattern(value) {\n return value.startsWith('/') || /^[a-zA-Z]:\\\\//.test(value);\n}\n\nfunction globToRegExp(value) {\n const source = escapeRegExp(value)\n .replace(/\\\\\\\\\\\\*\\\\\\\\\\\\*/g, '.*')\n .replace(/\\\\\\\\\\\\*/g, '[^/]*');\n\n return new RegExp('^' + source + '$');\n}\n\nfunction escapeRegExp(value) {\n return value.replace(/[|\\\\\\\\{}()[\\\\]^$+*?.]/g, '\\\\\\\\$&');\n}\n\nfunction normalizePath(value) {\n return value.replace(/\\\\\\\\/g, '/').replace(/\\\\/+$/, '');\n}\n`;\n}\n"],"mappings":"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","names":["path","readFile","path","cachedAsset","now","order","readFile","path","path"]}
1
+ {"version":3,"sources":["../src/vite/figma-image-store.ts","../src/vite/figma-image-store.server.ts","../src/vite/figma-image-store.image.ts","../src/vite.ts"],"sourcesContent":["import path from 'node:path';\nimport { loadEnv, type Plugin } from 'vite';\nimport type { ReviewFigmaImageFormat } from '../figma/image.types';\nimport {\n DEFAULT_REVIEW_FIGMA_IMAGE_STORE_ENDPOINT,\n} from '../figma/image.store';\nimport {\n DEFAULT_REVIEW_FIGMA_TOKEN_ENV_KEY,\n readReviewFigmaToken,\n requireReviewFigmaToken,\n type ReviewFigmaTokenEnv,\n} from '../figma/token';\nimport {\n renderReviewFigmaImage,\n type ReviewFigmaImageRenderOptions,\n type ReviewFigmaRenderFormat,\n} from '../figma/render';\nimport {\n handleReviewFigmaImageStoreRequest,\n normalizeEndpoint,\n readJsonRequestBody,\n sendJson,\n sendReviewFigmaAsset,\n} from './figma-image-store.server';\n\nexport interface ReviewFigmaImageStorePluginOptions\n extends ReviewFigmaServerTokenOptions {\n enabled?: boolean;\n projectId?: string;\n endpoint?: string;\n dataFile?: string;\n assetDir?: string;\n assetEndpoint?: string;\n cacheAssets?: boolean;\n imageFormat?: ReviewFigmaImageFormat;\n renderFormat?: ReviewFigmaRenderFormat;\n renderScale?: number;\n useAbsoluteBounds?: boolean;\n apiBaseUrl?: string;\n fetch?: typeof fetch;\n transformAsset?: ReviewFigmaImageAssetTransformer;\n}\n\nexport interface ReviewFigmaServerTokenOptions {\n token?: string | null;\n env?: ReviewFigmaTokenEnv;\n envKey?: string;\n enabled?: boolean;\n}\n\nexport type ReviewFigmaServerImageRenderOptions =\n Omit<ReviewFigmaImageRenderOptions, 'token'> &\n ReviewFigmaServerTokenOptions;\n\nexport type ReviewFigmaImageAssetTransformInput = {\n data: Uint8Array;\n imageFormat: ReviewFigmaImageFormat;\n mimeType: string;\n targetFormat: ReviewFigmaImageFormat;\n};\n\nexport type ReviewFigmaImageAssetTransformResult = {\n data: Uint8Array | ArrayBuffer;\n imageFormat: ReviewFigmaImageFormat;\n mimeType?: string;\n};\n\nexport type ReviewFigmaImageAssetTransformer = (\n input: ReviewFigmaImageAssetTransformInput\n) =>\n | ReviewFigmaImageAssetTransformResult\n | null\n | undefined\n | Promise<ReviewFigmaImageAssetTransformResult | null | undefined>;\n\nexport {\n createReviewFigmaImageApiUrl,\n renderReviewFigmaImage,\n} from '../figma/render';\nexport {\n collectReviewFigmaReleaseSnapshot,\n createReviewFigmaImagesSnapshot,\n createReviewFigmaReleaseSnapshot,\n} from '../figma/image.snapshot';\nexport type {\n ReviewFigmaImageRenderOptions,\n ReviewFigmaRenderFormat,\n ReviewFigmaRenderedImage,\n} from '../figma/render';\nexport type {\n CollectReviewFigmaReleaseSnapshotOptions,\n CreateReviewFigmaImagesSnapshotOptions,\n CreateReviewFigmaReleaseSnapshotOptions,\n ReviewFigmaImagesSnapshot,\n ReviewFigmaReleaseSnapshot,\n} from '../figma/image.snapshot';\n\nexport const readReviewFigmaServerToken = (\n options: ReviewFigmaServerTokenOptions = {}\n) =>\n readReviewFigmaToken({\n token: options.token,\n env: options.env ?? getServerEnv(),\n envKey: options.envKey ?? DEFAULT_REVIEW_FIGMA_TOKEN_ENV_KEY,\n enabled: options.enabled,\n });\n\nexport const requireReviewFigmaServerToken = (\n options: ReviewFigmaServerTokenOptions = {}\n) =>\n requireReviewFigmaToken({\n token: options.token,\n env: options.env ?? getServerEnv(),\n envKey: options.envKey ?? DEFAULT_REVIEW_FIGMA_TOKEN_ENV_KEY,\n enabled: options.enabled,\n });\n\nexport const renderReviewFigmaServerImage = (\n options: ReviewFigmaServerImageRenderOptions\n) => {\n const { token, env, envKey, enabled, ...renderOptions } = options;\n const explicitToken = typeof token === 'string' ? token.trim() : token;\n\n return renderReviewFigmaImage({\n ...renderOptions,\n token:\n explicitToken ||\n requireReviewFigmaServerToken({ env, envKey, enabled }),\n });\n};\n\nexport const reviewFigmaImageStore = (\n options: ReviewFigmaImageStorePluginOptions = {}\n): Plugin => {\n let root = '';\n let dataFile = '';\n let assetDir = '';\n let env: ReviewFigmaTokenEnv = {};\n const enabled = options.enabled ?? true;\n const endpoint = normalizeEndpoint(\n options.endpoint ?? DEFAULT_REVIEW_FIGMA_IMAGE_STORE_ENDPOINT\n );\n const assetEndpoint = normalizeEndpoint(\n options.assetEndpoint ?? `${endpoint}/assets`\n );\n\n return {\n name: 'df-web-review-kit-figma-image-store',\n apply: 'serve',\n configResolved(config) {\n root = config.root;\n dataFile = path.resolve(\n root,\n options.dataFile ?? '.df-review/figma-images.json'\n );\n assetDir = options.assetDir\n ? path.resolve(root, options.assetDir)\n : path.join(path.dirname(dataFile), 'figma-assets');\n env = {\n ...loadEnv(config.mode, config.envDir, ''),\n ...getServerEnv(),\n ...(options.env ?? {}),\n };\n },\n configureServer(server) {\n if (!enabled) return;\n\n server.middlewares.use(async (req, res, next) => {\n const requestUrl = new URL(req.url ?? '/', 'http://localhost');\n const pathname = requestUrl.pathname;\n if (pathname.startsWith(`${assetEndpoint}/`)) {\n await sendReviewFigmaAsset(res, assetDir, assetEndpoint, pathname);\n return;\n }\n if (pathname !== endpoint && !pathname.startsWith(`${endpoint}/`)) {\n next();\n return;\n }\n\n try {\n const response = await handleReviewFigmaImageStoreRequest({\n dataFile,\n assetDir,\n assetEndpoint,\n endpoint,\n options,\n env,\n pathname,\n requestUrl,\n method: req.method ?? 'GET',\n body: await readJsonRequestBody(req),\n requestToken: readRequestFigmaToken(req),\n });\n\n sendJson(res, response.status, response.body);\n } catch (error) {\n sendJson(res, 500, {\n error:\n error instanceof Error\n ? error.message\n : 'Figma image store request failed.',\n });\n }\n });\n },\n };\n};\n\nfunction readRequestFigmaToken(req: { headers?: Record<string, unknown> }) {\n const value = req.headers?.['x-figma-token'];\n const token = Array.isArray(value) ? value[0] : value;\n\n return typeof token === 'string' ? token.trim() || null : null;\n}\n\nfunction getServerEnv(): ReviewFigmaTokenEnv {\n const runtime = globalThis as typeof globalThis & {\n process?: {\n env?: ReviewFigmaTokenEnv;\n };\n };\n\n return runtime.process?.env ?? {};\n}\n","import { readFile } from 'node:fs/promises';\nimport type { IncomingMessage, ServerResponse } from 'node:http';\nimport path from 'node:path';\nimport type {\n ReviewFigmaImage,\n ReviewFigmaImageTarget,\n AddReviewFigmaImageInput,\n} from '../figma/image.types';\nimport {\n getReviewFigmaImageTargetKey,\n type ReorderReviewFigmaImagesInput,\n} from '../figma/image.store';\nimport type { ReviewFigmaTokenEnv } from '../figma/token';\nimport { createReviewFigmaReleaseSnapshot } from '../figma/image.snapshot';\nimport {\n getReviewFigmaAssetMimeType,\n getReviewFigmaAssetStorageKeyFromPathname,\n parseReviewFigmaImageFormat,\n} from './figma-asset';\nimport type { ReviewFigmaImageStorePluginOptions } from './figma-image-store';\nimport {\n createReviewFigmaImage,\n deleteReviewFigmaImageAsset,\n isNodeError,\n listImagesForTarget,\n normalizeOptionalText,\n readReviewFigmaImageStoreFile,\n reorderReviewFigmaImages,\n updateReviewFigmaImage,\n writeReviewFigmaImageStoreFile,\n} from './figma-image-store.image';\n\ntype ReviewFigmaImageStoreResponse = {\n status: number;\n body: unknown;\n};\n\nexport async function handleReviewFigmaImageStoreRequest({\n dataFile,\n assetDir,\n assetEndpoint,\n endpoint,\n method,\n options,\n pathname,\n requestUrl,\n body,\n env,\n requestToken,\n}: {\n dataFile: string;\n assetDir: string;\n assetEndpoint: string;\n endpoint: string;\n method: string;\n options: ReviewFigmaImageStorePluginOptions;\n env: ReviewFigmaTokenEnv;\n pathname: string;\n requestUrl: URL;\n body: unknown;\n requestToken?: string | null;\n}): Promise<ReviewFigmaImageStoreResponse> {\n if (method === 'OPTIONS') return { status: 204, body: null };\n\n if (\n (method === 'GET' || method === 'POST') &&\n pathname === `${endpoint}/snapshot`\n ) {\n const input = parseReleaseSnapshotInput(body, requestUrl, options.projectId);\n if (!input) return jsonError(400, 'valid snapshot input is required.');\n if (options.projectId && input.projectId !== options.projectId) {\n return jsonError(403, 'snapshot project is not allowed.');\n }\n if (input.targets.some((target) => !isAllowedProjectTarget(target, options.projectId))) {\n return jsonError(403, 'snapshot target project is not allowed.');\n }\n\n const data = await readReviewFigmaImageStoreFile(dataFile);\n return {\n status: 200,\n body: createReviewFigmaReleaseSnapshot({\n images: data.images,\n projectId: input.projectId,\n releaseId: input.releaseId,\n label: input.label,\n targets: input.targets.length > 0 ? input.targets : undefined,\n }),\n };\n }\n\n if (method === 'GET' && pathname === endpoint) {\n const target = parseTargetParam(requestUrl.searchParams.get('target'));\n if (!target) return jsonError(400, 'target query is required.');\n if (!isAllowedProjectTarget(target, options.projectId)) {\n return { status: 200, body: [] };\n }\n\n const data = await readReviewFigmaImageStoreFile(dataFile);\n return { status: 200, body: listImagesForTarget(data.images, target) };\n }\n\n if (method === 'POST' && pathname === endpoint) {\n const input = parseAddImageInput(body);\n if (!input) return jsonError(400, 'valid add image input is required.');\n if (!isAllowedProjectTarget(input.target, options.projectId)) {\n return jsonError(403, 'target project is not allowed.');\n }\n\n const data = await readReviewFigmaImageStoreFile(dataFile);\n const image = await createReviewFigmaImage({\n assetDir,\n assetEndpoint,\n currentImages: data.images,\n env,\n input,\n options,\n requestToken,\n });\n data.images = [image, ...data.images];\n await writeReviewFigmaImageStoreFile(dataFile, data);\n\n return { status: 201, body: image };\n }\n\n if (method === 'PATCH' && pathname === `${endpoint}/reorder`) {\n const input = parseReorderImagesInput(body);\n if (!input) return jsonError(400, 'valid reorder input is required.');\n if (!isAllowedProjectTarget(input.target, options.projectId)) {\n return jsonError(403, 'target project is not allowed.');\n }\n\n const data = await readReviewFigmaImageStoreFile(dataFile);\n const images = reorderReviewFigmaImages(data.images, input);\n data.images = images.allImages;\n await writeReviewFigmaImageStoreFile(dataFile, data);\n\n return { status: 200, body: images.targetImages };\n }\n\n const id = getEndpointItemId(pathname, endpoint);\n if (id && method === 'PATCH') {\n const patch = parseUpdateImageInput(body);\n if (!patch) return jsonError(400, 'valid update patch is required.');\n\n const data = await readReviewFigmaImageStoreFile(dataFile);\n const result = updateReviewFigmaImage(data.images, id, patch);\n if (!result) return jsonError(404, `Figma image not found: ${id}`);\n if (!isAllowedProjectTarget(result.image.target, options.projectId)) {\n return jsonError(403, 'target project is not allowed.');\n }\n\n data.images = result.images;\n await writeReviewFigmaImageStoreFile(dataFile, data);\n\n return { status: 200, body: result.image };\n }\n\n if (id && method === 'DELETE') {\n const data = await readReviewFigmaImageStoreFile(dataFile);\n const image = data.images.find((item) => item.id === id);\n if (!image) return jsonError(404, `Figma image not found: ${id}`);\n if (!isAllowedProjectTarget(image.target, options.projectId)) {\n return jsonError(403, 'target project is not allowed.');\n }\n\n data.images = data.images.filter((item) => item.id !== id);\n await writeReviewFigmaImageStoreFile(dataFile, data);\n await deleteReviewFigmaImageAsset(assetDir, image.storageKey);\n\n return { status: 200, body: { ok: true } };\n }\n\n return jsonError(405, 'method not allowed.');\n}\n\nexport async function readJsonRequestBody(req: IncomingMessage) {\n if (req.method === 'GET' || req.method === 'DELETE') return null;\n\n const chunks: Buffer[] = [];\n for await (const chunk of req) {\n chunks.push(typeof chunk === 'string' ? Buffer.from(chunk) : chunk);\n }\n const raw = Buffer.concat(chunks).toString('utf8').trim();\n if (!raw) return null;\n\n return JSON.parse(raw);\n}\n\nexport function sendJson(res: ServerResponse, status: number, body: unknown) {\n res.statusCode = status;\n res.setHeader('Content-Type', 'application/json; charset=utf-8');\n if (status === 204) {\n res.end();\n return;\n }\n res.end(JSON.stringify(body ?? null));\n}\n\nexport async function sendReviewFigmaAsset(\n res: ServerResponse,\n assetDir: string,\n assetEndpoint: string,\n pathname: string\n) {\n const storageKey = getReviewFigmaAssetStorageKeyFromPathname(pathname, assetEndpoint);\n if (!storageKey) {\n sendPlainText(res, 400, 'Invalid Figma image asset path.');\n return;\n }\n\n try {\n const data = await readFile(path.join(assetDir, storageKey));\n res.statusCode = 200;\n res.setHeader('Content-Type', getReviewFigmaAssetMimeType(storageKey));\n res.setHeader('Cache-Control', 'private, max-age=31536000, immutable');\n res.end(data);\n } catch (error) {\n if (isNodeError(error) && error.code === 'ENOENT') {\n sendPlainText(res, 404, 'Figma image asset not found.');\n return;\n }\n sendPlainText(\n res,\n 500,\n error instanceof Error ? error.message : 'Figma image asset request failed.'\n );\n }\n}\n\nfunction sendPlainText(res: ServerResponse, status: number, body: string) {\n res.statusCode = status;\n res.setHeader('Content-Type', 'text/plain; charset=utf-8');\n res.end(body);\n}\n\nfunction parseTargetParam(value: string | null) {\n if (!value) return null;\n try {\n return parseReviewFigmaImageTarget(JSON.parse(value));\n } catch {\n return null;\n }\n}\n\nfunction parseAddImageInput(value: unknown): AddReviewFigmaImageInput | null {\n if (!value || typeof value !== 'object') return null;\n const input = value as Partial<AddReviewFigmaImageInput>;\n const target = parseReviewFigmaImageTarget(input.target);\n if (!target || typeof input.figmaUrl !== 'string') return null;\n\n return {\n target,\n figmaUrl: input.figmaUrl,\n label: typeof input.label === 'string' ? input.label : undefined,\n order: typeof input.order === 'number' ? input.order : undefined,\n imageFormat: parseReviewFigmaImageFormat(input.imageFormat),\n asset: parseAddImageAssetInput(input.asset),\n };\n}\n\nfunction parseAddImageAssetInput(\n value: unknown\n): AddReviewFigmaImageInput['asset'] {\n if (!value || typeof value !== 'object') return undefined;\n const input = value as Partial<NonNullable<AddReviewFigmaImageInput['asset']>>;\n const imageFormat = parseReviewFigmaImageFormat(input.imageFormat);\n if (\n !imageFormat ||\n typeof input.dataUrl !== 'string' ||\n typeof input.mimeType !== 'string'\n ) {\n return undefined;\n }\n\n return {\n dataUrl: input.dataUrl,\n imageFormat,\n mimeType: input.mimeType,\n byteSize: typeof input.byteSize === 'number' ? input.byteSize : undefined,\n width: typeof input.width === 'number' ? input.width : undefined,\n height: typeof input.height === 'number' ? input.height : undefined,\n };\n}\n\nfunction parseUpdateImageInput(value: unknown) {\n if (!value || typeof value !== 'object') return null;\n const input = value as { label?: unknown; order?: unknown };\n\n return {\n label: typeof input.label === 'string' ? input.label : undefined,\n order: typeof input.order === 'number' ? input.order : undefined,\n };\n}\n\nfunction parseReorderImagesInput(\n value: unknown\n): ReorderReviewFigmaImagesInput | null {\n if (!value || typeof value !== 'object') return null;\n const input = value as ReorderReviewFigmaImagesInput;\n const target = parseReviewFigmaImageTarget(input.target);\n if (!target || !Array.isArray(input.imageIds)) return null;\n\n return {\n target,\n imageIds: input.imageIds.filter((id) => typeof id === 'string'),\n };\n}\n\nfunction parseReleaseSnapshotInput(\n value: unknown,\n requestUrl: URL,\n fallbackProjectId: string | undefined\n): {\n projectId: string;\n releaseId?: string;\n label?: string;\n targets: ReviewFigmaImageTarget[];\n} | null {\n const input = value && typeof value === 'object'\n ? (value as Partial<{ projectId: unknown; releaseId: unknown; label: unknown; targets: unknown }>)\n : null;\n const projectId = normalizeOptionalText(\n typeof input?.projectId === 'string'\n ? input.projectId\n : requestUrl.searchParams.get('projectId') ?? fallbackProjectId\n );\n if (!projectId) return null;\n\n return {\n projectId,\n releaseId: normalizeOptionalText(\n typeof input?.releaseId === 'string'\n ? input.releaseId\n : requestUrl.searchParams.get('releaseId')\n ),\n label: normalizeOptionalText(\n typeof input?.label === 'string'\n ? input.label\n : requestUrl.searchParams.get('label')\n ),\n targets: parseReleaseSnapshotTargets(input?.targets, requestUrl),\n };\n}\n\nfunction parseReleaseSnapshotTargets(value: unknown, requestUrl: URL) {\n const bodyTargets = Array.isArray(value)\n ? value.flatMap((target) => {\n const parsed = parseReviewFigmaImageTarget(target);\n return parsed ? [parsed] : [];\n })\n : [];\n const queryTargets = requestUrl.searchParams\n .getAll('target')\n .flatMap((target) => {\n const parsed = parseTargetParam(target);\n return parsed ? [parsed] : [];\n });\n const targetByKey = new Map(\n [...bodyTargets, ...queryTargets].map((target) => [\n getReviewFigmaImageTargetKey(target),\n target,\n ])\n );\n\n return Array.from(targetByKey.values());\n}\n\nfunction parseReviewFigmaImageTarget(\n value: unknown\n): ReviewFigmaImageTarget | null {\n if (!value || typeof value !== 'object') return null;\n const target = value as ReviewFigmaImageTarget;\n if (target.type === 'route') {\n if (\n typeof target.projectId !== 'string' ||\n typeof target.pageUrl !== 'string'\n ) {\n return null;\n }\n\n return {\n type: 'route',\n projectId: target.projectId,\n pageUrl: target.pageUrl,\n viewport:\n target.viewport && typeof target.viewport === 'object'\n ? target.viewport\n : undefined,\n slot: typeof target.slot === 'string' ? target.slot : undefined,\n };\n }\n\n if (target.type === 'figma-node') {\n if (\n typeof target.projectId !== 'string' ||\n typeof target.fileKey !== 'string' ||\n typeof target.nodeId !== 'string'\n ) {\n return null;\n }\n\n return {\n type: 'figma-node',\n projectId: target.projectId,\n fileKey: target.fileKey,\n nodeId: target.nodeId,\n };\n }\n\n return null;\n}\n\nexport function normalizeEndpoint(endpoint: string) {\n const normalized = endpoint.trim().replace(/\\/+$/, '');\n return normalized.startsWith('/') ? normalized : `/${normalized}`;\n}\n\nfunction getEndpointItemId(pathname: string, endpoint: string) {\n if (!pathname.startsWith(`${endpoint}/`)) return null;\n const value = pathname.slice(endpoint.length + 1);\n if (!value || value.includes('/')) return null;\n return decodeURIComponent(value);\n}\n\nfunction isAllowedProjectTarget(\n target: ReviewFigmaImageTarget,\n projectId: string | undefined\n) {\n return !projectId || target.projectId === projectId;\n}\n\nfunction jsonError(status: number, error: string): ReviewFigmaImageStoreResponse {\n return { status, body: { error } };\n}\n","import { mkdir, readFile, rm, writeFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport type {\n ReviewFigmaImage,\n ReviewFigmaImageAssetInput,\n ReviewFigmaImageFormat,\n ReviewFigmaImageTarget,\n AddReviewFigmaImageInput,\n UpdateReviewFigmaImageInput,\n} from '../figma/image.types';\nimport {\n getReviewFigmaImageMimeType,\n getReviewFigmaImageTargetKey,\n type ReorderReviewFigmaImagesInput,\n} from '../figma/image.store';\nimport { parseReviewFigmaNodeRef } from '../figma/parse';\nimport {\n DEFAULT_REVIEW_FIGMA_TOKEN_ENV_KEY,\n ReviewFigmaTokenError,\n readReviewFigmaToken,\n type ReviewFigmaTokenEnv,\n} from '../figma/token';\nimport {\n renderReviewFigmaImage,\n type ReviewFigmaRenderFormat,\n} from '../figma/render';\nimport {\n createReviewFigmaAssetStorageKey,\n createReviewFigmaAssetUrl,\n getReviewFigmaImageFormatFromMimeType,\n getStoreRenderFormat,\n isSafeReviewFigmaAssetStorageKey,\n normalizeImageMimeType,\n} from './figma-asset';\nimport type { ReviewFigmaImageStorePluginOptions, ReviewFigmaImageAssetTransformer } from './figma-image-store';\n\nexport type ReviewFigmaImageStoreFile = {\n version: 1;\n images: ReviewFigmaImage[];\n};\n\nfunction requireReviewFigmaRequestToken({\n enabled,\n env,\n envKey,\n requestToken,\n token,\n}: {\n enabled?: boolean;\n env: ReviewFigmaTokenEnv;\n envKey?: string;\n requestToken?: string | null;\n token?: string | null;\n}) {\n const tokenEnvKey = envKey ?? DEFAULT_REVIEW_FIGMA_TOKEN_ENV_KEY;\n const figmaToken =\n readReviewFigmaToken({ token, env, envKey: tokenEnvKey, enabled }) ||\n readReviewFigmaToken({\n token: requestToken,\n env: {},\n envKey: tokenEnvKey,\n enabled,\n });\n\n if (!figmaToken) throw new ReviewFigmaTokenError(tokenEnvKey);\n return figmaToken;\n}\n\nasync function readReviewFigmaNodeName({\n apiBaseUrl,\n enabled,\n env,\n envKey,\n fetchOption,\n fileKey,\n nodeId,\n token,\n requestToken,\n}: {\n apiBaseUrl?: string;\n enabled?: boolean;\n env: ReviewFigmaTokenEnv;\n envKey?: string;\n fetchOption?: typeof fetch;\n fileKey: string;\n nodeId: string;\n token?: string | null;\n requestToken?: string | null;\n}) {\n const figmaToken = requireReviewFigmaRequestToken({\n enabled,\n env,\n envKey,\n requestToken,\n token,\n });\n const fetchNode = fetchOption ?? globalThis.fetch;\n if (!fetchNode) throw new Error('Figma node name lookup requires fetch.');\n\n type FigmaNodeResponse = {\n err?: string | null;\n nodes?: Record<string, { document?: { name?: string | null } | null } | null | undefined>;\n };\n\n const response = await fetchNode(\n createReviewFigmaNodeApiUrl({ apiBaseUrl, fileKey, nodeId }),\n {\n headers: {\n 'X-Figma-Token': figmaToken,\n },\n }\n );\n const body = (await response.json().catch(() => null)) as FigmaNodeResponse | null;\n\n if (!response.ok) {\n throw new Error(body?.err || `Figma node lookup failed with ${response.status}`);\n }\n\n const nodes = body?.nodes;\n const node = nodes?.[nodeId] ?? Object.values(nodes ?? {})[0];\n return normalizeOptionalText(node?.document?.name);\n}\n\nfunction createReviewFigmaNodeApiUrl({\n apiBaseUrl = 'https://api.figma.com',\n fileKey,\n nodeId,\n}: {\n apiBaseUrl?: string;\n fileKey: string;\n nodeId: string;\n}) {\n const url = new URL(\n `/v1/files/${encodeURIComponent(fileKey)}/nodes`,\n apiBaseUrl\n );\n url.searchParams.set('ids', nodeId);\n return url.toString();\n}\n\nexport async function createReviewFigmaImage({\n assetDir,\n assetEndpoint,\n currentImages,\n env,\n input,\n options,\n requestToken,\n}: {\n assetDir: string;\n assetEndpoint: string;\n currentImages: ReviewFigmaImage[];\n env: ReviewFigmaTokenEnv;\n input: AddReviewFigmaImageInput;\n options: ReviewFigmaImageStorePluginOptions;\n requestToken?: string | null;\n}): Promise<ReviewFigmaImage> {\n const ref = parseReviewFigmaNodeRef(input.figmaUrl);\n if (!ref) {\n throw new Error('A Figma node copy link or fileKey->nodeId value is required.');\n }\n\n const id = createReviewFigmaImageId();\n const explicitLabel = normalizeOptionalText(input.label);\n if (input.asset) {\n const cachedAsset = await cacheReviewFigmaProvidedImageAsset({\n assetDir,\n assetEndpoint,\n id,\n asset: input.asset,\n options,\n });\n const now = new Date().toISOString();\n const order =\n typeof input.order === 'number' && Number.isFinite(input.order)\n ? input.order\n : getNextImageOrder(currentImages, input.target);\n\n return {\n id,\n projectId: input.target.projectId,\n target: input.target,\n figmaUrl: input.figmaUrl,\n fileKey: ref.fileKey,\n nodeId: ref.nodeId,\n imageUrl: cachedAsset.imageUrl,\n imageFormat: cachedAsset.imageFormat,\n mimeType: cachedAsset.mimeType,\n label: explicitLabel,\n order,\n storageKey: cachedAsset.storageKey,\n width: input.asset.width,\n height: input.asset.height,\n byteSize: cachedAsset.byteSize,\n createdAt: now,\n updatedAt: now,\n };\n }\n\n const nodeLabelPromise = explicitLabel\n ? Promise.resolve(undefined)\n : readReviewFigmaNodeName({\n apiBaseUrl: options.apiBaseUrl,\n enabled: options.enabled,\n env,\n envKey: options.envKey,\n fetchOption: options.fetch,\n fileKey: ref.fileKey,\n nodeId: ref.nodeId,\n token: options.token,\n requestToken,\n }).catch(() => undefined);\n const targetImageFormat = input.imageFormat ?? options.imageFormat ?? 'webp';\n const renderFormat = getStoreRenderFormat(options.renderFormat, targetImageFormat);\n const rendered = await renderReviewFigmaImage({\n figmaUrl: input.figmaUrl,\n format: renderFormat,\n scale: options.renderScale,\n useAbsoluteBounds: options.useAbsoluteBounds,\n apiBaseUrl: options.apiBaseUrl,\n fetch: options.fetch,\n token:\n requireReviewFigmaRequestToken({\n enabled: options.enabled,\n env,\n envKey: options.envKey,\n requestToken,\n token: options.token,\n }),\n });\n const cachedAsset = await cacheReviewFigmaImageAsset({\n assetDir,\n assetEndpoint,\n id,\n imageUrl: rendered.imageUrl,\n options,\n renderFormat,\n targetImageFormat,\n });\n const imageFormat =\n cachedAsset?.imageFormat ?? (renderFormat === 'jpg' ? 'jpg' : 'png');\n const now = new Date().toISOString();\n const order =\n typeof input.order === 'number' && Number.isFinite(input.order)\n ? input.order\n : getNextImageOrder(currentImages, input.target);\n const nodeLabel = await nodeLabelPromise;\n\n return {\n id,\n projectId: input.target.projectId,\n target: input.target,\n figmaUrl: input.figmaUrl,\n fileKey: rendered.fileKey,\n nodeId: rendered.nodeId,\n imageUrl: cachedAsset?.imageUrl ?? rendered.imageUrl,\n imageFormat,\n mimeType: cachedAsset?.mimeType ?? getReviewFigmaImageMimeType(imageFormat),\n label: explicitLabel ?? nodeLabel,\n order,\n storageKey: cachedAsset?.storageKey,\n byteSize: cachedAsset?.byteSize,\n createdAt: now,\n updatedAt: now,\n };\n}\n\ntype CachedReviewFigmaImageAsset = {\n imageUrl: string;\n imageFormat: ReviewFigmaImageFormat;\n mimeType: string;\n storageKey: string;\n byteSize: number;\n};\n\nasync function cacheReviewFigmaProvidedImageAsset({\n assetDir,\n assetEndpoint,\n id,\n asset,\n options,\n}: {\n assetDir: string;\n assetEndpoint: string;\n id: string;\n asset: ReviewFigmaImageAssetInput;\n options: ReviewFigmaImageStorePluginOptions;\n}): Promise<CachedReviewFigmaImageAsset> {\n const decodedAsset = decodeReviewFigmaImageAsset(asset);\n const storageKey = createReviewFigmaAssetStorageKey(\n id,\n decodedAsset.imageFormat\n );\n\n await mkdir(assetDir, { recursive: true });\n await writeFile(path.join(assetDir, storageKey), decodedAsset.data);\n\n return {\n imageUrl: createReviewFigmaAssetUrl(assetEndpoint, storageKey),\n imageFormat: decodedAsset.imageFormat,\n mimeType: decodedAsset.mimeType,\n storageKey,\n byteSize: decodedAsset.data.byteLength,\n };\n}\n\nfunction decodeReviewFigmaImageAsset(asset: ReviewFigmaImageAssetInput) {\n const mimeType = normalizeImageMimeType(asset.mimeType);\n if (!mimeType) throw new Error('Unsupported Figma image asset MIME type.');\n\n const imageFormat =\n getReviewFigmaImageFormatFromMimeType(mimeType) ?? asset.imageFormat;\n const match = /^data:([^;,]+);base64,([a-zA-Z0-9+/=\\s]+)$/.exec(\n asset.dataUrl\n );\n if (!match) throw new Error('Valid Figma image asset data URL is required.');\n\n const dataUrlMimeType = normalizeImageMimeType(match[1]);\n if (dataUrlMimeType && dataUrlMimeType !== mimeType) {\n throw new Error('Figma image asset MIME type mismatch.');\n }\n\n return {\n data: Buffer.from(match[2].replace(/\\s/g, ''), 'base64'),\n imageFormat,\n mimeType,\n };\n}\n\nasync function cacheReviewFigmaImageAsset({\n assetDir,\n assetEndpoint,\n id,\n imageUrl,\n options,\n renderFormat,\n targetImageFormat,\n}: {\n assetDir: string;\n assetEndpoint: string;\n id: string;\n imageUrl: string;\n options: ReviewFigmaImageStorePluginOptions;\n renderFormat: Extract<ReviewFigmaRenderFormat, 'png' | 'jpg'>;\n targetImageFormat: ReviewFigmaImageFormat;\n}): Promise<CachedReviewFigmaImageAsset | null> {\n if (options.cacheAssets === false) return null;\n\n const asset = await downloadReviewFigmaImageAsset({\n fetchOption: options.fetch,\n imageUrl,\n renderFormat,\n targetImageFormat,\n transformAsset: options.transformAsset,\n });\n const storageKey = createReviewFigmaAssetStorageKey(id, asset.imageFormat);\n\n await mkdir(assetDir, { recursive: true });\n await writeFile(path.join(assetDir, storageKey), asset.data);\n\n return {\n imageUrl: createReviewFigmaAssetUrl(assetEndpoint, storageKey),\n imageFormat: asset.imageFormat,\n mimeType: asset.mimeType,\n storageKey,\n byteSize: asset.data.byteLength,\n };\n}\n\nasync function downloadReviewFigmaImageAsset({\n fetchOption,\n imageUrl,\n renderFormat,\n targetImageFormat,\n transformAsset,\n}: {\n fetchOption: typeof fetch | undefined;\n imageUrl: string;\n renderFormat: Extract<ReviewFigmaRenderFormat, 'png' | 'jpg'>;\n targetImageFormat: ReviewFigmaImageFormat;\n transformAsset: ReviewFigmaImageAssetTransformer | undefined;\n}) {\n const fetchImage = fetchOption ?? globalThis.fetch;\n if (!fetchImage) throw new Error('Figma image caching requires fetch.');\n\n const response = await fetchImage(imageUrl);\n if (!response.ok) {\n throw new Error(`Figma image download failed with ${response.status}`);\n }\n\n const sourceMimeType =\n normalizeImageMimeType(response.headers.get('content-type')) ??\n getReviewFigmaImageMimeType(renderFormat === 'jpg' ? 'jpg' : 'png');\n const sourceImageFormat =\n getReviewFigmaImageFormatFromMimeType(sourceMimeType) ??\n (renderFormat === 'jpg' ? 'jpg' : 'png');\n const sourceData = new Uint8Array(await response.arrayBuffer());\n const transformed = transformAsset\n ? await transformAsset({\n data: sourceData,\n imageFormat: sourceImageFormat,\n mimeType: sourceMimeType,\n targetFormat: targetImageFormat,\n })\n : null;\n const imageFormat = transformed?.imageFormat ?? sourceImageFormat;\n const mimeType =\n normalizeImageMimeType(transformed?.mimeType) ??\n getReviewFigmaImageMimeType(imageFormat);\n const data = createBufferFromImageData(transformed?.data ?? sourceData);\n\n return { data, imageFormat, mimeType };\n}\n\nexport async function deleteReviewFigmaImageAsset(\n assetDir: string,\n storageKey: string | undefined\n) {\n if (!storageKey) return;\n if (!isSafeReviewFigmaAssetStorageKey(storageKey)) return;\n await rm(path.join(assetDir, storageKey), { force: true }).catch(() => null);\n}\n\nfunction createBufferFromImageData(data: Uint8Array | ArrayBuffer) {\n return data instanceof ArrayBuffer\n ? Buffer.from(new Uint8Array(data))\n : Buffer.from(data);\n}\n\nexport function listImagesForTarget(\n images: ReviewFigmaImage[],\n target: ReviewFigmaImageTarget\n) {\n const targetKey = getReviewFigmaImageTargetKey(target);\n return images\n .filter((image) => getReviewFigmaImageTargetKey(image.target) === targetKey)\n .sort(compareReviewFigmaImages);\n}\n\nexport function reorderReviewFigmaImages(\n images: ReviewFigmaImage[],\n input: ReorderReviewFigmaImagesInput\n) {\n const targetKey = getReviewFigmaImageTargetKey(input.target);\n const orderById = new Map(input.imageIds.map((id, index) => [id, index]));\n const targetImages = listImagesForTarget(images, input.target);\n const nextTargetImages = targetImages\n .map((image) => ({\n ...image,\n order: orderById.get(image.id) ?? input.imageIds.length + image.order,\n updatedAt: orderById.has(image.id) ? new Date().toISOString() : image.updatedAt,\n }))\n .sort(compareReviewFigmaImages);\n const nextTargetImageById = new Map(\n nextTargetImages.map((image, index) => [image.id, { ...image, order: index }])\n );\n const allImages = images.map((image) =>\n getReviewFigmaImageTargetKey(image.target) === targetKey\n ? nextTargetImageById.get(image.id) ?? image\n : image\n );\n\n return {\n allImages,\n targetImages: listImagesForTarget(allImages, input.target),\n };\n}\n\nexport function updateReviewFigmaImage(\n images: ReviewFigmaImage[],\n id: string,\n patch: UpdateReviewFigmaImageInput\n) {\n const index = images.findIndex((image) => image.id === id);\n if (index < 0) return null;\n\n const nextImage: ReviewFigmaImage = {\n ...images[index],\n label:\n patch.label === undefined\n ? images[index].label\n : normalizeOptionalText(patch.label),\n order:\n typeof patch.order === 'number' && Number.isFinite(patch.order)\n ? patch.order\n : images[index].order,\n updatedAt: new Date().toISOString(),\n };\n const nextImages = [...images];\n nextImages[index] = nextImage;\n\n return { image: nextImage, images: nextImages };\n}\n\nexport async function readReviewFigmaImageStoreFile(\n dataFile: string\n): Promise<ReviewFigmaImageStoreFile> {\n try {\n const raw = await readFile(dataFile, 'utf8');\n const parsed = JSON.parse(raw) as Partial<ReviewFigmaImageStoreFile>;\n\n return {\n version: 1,\n images: Array.isArray(parsed.images)\n ? parsed.images.flatMap((image) => (isReviewFigmaImage(image) ? [image] : []))\n : [],\n };\n } catch (error) {\n if (isNodeError(error) && error.code === 'ENOENT') {\n return { version: 1, images: [] };\n }\n throw error;\n }\n}\n\nexport async function writeReviewFigmaImageStoreFile(\n dataFile: string,\n data: ReviewFigmaImageStoreFile\n) {\n await mkdir(path.dirname(dataFile), { recursive: true });\n await writeFile(\n dataFile,\n `${JSON.stringify({ version: 1, images: data.images }, null, 2)}\\n`,\n 'utf8'\n );\n}\n\nfunction getNextImageOrder(\n images: ReviewFigmaImage[],\n target: ReviewFigmaImageTarget\n) {\n const targetImages = listImagesForTarget(images, target);\n return targetImages.length\n ? Math.max(...targetImages.map((image) => image.order)) + 1\n : 0;\n}\n\nfunction compareReviewFigmaImages(a: ReviewFigmaImage, b: ReviewFigmaImage) {\n return a.order - b.order || a.createdAt.localeCompare(b.createdAt);\n}\n\nfunction createReviewFigmaImageId() {\n return `figma_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;\n}\n\nfunction isReviewFigmaImage(value: unknown): value is ReviewFigmaImage {\n if (!value || typeof value !== 'object') return false;\n const image = value as ReviewFigmaImage;\n\n return (\n typeof image.id === 'string' &&\n typeof image.projectId === 'string' &&\n typeof image.figmaUrl === 'string' &&\n typeof image.fileKey === 'string' &&\n typeof image.nodeId === 'string' &&\n typeof image.imageUrl === 'string' &&\n typeof image.order === 'number' &&\n typeof image.createdAt === 'string' &&\n typeof image.updatedAt === 'string'\n );\n}\n\nexport function isNodeError(error: unknown): error is { code?: string } {\n if (!error || typeof error !== 'object') return false;\n return 'code' in error;\n}\n\nexport function normalizeOptionalText(value: string | null | undefined) {\n if (typeof value !== 'string') return undefined;\n return value.trim() || undefined;\n}\n","import { type Plugin, type ResolvedConfig } from 'vite';\n\nexport * from './vite/figma-image-store';\n\ntype SourceLocatorPattern = string | RegExp;\n\nexport interface ReviewSourceLocatorOptions {\n enabled?: boolean;\n root?: string;\n include?: readonly SourceLocatorPattern[];\n exclude?: readonly SourceLocatorPattern[];\n filePath?: 'relative' | 'absolute';\n line?: boolean;\n column?: boolean;\n attributePrefix?: string;\n}\n\ntype RuntimeMatcher =\n | { type: 'path'; value: string }\n | { type: 'regex'; value: string; flags: string };\n\ntype RuntimeOptions = {\n enabled: boolean;\n root: string;\n include: RuntimeMatcher[];\n exclude: RuntimeMatcher[];\n filePath: 'relative' | 'absolute';\n line: boolean;\n column: boolean;\n fileAttribute: string;\n lineAttribute: string;\n columnAttribute: string;\n};\n\nconst VIRTUAL_JSX_DEV_RUNTIME_ID =\n '\\0@designfever/web-review-kit/source-locator/jsx-dev-runtime';\n\nconst REVIEW_SOURCE_ENV_DEFINE_KEYS = [\n ['__DF_WRK_REVIEW_SOURCE_ROOT__', 'VITE_REVIEW_SOURCE_ROOT'],\n ['__DF_WRK_REVIEW_SOURCE_EDITOR__', 'VITE_REVIEW_SOURCE_EDITOR'],\n [\n '__DF_WRK_REVIEW_SOURCE_URL_TEMPLATE__',\n 'VITE_REVIEW_SOURCE_URL_TEMPLATE',\n ],\n] as const;\n\ntype ReviewSourceEnvReplacements = Record<string, string>;\n\nconst createReviewSourceEnvReplacements = (\n env: ResolvedConfig['env'] = {}\n): ReviewSourceEnvReplacements => {\n return Object.fromEntries(\n REVIEW_SOURCE_ENV_DEFINE_KEYS.map(([defineKey, envKey]) => [\n defineKey,\n JSON.stringify(env[envKey] ?? ''),\n ])\n );\n};\n\nconst injectReviewSourceEnv = (\n code: string,\n replacements: ReviewSourceEnvReplacements\n) => {\n let nextCode = code;\n for (const [defineKey, value] of Object.entries(replacements)) {\n nextCode = nextCode\n .split(`typeof ${defineKey}`)\n .join(`typeof ${value}`)\n .split(`: ${defineKey}`)\n .join(`: ${value}`);\n }\n\n return nextCode === code ? null : nextCode;\n};\n\nexport const reviewSourceLocator = (\n options: ReviewSourceLocatorOptions = {}\n): Plugin => {\n let runtimeOptions = createRuntimeOptions(options);\n let sourceEnvReplacements = createReviewSourceEnvReplacements();\n\n return {\n name: 'df-web-review-kit-source-locator',\n enforce: 'pre',\n configResolved(config) {\n runtimeOptions = createRuntimeOptions(options, config);\n sourceEnvReplacements = createReviewSourceEnvReplacements(config.env);\n },\n resolveId(id, importer) {\n if (!runtimeOptions.enabled) return null;\n if (id !== 'react/jsx-dev-runtime') return null;\n if (importer === VIRTUAL_JSX_DEV_RUNTIME_ID) return null;\n\n return VIRTUAL_JSX_DEV_RUNTIME_ID;\n },\n load(id) {\n if (id !== VIRTUAL_JSX_DEV_RUNTIME_ID) return null;\n return createJsxDevRuntime(runtimeOptions);\n },\n transform(code) {\n const injectedCode = injectReviewSourceEnv(code, sourceEnvReplacements);\n return injectedCode ? { code: injectedCode, map: null } : null;\n },\n };\n};\n\nexport interface ReviewDataLocatorOptions {\n enabled?: boolean;\n root?: string;\n include?: readonly SourceLocatorPattern[];\n exclude?: readonly SourceLocatorPattern[];\n filePath?: 'relative' | 'absolute';\n /** 매칭할 component 이름 패턴. 기본은 `Section`으로 시작하는 이름. */\n componentPattern?: RegExp;\n fileAttribute?: string;\n lineAttribute?: string;\n}\n\n/**\n * page data 파일의 section 객체(`component: 'SectionXxx'`)에 출처 파일/라인을\n * `__wrkDataFile`/`__wrkDataLine` prop 으로 주입한다. 라인 보존을 위해 같은 줄에만 삽입한다.\n */\nexport const reviewDataLocator = (\n options: ReviewDataLocatorOptions = {}\n): Plugin => {\n let root = normalizePath(options.root ?? '');\n let enabled = options.enabled ?? false;\n let sourceEnvReplacements = createReviewSourceEnvReplacements();\n const include = (options.include ?? []).map(createRuntimeMatcher);\n const exclude = (options.exclude ?? ['node_modules', 'dist']).map(\n createRuntimeMatcher\n );\n const componentPattern = options.componentPattern ?? /Section[A-Za-z0-9_]*/;\n const fileKey = options.fileAttribute ?? '__wrkDataFile';\n const lineKey = options.lineAttribute ?? '__wrkDataLine';\n\n const componentSource = `(^|[\\\\n,{(\\\\[]\\\\s*)(component:\\\\s*)(['\"\\`])(${componentPattern.source})\\\\3`;\n\n return {\n name: 'df-web-review-kit-data-locator',\n enforce: 'pre',\n configResolved(config) {\n root = normalizePath(options.root ?? config.root ?? '');\n enabled = options.enabled ?? config.command === 'serve';\n sourceEnvReplacements = createReviewSourceEnvReplacements(config.env);\n },\n transform(code, id) {\n const envInjectedCode = injectReviewSourceEnv(\n code,\n sourceEnvReplacements\n );\n const inputCode = envInjectedCode ?? code;\n if (!enabled) return null;\n const file = normalizePath(id.split('?')[0]);\n const relativeFile =\n root && file.startsWith(root + '/') ? file.slice(root.length + 1) : file;\n if (\n include.length > 0 &&\n !include.some((m) => matchesPath(m, file, relativeFile))\n )\n return envInjectedCode ? { code: envInjectedCode, map: null } : null;\n if (exclude.some((m) => matchesPath(m, file, relativeFile))) {\n return envInjectedCode ? { code: envInjectedCode, map: null } : null;\n }\n\n const sourceFile =\n (options.filePath ?? 'relative') === 'absolute' ? file : relativeFile;\n const regex = new RegExp(componentSource, 'g');\n let changed = false;\n const out = inputCode.replace(\n regex,\n (\n _match,\n pre: string,\n comp: string,\n quote: string,\n name: string,\n offset: number\n ) => {\n const line = inputCode\n .slice(0, offset + pre.length)\n .split('\\n').length;\n changed = true;\n return `${pre}${JSON.stringify(fileKey)}: ${JSON.stringify(sourceFile)}, ${JSON.stringify(lineKey)}: ${line}, ${comp}${quote}${name}${quote}`;\n }\n );\n\n return changed || envInjectedCode ? { code: out, map: null } : null;\n },\n };\n};\n\nfunction matchesPath(\n matcher: RuntimeMatcher,\n absoluteFile: string,\n relativeFile: string\n) {\n if (matcher.type === 'regex') {\n const regex = new RegExp(matcher.value, matcher.flags);\n return regex.test(absoluteFile) || regex.test(relativeFile);\n }\n const target = matcher.value.startsWith('/') ? absoluteFile : relativeFile;\n return target === matcher.value || target.startsWith(matcher.value + '/') || target.includes('/' + matcher.value);\n}\n\nfunction createRuntimeOptions(\n options: ReviewSourceLocatorOptions,\n config?: ResolvedConfig\n): RuntimeOptions {\n const attributePrefix = (options.attributePrefix ?? 'data-wrk-source').replace(\n /-+$/,\n ''\n );\n const root = normalizePath(options.root ?? config?.root ?? '');\n const enabled = options.enabled ?? (config?.command === 'serve');\n\n return {\n enabled,\n root,\n include: (options.include ?? []).map(createRuntimeMatcher),\n exclude: (options.exclude ?? ['node_modules', 'dist']).map(\n createRuntimeMatcher\n ),\n filePath: options.filePath ?? 'relative',\n line: options.line ?? true,\n column: options.column ?? true,\n fileAttribute: `${attributePrefix}-file`,\n lineAttribute: `${attributePrefix}-line`,\n columnAttribute: `${attributePrefix}-column`,\n };\n}\n\nfunction createRuntimeMatcher(pattern: SourceLocatorPattern): RuntimeMatcher {\n if (pattern instanceof RegExp) {\n return { type: 'regex', value: pattern.source, flags: pattern.flags };\n }\n\n return { type: 'path', value: normalizePath(pattern).replace(/^\\.\\//, '') };\n}\n\nfunction normalizePath(value: string) {\n return value.replace(/\\\\/g, '/').replace(/\\/+$/, '');\n}\n\nfunction createJsxDevRuntime(options: RuntimeOptions) {\n return `\nimport { Fragment, jsxDEV as baseJsxDEV } from 'react/jsx-dev-runtime';\n\nconst OPTIONS = ${JSON.stringify(options)};\n\nexport { Fragment };\n\nexport function jsxDEV(type, props, key, isStaticChildren, source, self) {\n return baseJsxDEV(\n type,\n injectSourceProps(type, props, source),\n key,\n isStaticChildren,\n source,\n self\n );\n}\n\nfunction injectSourceProps(type, props, source) {\n if (typeof type !== 'string') return props;\n if (!source || typeof source.fileName !== 'string') return props;\n\n const sourceFile = getSourceFile(source.fileName);\n if (!sourceFile) return props;\n\n const nextProps = props ? { ...props } : {};\n if (nextProps[OPTIONS.fileAttribute] == null) {\n nextProps[OPTIONS.fileAttribute] = sourceFile;\n }\n if (OPTIONS.line && source.lineNumber != null && nextProps[OPTIONS.lineAttribute] == null) {\n nextProps[OPTIONS.lineAttribute] = String(source.lineNumber);\n }\n if (OPTIONS.column && source.columnNumber != null && nextProps[OPTIONS.columnAttribute] == null) {\n nextProps[OPTIONS.columnAttribute] = String(source.columnNumber);\n }\n\n return nextProps;\n}\n\nfunction getSourceFile(fileName) {\n const absoluteFile = normalizePath(fileName);\n const relativeFile = getRelativeFile(absoluteFile);\n\n if (OPTIONS.include.length > 0 && !matchesAny(OPTIONS.include, absoluteFile, relativeFile)) {\n return null;\n }\n if (matchesAny(OPTIONS.exclude, absoluteFile, relativeFile)) return null;\n\n return OPTIONS.filePath === 'absolute' ? absoluteFile : relativeFile;\n}\n\nfunction getRelativeFile(absoluteFile) {\n if (!OPTIONS.root) return absoluteFile;\n if (absoluteFile === OPTIONS.root) return '';\n if (absoluteFile.startsWith(OPTIONS.root + '/')) {\n return absoluteFile.slice(OPTIONS.root.length + 1);\n }\n\n return absoluteFile;\n}\n\nfunction matchesAny(patterns, absoluteFile, relativeFile) {\n return patterns.some((pattern) =>\n matchesPattern(pattern, absoluteFile, relativeFile)\n );\n}\n\nfunction matchesPattern(pattern, absoluteFile, relativeFile) {\n if (pattern.type === 'regex') {\n const regex = new RegExp(pattern.value, pattern.flags);\n return regex.test(absoluteFile) || regex.test(relativeFile);\n }\n\n const value = pattern.value;\n const target = isAbsolutePattern(value) ? absoluteFile : relativeFile;\n if (!value.includes('*')) {\n return target === value || target.startsWith(value + '/');\n }\n\n return globToRegExp(value).test(target);\n}\n\nfunction isAbsolutePattern(value) {\n return value.startsWith('/') || /^[a-zA-Z]:\\\\//.test(value);\n}\n\nfunction globToRegExp(value) {\n const source = escapeRegExp(value)\n .replace(/\\\\\\\\\\\\*\\\\\\\\\\\\*/g, '.*')\n .replace(/\\\\\\\\\\\\*/g, '[^/]*');\n\n return new RegExp('^' + source + '$');\n}\n\nfunction escapeRegExp(value) {\n return value.replace(/[|\\\\\\\\{}()[\\\\]^$+*?.]/g, '\\\\\\\\$&');\n}\n\nfunction normalizePath(value) {\n return value.replace(/\\\\\\\\/g, '/').replace(/\\\\/+$/, '');\n}\n`;\n}\n"],"mappings":"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","names":["path","readFile","path","cachedAsset","now","order","readFile","path","path"]}
package/docs/README.md CHANGED
@@ -12,7 +12,9 @@ Public docs are intentionally small. Keep implementation history, handoff notes,
12
12
  6. [Architecture and runtime logic](architecture.md)
13
13
  7. [Figma overlay](figma-overlay.md)
14
14
  8. [Grid overlay](grid-overlay.md)
15
- 9. [Release notes 0.7.0](release-notes-0.7.0.md)
15
+ 9. [Release notes 0.7.2](release-notes-0.7.2.md)
16
+ 10. [Release notes 0.7.1](release-notes-0.7.1.md)
17
+ 11. [Release notes 0.7.0](release-notes-0.7.0.md)
16
18
 
17
19
  ## Document Roles
18
20
 
@@ -24,7 +26,9 @@ Public docs are intentionally small. Keep implementation history, handoff notes,
24
26
  - `architecture.md`: core/runtime, React shell, coordinate, anchor, and feature boundary notes.
25
27
  - `figma-overlay.md`: host requirements for the Figma overlay toggle.
26
28
  - `grid-overlay.md`: host requirements for the grid/helper overlay toggle.
27
- - `release-notes-0.7.0.md`: latest release changes, host notes, and validation scope.
29
+ - `release-notes-0.7.2.md`: latest release changes, remote Figma image store API, and validation scope.
30
+ - `release-notes-0.7.1.md`: previous release changes, host notes, and validation scope.
31
+ - `release-notes-0.7.0.md`: previous release changes and validation scope.
28
32
 
29
33
  ## Boundary
30
34
 
package/docs/adapters.md CHANGED
@@ -19,7 +19,7 @@ Use `adapter` for both concepts in conversation only when the context is clear.
19
19
  | Data | `ReviewItem` records | `ReviewFigmaImage` records and image assets |
20
20
  | Main actions | `get`, `list`, `create`, `update`, `remove` | `listImages`, `addImage`, `updateImage`, `reorderImages`, `deleteImage` |
21
21
  | UI fields | `fields.title`, `statusOptions`, `assigneeOptions` | image label, order, opacity/layer controls |
22
- | Token/secrets | Host backend decides | `FIGMA_TOKEN` stays server-side for Vite/dev endpoint |
22
+ | Token/secrets | Host backend decides | `FIGMA_TOKEN` server env first, Settings `figma-token` request fallback |
23
23
  | Typical storage | localStorage, Supabase, df-sheet, custom QA API | `.df-review/figma-images.json`, `.df-review/figma-assets/`, future asset backend |
24
24
 
25
25
  ## QA Adapter
@@ -101,12 +101,42 @@ export default defineConfig({
101
101
  });
102
102
  ```
103
103
 
104
+ For shared browser-side storage backed by a Supabase-like table and an asset
105
+ upload endpoint, use the remote factory:
106
+
107
+ ```tsx
108
+ import {
109
+ createRemoteReviewFigmaImageStore,
110
+ type ReviewFigmaRemoteDbClient,
111
+ } from '@designfever/web-review-kit';
112
+
113
+ const figmaImageStore = createRemoteReviewFigmaImageStore({
114
+ client: supabaseClient as unknown as ReviewFigmaRemoteDbClient,
115
+ uploadEndpoint: import.meta.env.VITE_FIGMA_ASSET_UPLOAD_ENDPOINT,
116
+ token: () => import.meta.env.VITE_FIGMA_TOKEN,
117
+ });
118
+
119
+ mountReviewShell({
120
+ projectId,
121
+ pages,
122
+ adapters,
123
+ figmaImages: {
124
+ store: figmaImageStore,
125
+ },
126
+ });
127
+ ```
128
+
104
129
  Guidelines:
105
130
 
106
131
  - Do not put Figma image metadata into QA adapter metadata.
107
- - Do not use `VITE_FIGMA_TOKEN` for server rendering. Keep `FIGMA_TOKEN` on the server/dev process.
132
+ - Do not use `VITE_FIGMA_TOKEN` for server rendering. Keep shared tokens in
133
+ `FIGMA_TOKEN`; use Settings `figma-token` only as a browser-local review
134
+ fallback.
135
+ - For remote browser-side stores, project env can provide a token via the
136
+ factory `token` option. If it is empty, Settings `figma-token` is used as the
137
+ fallback.
108
138
  - If no `figmaImages.store` is configured, QA should still work.
109
- - If Figma image storage moves remote later, implement a `ReviewFigmaImageStore`-shaped client instead of extending `ReviewShellAdapter`.
139
+ - Keep remote Figma image storage separate from `ReviewShellAdapter`.
110
140
 
111
141
  ## Host Wiring Rule
112
142
 
@@ -45,10 +45,10 @@ and submission sync do not become coupled to image management.
45
45
 
46
46
  ### Current Figma Integration
47
47
 
48
- The current Figma integration is only a host-helper toggle:
48
+ The previous Figma integration was only a host-helper toggle:
49
49
 
50
- - `docs/figma-overlay.md` states that the package does not fetch Figma data or
51
- own a server-side token.
50
+ - `docs/figma-overlay.md` documents the host-helper toggle and the image store
51
+ token fallback.
52
52
  - `src/react-shell/figma.ts` reads `window.__figma` and builds a Figma frame
53
53
  link from `fileKey->nodeId`.
54
54
  - `src/react-shell/hooks/use.review.target.overlay.ts` dispatches `KeyF` into
@@ -198,16 +198,19 @@ store contract:
198
198
  - `PATCH /__dfwr/figma-images/:id`
199
199
  - `DELETE /__dfwr/figma-images/:id`
200
200
 
201
- The dev endpoint reads `FIGMA_TOKEN` from server env only when it needs to call
202
- Figma. Do not expose `VITE_FIGMA_TOKEN`.
201
+ The dev endpoint reads `FIGMA_TOKEN` from server env first when it needs to call
202
+ Figma. If the env token is absent, the browser Settings token stored as
203
+ `figma-token` can be sent to the same-origin image store request as a fallback.
204
+ Do not expose `VITE_FIGMA_TOKEN`.
203
205
 
204
206
  Token validation is intentionally lazy:
205
207
 
206
208
  - Store missing or disabled: no token check, keep the current host-helper
207
209
  fallback.
208
210
  - Store enabled but no image render/add request has happened: no token check.
209
- - Image render/add request: require `FIGMA_TOKEN` from the dev/server
210
- environment and return `DFWR_FIGMA_TOKEN_MISSING` when it is absent.
211
+ - Image render/add request: use `FIGMA_TOKEN` from the dev/server environment
212
+ first, then the Settings `figma-token` request fallback. Return
213
+ `DFWR_FIGMA_TOKEN_MISSING` when both are absent.
211
214
 
212
215
  ## Impact Files
213
216
 
@@ -224,9 +227,8 @@ Expected package-side implementation files:
224
227
  - `src/react-shell/topbar.tsx`: keep the existing overlay toggle available when
225
228
  no image store exists.
226
229
  - `src/react-shell/review/settings.modal.tsx` and
227
- `src/react-shell/hooks/use.review.settings.ts`: stop treating browser
228
- `figma-token` as the new API token. Keep it only for legacy host helpers if
229
- needed.
230
+ `src/react-shell/hooks/use.review.settings.ts`: keep `figma-token` as a
231
+ browser-local review fallback for users who cannot set server env.
230
232
  - `src/vite.ts`: add the dev local metadata endpoint.
231
233
  - `dev/vite.config.ts` and `dev/src/main.tsx`: wire the local MVP for dogfood.
232
234
 
@@ -320,8 +322,9 @@ For the following implementation todos:
320
322
 
321
323
  - Adapter missing: `/review` still works with current keyboard shortcut/Figma
322
324
  helper fallback.
323
- - Adapter configured and `FIGMA_TOKEN` missing: adding/rendering a Figma image
324
- returns a clear error without breaking the rest of the review shell.
325
+ - Adapter configured and both `FIGMA_TOKEN` plus Settings `figma-token` are
326
+ missing: adding/rendering a Figma image returns a clear error without breaking
327
+ the rest of the review shell.
325
328
  - Adapter configured and `FIGMA_TOKEN` present: a Figma node copy link can be
326
329
  added, listed, deleted, reordered, and survives dev server restart.
327
330
  - Existing QA adapters still pass `pnpm typecheck` and `pnpm build`.