@allstak/react 0.3.1 → 0.3.3

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,226 +1,165 @@
1
1
  # @allstak/react
2
2
 
3
- **Drop-in error tracking for React. One `<AllStakErrorBoundary>`, zero config beyond your API key.**
3
+ AllStak React SDK for browser error capture, component error boundaries, breadcrumbs, user context, HTTP metadata, traces, Web Vitals, and source-map release support.
4
4
 
5
- [![npm version](https://img.shields.io/npm/v/@allstak/react.svg)](https://www.npmjs.com/package/@allstak/react)
6
- [![CI](https://github.com/AllStak/allstak-react/actions/workflows/ci.yml/badge.svg)](https://github.com/AllStak/allstak-react/actions)
7
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+ Stability: stable SDK runtime, beta live certification. Live dashboard proof is not claimed until you run a project with real AllStak credentials.
8
6
 
9
- Official AllStak SDK for React — error boundary component, hooks, and a profiler HOC on top of the browser SDK.
7
+ ## 1. Automatic Setup With Wizard
10
8
 
11
- ## Dashboard
9
+ Recommended flow:
12
10
 
13
- View captured events live at [app.allstak.sa](https://app.allstak.sa).
11
+ ```bash
12
+ npx @allstak/wizard setup --integration react
13
+ ```
14
14
 
15
- ![AllStak dashboard](https://app.allstak.sa/images/dashboard-preview.png)
15
+ The only value you may need to enter is the AllStak ingest API key. The wizard detects Vite React, CRA-style React, React Router, JavaScript, and TypeScript entry files. If the project is Next.js, use `@allstak/next` instead:
16
16
 
17
- ## Features
17
+ ```bash
18
+ npx @allstak/wizard setup --integration next
19
+ ```
18
20
 
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
24
- - Full TypeScript types
21
+ ## 2. What The Wizard Changes
25
22
 
26
- ## What You Get
23
+ The wizard:
27
24
 
28
- Once integrated, every event flows to your AllStak dashboard:
25
+ - Installs `@allstak/react`.
26
+ - Writes managed env vars to `.env.local` for Vite or `.env` for non-Vite React.
27
+ - Detects `src/main.tsx`, `src/main.jsx`, `src/index.tsx`, or `src/index.jsx`.
28
+ - Wraps the existing render tree with `AllStakProvider`.
29
+ - Preserves `React.StrictMode`, React Router, and existing providers.
30
+ - Enables provider-managed component error boundary capture.
31
+ - Wires Vite source-map upload hooks when a Vite config is present.
32
+ - Prints the changed files.
33
+ - Supports dry-run, repair, idempotent re-runs, and uninstall.
29
34
 
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
35
+ ## 3. Verification
35
36
 
36
- ## Installation
37
+ After setup:
37
38
 
38
39
  ```bash
39
- npm install @allstak/react
40
+ npm run build
41
+ npx @allstak/wizard doctor --integration react
40
42
  ```
41
43
 
42
- ## Quick Start
43
-
44
- > Create a project at [app.allstak.sa](https://app.allstak.sa) to get your API key.
44
+ With live credentials, send a test event from your app:
45
45
 
46
- ```tsx
47
- import { AllStak, AllStakErrorBoundary } from '@allstak/react';
46
+ ```ts
47
+ import { AllStak } from '@allstak/react';
48
48
 
49
- AllStak.init({
50
- apiKey: import.meta.env.VITE_ALLSTAK_API_KEY,
51
- environment: 'production',
49
+ AllStak.captureMessage('AllStak React setup verification', {
50
+ source: 'manual-verification',
52
51
  });
53
-
54
- AllStak.captureException(new Error('test: hello from allstak-react'));
55
-
56
- export function App() {
57
- return (
58
- <AllStakErrorBoundary fallback={<p>Something went wrong.</p>}>
59
- <Routes />
60
- </AllStakErrorBoundary>
61
- );
62
- }
63
52
  ```
64
53
 
65
- Load the app the test error appears in your dashboard within seconds.
54
+ Dashboard appearance is not guaranteed by local tests. Confirm the event in AllStak before claiming live certification.
66
55
 
67
- ## Get Your API Key
56
+ ## 4. Rollback / Uninstall
68
57
 
69
- 1. Sign up at [app.allstak.sa](https://app.allstak.sa)
70
- 2. Create a project
71
- 3. Copy your API key from **Project Settings → API Keys**
72
- 4. Export it as `ALLSTAK_API_KEY` or pass it to `AllStak.init(...)`
58
+ ```bash
59
+ npx @allstak/wizard uninstall --integration react
60
+ ```
73
61
 
74
- ## Configuration
62
+ Uninstall removes wizard-managed env blocks, source-map config, imports, and provider wrappers. User-owned code outside wizard markers is preserved.
75
63
 
76
- | Option | Type | Required | Default | Description |
77
- |---|---|---|---|---|
78
- | `apiKey` | `string` | yes | — | Project API key (`ask_live_…`) |
79
- | `environment` | `string` | no | — | Deployment env |
80
- | `release` | `string` | no | — | Version or git SHA |
81
- | `host` | `string` | no | `https://api.allstak.sa` | Ingest host override |
82
- | `user` | `{ id?, email? }` | no | — | Default user context |
83
- | `tags` | `Record<string,string>` | no | — | Default tags |
64
+ ## 5. Manual Setup Fallback
84
65
 
85
- ## Example Usage
66
+ Use manual setup only when the wizard cannot safely patch a custom entry file:
86
67
 
87
- Capture an error inside a component:
68
+ ```bash
69
+ npm install @allstak/react
70
+ ```
88
71
 
89
72
  ```tsx
90
- import { useAllStak } from '@allstak/react';
73
+ import { AllStakProvider } from '@allstak/react';
91
74
 
92
- function CheckoutButton() {
93
- const allstak = useAllStak();
75
+ export function Root({ children }: { children: React.ReactNode }) {
94
76
  return (
95
- <button onClick={() => {
96
- try { checkout(); }
97
- catch (e) { allstak.captureException(e as Error); }
98
- }}>Pay</button>
77
+ <AllStakProvider
78
+ apiKey={import.meta.env.VITE_ALLSTAK_API_KEY}
79
+ host={import.meta.env.VITE_ALLSTAK_HOST}
80
+ environment={import.meta.env.VITE_ALLSTAK_ENVIRONMENT ?? 'production'}
81
+ release={import.meta.env.VITE_ALLSTAK_RELEASE}
82
+ enableHttpTracking
83
+ >
84
+ {children}
85
+ </AllStakProvider>
99
86
  );
100
87
  }
101
88
  ```
102
89
 
103
- Wrap a component with the profiler:
104
-
105
- ```tsx
106
- import { withAllStakProfiler } from '@allstak/react';
107
- export default withAllStakProfiler(Dashboard, { name: 'Dashboard' });
108
- ```
109
-
110
- Set user context on login:
111
-
112
- ```tsx
113
- import { AllStak } from '@allstak/react';
114
- AllStak.setUser({ id: user.id, email: user.email });
115
- ```
116
-
117
- ## HTTP tracking
118
-
119
- Setting `enableHttpTracking: true` (off by default) auto-wraps `fetch`,
120
- `XMLHttpRequest`, and `axios` (when installed) so every outbound HTTP
121
- call is recorded as an `http_request` event.
122
-
123
- **Privacy defaults are aggressive:**
124
-
125
- - request/response bodies are **not** captured
126
- - headers are **not** captured
127
- - `Authorization`, `Cookie`, `Set-Cookie`, `X-API-Key`, `X-Auth-Token`,
128
- `Proxy-Authorization` are **always** redacted
129
- - query params named `token`, `password`, `api_key`, `apikey`,
130
- `authorization`, `auth`, `secret`, `access_token`, `refresh_token`,
131
- `session`, `sessionid`, `jwt` are **always** redacted in the URL
132
-
133
- To enable richer capture (only on routes you control):
90
+ For manual capture:
134
91
 
135
92
  ```ts
136
- AllStak.init({
137
- apiKey: '...',
138
- enableHttpTracking: true,
139
- httpTracking: {
140
- captureRequestBody: true,
141
- captureResponseBody: true,
142
- captureHeaders: true, // auth headers still hard-redacted
143
- redactHeaders: ['x-tenant'],
144
- redactQueryParams: ['custom_id'],
145
- ignoredUrls: [/health/i, '/metrics'],
146
- allowedUrls: [],
147
- maxBodyBytes: 4096,
148
- },
149
- });
93
+ import { AllStak } from '@allstak/react';
150
94
 
151
- // axios with custom adapter (rare):
152
- import axios from 'axios';
153
- const api = AllStak.instrumentAxios(axios.create({ baseURL: 'https://api.example.com' }));
95
+ AllStak.captureException(new Error('checkout failed'));
96
+ AllStak.captureMessage('user opened checkout');
97
+ AllStak.setUser({ id: 'user_123' });
98
+ AllStak.addBreadcrumb({ type: 'navigation', message: 'Checkout' });
154
99
  ```
155
100
 
156
- When an exception fires after a failed request, the most recent failed
157
- HTTP requests (last 10) are automatically attached to the error
158
- metadata under `http.recentFailed` for easy triage.
101
+ ## 6. Configuration
159
102
 
160
- ## Production Endpoint
103
+ Provider props include:
161
104
 
162
- Production endpoint: `https://api.allstak.sa`. Override via `host` for self-hosted installs:
105
+ | Option | Default | Notes |
106
+ | --- | --- | --- |
107
+ | `apiKey` | required | Public browser ingest key. |
108
+ | `host` | `https://api.allstak.sa` | Override for self-hosted ingest. |
109
+ | `environment` | `production` | Release environment tag. |
110
+ | `release` | unset | Use app version or commit SHA. |
111
+ | `debug` | `false` | Enables SDK diagnostic logs. |
112
+ | `enableHttpTracking` | `false` | Captures HTTP metadata with redaction. |
113
+ | `autoCaptureBrowserErrors` | `true` | Captures `window.onerror` and `unhandledrejection`. |
114
+ | `autoBreadcrumbsFetch` | `true` | Adds fetch/XHR breadcrumbs. |
115
+ | `autoBreadcrumbsConsole` | `true` | Captures `warn`/`error`; `log`/`info` stay off by default. |
116
+ | `beforeSend` | unset | Last-chance event scrub/drop hook. |
163
117
 
164
- ```tsx
165
- AllStak.init({ apiKey: '...', host: 'https://allstak.mycorp.com' });
166
- ```
118
+ ## 7. Privacy / PII / Redaction
167
119
 
168
- ## Source maps
120
+ Privacy defaults are conservative:
169
121
 
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.
122
+ - Authorization, cookie, API key, token, and secret headers are always redacted.
123
+ - Sensitive query parameters are redacted.
124
+ - Request/response bodies are disabled unless explicitly enabled.
125
+ - Console `log` and `info` breadcrumbs are off by default.
126
+ - Use `beforeSend` for app-specific PII removal.
175
127
 
176
- ### Vite
128
+ Do not send passwords, payment data, national IDs, raw tokens, or raw request/response bodies unless you have verified redaction in your app.
177
129
 
178
- ```bash
179
- npm install -D @allstak/js
180
- ```
130
+ ## 8. Source Maps / Releases
131
+
132
+ For Vite, the wizard wires the AllStak source-map plugin automatically when possible. Manual fallback:
181
133
 
182
134
  ```ts
183
- // vite.config.ts
184
135
  import { defineConfig } from 'vite';
185
- import { allstakSourcemaps } from '@allstak/js/vite';
136
+ import react from '@vitejs/plugin-react';
137
+ import { allstakSourcemaps } from '@allstak/react/vite';
186
138
 
187
139
  export default defineConfig({
188
- build: { sourcemap: 'hidden' },
189
140
  plugins: [
141
+ react(),
190
142
  allstakSourcemaps({
191
- release: process.env.RELEASE!,
192
- token: process.env.ALLSTAK_UPLOAD_TOKEN!,
143
+ release: process.env.VITE_ALLSTAK_RELEASE,
144
+ token: process.env.ALLSTAK_SOURCEMAP_TOKEN,
193
145
  }),
194
146
  ],
195
147
  });
196
148
  ```
197
149
 
198
- ### Webpack / Next / generic
199
-
200
- The plugin exists for `@allstak/js/webpack` and `@allstak/js/next`; for
201
- any other bundler call the underlying API after your build:
202
-
203
- ```ts
204
- import { processBuildOutput } from '@allstak/js/sourcemaps';
205
-
206
- await processBuildOutput({
207
- dir: 'dist',
208
- release: process.env.RELEASE!,
209
- token: process.env.ALLSTAK_UPLOAD_TOKEN!,
210
- });
211
- ```
212
-
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.
150
+ Use the same `release` value in the provider and source-map upload.
217
151
 
218
- ## Links
152
+ ## 9. Troubleshooting
219
153
 
220
- - Documentation: https://docs.allstak.sa
221
- - Dashboard: https://app.allstak.sa
222
- - Source: https://github.com/AllStak/allstak-react
154
+ - No events: verify `VITE_ALLSTAK_API_KEY` exists and the provider is present once.
155
+ - Duplicate events: rerun `npx @allstak/wizard doctor --integration react` and check for multiple providers.
156
+ - Next.js detected: use `@allstak/next`.
157
+ - Build fails after setup: run `npx @allstak/wizard uninstall --integration react`, then rerun with `--dry-run` and inspect the planned diff.
158
+ - Source maps missing: confirm `ALLSTAK_SOURCEMAP_TOKEN` and matching `release`.
223
159
 
224
- ## License
160
+ ## 10. Limitations
225
161
 
226
- MIT © AllStak
162
+ - Live dashboard delivery is not proven by local tests.
163
+ - Session replay is privacy-first and limited compared with full DOM replay tools.
164
+ - Edge runtimes should use framework-specific packages.
165
+ - Stable production launch requires live certification against your dashboard.
@@ -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"]}