@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/index.js CHANGED
@@ -435,7 +435,7 @@ async function install(opts = {}) {
435
435
  const pkgPath = join2(nodeRoot, "package.json");
436
436
  const pkg = JSON.parse(readFileSync2(pkgPath, "utf8"));
437
437
  const projectName = opts.projectName ?? pkg.name ?? nodeRoot.split("/").pop() ?? "unnamed";
438
- const isBrowser = isBrowserProject(pkg, nodeRoot);
438
+ const kind = detectJsProjectKind(pkg, nodeRoot);
439
439
  const projectsJsonPath = join2(home, ".gg", "projects.json");
440
440
  const envFilePath = join2(nodeRoot, ".env");
441
441
  const existing = findMappingByPath(projectsJsonPath, nodeRoot);
@@ -450,26 +450,31 @@ async function install(opts = {}) {
450
450
  }
451
451
  const pm = detectPackageManager(nodeRoot);
452
452
  const packageInstalled = opts.skipPackageInstall ? false : runInstall(nodeRoot, pm, "@kenkaiiii/gg-pixel");
453
- const initFilePath = join2(nodeRoot, "gg-pixel.init.mjs");
454
- const initContent = isBrowser ? renderBrowserInitFile(ingestUrl, created.key) : renderInitFile(ingestUrl);
455
- writeFileSync(initFilePath, initContent, "utf8");
456
- if (!isBrowser) {
453
+ const wired = wireFramework({
454
+ kind,
455
+ projectRoot: nodeRoot,
456
+ pkg,
457
+ projectKey: created.key,
458
+ ingestUrl
459
+ });
460
+ if (kind !== "browser" && kind !== "tauri") {
457
461
  writeEnvKey(envFilePath, "GG_PIXEL_KEY", created.key);
458
462
  }
459
463
  writeProjectsMapping(projectsJsonPath, created.id, projectName, nodeRoot);
460
- const entryWiring = wireEntryFile(nodeRoot, initFilePath, pkg);
461
464
  return {
462
465
  projectId: created.id,
463
466
  projectKey: created.key,
464
467
  projectName,
465
- projectKind: isBrowser ? "browser" : "node",
466
- initFilePath,
468
+ projectKind: kind,
469
+ initFilePath: wired.primaryInitPath,
467
470
  envFilePath,
468
471
  projectsJsonPath,
469
472
  packageManager: pm,
470
473
  packageInstalled,
471
- entryWiring,
472
- reused
474
+ entryWiring: wired.entryWiring,
475
+ reused,
476
+ secondaryInit: wired.secondaryInit,
477
+ warnings: wired.warnings
473
478
  };
474
479
  }
475
480
  function findMappingByPath(projectsJsonPath, projectRoot) {
@@ -659,6 +664,349 @@ function isCommonJsEntry(entryPath, pkg) {
659
664
  if (entryPath.endsWith(".ts") || entryPath.endsWith(".tsx")) return false;
660
665
  return pkg.type !== "module";
661
666
  }
667
+ function detectJsProjectKind(pkg, projectRoot) {
668
+ const all = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
669
+ if ("electron" in all) return "electron";
670
+ if (existsSync2(join2(projectRoot, "src-tauri")) || "@tauri-apps/api" in all) return "tauri";
671
+ if ("react-native" in all) return "react-native";
672
+ if ("next" in all) return "nextjs";
673
+ if ("@sveltejs/kit" in all) return "sveltekit";
674
+ if ("nuxt" in all || "nuxt3" in all) return "nuxt";
675
+ if ("@remix-run/react" in all || "@remix-run/node" in all) return "remix";
676
+ if (isBrowserProject(pkg, projectRoot)) return "browser";
677
+ return "node";
678
+ }
679
+ function wireFramework(w) {
680
+ switch (w.kind) {
681
+ case "node":
682
+ return wireNode(w);
683
+ case "browser":
684
+ return wireBrowser(w);
685
+ case "nextjs":
686
+ return wireNextjs(w);
687
+ case "sveltekit":
688
+ return wireSveltekit(w);
689
+ case "nuxt":
690
+ return wireNuxt(w);
691
+ case "remix":
692
+ return wireRemix(w);
693
+ case "electron":
694
+ return wireElectron(w);
695
+ case "tauri":
696
+ return wireTauri(w);
697
+ case "react-native":
698
+ return wireReactNative(w);
699
+ case "python":
700
+ throw new Error("Internal: python should have been handled earlier");
701
+ }
702
+ }
703
+ function wireNode({ projectRoot, pkg, ingestUrl }) {
704
+ const initPath = join2(projectRoot, "gg-pixel.init.mjs");
705
+ writeFileSync(initPath, renderInitFile(ingestUrl), "utf8");
706
+ return {
707
+ primaryInitPath: initPath,
708
+ entryWiring: wireEntryFile(projectRoot, initPath, pkg),
709
+ warnings: []
710
+ };
711
+ }
712
+ function wireBrowser({ projectRoot, pkg, projectKey, ingestUrl }) {
713
+ const initPath = join2(projectRoot, "gg-pixel.init.mjs");
714
+ writeFileSync(initPath, renderBrowserInitFile(ingestUrl, projectKey), "utf8");
715
+ return {
716
+ primaryInitPath: initPath,
717
+ entryWiring: wireEntryFile(projectRoot, initPath, pkg),
718
+ warnings: []
719
+ };
720
+ }
721
+ function wireNextjs({ projectRoot, projectKey, ingestUrl }) {
722
+ const warnings = [];
723
+ const serverInitPath = pickPath(projectRoot, ["instrumentation.ts", "instrumentation.js"]);
724
+ const finalServerPath = serverInitPath ?? join2(projectRoot, "instrumentation.ts");
725
+ writeNextInstrumentation(finalServerPath, ingestUrl);
726
+ const clientInitPath = join2(projectRoot, "gg-pixel.client.mjs");
727
+ writeFileSync(clientInitPath, renderBrowserInitFile(ingestUrl, projectKey), "utf8");
728
+ const layoutPath = findNextLayout(projectRoot);
729
+ let entryWiring;
730
+ if (!layoutPath) {
731
+ warnings.push(
732
+ '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.'
733
+ );
734
+ entryWiring = { kind: "no_entry_found" };
735
+ } else {
736
+ entryWiring = injectImport(layoutPath, clientInitPath);
737
+ }
738
+ return {
739
+ primaryInitPath: clientInitPath,
740
+ entryWiring,
741
+ secondaryInit: {
742
+ path: finalServerPath,
743
+ description: "Next.js server instrumentation (auto-loaded by Next runtime)"
744
+ },
745
+ warnings
746
+ };
747
+ }
748
+ function writeNextInstrumentation(path, ingestUrl) {
749
+ const existing = existsSync2(path) ? readFileSync2(path, "utf8") : "";
750
+ if (existing.includes("@kenkaiiii/gg-pixel")) return;
751
+ const newContent = existing ? existing + "\n" + nextInstrumentationAppend(ingestUrl) : nextInstrumentationStandalone(ingestUrl);
752
+ writeFileSync(path, newContent, "utf8");
753
+ }
754
+ function nextInstrumentationStandalone(ingestUrl) {
755
+ return `// Next.js auto-loads this file on server start. Pixel hooks the
756
+ // uncaughtExceptionMonitor + unhandledRejection events for API routes,
757
+ // Server Components, and route handlers.
758
+ export async function register() {
759
+ if (process.env.NEXT_RUNTIME === "nodejs") {
760
+ const { initPixel } = await import("@kenkaiiii/gg-pixel");
761
+ initPixel({
762
+ projectKey: process.env.GG_PIXEL_KEY ?? "",
763
+ sink: { kind: "http", ingestUrl: ${JSON.stringify(`${ingestUrl}/ingest`)} },
764
+ });
765
+ }
766
+ }
767
+ `;
768
+ }
769
+ function nextInstrumentationAppend(ingestUrl) {
770
+ return `// gg-pixel: server-side error tracking
771
+ import { initPixel } from "@kenkaiiii/gg-pixel";
772
+ if (typeof process !== "undefined" && process.env.NEXT_RUNTIME === "nodejs") {
773
+ initPixel({
774
+ projectKey: process.env.GG_PIXEL_KEY ?? "",
775
+ sink: { kind: "http", ingestUrl: ${JSON.stringify(`${ingestUrl}/ingest`)} },
776
+ });
777
+ }
778
+ `;
779
+ }
780
+ function findNextLayout(projectRoot) {
781
+ const candidates = [
782
+ "app/layout.tsx",
783
+ "app/layout.jsx",
784
+ "app/layout.ts",
785
+ "src/app/layout.tsx",
786
+ "src/app/layout.jsx",
787
+ "pages/_app.tsx",
788
+ "pages/_app.jsx",
789
+ "src/pages/_app.tsx",
790
+ "src/pages/_app.jsx"
791
+ ];
792
+ for (const c of candidates) {
793
+ const p = join2(projectRoot, c);
794
+ if (existsSync2(p)) return p;
795
+ }
796
+ return null;
797
+ }
798
+ function wireSveltekit({ projectRoot, projectKey, ingestUrl }) {
799
+ const serverPath = join2(projectRoot, "src/hooks.server.ts");
800
+ const clientPath = join2(projectRoot, "src/hooks.client.ts");
801
+ if (!existsSync2(dirname2(serverPath))) mkdirSync2(dirname2(serverPath), { recursive: true });
802
+ appendOrCreate(
803
+ serverPath,
804
+ `import { initPixel } from "@kenkaiiii/gg-pixel";
805
+ initPixel({
806
+ projectKey: process.env.GG_PIXEL_KEY ?? "",
807
+ sink: { kind: "http", ingestUrl: ${JSON.stringify(`${ingestUrl}/ingest`)} },
808
+ });
809
+ `,
810
+ "@kenkaiiii/gg-pixel"
811
+ );
812
+ appendOrCreate(
813
+ clientPath,
814
+ `import { initPixel } from "@kenkaiiii/gg-pixel/browser";
815
+ initPixel({
816
+ projectKey: ${JSON.stringify(projectKey)},
817
+ ingestUrl: ${JSON.stringify(ingestUrl)},
818
+ });
819
+ `,
820
+ "@kenkaiiii/gg-pixel/browser"
821
+ );
822
+ return {
823
+ primaryInitPath: clientPath,
824
+ entryWiring: { kind: "injected", entryPath: clientPath },
825
+ secondaryInit: {
826
+ path: serverPath,
827
+ description: "SvelteKit server hooks (auto-loaded)"
828
+ },
829
+ warnings: []
830
+ };
831
+ }
832
+ function wireNuxt({ projectRoot, projectKey, ingestUrl }) {
833
+ const pluginsDir = join2(projectRoot, "plugins");
834
+ mkdirSync2(pluginsDir, { recursive: true });
835
+ const serverPath = join2(pluginsDir, "gg-pixel.server.ts");
836
+ const clientPath = join2(pluginsDir, "gg-pixel.client.ts");
837
+ writeFileSync(
838
+ serverPath,
839
+ `import { initPixel } from "@kenkaiiii/gg-pixel";
840
+ export default defineNuxtPlugin(() => {
841
+ initPixel({
842
+ projectKey: process.env.GG_PIXEL_KEY ?? "",
843
+ sink: { kind: "http", ingestUrl: ${JSON.stringify(`${ingestUrl}/ingest`)} },
844
+ });
845
+ });
846
+ `,
847
+ "utf8"
848
+ );
849
+ writeFileSync(
850
+ clientPath,
851
+ `import { initPixel } from "@kenkaiiii/gg-pixel/browser";
852
+ export default defineNuxtPlugin(() => {
853
+ initPixel({
854
+ projectKey: ${JSON.stringify(projectKey)},
855
+ ingestUrl: ${JSON.stringify(ingestUrl)},
856
+ });
857
+ });
858
+ `,
859
+ "utf8"
860
+ );
861
+ return {
862
+ primaryInitPath: clientPath,
863
+ entryWiring: { kind: "injected", entryPath: clientPath },
864
+ secondaryInit: { path: serverPath, description: "Nuxt server plugin (auto-loaded)" },
865
+ warnings: []
866
+ };
867
+ }
868
+ function wireRemix({ projectRoot, projectKey, ingestUrl }) {
869
+ const serverPath = pickPath(projectRoot, ["app/entry.server.tsx", "app/entry.server.jsx"]);
870
+ const clientPath = pickPath(projectRoot, ["app/entry.client.tsx", "app/entry.client.jsx"]);
871
+ const warnings = [];
872
+ const clientInitPath = join2(projectRoot, "gg-pixel.client.mjs");
873
+ writeFileSync(clientInitPath, renderBrowserInitFile(ingestUrl, projectKey), "utf8");
874
+ if (clientPath) {
875
+ injectImport(clientPath, clientInitPath);
876
+ } else {
877
+ warnings.push(
878
+ "No app/entry.client.tsx found. Run `npx remix reveal` then re-run pixel install."
879
+ );
880
+ }
881
+ const serverInitPath = join2(projectRoot, "gg-pixel.server.mjs");
882
+ writeFileSync(serverInitPath, renderInitFile(ingestUrl), "utf8");
883
+ let serverEntry = { kind: "no_entry_found" };
884
+ if (serverPath) {
885
+ serverEntry = injectImport(serverPath, serverInitPath);
886
+ } else {
887
+ warnings.push(
888
+ "No app/entry.server.tsx found. Run `npx remix reveal` then re-run pixel install."
889
+ );
890
+ }
891
+ void serverEntry;
892
+ return {
893
+ primaryInitPath: clientInitPath,
894
+ entryWiring: clientPath ? { kind: "injected", entryPath: clientPath } : { kind: "no_entry_found" },
895
+ secondaryInit: { path: serverInitPath, description: "Remix server init" },
896
+ warnings
897
+ };
898
+ }
899
+ function wireElectron({ projectRoot, pkg, projectKey, ingestUrl }) {
900
+ const mainInitPath = join2(projectRoot, "gg-pixel.main.mjs");
901
+ writeFileSync(mainInitPath, renderInitFile(ingestUrl), "utf8");
902
+ const rendererInitPath = join2(projectRoot, "gg-pixel.renderer.mjs");
903
+ writeFileSync(rendererInitPath, renderBrowserInitFile(ingestUrl, projectKey), "utf8");
904
+ const mainEntry = pkg.main ? join2(projectRoot, pkg.main) : pickPath(projectRoot, [
905
+ "main.js",
906
+ "main.ts",
907
+ "src/main.js",
908
+ "src/main.ts",
909
+ "electron/main.js",
910
+ "electron/main.ts"
911
+ ]);
912
+ let mainWiring = { kind: "no_entry_found" };
913
+ if (mainEntry && existsSync2(mainEntry)) {
914
+ mainWiring = injectImport(mainEntry, mainInitPath);
915
+ }
916
+ const rendererEntry = pickPath(projectRoot, [
917
+ "src/renderer/index.ts",
918
+ "src/renderer/index.tsx",
919
+ "src/renderer/main.ts",
920
+ "src/renderer/main.tsx",
921
+ "renderer/index.ts",
922
+ "renderer/index.tsx",
923
+ "renderer.ts",
924
+ "renderer.tsx",
925
+ "src/index.tsx",
926
+ "src/main.tsx"
927
+ ]);
928
+ if (rendererEntry) {
929
+ injectImport(rendererEntry, rendererInitPath);
930
+ }
931
+ const warnings = [];
932
+ if (!rendererEntry) {
933
+ warnings.push(
934
+ 'Could not auto-detect the Electron renderer entry. Add `import "./gg-pixel.renderer.mjs";` to the top of your renderer entry file.'
935
+ );
936
+ }
937
+ return {
938
+ primaryInitPath: rendererInitPath,
939
+ entryWiring: rendererEntry ? { kind: "injected", entryPath: rendererEntry } : { kind: "no_entry_found" },
940
+ secondaryInit: {
941
+ path: mainInitPath,
942
+ description: "Electron main-process init" + (mainWiring.kind === "injected" ? ` (wired into ${mainEntry})` : "")
943
+ },
944
+ warnings
945
+ };
946
+ }
947
+ function wireTauri({ projectRoot, pkg, projectKey, ingestUrl }) {
948
+ const initPath = join2(projectRoot, "gg-pixel.init.mjs");
949
+ writeFileSync(initPath, renderBrowserInitFile(ingestUrl, projectKey), "utf8");
950
+ const entryWiring = wireEntryFile(projectRoot, initPath, pkg);
951
+ return {
952
+ primaryInitPath: initPath,
953
+ entryWiring,
954
+ warnings: [
955
+ "Tauri Rust backend is not instrumented \u2014 no Rust SDK exists yet. Frontend errors are captured."
956
+ ]
957
+ };
958
+ }
959
+ function wireReactNative({ projectRoot }) {
960
+ return {
961
+ primaryInitPath: join2(projectRoot, "(not-installed)"),
962
+ entryWiring: { kind: "skipped", reason: "react-native SDK not built yet" },
963
+ warnings: [
964
+ "React Native is not yet supported \u2014 its JS runtime is neither browser nor Node.",
965
+ "A dedicated React Native SDK will be a future slice."
966
+ ]
967
+ };
968
+ }
969
+ function pickPath(root, candidates) {
970
+ for (const c of candidates) {
971
+ const p = join2(root, c);
972
+ if (existsSync2(p)) return p;
973
+ }
974
+ return null;
975
+ }
976
+ function appendOrCreate(filePath, snippet, marker) {
977
+ if (existsSync2(filePath)) {
978
+ const existing = readFileSync2(filePath, "utf8");
979
+ if (existing.includes(marker)) return;
980
+ writeFileSync(filePath, existing + "\n" + snippet, "utf8");
981
+ return;
982
+ }
983
+ writeFileSync(filePath, snippet, "utf8");
984
+ }
985
+ function injectImport(entryPath, initFilePath) {
986
+ let content;
987
+ try {
988
+ content = readFileSync2(entryPath, "utf8");
989
+ } catch (err) {
990
+ return { kind: "skipped", reason: `unreadable: ${err.message}` };
991
+ }
992
+ const initBasename = initFilePath.split(sep).pop() ?? "gg-pixel.init.mjs";
993
+ if (content.includes(initBasename) || content.includes("@kenkaiiii/gg-pixel")) {
994
+ return { kind: "already_present", entryPath };
995
+ }
996
+ const fromDir = dirname2(entryPath);
997
+ let spec = relative(fromDir, initFilePath).split(sep).join("/");
998
+ if (!spec.startsWith(".")) spec = "./" + spec;
999
+ const importLine = `import ${JSON.stringify(spec)};`;
1000
+ const lines = content.split("\n");
1001
+ let insertAt = 0;
1002
+ if (lines[0]?.startsWith("#!")) insertAt = 1;
1003
+ while (insertAt < lines.length && /^\s*(?:["']use strict["']|\/\/|\/\*)/.test(lines[insertAt] ?? "")) {
1004
+ insertAt++;
1005
+ }
1006
+ const updated = [...lines.slice(0, insertAt), importLine, ...lines.slice(insertAt)].join("\n");
1007
+ writeFileSync(entryPath, updated, "utf8");
1008
+ return { kind: "injected", entryPath };
1009
+ }
662
1010
  function isBrowserProject(pkg, projectRoot) {
663
1011
  const all = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
664
1012
  const browserishDeps = [
@@ -751,7 +1099,8 @@ async function installPython(ctx) {
751
1099
  packageManager: pm,
752
1100
  packageInstalled,
753
1101
  entryWiring,
754
- reused
1102
+ reused,
1103
+ warnings: []
755
1104
  };
756
1105
  }
757
1106
  function readPyprojectName(projectRoot) {