@kenkaiiii/gg-pixel 4.3.69 → 4.3.70

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 CHANGED
@@ -28,7 +28,7 @@ async function install(opts = {}) {
28
28
  const pkgPath = join(nodeRoot, "package.json");
29
29
  const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
30
30
  const projectName = opts.projectName ?? pkg.name ?? nodeRoot.split("/").pop() ?? "unnamed";
31
- const isBrowser = isBrowserProject(pkg, nodeRoot);
31
+ const kind = detectJsProjectKind(pkg, nodeRoot);
32
32
  const projectsJsonPath = join(home, ".gg", "projects.json");
33
33
  const envFilePath = join(nodeRoot, ".env");
34
34
  const existing = findMappingByPath(projectsJsonPath, nodeRoot);
@@ -43,26 +43,31 @@ async function install(opts = {}) {
43
43
  }
44
44
  const pm = detectPackageManager(nodeRoot);
45
45
  const packageInstalled = opts.skipPackageInstall ? false : runInstall(nodeRoot, pm, "@kenkaiiii/gg-pixel");
46
- const initFilePath = join(nodeRoot, "gg-pixel.init.mjs");
47
- const initContent = isBrowser ? renderBrowserInitFile(ingestUrl, created.key) : renderInitFile(ingestUrl);
48
- writeFileSync(initFilePath, initContent, "utf8");
49
- if (!isBrowser) {
46
+ const wired = wireFramework({
47
+ kind,
48
+ projectRoot: nodeRoot,
49
+ pkg,
50
+ projectKey: created.key,
51
+ ingestUrl
52
+ });
53
+ if (kind !== "browser" && kind !== "tauri") {
50
54
  writeEnvKey(envFilePath, "GG_PIXEL_KEY", created.key);
51
55
  }
52
56
  writeProjectsMapping(projectsJsonPath, created.id, projectName, nodeRoot);
53
- const entryWiring = wireEntryFile(nodeRoot, initFilePath, pkg);
54
57
  return {
55
58
  projectId: created.id,
56
59
  projectKey: created.key,
57
60
  projectName,
58
- projectKind: isBrowser ? "browser" : "node",
59
- initFilePath,
61
+ projectKind: kind,
62
+ initFilePath: wired.primaryInitPath,
60
63
  envFilePath,
61
64
  projectsJsonPath,
62
65
  packageManager: pm,
63
66
  packageInstalled,
64
- entryWiring,
65
- reused
67
+ entryWiring: wired.entryWiring,
68
+ reused,
69
+ secondaryInit: wired.secondaryInit,
70
+ warnings: wired.warnings
66
71
  };
67
72
  }
68
73
  function findMappingByPath(projectsJsonPath, projectRoot) {
@@ -252,6 +257,349 @@ function isCommonJsEntry(entryPath, pkg) {
252
257
  if (entryPath.endsWith(".ts") || entryPath.endsWith(".tsx")) return false;
253
258
  return pkg.type !== "module";
254
259
  }
260
+ function detectJsProjectKind(pkg, projectRoot) {
261
+ const all = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
262
+ if ("electron" in all) return "electron";
263
+ if (existsSync(join(projectRoot, "src-tauri")) || "@tauri-apps/api" in all) return "tauri";
264
+ if ("react-native" in all) return "react-native";
265
+ if ("next" in all) return "nextjs";
266
+ if ("@sveltejs/kit" in all) return "sveltekit";
267
+ if ("nuxt" in all || "nuxt3" in all) return "nuxt";
268
+ if ("@remix-run/react" in all || "@remix-run/node" in all) return "remix";
269
+ if (isBrowserProject(pkg, projectRoot)) return "browser";
270
+ return "node";
271
+ }
272
+ function wireFramework(w) {
273
+ switch (w.kind) {
274
+ case "node":
275
+ return wireNode(w);
276
+ case "browser":
277
+ return wireBrowser(w);
278
+ case "nextjs":
279
+ return wireNextjs(w);
280
+ case "sveltekit":
281
+ return wireSveltekit(w);
282
+ case "nuxt":
283
+ return wireNuxt(w);
284
+ case "remix":
285
+ return wireRemix(w);
286
+ case "electron":
287
+ return wireElectron(w);
288
+ case "tauri":
289
+ return wireTauri(w);
290
+ case "react-native":
291
+ return wireReactNative(w);
292
+ case "python":
293
+ throw new Error("Internal: python should have been handled earlier");
294
+ }
295
+ }
296
+ function wireNode({ projectRoot, pkg, ingestUrl }) {
297
+ const initPath = join(projectRoot, "gg-pixel.init.mjs");
298
+ writeFileSync(initPath, renderInitFile(ingestUrl), "utf8");
299
+ return {
300
+ primaryInitPath: initPath,
301
+ entryWiring: wireEntryFile(projectRoot, initPath, pkg),
302
+ warnings: []
303
+ };
304
+ }
305
+ function wireBrowser({ projectRoot, pkg, projectKey, ingestUrl }) {
306
+ const initPath = join(projectRoot, "gg-pixel.init.mjs");
307
+ writeFileSync(initPath, renderBrowserInitFile(ingestUrl, projectKey), "utf8");
308
+ return {
309
+ primaryInitPath: initPath,
310
+ entryWiring: wireEntryFile(projectRoot, initPath, pkg),
311
+ warnings: []
312
+ };
313
+ }
314
+ function wireNextjs({ projectRoot, projectKey, ingestUrl }) {
315
+ const warnings = [];
316
+ const serverInitPath = pickPath(projectRoot, ["instrumentation.ts", "instrumentation.js"]);
317
+ const finalServerPath = serverInitPath ?? join(projectRoot, "instrumentation.ts");
318
+ writeNextInstrumentation(finalServerPath, ingestUrl);
319
+ const clientInitPath = join(projectRoot, "gg-pixel.client.mjs");
320
+ writeFileSync(clientInitPath, renderBrowserInitFile(ingestUrl, projectKey), "utf8");
321
+ const layoutPath = findNextLayout(projectRoot);
322
+ let entryWiring;
323
+ if (!layoutPath) {
324
+ warnings.push(
325
+ '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.'
326
+ );
327
+ entryWiring = { kind: "no_entry_found" };
328
+ } else {
329
+ entryWiring = injectImport(layoutPath, clientInitPath);
330
+ }
331
+ return {
332
+ primaryInitPath: clientInitPath,
333
+ entryWiring,
334
+ secondaryInit: {
335
+ path: finalServerPath,
336
+ description: "Next.js server instrumentation (auto-loaded by Next runtime)"
337
+ },
338
+ warnings
339
+ };
340
+ }
341
+ function writeNextInstrumentation(path, ingestUrl) {
342
+ const existing = existsSync(path) ? readFileSync(path, "utf8") : "";
343
+ if (existing.includes("@kenkaiiii/gg-pixel")) return;
344
+ const newContent = existing ? existing + "\n" + nextInstrumentationAppend(ingestUrl) : nextInstrumentationStandalone(ingestUrl);
345
+ writeFileSync(path, newContent, "utf8");
346
+ }
347
+ function nextInstrumentationStandalone(ingestUrl) {
348
+ return `// Next.js auto-loads this file on server start. Pixel hooks the
349
+ // uncaughtExceptionMonitor + unhandledRejection events for API routes,
350
+ // Server Components, and route handlers.
351
+ export async function register() {
352
+ if (process.env.NEXT_RUNTIME === "nodejs") {
353
+ const { initPixel } = await import("@kenkaiiii/gg-pixel");
354
+ initPixel({
355
+ projectKey: process.env.GG_PIXEL_KEY ?? "",
356
+ sink: { kind: "http", ingestUrl: ${JSON.stringify(`${ingestUrl}/ingest`)} },
357
+ });
358
+ }
359
+ }
360
+ `;
361
+ }
362
+ function nextInstrumentationAppend(ingestUrl) {
363
+ return `// gg-pixel: server-side error tracking
364
+ import { initPixel } from "@kenkaiiii/gg-pixel";
365
+ if (typeof process !== "undefined" && process.env.NEXT_RUNTIME === "nodejs") {
366
+ initPixel({
367
+ projectKey: process.env.GG_PIXEL_KEY ?? "",
368
+ sink: { kind: "http", ingestUrl: ${JSON.stringify(`${ingestUrl}/ingest`)} },
369
+ });
370
+ }
371
+ `;
372
+ }
373
+ function findNextLayout(projectRoot) {
374
+ const candidates = [
375
+ "app/layout.tsx",
376
+ "app/layout.jsx",
377
+ "app/layout.ts",
378
+ "src/app/layout.tsx",
379
+ "src/app/layout.jsx",
380
+ "pages/_app.tsx",
381
+ "pages/_app.jsx",
382
+ "src/pages/_app.tsx",
383
+ "src/pages/_app.jsx"
384
+ ];
385
+ for (const c of candidates) {
386
+ const p = join(projectRoot, c);
387
+ if (existsSync(p)) return p;
388
+ }
389
+ return null;
390
+ }
391
+ function wireSveltekit({ projectRoot, projectKey, ingestUrl }) {
392
+ const serverPath = join(projectRoot, "src/hooks.server.ts");
393
+ const clientPath = join(projectRoot, "src/hooks.client.ts");
394
+ if (!existsSync(dirname(serverPath))) mkdirSync(dirname(serverPath), { recursive: true });
395
+ appendOrCreate(
396
+ serverPath,
397
+ `import { initPixel } from "@kenkaiiii/gg-pixel";
398
+ initPixel({
399
+ projectKey: process.env.GG_PIXEL_KEY ?? "",
400
+ sink: { kind: "http", ingestUrl: ${JSON.stringify(`${ingestUrl}/ingest`)} },
401
+ });
402
+ `,
403
+ "@kenkaiiii/gg-pixel"
404
+ );
405
+ appendOrCreate(
406
+ clientPath,
407
+ `import { initPixel } from "@kenkaiiii/gg-pixel/browser";
408
+ initPixel({
409
+ projectKey: ${JSON.stringify(projectKey)},
410
+ ingestUrl: ${JSON.stringify(ingestUrl)},
411
+ });
412
+ `,
413
+ "@kenkaiiii/gg-pixel/browser"
414
+ );
415
+ return {
416
+ primaryInitPath: clientPath,
417
+ entryWiring: { kind: "injected", entryPath: clientPath },
418
+ secondaryInit: {
419
+ path: serverPath,
420
+ description: "SvelteKit server hooks (auto-loaded)"
421
+ },
422
+ warnings: []
423
+ };
424
+ }
425
+ function wireNuxt({ projectRoot, projectKey, ingestUrl }) {
426
+ const pluginsDir = join(projectRoot, "plugins");
427
+ mkdirSync(pluginsDir, { recursive: true });
428
+ const serverPath = join(pluginsDir, "gg-pixel.server.ts");
429
+ const clientPath = join(pluginsDir, "gg-pixel.client.ts");
430
+ writeFileSync(
431
+ serverPath,
432
+ `import { initPixel } from "@kenkaiiii/gg-pixel";
433
+ export default defineNuxtPlugin(() => {
434
+ initPixel({
435
+ projectKey: process.env.GG_PIXEL_KEY ?? "",
436
+ sink: { kind: "http", ingestUrl: ${JSON.stringify(`${ingestUrl}/ingest`)} },
437
+ });
438
+ });
439
+ `,
440
+ "utf8"
441
+ );
442
+ writeFileSync(
443
+ clientPath,
444
+ `import { initPixel } from "@kenkaiiii/gg-pixel/browser";
445
+ export default defineNuxtPlugin(() => {
446
+ initPixel({
447
+ projectKey: ${JSON.stringify(projectKey)},
448
+ ingestUrl: ${JSON.stringify(ingestUrl)},
449
+ });
450
+ });
451
+ `,
452
+ "utf8"
453
+ );
454
+ return {
455
+ primaryInitPath: clientPath,
456
+ entryWiring: { kind: "injected", entryPath: clientPath },
457
+ secondaryInit: { path: serverPath, description: "Nuxt server plugin (auto-loaded)" },
458
+ warnings: []
459
+ };
460
+ }
461
+ function wireRemix({ projectRoot, projectKey, ingestUrl }) {
462
+ const serverPath = pickPath(projectRoot, ["app/entry.server.tsx", "app/entry.server.jsx"]);
463
+ const clientPath = pickPath(projectRoot, ["app/entry.client.tsx", "app/entry.client.jsx"]);
464
+ const warnings = [];
465
+ const clientInitPath = join(projectRoot, "gg-pixel.client.mjs");
466
+ writeFileSync(clientInitPath, renderBrowserInitFile(ingestUrl, projectKey), "utf8");
467
+ if (clientPath) {
468
+ injectImport(clientPath, clientInitPath);
469
+ } else {
470
+ warnings.push(
471
+ "No app/entry.client.tsx found. Run `npx remix reveal` then re-run pixel install."
472
+ );
473
+ }
474
+ const serverInitPath = join(projectRoot, "gg-pixel.server.mjs");
475
+ writeFileSync(serverInitPath, renderInitFile(ingestUrl), "utf8");
476
+ let serverEntry = { kind: "no_entry_found" };
477
+ if (serverPath) {
478
+ serverEntry = injectImport(serverPath, serverInitPath);
479
+ } else {
480
+ warnings.push(
481
+ "No app/entry.server.tsx found. Run `npx remix reveal` then re-run pixel install."
482
+ );
483
+ }
484
+ void serverEntry;
485
+ return {
486
+ primaryInitPath: clientInitPath,
487
+ entryWiring: clientPath ? { kind: "injected", entryPath: clientPath } : { kind: "no_entry_found" },
488
+ secondaryInit: { path: serverInitPath, description: "Remix server init" },
489
+ warnings
490
+ };
491
+ }
492
+ function wireElectron({ projectRoot, pkg, projectKey, ingestUrl }) {
493
+ const mainInitPath = join(projectRoot, "gg-pixel.main.mjs");
494
+ writeFileSync(mainInitPath, renderInitFile(ingestUrl), "utf8");
495
+ const rendererInitPath = join(projectRoot, "gg-pixel.renderer.mjs");
496
+ writeFileSync(rendererInitPath, renderBrowserInitFile(ingestUrl, projectKey), "utf8");
497
+ const mainEntry = pkg.main ? join(projectRoot, pkg.main) : pickPath(projectRoot, [
498
+ "main.js",
499
+ "main.ts",
500
+ "src/main.js",
501
+ "src/main.ts",
502
+ "electron/main.js",
503
+ "electron/main.ts"
504
+ ]);
505
+ let mainWiring = { kind: "no_entry_found" };
506
+ if (mainEntry && existsSync(mainEntry)) {
507
+ mainWiring = injectImport(mainEntry, mainInitPath);
508
+ }
509
+ const rendererEntry = pickPath(projectRoot, [
510
+ "src/renderer/index.ts",
511
+ "src/renderer/index.tsx",
512
+ "src/renderer/main.ts",
513
+ "src/renderer/main.tsx",
514
+ "renderer/index.ts",
515
+ "renderer/index.tsx",
516
+ "renderer.ts",
517
+ "renderer.tsx",
518
+ "src/index.tsx",
519
+ "src/main.tsx"
520
+ ]);
521
+ if (rendererEntry) {
522
+ injectImport(rendererEntry, rendererInitPath);
523
+ }
524
+ const warnings = [];
525
+ if (!rendererEntry) {
526
+ warnings.push(
527
+ 'Could not auto-detect the Electron renderer entry. Add `import "./gg-pixel.renderer.mjs";` to the top of your renderer entry file.'
528
+ );
529
+ }
530
+ return {
531
+ primaryInitPath: rendererInitPath,
532
+ entryWiring: rendererEntry ? { kind: "injected", entryPath: rendererEntry } : { kind: "no_entry_found" },
533
+ secondaryInit: {
534
+ path: mainInitPath,
535
+ description: "Electron main-process init" + (mainWiring.kind === "injected" ? ` (wired into ${mainEntry})` : "")
536
+ },
537
+ warnings
538
+ };
539
+ }
540
+ function wireTauri({ projectRoot, pkg, projectKey, ingestUrl }) {
541
+ const initPath = join(projectRoot, "gg-pixel.init.mjs");
542
+ writeFileSync(initPath, renderBrowserInitFile(ingestUrl, projectKey), "utf8");
543
+ const entryWiring = wireEntryFile(projectRoot, initPath, pkg);
544
+ return {
545
+ primaryInitPath: initPath,
546
+ entryWiring,
547
+ warnings: [
548
+ "Tauri Rust backend is not instrumented \u2014 no Rust SDK exists yet. Frontend errors are captured."
549
+ ]
550
+ };
551
+ }
552
+ function wireReactNative({ projectRoot }) {
553
+ return {
554
+ primaryInitPath: join(projectRoot, "(not-installed)"),
555
+ entryWiring: { kind: "skipped", reason: "react-native SDK not built yet" },
556
+ warnings: [
557
+ "React Native is not yet supported \u2014 its JS runtime is neither browser nor Node.",
558
+ "A dedicated React Native SDK will be a future slice."
559
+ ]
560
+ };
561
+ }
562
+ function pickPath(root, candidates) {
563
+ for (const c of candidates) {
564
+ const p = join(root, c);
565
+ if (existsSync(p)) return p;
566
+ }
567
+ return null;
568
+ }
569
+ function appendOrCreate(filePath, snippet, marker) {
570
+ if (existsSync(filePath)) {
571
+ const existing = readFileSync(filePath, "utf8");
572
+ if (existing.includes(marker)) return;
573
+ writeFileSync(filePath, existing + "\n" + snippet, "utf8");
574
+ return;
575
+ }
576
+ writeFileSync(filePath, snippet, "utf8");
577
+ }
578
+ function injectImport(entryPath, initFilePath) {
579
+ let content;
580
+ try {
581
+ content = readFileSync(entryPath, "utf8");
582
+ } catch (err) {
583
+ return { kind: "skipped", reason: `unreadable: ${err.message}` };
584
+ }
585
+ const initBasename = initFilePath.split(sep).pop() ?? "gg-pixel.init.mjs";
586
+ if (content.includes(initBasename) || content.includes("@kenkaiiii/gg-pixel")) {
587
+ return { kind: "already_present", entryPath };
588
+ }
589
+ const fromDir = dirname(entryPath);
590
+ let spec = relative(fromDir, initFilePath).split(sep).join("/");
591
+ if (!spec.startsWith(".")) spec = "./" + spec;
592
+ const importLine = `import ${JSON.stringify(spec)};`;
593
+ const lines = content.split("\n");
594
+ let insertAt = 0;
595
+ if (lines[0]?.startsWith("#!")) insertAt = 1;
596
+ while (insertAt < lines.length && /^\s*(?:["']use strict["']|\/\/|\/\*)/.test(lines[insertAt] ?? "")) {
597
+ insertAt++;
598
+ }
599
+ const updated = [...lines.slice(0, insertAt), importLine, ...lines.slice(insertAt)].join("\n");
600
+ writeFileSync(entryPath, updated, "utf8");
601
+ return { kind: "injected", entryPath };
602
+ }
255
603
  function isBrowserProject(pkg, projectRoot) {
256
604
  const all = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
257
605
  const browserishDeps = [
@@ -344,7 +692,8 @@ async function installPython(ctx) {
344
692
  packageManager: pm,
345
693
  packageInstalled,
346
694
  entryWiring,
347
- reused
695
+ reused,
696
+ warnings: []
348
697
  };
349
698
  }
350
699
  function readPyprojectName(projectRoot) {
@@ -533,6 +882,13 @@ async function main(argv) {
533
882
  if (!result.packageInstalled && !args.skipPackageInstall) {
534
883
  console.log(` \u26A0 Package install failed via ${result.packageManager}. Run it manually.`);
535
884
  }
885
+ if (result.secondaryInit) {
886
+ console.log(` Also wrote: ${result.secondaryInit.path}`);
887
+ console.log(` ${result.secondaryInit.description}`);
888
+ }
889
+ for (const w of result.warnings) {
890
+ console.log(` \u26A0 ${w}`);
891
+ }
536
892
  console.log("");
537
893
  }
538
894
  main(process.argv.slice(2)).catch((err) => {