@hawk.so/nextjs 0.0.0-stage → 1.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,75 @@
1
+ /// <reference path="../global.d.ts" />
2
+ import { NextConfig } from 'next';
3
+
4
+ /**
5
+ * Options of the commits collecting
6
+ */
7
+ interface CommitsOptions {
8
+ /**
9
+ * Path to the repository with `.git` directory. Default is the Next.js project directory
10
+ */
11
+ repo?: string;
12
+ /**
13
+ * How many recent commits to send. Default is 5
14
+ */
15
+ number?: number;
16
+ }
17
+
18
+ /**
19
+ * Options of the Hawk Next.js integration
20
+ */
21
+ interface HawkNextOptions {
22
+ /**
23
+ * Hawk Integration Token. Default is the HAWK_TOKEN or NEXT_PUBLIC_HAWK_TOKEN environment variable
24
+ */
25
+ token?: string;
26
+ /**
27
+ * Unique identifier of the release.
28
+ * Default is HAWK_RELEASE environment variable, commit SHA from CI or git, or the build time
29
+ */
30
+ release?: string | number;
31
+ /**
32
+ * Whether to remove client source maps after sending. Default is true
33
+ */
34
+ removeSourceMaps?: boolean;
35
+ /**
36
+ * Options for sending recent commits with the release, or false to skip. Enabled by default
37
+ */
38
+ commits?: boolean | CommitsOptions;
39
+ /**
40
+ * Collector endpoint overwrite
41
+ */
42
+ collectorEndpoint?: string;
43
+ }
44
+ /**
45
+ * Context passed by Next.js to the config function
46
+ */
47
+ interface NextConfigContext {
48
+ /**
49
+ * Default Next.js config
50
+ */
51
+ defaultConfig: NextConfig;
52
+ }
53
+ /**
54
+ * Next.js config exported as a function
55
+ */
56
+ type NextConfigFunction = (phase: string, context: NextConfigContext) => NextConfig | Promise<NextConfig>;
57
+ /**
58
+ * Wraps Next.js config to send releases with source maps to Hawk.
59
+ *
60
+ * - exposes the release id to the app code as `process.env.HAWK_RELEASE`
61
+ * - enables client source maps for production builds
62
+ * - after `next build` sends client source maps and recent commits to Hawk and removes the maps
63
+ * @param nextConfig - Next.js config object
64
+ * @param options - Hawk plugin options
65
+ */
66
+ declare function withHawkConfig(nextConfig: NextConfig, options?: HawkNextOptions): NextConfig;
67
+ /**
68
+ * Wraps Next.js config function to send releases with source maps to Hawk
69
+ * @param nextConfig - function returning Next.js config
70
+ * @param options - Hawk plugin options
71
+ */
72
+ declare function withHawkConfig(nextConfig: NextConfigFunction, options?: HawkNextOptions): NextConfigFunction;
73
+
74
+ export { withHawkConfig };
75
+ export type { CommitsOptions, HawkNextOptions, NextConfigFunction };
package/dist/config.js ADDED
@@ -0,0 +1,533 @@
1
+ // src/config/with-hawk-config.ts
2
+ import { createRequire } from "module";
3
+ import path5 from "path";
4
+ import process2 from "process";
5
+
6
+ // src/config/instrumentation.ts
7
+ import { access } from "fs/promises";
8
+ import path from "path";
9
+ var DEFAULT_PAGE_EXTENSIONS = ["tsx", "ts", "jsx", "js"];
10
+ var INSTRUMENTATION_FILES = ["instrumentation", "instrumentation-client"];
11
+ async function exists(filePath) {
12
+ try {
13
+ await access(filePath);
14
+ return true;
15
+ } catch (_error) {
16
+ return false;
17
+ }
18
+ }
19
+ async function findMissingInstrumentationFiles(projectDir, pageExtensions = DEFAULT_PAGE_EXTENSIONS) {
20
+ const missing = [];
21
+ for (const name of INSTRUMENTATION_FILES) {
22
+ const candidates = ["", "src"].flatMap((dir) => pageExtensions.map((extension) => path.join(projectDir, dir, `${name}.${extension}`)));
23
+ const found = await Promise.all(candidates.map(exists));
24
+ if (!found.includes(true)) {
25
+ missing.push(name);
26
+ }
27
+ }
28
+ return missing;
29
+ }
30
+
31
+ // src/config/release.ts
32
+ import { execFileSync } from "child_process";
33
+ import process from "process";
34
+ var RELEASE_ENV_VARIABLE = "HAWK_RELEASE";
35
+ var COMMIT_SHA_ENV_VARIABLES = [
36
+ "GITHUB_SHA",
37
+ "CI_COMMIT_SHA",
38
+ "CI_MERGE_REQUEST_SOURCE_BRANCH_SHA",
39
+ "BITBUCKET_COMMIT",
40
+ "CIRCLE_SHA1",
41
+ "BUILDKITE_COMMIT",
42
+ "DRONE_COMMIT_SHA",
43
+ "TRAVIS_COMMIT",
44
+ "VERCEL_GIT_COMMIT_SHA",
45
+ "CF_PAGES_COMMIT_SHA",
46
+ "RAILWAY_GIT_COMMIT_SHA",
47
+ "RENDER_GIT_COMMIT",
48
+ "HEROKU_BUILD_COMMIT",
49
+ "SOURCE_VERSION",
50
+ "GIT_COMMIT",
51
+ "COMMIT_REF"
52
+ ];
53
+ function getGitRevision(cwd) {
54
+ try {
55
+ const revision = execFileSync("git", ["rev-parse", "HEAD"], {
56
+ cwd,
57
+ encoding: "utf8",
58
+ stdio: ["ignore", "pipe", "ignore"]
59
+ }).trim();
60
+ return revision !== "" ? revision : void 0;
61
+ } catch (_error) {
62
+ return void 0;
63
+ }
64
+ }
65
+ function resolveRelease(release, cwd) {
66
+ if (release !== void 0 && String(release) !== "") {
67
+ return String(release);
68
+ }
69
+ const candidates = [RELEASE_ENV_VARIABLE, ...COMMIT_SHA_ENV_VARIABLES].map((name) => process.env[name]);
70
+ const fromEnv = candidates.find((value) => value !== void 0 && value !== "");
71
+ if (fromEnv !== void 0) {
72
+ return fromEnv;
73
+ }
74
+ return getGitRevision(cwd) ?? (/* @__PURE__ */ new Date()).toISOString();
75
+ }
76
+
77
+ // src/config/token.ts
78
+ import { Buffer } from "buffer";
79
+ function getIntegrationId(token) {
80
+ try {
81
+ const payload = JSON.parse(Buffer.from(token, "base64").toString("utf-8"));
82
+ if (typeof payload.integrationId === "string" && payload.integrationId !== "") {
83
+ return payload.integrationId;
84
+ }
85
+ } catch (_error) {
86
+ }
87
+ return void 0;
88
+ }
89
+ function getDefaultCollectorEndpoint(integrationId) {
90
+ return `https://${integrationId}.k1.hawk.so/release`;
91
+ }
92
+
93
+ // src/config/upload-release.ts
94
+ import { readFile as readFile2 } from "fs/promises";
95
+
96
+ // src/config/cleanup.ts
97
+ import { readFile, unlink, writeFile } from "fs/promises";
98
+ import path4 from "path";
99
+
100
+ // src/config/sourcemaps.ts
101
+ import { createHash } from "crypto";
102
+ import { access as access2 } from "fs/promises";
103
+ import path3 from "path";
104
+
105
+ // src/config/utils/fs.ts
106
+ import { Buffer as Buffer2 } from "buffer";
107
+ import { open, readdir } from "fs/promises";
108
+ import path2 from "path";
109
+ var MAX_PARALLEL_FILE_OPERATIONS = 16;
110
+ async function listFiles(dir) {
111
+ try {
112
+ const entries = await readdir(dir, { withFileTypes: true });
113
+ const files = await Promise.all(entries.map(async (entry) => {
114
+ if (entry.isDirectory()) {
115
+ return (await listFiles(path2.join(dir, entry.name))).map((file) => `${entry.name}/${file}`);
116
+ }
117
+ return entry.isFile() ? [entry.name] : [];
118
+ }));
119
+ return files.flat();
120
+ } catch (error) {
121
+ if (error.code === "ENOENT") {
122
+ return [];
123
+ }
124
+ throw error;
125
+ }
126
+ }
127
+ async function readFileTail(filePath, maxBytes) {
128
+ const file = await open(filePath, "r");
129
+ try {
130
+ const { size } = await file.stat();
131
+ const length = Math.min(maxBytes, size);
132
+ const { bytesRead, buffer } = await file.read(Buffer2.alloc(length), 0, length, size - length);
133
+ const tail = buffer.subarray(0, bytesRead).toString("utf8");
134
+ if (length === size) {
135
+ return tail;
136
+ }
137
+ const cutLineEnd = tail.indexOf("\n");
138
+ return cutLineEnd === -1 ? "" : tail.slice(cutLineEnd);
139
+ } finally {
140
+ await file.close();
141
+ }
142
+ }
143
+
144
+ // src/config/utils/pool.ts
145
+ async function mapConcurrently(items, limit, mapper) {
146
+ const results = [];
147
+ let nextIndex = 0;
148
+ const runWorker = async () => {
149
+ while (nextIndex < items.length) {
150
+ const index = nextIndex;
151
+ nextIndex += 1;
152
+ results[index] = await mapper(items[index]);
153
+ }
154
+ };
155
+ const workersCount = Math.max(1, Math.min(limit, items.length));
156
+ await Promise.all(Array.from({ length: workersCount }, runWorker));
157
+ return results;
158
+ }
159
+
160
+ // src/config/sourcemaps.ts
161
+ var STATIC_DIR = "static";
162
+ var PUBLIC_PATH_PREFIX = "/_next/";
163
+ var PATH_HASH_LENGTH = 8;
164
+ var CHUNK_TAIL_SIZE = 4096;
165
+ var JS_SOURCE_MAPPING_URL = /(?:^|\n)\/\/[#@] sourceMappingURL=([^\s'"`*]+)\s*$/;
166
+ var SOURCE_PATH_PREFIXES = [
167
+ /^turbopack:\/\/\/\[project\]\//,
168
+ /^webpack:\/\/[^/]*\/(\.\/)?/
169
+ ];
170
+ function isRawSourceMap(value) {
171
+ return typeof value === "object" && value !== null && !Array.isArray(value);
172
+ }
173
+ function normalizeSourcePath(source) {
174
+ const prefix = SOURCE_PATH_PREFIXES.find((pattern) => pattern.test(source));
175
+ return prefix !== void 0 ? source.replace(prefix, "") : source;
176
+ }
177
+ function normalizeSources(map) {
178
+ if (Array.isArray(map.sources)) {
179
+ map.sources = map.sources.map((source) => typeof source === "string" ? normalizeSourcePath(source) : source);
180
+ }
181
+ }
182
+ function getSourceMapPath(chunk, content) {
183
+ const url = JS_SOURCE_MAPPING_URL.exec(content)?.[1];
184
+ if (url === void 0 || url.startsWith("/") || /^[a-z][a-z\d+.-]*:/i.test(url)) {
185
+ return void 0;
186
+ }
187
+ try {
188
+ const mapPath = path3.posix.join(path3.posix.dirname(chunk), decodeURIComponent(url));
189
+ return mapPath.startsWith("../") ? void 0 : mapPath;
190
+ } catch (_error) {
191
+ return void 0;
192
+ }
193
+ }
194
+ function encodeUrlPath(filePath) {
195
+ return filePath.split("/").map((segment) => encodeURIComponent(segment)).join("/");
196
+ }
197
+ function getSourceMapName(chunk) {
198
+ const extension = path3.posix.extname(chunk);
199
+ const hash = createHash("sha256").update(chunk).digest("hex").slice(0, PATH_HASH_LENGTH);
200
+ return `${path3.posix.basename(chunk, extension)}.${hash}${extension}.map`;
201
+ }
202
+ async function fileExists(filePath) {
203
+ try {
204
+ await access2(filePath);
205
+ return true;
206
+ } catch (_error) {
207
+ return false;
208
+ }
209
+ }
210
+ async function findClientSourceMaps(distDir) {
211
+ const staticDir = path3.join(distDir, STATIC_DIR);
212
+ const chunks = (await listFiles(staticDir)).filter((entry) => /\.[cm]?js$/.test(entry)).sort();
213
+ const maps = await mapConcurrently(chunks, MAX_PARALLEL_FILE_OPERATIONS, async (chunk) => {
214
+ const mapPath = getSourceMapPath(chunk, await readFileTail(path3.join(staticDir, chunk), CHUNK_TAIL_SIZE));
215
+ if (mapPath === void 0 || !await fileExists(path3.join(staticDir, mapPath))) {
216
+ return void 0;
217
+ }
218
+ return {
219
+ path: path3.join(staticDir, mapPath),
220
+ chunkUrl: `${PUBLIC_PATH_PREFIX}${STATIC_DIR}/${encodeUrlPath(chunk)}`,
221
+ name: getSourceMapName(chunk)
222
+ };
223
+ });
224
+ return maps.filter((map) => map !== void 0);
225
+ }
226
+ function prepareSourceMap(content, chunkUrl) {
227
+ const map = JSON.parse(content);
228
+ if (!isRawSourceMap(map)) {
229
+ throw new Error("Source map is not a JSON object");
230
+ }
231
+ map.file = chunkUrl;
232
+ normalizeSources(map);
233
+ if (Array.isArray(map.sections)) {
234
+ for (const section of map.sections) {
235
+ const sectionMap = isRawSourceMap(section) ? section.map : void 0;
236
+ if (isRawSourceMap(sectionMap)) {
237
+ normalizeSources(sectionMap);
238
+ }
239
+ }
240
+ }
241
+ return JSON.stringify(map);
242
+ }
243
+
244
+ // src/config/cleanup.ts
245
+ var CSS_SOURCE_MAPPING_URL = /(?:^|\n)\/\*[#@] sourceMappingURL=[^\s*]+\s*\*\/\s*$/;
246
+ async function deleteClientSourceMaps(distDir) {
247
+ const staticDir = path4.join(distDir, STATIC_DIR);
248
+ const maps = (await listFiles(staticDir)).filter((entry) => entry.endsWith(".map"));
249
+ await mapConcurrently(maps, MAX_PARALLEL_FILE_OPERATIONS, (entry) => unlink(path4.join(staticDir, entry)));
250
+ return maps.length;
251
+ }
252
+ async function stripSourceMappingUrlComments(distDir) {
253
+ const staticDir = path4.join(distDir, STATIC_DIR);
254
+ const assets = (await listFiles(staticDir)).filter((entry) => /\.([cm]?js|css)$/.test(entry));
255
+ const updated = await mapConcurrently(assets, MAX_PARALLEL_FILE_OPERATIONS, async (entry) => {
256
+ const filePath = path4.join(staticDir, entry);
257
+ const pattern = entry.endsWith(".css") ? CSS_SOURCE_MAPPING_URL : JS_SOURCE_MAPPING_URL;
258
+ const content = await readFile(filePath, "utf8");
259
+ const stripped = content.replace(pattern, "");
260
+ if (stripped === content) {
261
+ return false;
262
+ }
263
+ await writeFile(filePath, stripped);
264
+ return true;
265
+ });
266
+ return updated.filter(Boolean).length;
267
+ }
268
+
269
+ // src/config/commits.ts
270
+ import { execFile } from "child_process";
271
+ import { promisify } from "util";
272
+ var execFileAsync = promisify(execFile);
273
+ var FIELD_SEPARATOR = "";
274
+ var RECORD_SEPARATOR = "";
275
+ async function getCommits(repo, count) {
276
+ const { stdout } = await execFileAsync("git", [
277
+ "log",
278
+ `--max-count=${count}`,
279
+ "--format=%H%x1f%s%x1f%ae%x1f%aI%x1e"
280
+ ], {
281
+ cwd: repo,
282
+ encoding: "utf8"
283
+ });
284
+ return stdout.split(RECORD_SEPARATOR).map((record) => record.trim()).filter((record) => record !== "").map((record) => {
285
+ const [hash, title, author, date] = record.split(FIELD_SEPARATOR);
286
+ return {
287
+ hash,
288
+ title,
289
+ author,
290
+ date
291
+ };
292
+ });
293
+ }
294
+
295
+ // src/config/uploader.ts
296
+ var MAX_RESPONSE_PREVIEW_LENGTH = 200;
297
+ function sanitizeCollectorMessage(message) {
298
+ return message.startsWith("Integration token invalid") ? "Integration token invalid" : message;
299
+ }
300
+ async function send(collector, body) {
301
+ const response = await fetch(collector.endpoint, {
302
+ method: "POST",
303
+ body,
304
+ headers: [["Authorization", `Bearer ${collector.token}`]],
305
+ signal: AbortSignal.timeout(collector.timeout)
306
+ });
307
+ const text = await response.text();
308
+ let data;
309
+ try {
310
+ data = JSON.parse(text);
311
+ } catch (_error) {
312
+ throw new Error(`Unexpected collector response (HTTP ${response.status}): ${text.slice(0, MAX_RESPONSE_PREVIEW_LENGTH)}`);
313
+ }
314
+ if (data.error === true || !response.ok) {
315
+ throw new Error(sanitizeCollectorMessage(data.message ?? `HTTP ${response.status}`));
316
+ }
317
+ }
318
+ async function sendSourceMap(collector, name, content) {
319
+ const body = new FormData();
320
+ body.set("release", collector.release);
321
+ body.set("file", new Blob([content], { type: "application/json" }), name);
322
+ await send(collector, body);
323
+ }
324
+ async function sendCommits(collector, commits) {
325
+ const body = new FormData();
326
+ body.set("release", collector.release);
327
+ body.set("commits", JSON.stringify(commits));
328
+ await send(collector, body);
329
+ }
330
+
331
+ // src/config/utils/log.ts
332
+ var consoleColors = {
333
+ fgRed: 31,
334
+ fgGreen: 32,
335
+ fgYellow: 33,
336
+ fgCyan: 36
337
+ };
338
+ function wrapInColor(message, color) {
339
+ return `\x1B[${color}m${message}\x1B[0m`;
340
+ }
341
+ function log(message, color = consoleColors.fgCyan) {
342
+ console.log(wrapInColor("\u{1F985} Hawk | " + message, color));
343
+ }
344
+ function getErrorMessage(error) {
345
+ return error instanceof Error ? error.message : String(error);
346
+ }
347
+
348
+ // src/config/upload-release.ts
349
+ var MAX_PARALLEL_UPLOADS = 5;
350
+ var REQUEST_TIMEOUT = 3e4;
351
+ function getCollector(options) {
352
+ if (options.token === void 0 || options.token === "") {
353
+ log("Integration Token is not provided, release will not be sent", consoleColors.fgYellow);
354
+ return void 0;
355
+ }
356
+ const integrationId = getIntegrationId(options.token);
357
+ const endpoint = options.collectorEndpoint ?? (integrationId !== void 0 ? getDefaultCollectorEndpoint(integrationId) : void 0);
358
+ if (endpoint === void 0) {
359
+ log("Invalid Integration Token, release will not be sent", consoleColors.fgRed);
360
+ return void 0;
361
+ }
362
+ return {
363
+ endpoint,
364
+ token: options.token,
365
+ release: options.release,
366
+ timeout: REQUEST_TIMEOUT
367
+ };
368
+ }
369
+ async function uploadCommits(collector, repo, count) {
370
+ let commits;
371
+ try {
372
+ commits = await getCommits(repo, count);
373
+ } catch (_error) {
374
+ log("Commits are not sent: git history is not available. Pass `commits: false` to skip them", consoleColors.fgYellow);
375
+ return;
376
+ }
377
+ if (commits.length === 0) {
378
+ return;
379
+ }
380
+ try {
381
+ await sendCommits(collector, commits);
382
+ log(`Recent commits sent: ${commits.length}`, consoleColors.fgGreen);
383
+ } catch (error) {
384
+ log(`(\u2310\u25A0_\u25A0) Sending commits failed: ${getErrorMessage(error)}`, consoleColors.fgRed);
385
+ }
386
+ }
387
+ async function uploadSourceMaps(collector, distDir) {
388
+ const maps = await findClientSourceMaps(distDir);
389
+ if (maps.length === 0) {
390
+ log("No client source maps found. Make sure `productionBrowserSourceMaps` is not disabled in next.config", consoleColors.fgYellow);
391
+ return;
392
+ }
393
+ let sentCount = 0;
394
+ await mapConcurrently(maps, MAX_PARALLEL_UPLOADS, async (map) => {
395
+ try {
396
+ const content = prepareSourceMap(await readFile2(map.path, "utf8"), map.chunkUrl);
397
+ await sendSourceMap(collector, map.name, content);
398
+ sentCount += 1;
399
+ } catch (error) {
400
+ log(`(\u2310\u25A0_\u25A0) ${map.chunkUrl}.map \u2013 sending failed: ${getErrorMessage(error)}`, consoleColors.fgRed);
401
+ }
402
+ });
403
+ const color = sentCount === maps.length ? consoleColors.fgGreen : consoleColors.fgYellow;
404
+ log(`${sentCount} of ${maps.length} source maps sent for release "${collector.release}"`, color);
405
+ }
406
+ async function removeSourceMaps(distDir, stripSourceMappingUrls) {
407
+ const removedCount = await deleteClientSourceMaps(distDir);
408
+ if (removedCount > 0) {
409
+ log(`${removedCount} client source maps removed`);
410
+ }
411
+ if (stripSourceMappingUrls) {
412
+ await stripSourceMappingUrlComments(distDir);
413
+ }
414
+ }
415
+ async function uploadRelease(metadata, options) {
416
+ const startedAt = Date.now();
417
+ try {
418
+ const collector = getCollector(options);
419
+ if (collector === void 0) {
420
+ return;
421
+ }
422
+ try {
423
+ if (options.commits !== false) {
424
+ await uploadCommits(collector, options.commits.repo ?? metadata.projectDir, options.commits.number);
425
+ }
426
+ await uploadSourceMaps(collector, metadata.distDir);
427
+ } finally {
428
+ if (options.removeSourceMaps) {
429
+ await removeSourceMaps(metadata.distDir, options.stripSourceMappingUrls);
430
+ }
431
+ }
432
+ log(`Release "${collector.release}" processed in ${Date.now() - startedAt} ms`);
433
+ } catch (error) {
434
+ log(`(\u2310\u25A0_\u25A0) Release uploading failed: ${getErrorMessage(error)}`, consoleColors.fgRed);
435
+ }
436
+ }
437
+
438
+ // src/config/with-hawk-config.ts
439
+ var MIN_NEXT_VERSION = "15.4.1";
440
+ var DEFAULT_COMMITS_NUMBER = 5;
441
+ var TOKEN_ENV_VARIABLES = ["HAWK_TOKEN", "NEXT_PUBLIC_HAWK_TOKEN"];
442
+ function isVersionAtLeast(version, minVersion) {
443
+ const toNumbers = (value) => value.split("-")[0].split(".").map(Number);
444
+ const actual = toNumbers(version);
445
+ const min = toNumbers(minVersion);
446
+ for (const [i, minPart] of min.entries()) {
447
+ const part = actual[i] ?? 0;
448
+ if (part !== minPart) {
449
+ return part > minPart;
450
+ }
451
+ }
452
+ return true;
453
+ }
454
+ function getNextVersion(projectDir) {
455
+ try {
456
+ const requireFromProject = createRequire(path5.join(projectDir, "package.json"));
457
+ return requireFromProject("next/package.json").version;
458
+ } catch (_error) {
459
+ return void 0;
460
+ }
461
+ }
462
+ function getEnvToken() {
463
+ return TOKEN_ENV_VARIABLES.map((name) => process2.env[name]).find((value) => value !== void 0 && value !== "");
464
+ }
465
+ async function checkInstrumentationFiles(projectDir, pageExtensions) {
466
+ const missing = await findMissingInstrumentationFiles(projectDir, pageExtensions);
467
+ if (missing.length > 0) {
468
+ const files = missing.map((name) => `${name}.ts`).join(" and ");
469
+ log(`${files} not found, so errors are not caught. See https://github.com/codex-team/hawk.nextjs#error-tracking`, consoleColors.fgYellow);
470
+ }
471
+ }
472
+ function getCommitsOptions(commits = true) {
473
+ if (commits === false) {
474
+ return false;
475
+ }
476
+ const custom = commits === true ? {} : commits;
477
+ return {
478
+ repo: custom.repo,
479
+ number: custom.number ?? DEFAULT_COMMITS_NUMBER
480
+ };
481
+ }
482
+ function applyHawkConfig(config, options) {
483
+ const projectDir = process2.cwd();
484
+ const release = resolveRelease(options.release, projectDir);
485
+ process2.env[RELEASE_ENV_VARIABLE] = release;
486
+ const result = {
487
+ ...config,
488
+ env: {
489
+ ...config.env,
490
+ [RELEASE_ENV_VARIABLE]: release
491
+ }
492
+ };
493
+ const nextVersion = getNextVersion(projectDir);
494
+ if (nextVersion !== void 0 && !isVersionAtLeast(nextVersion, MIN_NEXT_VERSION)) {
495
+ log(`Next.js ${nextVersion} is not supported, update it to ${MIN_NEXT_VERSION} or newer to send releases`, consoleColors.fgYellow);
496
+ return result;
497
+ }
498
+ const token = options.token ?? getEnvToken();
499
+ const hasToken = token !== void 0 && token !== "";
500
+ const canUpload = hasToken && (options.collectorEndpoint !== void 0 || getIntegrationId(token) !== void 0);
501
+ if (canUpload && config.productionBrowserSourceMaps === void 0) {
502
+ result.productionBrowserSourceMaps = true;
503
+ }
504
+ const uploadOptions = {
505
+ token,
506
+ release,
507
+ collectorEndpoint: options.collectorEndpoint,
508
+ removeSourceMaps: options.removeSourceMaps ?? true,
509
+ stripSourceMappingUrls: config.experimental?.sri === void 0,
510
+ commits: getCommitsOptions(options.commits)
511
+ };
512
+ const userHook = config.compiler?.runAfterProductionCompile;
513
+ result.compiler = {
514
+ ...config.compiler,
515
+ runAfterProductionCompile: async (metadata) => {
516
+ if (userHook !== void 0) {
517
+ await userHook(metadata);
518
+ }
519
+ await checkInstrumentationFiles(metadata.projectDir, config.pageExtensions);
520
+ await uploadRelease(metadata, uploadOptions);
521
+ }
522
+ };
523
+ return result;
524
+ }
525
+ function withHawkConfig(nextConfig, options = {}) {
526
+ if (typeof nextConfig === "function") {
527
+ return async (phase, context) => applyHawkConfig(await nextConfig(phase, context), options);
528
+ }
529
+ return applyHawkConfig(nextConfig, options);
530
+ }
531
+ export {
532
+ withHawkConfig
533
+ };