@kenkaiiii/gg-pixel 4.3.90 → 4.3.91

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
@@ -495,6 +495,7 @@ async function install(opts = {}) {
495
495
  projectSecret: created.secret,
496
496
  projectName,
497
497
  projectKind: kind,
498
+ projectRoot: nodeRoot,
498
499
  initFilePath: wired.primaryInitPath,
499
500
  envFilePath,
500
501
  projectsJsonPath,
@@ -1574,6 +1575,7 @@ func init() {
1574
1575
  projectSecret: created.secret,
1575
1576
  projectName,
1576
1577
  projectKind: "go",
1578
+ projectRoot,
1577
1579
  initFilePath,
1578
1580
  envFilePath,
1579
1581
  projectsJsonPath,
@@ -1639,6 +1641,7 @@ GGPixel.init(
1639
1641
  projectSecret: created.secret,
1640
1642
  projectName,
1641
1643
  projectKind: "ruby",
1644
+ projectRoot,
1642
1645
  initFilePath,
1643
1646
  envFilePath,
1644
1647
  projectsJsonPath,
@@ -1708,6 +1711,7 @@ async function installPython(ctx) {
1708
1711
  projectSecret: created.secret,
1709
1712
  projectName,
1710
1713
  projectKind: "python",
1714
+ projectRoot,
1711
1715
  initFilePath,
1712
1716
  envFilePath,
1713
1717
  projectsJsonPath,
@@ -1839,6 +1843,203 @@ function writeProjectsMapping(projectsJsonPath, projectId, name, path, secret) {
1839
1843
  `, "utf8");
1840
1844
  }
1841
1845
 
1846
+ // src/verify.ts
1847
+ import { spawn as nodeSpawn } from "child_process";
1848
+ import { writeFileSync as writeFileSync2, unlinkSync, mkdirSync as mkdirSync3, existsSync as existsSync3 } from "fs";
1849
+ import { join as join3 } from "path";
1850
+ import { randomBytes } from "crypto";
1851
+ var PROBE_FINGERPRINT_PREFIX = "__pixel_install_probe__";
1852
+ function isInstallProbeFingerprint(fingerprint2) {
1853
+ return typeof fingerprint2 === "string" && fingerprint2.startsWith(PROBE_FINGERPRINT_PREFIX);
1854
+ }
1855
+ async function verifyInstall(opts) {
1856
+ const fetchFn = opts.fetchFn ?? fetch;
1857
+ const spawnFn = opts.spawnFn ?? nodeSpawn;
1858
+ const timeoutMs = opts.timeoutMs ?? 5e3;
1859
+ const ingest = opts.ingestUrl.replace(/\/+$/, "");
1860
+ const fingerprint2 = `${PROBE_FINGERPRINT_PREFIX}${randomBytes(6).toString("hex")}`;
1861
+ const start = Date.now();
1862
+ let method;
1863
+ let probeError = null;
1864
+ if (opts.skipChildProbe) {
1865
+ try {
1866
+ await postDirectIngest({
1867
+ ingestUrl: ingest,
1868
+ projectKey: opts.projectKey,
1869
+ fingerprint: fingerprint2,
1870
+ fetchFn
1871
+ });
1872
+ method = "direct_ingest";
1873
+ } catch (err) {
1874
+ return {
1875
+ kind: "failed",
1876
+ reason: "Direct ingest failed",
1877
+ hint: err.message
1878
+ };
1879
+ }
1880
+ } else {
1881
+ try {
1882
+ await runChildProbe({
1883
+ projectRoot: opts.projectRoot,
1884
+ ingestUrl: ingest,
1885
+ projectKey: opts.projectKey,
1886
+ fingerprint: fingerprint2,
1887
+ spawnFn
1888
+ });
1889
+ method = "child_process";
1890
+ } catch (err) {
1891
+ probeError = err.message;
1892
+ try {
1893
+ await postDirectIngest({
1894
+ ingestUrl: ingest,
1895
+ projectKey: opts.projectKey,
1896
+ fingerprint: fingerprint2,
1897
+ fetchFn
1898
+ });
1899
+ method = "direct_ingest";
1900
+ } catch (err2) {
1901
+ return {
1902
+ kind: "failed",
1903
+ reason: "Could not deliver probe event",
1904
+ hint: `child-process: ${probeError}; direct ingest: ${err2.message}`
1905
+ };
1906
+ }
1907
+ }
1908
+ }
1909
+ const probeRow = await pollForFingerprint({
1910
+ ingestUrl: ingest,
1911
+ projectId: opts.projectId,
1912
+ projectSecret: opts.projectSecret,
1913
+ fingerprint: fingerprint2,
1914
+ timeoutMs,
1915
+ fetchFn
1916
+ });
1917
+ if (!probeRow) {
1918
+ return {
1919
+ kind: "failed",
1920
+ reason: `Probe sent (${method}) but didn't appear in /api/projects/${opts.projectId}/errors within ${timeoutMs}ms`,
1921
+ hint: probeError ?? void 0
1922
+ };
1923
+ }
1924
+ try {
1925
+ await fetchFn(`${ingest}/api/errors/${probeRow.id}`, {
1926
+ method: "DELETE",
1927
+ headers: { authorization: `Bearer ${opts.projectSecret}` }
1928
+ });
1929
+ } catch {
1930
+ }
1931
+ return {
1932
+ kind: "ok",
1933
+ method,
1934
+ latencyMs: Date.now() - start
1935
+ };
1936
+ }
1937
+ async function runChildProbe(args) {
1938
+ const ggDir = join3(args.projectRoot, ".gg");
1939
+ if (!existsSync3(ggDir)) mkdirSync3(ggDir, { recursive: true });
1940
+ const probePath = join3(ggDir, `pixel-probe-${randomBytes(4).toString("hex")}.mjs`);
1941
+ const script = `import "@kenkaiiii/gg-pixel";
1942
+ const body = ${JSON.stringify({
1943
+ project_key: args.projectKey,
1944
+ fingerprint: args.fingerprint,
1945
+ type: "InstallProbe",
1946
+ message: "Install verification probe \u2014 auto-generated, safe to delete",
1947
+ stack: [],
1948
+ code_context: null,
1949
+ runtime: "",
1950
+ manual_report: true,
1951
+ level: "error"
1952
+ })};
1953
+ body.event_id = "evt_probe_" + crypto.randomUUID();
1954
+ body.runtime = "installer-node-" + process.versions.node;
1955
+ body.occurred_at = new Date().toISOString();
1956
+ const res = await fetch(${JSON.stringify(args.ingestUrl + "/ingest")}, {
1957
+ method: "POST",
1958
+ headers: { "content-type": "application/json" },
1959
+ body: JSON.stringify(body),
1960
+ });
1961
+ if (!res.ok) {
1962
+ const txt = await res.text().catch(() => "");
1963
+ console.error("probe ingest failed: status=" + res.status + " body=" + txt.slice(0, 200));
1964
+ process.exit(1);
1965
+ }
1966
+ `;
1967
+ writeFileSync2(probePath, script, "utf8");
1968
+ try {
1969
+ await new Promise((resolve2, reject) => {
1970
+ const opts = { cwd: args.projectRoot, stdio: "pipe" };
1971
+ const child = args.spawnFn("node", [probePath], opts);
1972
+ let stderr = "";
1973
+ child.stderr?.on("data", (b) => {
1974
+ stderr += b.toString();
1975
+ });
1976
+ const timer = setTimeout(() => {
1977
+ child.kill("SIGKILL");
1978
+ reject(new Error("Probe child timed out after 10s"));
1979
+ }, 1e4);
1980
+ child.on("error", (err) => {
1981
+ clearTimeout(timer);
1982
+ reject(err);
1983
+ });
1984
+ child.on("exit", (code) => {
1985
+ clearTimeout(timer);
1986
+ if (code === 0) resolve2();
1987
+ else reject(new Error(`Probe exited code=${code}; stderr: ${stderr.trim().slice(0, 400)}`));
1988
+ });
1989
+ });
1990
+ } finally {
1991
+ try {
1992
+ unlinkSync(probePath);
1993
+ } catch {
1994
+ }
1995
+ }
1996
+ }
1997
+ async function postDirectIngest(args) {
1998
+ const res = await args.fetchFn(`${args.ingestUrl}/ingest`, {
1999
+ method: "POST",
2000
+ headers: { "content-type": "application/json" },
2001
+ body: JSON.stringify({
2002
+ event_id: `evt_probe_${randomBytes(8).toString("hex")}`,
2003
+ project_key: args.projectKey,
2004
+ fingerprint: args.fingerprint,
2005
+ type: "InstallProbe",
2006
+ message: "Install verification probe",
2007
+ stack: [],
2008
+ code_context: null,
2009
+ runtime: `installer-node-${process.versions.node}`,
2010
+ manual_report: true,
2011
+ level: "error",
2012
+ occurred_at: (/* @__PURE__ */ new Date()).toISOString()
2013
+ })
2014
+ });
2015
+ if (!res.ok) {
2016
+ let body = "";
2017
+ try {
2018
+ body = await res.text();
2019
+ } catch {
2020
+ }
2021
+ throw new Error(`POST /ingest \u2192 ${res.status} ${body.slice(0, 200)}`);
2022
+ }
2023
+ }
2024
+ async function pollForFingerprint(args) {
2025
+ const deadline = Date.now() + args.timeoutMs;
2026
+ while (Date.now() < deadline) {
2027
+ try {
2028
+ const res = await args.fetchFn(`${args.ingestUrl}/api/projects/${args.projectId}/errors`, {
2029
+ headers: { authorization: `Bearer ${args.projectSecret}` }
2030
+ });
2031
+ if (res.ok) {
2032
+ const body = await res.json();
2033
+ const match = body.errors.find((e) => e.fingerprint === args.fingerprint);
2034
+ if (match) return { id: match.id };
2035
+ }
2036
+ } catch {
2037
+ }
2038
+ await new Promise((r) => setTimeout(r, 250));
2039
+ }
2040
+ return null;
2041
+ }
2042
+
1842
2043
  // src/index.ts
1843
2044
  var active = null;
1844
2045
  function initPixel(options) {
@@ -1890,6 +2091,8 @@ export {
1890
2091
  flushPixel,
1891
2092
  initPixel,
1892
2093
  install,
1893
- reportPixel
2094
+ isInstallProbeFingerprint,
2095
+ reportPixel,
2096
+ verifyInstall
1894
2097
  };
1895
2098
  //# sourceMappingURL=index.js.map