@allstak/react 0.3.1 → 0.3.2

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/CHANGELOG.md ADDED
@@ -0,0 +1,57 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@allstak/react` are documented in this file.
4
+
5
+ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.3.1] — 2026-05-11
9
+
10
+ ### Fixed
11
+
12
+ - Version constant in `client.ts` now matches `package.json` (was `0.3.0`).
13
+ - Added version consistency test to CI.
14
+
15
+ ## [0.3.0] — 2026-05-08
16
+
17
+ ### Added
18
+
19
+ - Full automatic HTTP instrumentation (fetch and XHR interception).
20
+ - Source map upload pipeline with Vite, Webpack, and Next.js build plugins.
21
+ - Per-frame `debugId` resolution via `globalThis._allstakDebugIds`.
22
+ - `debugMeta.images[]` aggregation in error payloads.
23
+
24
+ ## [0.2.0] — 2026-04-25
25
+
26
+ ### Added
27
+
28
+ - Scope management for tags, extras, and context.
29
+ - Distributed tracing with automatic `traceparent` propagation.
30
+ - Web session replay (surrogate events).
31
+ - React Router integration helpers.
32
+ - Debug-ID based source map resolution.
33
+
34
+ ## [0.1.4] — 2026-04-10
35
+
36
+ ### Changed
37
+
38
+ - Standalone release on public npm as `@allstak/react`.
39
+
40
+ ### Added
41
+
42
+ - `ErrorBoundary` component with `componentDidCatch` capture.
43
+ - Automatic `window.onerror` and `onunhandledrejection` handlers.
44
+ - `beforeSend` callback, `sampleRate`, `setTags`/`setExtra`/`setContext`.
45
+ - `flush()` with bounded timeout.
46
+ - Web Vitals (LCP, FID, CLS) capture.
47
+
48
+ ## [0.1.1] — 2026-03-28
49
+
50
+ Initial professional release.
51
+
52
+ ### Added
53
+
54
+ - Core error capture with stack trace parsing.
55
+ - Breadcrumb ring buffer (max 50).
56
+ - Fail-open transport with exponential backoff.
57
+ - Circuit breaker on 401 responses.
package/README.md CHANGED
@@ -1,12 +1,12 @@
1
1
  # @allstak/react
2
2
 
3
- **Drop-in error tracking for React. One `<AllStakErrorBoundary>`, zero config beyond your API key.**
3
+ **One wrapper. Full error tracking + Web Vitals + breadcrumbs + HTTP. For React 17, 18, and 19.**
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/@allstak/react.svg)](https://www.npmjs.com/package/@allstak/react)
6
6
  [![CI](https://github.com/AllStak/allstak-react/actions/workflows/ci.yml/badge.svg)](https://github.com/AllStak/allstak-react/actions)
7
7
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
8
8
 
9
- Official AllStak SDK for React — error boundary component, hooks, and a profiler HOC on top of the browser SDK.
9
+ Official AllStak SDK for React — provider component, error boundary, hooks, and Web Vitals on top of a self-contained browser client.
10
10
 
11
11
  ## Dashboard
12
12
 
@@ -16,42 +16,139 @@ View captured events live at [app.allstak.sa](https://app.allstak.sa).
16
16
 
17
17
  ## Features
18
18
 
19
- - `<AllStakErrorBoundary>` component for render-tree error capture
20
- - `useAllStak()` hook for in-component capture and context
21
- - `withAllStakProfiler` HOC for mount/update timing
22
- - Inherits automatic fetch, console, and window error capture from the core SDK
23
- - Works with React 17, 18, and 19
19
+ - **One-wrapper setup** `<AllStakProvider>` initializes everything
20
+ - React error boundary with custom fallback UI + `resetError`
21
+ - `window.onerror` + `unhandledrejection` capture
22
+ - `console.warn` + `console.error` capture by default; `log`/`info` opt-in
23
+ - `fetch` and `XMLHttpRequest` breadcrumbs (200 / 4xx / 5xx / network failure)
24
+ - Full HTTP request events with `enableHttpTracking: true` + privacy-first redaction
25
+ - Auto navigation breadcrumbs (history.pushState patch + popstate)
26
+ - Web Vitals via PerformanceObserver (CLS / LCP / INP / FCP / TTFB)
27
+ - React Router + Next.js helpers
28
+ - `useAllStak()` hook with stable identity
24
29
  - Full TypeScript types
25
30
 
26
- ## What You Get
27
-
28
- Once integrated, every event flows to your AllStak dashboard:
31
+ ## Quick Start
29
32
 
30
- - **Errors** render-tree crashes, caught hook errors, stack traces with component names
31
- - **Logs** console warnings and errors as structured breadcrumbs
32
- - **HTTP** — outbound `fetch` timing, status codes, failed calls
33
- - **Performance** — mount and update timing from the profiler HOC
34
- - **Alerts** — email and webhook notifications on regressions
33
+ > **Time to first event: under 2 minutes.**
34
+ > Create a project at [app.allstak.sa](https://app.allstak.sa) to get your API key.
35
35
 
36
- ## Installation
36
+ ### 1. Install
37
37
 
38
38
  ```bash
39
39
  npm install @allstak/react
40
40
  ```
41
41
 
42
- ## Quick Start
42
+ ### 2. Wrap your app
43
43
 
44
- > Create a project at [app.allstak.sa](https://app.allstak.sa) to get your API key.
44
+ ```tsx
45
+ import { AllStakProvider } from '@allstak/react';
46
+
47
+ export function App() {
48
+ return (
49
+ <AllStakProvider
50
+ apiKey="ask_live_YOUR_KEY_HERE"
51
+ environment="production"
52
+ release="web@1.0.0"
53
+ debug
54
+ >
55
+ <AppRoot />
56
+ </AllStakProvider>
57
+ );
58
+ }
59
+ ```
60
+
61
+ ### 3. Trigger a test error
45
62
 
46
63
  ```tsx
64
+ import { AllStak } from '@allstak/react';
65
+
66
+ // On a button press or on mount:
67
+ AllStak.captureException(new Error('test: hello from allstak-react'));
68
+ ```
69
+
70
+ ### 4. Check your dashboard
71
+
72
+ Open [app.allstak.sa](https://app.allstak.sa) — the error appears within seconds.
73
+
74
+ When `debug` is enabled you'll see `[AllStak] Initialized — session <id>` and `[AllStak] Navigation auto-instrumentation enabled` in your browser console.
75
+
76
+ `AllStakProvider` handles everything:
77
+ - Initializes the SDK
78
+ - Wraps your app in an `AllStakErrorBoundary`
79
+ - Hooks `window.onerror`, `unhandledrejection`, `console.*`, `fetch`, `history.*`
80
+ - Starts Web Vitals collection
81
+ - Tags every event with `platform: 'browser'`, sdk name + version
82
+
83
+ ## Automatic Capture Matrix
84
+
85
+ Status legend:
86
+ - ✅ **Auto by default** — works the moment the provider mounts
87
+ - ⚙️ **Auto when enabled by config** — set the listed prop
88
+ - 🛠 **Manual only** — call the listed function yourself
89
+ - 🟡 **Implemented but not browser-verified end-to-end yet**
90
+ - ❌ **Not supported yet** (roadmap)
91
+
92
+ | Capability | Status | Config / API | Wire form |
93
+ |---|---|---|---|
94
+ | Render errors via `<AllStakProvider>` boundary | ✅ | always-on inside the provider | `POST /ingest/v1/errors` with `metadata.source = 'AllStakProvider.ErrorBoundary'` + `componentStack` |
95
+ | `window.onerror` global handler | ✅ | `autoCaptureBrowserErrors` (default `true`) | `POST /ingest/v1/errors` with `metadata.source = 'window.onerror'` |
96
+ | `window.unhandledrejection` | ✅ | `autoCaptureBrowserErrors` | `POST /ingest/v1/errors` with `metadata.source = 'window.unhandledrejection'` |
97
+ | Manual `AllStak.captureException` | 🛠 | `AllStak.captureException(err, ctx?)` | `POST /ingest/v1/errors` |
98
+ | Manual `AllStak.captureMessage` | 🛠 | `AllStak.captureMessage(msg, level)` | `/ingest/v1/logs` (`info`/`warn`); both `errors` + `logs` for `error`/`fatal` |
99
+ | `console.warn` / `console.error` | ✅ | `autoBreadcrumbsConsole` (default `true`); per-method via `captureConsole={ warn: true, error: true }` | `breadcrumb` of `type: 'log'`, `level: 'warn'` / `'error'`, `data.category: 'console'` |
100
+ | `console.log` / `console.info` | ⚙️ | `captureConsole={{ log: true, info: true }}` (default `false` to avoid spam) | `breadcrumb` of `type: 'log'`, `level: 'info'`, `data.method: 'log'` / `'info'` |
101
+ | HTTP `fetch` breadcrumbs (success) | ✅ | `autoBreadcrumbsFetch` (default `true`) | `breadcrumb` of `type: 'http'`, `level: 'info'` |
102
+ | HTTP `fetch` 4xx / 5xx breadcrumbs | ✅ | `autoBreadcrumbsFetch` | `breadcrumb` of `type: 'http'`, `level: 'error'` when status ≥ 400 |
103
+ | HTTP `fetch` network failure | ✅ | `autoBreadcrumbsFetch` | `breadcrumb` with `data.error`; original error rethrown |
104
+ | HTTP `XMLHttpRequest` breadcrumbs | ✅ | `enableHttpTracking` (also covers fetch full events) | breadcrumb same shape |
105
+ | Full HTTP request events (method / URL / headers / body) | ⚙️ | `enableHttpTracking: true` (default `false`); `httpTracking: { ... }` for redaction control | `POST /ingest/v1/http-requests` |
106
+ | Recent failed HTTP attached to next exception | ⚙️ | `enableHttpTracking: true` | `metadata['http.recentFailed']` (last 5 failed requests) |
107
+ | Auto navigation breadcrumbs (pushState / popstate) | ✅ | `autoBreadcrumbsNavigation` (default `true`) | `breadcrumb` of `type: 'navigation'`, `data.from`/`data.to` |
108
+ | React Router integration | 🛠 | `instrumentReactRouter(useLocation())` in a `useEffect` | same shape |
109
+ | Next.js Pages Router | 🛠 | `instrumentNextRouter(url)` from `Router.events.on('routeChangeComplete')` | same shape |
110
+ | **Web Vitals (CLS / LCP / INP / FCP / TTFB)** | ✅ | `autoWebVitals` (default `true`) | `POST /ingest/v1/logs` with `metadata.category = 'web-vital'`, `metadata.name`, `metadata.value` |
111
+ | User context | 🛠 | `AllStak.setUser({ id, email })` | `payload.user` |
112
+ | Custom tags / extras / contexts | 🛠 | `AllStak.setTag` / `setExtra` / `setContext` | `payload.metadata.<key>` |
113
+ | `release`, `environment`, `dist` | 🛠 via init | `<AllStakProvider release="web@1.0.0">` etc. | top-level fields on every payload |
114
+ | Source maps | 🛠 build-time | `@allstak/js/sourcemaps` plugin (Vite / Webpack / Next) | injected `debugId` per chunk |
115
+ | Session replay (DOM-mutation) | ⚙️ | `replay={ sampleRate: 0.1, maskAllInputs: true }` (default OFF) | `POST /ingest/v1/replay` |
116
+ | Offline event queue (in-memory only — see roadmap) | ✅ | n/a; configured via transport (max 100 in-RAM, drops oldest) | retried on next successful send; lost on page navigation |
117
+
118
+ ### Roadmap
119
+
120
+ - Persistent offline queue (currently RAM-only; lost on page navigation)
121
+ - React Query / SWR breadcrumbs
122
+ - Vue / Svelte / Solid sister packages
123
+
124
+ ## Verification status
125
+
126
+ The capabilities marked ✅ in the matrix above were verified live in
127
+ Chrome against the SDK's own wire format (see
128
+ [docs/reports/react-sdk-production-readiness.md](docs/reports/react-sdk-production-readiness.md)
129
+ for command-by-command details). 156 error events, 84 logs (including
130
+ 5 web vitals), 9 full HTTP events, and a working render-error → fallback
131
+ → resetError → recovery cycle were all observed end-to-end.
132
+
133
+ The live-backend round-trip is documented in the report. The wire format
134
+ is identical to `@allstak/react-native`, which was fully round-trip
135
+ verified against the same backend in
136
+ `../../allstak-react-native/docs/reports/react-native-backend-ingestion-verification.md`.
137
+
138
+ ## Manual setup (advanced)
139
+
140
+ If you need full control over initialization order, you can skip
141
+ `AllStakProvider` and set up manually:
142
+
143
+ ```ts
47
144
  import { AllStak, AllStakErrorBoundary } from '@allstak/react';
48
145
 
49
146
  AllStak.init({
50
- apiKey: import.meta.env.VITE_ALLSTAK_API_KEY,
147
+ apiKey: 'ask_live_…',
51
148
  environment: 'production',
52
149
  });
53
150
 
54
- AllStak.captureException(new Error('test: hello from allstak-react'));
151
+ AllStak.captureException(new Error('test'));
55
152
 
56
153
  export function App() {
57
154
  return (
@@ -62,8 +159,6 @@ export function App() {
62
159
  }
63
160
  ```
64
161
 
65
- Load the app — the test error appears in your dashboard within seconds.
66
-
67
162
  ## Get Your API Key
68
163
 
69
164
  1. Sign up at [app.allstak.sa](https://app.allstak.sa)
@@ -104,7 +199,7 @@ Wrap a component with the profiler:
104
199
 
105
200
  ```tsx
106
201
  import { withAllStakProfiler } from '@allstak/react';
107
- export default withAllStakProfiler(Dashboard, { name: 'Dashboard' });
202
+ export default withAllStakProfiler(Dashboard, 'Dashboard');
108
203
  ```
109
204
 
110
205
  Set user context on login:
@@ -165,55 +260,126 @@ Production endpoint: `https://api.allstak.sa`. Override via `host` for self-host
165
260
  AllStak.init({ apiKey: '...', host: 'https://allstak.mycorp.com' });
166
261
  ```
167
262
 
168
- ## Source maps
263
+ ## Source maps — automatic via build plugin
169
264
 
170
- Production stack traces are minified to see real function names and
171
- line numbers in the AllStak dashboard you need to upload the source maps
172
- that your bundler emits. The CLI lives in `@allstak/js/sourcemaps`; you
173
- do **not** need to install `@allstak/js` as a runtime dependency, only
174
- as a `devDependency` for the build step.
265
+ Production stack traces are minified. To see real function names and
266
+ line numbers in the AllStak dashboard you need to upload your source
267
+ maps. The build-time tooling ships **inside `@allstak/react` itself** —
268
+ no extra package to install and runs **automatically** as part of
269
+ your normal build via the Vite / Webpack / Next.js plugin.
175
270
 
176
- ### Vite
271
+ The plugins inject a stable `debugId` into every chunk and (when a
272
+ token is present) upload the matching `.map` to the AllStak ingest. The
273
+ runtime resolver in `@allstak/react` reads `globalThis._allstakDebugIds`
274
+ at capture time and attaches the right debug-id to each frame, so the
275
+ symbolicator picks the correct map even after long-tail caching of
276
+ bundles.
177
277
 
178
- ```bash
179
- npm install -D @allstak/js
180
- ```
278
+ ### Vite
181
279
 
182
280
  ```ts
183
281
  // vite.config.ts
184
282
  import { defineConfig } from 'vite';
185
- import { allstakSourcemaps } from '@allstak/js/vite';
283
+ import react from '@vitejs/plugin-react';
284
+ import { allstakSourcemaps } from '@allstak/react/vite';
186
285
 
187
286
  export default defineConfig({
188
- build: { sourcemap: 'hidden' },
287
+ build: { sourcemap: true },
189
288
  plugins: [
289
+ react(),
190
290
  allstakSourcemaps({
191
- release: process.env.RELEASE!,
291
+ release: process.env.ALLSTAK_RELEASE!,
192
292
  token: process.env.ALLSTAK_UPLOAD_TOKEN!,
293
+ dist: 'web',
193
294
  }),
194
295
  ],
195
296
  });
196
297
  ```
197
298
 
198
- ### Webpack / Next / generic
299
+ ### Webpack
300
+
301
+ ```js
302
+ // webpack.config.js
303
+ const { AllStakWebpackPlugin } = require('@allstak/react/webpack');
304
+
305
+ module.exports = {
306
+ devtool: 'source-map',
307
+ plugins: [
308
+ new AllStakWebpackPlugin({
309
+ release: process.env.ALLSTAK_RELEASE,
310
+ token: process.env.ALLSTAK_UPLOAD_TOKEN,
311
+ dist: 'web',
312
+ }),
313
+ ],
314
+ };
315
+ ```
316
+
317
+ ### Next.js
318
+
319
+ ```js
320
+ // next.config.js
321
+ const { withAllStak } = require('@allstak/react/next');
322
+
323
+ module.exports = withAllStak(
324
+ {
325
+ release: process.env.ALLSTAK_RELEASE,
326
+ token: process.env.ALLSTAK_UPLOAD_TOKEN,
327
+ dist: 'web',
328
+ },
329
+ {
330
+ reactStrictMode: true,
331
+ // …rest of your Next config…
332
+ }
333
+ );
334
+ ```
335
+
336
+ The `withAllStak` wrapper sets `productionBrowserSourceMaps: true` for
337
+ you and only attaches the plugin to the client bundle (Node-side stack
338
+ traces are already symbolicated).
199
339
 
200
- The plugin exists for `@allstak/js/webpack` and `@allstak/js/next`; for
201
- any other bundler call the underlying API after your build:
340
+ ### Other bundlers / programmatic use
202
341
 
203
342
  ```ts
204
- import { processBuildOutput } from '@allstak/js/sourcemaps';
343
+ import { processBuildOutput } from '@allstak/react/sourcemaps';
205
344
 
206
345
  await processBuildOutput({
207
346
  dir: 'dist',
208
- release: process.env.RELEASE!,
347
+ release: process.env.ALLSTAK_RELEASE!,
209
348
  token: process.env.ALLSTAK_UPLOAD_TOKEN!,
349
+ dist: 'web',
350
+ });
351
+ ```
352
+
353
+ ### What gets sent
354
+
355
+ Each map is POSTed as `multipart/form-data` to
356
+ `POST /api/v1/artifacts/upload` with these fields:
357
+
358
+ - `debugId` — UUID injected into the bundle and the map (matching across both)
359
+ - `type` — `"sourcemap"` (and `"bundle"` if `uploadBundles: true`)
360
+ - `release` — your release identifier (e.g. `web@1.4.2`)
361
+ - `dist` — your distribution tag (e.g. `web`)
362
+ - `file` — the map content as a Blob
363
+
364
+ Auth uses `X-AllStak-Upload-Token` (a project-scoped upload token, NOT
365
+ the runtime API key). Generate one in **Project Settings → Upload Tokens**.
366
+
367
+ ### Privacy
368
+
369
+ By default the map is uploaded as-is, which includes embedded
370
+ `sourcesContent`. To strip it (smaller payloads, no source code stored
371
+ on AllStak's side), set `stripSources: true`:
372
+
373
+ ```ts
374
+ allstakSourcemaps({
375
+ release: '…',
376
+ token: '…',
377
+ stripSources: true,
210
378
  });
211
379
  ```
212
380
 
213
- This injects a stable `debugId` into every chunk and uploads the matching
214
- `.map`. The runtime resolver in `@allstak/react` reads `globalThis._allstakDebugIds`
215
- to attach the right debug-id to each captured frame, so the symbolicator
216
- picks the correct map even after long-tail caching of bundles.
381
+ The backend rejects uploads in privacy-mode projects when the map still
382
+ contains `sourcesContent` the SDK side surfaces a clear error.
217
383
 
218
384
  ## Links
219
385
 
@@ -0,0 +1,214 @@
1
+ // src/build/walk.ts
2
+ import { readdirSync, statSync } from "fs";
3
+ import { basename, join } from "path";
4
+ function walk(dir, out = []) {
5
+ for (const name of readdirSync(dir)) {
6
+ const full = join(dir, name);
7
+ const st = statSync(full);
8
+ if (st.isDirectory()) walk(full, out);
9
+ else out.push(full);
10
+ }
11
+ return out;
12
+ }
13
+ function findPairs(root) {
14
+ const all = walk(root);
15
+ const maps = new Set(all.filter((p) => p.endsWith(".map")));
16
+ const pairs = [];
17
+ for (const js of all) {
18
+ if (!js.endsWith(".js") && !js.endsWith(".mjs") && !js.endsWith(".cjs")) continue;
19
+ const map = js + ".map";
20
+ if (maps.has(map)) {
21
+ pairs.push({ jsPath: js, mapPath: map, bundleName: basename(js) });
22
+ }
23
+ }
24
+ return pairs;
25
+ }
26
+
27
+ // src/build/inject.ts
28
+ import { readFileSync, writeFileSync } from "fs";
29
+ import { randomUUID } from "crypto";
30
+ var DEBUG_ID_LINE_RE = /^\/\/# debugId=([0-9a-f-]{36})\s*$/m;
31
+ var REGISTRATION_MARKER = "/*!__allstak_debug_id_registration__*/";
32
+ function buildRegistrationSnippet(jsBody, debugId) {
33
+ const isEsm = /\bimport\.meta\b/.test(jsBody) || /^\s*(?:import|export)\b/m.test(jsBody);
34
+ if (isEsm) {
35
+ return `${REGISTRATION_MARKER}try{(globalThis._allstakDebugIds=globalThis._allstakDebugIds||{})[import.meta.url]="${debugId}"}catch(_){}`;
36
+ }
37
+ return `${REGISTRATION_MARKER}(function(){try{var u=(typeof document!=="undefined"&&document.currentScript&&document.currentScript.src)||(typeof location!=="undefined"?location.href:"");(globalThis._allstakDebugIds=globalThis._allstakDebugIds||{})[u]="${debugId}"}catch(_){}})();`;
38
+ }
39
+ function stripRegistration(js) {
40
+ const lineRe = new RegExp(
41
+ "^" + REGISTRATION_MARKER.replace(/[/*!]/g, (c) => "\\" + c) + ".*$",
42
+ "m"
43
+ );
44
+ return js.replace(lineRe, "");
45
+ }
46
+ function injectPair(p) {
47
+ const jsRaw = readFileSync(p.jsPath, "utf8");
48
+ const mapRaw = readFileSync(p.mapPath, "utf8");
49
+ const map = JSON.parse(mapRaw);
50
+ let debugId = typeof map.debugId === "string" ? map.debugId : "";
51
+ const existing = DEBUG_ID_LINE_RE.exec(jsRaw);
52
+ if (existing && existing[1]) debugId = debugId || existing[1];
53
+ const reused = !!debugId;
54
+ if (!debugId) debugId = randomUUID();
55
+ map.debugId = debugId;
56
+ writeFileSync(p.mapPath, JSON.stringify(map));
57
+ let jsOut = stripRegistration(jsRaw.replace(DEBUG_ID_LINE_RE, ""));
58
+ jsOut = jsOut.replace(/\s+$/, "");
59
+ jsOut += `
60
+ ${buildRegistrationSnippet(jsOut, debugId)}
61
+ //# debugId=${debugId}
62
+ `;
63
+ writeFileSync(p.jsPath, jsOut);
64
+ return { debugId, reused };
65
+ }
66
+ function injectAll(pairs) {
67
+ return pairs.map((pair) => ({ pair, result: injectPair(pair) }));
68
+ }
69
+ function readDebugIdFromMap(mapPath) {
70
+ const json = JSON.parse(readFileSync(mapPath, "utf8"));
71
+ return typeof json.debugId === "string" ? json.debugId : null;
72
+ }
73
+
74
+ // src/build/upload.ts
75
+ import { readFileSync as readFileSync2 } from "fs";
76
+ import { basename as basename2 } from "path";
77
+ var DEFAULT_HOST = "https://api.allstak.sa";
78
+ async function uploadOne(type, filePath, debugId, opts) {
79
+ let buf = readFileSync2(filePath);
80
+ if (type === "sourcemap" && opts.stripSources) {
81
+ const json = JSON.parse(buf.toString("utf8"));
82
+ if (Array.isArray(json.sourcesContent)) delete json.sourcesContent;
83
+ buf = Buffer.from(JSON.stringify(json));
84
+ }
85
+ const form = new FormData();
86
+ form.append("debugId", debugId);
87
+ form.append("type", type);
88
+ form.append("release", opts.release);
89
+ if (opts.dist) form.append("dist", opts.dist);
90
+ form.append(
91
+ "file",
92
+ new Blob([buf], {
93
+ type: type === "sourcemap" ? "application/json" : "application/javascript"
94
+ }),
95
+ basename2(filePath)
96
+ );
97
+ const res = await fetch(opts.host.replace(/\/$/, "") + "/api/v1/artifacts/upload", {
98
+ method: "POST",
99
+ headers: { "X-AllStak-Upload-Token": opts.token },
100
+ body: form
101
+ });
102
+ return { status: res.status, body: await res.text(), ok: res.ok };
103
+ }
104
+ function sha8(buf) {
105
+ let hash = 0;
106
+ for (let i = 0; i < buf.length; i++) hash = (hash << 5) - hash + buf[i] | 0;
107
+ return (hash >>> 0).toString(16).padStart(8, "0");
108
+ }
109
+ async function uploadPair(p, opts) {
110
+ const debugId = readDebugIdFromMap(p.mapPath);
111
+ if (!debugId) {
112
+ return {
113
+ bundleName: p.bundleName,
114
+ debugId: "",
115
+ ok: false,
116
+ steps: [{
117
+ type: "sourcemap",
118
+ status: 0,
119
+ sha8: "",
120
+ body: `[allstak/upload] no debugId in ${p.mapPath} \u2014 run inject first`
121
+ }]
122
+ };
123
+ }
124
+ const merged = {
125
+ release: opts.release,
126
+ host: opts.host ?? process.env.ALLSTAK_HOST ?? DEFAULT_HOST,
127
+ token: opts.token,
128
+ dist: opts.dist,
129
+ stripSources: opts.stripSources
130
+ };
131
+ const steps = [];
132
+ const mapResult = await uploadOne("sourcemap", p.mapPath, debugId, merged);
133
+ steps.push({
134
+ type: "sourcemap",
135
+ status: mapResult.status,
136
+ sha8: sha8(readFileSync2(p.mapPath)),
137
+ body: mapResult.ok ? void 0 : mapResult.body
138
+ });
139
+ let allOk = mapResult.ok;
140
+ if (opts.uploadBundles) {
141
+ const bundleResult = await uploadOne("bundle", p.jsPath, debugId, merged);
142
+ steps.push({
143
+ type: "bundle",
144
+ status: bundleResult.status,
145
+ sha8: sha8(readFileSync2(p.jsPath)),
146
+ body: bundleResult.ok ? void 0 : bundleResult.body
147
+ });
148
+ allOk = allOk && bundleResult.ok;
149
+ }
150
+ return { bundleName: p.bundleName, debugId, ok: allOk, steps };
151
+ }
152
+ async function uploadAll(pairs, opts) {
153
+ return Promise.all(pairs.map((p) => uploadPair(p, opts)));
154
+ }
155
+
156
+ // src/build/sourcemaps.ts
157
+ import { resolve } from "path";
158
+ async function processBuildOutput(opts) {
159
+ const dir = resolve(opts.dir);
160
+ const pairs = findPairs(dir);
161
+ const log = opts.silent ? () => void 0 : (m) => console.log(`[allstak/sourcemaps] ${m}`);
162
+ log(`scanning ${dir} \u2014 ${pairs.length} bundle/map pair(s)`);
163
+ if (pairs.length === 0) {
164
+ return { dir, pairs: 0, injected: [] };
165
+ }
166
+ const injectedRaw = injectAll(pairs);
167
+ const injected = injectedRaw.map(({ pair, result }) => ({
168
+ bundleName: pair.bundleName,
169
+ debugId: result.debugId,
170
+ reused: result.reused
171
+ }));
172
+ for (const i of injected) {
173
+ log(` ${i.bundleName} ${i.debugId} ${i.reused ? "(reused)" : "(new)"}`);
174
+ }
175
+ const token = opts.token ?? process.env.ALLSTAK_UPLOAD_TOKEN;
176
+ const release = opts.release ?? process.env.ALLSTAK_RELEASE;
177
+ if (opts.injectOnly || !token) {
178
+ if (!opts.injectOnly && !token) {
179
+ log("skipping upload \u2014 no token (set ALLSTAK_UPLOAD_TOKEN or pass `token`)");
180
+ }
181
+ return { dir, pairs: pairs.length, injected };
182
+ }
183
+ if (!release) {
184
+ log("skipping upload \u2014 no release (set ALLSTAK_RELEASE or pass `release`)");
185
+ return { dir, pairs: pairs.length, injected };
186
+ }
187
+ const uploaded = await uploadAll(pairs, {
188
+ ...opts,
189
+ release,
190
+ token
191
+ });
192
+ for (const u of uploaded) {
193
+ if (u.ok) {
194
+ log(` ${u.bundleName} uploaded debugId=${u.debugId}`);
195
+ } else {
196
+ const last = u.steps[u.steps.length - 1];
197
+ log(` ${u.bundleName} FAIL status=${last?.status ?? "?"} body=${last?.body ?? ""}`);
198
+ }
199
+ }
200
+ return { dir, pairs: pairs.length, injected, uploaded };
201
+ }
202
+
203
+ export {
204
+ walk,
205
+ findPairs,
206
+ injectPair,
207
+ injectAll,
208
+ readDebugIdFromMap,
209
+ DEFAULT_HOST,
210
+ uploadPair,
211
+ uploadAll,
212
+ processBuildOutput
213
+ };
214
+ //# sourceMappingURL=chunk-G6VPGDP6.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/build/walk.ts","../../src/build/inject.ts","../../src/build/upload.ts","../../src/build/sourcemaps.ts"],"sourcesContent":["/**\n * Build-time only. File-system walking for the source-map pipeline.\n * Pure Node 18+ (built-in `node:fs` only). Browser runtime never imports\n * this — it's behind a `./build/*` subpath that's marked Node-platform.\n */\n\nimport { readdirSync, statSync } from 'node:fs';\nimport { basename, join } from 'node:path';\n\n/** A bundle and its companion source map on disk. */\nexport interface BundlePair {\n /** Absolute path to the JS bundle (`.js` / `.mjs` / `.cjs`). */\n jsPath: string;\n /** Absolute path to the matching `.map` file. */\n mapPath: string;\n /** Bare filename of the bundle (no directory), for log lines. */\n bundleName: string;\n}\n\n/** Recursively list every file under `dir`. Symlinks are followed. */\nexport function walk(dir: string, out: string[] = []): string[] {\n for (const name of readdirSync(dir)) {\n const full = join(dir, name);\n const st = statSync(full);\n if (st.isDirectory()) walk(full, out);\n else out.push(full);\n }\n return out;\n}\n\n/**\n * Returns every `(bundle, sourcemap)` pair under `root`.\n *\n * A pair is a `.js` / `.mjs` / `.cjs` file with a sibling file of the\n * same name plus a `.map` suffix — the convention every modern bundler\n * (Vite, Webpack, esbuild, Rollup, tsup) follows.\n */\nexport function findPairs(root: string): BundlePair[] {\n const all = walk(root);\n const maps = new Set(all.filter((p) => p.endsWith('.map')));\n const pairs: BundlePair[] = [];\n for (const js of all) {\n if (!js.endsWith('.js') && !js.endsWith('.mjs') && !js.endsWith('.cjs')) continue;\n const map = js + '.map';\n if (maps.has(map)) {\n pairs.push({ jsPath: js, mapPath: map, bundleName: basename(js) });\n }\n }\n return pairs;\n}\n","/**\n * Debug-ID injection. Build-time only.\n *\n * For each `(bundle.js, bundle.js.map)` pair we:\n * - Generate a stable per-bundle UUID (one already on the bundle is\n * reused so re-running is idempotent).\n * - Append `//# debugId=<uuid>` to the JS so the runtime resolver in\n * `src/debug-id.ts` can read it back.\n * - Write a top-level `debugId` field into the `.map` JSON so the\n * symbolicator on the backend can join `bundle.js` ↔ `bundle.js.map`\n * by ID rather than guessing from filenames.\n *\n * Bundlers re-write hashed filenames on every build, so joining by ID\n * (instead of by URL or path) is what makes resolved stack frames\n * survive across releases.\n */\n\nimport { readFileSync, writeFileSync } from 'node:fs';\nimport { randomUUID } from 'node:crypto';\n\nimport type { BundlePair } from './walk';\n\nconst DEBUG_ID_LINE_RE = /^\\/\\/# debugId=([0-9a-f-]{36})\\s*$/m;\nconst REGISTRATION_MARKER = '/*!__allstak_debug_id_registration__*/';\n\nfunction buildRegistrationSnippet(jsBody: string, debugId: string): string {\n const isEsm = /\\bimport\\.meta\\b/.test(jsBody) || /^\\s*(?:import|export)\\b/m.test(jsBody);\n if (isEsm) {\n return `${REGISTRATION_MARKER}try{(globalThis._allstakDebugIds=globalThis._allstakDebugIds||{})[import.meta.url]=\"${debugId}\"}catch(_){}`;\n }\n return `${REGISTRATION_MARKER}(function(){try{var u=(typeof document!==\"undefined\"&&document.currentScript&&document.currentScript.src)||(typeof location!==\"undefined\"?location.href:\"\");(globalThis._allstakDebugIds=globalThis._allstakDebugIds||{})[u]=\"${debugId}\"}catch(_){}})();`;\n}\n\nfunction stripRegistration(js: string): string {\n const lineRe = new RegExp(\n '^' + REGISTRATION_MARKER.replace(/[/*!]/g, (c) => '\\\\' + c) + '.*$',\n 'm',\n );\n return js.replace(lineRe, '');\n}\n\n/** Outcome of injecting one pair. */\nexport interface InjectResult {\n /** UUID injected (or reused) for this bundle. */\n debugId: string;\n /** True if the bundle already had a debugId — we reused it. */\n reused: boolean;\n}\n\n/**\n * Inject (or reuse) the debug ID for a single bundle/sourcemap pair.\n * Mutates both files on disk. Pure synchronous Node — safe to call from\n * a Vite `closeBundle` or Webpack `afterEmit` hook.\n */\nexport function injectPair(p: BundlePair): InjectResult {\n const jsRaw = readFileSync(p.jsPath, 'utf8');\n const mapRaw = readFileSync(p.mapPath, 'utf8');\n const map = JSON.parse(mapRaw) as { debugId?: unknown; [k: string]: unknown };\n\n let debugId = typeof map.debugId === 'string' ? map.debugId : '';\n const existing = DEBUG_ID_LINE_RE.exec(jsRaw);\n if (existing && existing[1]) debugId = debugId || existing[1];\n const reused = !!debugId;\n if (!debugId) debugId = randomUUID();\n\n map.debugId = debugId;\n writeFileSync(p.mapPath, JSON.stringify(map));\n\n let jsOut = stripRegistration(jsRaw.replace(DEBUG_ID_LINE_RE, ''));\n jsOut = jsOut.replace(/\\s+$/, '');\n jsOut += `\\n${buildRegistrationSnippet(jsOut, debugId)}\\n//# debugId=${debugId}\\n`;\n writeFileSync(p.jsPath, jsOut);\n\n return { debugId, reused };\n}\n\n/** Inject every pair under `root`. Returns one record per pair. */\nexport function injectAll(pairs: BundlePair[]): Array<{ pair: BundlePair; result: InjectResult }> {\n return pairs.map((pair) => ({ pair, result: injectPair(pair) }));\n}\n\n/** Read a debug ID back from a `.map` file. */\nexport function readDebugIdFromMap(mapPath: string): string | null {\n const json = JSON.parse(readFileSync(mapPath, 'utf8')) as { debugId?: unknown };\n return typeof json.debugId === 'string' ? json.debugId : null;\n}\n","/**\n * Source-map / bundle upload client. Build-time only.\n *\n * Wraps the AllStak `/api/v1/artifacts/upload` endpoint with multipart\n * form data and best-effort retries. Pure Node 18+ (uses the global\n * `fetch` and `FormData`), no third-party HTTP client required.\n *\n * Auth: `X-AllStak-Upload-Token` header (NOT the runtime API key —\n * uploads have a dedicated, narrower scope. Generate one in the\n * dashboard → Project Settings → Upload Tokens).\n */\n\nimport { readFileSync } from 'node:fs';\nimport { basename } from 'node:path';\n\nimport type { BundlePair } from './walk';\nimport { readDebugIdFromMap } from './inject';\n\n/** Default ingest host — overridden via `host` option or `ALLSTAK_HOST`. */\nexport const DEFAULT_HOST = 'https://api.allstak.sa';\n\n/** Options for {@link uploadAll} / {@link uploadPair}. */\nexport interface UploadOptions {\n /** Release identifier, e.g. `myapp@1.4.2`. Required server-side. */\n release: string;\n /** Optional distribution tag (`web`, `ios-hermes`, `staging`, …). */\n dist?: string;\n /** AllStak ingest host (default `https://api.allstak.sa`). */\n host?: string;\n /** Project upload token (`aspk_…`). May come from `ALLSTAK_UPLOAD_TOKEN`. */\n token: string;\n /**\n * Drop `sourcesContent` from the map before upload (smaller payload).\n * Off by default — sourcesContent enables full-source rendering on the\n * dashboard. Turn on if you don't want source code uploaded.\n */\n stripSources?: boolean;\n /** Also upload the JS bundle alongside the map (off by default). */\n uploadBundles?: boolean;\n}\n\n/** One artifact upload result. */\nexport interface UploadResult {\n bundleName: string;\n debugId: string;\n /** True when both the map (and bundle, if requested) uploaded OK. */\n ok: boolean;\n /** Per-artifact responses, in the order we sent them. */\n steps: Array<{\n type: 'sourcemap' | 'bundle';\n status: number;\n sha8: string;\n body?: string;\n }>;\n}\n\ninterface OneStepResult {\n status: number;\n body: string;\n ok: boolean;\n}\n\nasync function uploadOne(\n type: 'sourcemap' | 'bundle',\n filePath: string,\n debugId: string,\n opts: Required<Pick<UploadOptions, 'release' | 'host' | 'token'>> &\n Pick<UploadOptions, 'dist' | 'stripSources'>,\n): Promise<OneStepResult> {\n let buf = readFileSync(filePath);\n if (type === 'sourcemap' && opts.stripSources) {\n const json = JSON.parse(buf.toString('utf8')) as { sourcesContent?: unknown };\n if (Array.isArray(json.sourcesContent)) delete json.sourcesContent;\n buf = Buffer.from(JSON.stringify(json));\n }\n\n const form = new FormData();\n form.append('debugId', debugId);\n form.append('type', type);\n form.append('release', opts.release);\n if (opts.dist) form.append('dist', opts.dist);\n form.append(\n 'file',\n new Blob([buf], {\n type: type === 'sourcemap' ? 'application/json' : 'application/javascript',\n }),\n basename(filePath),\n );\n\n const res = await fetch(opts.host.replace(/\\/$/, '') + '/api/v1/artifacts/upload', {\n method: 'POST',\n headers: { 'X-AllStak-Upload-Token': opts.token },\n body: form,\n });\n return { status: res.status, body: await res.text(), ok: res.ok };\n}\n\nfunction sha8(buf: Buffer): string {\n // Browser/Node fallback — keeps this file pure-JS (no node:crypto in\n // the public surface). Caller doesn't need cryptographic strength;\n // this is for log identification only.\n let hash = 0;\n for (let i = 0; i < buf.length; i++) hash = ((hash << 5) - hash + buf[i]!) | 0;\n return (hash >>> 0).toString(16).padStart(8, '0');\n}\n\n/** Upload one bundle/map pair. The map is always uploaded; bundle is opt-in. */\nexport async function uploadPair(p: BundlePair, opts: UploadOptions): Promise<UploadResult> {\n const debugId = readDebugIdFromMap(p.mapPath);\n if (!debugId) {\n return {\n bundleName: p.bundleName,\n debugId: '',\n ok: false,\n steps: [{\n type: 'sourcemap',\n status: 0,\n sha8: '',\n body: `[allstak/upload] no debugId in ${p.mapPath} — run inject first`,\n }],\n };\n }\n const merged = {\n release: opts.release,\n host: opts.host ?? process.env.ALLSTAK_HOST ?? DEFAULT_HOST,\n token: opts.token,\n dist: opts.dist,\n stripSources: opts.stripSources,\n };\n\n const steps: UploadResult['steps'] = [];\n const mapResult = await uploadOne('sourcemap', p.mapPath, debugId, merged);\n steps.push({\n type: 'sourcemap',\n status: mapResult.status,\n sha8: sha8(readFileSync(p.mapPath)),\n body: mapResult.ok ? undefined : mapResult.body,\n });\n\n let allOk = mapResult.ok;\n if (opts.uploadBundles) {\n const bundleResult = await uploadOne('bundle', p.jsPath, debugId, merged);\n steps.push({\n type: 'bundle',\n status: bundleResult.status,\n sha8: sha8(readFileSync(p.jsPath)),\n body: bundleResult.ok ? undefined : bundleResult.body,\n });\n allOk = allOk && bundleResult.ok;\n }\n\n return { bundleName: p.bundleName, debugId, ok: allOk, steps };\n}\n\n/** Upload every pair in parallel. */\nexport async function uploadAll(\n pairs: BundlePair[],\n opts: UploadOptions,\n): Promise<UploadResult[]> {\n return Promise.all(pairs.map((p) => uploadPair(p, opts)));\n}\n","/**\n * Programmatic source-map pipeline. Build-time only.\n *\n * The high-level \"do everything\" entry point Vite/Webpack/Next plugins\n * call. Walk the build output → inject debug IDs → upload artifacts.\n *\n * import { processBuildOutput } from '@allstak/react/sourcemaps';\n *\n * await processBuildOutput({\n * dir: 'dist',\n * release: 'web@1.4.2',\n * token: process.env.ALLSTAK_UPLOAD_TOKEN!,\n * });\n */\n\nexport type { BundlePair } from './walk';\nexport { findPairs, walk } from './walk';\nexport type { InjectResult } from './inject';\nexport { injectPair, injectAll, readDebugIdFromMap } from './inject';\nexport type { UploadOptions, UploadResult } from './upload';\nexport { uploadPair, uploadAll, DEFAULT_HOST } from './upload';\n\nimport { resolve } from 'node:path';\nimport { findPairs } from './walk';\nimport { injectAll } from './inject';\nimport { uploadAll, type UploadOptions, type UploadResult } from './upload';\n\n/** Options for {@link processBuildOutput}. */\nexport interface ProcessOptions extends Partial<UploadOptions> {\n /** Build output directory to scan (e.g. `dist`, `.next/static`). */\n dir: string;\n /**\n * If true, only inject debug IDs and skip upload. Useful for sample\n * apps and CI dry-runs.\n */\n injectOnly?: boolean;\n /** Suppress per-pair console output. Default false (you want to see this). */\n silent?: boolean;\n}\n\n/** What `processBuildOutput` reports back. */\nexport interface ProcessReport {\n /** Absolute output dir scanned. */\n dir: string;\n /** Pairs found. */\n pairs: number;\n /** Per-pair injection results (debugId + reused?). */\n injected: Array<{ bundleName: string; debugId: string; reused: boolean }>;\n /** Per-pair upload results, omitted when no token / `injectOnly: true`. */\n uploaded?: UploadResult[];\n}\n\nexport async function processBuildOutput(opts: ProcessOptions): Promise<ProcessReport> {\n const dir = resolve(opts.dir);\n const pairs = findPairs(dir);\n const log = opts.silent ? () => undefined : (m: string) => console.log(`[allstak/sourcemaps] ${m}`);\n\n log(`scanning ${dir} — ${pairs.length} bundle/map pair(s)`);\n if (pairs.length === 0) {\n return { dir, pairs: 0, injected: [] };\n }\n\n const injectedRaw = injectAll(pairs);\n const injected = injectedRaw.map(({ pair, result }) => ({\n bundleName: pair.bundleName,\n debugId: result.debugId,\n reused: result.reused,\n }));\n for (const i of injected) {\n log(` ${i.bundleName} ${i.debugId} ${i.reused ? '(reused)' : '(new)'}`);\n }\n\n const token = opts.token ?? process.env.ALLSTAK_UPLOAD_TOKEN;\n const release = opts.release ?? process.env.ALLSTAK_RELEASE;\n\n if (opts.injectOnly || !token) {\n if (!opts.injectOnly && !token) {\n log('skipping upload — no token (set ALLSTAK_UPLOAD_TOKEN or pass `token`)');\n }\n return { dir, pairs: pairs.length, injected };\n }\n if (!release) {\n log('skipping upload — no release (set ALLSTAK_RELEASE or pass `release`)');\n return { dir, pairs: pairs.length, injected };\n }\n\n const uploaded = await uploadAll(pairs, {\n ...opts,\n release,\n token,\n });\n for (const u of uploaded) {\n if (u.ok) {\n log(` ${u.bundleName} uploaded debugId=${u.debugId}`);\n } else {\n const last = u.steps[u.steps.length - 1];\n log(` ${u.bundleName} FAIL status=${last?.status ?? '?'} body=${last?.body ?? ''}`);\n }\n }\n return { dir, pairs: pairs.length, injected, uploaded };\n}\n"],"mappings":"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","names":["readFileSync","basename","readFileSync","basename"]}