@01.works/visual-review 0.1.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.
- package/LICENSE +10 -0
- package/LICENSES/Apache-2.0.txt +187 -0
- package/README.md +212 -0
- package/THIRD_PARTY_NOTICES.md +160 -0
- package/dist/build-identity-CgoY3-jy.js +114 -0
- package/dist/generated-frame-CkUKpj5U.js +1 -0
- package/dist/generated-source-context-9dbnOHdW.js +1 -0
- package/dist/hosted-runtime-CgyV50-M.js +13 -0
- package/dist/index.d.ts +200 -0
- package/dist/index.js +1 -0
- package/dist/install-BN5uBP-5.js +1 -0
- package/dist/next-config.d.ts +44 -0
- package/dist/next-config.js +406 -0
- package/dist/next.d.ts +1 -0
- package/dist/next.js +1 -0
- package/dist/primitives-lxsPH8SN.js +81 -0
- package/dist/react.d.ts +8 -0
- package/dist/react.js +1 -0
- package/dist/source-index-BdWeiNHZ.js +1398 -0
- package/dist/source-instrumentation-transform-BwKYKlp0.js +119 -0
- package/dist/source-loader.js +127 -0
- package/dist/src-1EIago0s.js +840 -0
- package/dist/vite.d.ts +29 -0
- package/dist/vite.js +141 -0
- package/package.json +89 -0
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { n as VISUAL_REVIEW_SOURCE_INDEX_ATTRIBUTE, o as visualReviewSourceMarker, r as VISUAL_REVIEW_SOURCE_MARKER_ATTRIBUTE, t as VISUAL_REVIEW_SOURCE_COORDINATE_ATTRIBUTE } from "./source-index-BdWeiNHZ.js";
|
|
2
|
+
import { createHmac } from "node:crypto";
|
|
3
|
+
import path, { win32 } from "node:path";
|
|
4
|
+
import { parse } from "@babel/parser";
|
|
5
|
+
import MagicString from "magic-string";
|
|
6
|
+
//#region src/source-instrumentation-transform.ts
|
|
7
|
+
const SOURCE_MODULE = /\.(?:[cm]?[jt]sx?)$/iu;
|
|
8
|
+
const SKIPPED_PATH_SEGMENT = /(?:^|\/)(?:node_modules|\.next|dist|build)(?:\/|$)/u;
|
|
9
|
+
const MARKER_SALT = /^[0-9a-f]{64}$/u;
|
|
10
|
+
/**
|
|
11
|
+
* Adds opaque, build-local markers to intrinsic JSX nodes. Repository paths
|
|
12
|
+
* are used only to derive a digest and never enter the transformed browser
|
|
13
|
+
* source. The final generated coordinates are emitted separately by the
|
|
14
|
+
* framework adapter after bundling.
|
|
15
|
+
*/
|
|
16
|
+
function instrumentVisualReviewSource(source, options) {
|
|
17
|
+
assertMarkerSalt(options.markerSalt);
|
|
18
|
+
const sourcePath = normalizedProjectSourcePath(options.projectRoot, options.id);
|
|
19
|
+
if (!sourcePath) return null;
|
|
20
|
+
const ast = parse(source, {
|
|
21
|
+
errorRecovery: false,
|
|
22
|
+
plugins: parserPlugins(sourcePath),
|
|
23
|
+
sourceFilename: sourcePath,
|
|
24
|
+
sourceType: "unambiguous"
|
|
25
|
+
});
|
|
26
|
+
const edits = new MagicString(source);
|
|
27
|
+
let markerCount = 0;
|
|
28
|
+
const candidates = [];
|
|
29
|
+
visitAst(ast, (node) => {
|
|
30
|
+
if (node.type !== "JSXOpeningElement" || !isIntrinsicJsxName(node.name) || hasVisualReviewMarker(node.attributes) || typeof node.start !== "number" || typeof node.end !== "number" || !node.loc) return;
|
|
31
|
+
const nameEnd = intrinsicJsxNameEnd(node.name);
|
|
32
|
+
if (nameEnd === null || nameEnd <= node.start || nameEnd > node.end) return;
|
|
33
|
+
candidates.push({
|
|
34
|
+
column0: node.loc.start.column,
|
|
35
|
+
end: node.end,
|
|
36
|
+
line1: node.loc.start.line,
|
|
37
|
+
nameEnd,
|
|
38
|
+
selfClosing: node.selfClosing === true,
|
|
39
|
+
start: node.start
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
for (const candidate of candidates) {
|
|
43
|
+
const line1 = candidate.line1;
|
|
44
|
+
const column1 = candidate.column0 + 1;
|
|
45
|
+
const digest = createHmac("sha256", Buffer.from(options.markerSalt, "hex")).update(`${sourcePath}\0${line1}\0${column1}`).digest("hex").slice(0, 32);
|
|
46
|
+
const marker = visualReviewSourceMarker(digest);
|
|
47
|
+
const coordinate = `vrc1_${digest}`;
|
|
48
|
+
const closingWidth = candidate.selfClosing ? 2 : 1;
|
|
49
|
+
const insertionPoint = candidate.end - closingWidth;
|
|
50
|
+
const metadata = ` ${VISUAL_REVIEW_SOURCE_MARKER_ATTRIBUTE}="${marker}" ${VISUAL_REVIEW_SOURCE_INDEX_ATTRIBUTE}="${options.indexUrl}"`;
|
|
51
|
+
edits.overwrite(candidate.start, candidate.nameEnd, `${source.slice(candidate.start, candidate.nameEnd)} ${VISUAL_REVIEW_SOURCE_COORDINATE_ATTRIBUTE}="${coordinate}"`);
|
|
52
|
+
edits.appendLeft(insertionPoint, metadata);
|
|
53
|
+
markerCount += 1;
|
|
54
|
+
}
|
|
55
|
+
if (markerCount === 0) return null;
|
|
56
|
+
return {
|
|
57
|
+
code: edits.toString(),
|
|
58
|
+
map: edits.generateMap({
|
|
59
|
+
hires: true,
|
|
60
|
+
includeContent: true,
|
|
61
|
+
source: sourcePath
|
|
62
|
+
}),
|
|
63
|
+
markerCount
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function assertMarkerSalt(value) {
|
|
67
|
+
if (!MARKER_SALT.test(value)) throw new Error("Visual Review source marker salt must be a lowercase 32-byte hex secret");
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Reports whether a module is first-party project source. Bundler rules already
|
|
71
|
+
* try to exclude dependencies, but Next 15's Turbopack does not honour the
|
|
72
|
+
* `foreign` condition, so the loader enforces the same boundary itself.
|
|
73
|
+
*/
|
|
74
|
+
function isVisualReviewProjectSource(projectRoot, id) {
|
|
75
|
+
return normalizedProjectSourcePath(projectRoot, id) !== null;
|
|
76
|
+
}
|
|
77
|
+
function normalizedProjectSourcePath(projectRoot, id) {
|
|
78
|
+
const cleanId = id.replace(/[?#].*$/u, "");
|
|
79
|
+
if (!SOURCE_MODULE.test(cleanId) || cleanId.endsWith(".d.ts")) return null;
|
|
80
|
+
const pathApi = /^[a-z]:[\\/]/iu.test(projectRoot) || /^[a-z]:[\\/]/iu.test(cleanId) ? win32 : path;
|
|
81
|
+
const root = pathApi.resolve(projectRoot);
|
|
82
|
+
const absolute = pathApi.resolve(cleanId);
|
|
83
|
+
const candidate = pathApi.relative(root, absolute);
|
|
84
|
+
if (!candidate || candidate === ".." || candidate.startsWith(`..${pathApi.sep}`) || pathApi.isAbsolute(candidate)) return null;
|
|
85
|
+
const normalized = candidate.split(pathApi.sep).join("/");
|
|
86
|
+
return SKIPPED_PATH_SEGMENT.test(normalized) ? null : normalized;
|
|
87
|
+
}
|
|
88
|
+
function parserPlugins(sourcePath) {
|
|
89
|
+
const plugins = ["jsx", "importAttributes"];
|
|
90
|
+
if (/\.(?:[cm]?tsx?)$/iu.test(sourcePath)) plugins.push("typescript");
|
|
91
|
+
return plugins;
|
|
92
|
+
}
|
|
93
|
+
function isIntrinsicJsxName(name) {
|
|
94
|
+
if (!name || typeof name === "string" || name.type !== "JSXIdentifier") return false;
|
|
95
|
+
const value = typeof name.name === "string" ? name.name : "";
|
|
96
|
+
return Boolean(value) && value[0] === value[0]?.toLowerCase();
|
|
97
|
+
}
|
|
98
|
+
function intrinsicJsxNameEnd(name) {
|
|
99
|
+
if (!isIntrinsicJsxName(name)) return null;
|
|
100
|
+
const candidate = name;
|
|
101
|
+
return typeof candidate.end === "number" ? candidate.end : null;
|
|
102
|
+
}
|
|
103
|
+
function hasVisualReviewMarker(attributes) {
|
|
104
|
+
return attributes?.some((attribute) => attribute.type === "JSXAttribute" && typeof attribute.name !== "string" && attribute.name?.type === "JSXIdentifier" && (attribute.name.name === "data-visual-review-source" || attribute.name.name === "data-visual-review-source-coordinate" || attribute.name.name === "data-visual-review-source-index" || attribute.name.name === "data-review-source")) ?? false;
|
|
105
|
+
}
|
|
106
|
+
function visitAst(node, visitor) {
|
|
107
|
+
visitor(node);
|
|
108
|
+
for (const [key, value] of Object.entries(node)) {
|
|
109
|
+
if (key === "loc" || key === "start" || key === "end") continue;
|
|
110
|
+
if (Array.isArray(value)) {
|
|
111
|
+
for (const child of value) if (isAstNode(child)) visitAst(child, visitor);
|
|
112
|
+
} else if (isAstNode(value)) visitAst(value, visitor);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
function isAstNode(value) {
|
|
116
|
+
return Boolean(value) && typeof value === "object" && typeof value.type === "string";
|
|
117
|
+
}
|
|
118
|
+
//#endregion
|
|
119
|
+
export { isVisualReviewProjectSource as n, instrumentVisualReviewSource as t };
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { n as isVisualReviewProjectSource, t as instrumentVisualReviewSource } from "./source-instrumentation-transform-BwKYKlp0.js";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { transformSync } from "@babel/core";
|
|
4
|
+
import transformReactJsx from "@babel/plugin-transform-react-jsx";
|
|
5
|
+
import transformTypeScript from "@babel/plugin-transform-typescript";
|
|
6
|
+
//#region src/source-loader.ts
|
|
7
|
+
const MARKER_SALT = /^[0-9a-f]{64}$/u;
|
|
8
|
+
const saltCache = /* @__PURE__ */ new Map();
|
|
9
|
+
/**
|
|
10
|
+
* Reads a build-local marker salt. Next serializes loader options into
|
|
11
|
+
* `required-server-files.json` and into Turbopack edge chunks, so the adapter
|
|
12
|
+
* publishes only this path and keeps the secret in an owner-only file.
|
|
13
|
+
*/
|
|
14
|
+
function readMarkerSaltFile(saltPath) {
|
|
15
|
+
const cached = saltCache.get(saltPath);
|
|
16
|
+
if (cached !== void 0) return cached;
|
|
17
|
+
let contents;
|
|
18
|
+
try {
|
|
19
|
+
contents = readFileSync(saltPath, "utf8").trim();
|
|
20
|
+
} catch {
|
|
21
|
+
throw new Error("Visual Review source loader could not read the marker salt");
|
|
22
|
+
}
|
|
23
|
+
if (!MARKER_SALT.test(contents)) throw new Error("Visual Review source loader marker salt is invalid");
|
|
24
|
+
saltCache.set(saltPath, contents);
|
|
25
|
+
return contents;
|
|
26
|
+
}
|
|
27
|
+
function visualReviewSourceLoader(source, inputMap) {
|
|
28
|
+
const done = this.async();
|
|
29
|
+
try {
|
|
30
|
+
const options = parseOptions(this.getOptions?.());
|
|
31
|
+
const projectRoot = options.projectRoot ?? this.rootContext;
|
|
32
|
+
if (!isVisualReviewProjectSource(projectRoot, this.resourcePath)) {
|
|
33
|
+
done(null, source, inputMap);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const transformed = instrumentVisualReviewSource(source, {
|
|
37
|
+
id: this.resourcePath,
|
|
38
|
+
indexUrl: options.indexUrl,
|
|
39
|
+
markerSalt: options.markerSalt,
|
|
40
|
+
projectRoot
|
|
41
|
+
});
|
|
42
|
+
const code = transformed?.code ?? source;
|
|
43
|
+
const map = transformed?.map ?? inputMap;
|
|
44
|
+
if (options.compile) {
|
|
45
|
+
const compiled = compileForTurbopack(code, map, this.resourcePath, transformed !== null);
|
|
46
|
+
done(null, compiled.code, compiled.map);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
if (!transformed) {
|
|
50
|
+
done(null, source, inputMap);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
done(null, transformed.code, transformed.map);
|
|
54
|
+
} catch (cause) {
|
|
55
|
+
done(cause instanceof Error ? cause : new Error(String(cause)));
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function compileForTurbopack(source, inputMap, filename, instrumented) {
|
|
59
|
+
const isTypeScript = /\.(?:[cm]?tsx?)$/iu.test(filename);
|
|
60
|
+
const isTsx = /\.(?:[cm]?tsx)$/iu.test(filename);
|
|
61
|
+
const result = transformSync(source, {
|
|
62
|
+
babelrc: false,
|
|
63
|
+
comments: true,
|
|
64
|
+
compact: false,
|
|
65
|
+
configFile: false,
|
|
66
|
+
filename,
|
|
67
|
+
inputSourceMap: normalizeTurbopackInputMap(inputMap, filename, instrumented),
|
|
68
|
+
plugins: [...isTypeScript ? [[transformTypeScript, {
|
|
69
|
+
allExtensions: true,
|
|
70
|
+
allowDeclareFields: true,
|
|
71
|
+
isTSX: isTsx
|
|
72
|
+
}]] : [], [transformReactJsx, { runtime: "automatic" }]],
|
|
73
|
+
retainLines: true,
|
|
74
|
+
sourceMaps: true,
|
|
75
|
+
sourceType: "unambiguous"
|
|
76
|
+
});
|
|
77
|
+
if (typeof result?.code !== "string") throw new Error("Visual Review source loader could not compile the module");
|
|
78
|
+
return {
|
|
79
|
+
code: result.code,
|
|
80
|
+
map: result.map
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
function normalizeTurbopackInputMap(value, filename, instrumented) {
|
|
84
|
+
const normalized = normalizeInputMap(value);
|
|
85
|
+
if (!normalized || !instrumented) return normalized;
|
|
86
|
+
if (!Array.isArray(normalized.sources) || normalized.sources.length !== 1) throw new Error("Visual Review source loader produced an invalid source map");
|
|
87
|
+
const resourceFilename = filename.replaceAll("\\", "/").split("/").at(-1);
|
|
88
|
+
if (!resourceFilename || resourceFilename === "." || resourceFilename === "..") throw new Error("Visual Review source loader resource path is invalid");
|
|
89
|
+
return {
|
|
90
|
+
...normalized,
|
|
91
|
+
sourceRoot: "",
|
|
92
|
+
sources: [resourceFilename]
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
function normalizeInputMap(value) {
|
|
96
|
+
if (!value) return void 0;
|
|
97
|
+
if (typeof value === "string") return parsedSourceMap(value);
|
|
98
|
+
if (typeof value === "object" && "toString" in value) {
|
|
99
|
+
const serialized = String(value);
|
|
100
|
+
if (serialized.startsWith("{")) return parsedSourceMap(serialized);
|
|
101
|
+
}
|
|
102
|
+
return isRecord(value) ? value : void 0;
|
|
103
|
+
}
|
|
104
|
+
function parsedSourceMap(value) {
|
|
105
|
+
const parsed = JSON.parse(value);
|
|
106
|
+
if (!isRecord(parsed)) throw new Error("Visual Review source loader received an invalid source map");
|
|
107
|
+
return parsed;
|
|
108
|
+
}
|
|
109
|
+
function isRecord(value) {
|
|
110
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
111
|
+
}
|
|
112
|
+
function parseOptions(value) {
|
|
113
|
+
if (!value || typeof value !== "object") throw new Error("Visual Review source loader options are missing");
|
|
114
|
+
const options = value;
|
|
115
|
+
if (typeof options.indexUrl !== "string" || !options.indexUrl.startsWith("/") || options.indexUrl.startsWith("//") || options.indexUrl.includes("\\")) throw new Error("Visual Review source loader index URL is invalid");
|
|
116
|
+
if (options.projectRoot !== void 0 && typeof options.projectRoot !== "string") throw new Error("Visual Review source loader project root is invalid");
|
|
117
|
+
const markerSalt = typeof options.saltPath === "string" ? readMarkerSaltFile(options.saltPath) : options.markerSalt;
|
|
118
|
+
if (typeof markerSalt !== "string" || !MARKER_SALT.test(markerSalt)) throw new Error("Visual Review source loader marker salt is invalid");
|
|
119
|
+
return {
|
|
120
|
+
...options.compile === true ? { compile: true } : {},
|
|
121
|
+
indexUrl: options.indexUrl,
|
|
122
|
+
markerSalt,
|
|
123
|
+
...typeof options.projectRoot === "string" ? { projectRoot: options.projectRoot } : {}
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
//#endregion
|
|
127
|
+
export { visualReviewSourceLoader as default, normalizeTurbopackInputMap };
|