@tourmind-frontend/monitor-plugin-nuxt 1.2.0 → 1.3.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/index.cjs CHANGED
@@ -34,8 +34,52 @@ __export(src_exports, {
34
34
  });
35
35
  module.exports = __toCommonJS(src_exports);
36
36
  var import_node_path = require("path");
37
+ var import_node_fs = require("fs");
37
38
  var import_monitor_plugin_webpack = __toESM(require("@tourmind-frontend/monitor-plugin-webpack"));
38
39
  var LOG_PREFIX = "[frontend-monitor]";
40
+ function extractFirstFrame(stack) {
41
+ const re = /\(?([^()\s][^():\n]*?\.(?:vue|ts|tsx|js|jsx|mjs)):(\d+)(?::(\d+))?\)?/;
42
+ for (const raw of stack.split("\n")) {
43
+ const m = raw.match(re);
44
+ if (!m) continue;
45
+ const file = m[1];
46
+ if (!file || file.includes("node_modules") || file.startsWith(".nuxt/")) continue;
47
+ return { file, line: Number(m[2]), col: m[3] ? Number(m[3]) : 0 };
48
+ }
49
+ return null;
50
+ }
51
+ function refineErrorLine(lines, hintLine, hintCol) {
52
+ var _a, _b;
53
+ if (hintCol > 0) return hintLine;
54
+ const isBoilerplate = (s) => /^\s*[\])},;]*\s*$/.test(s);
55
+ const cur = (_a = lines[hintLine - 1]) != null ? _a : "";
56
+ if (!isBoilerplate(cur)) return hintLine;
57
+ for (let i = hintLine - 2; i >= Math.max(0, hintLine - 4); i--) {
58
+ const prev = (_b = lines[i]) != null ? _b : "";
59
+ if (!isBoilerplate(prev) && /[A-Za-z_$][\w$]*/.test(prev)) return i + 1;
60
+ }
61
+ return hintLine;
62
+ }
63
+ function readSourceContext(rootDir, file, hintLine, hintCol, contextLines = 10) {
64
+ const abs = (0, import_node_path.isAbsolute)(file) ? file : (0, import_node_path.resolve)(rootDir, file);
65
+ if (!(0, import_node_fs.existsSync)(abs)) return null;
66
+ try {
67
+ const all = (0, import_node_fs.readFileSync)(abs, "utf-8").split("\n");
68
+ const line = refineErrorLine(all, hintLine, hintCol);
69
+ const idx = line - 1;
70
+ if (idx < 0 || idx >= all.length) return null;
71
+ const start = Math.max(0, idx - contextLines);
72
+ const end = Math.min(all.length - 1, idx + contextLines);
73
+ return {
74
+ file,
75
+ line,
76
+ context: all.slice(start, end + 1).join("\n"),
77
+ context_line: idx - start + 1
78
+ };
79
+ } catch {
80
+ return null;
81
+ }
82
+ }
39
83
  function MonitorModule(options) {
40
84
  if (!options.url) throw new Error(`${LOG_PREFIX} "url" is required`);
41
85
  if (!options.token) throw new Error(`${LOG_PREFIX} "token" is required`);
@@ -61,4 +105,36 @@ function MonitorModule(options) {
61
105
  token: options.token
62
106
  }
63
107
  });
108
+ const ssrReportUrl = `${options.url.replace(/\/+$/, "")}/api/ssr-report`;
109
+ const token = options.token;
110
+ const rootDir = this.options.rootDir || this.options.srcDir || process.cwd();
111
+ this.nuxt.hook("render:errorMiddleware", (app) => {
112
+ app.use((err, req, _res, next) => {
113
+ if (typeof fetch === "function") {
114
+ try {
115
+ const e = err;
116
+ const stack = e && e.stack ? e.stack : String(e && e.message || e);
117
+ const frame = extractFirstFrame(stack);
118
+ const sourceCtx = frame ? readSourceContext(rootDir, frame.file, frame.line, frame.col) : null;
119
+ const refinedStack = sourceCtx && frame && sourceCtx.line !== frame.line ? stack.replace(
120
+ new RegExp(`(${frame.file.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}:)${frame.line}(:)`),
121
+ `$1${sourceCtx.line}$2`
122
+ ) : stack;
123
+ fetch(ssrReportUrl, {
124
+ method: "POST",
125
+ headers: { "Content-Type": "application/json" },
126
+ body: JSON.stringify({
127
+ token,
128
+ stack: refinedStack,
129
+ url: req.url || "",
130
+ ...sourceCtx || {}
131
+ })
132
+ }).catch(() => {
133
+ });
134
+ } catch {
135
+ }
136
+ }
137
+ next(err);
138
+ });
139
+ });
64
140
  }
package/dist/index.d.ts CHANGED
@@ -15,8 +15,20 @@ interface NuxtWebpackConfig {
15
15
  plugins?: unknown[];
16
16
  devtool?: string | false;
17
17
  }
18
+ interface ConnectApp {
19
+ use(handler: (err: unknown, req: {
20
+ url?: string;
21
+ }, res: unknown, next: (err?: unknown) => void) => void): void;
22
+ }
23
+ interface NuxtInstance {
24
+ hook(name: "render:errorMiddleware", handler: (app: ConnectApp) => void): void;
25
+ }
18
26
  interface NuxtModuleThis {
19
- options: unknown;
27
+ options: {
28
+ rootDir?: string;
29
+ srcDir?: string;
30
+ } & Record<string, unknown>;
31
+ nuxt: NuxtInstance;
20
32
  extendBuild(fn: (config: NuxtWebpackConfig, ctx: NuxtBuildContext) => void): void;
21
33
  addPlugin(opts: {
22
34
  src: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tourmind-frontend/monitor-plugin-nuxt",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "Nuxt 2 module that captures runtime errors and uploads sourcemaps to a frontend-monitor server.",
5
5
  "main": "./dist/index.cjs",
6
6
  "types": "./dist/index.d.ts",
@@ -22,7 +22,7 @@
22
22
  "peerDependencies": {
23
23
  "nuxt": "^2.4.0",
24
24
  "vue": "^2.5.0",
25
- "@tourmind-frontend/monitor-tracking": "^1.2.0"
25
+ "@tourmind-frontend/monitor-tracking": "^1.3.0"
26
26
  },
27
27
  "devDependencies": {
28
28
  "@types/node": "^20.0.0",
@@ -7,7 +7,7 @@ export default function () {
7
7
  url: "<%= options.url %>",
8
8
  token: "<%= options.token %>",
9
9
  // 桥接 Vue 2 的 errorHandler,链式调用既有 handler,不覆盖。
10
- callback: function (handler) {
10
+ listener: function (handler) {
11
11
  var existing = Vue.config.errorHandler;
12
12
  Vue.config.errorHandler = function (err, vm, info) {
13
13
  try {