@bigbertha/sdk 0.0.0-stage → 0.1.0

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.
Files changed (46) hide show
  1. package/README.md +149 -2
  2. package/dist/bin.d.ts +2 -0
  3. package/dist/bin.js +3 -0
  4. package/dist/browser/index.d.ts +9 -0
  5. package/dist/browser/index.js +164 -0
  6. package/dist/browser/user-agent.d.ts +4 -0
  7. package/dist/browser/user-agent.js +38 -0
  8. package/dist/build/index.d.ts +39 -0
  9. package/dist/build/index.js +177 -0
  10. package/dist/build/release.d.ts +8 -0
  11. package/dist/build/release.js +36 -0
  12. package/dist/cli.d.ts +1 -0
  13. package/dist/cli.js +89 -0
  14. package/dist/core/client.d.ts +39 -0
  15. package/dist/core/client.js +211 -0
  16. package/dist/core/debug-ids.d.ts +3 -0
  17. package/dist/core/debug-ids.js +35 -0
  18. package/dist/core/env.d.ts +2 -0
  19. package/dist/core/env.js +18 -0
  20. package/dist/core/hub.d.ts +12 -0
  21. package/dist/core/hub.js +27 -0
  22. package/dist/core/scrub.d.ts +2 -0
  23. package/dist/core/scrub.js +11 -0
  24. package/dist/core/stack.d.ts +5 -0
  25. package/dist/core/stack.js +45 -0
  26. package/dist/core/transport.d.ts +15 -0
  27. package/dist/core/transport.js +48 -0
  28. package/dist/core/types.d.ts +37 -0
  29. package/dist/core/types.js +1 -0
  30. package/dist/core/version.d.ts +1 -0
  31. package/dist/core/version.js +1 -0
  32. package/dist/edge/index.d.ts +5 -0
  33. package/dist/edge/index.js +25 -0
  34. package/dist/generated/ingest.d.ts +153 -0
  35. package/dist/generated/ingest.js +2 -0
  36. package/dist/next/client.d.ts +2 -0
  37. package/dist/next/client.js +34 -0
  38. package/dist/next/config.d.ts +8 -0
  39. package/dist/next/config.js +36 -0
  40. package/dist/next/index.d.ts +16 -0
  41. package/dist/next/index.js +22 -0
  42. package/dist/node/index.d.ts +19 -0
  43. package/dist/node/index.js +98 -0
  44. package/dist/vite/index.d.ts +44 -0
  45. package/dist/vite/index.js +63 -0
  46. package/package.json +58 -4
@@ -0,0 +1,153 @@
1
+ /**
2
+ * Answer to `POST /ingest/v1/deploys`.
3
+ */
4
+ export type DeployRecorded = {
5
+ releaseId: string;
6
+ };
7
+ /**
8
+ * `POST /ingest/v1/events`, gzip or plain JSON, `Authorization: Bearer <ingest key>`.
9
+ */
10
+ export type IngestBatch = {
11
+ sdk: IngestSdk;
12
+ events: Array<IngestEvent>;
13
+ };
14
+ export type IngestBreadcrumb = {
15
+ timestamp: string;
16
+ category: string;
17
+ level?: string;
18
+ message: string;
19
+ };
20
+ export type IngestCommit = {
21
+ sha: string;
22
+ message: string;
23
+ author: string;
24
+ };
25
+ export type IngestContext = {
26
+ browser?: string;
27
+ os?: string;
28
+ runtime?: string;
29
+ userAgent?: string;
30
+ };
31
+ /**
32
+ * `POST /ingest/v1/deploys`, from CI after a deploy.
33
+ */
34
+ export type IngestDeploy = {
35
+ release: string;
36
+ environment?: string;
37
+ commit?: IngestCommit;
38
+ /**
39
+ * Defaults to now.
40
+ */
41
+ deployedAt?: string;
42
+ };
43
+ /**
44
+ * Errors from ingest endpoints: `{ error: "…" }` with a 4xx status.
45
+ */
46
+ export type IngestError = {
47
+ error: string;
48
+ };
49
+ export type IngestEvent = {
50
+ /**
51
+ * Client-generated id; repeats are dropped, so retries are safe.
52
+ */
53
+ id?: string;
54
+ /**
55
+ * When it happened, ISO 8601. Clamped to the last 7 days and now.
56
+ */
57
+ timestamp: string;
58
+ level?: IngestLevel;
59
+ /**
60
+ * Free text; mapped to production, staging or development.
61
+ */
62
+ environment?: string;
63
+ release?: string;
64
+ /**
65
+ * The error, with its `cause` chain.
66
+ */
67
+ exception?: IngestException;
68
+ /**
69
+ * For reports without an error object (`captureMessage`).
70
+ */
71
+ message?: string;
72
+ /**
73
+ * Oldest first.
74
+ */
75
+ breadcrumbs?: Array<IngestBreadcrumb>;
76
+ request?: IngestRequest;
77
+ user?: IngestUser;
78
+ context?: IngestContext;
79
+ tags?: {
80
+ [key in string]: string;
81
+ };
82
+ /**
83
+ * Overrides grouping: events with the same fingerprint are one issue.
84
+ */
85
+ fingerprint?: Array<string>;
86
+ /**
87
+ * Bundle file URL → debug id, for source maps.
88
+ */
89
+ debugIds?: {
90
+ [key in string]: string;
91
+ };
92
+ };
93
+ export type IngestException = {
94
+ type: string;
95
+ message: string;
96
+ /**
97
+ * Innermost (where it was thrown) first.
98
+ */
99
+ frames: Array<IngestFrame>;
100
+ cause?: IngestException;
101
+ };
102
+ export type IngestFrame = {
103
+ file: string;
104
+ function?: string;
105
+ line?: number;
106
+ column?: number;
107
+ /**
108
+ * The SDK's guess; the server decides from the file path when absent.
109
+ */
110
+ inApp?: boolean;
111
+ };
112
+ /**
113
+ * `POST /ingest/v1/heartbeat`: a long-running process saying it is alive, so
114
+ * bertha can tell "no errors" (0) from "no data" (a gap) in charts.
115
+ */
116
+ export type IngestHeartbeat = {
117
+ environment?: string;
118
+ release?: string;
119
+ };
120
+ export type IngestLevel = "fatal" | "error" | "warning";
121
+ export type IngestRequest = {
122
+ method: string;
123
+ url: string;
124
+ status?: number;
125
+ };
126
+ export type IngestResponse = {
127
+ accepted: number;
128
+ rejected: number;
129
+ /**
130
+ * Seconds to wait before sending again, when throttled.
131
+ */
132
+ retryAfter?: number;
133
+ };
134
+ export type IngestSdk = {
135
+ name: string;
136
+ version: string;
137
+ };
138
+ export type IngestUser = {
139
+ id?: string;
140
+ email?: string;
141
+ /**
142
+ * `"auto"` = use the address the report came from.
143
+ */
144
+ ip?: string;
145
+ };
146
+ /**
147
+ * Answer to `POST /ingest/v1/sourcemaps?debugId=…&file=…&release=…`
148
+ * (body: the raw map, gzip or plain).
149
+ */
150
+ export type SourceMapUploaded = {
151
+ debugId: string;
152
+ sources: number;
153
+ };
@@ -0,0 +1,2 @@
1
+ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
2
+ export {};
@@ -0,0 +1,2 @@
1
+ export { captureException, captureMessage, setTag, setUser } from "../core/hub.ts";
2
+ export declare function onRouterTransitionStart(url: string, navigationType: "push" | "replace" | "traverse"): void;
@@ -0,0 +1,34 @@
1
+ // @bigbertha/sdk/next/client, for instrumentation-client.ts:
2
+ // export { onRouterTransitionStart } from "@bigbertha/sdk/next/client"
3
+ // Importing it starts reporting. Next inlines the NEXT_PUBLIC_ variables below.
4
+ import { init } from "../browser/index.js";
5
+ import { addBreadcrumb } from "../core/hub.js";
6
+ export { captureException, captureMessage, setTag, setUser } from "../core/hub.js";
7
+ function read(f) {
8
+ try {
9
+ return f() || undefined;
10
+ }
11
+ catch {
12
+ return undefined;
13
+ }
14
+ }
15
+ // Written out in full so Next's compiler can replace each one.
16
+ const key = read(() => process.env.NEXT_PUBLIC_BERTHA_KEY);
17
+ const url = read(() => process.env.NEXT_PUBLIC_BERTHA_URL);
18
+ if (typeof window === "undefined") {
19
+ // Prerendering on the server: instrumentation.ts covers that side.
20
+ }
21
+ else if (key && url) {
22
+ init({
23
+ key,
24
+ url,
25
+ release: read(() => process.env.BERTHA_RELEASE),
26
+ environment: read(() => process.env.NODE_ENV),
27
+ });
28
+ }
29
+ else {
30
+ console.warn("[bertha] Set NEXT_PUBLIC_BERTHA_KEY and NEXT_PUBLIC_BERTHA_URL. Nothing will be sent.");
31
+ }
32
+ export function onRouterTransitionStart(url, navigationType) {
33
+ addBreadcrumb({ category: "navigation", message: `${navigationType} ${url}` });
34
+ }
@@ -0,0 +1,8 @@
1
+ export interface BerthaNextOptions {
2
+ release?: string;
3
+ /** Keep .map files in `.next` after uploading. Default false. */
4
+ keepMaps?: boolean;
5
+ /** Skip the post-build step entirely (the release is still stamped). */
6
+ disableUpload?: boolean;
7
+ }
8
+ export declare function withBertha<C extends object>(config?: C, options?: BerthaNextOptions): C;
@@ -0,0 +1,36 @@
1
+ // @bigbertha/sdk/next/config, for next.config.ts:
2
+ // export default withBertha(nextConfig)
3
+ // Turns on source maps for production builds, stamps the release, and after each
4
+ // build gives bundles debug ids, uploads the maps and removes them from `.next`.
5
+ import { join } from "node:path";
6
+ import { detectRelease, processDirectory } from "../build/index.js";
7
+ import { firstEnv } from "../core/env.js";
8
+ export function withBertha(config = {}, options = {}) {
9
+ const c = config;
10
+ const release = options.release ?? detectRelease();
11
+ const previous = c.compiler?.runAfterProductionCompile;
12
+ const next = {
13
+ ...c,
14
+ productionBrowserSourceMaps: true,
15
+ env: { ...c.env, ...(release ? { BERTHA_RELEASE: release } : {}) },
16
+ experimental: { ...c.experimental, serverSourceMaps: true },
17
+ compiler: {
18
+ ...c.compiler,
19
+ async runAfterProductionCompile(meta) {
20
+ await previous?.(meta);
21
+ if (options.disableUpload)
22
+ return;
23
+ await processDirectory({
24
+ dir: meta.distDir ?? join(meta.projectDir, ".next"),
25
+ release,
26
+ url: firstEnv("BERTHA_URL", "NEXT_PUBLIC_BERTHA_URL"),
27
+ uploadKey: firstEnv("BERTHA_UPLOAD_KEY"),
28
+ keepMaps: options.keepMaps,
29
+ skip: ["cache", "standalone", "types"],
30
+ log: (m) => console.log(`[bertha] ${m}`),
31
+ }).catch((e) => console.warn(`[bertha] Source maps weren't processed: ${String(e)}`));
32
+ },
33
+ },
34
+ };
35
+ return next;
36
+ }
@@ -0,0 +1,16 @@
1
+ export declare function register(): Promise<void>;
2
+ interface NextRequestInfo {
3
+ path: string;
4
+ method: string;
5
+ headers: Record<string, string | string[] | undefined>;
6
+ }
7
+ interface NextErrorContext {
8
+ routerKind: "Pages Router" | "App Router";
9
+ routePath: string;
10
+ routeType: "render" | "route" | "action" | "middleware" | "proxy";
11
+ renderSource?: string;
12
+ revalidateReason?: string;
13
+ }
14
+ /** Next calls this for errors in server components, route handlers, actions and middleware. */
15
+ export declare function onRequestError(error: unknown, request: NextRequestInfo, context: NextErrorContext): Promise<void>;
16
+ export {};
@@ -0,0 +1,22 @@
1
+ // @bigbertha/sdk/next, for instrumentation.ts:
2
+ // export { register, onRequestError } from "@bigbertha/sdk/next"
3
+ import { captureException, flush } from "../core/hub.js";
4
+ export async function register() {
5
+ if (process.env.NEXT_RUNTIME === "nodejs")
6
+ (await import("../node/index.js")).init();
7
+ else if (process.env.NEXT_RUNTIME === "edge")
8
+ (await import("../edge/index.js")).init();
9
+ }
10
+ /** Next calls this for errors in server components, route handlers, actions and middleware. */
11
+ export async function onRequestError(error, request, context) {
12
+ const host = header(request.headers.host);
13
+ const proto = header(request.headers["x-forwarded-proto"]) ?? "https";
14
+ captureException(error, {
15
+ request: { method: request.method, url: host ? `${proto}://${host}${request.path}` : request.path },
16
+ tags: { route: context.routePath, routeType: context.routeType },
17
+ });
18
+ await flush();
19
+ }
20
+ function header(v) {
21
+ return Array.isArray(v) ? v[0] : v;
22
+ }
@@ -0,0 +1,19 @@
1
+ import { Client } from "../core/client.ts";
2
+ import type { Options } from "../core/types.ts";
3
+ export { addBreadcrumb, captureException, captureMessage, flush, getClient, setTag, setUser } from "../core/hub.ts";
4
+ export type { CaptureContext, IngestEvent, Options } from "../core/types.ts";
5
+ export interface NodeOptions extends Partial<Options> {
6
+ /** Report uncaught exceptions and unhandled rejections, then exit as Node would. On by default. */
7
+ handlers?: boolean;
8
+ /** Tell bertha the process is alive every 20 minutes. On by default. */
9
+ heartbeat?: boolean;
10
+ }
11
+ export declare const HEARTBEAT_MS: number;
12
+ /** Reads BERTHA_KEY, BERTHA_URL, BERTHA_RELEASE and NODE_ENV unless given. */
13
+ export declare function init(options?: NodeOptions): Client | undefined;
14
+ export declare function sendHeartbeat(o: Pick<Options, "url" | "key" | "release" | "environment">): Promise<boolean>;
15
+ /**
16
+ * Wraps a request handler (Node http, Express, Fastify, Hono…): errors it throws
17
+ * or rejects with are reported with the request, then rethrown.
18
+ */
19
+ export declare function wrapHandler<A extends unknown[], R>(handler: (...args: A) => R): (...args: A) => R;
@@ -0,0 +1,98 @@
1
+ // @bigbertha/sdk/node: uncaught exceptions and unhandled rejections, a heartbeat so
2
+ // quiet hours read as "no errors" rather than "no data", and a handler wrapper.
3
+ import { arch, platform } from "node:os";
4
+ import { gzipSync } from "node:zlib";
5
+ import { Client } from "../core/client.js";
6
+ import { firstEnv } from "../core/env.js";
7
+ import { getClient, setClient } from "../core/hub.js";
8
+ import { endpoint, fetchTransport } from "../core/transport.js";
9
+ import { VERSION } from "../core/version.js";
10
+ export { addBreadcrumb, captureException, captureMessage, flush, getClient, setTag, setUser } from "../core/hub.js";
11
+ export const HEARTBEAT_MS = 20 * 60 * 1000;
12
+ const EXIT_FLUSH_MS = 2000;
13
+ let handlersInstalled = false;
14
+ let heartbeat;
15
+ /** Reads BERTHA_KEY, BERTHA_URL, BERTHA_RELEASE and NODE_ENV unless given. */
16
+ export function init(options = {}) {
17
+ const key = options.key ?? firstEnv("BERTHA_KEY", "NEXT_PUBLIC_BERTHA_KEY");
18
+ const url = options.url ?? firstEnv("BERTHA_URL", "NEXT_PUBLIC_BERTHA_URL");
19
+ if (!key || !url) {
20
+ console.warn("[bertha] Set BERTHA_KEY and BERTHA_URL (or pass key and url). Nothing will be sent.");
21
+ return undefined;
22
+ }
23
+ const opts = {
24
+ ...options,
25
+ key,
26
+ url,
27
+ release: options.release ?? firstEnv("BERTHA_RELEASE"),
28
+ environment: options.environment ?? firstEnv("BERTHA_ENVIRONMENT", "NODE_ENV"),
29
+ };
30
+ const client = new Client(opts, fetchTransport({ url, key, gzip: async (body) => new Uint8Array(gzipSync(body)) }), { name: "bertha.node", version: VERSION }, { runtime: `Node ${process.versions.node}`, os: `${platform()} ${arch()}` });
31
+ setClient(client);
32
+ if (options.handlers !== false && !handlersInstalled) {
33
+ handlersInstalled = true;
34
+ process.on("uncaughtException", (error) => void crash("uncaughtException", error));
35
+ process.on("unhandledRejection", (reason) => void crash("unhandledRejection", reason));
36
+ process.on("beforeExit", () => void getClient()?.flush());
37
+ }
38
+ if (options.heartbeat !== false) {
39
+ clearInterval(heartbeat);
40
+ const beat = () => void sendHeartbeat(opts);
41
+ beat();
42
+ heartbeat = setInterval(beat, HEARTBEAT_MS);
43
+ heartbeat.unref();
44
+ }
45
+ return client;
46
+ }
47
+ /** Reports, flushes and then, if nothing else is listening, exits as Node would have. */
48
+ async function crash(event, error) {
49
+ const client = getClient();
50
+ client?.captureException(error, { level: "fatal" });
51
+ await Promise.race([client?.flush(), new Promise((r) => setTimeout(r, EXIT_FLUSH_MS).unref())]);
52
+ if (process.listenerCount(event) > 1)
53
+ return;
54
+ console.error(event === "unhandledRejection" && !(error instanceof Error) ? `Unhandled rejection: ${String(error)}` : error);
55
+ process.exit(1);
56
+ }
57
+ export async function sendHeartbeat(o) {
58
+ try {
59
+ const res = await fetch(endpoint(o.url, "heartbeat"), {
60
+ method: "POST",
61
+ headers: { authorization: `Bearer ${o.key}`, "content-type": "application/json" },
62
+ body: JSON.stringify({ environment: o.environment, release: o.release }),
63
+ signal: AbortSignal.timeout(10_000),
64
+ });
65
+ return res.ok;
66
+ }
67
+ catch {
68
+ return false;
69
+ }
70
+ }
71
+ /**
72
+ * Wraps a request handler (Node http, Express, Fastify, Hono…): errors it throws
73
+ * or rejects with are reported with the request, then rethrown.
74
+ */
75
+ export function wrapHandler(handler) {
76
+ return function (...args) {
77
+ const report = (error) => {
78
+ getClient()?.captureException(error, requestContext(args[0]));
79
+ throw error;
80
+ };
81
+ try {
82
+ const r = handler.apply(this, args);
83
+ return (r instanceof Promise ? r.catch(report) : r);
84
+ }
85
+ catch (error) {
86
+ return report(error);
87
+ }
88
+ };
89
+ }
90
+ function requestContext(req) {
91
+ const r = req;
92
+ if (!r || typeof r.method !== "string")
93
+ return {};
94
+ const path = r.originalUrl ?? r.url ?? "/";
95
+ const host = typeof r.headers?.host === "string" ? r.headers.host : undefined;
96
+ const proto = typeof r.headers?.["x-forwarded-proto"] === "string" ? r.headers["x-forwarded-proto"] : "http";
97
+ return { request: { method: r.method, url: host && path.startsWith("/") ? `${proto}://${host}${path}` : path } };
98
+ }
@@ -0,0 +1,44 @@
1
+ import { type Report } from "../build/index.ts";
2
+ export interface BerthaViteOptions {
3
+ release?: string;
4
+ url?: string;
5
+ uploadKey?: string;
6
+ /** Keep .map files in the output after uploading. Default false. */
7
+ keepMaps?: boolean;
8
+ /** Called with what was uploaded, for tests and custom logging. */
9
+ onReport?: (report: Report) => void;
10
+ }
11
+ interface ResolvedConfig {
12
+ root: string;
13
+ mode: string;
14
+ build: {
15
+ outDir: string;
16
+ ssr?: unknown;
17
+ };
18
+ logger: {
19
+ info(msg: string): void;
20
+ warn(msg: string): void;
21
+ };
22
+ }
23
+ export interface VitePlugin {
24
+ name: string;
25
+ apply: "build";
26
+ enforce: "post";
27
+ config(config: {
28
+ build?: {
29
+ sourcemap?: unknown;
30
+ };
31
+ }): {
32
+ build: {
33
+ sourcemap: "hidden";
34
+ };
35
+ } | undefined;
36
+ configResolved(config: ResolvedConfig): Promise<void>;
37
+ closeBundle: {
38
+ order: "post";
39
+ sequential: true;
40
+ handler(): Promise<void>;
41
+ };
42
+ }
43
+ export declare function bertha(options?: BerthaViteOptions): VitePlugin;
44
+ export {};
@@ -0,0 +1,63 @@
1
+ // @bigbertha/sdk/vite: plugins: [bertha()]
2
+ // Builds with hidden source maps, then gives bundles debug ids, uploads the maps
3
+ // and removes them from the output. Reads BERTHA_UPLOAD_KEY and VITE_BERTHA_URL
4
+ // (or BERTHA_URL) from the environment or .env files.
5
+ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
6
+ if (typeof path === "string" && /^\.\.?\//.test(path)) {
7
+ return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
8
+ return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
9
+ });
10
+ }
11
+ return path;
12
+ };
13
+ import { resolve } from "node:path";
14
+ import { detectRelease, processDirectory } from "../build/index.js";
15
+ export function bertha(options = {}) {
16
+ let outDir = "";
17
+ let root = "";
18
+ let env = {};
19
+ let logger;
20
+ return {
21
+ name: "bertha",
22
+ apply: "build",
23
+ enforce: "post",
24
+ config(config) {
25
+ if (!config.build?.sourcemap)
26
+ return { build: { sourcemap: "hidden" } };
27
+ return undefined;
28
+ },
29
+ async configResolved(config) {
30
+ root = config.root;
31
+ outDir = resolve(config.root, config.build.outDir);
32
+ logger = config.logger;
33
+ try {
34
+ const vite = (await import(__rewriteRelativeImportExtension("vite")));
35
+ env = vite.loadEnv(config.mode, config.root, "");
36
+ }
37
+ catch {
38
+ env = {};
39
+ }
40
+ },
41
+ closeBundle: {
42
+ order: "post",
43
+ sequential: true,
44
+ async handler() {
45
+ const pick = (...names) => names.map((n) => process.env[n] || env[n]).find(Boolean);
46
+ try {
47
+ const report = await processDirectory({
48
+ dir: outDir,
49
+ release: options.release ?? pick("BERTHA_RELEASE") ?? detectRelease(root),
50
+ url: options.url ?? pick("BERTHA_URL", "VITE_BERTHA_URL"),
51
+ uploadKey: options.uploadKey ?? pick("BERTHA_UPLOAD_KEY"),
52
+ keepMaps: options.keepMaps,
53
+ log: (m) => logger?.info(`[bertha] ${m}`),
54
+ });
55
+ options.onReport?.(report);
56
+ }
57
+ catch (e) {
58
+ logger?.warn(`[bertha] Source maps weren't processed: ${String(e)}`);
59
+ }
60
+ },
61
+ },
62
+ };
63
+ }
package/package.json CHANGED
@@ -1,6 +1,60 @@
1
1
  {
2
2
  "name": "@bigbertha/sdk",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "Error reporting for bertha: browser, Node, edge and Next.js, with source maps by debug id.",
5
+ "license": "UNLICENSED",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/felixowens/bertha.git",
9
+ "directory": "sdk"
10
+ },
11
+ "homepage": "https://github.com/felixowens/bertha/tree/main/sdk#readme",
12
+ "keywords": [
13
+ "error-tracking",
14
+ "bertha",
15
+ "nextjs",
16
+ "vite",
17
+ "source-maps"
18
+ ],
19
+ "publishConfig": {
20
+ "access": "public"
21
+ },
22
+ "type": "module",
23
+ "sideEffects": [
24
+ "./dist/next/client.js"
25
+ ],
26
+ "files": [
27
+ "dist"
28
+ ],
29
+ "bin": {
30
+ "bertha": "./dist/bin.js"
31
+ },
32
+ "exports": {
33
+ "./browser": "./dist/browser/index.js",
34
+ "./node": "./dist/node/index.js",
35
+ "./edge": "./dist/edge/index.js",
36
+ "./next": "./dist/next/index.js",
37
+ "./next/client": "./dist/next/client.js",
38
+ "./next/config": "./dist/next/config.js",
39
+ "./vite": "./dist/vite/index.js",
40
+ "./build": "./dist/build/index.js",
41
+ "./package.json": "./package.json"
42
+ },
43
+ "engines": {
44
+ "node": ">=20"
45
+ },
46
+ "scripts": {
47
+ "build": "rm -rf dist && tsc -p tsconfig.build.json && chmod +x dist/bin.js",
48
+ "check": "tsc -p tsconfig.json",
49
+ "test": "vitest run",
50
+ "size": "node scripts/size.ts",
51
+ "e2e": "node e2e/run.ts"
52
+ },
53
+ "devDependencies": {
54
+ "@types/node": "^24.13.3",
55
+ "esbuild": "^0.27.0",
56
+ "typescript": "~6.0.2",
57
+ "vite": "^8.3.4",
58
+ "vitest": "^4.0.0"
59
+ }
60
+ }