@hawk.so/nextjs 1.0.0 → 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.
@@ -27,19 +27,44 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
27
27
  ));
28
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
29
 
30
- // src/index.ts
31
- var index_exports = {};
32
- __export(index_exports, {
30
+ // src/config/index.ts
31
+ var config_exports = {};
32
+ __export(config_exports, {
33
33
  withHawkConfig: () => withHawkConfig
34
34
  });
35
- module.exports = __toCommonJS(index_exports);
35
+ module.exports = __toCommonJS(config_exports);
36
36
 
37
- // src/with-hawk-config.ts
37
+ // src/config/with-hawk-config.ts
38
38
  var import_node_module = require("module");
39
- var import_node_path4 = __toESM(require("path"), 1);
39
+ var import_node_path5 = __toESM(require("path"), 1);
40
40
  var import_node_process2 = __toESM(require("process"), 1);
41
41
 
42
- // src/release.ts
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
43
68
  var import_node_child_process = require("child_process");
44
69
  var import_node_process = __toESM(require("process"), 1);
45
70
  var RELEASE_ENV_VARIABLE = "HAWK_RELEASE";
@@ -85,7 +110,7 @@ function resolveRelease(release, cwd) {
85
110
  return getGitRevision(cwd) ?? (/* @__PURE__ */ new Date()).toISOString();
86
111
  }
87
112
 
88
- // src/token.ts
113
+ // src/config/token.ts
89
114
  var import_node_buffer = require("buffer");
90
115
  function getIntegrationId(token) {
91
116
  try {
@@ -101,29 +126,29 @@ function getDefaultCollectorEndpoint(integrationId) {
101
126
  return `https://${integrationId}.k1.hawk.so/release`;
102
127
  }
103
128
 
104
- // src/upload-release.ts
129
+ // src/config/upload-release.ts
130
+ var import_promises5 = require("fs/promises");
131
+
132
+ // src/config/cleanup.ts
105
133
  var import_promises4 = require("fs/promises");
134
+ var import_node_path4 = __toESM(require("path"), 1);
106
135
 
107
- // src/cleanup.ts
136
+ // src/config/sourcemaps.ts
137
+ var import_node_crypto = require("crypto");
108
138
  var import_promises3 = require("fs/promises");
109
139
  var import_node_path3 = __toESM(require("path"), 1);
110
140
 
111
- // src/sourcemaps.ts
112
- var import_node_crypto = require("crypto");
141
+ // src/config/utils/fs.ts
142
+ var import_node_buffer2 = require("buffer");
113
143
  var import_promises2 = require("fs/promises");
114
144
  var import_node_path2 = __toESM(require("path"), 1);
115
-
116
- // src/utils/fs.ts
117
- var import_node_buffer2 = require("buffer");
118
- var import_promises = require("fs/promises");
119
- var import_node_path = __toESM(require("path"), 1);
120
145
  var MAX_PARALLEL_FILE_OPERATIONS = 16;
121
146
  async function listFiles(dir) {
122
147
  try {
123
- const entries = await (0, import_promises.readdir)(dir, { withFileTypes: true });
148
+ const entries = await (0, import_promises2.readdir)(dir, { withFileTypes: true });
124
149
  const files = await Promise.all(entries.map(async (entry) => {
125
150
  if (entry.isDirectory()) {
126
- return (await listFiles(import_node_path.default.join(dir, entry.name))).map((file) => `${entry.name}/${file}`);
151
+ return (await listFiles(import_node_path2.default.join(dir, entry.name))).map((file) => `${entry.name}/${file}`);
127
152
  }
128
153
  return entry.isFile() ? [entry.name] : [];
129
154
  }));
@@ -136,7 +161,7 @@ async function listFiles(dir) {
136
161
  }
137
162
  }
138
163
  async function readFileTail(filePath, maxBytes) {
139
- const file = await (0, import_promises.open)(filePath, "r");
164
+ const file = await (0, import_promises2.open)(filePath, "r");
140
165
  try {
141
166
  const { size } = await file.stat();
142
167
  const length = Math.min(maxBytes, size);
@@ -152,7 +177,7 @@ async function readFileTail(filePath, maxBytes) {
152
177
  }
153
178
  }
154
179
 
155
- // src/utils/pool.ts
180
+ // src/config/utils/pool.ts
156
181
  async function mapConcurrently(items, limit, mapper) {
157
182
  const results = [];
158
183
  let nextIndex = 0;
@@ -168,7 +193,7 @@ async function mapConcurrently(items, limit, mapper) {
168
193
  return results;
169
194
  }
170
195
 
171
- // src/sourcemaps.ts
196
+ // src/config/sourcemaps.ts
172
197
  var STATIC_DIR = "static";
173
198
  var PUBLIC_PATH_PREFIX = "/_next/";
174
199
  var PATH_HASH_LENGTH = 8;
@@ -196,7 +221,7 @@ function getSourceMapPath(chunk, content) {
196
221
  return void 0;
197
222
  }
198
223
  try {
199
- const mapPath = import_node_path2.default.posix.join(import_node_path2.default.posix.dirname(chunk), decodeURIComponent(url));
224
+ const mapPath = import_node_path3.default.posix.join(import_node_path3.default.posix.dirname(chunk), decodeURIComponent(url));
200
225
  return mapPath.startsWith("../") ? void 0 : mapPath;
201
226
  } catch (_error) {
202
227
  return void 0;
@@ -206,28 +231,28 @@ function encodeUrlPath(filePath) {
206
231
  return filePath.split("/").map((segment) => encodeURIComponent(segment)).join("/");
207
232
  }
208
233
  function getSourceMapName(chunk) {
209
- const extension = import_node_path2.default.posix.extname(chunk);
234
+ const extension = import_node_path3.default.posix.extname(chunk);
210
235
  const hash = (0, import_node_crypto.createHash)("sha256").update(chunk).digest("hex").slice(0, PATH_HASH_LENGTH);
211
- return `${import_node_path2.default.posix.basename(chunk, extension)}.${hash}${extension}.map`;
236
+ return `${import_node_path3.default.posix.basename(chunk, extension)}.${hash}${extension}.map`;
212
237
  }
213
238
  async function fileExists(filePath) {
214
239
  try {
215
- await (0, import_promises2.access)(filePath);
240
+ await (0, import_promises3.access)(filePath);
216
241
  return true;
217
242
  } catch (_error) {
218
243
  return false;
219
244
  }
220
245
  }
221
246
  async function findClientSourceMaps(distDir) {
222
- const staticDir = import_node_path2.default.join(distDir, STATIC_DIR);
247
+ const staticDir = import_node_path3.default.join(distDir, STATIC_DIR);
223
248
  const chunks = (await listFiles(staticDir)).filter((entry) => /\.[cm]?js$/.test(entry)).sort();
224
249
  const maps = await mapConcurrently(chunks, MAX_PARALLEL_FILE_OPERATIONS, async (chunk) => {
225
- const mapPath = getSourceMapPath(chunk, await readFileTail(import_node_path2.default.join(staticDir, chunk), CHUNK_TAIL_SIZE));
226
- if (mapPath === void 0 || !await fileExists(import_node_path2.default.join(staticDir, mapPath))) {
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))) {
227
252
  return void 0;
228
253
  }
229
254
  return {
230
- path: import_node_path2.default.join(staticDir, mapPath),
255
+ path: import_node_path3.default.join(staticDir, mapPath),
231
256
  chunkUrl: `${PUBLIC_PATH_PREFIX}${STATIC_DIR}/${encodeUrlPath(chunk)}`,
232
257
  name: getSourceMapName(chunk)
233
258
  };
@@ -252,32 +277,32 @@ function prepareSourceMap(content, chunkUrl) {
252
277
  return JSON.stringify(map);
253
278
  }
254
279
 
255
- // src/cleanup.ts
280
+ // src/config/cleanup.ts
256
281
  var CSS_SOURCE_MAPPING_URL = /(?:^|\n)\/\*[#@] sourceMappingURL=[^\s*]+\s*\*\/\s*$/;
257
282
  async function deleteClientSourceMaps(distDir) {
258
- const staticDir = import_node_path3.default.join(distDir, STATIC_DIR);
283
+ const staticDir = import_node_path4.default.join(distDir, STATIC_DIR);
259
284
  const maps = (await listFiles(staticDir)).filter((entry) => entry.endsWith(".map"));
260
- await mapConcurrently(maps, MAX_PARALLEL_FILE_OPERATIONS, (entry) => (0, import_promises3.unlink)(import_node_path3.default.join(staticDir, entry)));
285
+ await mapConcurrently(maps, MAX_PARALLEL_FILE_OPERATIONS, (entry) => (0, import_promises4.unlink)(import_node_path4.default.join(staticDir, entry)));
261
286
  return maps.length;
262
287
  }
263
288
  async function stripSourceMappingUrlComments(distDir) {
264
- const staticDir = import_node_path3.default.join(distDir, STATIC_DIR);
289
+ const staticDir = import_node_path4.default.join(distDir, STATIC_DIR);
265
290
  const assets = (await listFiles(staticDir)).filter((entry) => /\.([cm]?js|css)$/.test(entry));
266
291
  const updated = await mapConcurrently(assets, MAX_PARALLEL_FILE_OPERATIONS, async (entry) => {
267
- const filePath = import_node_path3.default.join(staticDir, entry);
292
+ const filePath = import_node_path4.default.join(staticDir, entry);
268
293
  const pattern = entry.endsWith(".css") ? CSS_SOURCE_MAPPING_URL : JS_SOURCE_MAPPING_URL;
269
- const content = await (0, import_promises3.readFile)(filePath, "utf8");
294
+ const content = await (0, import_promises4.readFile)(filePath, "utf8");
270
295
  const stripped = content.replace(pattern, "");
271
296
  if (stripped === content) {
272
297
  return false;
273
298
  }
274
- await (0, import_promises3.writeFile)(filePath, stripped);
299
+ await (0, import_promises4.writeFile)(filePath, stripped);
275
300
  return true;
276
301
  });
277
302
  return updated.filter(Boolean).length;
278
303
  }
279
304
 
280
- // src/commits.ts
305
+ // src/config/commits.ts
281
306
  var import_node_child_process2 = require("child_process");
282
307
  var import_node_util = require("util");
283
308
  var execFileAsync = (0, import_node_util.promisify)(import_node_child_process2.execFile);
@@ -303,7 +328,7 @@ async function getCommits(repo, count) {
303
328
  });
304
329
  }
305
330
 
306
- // src/uploader.ts
331
+ // src/config/uploader.ts
307
332
  var MAX_RESPONSE_PREVIEW_LENGTH = 200;
308
333
  function sanitizeCollectorMessage(message) {
309
334
  return message.startsWith("Integration token invalid") ? "Integration token invalid" : message;
@@ -339,7 +364,7 @@ async function sendCommits(collector, commits) {
339
364
  await send(collector, body);
340
365
  }
341
366
 
342
- // src/utils/log.ts
367
+ // src/config/utils/log.ts
343
368
  var consoleColors = {
344
369
  fgRed: 31,
345
370
  fgGreen: 32,
@@ -356,7 +381,7 @@ function getErrorMessage(error) {
356
381
  return error instanceof Error ? error.message : String(error);
357
382
  }
358
383
 
359
- // src/upload-release.ts
384
+ // src/config/upload-release.ts
360
385
  var MAX_PARALLEL_UPLOADS = 5;
361
386
  var REQUEST_TIMEOUT = 3e4;
362
387
  function getCollector(options) {
@@ -404,7 +429,7 @@ async function uploadSourceMaps(collector, distDir) {
404
429
  let sentCount = 0;
405
430
  await mapConcurrently(maps, MAX_PARALLEL_UPLOADS, async (map) => {
406
431
  try {
407
- const content = prepareSourceMap(await (0, import_promises4.readFile)(map.path, "utf8"), map.chunkUrl);
432
+ const content = prepareSourceMap(await (0, import_promises5.readFile)(map.path, "utf8"), map.chunkUrl);
408
433
  await sendSourceMap(collector, map.name, content);
409
434
  sentCount += 1;
410
435
  } catch (error) {
@@ -446,9 +471,10 @@ async function uploadRelease(metadata, options) {
446
471
  }
447
472
  }
448
473
 
449
- // src/with-hawk-config.ts
474
+ // src/config/with-hawk-config.ts
450
475
  var MIN_NEXT_VERSION = "15.4.1";
451
476
  var DEFAULT_COMMITS_NUMBER = 5;
477
+ var TOKEN_ENV_VARIABLES = ["HAWK_TOKEN", "NEXT_PUBLIC_HAWK_TOKEN"];
452
478
  function isVersionAtLeast(version, minVersion) {
453
479
  const toNumbers = (value) => value.split("-")[0].split(".").map(Number);
454
480
  const actual = toNumbers(version);
@@ -463,12 +489,22 @@ function isVersionAtLeast(version, minVersion) {
463
489
  }
464
490
  function getNextVersion(projectDir) {
465
491
  try {
466
- const requireFromProject = (0, import_node_module.createRequire)(import_node_path4.default.join(projectDir, "package.json"));
492
+ const requireFromProject = (0, import_node_module.createRequire)(import_node_path5.default.join(projectDir, "package.json"));
467
493
  return requireFromProject("next/package.json").version;
468
494
  } catch (_error) {
469
495
  return void 0;
470
496
  }
471
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
+ }
472
508
  function getCommitsOptions(commits = true) {
473
509
  if (commits === false) {
474
510
  return false;
@@ -495,7 +531,7 @@ function applyHawkConfig(config, options) {
495
531
  log(`Next.js ${nextVersion} is not supported, update it to ${MIN_NEXT_VERSION} or newer to send releases`, consoleColors.fgYellow);
496
532
  return result;
497
533
  }
498
- const token = options.token ?? import_node_process2.default.env.HAWK_TOKEN;
534
+ const token = options.token ?? getEnvToken();
499
535
  const hasToken = token !== void 0 && token !== "";
500
536
  const canUpload = hasToken && (options.collectorEndpoint !== void 0 || getIntegrationId(token) !== void 0);
501
537
  if (canUpload && config.productionBrowserSourceMaps === void 0) {
@@ -516,6 +552,7 @@ function applyHawkConfig(config, options) {
516
552
  if (userHook !== void 0) {
517
553
  await userHook(metadata);
518
554
  }
555
+ await checkInstrumentationFiles(metadata.projectDir, config.pageExtensions);
519
556
  await uploadRelease(metadata, uploadOptions);
520
557
  }
521
558
  };
@@ -20,7 +20,7 @@ interface CommitsOptions {
20
20
  */
21
21
  interface HawkNextOptions {
22
22
  /**
23
- * Hawk Integration Token. Default is the HAWK_TOKEN environment variable
23
+ * Hawk Integration Token. Default is the HAWK_TOKEN or NEXT_PUBLIC_HAWK_TOKEN environment variable
24
24
  */
25
25
  token?: string;
26
26
  /**
@@ -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 };
@@ -1,9 +1,34 @@
1
- // src/with-hawk-config.ts
1
+ // src/config/with-hawk-config.ts
2
2
  import { createRequire } from "module";
3
- import path4 from "path";
3
+ import path5 from "path";
4
4
  import process2 from "process";
5
5
 
6
- // src/release.ts
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
7
32
  import { execFileSync } from "child_process";
8
33
  import process from "process";
9
34
  var RELEASE_ENV_VARIABLE = "HAWK_RELEASE";
@@ -49,7 +74,7 @@ function resolveRelease(release, cwd) {
49
74
  return getGitRevision(cwd) ?? (/* @__PURE__ */ new Date()).toISOString();
50
75
  }
51
76
 
52
- // src/token.ts
77
+ // src/config/token.ts
53
78
  import { Buffer } from "buffer";
54
79
  function getIntegrationId(token) {
55
80
  try {
@@ -65,29 +90,29 @@ function getDefaultCollectorEndpoint(integrationId) {
65
90
  return `https://${integrationId}.k1.hawk.so/release`;
66
91
  }
67
92
 
68
- // src/upload-release.ts
93
+ // src/config/upload-release.ts
69
94
  import { readFile as readFile2 } from "fs/promises";
70
95
 
71
- // src/cleanup.ts
96
+ // src/config/cleanup.ts
72
97
  import { readFile, unlink, writeFile } from "fs/promises";
73
- import path3 from "path";
98
+ import path4 from "path";
74
99
 
75
- // src/sourcemaps.ts
100
+ // src/config/sourcemaps.ts
76
101
  import { createHash } from "crypto";
77
- import { access } from "fs/promises";
78
- import path2 from "path";
102
+ import { access as access2 } from "fs/promises";
103
+ import path3 from "path";
79
104
 
80
- // src/utils/fs.ts
105
+ // src/config/utils/fs.ts
81
106
  import { Buffer as Buffer2 } from "buffer";
82
107
  import { open, readdir } from "fs/promises";
83
- import path from "path";
108
+ import path2 from "path";
84
109
  var MAX_PARALLEL_FILE_OPERATIONS = 16;
85
110
  async function listFiles(dir) {
86
111
  try {
87
112
  const entries = await readdir(dir, { withFileTypes: true });
88
113
  const files = await Promise.all(entries.map(async (entry) => {
89
114
  if (entry.isDirectory()) {
90
- return (await listFiles(path.join(dir, entry.name))).map((file) => `${entry.name}/${file}`);
115
+ return (await listFiles(path2.join(dir, entry.name))).map((file) => `${entry.name}/${file}`);
91
116
  }
92
117
  return entry.isFile() ? [entry.name] : [];
93
118
  }));
@@ -116,7 +141,7 @@ async function readFileTail(filePath, maxBytes) {
116
141
  }
117
142
  }
118
143
 
119
- // src/utils/pool.ts
144
+ // src/config/utils/pool.ts
120
145
  async function mapConcurrently(items, limit, mapper) {
121
146
  const results = [];
122
147
  let nextIndex = 0;
@@ -132,7 +157,7 @@ async function mapConcurrently(items, limit, mapper) {
132
157
  return results;
133
158
  }
134
159
 
135
- // src/sourcemaps.ts
160
+ // src/config/sourcemaps.ts
136
161
  var STATIC_DIR = "static";
137
162
  var PUBLIC_PATH_PREFIX = "/_next/";
138
163
  var PATH_HASH_LENGTH = 8;
@@ -160,7 +185,7 @@ function getSourceMapPath(chunk, content) {
160
185
  return void 0;
161
186
  }
162
187
  try {
163
- const mapPath = path2.posix.join(path2.posix.dirname(chunk), decodeURIComponent(url));
188
+ const mapPath = path3.posix.join(path3.posix.dirname(chunk), decodeURIComponent(url));
164
189
  return mapPath.startsWith("../") ? void 0 : mapPath;
165
190
  } catch (_error) {
166
191
  return void 0;
@@ -170,28 +195,28 @@ function encodeUrlPath(filePath) {
170
195
  return filePath.split("/").map((segment) => encodeURIComponent(segment)).join("/");
171
196
  }
172
197
  function getSourceMapName(chunk) {
173
- const extension = path2.posix.extname(chunk);
198
+ const extension = path3.posix.extname(chunk);
174
199
  const hash = createHash("sha256").update(chunk).digest("hex").slice(0, PATH_HASH_LENGTH);
175
- return `${path2.posix.basename(chunk, extension)}.${hash}${extension}.map`;
200
+ return `${path3.posix.basename(chunk, extension)}.${hash}${extension}.map`;
176
201
  }
177
202
  async function fileExists(filePath) {
178
203
  try {
179
- await access(filePath);
204
+ await access2(filePath);
180
205
  return true;
181
206
  } catch (_error) {
182
207
  return false;
183
208
  }
184
209
  }
185
210
  async function findClientSourceMaps(distDir) {
186
- const staticDir = path2.join(distDir, STATIC_DIR);
211
+ const staticDir = path3.join(distDir, STATIC_DIR);
187
212
  const chunks = (await listFiles(staticDir)).filter((entry) => /\.[cm]?js$/.test(entry)).sort();
188
213
  const maps = await mapConcurrently(chunks, MAX_PARALLEL_FILE_OPERATIONS, async (chunk) => {
189
- const mapPath = getSourceMapPath(chunk, await readFileTail(path2.join(staticDir, chunk), CHUNK_TAIL_SIZE));
190
- if (mapPath === void 0 || !await fileExists(path2.join(staticDir, mapPath))) {
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))) {
191
216
  return void 0;
192
217
  }
193
218
  return {
194
- path: path2.join(staticDir, mapPath),
219
+ path: path3.join(staticDir, mapPath),
195
220
  chunkUrl: `${PUBLIC_PATH_PREFIX}${STATIC_DIR}/${encodeUrlPath(chunk)}`,
196
221
  name: getSourceMapName(chunk)
197
222
  };
@@ -216,19 +241,19 @@ function prepareSourceMap(content, chunkUrl) {
216
241
  return JSON.stringify(map);
217
242
  }
218
243
 
219
- // src/cleanup.ts
244
+ // src/config/cleanup.ts
220
245
  var CSS_SOURCE_MAPPING_URL = /(?:^|\n)\/\*[#@] sourceMappingURL=[^\s*]+\s*\*\/\s*$/;
221
246
  async function deleteClientSourceMaps(distDir) {
222
- const staticDir = path3.join(distDir, STATIC_DIR);
247
+ const staticDir = path4.join(distDir, STATIC_DIR);
223
248
  const maps = (await listFiles(staticDir)).filter((entry) => entry.endsWith(".map"));
224
- await mapConcurrently(maps, MAX_PARALLEL_FILE_OPERATIONS, (entry) => unlink(path3.join(staticDir, entry)));
249
+ await mapConcurrently(maps, MAX_PARALLEL_FILE_OPERATIONS, (entry) => unlink(path4.join(staticDir, entry)));
225
250
  return maps.length;
226
251
  }
227
252
  async function stripSourceMappingUrlComments(distDir) {
228
- const staticDir = path3.join(distDir, STATIC_DIR);
253
+ const staticDir = path4.join(distDir, STATIC_DIR);
229
254
  const assets = (await listFiles(staticDir)).filter((entry) => /\.([cm]?js|css)$/.test(entry));
230
255
  const updated = await mapConcurrently(assets, MAX_PARALLEL_FILE_OPERATIONS, async (entry) => {
231
- const filePath = path3.join(staticDir, entry);
256
+ const filePath = path4.join(staticDir, entry);
232
257
  const pattern = entry.endsWith(".css") ? CSS_SOURCE_MAPPING_URL : JS_SOURCE_MAPPING_URL;
233
258
  const content = await readFile(filePath, "utf8");
234
259
  const stripped = content.replace(pattern, "");
@@ -241,7 +266,7 @@ async function stripSourceMappingUrlComments(distDir) {
241
266
  return updated.filter(Boolean).length;
242
267
  }
243
268
 
244
- // src/commits.ts
269
+ // src/config/commits.ts
245
270
  import { execFile } from "child_process";
246
271
  import { promisify } from "util";
247
272
  var execFileAsync = promisify(execFile);
@@ -267,7 +292,7 @@ async function getCommits(repo, count) {
267
292
  });
268
293
  }
269
294
 
270
- // src/uploader.ts
295
+ // src/config/uploader.ts
271
296
  var MAX_RESPONSE_PREVIEW_LENGTH = 200;
272
297
  function sanitizeCollectorMessage(message) {
273
298
  return message.startsWith("Integration token invalid") ? "Integration token invalid" : message;
@@ -303,7 +328,7 @@ async function sendCommits(collector, commits) {
303
328
  await send(collector, body);
304
329
  }
305
330
 
306
- // src/utils/log.ts
331
+ // src/config/utils/log.ts
307
332
  var consoleColors = {
308
333
  fgRed: 31,
309
334
  fgGreen: 32,
@@ -320,7 +345,7 @@ function getErrorMessage(error) {
320
345
  return error instanceof Error ? error.message : String(error);
321
346
  }
322
347
 
323
- // src/upload-release.ts
348
+ // src/config/upload-release.ts
324
349
  var MAX_PARALLEL_UPLOADS = 5;
325
350
  var REQUEST_TIMEOUT = 3e4;
326
351
  function getCollector(options) {
@@ -410,9 +435,10 @@ async function uploadRelease(metadata, options) {
410
435
  }
411
436
  }
412
437
 
413
- // src/with-hawk-config.ts
438
+ // src/config/with-hawk-config.ts
414
439
  var MIN_NEXT_VERSION = "15.4.1";
415
440
  var DEFAULT_COMMITS_NUMBER = 5;
441
+ var TOKEN_ENV_VARIABLES = ["HAWK_TOKEN", "NEXT_PUBLIC_HAWK_TOKEN"];
416
442
  function isVersionAtLeast(version, minVersion) {
417
443
  const toNumbers = (value) => value.split("-")[0].split(".").map(Number);
418
444
  const actual = toNumbers(version);
@@ -427,12 +453,22 @@ function isVersionAtLeast(version, minVersion) {
427
453
  }
428
454
  function getNextVersion(projectDir) {
429
455
  try {
430
- const requireFromProject = createRequire(path4.join(projectDir, "package.json"));
456
+ const requireFromProject = createRequire(path5.join(projectDir, "package.json"));
431
457
  return requireFromProject("next/package.json").version;
432
458
  } catch (_error) {
433
459
  return void 0;
434
460
  }
435
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
+ }
436
472
  function getCommitsOptions(commits = true) {
437
473
  if (commits === false) {
438
474
  return false;
@@ -459,7 +495,7 @@ function applyHawkConfig(config, options) {
459
495
  log(`Next.js ${nextVersion} is not supported, update it to ${MIN_NEXT_VERSION} or newer to send releases`, consoleColors.fgYellow);
460
496
  return result;
461
497
  }
462
- const token = options.token ?? process2.env.HAWK_TOKEN;
498
+ const token = options.token ?? getEnvToken();
463
499
  const hasToken = token !== void 0 && token !== "";
464
500
  const canUpload = hasToken && (options.collectorEndpoint !== void 0 || getIntegrationId(token) !== void 0);
465
501
  if (canUpload && config.productionBrowserSourceMaps === void 0) {
@@ -480,6 +516,7 @@ function applyHawkConfig(config, options) {
480
516
  if (userHook !== void 0) {
481
517
  await userHook(metadata);
482
518
  }
519
+ await checkInstrumentationFiles(metadata.projectDir, config.pageExtensions);
483
520
  await uploadRelease(metadata, uploadOptions);
484
521
  }
485
522
  };