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.
Files changed (2) hide show
  1. package/dist/cli.js +75 -38
  2. 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, readdir, stat, writeFile } from "fs/promises";
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
- var VERSION = "0.6.0";
14
- var CONFIG_DIR = join(homedir(), ".error-mom");
15
- var CONFIG_FILE = join(CONFIG_DIR, "config.json");
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 : basename(process.cwd()));
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 mapFiles = await findMapFiles(dir);
164
- if (mapFiles.length === 0) {
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 mapFiles) {
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 readFile(mapFile, "utf8"));
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 readFile(CONFIG_FILE, "utf8"));
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 readFile(join(cwd, "package.json"), "utf8"));
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(join(cwd, "pnpm-lock.yaml"))) return "pnpm";
476
- if (existsSync(join(cwd, "yarn.lock"))) return "yarn";
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(join(process.cwd(), "src")) ? "src" : ".";
492
- const relativePath = join(sourceDirectory, "error-mom.ts");
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(join(process.cwd(), relativePath), contents);
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 = join(process.cwd(), sourceDirectory, "instrumentation.ts");
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "error-mom",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "Agent-first CLI and MCP tools for self-hosted Error Mom",
5
5
  "type": "module",
6
6
  "bin": {