@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.
@@ -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 };