@kenkaiiii/gg-pixel 4.3.89 → 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,
@@ -1097,6 +1098,11 @@ function wireElectron({ projectRoot, pkg, projectKey, ingestUrl }) {
1097
1098
  "renderer.ts",
1098
1099
  "renderer.tsx",
1099
1100
  "renderer.js",
1101
+ // `src/renderer.{ts,tsx,js}` is the convention used by multi-window
1102
+ // Electron apps that keep all renderer entries in src/.
1103
+ "src/renderer.ts",
1104
+ "src/renderer.tsx",
1105
+ "src/renderer.js",
1100
1106
  "src/index.tsx",
1101
1107
  "src/index.jsx",
1102
1108
  "src/main.tsx",
@@ -1152,7 +1158,7 @@ function resolveMainEntryFromPkg(projectRoot, pkg) {
1152
1158
  "electron/main.ts"
1153
1159
  ]);
1154
1160
  }
1155
- var RENDERER_HTML_DIRS = ["ui", "renderer", "src/renderer", "public", "static"];
1161
+ var RENDERER_HTML_DIRS = ["ui", "renderer", "src/renderer", "src", "public", "static"];
1156
1162
  function findRendererHtmlFiles(projectRoot) {
1157
1163
  for (const dir of RENDERER_HTML_DIRS) {
1158
1164
  const root = join2(projectRoot, dir);
@@ -1569,6 +1575,7 @@ func init() {
1569
1575
  projectSecret: created.secret,
1570
1576
  projectName,
1571
1577
  projectKind: "go",
1578
+ projectRoot,
1572
1579
  initFilePath,
1573
1580
  envFilePath,
1574
1581
  projectsJsonPath,
@@ -1634,6 +1641,7 @@ GGPixel.init(
1634
1641
  projectSecret: created.secret,
1635
1642
  projectName,
1636
1643
  projectKind: "ruby",
1644
+ projectRoot,
1637
1645
  initFilePath,
1638
1646
  envFilePath,
1639
1647
  projectsJsonPath,
@@ -1703,6 +1711,7 @@ async function installPython(ctx) {
1703
1711
  projectSecret: created.secret,
1704
1712
  projectName,
1705
1713
  projectKind: "python",
1714
+ projectRoot,
1706
1715
  initFilePath,
1707
1716
  envFilePath,
1708
1717
  projectsJsonPath,
@@ -1834,6 +1843,203 @@ function writeProjectsMapping(projectsJsonPath, projectId, name, path, secret) {
1834
1843
  `, "utf8");
1835
1844
  }
1836
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
+
1837
2043
  // src/index.ts
1838
2044
  var active = null;
1839
2045
  function initPixel(options) {
@@ -1885,6 +2091,8 @@ export {
1885
2091
  flushPixel,
1886
2092
  initPixel,
1887
2093
  install,
1888
- reportPixel
2094
+ isInstallProbeFingerprint,
2095
+ reportPixel,
2096
+ verifyInstall
1889
2097
  };
1890
2098
  //# sourceMappingURL=index.js.map