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