@kenkaiiii/gg-pixel 4.3.69 → 4.3.71
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 +367 -11
- package/dist/cli.js.map +1 -1
- package/dist/deno.d.ts +63 -0
- package/dist/deno.js +328 -0
- package/dist/deno.js.map +1 -0
- package/dist/index.cjs +360 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -1
- package/dist/index.d.ts +8 -1
- package/dist/index.js +360 -11
- package/dist/index.js.map +1 -1
- package/package.json +5 -1
package/dist/deno.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/stack-web.ts","../src/core/fingerprint-web.ts","../src/core/queue.ts","../src/adapters/deno.ts","../src/core/sinks/http.ts","../src/deno.ts"],"sourcesContent":["import type { StackFrame } from \"./types.js\";\n\n/**\n * Cross-browser stack-trace parser.\n *\n * Browser stack formats differ wildly. We support two — that covers\n * Chrome/Edge/Safari (V8-shaped) and Firefox (Gecko-shaped). Real-world\n * coverage cited by Sentry's parsers in `packages/browser/src/stack-parsers.ts`.\n */\n\n// Chrome / V8 / Edge / modern Safari. Examples:\n// \" at fnName (https://app.com/main.js:42:13)\"\n// \" at https://app.com/main.js:42:13\"\n// \" at fnName (eval at <anonymous> (https://x/y.js:1:1), <anonymous>:1:5)\"\nconst CHROME_FRAME = /^\\s*at\\s+(?:(.+?)\\s+\\()?(?:(.+?):(\\d+):(\\d+))(?:\\))?\\s*$/;\n\n// Firefox / Gecko. Examples:\n// \"fnName@https://app.com/main.js:42:13\"\n// \"@https://app.com/main.js:42:13\"\nconst GECKO_FRAME = /^\\s*(.*?)@(.+?):(\\d+)(?::(\\d+))?\\s*$/;\n\nexport function parseBrowserStack(stack: string | undefined, sameOrigin?: string): StackFrame[] {\n if (!stack) return [];\n const frames: StackFrame[] = [];\n for (const line of stack.split(\"\\n\")) {\n const trimmed = line.trim();\n if (!trimmed) continue;\n // Skip the leading \"TypeError: x\" header line — no `at` prefix and no `@`.\n if (!/^(?:at\\s|.*@)/.test(trimmed)) continue;\n\n const chrome = CHROME_FRAME.exec(line);\n if (chrome) {\n const fn = (chrome[1] ?? \"\").trim() || \"<anon>\";\n const file = chrome[2];\n if (!file) continue;\n frames.push({\n fn,\n file,\n line: Number(chrome[3]),\n col: Number(chrome[4]),\n in_app: isInApp(file, sameOrigin),\n });\n continue;\n }\n\n const gecko = GECKO_FRAME.exec(line);\n if (gecko) {\n const fn = (gecko[1] ?? \"\").trim() || \"<anon>\";\n const file = gecko[2];\n if (!file) continue;\n frames.push({\n fn,\n file,\n line: Number(gecko[3]),\n col: gecko[4] ? Number(gecko[4]) : 0,\n in_app: isInApp(file, sameOrigin),\n });\n }\n }\n return frames;\n}\n\n/**\n * Mark a frame as in-app when its URL matches the page's origin (or other\n * provided origin). Cross-origin scripts (CDN bundles, third-party widgets,\n * browser extensions) are framework/library noise — not the user's code.\n */\nfunction isInApp(file: string, sameOrigin?: string): boolean {\n if (!file) return false;\n // Browser-extension and inline-eval frames — never user code.\n if (/^chrome-extension:\\/\\//.test(file)) return false;\n if (/^moz-extension:\\/\\//.test(file)) return false;\n if (/^safari-extension:\\/\\//.test(file)) return false;\n if (/^webkit-masked-url:\\/\\//.test(file)) return false;\n if (file === \"<anonymous>\" || file === \"[native code]\") return false;\n\n if (!sameOrigin) {\n // No origin context — best-effort: any http(s) URL counts as in_app\n // unless we can prove otherwise.\n return /^https?:\\/\\//.test(file) || /^[a-z]+:\\/\\//.test(file) === false;\n }\n\n try {\n const url = new URL(file, sameOrigin);\n return url.origin === sameOrigin;\n } catch {\n return false;\n }\n}\n","import type { StackFrame } from \"./types.js\";\n\n/**\n * Browser-safe synchronous fingerprint.\n *\n * `node:crypto` doesn't exist in browsers, and Web Crypto's `subtle.digest`\n * is async — making the queue async would break the fast-path. Sentry's\n * browser SDK uses simple sync hashing for the same reason. Our fingerprint\n * just needs to be stable for the same inputs; cryptographic strength isn't\n * required.\n *\n * Implementation: FNV-1a 64-bit. Fast, sync, ~1KB. Output as 16-char hex.\n */\nexport function fingerprintWeb(type: string, stack: StackFrame[]): string {\n const top = stack[0];\n const normalized = top\n ? `${type}|${normalizeFile(top.file)}|${top.fn || \"<anon>\"}|${top.line}`\n : `${type}|<no-stack>`;\n return fnv1a64(normalized);\n}\n\n/** FNV-1a 64-bit hash. Returns 16-char lowercase hex. */\nexport function fnv1a64(input: string): string {\n // 64-bit math via two 32-bit halves, since JS doesn't have native u64.\n // Constants from the FNV-1a spec.\n let hHi = 0xcbf29ce4;\n let hLo = 0x84222325;\n for (let i = 0; i < input.length; i++) {\n const code = input.charCodeAt(i);\n hLo ^= code & 0xff;\n if (input.charCodeAt(i) > 0xff) hHi ^= (code >>> 8) & 0xff;\n\n // h = h * 0x100000001b3 (FNV prime), as 64-bit math:\n // The prime split into hi/lo 32-bit parts is { 0x100, 0x000001b3 }.\n const lo16 = hLo & 0xffff;\n const lo32 = (hLo >>> 16) & 0xffff;\n const hi16 = hHi & 0xffff;\n const hi32 = (hHi >>> 16) & 0xffff;\n\n let nLo = lo16 * 0x1b3;\n let nMid = lo32 * 0x1b3 + ((nLo >>> 16) & 0xffff);\n let nHi = hi16 * 0x1b3 + ((nMid >>> 16) & 0xffff);\n let nTop = hi32 * 0x1b3 + ((nHi >>> 16) & 0xffff);\n\n nLo += lo16 * 0; // *= 0x100... carry from low parts\n nMid += lo32 * 0;\n nHi += hi16 * 0;\n nTop += hi32 * 0;\n\n // Add the * 0x1_00000000_00 portion (lo16 << 32).\n nHi += lo16;\n nTop += lo32 + ((nHi >>> 16) & 0xffff);\n\n hLo = (((nMid & 0xffff) << 16) | (nLo & 0xffff)) >>> 0;\n hHi = (((nTop & 0xffff) << 16) | (nHi & 0xffff)) >>> 0;\n }\n\n return toHex32(hHi) + toHex32(hLo);\n}\n\nfunction toHex32(n: number): string {\n return (n >>> 0).toString(16).padStart(8, \"0\");\n}\n\nfunction normalizeFile(file: string): string {\n return file.replace(/\\?.*$/, \"\").replace(/#.*$/, \"\");\n}\n","import type { Sink, WireEvent } from \"./types.js\";\n\nconst MAX_BUFFER = 100;\nconst BASE_DELAY_MS = 200;\nconst MAX_DELAY_MS = 5_000;\n\nexport class EventQueue {\n private readonly buffer: WireEvent[] = [];\n private draining = false;\n private closed = false;\n\n constructor(private readonly sink: Sink) {}\n\n enqueue(event: WireEvent): void {\n if (this.closed) return;\n if (this.buffer.length >= MAX_BUFFER) {\n this.buffer.shift();\n }\n this.buffer.push(event);\n void this.drain();\n }\n\n enqueueSync(event: WireEvent): void {\n if (this.closed) return;\n if (this.sink.emitSync) {\n try {\n this.sink.emitSync(event);\n return;\n } catch {\n // fall through to async path\n }\n }\n this.enqueue(event);\n }\n\n async flush(): Promise<void> {\n while (this.buffer.length > 0 || this.draining) {\n await new Promise((r) => setTimeout(r, 10));\n }\n }\n\n async close(): Promise<void> {\n await this.flush();\n this.closed = true;\n if (this.sink.close) await this.sink.close();\n }\n\n private async drain(): Promise<void> {\n if (this.draining) return;\n this.draining = true;\n let attempt = 0;\n while (this.buffer.length > 0) {\n const event = this.buffer[0];\n try {\n await this.sink.emit(event);\n this.buffer.shift();\n attempt = 0;\n } catch (err) {\n attempt++;\n if (attempt >= 5) {\n // Drop the event but make the loss observable — silent data loss\n // from the error tracker is the worst possible failure mode.\n console.warn(\n `[gg-pixel] dropping event after 5 failed deliveries: ${\n err instanceof Error ? err.message : String(err)\n }`,\n );\n this.buffer.shift();\n attempt = 0;\n continue;\n }\n const delay = Math.min(BASE_DELAY_MS * 2 ** (attempt - 1), MAX_DELAY_MS);\n await new Promise((r) => setTimeout(r, delay));\n }\n }\n this.draining = false;\n }\n}\n","import { parseBrowserStack } from \"../core/stack-web.js\";\nimport { fingerprintWeb } from \"../core/fingerprint-web.js\";\nimport { EventQueue } from \"../core/queue.js\";\nimport type { Level, ReportInput, Sink, WireEvent } from \"../core/types.js\";\n\n/**\n * Deno adapter.\n *\n * Deno fires `error` and `unhandledrejection` events on `globalThis`\n * (the same Web events the browser uses), but doesn't have `window` or\n * `navigator`. We can hook them via `globalThis.addEventListener`.\n *\n * Deno's `error` event fires for uncaught synchronous errors. By default\n * Deno also exits the process; we capture sync via the `Sink.emitSync`\n * if the sink supports it, otherwise enqueue and hope.\n */\n\nexport interface DenoAdapterOptions {\n projectKey: string;\n runtime: string;\n sink: Sink;\n captureUnhandledRejections: boolean;\n captureUncaughtExceptions: boolean;\n}\n\nexport interface DenoAdapter {\n report(input: ReportInput): void;\n flush(): Promise<void>;\n close(): Promise<void>;\n}\n\nexport function installDenoAdapter(opts: DenoAdapterOptions): DenoAdapter {\n const queue = new EventQueue(opts.sink);\n const detach: Array<() => void> = [];\n\n const enqueueError = (err: unknown, level: Level, manual: boolean) => {\n try {\n const event = buildEvent(err, level, manual, opts.projectKey, opts.runtime);\n queue.enqueue(event);\n } catch {\n // never break the host\n }\n };\n\n if (opts.captureUncaughtExceptions) {\n const handler = (e: Event) => {\n const errorEvent = e as ErrorEvent;\n const err = errorEvent.error ?? errorEvent.message ?? \"unknown error\";\n enqueueError(err, \"fatal\", false);\n };\n globalThis.addEventListener(\"error\", handler);\n detach.push(() => globalThis.removeEventListener(\"error\", handler));\n }\n\n if (opts.captureUnhandledRejections) {\n const handler = (e: Event) => {\n const ev = e as PromiseRejectionEvent;\n enqueueError(ev.reason, \"error\", false);\n };\n globalThis.addEventListener(\"unhandledrejection\", handler);\n detach.push(() => globalThis.removeEventListener(\"unhandledrejection\", handler));\n }\n\n return {\n report(input: ReportInput) {\n const level = input.level ?? \"error\";\n if (input.error !== undefined) {\n try {\n const event = buildEvent(input.error, level, true, opts.projectKey, opts.runtime);\n if (input.message) event.message = input.message;\n queue.enqueue(event);\n } catch {\n // never break the host\n }\n return;\n }\n const err = new Error(input.message);\n err.name = \"ManualReport\";\n enqueueError(err, level, true);\n },\n flush: () => queue.flush(),\n close: async () => {\n for (const fn of detach) fn();\n await queue.close();\n },\n };\n}\n\nfunction buildEvent(\n err: unknown,\n level: Level,\n manual: boolean,\n projectKey: string,\n runtime: string,\n): WireEvent {\n const { type, message, stackString } = normalize(err);\n const stack = parseBrowserStack(stackString);\n return {\n event_id: uuid(),\n project_key: projectKey,\n fingerprint: fingerprintWeb(type, stack),\n type,\n message,\n stack,\n code_context: null,\n runtime,\n manual_report: manual,\n level,\n occurred_at: new Date().toISOString(),\n };\n}\n\nfunction normalize(err: unknown): { type: string; message: string; stackString?: string } {\n if (err instanceof Error) {\n return { type: err.name || \"Error\", message: err.message, stackString: err.stack };\n }\n if (typeof err === \"string\") {\n return { type: \"StringError\", message: err };\n }\n try {\n return { type: \"UnknownError\", message: JSON.stringify(err) };\n } catch {\n return { type: \"UnknownError\", message: String(err) };\n }\n}\n\nfunction uuid(): string {\n const c = (globalThis as { crypto?: Crypto }).crypto;\n if (c?.randomUUID) return c.randomUUID();\n const bytes = new Uint8Array(16);\n if (c?.getRandomValues) c.getRandomValues(bytes);\n bytes[6] = (bytes[6]! & 0x0f) | 0x40;\n bytes[8] = (bytes[8]! & 0x3f) | 0x80;\n const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;\n}\n","import type { Sink, WireEvent } from \"../types.js\";\n\nexport class HttpSink implements Sink {\n private readonly fetchFn: typeof fetch;\n\n constructor(\n private readonly ingestUrl: string,\n fetchFn?: typeof fetch,\n ) {\n // CRITICAL: in browsers `fetch` is `window.fetch` and requires `this === window`.\n // Storing it as a property and calling via `this.fetchFn(...)` strips that\n // binding and throws \"Illegal invocation\". Bind to globalThis on assignment.\n // Tests can still inject a custom fetchFn (no binding needed for plain fns).\n this.fetchFn = fetchFn ?? globalThis.fetch.bind(globalThis);\n }\n\n async emit(event: WireEvent): Promise<void> {\n const body = JSON.stringify(event);\n // `keepalive: true` lets the request survive page unload (browser).\n // `mode: \"cors\"` is the explicit default but stating it makes the\n // intent clear and avoids surprises on stricter contexts.\n const res = await this.fetchFn(this.ingestUrl, {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\",\n \"x-pixel-key\": event.project_key,\n },\n body,\n keepalive: typeof window !== \"undefined\",\n mode: typeof window !== \"undefined\" ? \"cors\" : undefined,\n });\n if (!res.ok) {\n throw new Error(`pixel ingest failed: ${res.status}`);\n }\n }\n}\n","import { installDenoAdapter, type DenoAdapter } from \"./adapters/deno.js\";\nimport { HttpSink } from \"./core/sinks/http.js\";\nimport type { Sink } from \"./core/types.js\";\n\nexport const DEFAULT_INGEST_URL = \"https://gg-pixel-server.buzzbeamaustralia.workers.dev\";\n\nexport interface DenoPixelOptions {\n projectKey: string;\n ingestUrl?: string;\n runtime?: string;\n sink?: Sink;\n captureUnhandledRejections?: boolean;\n captureUncaughtExceptions?: boolean;\n}\n\nlet active: DenoAdapter | null = null;\n\nexport function initPixel(options: DenoPixelOptions): DenoAdapter {\n if (active) {\n throw new Error(\"gg-pixel is already initialized; call closePixel() first\");\n }\n const sink: Sink = options.sink ?? new HttpSink(buildIngestUrl(options.ingestUrl));\n active = installDenoAdapter({\n projectKey: options.projectKey,\n runtime: options.runtime ?? defaultRuntime(),\n sink,\n captureUnhandledRejections: options.captureUnhandledRejections ?? true,\n captureUncaughtExceptions: options.captureUncaughtExceptions ?? true,\n });\n return active;\n}\n\nexport function reportPixel(input: {\n message: string;\n error?: unknown;\n level?: \"error\" | \"warning\" | \"fatal\";\n}): void {\n if (!active) return;\n active.report(input);\n}\n\nexport async function flushPixel(): Promise<void> {\n if (!active) return;\n await active.flush();\n}\n\nexport async function closePixel(): Promise<void> {\n if (!active) return;\n await active.close();\n active = null;\n}\n\nfunction buildIngestUrl(base?: string): string {\n const url = (base ?? DEFAULT_INGEST_URL).replace(/\\/+$/, \"\");\n return `${url}/ingest`;\n}\n\nfunction defaultRuntime(): string {\n // Deno exposes its version on `Deno.version.deno`. Cast through unknown\n // because TypeScript doesn't see `Deno` in standard lib types.\n const d = (globalThis as { Deno?: { version?: { deno?: string } } }).Deno;\n return d?.version?.deno ? `deno-${d.version.deno}` : \"deno\";\n}\n\nexport type { Level, ReportInput, StackFrame, WireEvent } from \"./core/types.js\";\n"],"mappings":"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","names":[]}
|
package/dist/index.cjs
CHANGED
|
@@ -476,7 +476,7 @@ async function install(opts = {}) {
|
|
|
476
476
|
const pkgPath = (0, import_node_path2.join)(nodeRoot, "package.json");
|
|
477
477
|
const pkg = JSON.parse((0, import_node_fs3.readFileSync)(pkgPath, "utf8"));
|
|
478
478
|
const projectName = opts.projectName ?? pkg.name ?? nodeRoot.split("/").pop() ?? "unnamed";
|
|
479
|
-
const
|
|
479
|
+
const kind = detectJsProjectKind(pkg, nodeRoot);
|
|
480
480
|
const projectsJsonPath = (0, import_node_path2.join)(home, ".gg", "projects.json");
|
|
481
481
|
const envFilePath = (0, import_node_path2.join)(nodeRoot, ".env");
|
|
482
482
|
const existing = findMappingByPath(projectsJsonPath, nodeRoot);
|
|
@@ -491,26 +491,31 @@ async function install(opts = {}) {
|
|
|
491
491
|
}
|
|
492
492
|
const pm = detectPackageManager(nodeRoot);
|
|
493
493
|
const packageInstalled = opts.skipPackageInstall ? false : runInstall(nodeRoot, pm, "@kenkaiiii/gg-pixel");
|
|
494
|
-
const
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
494
|
+
const wired = wireFramework({
|
|
495
|
+
kind,
|
|
496
|
+
projectRoot: nodeRoot,
|
|
497
|
+
pkg,
|
|
498
|
+
projectKey: created.key,
|
|
499
|
+
ingestUrl
|
|
500
|
+
});
|
|
501
|
+
if (kind !== "browser" && kind !== "tauri") {
|
|
498
502
|
writeEnvKey(envFilePath, "GG_PIXEL_KEY", created.key);
|
|
499
503
|
}
|
|
500
504
|
writeProjectsMapping(projectsJsonPath, created.id, projectName, nodeRoot);
|
|
501
|
-
const entryWiring = wireEntryFile(nodeRoot, initFilePath, pkg);
|
|
502
505
|
return {
|
|
503
506
|
projectId: created.id,
|
|
504
507
|
projectKey: created.key,
|
|
505
508
|
projectName,
|
|
506
|
-
projectKind:
|
|
507
|
-
initFilePath,
|
|
509
|
+
projectKind: kind,
|
|
510
|
+
initFilePath: wired.primaryInitPath,
|
|
508
511
|
envFilePath,
|
|
509
512
|
projectsJsonPath,
|
|
510
513
|
packageManager: pm,
|
|
511
514
|
packageInstalled,
|
|
512
|
-
entryWiring,
|
|
513
|
-
reused
|
|
515
|
+
entryWiring: wired.entryWiring,
|
|
516
|
+
reused,
|
|
517
|
+
secondaryInit: wired.secondaryInit,
|
|
518
|
+
warnings: wired.warnings
|
|
514
519
|
};
|
|
515
520
|
}
|
|
516
521
|
function findMappingByPath(projectsJsonPath, projectRoot) {
|
|
@@ -700,6 +705,349 @@ function isCommonJsEntry(entryPath, pkg) {
|
|
|
700
705
|
if (entryPath.endsWith(".ts") || entryPath.endsWith(".tsx")) return false;
|
|
701
706
|
return pkg.type !== "module";
|
|
702
707
|
}
|
|
708
|
+
function detectJsProjectKind(pkg, projectRoot) {
|
|
709
|
+
const all = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
710
|
+
if ("electron" in all) return "electron";
|
|
711
|
+
if ((0, import_node_fs3.existsSync)((0, import_node_path2.join)(projectRoot, "src-tauri")) || "@tauri-apps/api" in all) return "tauri";
|
|
712
|
+
if ("react-native" in all) return "react-native";
|
|
713
|
+
if ("next" in all) return "nextjs";
|
|
714
|
+
if ("@sveltejs/kit" in all) return "sveltekit";
|
|
715
|
+
if ("nuxt" in all || "nuxt3" in all) return "nuxt";
|
|
716
|
+
if ("@remix-run/react" in all || "@remix-run/node" in all) return "remix";
|
|
717
|
+
if (isBrowserProject(pkg, projectRoot)) return "browser";
|
|
718
|
+
return "node";
|
|
719
|
+
}
|
|
720
|
+
function wireFramework(w) {
|
|
721
|
+
switch (w.kind) {
|
|
722
|
+
case "node":
|
|
723
|
+
return wireNode(w);
|
|
724
|
+
case "browser":
|
|
725
|
+
return wireBrowser(w);
|
|
726
|
+
case "nextjs":
|
|
727
|
+
return wireNextjs(w);
|
|
728
|
+
case "sveltekit":
|
|
729
|
+
return wireSveltekit(w);
|
|
730
|
+
case "nuxt":
|
|
731
|
+
return wireNuxt(w);
|
|
732
|
+
case "remix":
|
|
733
|
+
return wireRemix(w);
|
|
734
|
+
case "electron":
|
|
735
|
+
return wireElectron(w);
|
|
736
|
+
case "tauri":
|
|
737
|
+
return wireTauri(w);
|
|
738
|
+
case "react-native":
|
|
739
|
+
return wireReactNative(w);
|
|
740
|
+
case "python":
|
|
741
|
+
throw new Error("Internal: python should have been handled earlier");
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
function wireNode({ projectRoot, pkg, ingestUrl }) {
|
|
745
|
+
const initPath = (0, import_node_path2.join)(projectRoot, "gg-pixel.init.mjs");
|
|
746
|
+
(0, import_node_fs3.writeFileSync)(initPath, renderInitFile(ingestUrl), "utf8");
|
|
747
|
+
return {
|
|
748
|
+
primaryInitPath: initPath,
|
|
749
|
+
entryWiring: wireEntryFile(projectRoot, initPath, pkg),
|
|
750
|
+
warnings: []
|
|
751
|
+
};
|
|
752
|
+
}
|
|
753
|
+
function wireBrowser({ projectRoot, pkg, projectKey, ingestUrl }) {
|
|
754
|
+
const initPath = (0, import_node_path2.join)(projectRoot, "gg-pixel.init.mjs");
|
|
755
|
+
(0, import_node_fs3.writeFileSync)(initPath, renderBrowserInitFile(ingestUrl, projectKey), "utf8");
|
|
756
|
+
return {
|
|
757
|
+
primaryInitPath: initPath,
|
|
758
|
+
entryWiring: wireEntryFile(projectRoot, initPath, pkg),
|
|
759
|
+
warnings: []
|
|
760
|
+
};
|
|
761
|
+
}
|
|
762
|
+
function wireNextjs({ projectRoot, projectKey, ingestUrl }) {
|
|
763
|
+
const warnings = [];
|
|
764
|
+
const serverInitPath = pickPath(projectRoot, ["instrumentation.ts", "instrumentation.js"]);
|
|
765
|
+
const finalServerPath = serverInitPath ?? (0, import_node_path2.join)(projectRoot, "instrumentation.ts");
|
|
766
|
+
writeNextInstrumentation(finalServerPath, ingestUrl);
|
|
767
|
+
const clientInitPath = (0, import_node_path2.join)(projectRoot, "gg-pixel.client.mjs");
|
|
768
|
+
(0, import_node_fs3.writeFileSync)(clientInitPath, renderBrowserInitFile(ingestUrl, projectKey), "utf8");
|
|
769
|
+
const layoutPath = findNextLayout(projectRoot);
|
|
770
|
+
let entryWiring;
|
|
771
|
+
if (!layoutPath) {
|
|
772
|
+
warnings.push(
|
|
773
|
+
'Could not auto-wire the Next.js client init \u2014 no app/layout.{tsx,jsx} or pages/_app.{tsx,jsx} found. Add `import "./gg-pixel.client.mjs";` to your root layout/_app.'
|
|
774
|
+
);
|
|
775
|
+
entryWiring = { kind: "no_entry_found" };
|
|
776
|
+
} else {
|
|
777
|
+
entryWiring = injectImport(layoutPath, clientInitPath);
|
|
778
|
+
}
|
|
779
|
+
return {
|
|
780
|
+
primaryInitPath: clientInitPath,
|
|
781
|
+
entryWiring,
|
|
782
|
+
secondaryInit: {
|
|
783
|
+
path: finalServerPath,
|
|
784
|
+
description: "Next.js server instrumentation (auto-loaded by Next runtime)"
|
|
785
|
+
},
|
|
786
|
+
warnings
|
|
787
|
+
};
|
|
788
|
+
}
|
|
789
|
+
function writeNextInstrumentation(path, ingestUrl) {
|
|
790
|
+
const existing = (0, import_node_fs3.existsSync)(path) ? (0, import_node_fs3.readFileSync)(path, "utf8") : "";
|
|
791
|
+
if (existing.includes("@kenkaiiii/gg-pixel")) return;
|
|
792
|
+
const newContent = existing ? existing + "\n" + nextInstrumentationAppend(ingestUrl) : nextInstrumentationStandalone(ingestUrl);
|
|
793
|
+
(0, import_node_fs3.writeFileSync)(path, newContent, "utf8");
|
|
794
|
+
}
|
|
795
|
+
function nextInstrumentationStandalone(ingestUrl) {
|
|
796
|
+
return `// Next.js auto-loads this file on server start. Pixel hooks the
|
|
797
|
+
// uncaughtExceptionMonitor + unhandledRejection events for API routes,
|
|
798
|
+
// Server Components, and route handlers.
|
|
799
|
+
export async function register() {
|
|
800
|
+
if (process.env.NEXT_RUNTIME === "nodejs") {
|
|
801
|
+
const { initPixel } = await import("@kenkaiiii/gg-pixel");
|
|
802
|
+
initPixel({
|
|
803
|
+
projectKey: process.env.GG_PIXEL_KEY ?? "",
|
|
804
|
+
sink: { kind: "http", ingestUrl: ${JSON.stringify(`${ingestUrl}/ingest`)} },
|
|
805
|
+
});
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
`;
|
|
809
|
+
}
|
|
810
|
+
function nextInstrumentationAppend(ingestUrl) {
|
|
811
|
+
return `// gg-pixel: server-side error tracking
|
|
812
|
+
import { initPixel } from "@kenkaiiii/gg-pixel";
|
|
813
|
+
if (typeof process !== "undefined" && process.env.NEXT_RUNTIME === "nodejs") {
|
|
814
|
+
initPixel({
|
|
815
|
+
projectKey: process.env.GG_PIXEL_KEY ?? "",
|
|
816
|
+
sink: { kind: "http", ingestUrl: ${JSON.stringify(`${ingestUrl}/ingest`)} },
|
|
817
|
+
});
|
|
818
|
+
}
|
|
819
|
+
`;
|
|
820
|
+
}
|
|
821
|
+
function findNextLayout(projectRoot) {
|
|
822
|
+
const candidates = [
|
|
823
|
+
"app/layout.tsx",
|
|
824
|
+
"app/layout.jsx",
|
|
825
|
+
"app/layout.ts",
|
|
826
|
+
"src/app/layout.tsx",
|
|
827
|
+
"src/app/layout.jsx",
|
|
828
|
+
"pages/_app.tsx",
|
|
829
|
+
"pages/_app.jsx",
|
|
830
|
+
"src/pages/_app.tsx",
|
|
831
|
+
"src/pages/_app.jsx"
|
|
832
|
+
];
|
|
833
|
+
for (const c of candidates) {
|
|
834
|
+
const p = (0, import_node_path2.join)(projectRoot, c);
|
|
835
|
+
if ((0, import_node_fs3.existsSync)(p)) return p;
|
|
836
|
+
}
|
|
837
|
+
return null;
|
|
838
|
+
}
|
|
839
|
+
function wireSveltekit({ projectRoot, projectKey, ingestUrl }) {
|
|
840
|
+
const serverPath = (0, import_node_path2.join)(projectRoot, "src/hooks.server.ts");
|
|
841
|
+
const clientPath = (0, import_node_path2.join)(projectRoot, "src/hooks.client.ts");
|
|
842
|
+
if (!(0, import_node_fs3.existsSync)((0, import_node_path2.dirname)(serverPath))) (0, import_node_fs3.mkdirSync)((0, import_node_path2.dirname)(serverPath), { recursive: true });
|
|
843
|
+
appendOrCreate(
|
|
844
|
+
serverPath,
|
|
845
|
+
`import { initPixel } from "@kenkaiiii/gg-pixel";
|
|
846
|
+
initPixel({
|
|
847
|
+
projectKey: process.env.GG_PIXEL_KEY ?? "",
|
|
848
|
+
sink: { kind: "http", ingestUrl: ${JSON.stringify(`${ingestUrl}/ingest`)} },
|
|
849
|
+
});
|
|
850
|
+
`,
|
|
851
|
+
"@kenkaiiii/gg-pixel"
|
|
852
|
+
);
|
|
853
|
+
appendOrCreate(
|
|
854
|
+
clientPath,
|
|
855
|
+
`import { initPixel } from "@kenkaiiii/gg-pixel/browser";
|
|
856
|
+
initPixel({
|
|
857
|
+
projectKey: ${JSON.stringify(projectKey)},
|
|
858
|
+
ingestUrl: ${JSON.stringify(ingestUrl)},
|
|
859
|
+
});
|
|
860
|
+
`,
|
|
861
|
+
"@kenkaiiii/gg-pixel/browser"
|
|
862
|
+
);
|
|
863
|
+
return {
|
|
864
|
+
primaryInitPath: clientPath,
|
|
865
|
+
entryWiring: { kind: "injected", entryPath: clientPath },
|
|
866
|
+
secondaryInit: {
|
|
867
|
+
path: serverPath,
|
|
868
|
+
description: "SvelteKit server hooks (auto-loaded)"
|
|
869
|
+
},
|
|
870
|
+
warnings: []
|
|
871
|
+
};
|
|
872
|
+
}
|
|
873
|
+
function wireNuxt({ projectRoot, projectKey, ingestUrl }) {
|
|
874
|
+
const pluginsDir = (0, import_node_path2.join)(projectRoot, "plugins");
|
|
875
|
+
(0, import_node_fs3.mkdirSync)(pluginsDir, { recursive: true });
|
|
876
|
+
const serverPath = (0, import_node_path2.join)(pluginsDir, "gg-pixel.server.ts");
|
|
877
|
+
const clientPath = (0, import_node_path2.join)(pluginsDir, "gg-pixel.client.ts");
|
|
878
|
+
(0, import_node_fs3.writeFileSync)(
|
|
879
|
+
serverPath,
|
|
880
|
+
`import { initPixel } from "@kenkaiiii/gg-pixel";
|
|
881
|
+
export default defineNuxtPlugin(() => {
|
|
882
|
+
initPixel({
|
|
883
|
+
projectKey: process.env.GG_PIXEL_KEY ?? "",
|
|
884
|
+
sink: { kind: "http", ingestUrl: ${JSON.stringify(`${ingestUrl}/ingest`)} },
|
|
885
|
+
});
|
|
886
|
+
});
|
|
887
|
+
`,
|
|
888
|
+
"utf8"
|
|
889
|
+
);
|
|
890
|
+
(0, import_node_fs3.writeFileSync)(
|
|
891
|
+
clientPath,
|
|
892
|
+
`import { initPixel } from "@kenkaiiii/gg-pixel/browser";
|
|
893
|
+
export default defineNuxtPlugin(() => {
|
|
894
|
+
initPixel({
|
|
895
|
+
projectKey: ${JSON.stringify(projectKey)},
|
|
896
|
+
ingestUrl: ${JSON.stringify(ingestUrl)},
|
|
897
|
+
});
|
|
898
|
+
});
|
|
899
|
+
`,
|
|
900
|
+
"utf8"
|
|
901
|
+
);
|
|
902
|
+
return {
|
|
903
|
+
primaryInitPath: clientPath,
|
|
904
|
+
entryWiring: { kind: "injected", entryPath: clientPath },
|
|
905
|
+
secondaryInit: { path: serverPath, description: "Nuxt server plugin (auto-loaded)" },
|
|
906
|
+
warnings: []
|
|
907
|
+
};
|
|
908
|
+
}
|
|
909
|
+
function wireRemix({ projectRoot, projectKey, ingestUrl }) {
|
|
910
|
+
const serverPath = pickPath(projectRoot, ["app/entry.server.tsx", "app/entry.server.jsx"]);
|
|
911
|
+
const clientPath = pickPath(projectRoot, ["app/entry.client.tsx", "app/entry.client.jsx"]);
|
|
912
|
+
const warnings = [];
|
|
913
|
+
const clientInitPath = (0, import_node_path2.join)(projectRoot, "gg-pixel.client.mjs");
|
|
914
|
+
(0, import_node_fs3.writeFileSync)(clientInitPath, renderBrowserInitFile(ingestUrl, projectKey), "utf8");
|
|
915
|
+
if (clientPath) {
|
|
916
|
+
injectImport(clientPath, clientInitPath);
|
|
917
|
+
} else {
|
|
918
|
+
warnings.push(
|
|
919
|
+
"No app/entry.client.tsx found. Run `npx remix reveal` then re-run pixel install."
|
|
920
|
+
);
|
|
921
|
+
}
|
|
922
|
+
const serverInitPath = (0, import_node_path2.join)(projectRoot, "gg-pixel.server.mjs");
|
|
923
|
+
(0, import_node_fs3.writeFileSync)(serverInitPath, renderInitFile(ingestUrl), "utf8");
|
|
924
|
+
let serverEntry = { kind: "no_entry_found" };
|
|
925
|
+
if (serverPath) {
|
|
926
|
+
serverEntry = injectImport(serverPath, serverInitPath);
|
|
927
|
+
} else {
|
|
928
|
+
warnings.push(
|
|
929
|
+
"No app/entry.server.tsx found. Run `npx remix reveal` then re-run pixel install."
|
|
930
|
+
);
|
|
931
|
+
}
|
|
932
|
+
void serverEntry;
|
|
933
|
+
return {
|
|
934
|
+
primaryInitPath: clientInitPath,
|
|
935
|
+
entryWiring: clientPath ? { kind: "injected", entryPath: clientPath } : { kind: "no_entry_found" },
|
|
936
|
+
secondaryInit: { path: serverInitPath, description: "Remix server init" },
|
|
937
|
+
warnings
|
|
938
|
+
};
|
|
939
|
+
}
|
|
940
|
+
function wireElectron({ projectRoot, pkg, projectKey, ingestUrl }) {
|
|
941
|
+
const mainInitPath = (0, import_node_path2.join)(projectRoot, "gg-pixel.main.mjs");
|
|
942
|
+
(0, import_node_fs3.writeFileSync)(mainInitPath, renderInitFile(ingestUrl), "utf8");
|
|
943
|
+
const rendererInitPath = (0, import_node_path2.join)(projectRoot, "gg-pixel.renderer.mjs");
|
|
944
|
+
(0, import_node_fs3.writeFileSync)(rendererInitPath, renderBrowserInitFile(ingestUrl, projectKey), "utf8");
|
|
945
|
+
const mainEntry = pkg.main ? (0, import_node_path2.join)(projectRoot, pkg.main) : pickPath(projectRoot, [
|
|
946
|
+
"main.js",
|
|
947
|
+
"main.ts",
|
|
948
|
+
"src/main.js",
|
|
949
|
+
"src/main.ts",
|
|
950
|
+
"electron/main.js",
|
|
951
|
+
"electron/main.ts"
|
|
952
|
+
]);
|
|
953
|
+
let mainWiring = { kind: "no_entry_found" };
|
|
954
|
+
if (mainEntry && (0, import_node_fs3.existsSync)(mainEntry)) {
|
|
955
|
+
mainWiring = injectImport(mainEntry, mainInitPath);
|
|
956
|
+
}
|
|
957
|
+
const rendererEntry = pickPath(projectRoot, [
|
|
958
|
+
"src/renderer/index.ts",
|
|
959
|
+
"src/renderer/index.tsx",
|
|
960
|
+
"src/renderer/main.ts",
|
|
961
|
+
"src/renderer/main.tsx",
|
|
962
|
+
"renderer/index.ts",
|
|
963
|
+
"renderer/index.tsx",
|
|
964
|
+
"renderer.ts",
|
|
965
|
+
"renderer.tsx",
|
|
966
|
+
"src/index.tsx",
|
|
967
|
+
"src/main.tsx"
|
|
968
|
+
]);
|
|
969
|
+
if (rendererEntry) {
|
|
970
|
+
injectImport(rendererEntry, rendererInitPath);
|
|
971
|
+
}
|
|
972
|
+
const warnings = [];
|
|
973
|
+
if (!rendererEntry) {
|
|
974
|
+
warnings.push(
|
|
975
|
+
'Could not auto-detect the Electron renderer entry. Add `import "./gg-pixel.renderer.mjs";` to the top of your renderer entry file.'
|
|
976
|
+
);
|
|
977
|
+
}
|
|
978
|
+
return {
|
|
979
|
+
primaryInitPath: rendererInitPath,
|
|
980
|
+
entryWiring: rendererEntry ? { kind: "injected", entryPath: rendererEntry } : { kind: "no_entry_found" },
|
|
981
|
+
secondaryInit: {
|
|
982
|
+
path: mainInitPath,
|
|
983
|
+
description: "Electron main-process init" + (mainWiring.kind === "injected" ? ` (wired into ${mainEntry})` : "")
|
|
984
|
+
},
|
|
985
|
+
warnings
|
|
986
|
+
};
|
|
987
|
+
}
|
|
988
|
+
function wireTauri({ projectRoot, pkg, projectKey, ingestUrl }) {
|
|
989
|
+
const initPath = (0, import_node_path2.join)(projectRoot, "gg-pixel.init.mjs");
|
|
990
|
+
(0, import_node_fs3.writeFileSync)(initPath, renderBrowserInitFile(ingestUrl, projectKey), "utf8");
|
|
991
|
+
const entryWiring = wireEntryFile(projectRoot, initPath, pkg);
|
|
992
|
+
return {
|
|
993
|
+
primaryInitPath: initPath,
|
|
994
|
+
entryWiring,
|
|
995
|
+
warnings: [
|
|
996
|
+
"Tauri Rust backend is not instrumented \u2014 no Rust SDK exists yet. Frontend errors are captured."
|
|
997
|
+
]
|
|
998
|
+
};
|
|
999
|
+
}
|
|
1000
|
+
function wireReactNative({ projectRoot }) {
|
|
1001
|
+
return {
|
|
1002
|
+
primaryInitPath: (0, import_node_path2.join)(projectRoot, "(not-installed)"),
|
|
1003
|
+
entryWiring: { kind: "skipped", reason: "react-native SDK not built yet" },
|
|
1004
|
+
warnings: [
|
|
1005
|
+
"React Native is not yet supported \u2014 its JS runtime is neither browser nor Node.",
|
|
1006
|
+
"A dedicated React Native SDK will be a future slice."
|
|
1007
|
+
]
|
|
1008
|
+
};
|
|
1009
|
+
}
|
|
1010
|
+
function pickPath(root, candidates) {
|
|
1011
|
+
for (const c of candidates) {
|
|
1012
|
+
const p = (0, import_node_path2.join)(root, c);
|
|
1013
|
+
if ((0, import_node_fs3.existsSync)(p)) return p;
|
|
1014
|
+
}
|
|
1015
|
+
return null;
|
|
1016
|
+
}
|
|
1017
|
+
function appendOrCreate(filePath, snippet, marker) {
|
|
1018
|
+
if ((0, import_node_fs3.existsSync)(filePath)) {
|
|
1019
|
+
const existing = (0, import_node_fs3.readFileSync)(filePath, "utf8");
|
|
1020
|
+
if (existing.includes(marker)) return;
|
|
1021
|
+
(0, import_node_fs3.writeFileSync)(filePath, existing + "\n" + snippet, "utf8");
|
|
1022
|
+
return;
|
|
1023
|
+
}
|
|
1024
|
+
(0, import_node_fs3.writeFileSync)(filePath, snippet, "utf8");
|
|
1025
|
+
}
|
|
1026
|
+
function injectImport(entryPath, initFilePath) {
|
|
1027
|
+
let content;
|
|
1028
|
+
try {
|
|
1029
|
+
content = (0, import_node_fs3.readFileSync)(entryPath, "utf8");
|
|
1030
|
+
} catch (err) {
|
|
1031
|
+
return { kind: "skipped", reason: `unreadable: ${err.message}` };
|
|
1032
|
+
}
|
|
1033
|
+
const initBasename = initFilePath.split(import_node_path2.sep).pop() ?? "gg-pixel.init.mjs";
|
|
1034
|
+
if (content.includes(initBasename) || content.includes("@kenkaiiii/gg-pixel")) {
|
|
1035
|
+
return { kind: "already_present", entryPath };
|
|
1036
|
+
}
|
|
1037
|
+
const fromDir = (0, import_node_path2.dirname)(entryPath);
|
|
1038
|
+
let spec = (0, import_node_path2.relative)(fromDir, initFilePath).split(import_node_path2.sep).join("/");
|
|
1039
|
+
if (!spec.startsWith(".")) spec = "./" + spec;
|
|
1040
|
+
const importLine = `import ${JSON.stringify(spec)};`;
|
|
1041
|
+
const lines = content.split("\n");
|
|
1042
|
+
let insertAt = 0;
|
|
1043
|
+
if (lines[0]?.startsWith("#!")) insertAt = 1;
|
|
1044
|
+
while (insertAt < lines.length && /^\s*(?:["']use strict["']|\/\/|\/\*)/.test(lines[insertAt] ?? "")) {
|
|
1045
|
+
insertAt++;
|
|
1046
|
+
}
|
|
1047
|
+
const updated = [...lines.slice(0, insertAt), importLine, ...lines.slice(insertAt)].join("\n");
|
|
1048
|
+
(0, import_node_fs3.writeFileSync)(entryPath, updated, "utf8");
|
|
1049
|
+
return { kind: "injected", entryPath };
|
|
1050
|
+
}
|
|
703
1051
|
function isBrowserProject(pkg, projectRoot) {
|
|
704
1052
|
const all = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
705
1053
|
const browserishDeps = [
|
|
@@ -792,7 +1140,8 @@ async function installPython(ctx) {
|
|
|
792
1140
|
packageManager: pm,
|
|
793
1141
|
packageInstalled,
|
|
794
1142
|
entryWiring,
|
|
795
|
-
reused
|
|
1143
|
+
reused,
|
|
1144
|
+
warnings: []
|
|
796
1145
|
};
|
|
797
1146
|
}
|
|
798
1147
|
function readPyprojectName(projectRoot) {
|