Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
79 commits
Select commit Hold shift + click to select a range
5b0a816
docs: plan for aligning the JS public API with the mobile SDKs
krassx Oct 12, 2026
0a3f126
feat(core): lifecycle events listener and ReportHandler.after
krassx Oct 12, 2026
74a1dc9
refactor(bugsee): performance facade pass-throughs, typed upload mode…
krassx Oct 12, 2026
03bb314
Merge branch 'worktree-agent-a606ed84469b1fd67'
krassx Oct 12, 2026
9041efb
feat(core): user attachments on reports
krassx Oct 12, 2026
8a59c9b
feat(core,browser): blackout (startBlackout/endBlackout/isBlackout)
krassx Oct 12, 2026
5d6d2df
Merge branch 'worktree-agent-a46512d32a9ae0b44'
krassx Oct 12, 2026
494a1c6
fix: keep attachments on the public UploadOptions after merging T2 an…
krassx Oct 12, 2026
d90e736
Merge branch 'worktree-agent-a430e9c412370ae10'
krassx Oct 12, 2026
87dd417
feat(core,browser,node): log level, breadcrumbs, http-errors, sanitiz…
krassx Oct 12, 2026
8a2fb4c
Merge branch 'worktree-agent-a533703a59d499e43'
krassx Oct 12, 2026
8d9e88f
feat(core,browser,node): getStatus, relaunch, getLaunchOptions
krassx Oct 12, 2026
1e9603e
Merge branch 'worktree-agent-acf8dd52fedb07b5e'
krassx Oct 12, 2026
e3e8253
feat(core): emit BlackoutStarted/BlackoutEnded lifecycle events
krassx Oct 12, 2026
e4763b2
feat(core): logException domain and skipFrames options
krassx Oct 12, 2026
2eda16f
feat(browser): persist the user identifier across page loads
krassx Oct 12, 2026
b5ff961
Merge branch 'worktree-agent-ad74128a517db0e6a'
krassx Oct 12, 2026
d68efad
feat(core): notify()
krassx Oct 12, 2026
6c63d24
Merge branch 'worktree-agent-a7e5b404d0bf1c37a'
krassx Oct 12, 2026
3f27b81
fix(browser): user-identifier persistence is opt-in (PII), erasable, …
krassx Oct 12, 2026
83327ee
docs(review): round 1 findings and dispositions for the mobile-alignm…
krassx Oct 12, 2026
f498229
fix(core,capture): option resolver, http-error detection parity, expo…
krassx Oct 12, 2026
06ec18a
fix(core): attachment marker/validation, notify relay and skipFrames …
krassx Oct 12, 2026
7395c6d
Merge branch 'worktree-agent-a2459d5afb9d2a4d2'
krassx Oct 12, 2026
61ce259
fix(electron,browser,replay,webview): blackout completeness - review …
krassx Oct 12, 2026
eef7e17
fix(node,browser,webworker,vercel-edge): stop/recovery data safety an…
krassx Oct 12, 2026
03b85b7
Merge branch 'worktree-agent-a1fe5a61f8f10f1ff'
krassx Oct 12, 2026
d955dda
Merge branch 'worktree-agent-abb26dcd29c1f6e99'
krassx Oct 12, 2026
f1086a2
fix(core): review round 1 - lifecycle events, relaunch, ReportHandler…
krassx Oct 12, 2026
feabbde
Merge branch 'worktree-agent-a7d7dc732d1f068c2'
krassx Oct 12, 2026
bb6128c
test(bugsee): drop the parity exemption now that every entry exports …
krassx Oct 12, 2026
e2f928c
refactor(core): relaunch copies the carrier's already-filtered seams
krassx Oct 12, 2026
01552b2
docs(review): round 2 findings and dispositions for the mobile-alignm…
krassx Oct 12, 2026
eb0fe57
fix(core,replay,electron,webview,browser): blackout fail-closed and h…
krassx Oct 12, 2026
1f7a336
fix(exports,docs,tests): adapter value exports, docs accuracy, test h…
krassx Oct 12, 2026
818b228
Merge branch 'worktree-agent-a514f51c3f7bd2dd5'
krassx Oct 12, 2026
26c1589
fix(core,capture): frame boundary, retry floor, fail-closed options, …
krassx Oct 12, 2026
f8658b4
Merge branch 'worktree-agent-acb12803119597e05'
krassx Oct 12, 2026
b24b382
fix(core,node,browser,webworker,vercel-edge,webview): stop/launch saf…
krassx Oct 12, 2026
f930d6b
Merge branch 'worktree-agent-a6901633b9d9f059a'
krassx Oct 12, 2026
63b5f9c
docs(review): round 3 findings and dispositions for the mobile-alignm…
krassx Oct 12, 2026
cf080f9
fix(core,webview,webworker,nextjs,nuxt): JSC call-site frames, relaun…
krassx Oct 12, 2026
5366036
fix(core,node,browser,webview,electron,capture): fail-closed privacy …
krassx Oct 12, 2026
f164760
Merge branch 'worktree-agent-a7f0f7d4f60c764d4'
krassx Oct 12, 2026
2e1a7b3
fix(core,node,browser,webworker): shared stop drain, blackout ownersh…
krassx Oct 12, 2026
a98dabb
Merge branch 'worktree-agent-ac4eaa0e0f487ed3d'
krassx Oct 12, 2026
ca13a0e
docs(review): round 4 findings and dispositions for the mobile-alignm…
krassx Oct 12, 2026
17c861c
test(protocol): give the deliberately-quadratic linearity tests a rea…
krassx Oct 12, 2026
cb79173
fix(core,nextjs,sveltekit,astro,nuxt): marker change signature covera…
krassx Oct 12, 2026
46d7556
fix(node,core,browser,webworker,webview): subtree-aware stop marker, …
krassx Oct 12, 2026
76b607d
Merge branch 'worktree-agent-a29b513d9360048aa'
krassx Oct 12, 2026
b2e3e91
fix(core,browser,node,cloudflare,electron,capture): protecting option…
krassx Oct 12, 2026
965ae08
Merge branch 'worktree-agent-aaf2fcb560c6289dd'
krassx Oct 12, 2026
749a5a7
chore: clear the three lint warnings the round-4 fixes introduced
krassx Oct 12, 2026
08bc589
test(bugsee): build each TypeScript program once and budget the inter…
krassx Oct 12, 2026
ab6d920
docs(review): round 5 findings and dispositions for the mobile-alignm…
krassx Oct 12, 2026
be4b123
test(bugsee,protocol): share TypeScript program source files across t…
krassx Oct 12, 2026
83fda32
fix(webview,core,node,vercel-edge): allow-listed WebView facade, priv…
krassx Oct 12, 2026
4830336
Merge branch 'worktree-agent-a256c21ddd41f5977'
krassx Oct 12, 2026
cc24345
fix(capture,node,vercel-edge,browser,bugsee,nextjs,nuxt): word-likene…
krassx Oct 12, 2026
ba40cfe
Merge branch 'worktree-agent-a610a1acb050c0b76'
krassx Oct 12, 2026
37a8b06
fix(core): contain rejecting async lifecycle listeners and ReportHand…
krassx Oct 12, 2026
5bf4791
fix(browser,core,bugsee): pointer gestures cannot span or start in a …
krassx Oct 12, 2026
7da7f3e
fix(core,browser): after runs from the handler installed at submit, a…
krassx Oct 12, 2026
a1caa99
fix(core): a failed urgent notify at the queue cap keeps the newest i…
krassx Oct 12, 2026
dc5fc29
fix(core,electron,webview,performance,bugsee): binary user attachment…
krassx Oct 12, 2026
f1982c5
fix(core): SDK .dmp attachments and hostile attachment lists survive …
krassx Oct 12, 2026
3e91243
test(protocol): linearity guards re-measure up to three times, a real…
krassx Oct 12, 2026
9fc8470
fix(core,webview): a page clearing carrier.client cannot re-open the …
krassx Oct 12, 2026
99619f7
Merge branch 'worktree-agent-a73f7d1d1be90e760'
krassx Oct 12, 2026
5bfc3a7
fix(capture): template numberless 24+ char tokens and single-digit-ru…
krassx Oct 12, 2026
f0ff405
Merge branch 'worktree-agent-a98f1f20e43f21f20'
krassx Oct 12, 2026
cfa4677
fix(webview,performance): a page-supplied carrier cannot mint a secon…
krassx Oct 12, 2026
31208e3
test(performance): fix the fire() call in the KillSdk uploader test
krassx Oct 12, 2026
51d2c1e
fix(webview): the page-reachable launch() drops every injectable seam…
krassx Oct 12, 2026
968f61d
test(instrumentation-tests,webview): the conformance harness uses lau…
krassx Oct 12, 2026
9366ba5
fix(webview): shadow the stripped seams with own undefined so a pollu…
krassx Oct 12, 2026
ba608c4
fix(webview,core): the page-reachable launch() forwards a null-protot…
krassx Oct 12, 2026
014d37f
test(node): pin that captureLogs gates automatic capture and not clie…
krassx Oct 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
177 changes: 177 additions & 0 deletions dev-packages/packaging-guard/src/adapter-export-parity.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
import * as astroClient from '@bugsee/astro/client';
import * as astroEdge from '@bugsee/astro/edge';
import * as astroServer from '@bugsee/astro/server';
import * as browser from '@bugsee/browser';
import * as umbrellaBrowser from '@bugsee/bugsee';
import * as umbrellaEdge from '@bugsee/bugsee/edge-light';
import * as umbrellaNode from '@bugsee/bugsee/node';
import * as bun from '@bugsee/bun';
import { getDefaultNetworkSanitizer } from '@bugsee/capture';
import * as cloudflare from '@bugsee/cloudflare';
import { addReportAttachment, BugseeStatus, LifecycleEvents } from '@bugsee/core';
import * as deno from '@bugsee/deno';
import * as electronMain from '@bugsee/electron/main';
import * as electronRenderer from '@bugsee/electron/renderer';
import * as nextClient from '@bugsee/nextjs/client';
import * as nextEdge from '@bugsee/nextjs/edge';
import * as nextServer from '@bugsee/nextjs/server';
import * as node from '@bugsee/node';
import * as nuxtClient from '@bugsee/nuxt/client';
import * as nuxtEdge from '@bugsee/nuxt/edge';
import * as nuxtServer from '@bugsee/nuxt/server';
import * as remixClient from '@bugsee/remix/client';
import * as remixServer from '@bugsee/remix/server';
import * as svelteKitClient from '@bugsee/sveltekit/client';
import * as svelteKitEdge from '@bugsee/sveltekit/edge';
import * as svelteKitServer from '@bugsee/sveltekit/server';
import * as vercelEdge from '@bugsee/vercel-edge';
import * as webview from '@bugsee/webview';
import * as webworker from '@bugsee/webworker';
import { describe, expect, it } from 'vitest';

// EXPORT PARITY across the directly imported packages and the framework-adapter subpaths (review R2D-4).
//
// The umbrella entries are pinned in `packages/bugsee/src/export-parity.test.ts`. A user who imports
// `@bugsee/vercel-edge`, `@bugsee/webworker`, `@bugsee/nextjs/server`... directly gets the same product, so the
// same VALUE API must be there too; when it was not, `import { getStatus } from '@bugsee/nextjs/server'` was a
// build error and `LifecycleEvents` an `undefined` at runtime. WHEN YOU ADD A NEW PUBLIC RUNTIME API TO THE
// UMBRELLA: add its name to `PUBLIC_RUNTIME_API` below (and to the umbrella parity test), then this file fails
// for every package that forgot to re-export it. A package that CANNOT offer a name lists it in `EXEMPT` with the
// reason, so an omission is always a decision, never an accident.

const PUBLIC_RUNTIME_API = [
'addReportAttachment',
'BugseeStatus',
'getDefaultNetworkSanitizer',
'getStatus',
'LifecycleEvents',
'relaunch',
] as const;
type ApiName = (typeof PUBLIC_RUNTIME_API)[number];

const ELECTRON_RELAUNCH =
'Electron: launchMain needs the app ipcMain (not a retained seam) and registers receivers on it; a renderer session is owned by main';
const WEBVIEW =
'WebView: the process carrier holds only a resolver-only facade, so there is no status to read and no launch to re-run';

const targets: Record<
string,
{ entry: Record<string, unknown>; exempt?: Partial<Record<ApiName, string>> }
> = {
'@bugsee/browser': { entry: browser },
'@bugsee/node': { entry: node },
'@bugsee/bun': { entry: bun },
'@bugsee/deno': { entry: deno },
'@bugsee/vercel-edge': { entry: vercelEdge },
'@bugsee/cloudflare': { entry: cloudflare },
'@bugsee/webworker': { entry: webworker },
'@bugsee/webview': {
entry: webview,
exempt: { getStatus: WEBVIEW, relaunch: WEBVIEW },
},
'@bugsee/electron/main': { entry: electronMain, exempt: { relaunch: ELECTRON_RELAUNCH } },
'@bugsee/electron/renderer': { entry: electronRenderer, exempt: { relaunch: ELECTRON_RELAUNCH } },
'@bugsee/nextjs/server': { entry: nextServer },
'@bugsee/nextjs/edge': { entry: nextEdge },
'@bugsee/nextjs/client': { entry: nextClient },
'@bugsee/remix/server': { entry: remixServer },
'@bugsee/remix/client': { entry: remixClient },
'@bugsee/sveltekit/server': { entry: svelteKitServer },
'@bugsee/sveltekit/edge': { entry: svelteKitEdge },
'@bugsee/sveltekit/client': { entry: svelteKitClient },
'@bugsee/astro/server': { entry: astroServer },
'@bugsee/astro/edge': { entry: astroEdge },
'@bugsee/astro/client': { entry: astroClient },
'@bugsee/nuxt/server': { entry: nuxtServer },
'@bugsee/nuxt/edge': { entry: nuxtEdge },
'@bugsee/nuxt/client': { entry: nuxtClient },
};

describe('the umbrella still exports every name this guard requires of the adapters', () => {
for (const [name, entry] of Object.entries({
browser: umbrellaBrowser,
node: umbrellaNode,
'edge-light': umbrellaEdge,
})) {
for (const api of PUBLIC_RUNTIME_API) {
it(`${name} exports ${api}`, () => {
expect(typeof (entry as Record<string, unknown>)[api]).not.toBe('undefined');
});
}
}
});

describe('directly imported packages and adapter subpaths expose the umbrella value API', () => {
for (const [pkg, { entry, exempt = {} }] of Object.entries(targets)) {
for (const api of PUBLIC_RUNTIME_API) {
if (exempt[api] !== undefined) {
it(`${pkg} does not export ${api} (${exempt[api]})`, () => {
expect(api in entry).toBe(false);
});
continue;
}
it(`${pkg} exports ${api}`, () => {
expect(entry[api]).toBeDefined();
});
}
}
});

describe('the re-exports are the SAME functions, not look-alikes', () => {
for (const [pkg, { entry }] of Object.entries(targets)) {
it(`${pkg} re-exports core's constants and helpers`, () => {
expect(entry.addReportAttachment).toBe(addReportAttachment);
expect(entry.BugseeStatus).toBe(BugseeStatus);
expect(entry.LifecycleEvents).toBe(LifecycleEvents);
expect(entry.getDefaultNetworkSanitizer).toBe(getDefaultNetworkSanitizer);
});
}

it('getStatus answers Stopped when nothing is launched, wherever it is imported from', () => {
for (const [pkg, { entry }] of Object.entries(targets)) {
if (typeof entry.getStatus === 'function') {
expect([pkg, (entry.getStatus as () => number)()]).toEqual([pkg, BugseeStatus.Stopped]);
}
}
});

it('relaunch is built per package and rejects before any launch, on every package that offers it', async () => {
for (const [pkg, { entry }] of Object.entries(targets)) {
if (typeof entry.relaunch === 'function') {
await expect((entry.relaunch as () => Promise<unknown>)(), pkg).rejects.toThrow(
/before launch/,
);
}
}
});

it('the umbrella-based adapters keep relaunch off the plain-launch path where their launch differs', () => {
// Cloudflare / Bun / Deno / Next.js server run a launch that adds defaults on top of the shared one; the
// package-level `relaunch` must be THEIR relaunch, not the one re-exported from the layer below.
expect(cloudflare.relaunch).not.toBe(vercelEdge.relaunch);
expect(bun.relaunch).not.toBe(node.relaunch);
expect(deno.relaunch).not.toBe(node.relaunch);
expect(nextServer.relaunch).not.toBe(umbrellaNode.relaunch);
});
});

// FORBIDDEN names (R5H-4). `relaunchEdge` / `rememberEdgeLaunch` are the SEAM of the meta-framework edge adapters
// (`@bugsee/vercel-edge/relaunch-edge`). They once leaked into `@bugsee/bugsee/edge-light` and `@bugsee/cloudflare`
// through `export *` from the vercel-edge index, where a plain `relaunchEdge()` silently re-ran `launchEdge` and
// dropped the umbrella's extensions / the Cloudflare launch. They must never be public on any package entry.
describe('internal edge relaunch seam names are on NO public entry', () => {
const FORBIDDEN = ['relaunchEdge', 'rememberEdgeLaunch'] as const;
const entries: Record<string, Record<string, unknown>> = {
'@bugsee/bugsee (browser)': umbrellaBrowser,
'@bugsee/bugsee (node)': umbrellaNode,
'@bugsee/bugsee/edge-light': umbrellaEdge,
...Object.fromEntries(Object.entries(targets).map(([pkg, { entry }]) => [pkg, entry])),
};
for (const [pkg, entry] of Object.entries(entries)) {
for (const name of FORBIDDEN) {
it(`${pkg} does not export ${name}`, () => {
expect(name in entry).toBe(false);
});
}
}
});
Loading
Loading