@designfever/web-review-kit 0.6.0 → 0.7.1
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/.env.sample +32 -0
- package/README.md +35 -10
- package/dist/chunk-2ZLU5FTD.js +602 -0
- package/dist/chunk-2ZLU5FTD.js.map +1 -0
- package/dist/{chunk-IN36JHEU.js → chunk-RPVLRULC.js} +504 -143
- package/dist/chunk-RPVLRULC.js.map +1 -0
- package/dist/image.types-BmzkFSPX.d.cts +71 -0
- package/dist/image.types-BmzkFSPX.d.ts +71 -0
- package/dist/index.cjs +1037 -144
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +29 -3
- package/dist/index.d.ts +29 -3
- package/dist/index.js +50 -3
- package/dist/index.js.map +1 -1
- package/dist/react-shell.cjs +10339 -5168
- package/dist/react-shell.cjs.map +1 -1
- package/dist/react-shell.d.cts +39 -4
- package/dist/react-shell.d.ts +39 -4
- package/dist/react-shell.js +9687 -4856
- package/dist/react-shell.js.map +1 -1
- package/dist/token-Dt-ZH-YO.d.cts +88 -0
- package/dist/token-nJXPPdYX.d.ts +88 -0
- package/dist/{types-DFHHVRBc.d.cts → types-DT9Z66mV.d.cts} +13 -1
- package/dist/{types-DFHHVRBc.d.ts → types-DT9Z66mV.d.ts} +13 -1
- package/dist/vite.cjs +1144 -5
- package/dist/vite.cjs.map +1 -1
- package/dist/vite.d.cts +45 -1
- package/dist/vite.d.ts +45 -1
- package/dist/vite.js +829 -5
- package/dist/vite.js.map +1 -1
- package/docs/README.md +13 -7
- package/docs/adapters.md +128 -0
- package/docs/adaptor.sample.ts +13 -1
- package/docs/architecture.md +2 -1
- package/docs/code-review-0.6.0.md +232 -0
- package/docs/db-setup.md +4 -2
- package/docs/figma-image-mvp-0.7.0.md +330 -0
- package/docs/figma-overlay.md +11 -4
- package/docs/installation.md +42 -7
- package/docs/release-notes-0.7.0.md +132 -0
- package/docs/release-notes-0.7.1.md +34 -0
- package/package.json +6 -2
- package/dist/chunk-IN36JHEU.js.map +0 -1
package/dist/vite.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/vite.ts"],"sourcesContent":["import type { Plugin, ResolvedConfig } from 'vite';\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\nexport const reviewSourceLocator = (\n options: ReviewSourceLocatorOptions = {}\n): Plugin => {\n let runtimeOptions = createRuntimeOptions(options);\n\n return {\n name: 'df-web-review-kit-source-locator',\n enforce: 'pre',\n configResolved(config) {\n runtimeOptions = createRuntimeOptions(options, config);\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 };\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 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 },\n transform(code, id) {\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 (include.length > 0 && !include.some((m) => matchesPath(m, file, relativeFile)))\n return null;\n if (exclude.some((m) => matchesPath(m, file, relativeFile))) return null;\n\n const sourceFile = (options.filePath ?? 'relative') === 'absolute' ? file : relativeFile;\n const regex = new RegExp(componentSource, 'g');\n let changed = false;\n const out = code.replace(\n regex,\n (_match, pre: string, comp: string, quote: string, name: string, offset: number) => {\n const line = code.slice(0, offset + pre.length).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 ? { 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":";AAgCA,IAAM,6BACJ;AAEK,IAAM,sBAAsB,CACjC,UAAsC,CAAC,MAC5B;AACX,MAAI,iBAAiB,qBAAqB,OAAO;AAEjD,SAAO;AAAA,IACL,MAAM;AAAA,IACN,SAAS;AAAA,IACT,eAAe,QAAQ;AACrB,uBAAiB,qBAAqB,SAAS,MAAM;AAAA,IACvD;AAAA,IACA,UAAU,IAAI,UAAU;AACtB,UAAI,CAAC,eAAe,QAAS,QAAO;AACpC,UAAI,OAAO,wBAAyB,QAAO;AAC3C,UAAI,aAAa,2BAA4B,QAAO;AAEpD,aAAO;AAAA,IACT;AAAA,IACA,KAAK,IAAI;AACP,UAAI,OAAO,2BAA4B,QAAO;AAC9C,aAAO,oBAAoB,cAAc;AAAA,IAC3C;AAAA,EACF;AACF;AAkBO,IAAM,oBAAoB,CAC/B,UAAoC,CAAC,MAC1B;AACX,MAAI,OAAO,cAAc,QAAQ,QAAQ,EAAE;AAC3C,MAAI,UAAU,QAAQ,WAAW;AACjC,QAAM,WAAW,QAAQ,WAAW,CAAC,GAAG,IAAI,oBAAoB;AAChE,QAAM,WAAW,QAAQ,WAAW,CAAC,gBAAgB,MAAM,GAAG;AAAA,IAC5D;AAAA,EACF;AACA,QAAM,mBAAmB,QAAQ,oBAAoB;AACrD,QAAM,UAAU,QAAQ,iBAAiB;AACzC,QAAM,UAAU,QAAQ,iBAAiB;AAEzC,QAAM,kBAAkB,+CAA+C,iBAAiB,MAAM;AAE9F,SAAO;AAAA,IACL,MAAM;AAAA,IACN,SAAS;AAAA,IACT,eAAe,QAAQ;AACrB,aAAO,cAAc,QAAQ,QAAQ,OAAO,QAAQ,EAAE;AACtD,gBAAU,QAAQ,WAAW,OAAO,YAAY;AAAA,IAClD;AAAA,IACA,UAAU,MAAM,IAAI;AAClB,UAAI,CAAC,QAAS,QAAO;AACrB,YAAM,OAAO,cAAc,GAAG,MAAM,GAAG,EAAE,CAAC,CAAC;AAC3C,YAAM,eACJ,QAAQ,KAAK,WAAW,OAAO,GAAG,IAAI,KAAK,MAAM,KAAK,SAAS,CAAC,IAAI;AACtE,UAAI,QAAQ,SAAS,KAAK,CAAC,QAAQ,KAAK,CAAC,MAAM,YAAY,GAAG,MAAM,YAAY,CAAC;AAC/E,eAAO;AACT,UAAI,QAAQ,KAAK,CAAC,MAAM,YAAY,GAAG,MAAM,YAAY,CAAC,EAAG,QAAO;AAEpE,YAAM,cAAc,QAAQ,YAAY,gBAAgB,aAAa,OAAO;AAC5E,YAAM,QAAQ,IAAI,OAAO,iBAAiB,GAAG;AAC7C,UAAI,UAAU;AACd,YAAM,MAAM,KAAK;AAAA,QACf;AAAA,QACA,CAAC,QAAQ,KAAa,MAAc,OAAe,MAAc,WAAmB;AAClF,gBAAM,OAAO,KAAK,MAAM,GAAG,SAAS,IAAI,MAAM,EAAE,MAAM,IAAI,EAAE;AAC5D,oBAAU;AACV,iBAAO,GAAG,GAAG,GAAG,KAAK,UAAU,OAAO,CAAC,KAAK,KAAK,UAAU,UAAU,CAAC,KAAK,KAAK,UAAU,OAAO,CAAC,KAAK,IAAI,KAAK,IAAI,GAAG,KAAK,GAAG,IAAI,GAAG,KAAK;AAAA,QAC7I;AAAA,MACF;AAEA,aAAO,UAAU,EAAE,MAAM,KAAK,KAAK,KAAK,IAAI;AAAA,IAC9C;AAAA,EACF;AACF;AAEA,SAAS,YACP,SACA,cACA,cACA;AACA,MAAI,QAAQ,SAAS,SAAS;AAC5B,UAAM,QAAQ,IAAI,OAAO,QAAQ,OAAO,QAAQ,KAAK;AACrD,WAAO,MAAM,KAAK,YAAY,KAAK,MAAM,KAAK,YAAY;AAAA,EAC5D;AACA,QAAM,SAAS,QAAQ,MAAM,WAAW,GAAG,IAAI,eAAe;AAC9D,SAAO,WAAW,QAAQ,SAAS,OAAO,WAAW,QAAQ,QAAQ,GAAG,KAAK,OAAO,SAAS,MAAM,QAAQ,KAAK;AAClH;AAEA,SAAS,qBACP,SACA,QACgB;AAChB,QAAM,mBAAmB,QAAQ,mBAAmB,mBAAmB;AAAA,IACrE;AAAA,IACA;AAAA,EACF;AACA,QAAM,OAAO,cAAc,QAAQ,QAAQ,QAAQ,QAAQ,EAAE;AAC7D,QAAM,UAAU,QAAQ,WAAY,QAAQ,YAAY;AAExD,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,UAAU,QAAQ,WAAW,CAAC,GAAG,IAAI,oBAAoB;AAAA,IACzD,UAAU,QAAQ,WAAW,CAAC,gBAAgB,MAAM,GAAG;AAAA,MACrD;AAAA,IACF;AAAA,IACA,UAAU,QAAQ,YAAY;AAAA,IAC9B,MAAM,QAAQ,QAAQ;AAAA,IACtB,QAAQ,QAAQ,UAAU;AAAA,IAC1B,eAAe,GAAG,eAAe;AAAA,IACjC,eAAe,GAAG,eAAe;AAAA,IACjC,iBAAiB,GAAG,eAAe;AAAA,EACrC;AACF;AAEA,SAAS,qBAAqB,SAA+C;AAC3E,MAAI,mBAAmB,QAAQ;AAC7B,WAAO,EAAE,MAAM,SAAS,OAAO,QAAQ,QAAQ,OAAO,QAAQ,MAAM;AAAA,EACtE;AAEA,SAAO,EAAE,MAAM,QAAQ,OAAO,cAAc,OAAO,EAAE,QAAQ,SAAS,EAAE,EAAE;AAC5E;AAEA,SAAS,cAAc,OAAe;AACpC,SAAO,MAAM,QAAQ,OAAO,GAAG,EAAE,QAAQ,QAAQ,EAAE;AACrD;AAEA,SAAS,oBAAoB,SAAyB;AACpD,SAAO;AAAA;AAAA;AAAA,kBAGS,KAAK,UAAU,OAAO,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAmGzC;","names":[]}
|
|
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
|
@@ -5,22 +5,28 @@ Public docs are intentionally small. Keep implementation history, handoff notes,
|
|
|
5
5
|
## Read This Order
|
|
6
6
|
|
|
7
7
|
1. [Installation](installation.md)
|
|
8
|
-
2. [
|
|
9
|
-
3. [
|
|
10
|
-
4. [
|
|
11
|
-
5. [
|
|
12
|
-
6. [
|
|
13
|
-
7. [
|
|
8
|
+
2. [Host env sample](../.env.sample)
|
|
9
|
+
3. [Adapter boundaries](adapters.md)
|
|
10
|
+
4. [Custom adapter sample](adaptor.sample.ts)
|
|
11
|
+
5. [DB setup](db-setup.md)
|
|
12
|
+
6. [Architecture and runtime logic](architecture.md)
|
|
13
|
+
7. [Figma overlay](figma-overlay.md)
|
|
14
|
+
8. [Grid overlay](grid-overlay.md)
|
|
15
|
+
9. [Release notes 0.7.1](release-notes-0.7.1.md)
|
|
16
|
+
10. [Release notes 0.7.0](release-notes-0.7.0.md)
|
|
14
17
|
|
|
15
18
|
## Document Roles
|
|
16
19
|
|
|
17
20
|
- `installation.md`: install the npm package, create the `/review` route, wire adapters, and run checks.
|
|
21
|
+
- `../.env.sample`: copyable host project env template for local, Supabase, and source opening.
|
|
22
|
+
- `adapters.md`: QA adapter and Figma image store responsibility boundary.
|
|
18
23
|
- `adaptor.sample.ts`: copyable starting point for host-owned remote adapters.
|
|
19
24
|
- `db-setup.md`: optional Supabase review item table/RPC/RLS/presence setup.
|
|
20
25
|
- `architecture.md`: core/runtime, React shell, coordinate, anchor, and feature boundary notes.
|
|
21
26
|
- `figma-overlay.md`: host requirements for the Figma overlay toggle.
|
|
22
27
|
- `grid-overlay.md`: host requirements for the grid/helper overlay toggle.
|
|
23
|
-
- `release-notes-0.
|
|
28
|
+
- `release-notes-0.7.1.md`: latest release changes, host notes, and validation scope.
|
|
29
|
+
- `release-notes-0.7.0.md`: previous release changes and validation scope.
|
|
24
30
|
|
|
25
31
|
## Boundary
|
|
26
32
|
|
package/docs/adapters.md
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# Adapter Boundaries
|
|
2
|
+
|
|
3
|
+
`df-web-review-kit` has two integration surfaces that look similar but should stay separate:
|
|
4
|
+
|
|
5
|
+
- QA adapter: persists review items such as note, area, DOM QA, status, title, and assignee.
|
|
6
|
+
- Figma image store: persists Figma reference image metadata and cached image assets.
|
|
7
|
+
|
|
8
|
+
Use `adapter` for both concepts in conversation only when the context is clear. In code/docs, prefer the explicit names `ReviewShellAdapter` and `ReviewFigmaImageStore`.
|
|
9
|
+
|
|
10
|
+
> Note: `docs/adaptor.sample.ts` keeps the old filename spelling for compatibility with existing links. The public API name is still `adapter`.
|
|
11
|
+
|
|
12
|
+
## Responsibility Matrix
|
|
13
|
+
|
|
14
|
+
| Concern | QA adapter | Figma image store |
|
|
15
|
+
| --- | --- | --- |
|
|
16
|
+
| Public type | `ReviewShellAdapter`, `WebReviewKitAdapter` | `ReviewFigmaImageStore` |
|
|
17
|
+
| Mount option | `mountReviewShell({ adapters })` | `mountReviewShell({ figmaImages: { store } })` |
|
|
18
|
+
| Vite plugin | Not required | `reviewFigmaImageStore()` for dev/local cache |
|
|
19
|
+
| Data | `ReviewItem` records | `ReviewFigmaImage` records and image assets |
|
|
20
|
+
| Main actions | `get`, `list`, `create`, `update`, `remove` | `listImages`, `addImage`, `updateImage`, `reorderImages`, `deleteImage` |
|
|
21
|
+
| UI fields | `fields.title`, `statusOptions`, `assigneeOptions` | image label, order, opacity/layer controls |
|
|
22
|
+
| Token/secrets | Host backend decides | `FIGMA_TOKEN` server env first, Settings `figma-token` request fallback |
|
|
23
|
+
| Typical storage | localStorage, Supabase, df-sheet, custom QA API | `.df-review/figma-images.json`, `.df-review/figma-assets/`, future asset backend |
|
|
24
|
+
|
|
25
|
+
## QA Adapter
|
|
26
|
+
|
|
27
|
+
QA adapters own review item persistence. They should answer one question:
|
|
28
|
+
|
|
29
|
+
> Where do QA items for this project/page live?
|
|
30
|
+
|
|
31
|
+
The shell reads and writes QA through `adapters`.
|
|
32
|
+
|
|
33
|
+
```tsx
|
|
34
|
+
mountReviewShell({
|
|
35
|
+
projectId,
|
|
36
|
+
pages,
|
|
37
|
+
adapters: [
|
|
38
|
+
{
|
|
39
|
+
label: 'df-sheet',
|
|
40
|
+
get: (id) => dfSheet.get(id),
|
|
41
|
+
list: (query) => dfSheet.list(query),
|
|
42
|
+
create: (item) => dfSheet.create(item),
|
|
43
|
+
fields: { title: true },
|
|
44
|
+
statusOptions,
|
|
45
|
+
updateStatus: ({ id, status }) => dfSheet.updateStatus(id, status),
|
|
46
|
+
assigneeTitle: '담당자',
|
|
47
|
+
assigneeOptions,
|
|
48
|
+
updateAssignee: ({ id, assigneeId, assigneeName }) =>
|
|
49
|
+
dfSheet.updateAssignee(id, { assigneeId, assigneeName }),
|
|
50
|
+
remove: (id) => dfSheet.remove(id),
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
});
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Guidelines:
|
|
57
|
+
|
|
58
|
+
- Keep backend-specific mapping inside the host adapter.
|
|
59
|
+
- `fields.title` only controls whether title UI appears. Omit it for comment-only QA.
|
|
60
|
+
- `assigneeOptions` and `updateAssignee` are optional. If omitted, assignee UI should not become a required workflow.
|
|
61
|
+
- `statusOptions` can be project-specific, but status remains a QA item field.
|
|
62
|
+
- Slow remote adapters should return promises normally; the shell owns loading and pending UI.
|
|
63
|
+
|
|
64
|
+
## Figma Image Store
|
|
65
|
+
|
|
66
|
+
Figma image stores own reference image metadata and assets. They should answer one question:
|
|
67
|
+
|
|
68
|
+
> Which Figma reference images are attached to this route/viewport?
|
|
69
|
+
|
|
70
|
+
The shell reads and writes Figma image data through `figmaImages.store`.
|
|
71
|
+
|
|
72
|
+
```tsx
|
|
73
|
+
import { createReviewFigmaImageStoreClient } from '@designfever/web-review-kit';
|
|
74
|
+
|
|
75
|
+
const figmaImageStore = createReviewFigmaImageStoreClient();
|
|
76
|
+
|
|
77
|
+
mountReviewShell({
|
|
78
|
+
projectId,
|
|
79
|
+
pages,
|
|
80
|
+
adapters,
|
|
81
|
+
figmaImages: {
|
|
82
|
+
store: figmaImageStore,
|
|
83
|
+
imageFormat: 'webp',
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
For local/dev caching, the host Vite config wires the server endpoint:
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
import { defineConfig } from 'vite';
|
|
92
|
+
import { reviewFigmaImageStore } from '@designfever/web-review-kit/vite';
|
|
93
|
+
|
|
94
|
+
export default defineConfig({
|
|
95
|
+
plugins: [
|
|
96
|
+
reviewFigmaImageStore({
|
|
97
|
+
projectId: 'my-project',
|
|
98
|
+
dataFile: '.df-review/figma-images.json',
|
|
99
|
+
}),
|
|
100
|
+
],
|
|
101
|
+
});
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Guidelines:
|
|
105
|
+
|
|
106
|
+
- Do not put Figma image metadata into QA adapter metadata.
|
|
107
|
+
- Do not use `VITE_FIGMA_TOKEN` for server rendering. Keep shared tokens in
|
|
108
|
+
`FIGMA_TOKEN`; use Settings `figma-token` only as a browser-local review
|
|
109
|
+
fallback.
|
|
110
|
+
- If no `figmaImages.store` is configured, QA should still work.
|
|
111
|
+
- If Figma image storage moves remote later, implement a `ReviewFigmaImageStore`-shaped client instead of extending `ReviewShellAdapter`.
|
|
112
|
+
|
|
113
|
+
## Host Wiring Rule
|
|
114
|
+
|
|
115
|
+
Host projects can use both surfaces together, but they should be configured independently.
|
|
116
|
+
|
|
117
|
+
```tsx
|
|
118
|
+
mountReviewShell({
|
|
119
|
+
projectId,
|
|
120
|
+
pages,
|
|
121
|
+
adapters, // QA item persistence
|
|
122
|
+
figmaImages: {
|
|
123
|
+
store: figmaImageStore, // Figma reference image persistence
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
This keeps QA workflows, release notes, and future backend migrations from coupling unrelated data models.
|
package/docs/adaptor.sample.ts
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
type WebReviewKitAdapter,
|
|
8
8
|
} from '@designfever/web-review-kit';
|
|
9
9
|
import type {
|
|
10
|
+
ReviewShellAssigneeOption,
|
|
10
11
|
ReviewShellAdapter,
|
|
11
12
|
} from '@designfever/web-review-kit/react-shell';
|
|
12
13
|
|
|
@@ -15,6 +16,9 @@ type RemoteReviewAdapterOptions = {
|
|
|
15
16
|
projectId: string;
|
|
16
17
|
source?: ReviewSource;
|
|
17
18
|
token?: string;
|
|
19
|
+
fields?: ReviewShellAdapter['fields'];
|
|
20
|
+
assigneeTitle?: string;
|
|
21
|
+
assigneeOptions?: ReviewShellAssigneeOption[];
|
|
18
22
|
};
|
|
19
23
|
|
|
20
24
|
type RemoteReviewItemResponse = ReviewItem | { item: ReviewItem };
|
|
@@ -98,8 +102,11 @@ export function createRemoteReviewAdapter(
|
|
|
98
102
|
* label becomes the URL source, for example /review?source=remote.
|
|
99
103
|
* create controls whether the shell can write to this adapter.
|
|
100
104
|
* canWrite can be true or limited to ['dom', 'note', 'area'].
|
|
101
|
-
* update enables comment edits in the QA panel.
|
|
105
|
+
* update enables title/comment edits in the QA panel.
|
|
106
|
+
* fields enables optional UI fields such as title. Omit title to keep comment-only UI.
|
|
102
107
|
* updateStatus drives the status buttons in the QA panel.
|
|
108
|
+
* assigneeOptions + updateAssignee drive the assignee select next to status.
|
|
109
|
+
* assigneeTitle customizes the empty option/field label. Defaults to "Assignee".
|
|
103
110
|
* remove enables delete actions for this source.
|
|
104
111
|
*/
|
|
105
112
|
export function createRemoteReviewShellAdapter(
|
|
@@ -114,9 +121,14 @@ export function createRemoteReviewShellAdapter(
|
|
|
114
121
|
create: (item) => adapter.create(item),
|
|
115
122
|
update: (id, patch) => adapter.update(id, patch),
|
|
116
123
|
canWrite: true,
|
|
124
|
+
fields: options.fields,
|
|
117
125
|
statusOptions: REVIEW_WORKFLOW_STATUS_OPTIONS,
|
|
118
126
|
updateStatus: ({ id, status }) =>
|
|
119
127
|
adapter.update(id, { status: normalizeRemoteStatus(status) }),
|
|
128
|
+
assigneeTitle: options.assigneeTitle,
|
|
129
|
+
assigneeOptions: options.assigneeOptions ?? [],
|
|
130
|
+
updateAssignee: ({ id, assigneeId, assigneeName }) =>
|
|
131
|
+
adapter.update(id, { assigneeId, assigneeName }),
|
|
120
132
|
remove: (id) => adapter.remove(id),
|
|
121
133
|
};
|
|
122
134
|
}
|
package/docs/architecture.md
CHANGED
|
@@ -41,6 +41,7 @@ When the React shell provides a composer host, core docks DOM/area draft compose
|
|
|
41
41
|
|
|
42
42
|
- `web.review.kit.app.ts`: controller lifecycle, state transitions, adapter calls, item creation, restore flow.
|
|
43
43
|
- `web.review.kit.view.ts`: vanilla DOM renderer for core overlay UI.
|
|
44
|
+
- `draft.metrics.ts`: pure geometry for draft adjustment (nudge/scale) previews, kept out of the renderer.
|
|
44
45
|
- `dom.anchor.ts`: selector candidate generation, anchor rebinding, text fingerprint matching.
|
|
45
46
|
- `geometry.ts`: target-space and host-space coordinate conversion.
|
|
46
47
|
- `review/item.ts`: marker, selection, highlight, and fallback resolution.
|
|
@@ -83,7 +84,7 @@ Core never owns persistence storage directly. It only calls the configured `WebR
|
|
|
83
84
|
- `update`
|
|
84
85
|
- `remove`
|
|
85
86
|
|
|
86
|
-
The default local adapter is for draft/local review work. Supabase is optional host wiring, not a required backend.
|
|
87
|
+
The default local adapter is for draft/local review work. Supabase is optional host wiring, not a required backend. Figma reference images use a separate `ReviewFigmaImageStore`; see [Adapter boundaries](adapters.md).
|
|
87
88
|
|
|
88
89
|
## React Shell Boundary
|
|
89
90
|
|