error-mom 0.6.0 → 0.7.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/dist/cli.js +75 -38
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -2,17 +2,73 @@
|
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
4
|
import { execFileSync } from "child_process";
|
|
5
|
-
import { chmod, mkdir, readFile
|
|
5
|
+
import { chmod, mkdir, readFile as readFile2, stat, writeFile } from "fs/promises";
|
|
6
6
|
import { existsSync } from "fs";
|
|
7
7
|
import { homedir } from "os";
|
|
8
|
-
import { basename, join } from "path";
|
|
8
|
+
import { basename as basename2, join as join2 } from "path";
|
|
9
9
|
import { Command } from "commander";
|
|
10
10
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
11
11
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
12
12
|
import { z } from "zod";
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
|
|
14
|
+
// src/sourcemap-files.ts
|
|
15
|
+
import { readFile, readdir } from "fs/promises";
|
|
16
|
+
import { basename, dirname, join } from "path";
|
|
17
|
+
async function associateMaps(dir) {
|
|
18
|
+
const { jsFiles, mapFiles } = await findBuildFiles(dir);
|
|
19
|
+
const associations = /* @__PURE__ */ new Map();
|
|
20
|
+
const referencedMaps = /* @__PURE__ */ new Set();
|
|
21
|
+
for (const jsFile of jsFiles) {
|
|
22
|
+
const contents = await readFile(jsFile, "utf8");
|
|
23
|
+
const matches = [...contents.matchAll(/\/\/[#@] sourceMappingURL=([^\s'"]+)/g)];
|
|
24
|
+
const reference = matches[matches.length - 1]?.[1];
|
|
25
|
+
if (!reference || reference.startsWith("data:")) continue;
|
|
26
|
+
const mapFile = join(dirname(jsFile), decodeURIComponent(reference));
|
|
27
|
+
if (!mapFiles.includes(mapFile)) continue;
|
|
28
|
+
const fileName = basename(jsFile);
|
|
29
|
+
referencedMaps.add(mapFile);
|
|
30
|
+
associations.set(`${mapFile}\0${fileName}`, { mapFile, fileName });
|
|
31
|
+
}
|
|
32
|
+
for (const mapFile of mapFiles) {
|
|
33
|
+
if (referencedMaps.has(mapFile)) continue;
|
|
34
|
+
associations.set(`${mapFile}\0`, {
|
|
35
|
+
mapFile,
|
|
36
|
+
fileName: basename(mapFile).replace(/\.map$/, "")
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
return [...associations.values()].sort((a, b) => a.fileName.localeCompare(b.fileName));
|
|
40
|
+
}
|
|
41
|
+
async function findBuildFiles(dir) {
|
|
42
|
+
const jsFiles = [];
|
|
43
|
+
const mapFiles = [];
|
|
44
|
+
let entries;
|
|
45
|
+
try {
|
|
46
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
47
|
+
} catch {
|
|
48
|
+
throw new Error(`Cannot read directory ${dir}.`);
|
|
49
|
+
}
|
|
50
|
+
for (const entry of entries) {
|
|
51
|
+
const fullPath = join(dir, entry.name);
|
|
52
|
+
if (entry.isDirectory() && entry.name !== "node_modules") {
|
|
53
|
+
const nested = await findBuildFiles(fullPath);
|
|
54
|
+
jsFiles.push(...nested.jsFiles);
|
|
55
|
+
mapFiles.push(...nested.mapFiles);
|
|
56
|
+
} else if (entry.isFile() && entry.name.endsWith(".map")) {
|
|
57
|
+
mapFiles.push(fullPath);
|
|
58
|
+
} else if (entry.isFile() && /\.(js|mjs|cjs)$/.test(entry.name)) {
|
|
59
|
+
jsFiles.push(fullPath);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return { jsFiles: jsFiles.sort(), mapFiles: mapFiles.sort() };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// src/cli.ts
|
|
66
|
+
var VERSION = "0.7.0";
|
|
67
|
+
var CONFIG_DIR = join2(homedir(), ".error-mom");
|
|
68
|
+
var CONFIG_FILE = join2(CONFIG_DIR, "config.json");
|
|
69
|
+
function slugifyName(value) {
|
|
70
|
+
return value.toLowerCase().normalize("NFKD").replace(/[\u0300-\u036f]/g, "").replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 64);
|
|
71
|
+
}
|
|
16
72
|
var program = new Command().name("error-mom").description("Query and operate a self-hosted Error Mom incident desk").version(VERSION);
|
|
17
73
|
program.command("login").description("Save the URL and private admin token for one Error Mom deployment").argument("<server>", "Error Mom server URL").requiredOption("--token <token>", "ERROR_MOM_ADMIN_TOKEN value").action(async (server, options) => {
|
|
18
74
|
const normalized = normalizeServer(server);
|
|
@@ -120,14 +176,14 @@ program.command("doctor").description("Verify collector health, credentials, and
|
|
|
120
176
|
program.command("init").description("Create/select a project, install the SDK, and generate framework-aware setup").option("--name <name>", "Project name").option("--project <slug>", "Use an existing project slug").option("--skip-install", "Generate setup without invoking the package manager").action(async (options) => {
|
|
121
177
|
const config = await loadConfig();
|
|
122
178
|
const packageJson = await readPackageJson(process.cwd());
|
|
123
|
-
const name = options.name ?? (typeof packageJson.name === "string" ? packageJson.name :
|
|
179
|
+
const name = options.name ?? (typeof packageJson.name === "string" ? packageJson.name : basename2(process.cwd()));
|
|
124
180
|
const projectResponse = await request(
|
|
125
181
|
config.server,
|
|
126
182
|
config.adminToken,
|
|
127
183
|
"/api/v1/projects"
|
|
128
184
|
);
|
|
129
185
|
let project = options.project ? projectResponse.projects.find((candidate) => candidate.slug === options.project) : projectResponse.projects.find(
|
|
130
|
-
(candidate) => candidate.name.toLowerCase() === name.toLowerCase()
|
|
186
|
+
(candidate) => candidate.name.toLowerCase() === name.toLowerCase() || candidate.slug === slugifyName(name)
|
|
131
187
|
);
|
|
132
188
|
if (!project) {
|
|
133
189
|
const created = await request(config.server, config.adminToken, "/api/v1/projects", {
|
|
@@ -160,14 +216,14 @@ program.command("init").description("Create/select a project, install the SDK, a
|
|
|
160
216
|
});
|
|
161
217
|
program.command("sourcemaps").description("Upload production source maps so minified stacks symbolicate on ingest").argument("<dir>", "Build output directory containing *.map files (e.g. dist)").requiredOption("--release <release>", "Release the maps belong to (must match the SDK release)").requiredOption("--project <id-or-slug>", "Project id or slug").action(async (dir, options) => {
|
|
162
218
|
const config = await loadConfig();
|
|
163
|
-
const
|
|
164
|
-
if (
|
|
219
|
+
const associations = await associateMaps(dir);
|
|
220
|
+
if (associations.length === 0) {
|
|
165
221
|
throw new Error(`No .map files found under ${dir}. Build with source maps enabled first.`);
|
|
166
222
|
}
|
|
167
223
|
const uploaded = [];
|
|
168
224
|
const skipped = [];
|
|
169
225
|
const warnings = [];
|
|
170
|
-
for (const mapFile of
|
|
226
|
+
for (const { mapFile, fileName } of associations) {
|
|
171
227
|
const info = await stat(mapFile);
|
|
172
228
|
if (info.size > 20 * 1024 * 1024) {
|
|
173
229
|
skipped.push({ file: mapFile, reason: "larger than 20 MB" });
|
|
@@ -175,12 +231,11 @@ program.command("sourcemaps").description("Upload production source maps so mini
|
|
|
175
231
|
}
|
|
176
232
|
let map;
|
|
177
233
|
try {
|
|
178
|
-
map = JSON.parse(await
|
|
234
|
+
map = JSON.parse(await readFile2(mapFile, "utf8"));
|
|
179
235
|
} catch {
|
|
180
236
|
skipped.push({ file: mapFile, reason: "not valid JSON" });
|
|
181
237
|
continue;
|
|
182
238
|
}
|
|
183
|
-
const fileName = basename(mapFile).replace(/\.map$/, "");
|
|
184
239
|
const sourcesContent = map.sourcesContent;
|
|
185
240
|
if (!Array.isArray(sourcesContent) || sourcesContent.every((entry) => entry == null)) {
|
|
186
241
|
warnings.push(
|
|
@@ -318,7 +373,7 @@ async function loadConfig() {
|
|
|
318
373
|
};
|
|
319
374
|
}
|
|
320
375
|
try {
|
|
321
|
-
const config = JSON.parse(await
|
|
376
|
+
const config = JSON.parse(await readFile2(CONFIG_FILE, "utf8"));
|
|
322
377
|
if (!config.server || !config.adminToken) throw new Error("Incomplete config");
|
|
323
378
|
return { server: normalizeServer(config.server), adminToken: config.adminToken };
|
|
324
379
|
} catch {
|
|
@@ -344,7 +399,7 @@ async function request(server, token, path, options = {}) {
|
|
|
344
399
|
}
|
|
345
400
|
async function readPackageJson(cwd) {
|
|
346
401
|
try {
|
|
347
|
-
return JSON.parse(await
|
|
402
|
+
return JSON.parse(await readFile2(join2(cwd, "package.json"), "utf8"));
|
|
348
403
|
} catch {
|
|
349
404
|
throw new Error(
|
|
350
405
|
"Run error-mom init from a JavaScript or TypeScript project containing package.json."
|
|
@@ -472,8 +527,8 @@ function detectFramework(packageJson) {
|
|
|
472
527
|
};
|
|
473
528
|
}
|
|
474
529
|
function detectPackageManager(cwd) {
|
|
475
|
-
if (existsSync(
|
|
476
|
-
if (existsSync(
|
|
530
|
+
if (existsSync(join2(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
531
|
+
if (existsSync(join2(cwd, "yarn.lock"))) return "yarn";
|
|
477
532
|
return "npm";
|
|
478
533
|
}
|
|
479
534
|
function installSdk(packageManager) {
|
|
@@ -488,8 +543,8 @@ function installSdk(packageManager) {
|
|
|
488
543
|
});
|
|
489
544
|
}
|
|
490
545
|
async function writeSetup(framework, server, ingestKey) {
|
|
491
|
-
const sourceDirectory = existsSync(
|
|
492
|
-
const relativePath =
|
|
546
|
+
const sourceDirectory = existsSync(join2(process.cwd(), "src")) ? "src" : ".";
|
|
547
|
+
const relativePath = join2(sourceDirectory, "error-mom.ts");
|
|
493
548
|
if (framework.id === "next") await writeNextInstrumentation(sourceDirectory, server, ingestKey);
|
|
494
549
|
const environment = framework.envStyle === "next" ? "process.env.NEXT_PUBLIC_" : framework.envStyle === "vite" ? "import.meta.env.VITE_" : "process.env.";
|
|
495
550
|
const moduleName = framework.envStyle === "node" ? "@kenkaiiii/error-mom/node" : "@kenkaiiii/error-mom";
|
|
@@ -508,11 +563,11 @@ export const errorMom = initErrorMom({
|
|
|
508
563
|
...(release ? { release } : {}),
|
|
509
564
|
});
|
|
510
565
|
`;
|
|
511
|
-
await writeFile(
|
|
566
|
+
await writeFile(join2(process.cwd(), relativePath), contents);
|
|
512
567
|
return relativePath;
|
|
513
568
|
}
|
|
514
569
|
async function writeNextInstrumentation(sourceDirectory, configuredServer, ingestKey) {
|
|
515
|
-
const file =
|
|
570
|
+
const file = join2(process.cwd(), sourceDirectory, "instrumentation.ts");
|
|
516
571
|
if (existsSync(file)) return;
|
|
517
572
|
const contents = `import type { Instrumentation } from "next";
|
|
518
573
|
|
|
@@ -585,24 +640,6 @@ async function releaseMismatchWarning(config, projectIdOrSlug, release) {
|
|
|
585
640
|
return [];
|
|
586
641
|
}
|
|
587
642
|
}
|
|
588
|
-
async function findMapFiles(dir) {
|
|
589
|
-
const found = [];
|
|
590
|
-
let entries;
|
|
591
|
-
try {
|
|
592
|
-
entries = await readdir(dir, { withFileTypes: true });
|
|
593
|
-
} catch {
|
|
594
|
-
throw new Error(`Cannot read directory ${dir}.`);
|
|
595
|
-
}
|
|
596
|
-
for (const entry of entries) {
|
|
597
|
-
const fullPath = join(dir, entry.name);
|
|
598
|
-
if (entry.isDirectory() && entry.name !== "node_modules") {
|
|
599
|
-
found.push(...await findMapFiles(fullPath));
|
|
600
|
-
} else if (entry.isFile() && entry.name.endsWith(".map")) {
|
|
601
|
-
found.push(fullPath);
|
|
602
|
-
}
|
|
603
|
-
}
|
|
604
|
-
return found.sort();
|
|
605
|
-
}
|
|
606
643
|
function normalizeServer(server) {
|
|
607
644
|
return server.replace(/\/$/, "");
|
|
608
645
|
}
|