@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,406 @@
|
|
|
1
|
+
import { t as resolveVisualReviewBuildIdentity } from "./build-identity-CgoY3-jy.js";
|
|
2
|
+
import { a as visualReviewSourceIndexFilename, c as writeNextVisualReviewSourceIndex, d as uploadNextSourceMaps, i as nextSourceIndexUrl, u as cleanupNextSourceMapOutput } from "./source-index-BdWeiNHZ.js";
|
|
3
|
+
import { randomBytes } from "node:crypto";
|
|
4
|
+
import { dirname, join, resolve } from "node:path";
|
|
5
|
+
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
6
|
+
import { tmpdir } from "node:os";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
//#region src/next-config.ts
|
|
9
|
+
const BUILD_ID_LITERAL = "VISUAL_REVIEW_PUBLIC_BUILD_ID";
|
|
10
|
+
const GIT_COMMIT_LITERAL = "VISUAL_REVIEW_PUBLIC_GIT_COMMIT";
|
|
11
|
+
const BUILD_MODE_LITERAL = "VISUAL_REVIEW_PUBLIC_BUILD_MODE";
|
|
12
|
+
const NEXT_BUILD_IDENTITY_RECORD = "VISUAL_REVIEW_INTERNAL_NEXT_BUILD_IDENTITY";
|
|
13
|
+
const NEXT_MARKER_SALT_PATH = "VISUAL_REVIEW_INTERNAL_NEXT_MARKER_SALT_PATH";
|
|
14
|
+
const FULL_GIT_COMMIT = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/u;
|
|
15
|
+
const PUBLIC_BUILD_ID = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u;
|
|
16
|
+
const DEPLOYMENT_VARIANT = /^[0-9a-f]{32}$/u;
|
|
17
|
+
const GIT_FALLBACK_BUILD_ID = /^(git:([0-9a-f]{40}|[0-9a-f]{64}):deploy:([0-9a-f]{32}))$/u;
|
|
18
|
+
const MAX_NEXT_BUILD_IDENTITY_RECORD_BYTES = 512;
|
|
19
|
+
const trackedMarkerSaltPaths = /* @__PURE__ */ new Set();
|
|
20
|
+
const VISUAL_REVIEW_HOOK = Symbol.for("01works.visual-review.next-source-map-hook");
|
|
21
|
+
const VISUAL_REVIEW_CONFIG = Symbol.for("01works.visual-review.next-config");
|
|
22
|
+
/**
|
|
23
|
+
* Next evaluates next.config again in compiler workers. A Git fallback contains
|
|
24
|
+
* a per-build public nonce, so recomputing it in each process would make the
|
|
25
|
+
* browser literal disagree with the main process upload hook. Next 15/16 copy
|
|
26
|
+
* the main process environment into those workers; hand off only the already
|
|
27
|
+
* public identity and validate it before reuse. Marker salts and upload tokens
|
|
28
|
+
* are deliberately outside this record.
|
|
29
|
+
*/
|
|
30
|
+
function resolveNextVisualReviewBuildIdentity(options, env) {
|
|
31
|
+
const privateBuildWorker = env.NEXT_PRIVATE_BUILD_WORKER === "1";
|
|
32
|
+
const nextWorker = env.IS_NEXT_WORKER === "true";
|
|
33
|
+
if (!privateBuildWorker && !nextWorker) {
|
|
34
|
+
delete env[NEXT_BUILD_IDENTITY_RECORD];
|
|
35
|
+
const identity = resolveVisualReviewBuildIdentity(options, {
|
|
36
|
+
cwd: process.cwd(),
|
|
37
|
+
env,
|
|
38
|
+
createDeploymentVariant: () => randomBytes(16).toString("hex")
|
|
39
|
+
});
|
|
40
|
+
const serializedRecord = JSON.stringify({
|
|
41
|
+
schemaVersion: 1,
|
|
42
|
+
buildId: identity.buildId,
|
|
43
|
+
gitCommit: identity.gitCommit
|
|
44
|
+
});
|
|
45
|
+
const record = parseNextBuildIdentityRecord(serializedRecord);
|
|
46
|
+
env[NEXT_BUILD_IDENTITY_RECORD] = serializedRecord;
|
|
47
|
+
return {
|
|
48
|
+
buildId: record.buildId,
|
|
49
|
+
gitCommit: record.gitCommit
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
if (!privateBuildWorker || !nextWorker) throw new Error("Visual Review Next.js build worker identity context is invalid");
|
|
53
|
+
const record = parseNextBuildIdentityRecord(env[NEXT_BUILD_IDENTITY_RECORD]);
|
|
54
|
+
const fallbackMatch = GIT_FALLBACK_BUILD_ID.exec(record.buildId);
|
|
55
|
+
const expected = resolveVisualReviewBuildIdentity(options, {
|
|
56
|
+
cwd: process.cwd(),
|
|
57
|
+
env,
|
|
58
|
+
runGit: (arguments_) => {
|
|
59
|
+
if (arguments_[0] === "status") return record.gitCommit === null ? " M .next/visual-review-build-output" : "";
|
|
60
|
+
if (arguments_[0] === "rev-parse" && record.gitCommit !== null) return record.gitCommit;
|
|
61
|
+
throw new Error("Visual Review Next.js worker cannot rediscover Git identity");
|
|
62
|
+
},
|
|
63
|
+
createDeploymentVariant: () => fallbackMatch?.[3] ?? "0".repeat(32)
|
|
64
|
+
});
|
|
65
|
+
if (expected.buildId !== record.buildId || expected.gitCommit !== record.gitCommit) throw new Error("Visual Review Next.js build worker identity does not match the main build");
|
|
66
|
+
return {
|
|
67
|
+
buildId: record.buildId,
|
|
68
|
+
gitCommit: record.gitCommit
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function parseNextBuildIdentityRecord(value) {
|
|
72
|
+
if (typeof value !== "string" || value.length === 0 || Buffer.byteLength(value) > MAX_NEXT_BUILD_IDENTITY_RECORD_BYTES) throw new Error("Visual Review Next.js build worker identity is missing or invalid");
|
|
73
|
+
let parsed;
|
|
74
|
+
try {
|
|
75
|
+
parsed = JSON.parse(value);
|
|
76
|
+
} catch {
|
|
77
|
+
throw new Error("Visual Review Next.js build worker identity is missing or invalid");
|
|
78
|
+
}
|
|
79
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("Visual Review Next.js build worker identity is missing or invalid");
|
|
80
|
+
const record = parsed;
|
|
81
|
+
if (Object.keys(record).sort().join(",") !== "buildId,gitCommit,schemaVersion" || record.schemaVersion !== 1 || typeof record.buildId !== "string" || !PUBLIC_BUILD_ID.test(record.buildId) || record.gitCommit !== null && (typeof record.gitCommit !== "string" || !FULL_GIT_COMMIT.test(record.gitCommit))) throw new Error("Visual Review Next.js build worker identity is missing or invalid");
|
|
82
|
+
const fallbackMatch = GIT_FALLBACK_BUILD_ID.exec(record.buildId);
|
|
83
|
+
if (/^git:(?:[0-9a-f]{40}|[0-9a-f]{64}):deploy:/u.test(record.buildId) && !fallbackMatch) throw new Error("Visual Review Next.js build worker identity is missing or invalid");
|
|
84
|
+
if (fallbackMatch && (!DEPLOYMENT_VARIANT.test(fallbackMatch[3] ?? "") || fallbackMatch[2] !== record.gitCommit)) throw new Error("Visual Review Next.js build worker identity is missing or invalid");
|
|
85
|
+
return {
|
|
86
|
+
schemaVersion: 1,
|
|
87
|
+
buildId: record.buildId,
|
|
88
|
+
gitCommit: record.gitCommit
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
function withVisualReview(nextConfig = {}, options = {}) {
|
|
92
|
+
const identity = resolveNextVisualReviewBuildIdentity(options, process.env);
|
|
93
|
+
const sourceMapUpload = resolveSourceMapUpload(options.sourceMaps, process.env);
|
|
94
|
+
const sourceMetadataEnabled = options.sourceMetadata ?? true;
|
|
95
|
+
const sourceMarkerSalt = sourceMetadataEnabled ? resolveBuildLocalMarkerSalt(process.env) : void 0;
|
|
96
|
+
const injected = {
|
|
97
|
+
[BUILD_ID_LITERAL]: identity.buildId,
|
|
98
|
+
[GIT_COMMIT_LITERAL]: identity.gitCommit ?? "",
|
|
99
|
+
[BUILD_MODE_LITERAL]: sourceMapUpload?.mode ?? configuredReleaseMode(options.sourceMaps) ?? inferReleaseMode(process.env)
|
|
100
|
+
};
|
|
101
|
+
if (typeof nextConfig === "function") return function visualReviewNextConfig(...arguments_) {
|
|
102
|
+
const resolved = nextConfig.apply(this, arguments_);
|
|
103
|
+
return isPromiseLike(resolved) ? resolved.then((config) => configureNext(config, injected, identity, sourceMapUpload, sourceMetadataEnabled, sourceMarkerSalt)) : configureNext(resolved, injected, identity, sourceMapUpload, sourceMetadataEnabled, sourceMarkerSalt);
|
|
104
|
+
};
|
|
105
|
+
return configureNext(nextConfig, injected, identity, sourceMapUpload, sourceMetadataEnabled, sourceMarkerSalt);
|
|
106
|
+
}
|
|
107
|
+
function configureNext(nextConfig, injected, identity, sourceMapUpload, sourceMetadataEnabled, sourceMarkerSalt) {
|
|
108
|
+
const previousHookMetadata = getVisualReviewHookMetadata(nextConfig.compiler?.runAfterProductionCompile);
|
|
109
|
+
const previouslyWrapped = isVisualReviewConfig(nextConfig) || previousHookMetadata !== void 0;
|
|
110
|
+
assertReservedLiterals(nextConfig.env, injected, previouslyWrapped);
|
|
111
|
+
let configured = {
|
|
112
|
+
...nextConfig,
|
|
113
|
+
env: {
|
|
114
|
+
...nextConfig.env,
|
|
115
|
+
...injected
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
if (previousHookMetadata) configured = restoreSourceMapConfiguration(configured, previousHookMetadata);
|
|
119
|
+
if (!sourceMapUpload && !sourceMetadataEnabled) return markVisualReviewConfig(configured);
|
|
120
|
+
const hookMetadata = previousHookMetadata ?? captureHookMetadata(nextConfig);
|
|
121
|
+
if (sourceMapUpload && (nextConfig.productionBrowserSourceMaps === false || hookMetadata.productionBrowserSourceMaps.present && hookMetadata.productionBrowserSourceMaps.value === false)) throw new Error("Next productionBrowserSourceMaps=false conflicts with Visual Review source-map upload");
|
|
122
|
+
if (sourceMapUpload && (nextConfig.experimental?.serverSourceMaps === false || hookMetadata.serverSourceMaps.present && hookMetadata.serverSourceMaps.value === false)) throw new Error("Next experimental.serverSourceMaps=false conflicts with Visual Review source-map upload");
|
|
123
|
+
const sourceMetadata = sourceMetadataEnabled ? resolveNextSourceMetadata(identity, nextConfig.basePath, sourceMarkerSalt) : void 0;
|
|
124
|
+
const sourceConfigured = sourceMetadata ? configureNextSourceMetadata(configured, hookMetadata, sourceMetadata) : configured;
|
|
125
|
+
const compiler = sourceConfigured.compiler ?? {};
|
|
126
|
+
return markVisualReviewConfig({
|
|
127
|
+
...sourceConfigured,
|
|
128
|
+
...sourceMapUpload ? { productionBrowserSourceMaps: true } : {},
|
|
129
|
+
...sourceMapUpload ? { experimental: {
|
|
130
|
+
...sourceConfigured.experimental,
|
|
131
|
+
serverSourceMaps: true
|
|
132
|
+
} } : {},
|
|
133
|
+
compiler: {
|
|
134
|
+
...compiler,
|
|
135
|
+
runAfterProductionCompile: composeProductionCompileHook(hookMetadata.originalHook, identity, sourceMapUpload, sourceMetadata, nextConfig.basePath, nextConfig.assetPrefix, hookMetadata)
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
function composeProductionCompileHook(existingHook, identity, sourceMapUpload, sourceMetadata, basePath, assetPrefix, metadata) {
|
|
140
|
+
const hook = async function visualReviewAfterProductionCompile(metadata) {
|
|
141
|
+
if (sourceMetadata) removeBuildLocalMarkerSalt(sourceMetadata.saltPath);
|
|
142
|
+
if (existingHook) try {
|
|
143
|
+
await existingHook.call(this, metadata);
|
|
144
|
+
} catch (existingError) {
|
|
145
|
+
if (!sourceMapUpload) throw existingError;
|
|
146
|
+
try {
|
|
147
|
+
await cleanupNextSourceMapOutput(metadata.projectDir, metadata.distDir);
|
|
148
|
+
} catch (cleanupError) {
|
|
149
|
+
throw new AggregateError([existingError, cleanupError], "Next.js production compile hook failed and Visual Review could not clean source maps");
|
|
150
|
+
}
|
|
151
|
+
throw existingError;
|
|
152
|
+
}
|
|
153
|
+
if (sourceMetadata) try {
|
|
154
|
+
await writeNextVisualReviewSourceIndex({
|
|
155
|
+
projectDir: metadata.projectDir,
|
|
156
|
+
distDir: metadata.distDir,
|
|
157
|
+
filename: sourceMetadata.filename,
|
|
158
|
+
...basePath ? { basePath } : {},
|
|
159
|
+
...assetPrefix ? { assetPrefix } : {}
|
|
160
|
+
});
|
|
161
|
+
} catch (sourceIndexError) {
|
|
162
|
+
if (!sourceMapUpload) throw sourceIndexError;
|
|
163
|
+
try {
|
|
164
|
+
await cleanupNextSourceMapOutput(metadata.projectDir, metadata.distDir);
|
|
165
|
+
} catch (cleanupError) {
|
|
166
|
+
throw new AggregateError([sourceIndexError, cleanupError], "Visual Review source index failed and Next.js source maps could not be cleaned");
|
|
167
|
+
}
|
|
168
|
+
throw sourceIndexError;
|
|
169
|
+
}
|
|
170
|
+
if (!sourceMapUpload) return;
|
|
171
|
+
await uploadNextSourceMaps({
|
|
172
|
+
projectRoot: metadata.projectDir,
|
|
173
|
+
distDir: metadata.distDir,
|
|
174
|
+
token: sourceMapUpload.token,
|
|
175
|
+
...sourceMapUpload.serviceUrl ? { serviceUrl: sourceMapUpload.serviceUrl } : {},
|
|
176
|
+
buildId: identity.buildId,
|
|
177
|
+
gitCommit: identity.gitCommit,
|
|
178
|
+
mode: sourceMapUpload.mode,
|
|
179
|
+
...basePath ? { basePath } : {},
|
|
180
|
+
...assetPrefix ? { assetPrefix } : {}
|
|
181
|
+
});
|
|
182
|
+
};
|
|
183
|
+
Object.defineProperty(hook, VISUAL_REVIEW_HOOK, { value: metadata });
|
|
184
|
+
return hook;
|
|
185
|
+
}
|
|
186
|
+
function getVisualReviewHookMetadata(hook) {
|
|
187
|
+
return hook?.[VISUAL_REVIEW_HOOK];
|
|
188
|
+
}
|
|
189
|
+
function captureHookMetadata(nextConfig) {
|
|
190
|
+
return {
|
|
191
|
+
originalHook: nextConfig.compiler?.runAfterProductionCompile,
|
|
192
|
+
compilerPresent: Object.prototype.hasOwnProperty.call(nextConfig, "compiler"),
|
|
193
|
+
experimentalPresent: Object.prototype.hasOwnProperty.call(nextConfig, "experimental"),
|
|
194
|
+
productionBrowserSourceMaps: propertyState(nextConfig, "productionBrowserSourceMaps"),
|
|
195
|
+
serverSourceMaps: propertyState(nextConfig.experimental, "serverSourceMaps"),
|
|
196
|
+
turbopack: propertyState(nextConfig, "turbopack"),
|
|
197
|
+
webpack: propertyState(nextConfig, "webpack")
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
function propertyState(value, key) {
|
|
201
|
+
return {
|
|
202
|
+
present: value !== void 0 && Object.prototype.hasOwnProperty.call(value, key),
|
|
203
|
+
value: value?.[key]
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
function restoreSourceMapConfiguration(configured, metadata) {
|
|
207
|
+
if (!metadata) return configured;
|
|
208
|
+
const restored = { ...configured };
|
|
209
|
+
if (metadata.productionBrowserSourceMaps.present) restored.productionBrowserSourceMaps = metadata.productionBrowserSourceMaps.value;
|
|
210
|
+
else delete restored.productionBrowserSourceMaps;
|
|
211
|
+
const experimental = { ...restored.experimental ?? {} };
|
|
212
|
+
if (metadata.serverSourceMaps.present) experimental.serverSourceMaps = metadata.serverSourceMaps.value;
|
|
213
|
+
else delete experimental.serverSourceMaps;
|
|
214
|
+
if (!metadata.experimentalPresent && Object.keys(experimental).length === 0) delete restored.experimental;
|
|
215
|
+
else restored.experimental = experimental;
|
|
216
|
+
const compiler = { ...restored.compiler ?? {} };
|
|
217
|
+
if (metadata.originalHook) compiler.runAfterProductionCompile = metadata.originalHook;
|
|
218
|
+
else delete compiler.runAfterProductionCompile;
|
|
219
|
+
if (!metadata.compilerPresent && Object.keys(compiler).length === 0) delete restored.compiler;
|
|
220
|
+
else restored.compiler = compiler;
|
|
221
|
+
restoreProperty(restored, "turbopack", metadata.turbopack);
|
|
222
|
+
restoreProperty(restored, "webpack", metadata.webpack);
|
|
223
|
+
return restored;
|
|
224
|
+
}
|
|
225
|
+
function restoreProperty(target, key, state) {
|
|
226
|
+
if (state.present) target[key] = state.value;
|
|
227
|
+
else delete target[key];
|
|
228
|
+
}
|
|
229
|
+
function resolveNextSourceMetadata(identity, basePath, markerSalt) {
|
|
230
|
+
if (!markerSalt) throw new Error("Visual Review source marker salt is unavailable");
|
|
231
|
+
const filename = visualReviewSourceIndexFilename(identity.buildId, markerSalt.salt);
|
|
232
|
+
const loaderUrl = new URL("./source-loader.js", import.meta.url);
|
|
233
|
+
return {
|
|
234
|
+
filename,
|
|
235
|
+
indexUrl: nextSourceIndexUrl(basePath, filename),
|
|
236
|
+
loader: loaderUrl.protocol === "file:" ? fileURLToPath(loaderUrl) : resolve(process.cwd(), "packages/review-client/dist/source-loader.js"),
|
|
237
|
+
saltPath: markerSalt.path
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Generates the marker salt and persists it outside the project so compiler
|
|
242
|
+
* workers can read it without the secret entering any build output. The file is
|
|
243
|
+
* owner-only and is removed once the production compile hook has run.
|
|
244
|
+
*/
|
|
245
|
+
function resolveBuildLocalMarkerSalt(env) {
|
|
246
|
+
const inherited = env[NEXT_MARKER_SALT_PATH];
|
|
247
|
+
if (env.NEXT_PRIVATE_BUILD_WORKER === "1" && env.IS_NEXT_WORKER === "true" && typeof inherited === "string" && inherited.length > 0) return {
|
|
248
|
+
salt: readMarkerSaltFile(inherited),
|
|
249
|
+
path: inherited
|
|
250
|
+
};
|
|
251
|
+
const salt = randomBytes(32).toString("hex");
|
|
252
|
+
const path = join(mkdtempSync(join(tmpdir(), "visual-review-marker-salt-")), "salt");
|
|
253
|
+
writeFileSync(path, salt, {
|
|
254
|
+
encoding: "utf8",
|
|
255
|
+
mode: 384
|
|
256
|
+
});
|
|
257
|
+
env[NEXT_MARKER_SALT_PATH] = path;
|
|
258
|
+
trackBuildLocalMarkerSalt(path);
|
|
259
|
+
return {
|
|
260
|
+
salt,
|
|
261
|
+
path
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
function readMarkerSaltFile(saltPath) {
|
|
265
|
+
const salt = readFileSync(saltPath, "utf8").trim();
|
|
266
|
+
if (!/^[0-9a-f]{64}$/u.test(salt)) throw new Error("Visual Review inherited marker salt is invalid");
|
|
267
|
+
return salt;
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* `next dev` never reaches the production compile hook, and every compiler
|
|
271
|
+
* worker evaluates the config in its own process, so exit is the only point
|
|
272
|
+
* that reliably removes each salt file it created.
|
|
273
|
+
*/
|
|
274
|
+
function trackBuildLocalMarkerSalt(saltPath) {
|
|
275
|
+
if (trackedMarkerSaltPaths.size === 0) process.once("exit", () => {
|
|
276
|
+
for (const tracked of trackedMarkerSaltPaths) removeMarkerSaltDirectory(tracked);
|
|
277
|
+
});
|
|
278
|
+
trackedMarkerSaltPaths.add(saltPath);
|
|
279
|
+
}
|
|
280
|
+
function removeBuildLocalMarkerSalt(saltPath) {
|
|
281
|
+
trackedMarkerSaltPaths.delete(saltPath);
|
|
282
|
+
removeMarkerSaltDirectory(saltPath);
|
|
283
|
+
}
|
|
284
|
+
function removeMarkerSaltDirectory(saltPath) {
|
|
285
|
+
try {
|
|
286
|
+
rmSync(dirname(saltPath), {
|
|
287
|
+
recursive: true,
|
|
288
|
+
force: true
|
|
289
|
+
});
|
|
290
|
+
} catch {}
|
|
291
|
+
}
|
|
292
|
+
function configureNextSourceMetadata(configured, metadata, sourceMetadata) {
|
|
293
|
+
const bundler = resolveNextBundler();
|
|
294
|
+
const next = { ...configured };
|
|
295
|
+
restoreProperty(next, "turbopack", metadata.turbopack);
|
|
296
|
+
restoreProperty(next, "webpack", metadata.webpack);
|
|
297
|
+
if (bundler === "turbopack") next.turbopack = appendTurbopackRules(next.turbopack, sourceMetadata);
|
|
298
|
+
else next.webpack = appendWebpackRule(next.webpack, sourceMetadata);
|
|
299
|
+
return next;
|
|
300
|
+
}
|
|
301
|
+
function resolveNextBundler() {
|
|
302
|
+
return process.env.TURBOPACK ? "turbopack" : "webpack";
|
|
303
|
+
}
|
|
304
|
+
function appendWebpackRule(existing, sourceMetadata) {
|
|
305
|
+
return (config, context) => {
|
|
306
|
+
const resolved = existing?.(config, context) ?? config;
|
|
307
|
+
if (!resolved || typeof resolved !== "object" || isPromiseLikeUnknown(resolved)) throw new Error("Next webpack() must return a synchronous config for Visual Review");
|
|
308
|
+
const webpackConfig = resolved;
|
|
309
|
+
const moduleConfig = isRecord(webpackConfig.module) ? webpackConfig.module : {};
|
|
310
|
+
const rules = Array.isArray(moduleConfig.rules) ? moduleConfig.rules : [];
|
|
311
|
+
return {
|
|
312
|
+
...webpackConfig,
|
|
313
|
+
module: {
|
|
314
|
+
...moduleConfig,
|
|
315
|
+
rules: [...rules, {
|
|
316
|
+
enforce: "pre",
|
|
317
|
+
exclude: /node_modules/u,
|
|
318
|
+
test: /\.[cm]?[jt]sx?$/u,
|
|
319
|
+
use: [{
|
|
320
|
+
loader: sourceMetadata.loader,
|
|
321
|
+
options: {
|
|
322
|
+
indexUrl: sourceMetadata.indexUrl,
|
|
323
|
+
saltPath: sourceMetadata.saltPath
|
|
324
|
+
}
|
|
325
|
+
}]
|
|
326
|
+
}]
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
function appendTurbopackRules(existing, sourceMetadata) {
|
|
332
|
+
const rules = { ...existing?.rules ?? {} };
|
|
333
|
+
for (const extension of [
|
|
334
|
+
"js",
|
|
335
|
+
"jsx",
|
|
336
|
+
"ts",
|
|
337
|
+
"tsx"
|
|
338
|
+
]) {
|
|
339
|
+
const pattern = `*.${extension}`;
|
|
340
|
+
const rule = {
|
|
341
|
+
condition: { all: [{ not: "foreign" }, "production"] },
|
|
342
|
+
loaders: [{
|
|
343
|
+
loader: sourceMetadata.loader,
|
|
344
|
+
options: {
|
|
345
|
+
compile: true,
|
|
346
|
+
indexUrl: sourceMetadata.indexUrl,
|
|
347
|
+
saltPath: sourceMetadata.saltPath
|
|
348
|
+
}
|
|
349
|
+
}]
|
|
350
|
+
};
|
|
351
|
+
const current = rules[pattern];
|
|
352
|
+
rules[pattern] = current === void 0 ? rule : Array.isArray(current) ? [...current, rule] : [current, rule];
|
|
353
|
+
}
|
|
354
|
+
return {
|
|
355
|
+
...existing,
|
|
356
|
+
rules
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
function isPromiseLikeUnknown(value) {
|
|
360
|
+
return Boolean(value) && typeof value === "object" && typeof value.then === "function";
|
|
361
|
+
}
|
|
362
|
+
function isRecord(value) {
|
|
363
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
364
|
+
}
|
|
365
|
+
function isVisualReviewConfig(config) {
|
|
366
|
+
return Boolean(config[VISUAL_REVIEW_CONFIG]);
|
|
367
|
+
}
|
|
368
|
+
function markVisualReviewConfig(config) {
|
|
369
|
+
Object.defineProperty(config, VISUAL_REVIEW_CONFIG, { value: true });
|
|
370
|
+
return config;
|
|
371
|
+
}
|
|
372
|
+
function resolveSourceMapUpload(option, env) {
|
|
373
|
+
if (option === false || typeof option === "object" && option.enabled === false) return;
|
|
374
|
+
const configured = typeof option === "object" ? option : void 0;
|
|
375
|
+
const token = configured?.token ?? env.VISUAL_REVIEW_SOURCE_MAP_TOKEN;
|
|
376
|
+
if (!(option === true || configured !== void 0 || token !== void 0)) return void 0;
|
|
377
|
+
if (typeof token !== "string" || token.length === 0 || token.length > 4096 || /\s/u.test(token)) throw new Error("Visual Review source-map upload requires VISUAL_REVIEW_SOURCE_MAP_TOKEN in the Node build environment");
|
|
378
|
+
return {
|
|
379
|
+
token,
|
|
380
|
+
mode: configured?.mode ?? inferReleaseMode(env),
|
|
381
|
+
...configured?.serviceUrl ? { serviceUrl: configured.serviceUrl } : {}
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
function configuredReleaseMode(option) {
|
|
385
|
+
return typeof option === "object" ? option.mode : void 0;
|
|
386
|
+
}
|
|
387
|
+
function inferReleaseMode(env) {
|
|
388
|
+
if (env.VERCEL_ENV === "preview") return "preview";
|
|
389
|
+
if (env.VERCEL_ENV === "development") return "development";
|
|
390
|
+
if (env.CONTEXT === "deploy-preview" || env.CONTEXT === "branch-deploy") return "preview";
|
|
391
|
+
if (env.NODE_ENV === "development") return "development";
|
|
392
|
+
return "production";
|
|
393
|
+
}
|
|
394
|
+
function isPromiseLike(value) {
|
|
395
|
+
return typeof value.then === "function";
|
|
396
|
+
}
|
|
397
|
+
function assertReservedLiterals(current, expected, allowVisualReviewOverride = false) {
|
|
398
|
+
if (!current) return;
|
|
399
|
+
for (const key of [
|
|
400
|
+
BUILD_ID_LITERAL,
|
|
401
|
+
GIT_COMMIT_LITERAL,
|
|
402
|
+
BUILD_MODE_LITERAL
|
|
403
|
+
]) if (!allowVisualReviewOverride && Object.prototype.hasOwnProperty.call(current, key) && current[key] !== expected[key]) throw new Error(`Next env already defines reserved ${key}; configure it through withVisualReview() options`);
|
|
404
|
+
}
|
|
405
|
+
//#endregion
|
|
406
|
+
export { withVisualReview };
|
package/dist/next.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { VisualReview, type VisualReviewProps } from './react.js';
|
package/dist/next.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use client";import{VisualReview as e}from"./react.js";export{e as VisualReview};
|