@designfever/web-review-kit 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -20,6 +20,8 @@ interface DomAnchorCandidate {
20
20
  interface DomSourceHint {
21
21
  component?: string;
22
22
  file?: string;
23
+ line?: string;
24
+ column?: string;
23
25
  sectionId?: string;
24
26
  sectionIndex?: string;
25
27
  }
@@ -20,6 +20,8 @@ interface DomAnchorCandidate {
20
20
  interface DomSourceHint {
21
21
  component?: string;
22
22
  file?: string;
23
+ line?: string;
24
+ column?: string;
23
25
  sectionId?: string;
24
26
  sectionIndex?: string;
25
27
  }
package/dist/vite.cjs ADDED
@@ -0,0 +1,186 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/vite.ts
21
+ var vite_exports = {};
22
+ __export(vite_exports, {
23
+ reviewSourceLocator: () => reviewSourceLocator
24
+ });
25
+ module.exports = __toCommonJS(vite_exports);
26
+ var VIRTUAL_JSX_DEV_RUNTIME_ID = "\0@designfever/web-review-kit/source-locator/jsx-dev-runtime";
27
+ var reviewSourceLocator = (options = {}) => {
28
+ let runtimeOptions = createRuntimeOptions(options);
29
+ return {
30
+ name: "df-web-review-kit-source-locator",
31
+ enforce: "pre",
32
+ configResolved(config) {
33
+ runtimeOptions = createRuntimeOptions(options, config);
34
+ },
35
+ resolveId(id, importer) {
36
+ if (!runtimeOptions.enabled) return null;
37
+ if (id !== "react/jsx-dev-runtime") return null;
38
+ if (importer === VIRTUAL_JSX_DEV_RUNTIME_ID) return null;
39
+ return VIRTUAL_JSX_DEV_RUNTIME_ID;
40
+ },
41
+ load(id) {
42
+ if (id !== VIRTUAL_JSX_DEV_RUNTIME_ID) return null;
43
+ return createJsxDevRuntime(runtimeOptions);
44
+ }
45
+ };
46
+ };
47
+ function createRuntimeOptions(options, config) {
48
+ const attributePrefix = (options.attributePrefix ?? "data-wrk-source").replace(
49
+ /-+$/,
50
+ ""
51
+ );
52
+ const root = normalizePath(options.root ?? config?.root ?? "");
53
+ const enabled = options.enabled ?? config?.command === "serve";
54
+ return {
55
+ enabled,
56
+ root,
57
+ include: (options.include ?? []).map(createRuntimeMatcher),
58
+ exclude: (options.exclude ?? ["node_modules", "dist"]).map(
59
+ createRuntimeMatcher
60
+ ),
61
+ filePath: options.filePath ?? "relative",
62
+ line: options.line ?? true,
63
+ column: options.column ?? true,
64
+ fileAttribute: `${attributePrefix}-file`,
65
+ lineAttribute: `${attributePrefix}-line`,
66
+ columnAttribute: `${attributePrefix}-column`
67
+ };
68
+ }
69
+ function createRuntimeMatcher(pattern) {
70
+ if (pattern instanceof RegExp) {
71
+ return { type: "regex", value: pattern.source, flags: pattern.flags };
72
+ }
73
+ return { type: "path", value: normalizePath(pattern).replace(/^\.\//, "") };
74
+ }
75
+ function normalizePath(value) {
76
+ return value.replace(/\\/g, "/").replace(/\/+$/, "");
77
+ }
78
+ function createJsxDevRuntime(options) {
79
+ return `
80
+ import { Fragment, jsxDEV as baseJsxDEV } from 'react/jsx-dev-runtime';
81
+
82
+ const OPTIONS = ${JSON.stringify(options)};
83
+
84
+ export { Fragment };
85
+
86
+ export function jsxDEV(type, props, key, isStaticChildren, source, self) {
87
+ return baseJsxDEV(
88
+ type,
89
+ injectSourceProps(type, props, source),
90
+ key,
91
+ isStaticChildren,
92
+ source,
93
+ self
94
+ );
95
+ }
96
+
97
+ function injectSourceProps(type, props, source) {
98
+ if (typeof type !== 'string') return props;
99
+ if (!source || typeof source.fileName !== 'string') return props;
100
+
101
+ const sourceFile = getSourceFile(source.fileName);
102
+ if (!sourceFile) return props;
103
+
104
+ const nextProps = props ? { ...props } : {};
105
+ if (nextProps[OPTIONS.fileAttribute] == null) {
106
+ nextProps[OPTIONS.fileAttribute] = sourceFile;
107
+ }
108
+ if (OPTIONS.line && source.lineNumber != null && nextProps[OPTIONS.lineAttribute] == null) {
109
+ nextProps[OPTIONS.lineAttribute] = String(source.lineNumber);
110
+ }
111
+ if (OPTIONS.column && source.columnNumber != null && nextProps[OPTIONS.columnAttribute] == null) {
112
+ nextProps[OPTIONS.columnAttribute] = String(source.columnNumber);
113
+ }
114
+
115
+ return nextProps;
116
+ }
117
+
118
+ function getSourceFile(fileName) {
119
+ const absoluteFile = normalizePath(fileName);
120
+ const relativeFile = getRelativeFile(absoluteFile);
121
+
122
+ if (OPTIONS.include.length > 0 && !matchesAny(OPTIONS.include, absoluteFile, relativeFile)) {
123
+ return null;
124
+ }
125
+ if (matchesAny(OPTIONS.exclude, absoluteFile, relativeFile)) return null;
126
+
127
+ return OPTIONS.filePath === 'absolute' ? absoluteFile : relativeFile;
128
+ }
129
+
130
+ function getRelativeFile(absoluteFile) {
131
+ if (!OPTIONS.root) return absoluteFile;
132
+ if (absoluteFile === OPTIONS.root) return '';
133
+ if (absoluteFile.startsWith(OPTIONS.root + '/')) {
134
+ return absoluteFile.slice(OPTIONS.root.length + 1);
135
+ }
136
+
137
+ return absoluteFile;
138
+ }
139
+
140
+ function matchesAny(patterns, absoluteFile, relativeFile) {
141
+ return patterns.some((pattern) =>
142
+ matchesPattern(pattern, absoluteFile, relativeFile)
143
+ );
144
+ }
145
+
146
+ function matchesPattern(pattern, absoluteFile, relativeFile) {
147
+ if (pattern.type === 'regex') {
148
+ const regex = new RegExp(pattern.value, pattern.flags);
149
+ return regex.test(absoluteFile) || regex.test(relativeFile);
150
+ }
151
+
152
+ const value = pattern.value;
153
+ const target = isAbsolutePattern(value) ? absoluteFile : relativeFile;
154
+ if (!value.includes('*')) {
155
+ return target === value || target.startsWith(value + '/');
156
+ }
157
+
158
+ return globToRegExp(value).test(target);
159
+ }
160
+
161
+ function isAbsolutePattern(value) {
162
+ return value.startsWith('/') || /^[a-zA-Z]:\\//.test(value);
163
+ }
164
+
165
+ function globToRegExp(value) {
166
+ const source = escapeRegExp(value)
167
+ .replace(/\\\\\\*\\\\\\*/g, '.*')
168
+ .replace(/\\\\\\*/g, '[^/]*');
169
+
170
+ return new RegExp('^' + source + '$');
171
+ }
172
+
173
+ function escapeRegExp(value) {
174
+ return value.replace(/[|\\\\{}()[\\]^$+*?.]/g, '\\\\$&');
175
+ }
176
+
177
+ function normalizePath(value) {
178
+ return value.replace(/\\\\/g, '/').replace(/\\/+$/, '');
179
+ }
180
+ `;
181
+ }
182
+ // Annotate the CommonJS export names for ESM import in node:
183
+ 0 && (module.exports = {
184
+ reviewSourceLocator
185
+ });
186
+ //# sourceMappingURL=vite.cjs.map
@@ -0,0 +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\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":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;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;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":[]}
@@ -0,0 +1,16 @@
1
+ import { Plugin } from 'vite';
2
+
3
+ type SourceLocatorPattern = string | RegExp;
4
+ interface ReviewSourceLocatorOptions {
5
+ enabled?: boolean;
6
+ root?: string;
7
+ include?: readonly SourceLocatorPattern[];
8
+ exclude?: readonly SourceLocatorPattern[];
9
+ filePath?: 'relative' | 'absolute';
10
+ line?: boolean;
11
+ column?: boolean;
12
+ attributePrefix?: string;
13
+ }
14
+ declare const reviewSourceLocator: (options?: ReviewSourceLocatorOptions) => Plugin;
15
+
16
+ export { type ReviewSourceLocatorOptions, reviewSourceLocator };
package/dist/vite.d.ts ADDED
@@ -0,0 +1,16 @@
1
+ import { Plugin } from 'vite';
2
+
3
+ type SourceLocatorPattern = string | RegExp;
4
+ interface ReviewSourceLocatorOptions {
5
+ enabled?: boolean;
6
+ root?: string;
7
+ include?: readonly SourceLocatorPattern[];
8
+ exclude?: readonly SourceLocatorPattern[];
9
+ filePath?: 'relative' | 'absolute';
10
+ line?: boolean;
11
+ column?: boolean;
12
+ attributePrefix?: string;
13
+ }
14
+ declare const reviewSourceLocator: (options?: ReviewSourceLocatorOptions) => Plugin;
15
+
16
+ export { type ReviewSourceLocatorOptions, reviewSourceLocator };
package/dist/vite.js ADDED
@@ -0,0 +1,161 @@
1
+ // src/vite.ts
2
+ var VIRTUAL_JSX_DEV_RUNTIME_ID = "\0@designfever/web-review-kit/source-locator/jsx-dev-runtime";
3
+ var reviewSourceLocator = (options = {}) => {
4
+ let runtimeOptions = createRuntimeOptions(options);
5
+ return {
6
+ name: "df-web-review-kit-source-locator",
7
+ enforce: "pre",
8
+ configResolved(config) {
9
+ runtimeOptions = createRuntimeOptions(options, config);
10
+ },
11
+ resolveId(id, importer) {
12
+ if (!runtimeOptions.enabled) return null;
13
+ if (id !== "react/jsx-dev-runtime") return null;
14
+ if (importer === VIRTUAL_JSX_DEV_RUNTIME_ID) return null;
15
+ return VIRTUAL_JSX_DEV_RUNTIME_ID;
16
+ },
17
+ load(id) {
18
+ if (id !== VIRTUAL_JSX_DEV_RUNTIME_ID) return null;
19
+ return createJsxDevRuntime(runtimeOptions);
20
+ }
21
+ };
22
+ };
23
+ function createRuntimeOptions(options, config) {
24
+ const attributePrefix = (options.attributePrefix ?? "data-wrk-source").replace(
25
+ /-+$/,
26
+ ""
27
+ );
28
+ const root = normalizePath(options.root ?? config?.root ?? "");
29
+ const enabled = options.enabled ?? config?.command === "serve";
30
+ return {
31
+ enabled,
32
+ root,
33
+ include: (options.include ?? []).map(createRuntimeMatcher),
34
+ exclude: (options.exclude ?? ["node_modules", "dist"]).map(
35
+ createRuntimeMatcher
36
+ ),
37
+ filePath: options.filePath ?? "relative",
38
+ line: options.line ?? true,
39
+ column: options.column ?? true,
40
+ fileAttribute: `${attributePrefix}-file`,
41
+ lineAttribute: `${attributePrefix}-line`,
42
+ columnAttribute: `${attributePrefix}-column`
43
+ };
44
+ }
45
+ function createRuntimeMatcher(pattern) {
46
+ if (pattern instanceof RegExp) {
47
+ return { type: "regex", value: pattern.source, flags: pattern.flags };
48
+ }
49
+ return { type: "path", value: normalizePath(pattern).replace(/^\.\//, "") };
50
+ }
51
+ function normalizePath(value) {
52
+ return value.replace(/\\/g, "/").replace(/\/+$/, "");
53
+ }
54
+ function createJsxDevRuntime(options) {
55
+ return `
56
+ import { Fragment, jsxDEV as baseJsxDEV } from 'react/jsx-dev-runtime';
57
+
58
+ const OPTIONS = ${JSON.stringify(options)};
59
+
60
+ export { Fragment };
61
+
62
+ export function jsxDEV(type, props, key, isStaticChildren, source, self) {
63
+ return baseJsxDEV(
64
+ type,
65
+ injectSourceProps(type, props, source),
66
+ key,
67
+ isStaticChildren,
68
+ source,
69
+ self
70
+ );
71
+ }
72
+
73
+ function injectSourceProps(type, props, source) {
74
+ if (typeof type !== 'string') return props;
75
+ if (!source || typeof source.fileName !== 'string') return props;
76
+
77
+ const sourceFile = getSourceFile(source.fileName);
78
+ if (!sourceFile) return props;
79
+
80
+ const nextProps = props ? { ...props } : {};
81
+ if (nextProps[OPTIONS.fileAttribute] == null) {
82
+ nextProps[OPTIONS.fileAttribute] = sourceFile;
83
+ }
84
+ if (OPTIONS.line && source.lineNumber != null && nextProps[OPTIONS.lineAttribute] == null) {
85
+ nextProps[OPTIONS.lineAttribute] = String(source.lineNumber);
86
+ }
87
+ if (OPTIONS.column && source.columnNumber != null && nextProps[OPTIONS.columnAttribute] == null) {
88
+ nextProps[OPTIONS.columnAttribute] = String(source.columnNumber);
89
+ }
90
+
91
+ return nextProps;
92
+ }
93
+
94
+ function getSourceFile(fileName) {
95
+ const absoluteFile = normalizePath(fileName);
96
+ const relativeFile = getRelativeFile(absoluteFile);
97
+
98
+ if (OPTIONS.include.length > 0 && !matchesAny(OPTIONS.include, absoluteFile, relativeFile)) {
99
+ return null;
100
+ }
101
+ if (matchesAny(OPTIONS.exclude, absoluteFile, relativeFile)) return null;
102
+
103
+ return OPTIONS.filePath === 'absolute' ? absoluteFile : relativeFile;
104
+ }
105
+
106
+ function getRelativeFile(absoluteFile) {
107
+ if (!OPTIONS.root) return absoluteFile;
108
+ if (absoluteFile === OPTIONS.root) return '';
109
+ if (absoluteFile.startsWith(OPTIONS.root + '/')) {
110
+ return absoluteFile.slice(OPTIONS.root.length + 1);
111
+ }
112
+
113
+ return absoluteFile;
114
+ }
115
+
116
+ function matchesAny(patterns, absoluteFile, relativeFile) {
117
+ return patterns.some((pattern) =>
118
+ matchesPattern(pattern, absoluteFile, relativeFile)
119
+ );
120
+ }
121
+
122
+ function matchesPattern(pattern, absoluteFile, relativeFile) {
123
+ if (pattern.type === 'regex') {
124
+ const regex = new RegExp(pattern.value, pattern.flags);
125
+ return regex.test(absoluteFile) || regex.test(relativeFile);
126
+ }
127
+
128
+ const value = pattern.value;
129
+ const target = isAbsolutePattern(value) ? absoluteFile : relativeFile;
130
+ if (!value.includes('*')) {
131
+ return target === value || target.startsWith(value + '/');
132
+ }
133
+
134
+ return globToRegExp(value).test(target);
135
+ }
136
+
137
+ function isAbsolutePattern(value) {
138
+ return value.startsWith('/') || /^[a-zA-Z]:\\//.test(value);
139
+ }
140
+
141
+ function globToRegExp(value) {
142
+ const source = escapeRegExp(value)
143
+ .replace(/\\\\\\*\\\\\\*/g, '.*')
144
+ .replace(/\\\\\\*/g, '[^/]*');
145
+
146
+ return new RegExp('^' + source + '$');
147
+ }
148
+
149
+ function escapeRegExp(value) {
150
+ return value.replace(/[|\\\\{}()[\\]^$+*?.]/g, '\\\\$&');
151
+ }
152
+
153
+ function normalizePath(value) {
154
+ return value.replace(/\\\\/g, '/').replace(/\\/+$/, '');
155
+ }
156
+ `;
157
+ }
158
+ export {
159
+ reviewSourceLocator
160
+ };
161
+ //# sourceMappingURL=vite.js.map
@@ -0,0 +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\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;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":[]}
@@ -170,6 +170,39 @@ mountReviewShell({
170
170
 
171
171
  See [DB setup](db-setup.md) before enabling Supabase in a shared environment.
172
172
 
173
+ ## Source Locator
174
+
175
+ Host projects can opt into source file hints for local QA. The Vite plugin wraps React's dev JSX runtime and writes source attributes to rendered DOM nodes.
176
+
177
+ ```ts
178
+ import { defineConfig } from 'vite';
179
+ import { reviewSourceLocator } from '@designfever/web-review-kit/vite';
180
+
181
+ export default defineConfig({
182
+ plugins: [
183
+ reviewSourceLocator({
184
+ enabled: true,
185
+ include: ['src'],
186
+ filePath: 'absolute',
187
+ }),
188
+ ],
189
+ });
190
+ ```
191
+
192
+ Captured DOM nodes will include `data-wrk-source-file`, `data-wrk-source-line`, and `data-wrk-source-column`. In the review shell, `Option` + click inside the target iframe opens that source in VS Code. If the file path is absolute, it opens directly. If the plugin stores relative paths, pass `sourceRoot` when mounting the shell.
193
+
194
+ ```tsx
195
+ mountReviewShell({
196
+ projectId: REVIEW_PROJECT_ID,
197
+ pages,
198
+ adapters,
199
+ reviewPathPrefix: REVIEW_PATH_PREFIX,
200
+ sourceRoot: import.meta.env.VITE_REVIEW_SOURCE_ROOT,
201
+ });
202
+ ```
203
+
204
+ Use this only in dev/review builds. Source paths are written into the browser DOM and can be persisted with QA items.
205
+
173
206
  ## Custom Adapter
174
207
 
175
208
  If a team or host project owns its own QA backend, keep that adapter in the host project or in a separate package. Start from [adaptor.sample.ts](adaptor.sample.ts) and map its `WebReviewKitAdapter` methods to your backend API.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@designfever/web-review-kit",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "description": "Designfever web page review overlay toolkit.",
6
6
  "license": "UNLICENSED",
@@ -31,6 +31,11 @@
31
31
  "import": "./dist/react-shell.js",
32
32
  "require": "./dist/react-shell.cjs"
33
33
  },
34
+ "./vite": {
35
+ "types": "./dist/vite.d.ts",
36
+ "import": "./dist/vite.js",
37
+ "require": "./dist/vite.cjs"
38
+ },
34
39
  "./package.json": "./package.json"
35
40
  },
36
41
  "files": [
@@ -39,8 +44,8 @@
39
44
  "README.md"
40
45
  ],
41
46
  "scripts": {
42
- "build": "tsup src/index.ts src/react-shell.tsx --format esm,cjs --dts --sourcemap --clean --external react --external react-dom --external react/jsx-runtime",
43
- "dev": "tsup src/index.ts src/react-shell.tsx --format esm,cjs --dts --sourcemap --watch --external react --external react-dom --external react/jsx-runtime",
47
+ "build": "tsup src/index.ts src/react-shell.tsx src/vite.ts --format esm,cjs --dts --sourcemap --clean --external react --external react-dom --external react/jsx-runtime",
48
+ "dev": "tsup src/index.ts src/react-shell.tsx src/vite.ts --format esm,cjs --dts --sourcemap --watch --external react --external react-dom --external react/jsx-runtime",
44
49
  "dev:review": "vite --config dev/vite.config.ts",
45
50
  "build:dev": "vite build --config dev/vite.config.ts",
46
51
  "prepare": "pnpm build",
@@ -49,7 +54,13 @@
49
54
  },
50
55
  "peerDependencies": {
51
56
  "react": ">=18",
52
- "react-dom": ">=18"
57
+ "react-dom": ">=18",
58
+ "vite": ">=5"
59
+ },
60
+ "peerDependenciesMeta": {
61
+ "vite": {
62
+ "optional": true
63
+ }
53
64
  },
54
65
  "devDependencies": {
55
66
  "@supabase/supabase-js": "^2.108.2",