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