@kenkaiiii/gg-pixel 4.3.90 → 4.3.92

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.d.cts CHANGED
@@ -1,3 +1,5 @@
1
+ import { spawn } from 'node:child_process';
2
+
1
3
  type Level = "error" | "warning" | "fatal";
2
4
  interface StackFrame {
3
5
  file: string;
@@ -82,6 +84,10 @@ interface InstallResult {
82
84
  projectSecret: string;
83
85
  projectName: string;
84
86
  projectKind: ProjectKind;
87
+ /** Resolved root of the user's project (the dir containing package.json /
88
+ * pyproject.toml / go.mod / Gemfile, depending on kind). Needed by the
89
+ * verifier so it can spawn a probe child from the right cwd. */
90
+ projectRoot: string;
85
91
  initFilePath: string;
86
92
  envFilePath: string;
87
93
  projectsJsonPath: string;
@@ -115,9 +121,58 @@ type ProjectKind = "node" | "browser" | "python" | "nextjs" | "sveltekit" | "nux
115
121
  type PythonPackageManager = "uv" | "poetry" | "pipenv" | "pip";
116
122
  declare function install(opts?: InstallOptions): Promise<InstallResult>;
117
123
 
124
+ /**
125
+ * Post-install verification.
126
+ *
127
+ * Mature SDKs (Sentry, PostHog, Datadog) all skip this — the wizard ends with
128
+ * "looks good!" if file writes succeeded, even when the wired code never ran
129
+ * a single byte. That's how every silent-failure bug we've debugged this
130
+ * session got past the installer (stale project keys, orphaned init files,
131
+ * sandboxed Electron renderers, missing dotenv, broken bundler copy steps).
132
+ *
133
+ * verifyInstall fires a synthetic event end-to-end after wiring completes:
134
+ * spawn a Node child in the user's project, import the SDK, report a probe,
135
+ * then poll the project's error list with the bearer secret. If the probe
136
+ * doesn't round-trip, we know the install is broken NOW instead of weeks
137
+ * later when an end-user finally hits a real error.
138
+ */
139
+
140
+ interface VerifyOptions {
141
+ projectId: string;
142
+ projectKey: string;
143
+ /** Per-project bearer secret used to query the error list. */
144
+ projectSecret: string;
145
+ /** Backend root, e.g. "https://gg-pixel-server.buzzbeamaustralia.workers.dev" (no trailing /ingest). */
146
+ ingestUrl: string;
147
+ /** User's project root — the cwd we spawn the probe from for `node_modules` resolution. */
148
+ projectRoot: string;
149
+ fetchFn?: typeof fetch;
150
+ spawnFn?: typeof spawn;
151
+ /** Skip the spawned child probe entirely; useful for non-Node kinds (browser, RN). */
152
+ skipChildProbe?: boolean;
153
+ /** Max time to wait for the probe to appear in the error list. Default 5s. */
154
+ timeoutMs?: number;
155
+ }
156
+ type VerifyOutcome = {
157
+ kind: "ok";
158
+ /** Which path delivered the probe successfully. */
159
+ method: "child_process" | "direct_ingest";
160
+ latencyMs: number;
161
+ } | {
162
+ kind: "failed";
163
+ reason: string;
164
+ hint?: string;
165
+ };
166
+ /**
167
+ * Returns true if the fingerprint string was produced by an install probe.
168
+ * Used by the overlay/listing code to hide probes if cleanup ever fails.
169
+ */
170
+ declare function isInstallProbeFingerprint(fingerprint: string | null | undefined): boolean;
171
+ declare function verifyInstall(opts: VerifyOptions): Promise<VerifyOutcome>;
172
+
118
173
  declare function initPixel(options: PixelOptions): NodeAdapter;
119
174
  declare function reportPixel(input: ReportInput): void;
120
175
  declare function flushPixel(): Promise<void>;
121
176
  declare function closePixel(): Promise<void>;
122
177
 
123
- export { type CodeContext, DEFAULT_INGEST_URL, type InstallOptions, type InstallResult, type Level, type PackageManager, type PixelOptions, type ReportInput, type Sink, type SinkConfig, type StackFrame, type WireEvent, closePixel, flushPixel, initPixel, install, reportPixel };
178
+ export { type CodeContext, DEFAULT_INGEST_URL, type InstallOptions, type InstallResult, type Level, type PackageManager, type PixelOptions, type ReportInput, type Sink, type SinkConfig, type StackFrame, type VerifyOptions, type VerifyOutcome, type WireEvent, closePixel, flushPixel, initPixel, install, isInstallProbeFingerprint, reportPixel, verifyInstall };
package/dist/index.d.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import { spawn } from 'node:child_process';
2
+
1
3
  type Level = "error" | "warning" | "fatal";
2
4
  interface StackFrame {
3
5
  file: string;
@@ -82,6 +84,10 @@ interface InstallResult {
82
84
  projectSecret: string;
83
85
  projectName: string;
84
86
  projectKind: ProjectKind;
87
+ /** Resolved root of the user's project (the dir containing package.json /
88
+ * pyproject.toml / go.mod / Gemfile, depending on kind). Needed by the
89
+ * verifier so it can spawn a probe child from the right cwd. */
90
+ projectRoot: string;
85
91
  initFilePath: string;
86
92
  envFilePath: string;
87
93
  projectsJsonPath: string;
@@ -115,9 +121,58 @@ type ProjectKind = "node" | "browser" | "python" | "nextjs" | "sveltekit" | "nux
115
121
  type PythonPackageManager = "uv" | "poetry" | "pipenv" | "pip";
116
122
  declare function install(opts?: InstallOptions): Promise<InstallResult>;
117
123
 
124
+ /**
125
+ * Post-install verification.
126
+ *
127
+ * Mature SDKs (Sentry, PostHog, Datadog) all skip this — the wizard ends with
128
+ * "looks good!" if file writes succeeded, even when the wired code never ran
129
+ * a single byte. That's how every silent-failure bug we've debugged this
130
+ * session got past the installer (stale project keys, orphaned init files,
131
+ * sandboxed Electron renderers, missing dotenv, broken bundler copy steps).
132
+ *
133
+ * verifyInstall fires a synthetic event end-to-end after wiring completes:
134
+ * spawn a Node child in the user's project, import the SDK, report a probe,
135
+ * then poll the project's error list with the bearer secret. If the probe
136
+ * doesn't round-trip, we know the install is broken NOW instead of weeks
137
+ * later when an end-user finally hits a real error.
138
+ */
139
+
140
+ interface VerifyOptions {
141
+ projectId: string;
142
+ projectKey: string;
143
+ /** Per-project bearer secret used to query the error list. */
144
+ projectSecret: string;
145
+ /** Backend root, e.g. "https://gg-pixel-server.buzzbeamaustralia.workers.dev" (no trailing /ingest). */
146
+ ingestUrl: string;
147
+ /** User's project root — the cwd we spawn the probe from for `node_modules` resolution. */
148
+ projectRoot: string;
149
+ fetchFn?: typeof fetch;
150
+ spawnFn?: typeof spawn;
151
+ /** Skip the spawned child probe entirely; useful for non-Node kinds (browser, RN). */
152
+ skipChildProbe?: boolean;
153
+ /** Max time to wait for the probe to appear in the error list. Default 5s. */
154
+ timeoutMs?: number;
155
+ }
156
+ type VerifyOutcome = {
157
+ kind: "ok";
158
+ /** Which path delivered the probe successfully. */
159
+ method: "child_process" | "direct_ingest";
160
+ latencyMs: number;
161
+ } | {
162
+ kind: "failed";
163
+ reason: string;
164
+ hint?: string;
165
+ };
166
+ /**
167
+ * Returns true if the fingerprint string was produced by an install probe.
168
+ * Used by the overlay/listing code to hide probes if cleanup ever fails.
169
+ */
170
+ declare function isInstallProbeFingerprint(fingerprint: string | null | undefined): boolean;
171
+ declare function verifyInstall(opts: VerifyOptions): Promise<VerifyOutcome>;
172
+
118
173
  declare function initPixel(options: PixelOptions): NodeAdapter;
119
174
  declare function reportPixel(input: ReportInput): void;
120
175
  declare function flushPixel(): Promise<void>;
121
176
  declare function closePixel(): Promise<void>;
122
177
 
123
- export { type CodeContext, DEFAULT_INGEST_URL, type InstallOptions, type InstallResult, type Level, type PackageManager, type PixelOptions, type ReportInput, type Sink, type SinkConfig, type StackFrame, type WireEvent, closePixel, flushPixel, initPixel, install, reportPixel };
178
+ export { type CodeContext, DEFAULT_INGEST_URL, type InstallOptions, type InstallResult, type Level, type PackageManager, type PixelOptions, type ReportInput, type Sink, type SinkConfig, type StackFrame, type VerifyOptions, type VerifyOutcome, type WireEvent, closePixel, flushPixel, initPixel, install, isInstallProbeFingerprint, reportPixel, verifyInstall };
package/dist/index.js CHANGED
@@ -429,9 +429,11 @@ import {
429
429
  mkdirSync as mkdirSync2,
430
430
  readdirSync
431
431
  } from "fs";
432
+ import { createRequire as createRequire2 } from "module";
432
433
  import { homedir as homedir2 } from "os";
433
434
  import { dirname as dirname2, join as join2, relative, resolve, sep } from "path";
434
435
  import { spawnSync as spawnSync2 } from "child_process";
436
+ var nodeRequire = createRequire2(import.meta.url);
435
437
  var DEFAULT_INGEST_URL = "https://gg-pixel-server.buzzbeamaustralia.workers.dev";
436
438
  async function install(opts = {}) {
437
439
  const cwd = resolve(opts.cwd ?? process.cwd());
@@ -495,6 +497,7 @@ async function install(opts = {}) {
495
497
  projectSecret: created.secret,
496
498
  projectName,
497
499
  projectKind: kind,
500
+ projectRoot: nodeRoot,
498
501
  initFilePath: wired.primaryInitPath,
499
502
  envFilePath,
500
503
  projectsJsonPath,
@@ -927,26 +930,85 @@ export default nextConfig;
927
930
  );
928
931
  return;
929
932
  }
930
- const content = readFileSync2(configPath, "utf8");
931
- if (content.includes("@kenkaiiii/gg-pixel")) return;
933
+ patchNextConfigViaAst(configPath);
934
+ }
935
+ var PIXEL_PKG = "@kenkaiiii/gg-pixel";
936
+ function patchNextConfigViaAst(configPath) {
937
+ const original = readFileSync2(configPath, "utf8");
938
+ if (original.includes(PIXEL_PKG)) return;
939
+ let parseModule;
940
+ let generateCode;
941
+ try {
942
+ const mod = nodeRequire("magicast");
943
+ parseModule = mod.parseModule;
944
+ generateCode = mod.generateCode;
945
+ } catch {
946
+ return patchNextConfigViaRegex(configPath, original);
947
+ }
948
+ let module_;
949
+ try {
950
+ module_ = parseModule(original);
951
+ } catch {
952
+ return patchNextConfigViaRegex(configPath, original);
953
+ }
954
+ const cfg = resolveNextConfigObject(module_);
955
+ if (!cfg) {
956
+ return patchNextConfigViaRegex(configPath, original);
957
+ }
958
+ const existing = cfg.serverExternalPackages;
959
+ const isArrayLike = Array.isArray(existing) || typeof existing === "object" && existing !== null && existing.$type === "array";
960
+ if (isArrayLike) {
961
+ const arr = existing;
962
+ if (arr.includes(PIXEL_PKG)) return;
963
+ arr.push(PIXEL_PKG);
964
+ } else {
965
+ cfg.serverExternalPackages = [PIXEL_PKG];
966
+ }
967
+ const out = generateCode(module_).code;
968
+ if (out !== original) writeFileSync(configPath, out, "utf8");
969
+ }
970
+ function resolveNextConfigObject(mod) {
971
+ const root = mod.exports.default ?? mod.exports;
972
+ if (!root) return null;
973
+ return unwrapWrappers(root);
974
+ }
975
+ function unwrapWrappers(node) {
976
+ let cur = node;
977
+ for (let i = 0; i < 6; i++) {
978
+ if (cur.$type === "function-call") {
979
+ const args = cur.$args ?? [];
980
+ const objArg = args.find(
981
+ (a) => typeof a === "object" && a !== null && a.$type !== "function-call"
982
+ );
983
+ if (!objArg) return null;
984
+ cur = objArg;
985
+ continue;
986
+ }
987
+ if (cur.$type === "object" || cur.$type === void 0) return cur;
988
+ return null;
989
+ }
990
+ return null;
991
+ }
992
+ function patchNextConfigViaRegex(configPath, content) {
993
+ if (content.includes(PIXEL_PKG)) return;
932
994
  if (content.includes("serverExternalPackages")) {
933
995
  const updated = content.replace(
934
996
  /serverExternalPackages\s*:\s*\[([^\]]*)\]/,
935
997
  (_match, inside) => {
936
998
  const trimmed = inside.trim();
937
999
  const sep2 = trimmed.length > 0 ? ", " : "";
938
- return `serverExternalPackages: [${trimmed}${sep2}"@kenkaiiii/gg-pixel"]`;
1000
+ return `serverExternalPackages: [${trimmed}${sep2}${JSON.stringify(PIXEL_PKG)}]`;
939
1001
  }
940
1002
  );
941
1003
  if (updated !== content) writeFileSync(configPath, updated, "utf8");
942
1004
  return;
943
1005
  }
944
- const objStart = /(const\s+\w+\s*:\s*NextConfig\s*=\s*\{|module\.exports\s*=\s*\{|export\s+default\s*\{)/;
1006
+ const objStart = /(const\s+\w+\s*(?::\s*\w+)?\s*=\s*\{|module\.exports\s*=\s*\{|export\s+default\s*\{)/;
945
1007
  const m = objStart.exec(content);
946
1008
  if (m) {
947
1009
  const insertAt = m.index + m[0].length;
948
1010
  const updated = content.slice(0, insertAt) + `
949
- serverExternalPackages: ["@kenkaiiii/gg-pixel"],` + content.slice(insertAt);
1011
+ serverExternalPackages: [${JSON.stringify(PIXEL_PKG)}],` + content.slice(insertAt);
950
1012
  writeFileSync(configPath, updated, "utf8");
951
1013
  }
952
1014
  }
@@ -1574,6 +1636,7 @@ func init() {
1574
1636
  projectSecret: created.secret,
1575
1637
  projectName,
1576
1638
  projectKind: "go",
1639
+ projectRoot,
1577
1640
  initFilePath,
1578
1641
  envFilePath,
1579
1642
  projectsJsonPath,
@@ -1639,6 +1702,7 @@ GGPixel.init(
1639
1702
  projectSecret: created.secret,
1640
1703
  projectName,
1641
1704
  projectKind: "ruby",
1705
+ projectRoot,
1642
1706
  initFilePath,
1643
1707
  envFilePath,
1644
1708
  projectsJsonPath,
@@ -1708,6 +1772,7 @@ async function installPython(ctx) {
1708
1772
  projectSecret: created.secret,
1709
1773
  projectName,
1710
1774
  projectKind: "python",
1775
+ projectRoot,
1711
1776
  initFilePath,
1712
1777
  envFilePath,
1713
1778
  projectsJsonPath,
@@ -1839,6 +1904,203 @@ function writeProjectsMapping(projectsJsonPath, projectId, name, path, secret) {
1839
1904
  `, "utf8");
1840
1905
  }
1841
1906
 
1907
+ // src/verify.ts
1908
+ import { spawn as nodeSpawn } from "child_process";
1909
+ import { writeFileSync as writeFileSync2, unlinkSync, mkdirSync as mkdirSync3, existsSync as existsSync3 } from "fs";
1910
+ import { join as join3 } from "path";
1911
+ import { randomBytes } from "crypto";
1912
+ var PROBE_FINGERPRINT_PREFIX = "__pixel_install_probe__";
1913
+ function isInstallProbeFingerprint(fingerprint2) {
1914
+ return typeof fingerprint2 === "string" && fingerprint2.startsWith(PROBE_FINGERPRINT_PREFIX);
1915
+ }
1916
+ async function verifyInstall(opts) {
1917
+ const fetchFn = opts.fetchFn ?? fetch;
1918
+ const spawnFn = opts.spawnFn ?? nodeSpawn;
1919
+ const timeoutMs = opts.timeoutMs ?? 5e3;
1920
+ const ingest = opts.ingestUrl.replace(/\/+$/, "");
1921
+ const fingerprint2 = `${PROBE_FINGERPRINT_PREFIX}${randomBytes(6).toString("hex")}`;
1922
+ const start = Date.now();
1923
+ let method;
1924
+ let probeError = null;
1925
+ if (opts.skipChildProbe) {
1926
+ try {
1927
+ await postDirectIngest({
1928
+ ingestUrl: ingest,
1929
+ projectKey: opts.projectKey,
1930
+ fingerprint: fingerprint2,
1931
+ fetchFn
1932
+ });
1933
+ method = "direct_ingest";
1934
+ } catch (err) {
1935
+ return {
1936
+ kind: "failed",
1937
+ reason: "Direct ingest failed",
1938
+ hint: err.message
1939
+ };
1940
+ }
1941
+ } else {
1942
+ try {
1943
+ await runChildProbe({
1944
+ projectRoot: opts.projectRoot,
1945
+ ingestUrl: ingest,
1946
+ projectKey: opts.projectKey,
1947
+ fingerprint: fingerprint2,
1948
+ spawnFn
1949
+ });
1950
+ method = "child_process";
1951
+ } catch (err) {
1952
+ probeError = err.message;
1953
+ try {
1954
+ await postDirectIngest({
1955
+ ingestUrl: ingest,
1956
+ projectKey: opts.projectKey,
1957
+ fingerprint: fingerprint2,
1958
+ fetchFn
1959
+ });
1960
+ method = "direct_ingest";
1961
+ } catch (err2) {
1962
+ return {
1963
+ kind: "failed",
1964
+ reason: "Could not deliver probe event",
1965
+ hint: `child-process: ${probeError}; direct ingest: ${err2.message}`
1966
+ };
1967
+ }
1968
+ }
1969
+ }
1970
+ const probeRow = await pollForFingerprint({
1971
+ ingestUrl: ingest,
1972
+ projectId: opts.projectId,
1973
+ projectSecret: opts.projectSecret,
1974
+ fingerprint: fingerprint2,
1975
+ timeoutMs,
1976
+ fetchFn
1977
+ });
1978
+ if (!probeRow) {
1979
+ return {
1980
+ kind: "failed",
1981
+ reason: `Probe sent (${method}) but didn't appear in /api/projects/${opts.projectId}/errors within ${timeoutMs}ms`,
1982
+ hint: probeError ?? void 0
1983
+ };
1984
+ }
1985
+ try {
1986
+ await fetchFn(`${ingest}/api/errors/${probeRow.id}`, {
1987
+ method: "DELETE",
1988
+ headers: { authorization: `Bearer ${opts.projectSecret}` }
1989
+ });
1990
+ } catch {
1991
+ }
1992
+ return {
1993
+ kind: "ok",
1994
+ method,
1995
+ latencyMs: Date.now() - start
1996
+ };
1997
+ }
1998
+ async function runChildProbe(args) {
1999
+ const ggDir = join3(args.projectRoot, ".gg");
2000
+ if (!existsSync3(ggDir)) mkdirSync3(ggDir, { recursive: true });
2001
+ const probePath = join3(ggDir, `pixel-probe-${randomBytes(4).toString("hex")}.mjs`);
2002
+ const script = `import "@kenkaiiii/gg-pixel";
2003
+ const body = ${JSON.stringify({
2004
+ project_key: args.projectKey,
2005
+ fingerprint: args.fingerprint,
2006
+ type: "InstallProbe",
2007
+ message: "Install verification probe \u2014 auto-generated, safe to delete",
2008
+ stack: [],
2009
+ code_context: null,
2010
+ runtime: "",
2011
+ manual_report: true,
2012
+ level: "error"
2013
+ })};
2014
+ body.event_id = "evt_probe_" + crypto.randomUUID();
2015
+ body.runtime = "installer-node-" + process.versions.node;
2016
+ body.occurred_at = new Date().toISOString();
2017
+ const res = await fetch(${JSON.stringify(args.ingestUrl + "/ingest")}, {
2018
+ method: "POST",
2019
+ headers: { "content-type": "application/json" },
2020
+ body: JSON.stringify(body),
2021
+ });
2022
+ if (!res.ok) {
2023
+ const txt = await res.text().catch(() => "");
2024
+ console.error("probe ingest failed: status=" + res.status + " body=" + txt.slice(0, 200));
2025
+ process.exit(1);
2026
+ }
2027
+ `;
2028
+ writeFileSync2(probePath, script, "utf8");
2029
+ try {
2030
+ await new Promise((resolve2, reject) => {
2031
+ const opts = { cwd: args.projectRoot, stdio: "pipe" };
2032
+ const child = args.spawnFn("node", [probePath], opts);
2033
+ let stderr = "";
2034
+ child.stderr?.on("data", (b) => {
2035
+ stderr += b.toString();
2036
+ });
2037
+ const timer = setTimeout(() => {
2038
+ child.kill("SIGKILL");
2039
+ reject(new Error("Probe child timed out after 10s"));
2040
+ }, 1e4);
2041
+ child.on("error", (err) => {
2042
+ clearTimeout(timer);
2043
+ reject(err);
2044
+ });
2045
+ child.on("exit", (code) => {
2046
+ clearTimeout(timer);
2047
+ if (code === 0) resolve2();
2048
+ else reject(new Error(`Probe exited code=${code}; stderr: ${stderr.trim().slice(0, 400)}`));
2049
+ });
2050
+ });
2051
+ } finally {
2052
+ try {
2053
+ unlinkSync(probePath);
2054
+ } catch {
2055
+ }
2056
+ }
2057
+ }
2058
+ async function postDirectIngest(args) {
2059
+ const res = await args.fetchFn(`${args.ingestUrl}/ingest`, {
2060
+ method: "POST",
2061
+ headers: { "content-type": "application/json" },
2062
+ body: JSON.stringify({
2063
+ event_id: `evt_probe_${randomBytes(8).toString("hex")}`,
2064
+ project_key: args.projectKey,
2065
+ fingerprint: args.fingerprint,
2066
+ type: "InstallProbe",
2067
+ message: "Install verification probe",
2068
+ stack: [],
2069
+ code_context: null,
2070
+ runtime: `installer-node-${process.versions.node}`,
2071
+ manual_report: true,
2072
+ level: "error",
2073
+ occurred_at: (/* @__PURE__ */ new Date()).toISOString()
2074
+ })
2075
+ });
2076
+ if (!res.ok) {
2077
+ let body = "";
2078
+ try {
2079
+ body = await res.text();
2080
+ } catch {
2081
+ }
2082
+ throw new Error(`POST /ingest \u2192 ${res.status} ${body.slice(0, 200)}`);
2083
+ }
2084
+ }
2085
+ async function pollForFingerprint(args) {
2086
+ const deadline = Date.now() + args.timeoutMs;
2087
+ while (Date.now() < deadline) {
2088
+ try {
2089
+ const res = await args.fetchFn(`${args.ingestUrl}/api/projects/${args.projectId}/errors`, {
2090
+ headers: { authorization: `Bearer ${args.projectSecret}` }
2091
+ });
2092
+ if (res.ok) {
2093
+ const body = await res.json();
2094
+ const match = body.errors.find((e) => e.fingerprint === args.fingerprint);
2095
+ if (match) return { id: match.id };
2096
+ }
2097
+ } catch {
2098
+ }
2099
+ await new Promise((r) => setTimeout(r, 250));
2100
+ }
2101
+ return null;
2102
+ }
2103
+
1842
2104
  // src/index.ts
1843
2105
  var active = null;
1844
2106
  function initPixel(options) {
@@ -1890,6 +2152,8 @@ export {
1890
2152
  flushPixel,
1891
2153
  initPixel,
1892
2154
  install,
1893
- reportPixel
2155
+ isInstallProbeFingerprint,
2156
+ reportPixel,
2157
+ verifyInstall
1894
2158
  };
1895
2159
  //# sourceMappingURL=index.js.map