@allstak/react-native 0.3.4 → 0.4.1

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.
@@ -83,15 +83,14 @@ async function uploadReactNativeSourcemap(opts) {
83
83
  const log = opts.silent ? () => void 0 : (m) => console.log(`[allstak/sourcemaps] ${m}`);
84
84
  const inject = injectReactNativeSourcemap(opts);
85
85
  log(`bundle: ${(0, import_node_path.basename)(opts.bundle)} debugId: ${inject.debugId} ${inject.reused ? "(reused)" : "(new)"}`);
86
- const env = loadAllStakEnv();
87
- const token = opts.token ?? env.ALLSTAK_UPLOAD_TOKEN;
86
+ const token = opts.token ?? process.env.ALLSTAK_UPLOAD_TOKEN;
88
87
  if (opts.injectOnly || !token) {
89
88
  if (!opts.injectOnly && !token) {
90
89
  log("skipping upload \u2014 no token (set ALLSTAK_UPLOAD_TOKEN or pass `token`)");
91
90
  }
92
91
  return inject;
93
92
  }
94
- const host = opts.host ?? env.ALLSTAK_HOST ?? DEFAULT_HOST;
93
+ const host = opts.host ?? process.env.ALLSTAK_HOST ?? DEFAULT_HOST;
95
94
  const stripSources = opts.stripSources ?? false;
96
95
  const steps = [];
97
96
  const mapResult = await uploadOne(
@@ -134,21 +133,6 @@ async function uploadReactNativeSourcemap(opts) {
134
133
  }
135
134
  return { ...inject, uploaded: allOk, steps };
136
135
  }
137
- function loadAllStakEnv() {
138
- const out = { ...process.env };
139
- for (const file of [".env.local", ".env"]) {
140
- const full = (0, import_node_path.resolve)(process.cwd(), file);
141
- if (!(0, import_node_fs.existsSync)(full)) continue;
142
- for (const line of (0, import_node_fs.readFileSync)(full, "utf8").split(/\r?\n/)) {
143
- const trimmed = line.trim();
144
- if (!trimmed || trimmed.startsWith("#")) continue;
145
- const match = /^([A-Z0-9_]+)\s*=\s*(.*)$/.exec(trimmed);
146
- if (!match || out[match[1]] !== void 0) continue;
147
- out[match[1]] = match[2].replace(/^['"]|['"]$/g, "");
148
- }
149
- }
150
- return out;
151
- }
152
136
  // Annotate the CommonJS export names for ESM import in node:
153
137
  0 && (module.exports = {
154
138
  DEFAULT_HOST,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/build/sourcemaps.ts"],"sourcesContent":["/**\n * Source-map upload pipeline for React Native (Metro / Hermes). Build-time only.\n *\n * Metro emits one bundle + one source map per build, so the React Native\n * flow is simpler than the React-web flow (no `dist/` walk needed). The\n * developer hands us the bundle + map paths produced by:\n *\n * npx react-native bundle \\\n * --platform ios --dev false --entry-file index.js \\\n * --bundle-output ios.bundle \\\n * --sourcemap-output ios.bundle.map\n *\n * If Hermes is enabled, the resulting `.hbc` bytecode replaces the JS\n * bundle on the device, and the user must compose the Metro map with the\n * Hermes map BEFORE uploading. We accept whatever map the user gives us\n * and inject a debug-id into both the map and the bundle.\n *\n * Usage from a build script (`scripts/upload-sourcemaps.js`):\n *\n * const { uploadReactNativeSourcemap } = require('@allstak/react-native/sourcemaps');\n *\n * await uploadReactNativeSourcemap({\n * bundle: 'ios.bundle',\n * sourcemap: 'ios.bundle.map',\n * release: 'mobile@1.2.3',\n * dist: 'ios-hermes',\n * token: process.env.ALLSTAK_UPLOAD_TOKEN,\n * });\n *\n * Or `injectOnly: true` to add the debug-id without uploading (useful in\n * CI dry-runs or when you don't have an upload token yet).\n */\n\nimport { existsSync, readFileSync, writeFileSync } from 'node:fs';\nimport { randomUUID, createHash } from 'node:crypto';\nimport { basename, resolve } from 'node:path';\n\nexport const DEFAULT_HOST = 'https://api.allstak.sa';\n\nconst DEBUG_ID_LINE_RE = /^\\/\\/# debugId=([0-9a-f-]{36})\\s*$/m;\n\nexport interface UploadReactNativeSourcemapOptions {\n /** Path to the JS/Hermes-bytecode bundle (Metro's `--bundle-output`). */\n bundle: string;\n /** Path to the matching `.map` (Metro's `--sourcemap-output`). */\n sourcemap: string;\n /** Release identifier (e.g. `mobile@1.2.3` — match `release` in AllStak.init). */\n release: string;\n /**\n * Distribution tag — recommended values: `ios-hermes`, `android-hermes`,\n * `ios-jsc`, `android-jsc`. Match the `dist` the SDK auto-detects at\n * runtime (see `src/install.ts`). Required for the symbolicator to\n * pick the right map per platform.\n */\n dist?: string;\n /** Project upload token (`aspk_…`). Defaults to `ALLSTAK_UPLOAD_TOKEN`. */\n token?: string;\n /** Override ingest host. Defaults to `ALLSTAK_HOST` or production. */\n host?: string;\n /** Drop `sourcesContent` from the map before upload (smaller payload). */\n stripSources?: boolean;\n /** Also upload the JS/HBC bundle alongside the map. Off by default. */\n uploadBundle?: boolean;\n /** Inject debug-id but skip the upload (CI dry-run). */\n injectOnly?: boolean;\n /** Pre-existing debug-id to use instead of generating one. Optional. */\n debugId?: string;\n /** Suppress per-step console output. Default false. */\n silent?: boolean;\n}\n\nexport interface UploadReactNativeSourcemapResult {\n /** The debug-id injected into the bundle + map. */\n debugId: string;\n /** True if the bundle already had a debug-id; we reused it. */\n reused: boolean;\n /** True when upload(s) succeeded — undefined when `injectOnly: true`. */\n uploaded?: boolean;\n /** Per-artifact responses, in the order we sent them. */\n steps?: Array<{ type: 'sourcemap' | 'bundle'; status: number; sha8: string; body?: string }>;\n}\n\nfunction sha8(buf: Buffer): string {\n return createHash('sha256').update(buf).digest('hex').slice(0, 8);\n}\n\n/**\n * Inject a debug-id into the bundle + map (idempotent — reuses an\n * existing id if present).\n */\nexport function injectReactNativeSourcemap(\n opts: Pick<UploadReactNativeSourcemapOptions, 'bundle' | 'sourcemap' | 'debugId'>,\n): { debugId: string; reused: boolean } {\n const bundleRaw = readFileSync(opts.bundle, 'utf8');\n const mapRaw = readFileSync(opts.sourcemap, 'utf8');\n const map = JSON.parse(mapRaw) as { debugId?: unknown; [k: string]: unknown };\n\n let debugId = opts.debugId ?? '';\n if (!debugId && typeof map.debugId === 'string') debugId = map.debugId;\n const existing = DEBUG_ID_LINE_RE.exec(bundleRaw);\n if (!debugId && existing && existing[1]) debugId = existing[1];\n const reused = !!debugId;\n if (!debugId) debugId = randomUUID();\n\n // Re-stringify map with canonical debugId field.\n map.debugId = debugId;\n writeFileSync(opts.sourcemap, JSON.stringify(map));\n\n // Append `//# debugId=…` to the bundle (idempotent — strip prior line first).\n let bundleOut = bundleRaw.replace(DEBUG_ID_LINE_RE, '');\n bundleOut = bundleOut.replace(/\\s+$/, '');\n bundleOut += `\\n//# debugId=${debugId}\\n`;\n writeFileSync(opts.bundle, bundleOut);\n\n return { debugId, reused };\n}\n\nasync function uploadOne(\n type: 'sourcemap' | 'bundle',\n filePath: string,\n debugId: string,\n release: string,\n host: string,\n token: string,\n dist: string | undefined,\n stripSources: boolean,\n): Promise<{ status: number; body: string; ok: boolean }> {\n let buf = readFileSync(filePath);\n if (type === 'sourcemap' && 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', release);\n if (dist) form.append('dist', 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(host.replace(/\\/$/, '') + '/api/v1/artifacts/upload', {\n method: 'POST',\n headers: { 'X-AllStak-Upload-Token': token },\n body: form,\n });\n return { status: res.status, body: await res.text(), ok: res.ok };\n}\n\n/**\n * Inject debug-id into the bundle + map and (optionally) upload.\n *\n * Returns the debug-id (so the caller can stash it for symbolicator\n * lookups), whether it was reused, and per-step upload statuses.\n */\nexport async function uploadReactNativeSourcemap(\n opts: UploadReactNativeSourcemapOptions,\n): Promise<UploadReactNativeSourcemapResult> {\n const log = opts.silent ? () => undefined : (m: string) => console.log(`[allstak/sourcemaps] ${m}`);\n\n const inject = injectReactNativeSourcemap(opts);\n log(`bundle: ${basename(opts.bundle)} debugId: ${inject.debugId} ${inject.reused ? '(reused)' : '(new)'}`);\n\n const env = loadAllStakEnv();\n const token = opts.token ?? env.ALLSTAK_UPLOAD_TOKEN;\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 inject;\n }\n\n const host = opts.host ?? env.ALLSTAK_HOST ?? DEFAULT_HOST;\n const stripSources = opts.stripSources ?? false;\n\n const steps: UploadReactNativeSourcemapResult['steps'] = [];\n const mapResult = await uploadOne(\n 'sourcemap', opts.sourcemap, inject.debugId, opts.release, host, token, opts.dist, stripSources,\n );\n steps.push({\n type: 'sourcemap',\n status: mapResult.status,\n sha8: sha8(readFileSync(opts.sourcemap)),\n body: mapResult.ok ? undefined : mapResult.body,\n });\n log(` sourcemap → ${mapResult.status}${mapResult.ok ? '' : ' ' + mapResult.body.slice(0, 120)}`);\n\n let allOk = mapResult.ok;\n if (opts.uploadBundle) {\n const bundleResult = await uploadOne(\n 'bundle', opts.bundle, inject.debugId, opts.release, host, token, opts.dist, false,\n );\n steps.push({\n type: 'bundle',\n status: bundleResult.status,\n sha8: sha8(readFileSync(opts.bundle)),\n body: bundleResult.ok ? undefined : bundleResult.body,\n });\n allOk = allOk && bundleResult.ok;\n log(` bundle → ${bundleResult.status}${bundleResult.ok ? '' : ' ' + bundleResult.body.slice(0, 120)}`);\n }\n\n return { ...inject, uploaded: allOk, steps };\n}\n\nfunction loadAllStakEnv(): Record<string, string | undefined> {\n const out: Record<string, string | undefined> = { ...process.env };\n for (const file of ['.env.local', '.env']) {\n const full = resolve(process.cwd(), file);\n if (!existsSync(full)) continue;\n for (const line of readFileSync(full, 'utf8').split(/\\r?\\n/)) {\n const trimmed = line.trim();\n if (!trimmed || trimmed.startsWith('#')) continue;\n const match = /^([A-Z0-9_]+)\\s*=\\s*(.*)$/.exec(trimmed);\n if (!match || out[match[1]!] !== undefined) continue;\n out[match[1]!] = match[2]!.replace(/^['\"]|['\"]$/g, '');\n }\n }\n return out;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAiCA,qBAAwD;AACxD,yBAAuC;AACvC,uBAAkC;AAE3B,IAAM,eAAe;AAE5B,IAAM,mBAAmB;AA2CzB,SAAS,KAAK,KAAqB;AACjC,aAAO,+BAAW,QAAQ,EAAE,OAAO,GAAG,EAAE,OAAO,KAAK,EAAE,MAAM,GAAG,CAAC;AAClE;AAMO,SAAS,2BACd,MACsC;AACtC,QAAM,gBAAY,6BAAa,KAAK,QAAQ,MAAM;AAClD,QAAM,aAAS,6BAAa,KAAK,WAAW,MAAM;AAClD,QAAM,MAAM,KAAK,MAAM,MAAM;AAE7B,MAAI,UAAU,KAAK,WAAW;AAC9B,MAAI,CAAC,WAAW,OAAO,IAAI,YAAY,SAAU,WAAU,IAAI;AAC/D,QAAM,WAAW,iBAAiB,KAAK,SAAS;AAChD,MAAI,CAAC,WAAW,YAAY,SAAS,CAAC,EAAG,WAAU,SAAS,CAAC;AAC7D,QAAM,SAAS,CAAC,CAAC;AACjB,MAAI,CAAC,QAAS,eAAU,+BAAW;AAGnC,MAAI,UAAU;AACd,oCAAc,KAAK,WAAW,KAAK,UAAU,GAAG,CAAC;AAGjD,MAAI,YAAY,UAAU,QAAQ,kBAAkB,EAAE;AACtD,cAAY,UAAU,QAAQ,QAAQ,EAAE;AACxC,eAAa;AAAA,cAAiB,OAAO;AAAA;AACrC,oCAAc,KAAK,QAAQ,SAAS;AAEpC,SAAO,EAAE,SAAS,OAAO;AAC3B;AAEA,eAAe,UACb,MACA,UACA,SACA,SACA,MACA,OACA,MACA,cACwD;AACxD,MAAI,UAAM,6BAAa,QAAQ;AAC/B,MAAI,SAAS,eAAe,cAAc;AACxC,UAAM,OAAO,KAAK,MAAM,IAAI,SAAS,MAAM,CAAC;AAC5C,QAAI,MAAM,QAAQ,KAAK,cAAc,EAAG,QAAO,KAAK;AACpD,UAAM,OAAO,KAAK,KAAK,UAAU,IAAI,CAAC;AAAA,EACxC;AAEA,QAAM,OAAO,IAAI,SAAS;AAC1B,OAAK,OAAO,WAAW,OAAO;AAC9B,OAAK,OAAO,QAAQ,IAAI;AACxB,OAAK,OAAO,WAAW,OAAO;AAC9B,MAAI,KAAM,MAAK,OAAO,QAAQ,IAAI;AAClC,OAAK;AAAA,IACH;AAAA,IACA,IAAI,KAAK,CAAC,GAAG,GAAG;AAAA,MACd,MAAM,SAAS,cAAc,qBAAqB;AAAA,IACpD,CAAC;AAAA,QACD,2BAAS,QAAQ;AAAA,EACnB;AAEA,QAAM,MAAM,MAAM,MAAM,KAAK,QAAQ,OAAO,EAAE,IAAI,4BAA4B;AAAA,IAC5E,QAAQ;AAAA,IACR,SAAS,EAAE,0BAA0B,MAAM;AAAA,IAC3C,MAAM;AAAA,EACR,CAAC;AACD,SAAO,EAAE,QAAQ,IAAI,QAAQ,MAAM,MAAM,IAAI,KAAK,GAAG,IAAI,IAAI,GAAG;AAClE;AAQA,eAAsB,2BACpB,MAC2C;AAC3C,QAAM,MAAM,KAAK,SAAS,MAAM,SAAY,CAAC,MAAc,QAAQ,IAAI,wBAAwB,CAAC,EAAE;AAElG,QAAM,SAAS,2BAA2B,IAAI;AAC9C,MAAI,eAAW,2BAAS,KAAK,MAAM,CAAC,cAAc,OAAO,OAAO,IAAI,OAAO,SAAS,aAAa,OAAO,EAAE;AAE1G,QAAM,MAAM,eAAe;AAC3B,QAAM,QAAQ,KAAK,SAAS,IAAI;AAChC,MAAI,KAAK,cAAc,CAAC,OAAO;AAC7B,QAAI,CAAC,KAAK,cAAc,CAAC,OAAO;AAC9B,UAAI,4EAAuE;AAAA,IAC7E;AACA,WAAO;AAAA,EACT;AAEA,QAAM,OAAO,KAAK,QAAQ,IAAI,gBAAgB;AAC9C,QAAM,eAAe,KAAK,gBAAgB;AAE1C,QAAM,QAAmD,CAAC;AAC1D,QAAM,YAAY,MAAM;AAAA,IACtB;AAAA,IAAa,KAAK;AAAA,IAAW,OAAO;AAAA,IAAS,KAAK;AAAA,IAAS;AAAA,IAAM;AAAA,IAAO,KAAK;AAAA,IAAM;AAAA,EACrF;AACA,QAAM,KAAK;AAAA,IACT,MAAM;AAAA,IACN,QAAQ,UAAU;AAAA,IAClB,MAAM,SAAK,6BAAa,KAAK,SAAS,CAAC;AAAA,IACvC,MAAM,UAAU,KAAK,SAAY,UAAU;AAAA,EAC7C,CAAC;AACD,MAAI,sBAAiB,UAAU,MAAM,GAAG,UAAU,KAAK,KAAK,MAAM,UAAU,KAAK,MAAM,GAAG,GAAG,CAAC,EAAE;AAEhG,MAAI,QAAQ,UAAU;AACtB,MAAI,KAAK,cAAc;AACrB,UAAM,eAAe,MAAM;AAAA,MACzB;AAAA,MAAU,KAAK;AAAA,MAAQ,OAAO;AAAA,MAAS,KAAK;AAAA,MAAS;AAAA,MAAM;AAAA,MAAO,KAAK;AAAA,MAAM;AAAA,IAC/E;AACA,UAAM,KAAK;AAAA,MACT,MAAM;AAAA,MACN,QAAQ,aAAa;AAAA,MACrB,MAAM,SAAK,6BAAa,KAAK,MAAM,CAAC;AAAA,MACpC,MAAM,aAAa,KAAK,SAAY,aAAa;AAAA,IACnD,CAAC;AACD,YAAQ,SAAS,aAAa;AAC9B,QAAI,sBAAiB,aAAa,MAAM,GAAG,aAAa,KAAK,KAAK,MAAM,aAAa,KAAK,MAAM,GAAG,GAAG,CAAC,EAAE;AAAA,EAC3G;AAEA,SAAO,EAAE,GAAG,QAAQ,UAAU,OAAO,MAAM;AAC7C;AAEA,SAAS,iBAAqD;AAC5D,QAAM,MAA0C,EAAE,GAAG,QAAQ,IAAI;AACjE,aAAW,QAAQ,CAAC,cAAc,MAAM,GAAG;AACzC,UAAM,WAAO,0BAAQ,QAAQ,IAAI,GAAG,IAAI;AACxC,QAAI,KAAC,2BAAW,IAAI,EAAG;AACvB,eAAW,YAAQ,6BAAa,MAAM,MAAM,EAAE,MAAM,OAAO,GAAG;AAC5D,YAAM,UAAU,KAAK,KAAK;AAC1B,UAAI,CAAC,WAAW,QAAQ,WAAW,GAAG,EAAG;AACzC,YAAM,QAAQ,4BAA4B,KAAK,OAAO;AACtD,UAAI,CAAC,SAAS,IAAI,MAAM,CAAC,CAAE,MAAM,OAAW;AAC5C,UAAI,MAAM,CAAC,CAAE,IAAI,MAAM,CAAC,EAAG,QAAQ,gBAAgB,EAAE;AAAA,IACvD;AAAA,EACF;AACA,SAAO;AACT;","names":[]}
1
+ {"version":3,"sources":["../../src/build/sourcemaps.ts"],"sourcesContent":["/**\n * Source-map upload pipeline for React Native (Metro / Hermes). Build-time only.\n *\n * Metro emits one bundle + one source map per build, so the React Native\n * flow is simpler than the React-web flow (no `dist/` walk needed). The\n * developer hands us the bundle + map paths produced by:\n *\n * npx react-native bundle \\\n * --platform ios --dev false --entry-file index.js \\\n * --bundle-output ios.bundle \\\n * --sourcemap-output ios.bundle.map\n *\n * If Hermes is enabled, the resulting `.hbc` bytecode replaces the JS\n * bundle on the device, and the user must compose the Metro map with the\n * Hermes map BEFORE uploading. We accept whatever map the user gives us\n * and inject a debug-id into both the map and the bundle.\n *\n * Usage from a build script (`scripts/upload-sourcemaps.js`):\n *\n * const { uploadReactNativeSourcemap } = require('@allstak/react-native/sourcemaps');\n *\n * await uploadReactNativeSourcemap({\n * bundle: 'ios.bundle',\n * sourcemap: 'ios.bundle.map',\n * release: 'mobile@1.2.3',\n * dist: 'ios-hermes',\n * token: process.env.ALLSTAK_UPLOAD_TOKEN,\n * });\n *\n * Or `injectOnly: true` to add the debug-id without uploading (useful in\n * CI dry-runs or when you don't have an upload token yet).\n */\n\nimport { readFileSync, writeFileSync } from 'node:fs';\nimport { randomUUID, createHash } from 'node:crypto';\nimport { basename } from 'node:path';\n\nexport const DEFAULT_HOST = 'https://api.allstak.sa';\n\nconst DEBUG_ID_LINE_RE = /^\\/\\/# debugId=([0-9a-f-]{36})\\s*$/m;\n\nexport interface UploadReactNativeSourcemapOptions {\n /** Path to the JS/Hermes-bytecode bundle (Metro's `--bundle-output`). */\n bundle: string;\n /** Path to the matching `.map` (Metro's `--sourcemap-output`). */\n sourcemap: string;\n /** Release identifier (e.g. `mobile@1.2.3` — match `release` in AllStak.init). */\n release: string;\n /**\n * Distribution tag — recommended values: `ios-hermes`, `android-hermes`,\n * `ios-jsc`, `android-jsc`. Match the `dist` the SDK auto-detects at\n * runtime (see `src/install.ts`). Required for the symbolicator to\n * pick the right map per platform.\n */\n dist?: string;\n /** Project upload token (`aspk_…`). Defaults to `ALLSTAK_UPLOAD_TOKEN`. */\n token?: string;\n /** Override ingest host. Defaults to `ALLSTAK_HOST` or production. */\n host?: string;\n /** Drop `sourcesContent` from the map before upload (smaller payload). */\n stripSources?: boolean;\n /** Also upload the JS/HBC bundle alongside the map. Off by default. */\n uploadBundle?: boolean;\n /** Inject debug-id but skip the upload (CI dry-run). */\n injectOnly?: boolean;\n /** Pre-existing debug-id to use instead of generating one. Optional. */\n debugId?: string;\n /** Suppress per-step console output. Default false. */\n silent?: boolean;\n}\n\nexport interface UploadReactNativeSourcemapResult {\n /** The debug-id injected into the bundle + map. */\n debugId: string;\n /** True if the bundle already had a debug-id; we reused it. */\n reused: boolean;\n /** True when upload(s) succeeded — undefined when `injectOnly: true`. */\n uploaded?: boolean;\n /** Per-artifact responses, in the order we sent them. */\n steps?: Array<{ type: 'sourcemap' | 'bundle'; status: number; sha8: string; body?: string }>;\n}\n\nfunction sha8(buf: Buffer): string {\n return createHash('sha256').update(buf).digest('hex').slice(0, 8);\n}\n\n/**\n * Inject a debug-id into the bundle + map (idempotent — reuses an\n * existing id if present).\n */\nexport function injectReactNativeSourcemap(\n opts: Pick<UploadReactNativeSourcemapOptions, 'bundle' | 'sourcemap' | 'debugId'>,\n): { debugId: string; reused: boolean } {\n const bundleRaw = readFileSync(opts.bundle, 'utf8');\n const mapRaw = readFileSync(opts.sourcemap, 'utf8');\n const map = JSON.parse(mapRaw) as { debugId?: unknown; [k: string]: unknown };\n\n let debugId = opts.debugId ?? '';\n if (!debugId && typeof map.debugId === 'string') debugId = map.debugId;\n const existing = DEBUG_ID_LINE_RE.exec(bundleRaw);\n if (!debugId && existing && existing[1]) debugId = existing[1];\n const reused = !!debugId;\n if (!debugId) debugId = randomUUID();\n\n // Re-stringify map with canonical debugId field.\n map.debugId = debugId;\n writeFileSync(opts.sourcemap, JSON.stringify(map));\n\n // Append `//# debugId=…` to the bundle (idempotent — strip prior line first).\n let bundleOut = bundleRaw.replace(DEBUG_ID_LINE_RE, '');\n bundleOut = bundleOut.replace(/\\s+$/, '');\n bundleOut += `\\n//# debugId=${debugId}\\n`;\n writeFileSync(opts.bundle, bundleOut);\n\n return { debugId, reused };\n}\n\nasync function uploadOne(\n type: 'sourcemap' | 'bundle',\n filePath: string,\n debugId: string,\n release: string,\n host: string,\n token: string,\n dist: string | undefined,\n stripSources: boolean,\n): Promise<{ status: number; body: string; ok: boolean }> {\n let buf = readFileSync(filePath);\n if (type === 'sourcemap' && 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', release);\n if (dist) form.append('dist', 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(host.replace(/\\/$/, '') + '/api/v1/artifacts/upload', {\n method: 'POST',\n headers: { 'X-AllStak-Upload-Token': token },\n body: form,\n });\n return { status: res.status, body: await res.text(), ok: res.ok };\n}\n\n/**\n * Inject debug-id into the bundle + map and (optionally) upload.\n *\n * Returns the debug-id (so the caller can stash it for symbolicator\n * lookups), whether it was reused, and per-step upload statuses.\n */\nexport async function uploadReactNativeSourcemap(\n opts: UploadReactNativeSourcemapOptions,\n): Promise<UploadReactNativeSourcemapResult> {\n const log = opts.silent ? () => undefined : (m: string) => console.log(`[allstak/sourcemaps] ${m}`);\n\n const inject = injectReactNativeSourcemap(opts);\n log(`bundle: ${basename(opts.bundle)} debugId: ${inject.debugId} ${inject.reused ? '(reused)' : '(new)'}`);\n\n const token = opts.token ?? process.env.ALLSTAK_UPLOAD_TOKEN;\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 inject;\n }\n\n const host = opts.host ?? process.env.ALLSTAK_HOST ?? DEFAULT_HOST;\n const stripSources = opts.stripSources ?? false;\n\n const steps: UploadReactNativeSourcemapResult['steps'] = [];\n const mapResult = await uploadOne(\n 'sourcemap', opts.sourcemap, inject.debugId, opts.release, host, token, opts.dist, stripSources,\n );\n steps.push({\n type: 'sourcemap',\n status: mapResult.status,\n sha8: sha8(readFileSync(opts.sourcemap)),\n body: mapResult.ok ? undefined : mapResult.body,\n });\n log(` sourcemap → ${mapResult.status}${mapResult.ok ? '' : ' ' + mapResult.body.slice(0, 120)}`);\n\n let allOk = mapResult.ok;\n if (opts.uploadBundle) {\n const bundleResult = await uploadOne(\n 'bundle', opts.bundle, inject.debugId, opts.release, host, token, opts.dist, false,\n );\n steps.push({\n type: 'bundle',\n status: bundleResult.status,\n sha8: sha8(readFileSync(opts.bundle)),\n body: bundleResult.ok ? undefined : bundleResult.body,\n });\n allOk = allOk && bundleResult.ok;\n log(` bundle → ${bundleResult.status}${bundleResult.ok ? '' : ' ' + bundleResult.body.slice(0, 120)}`);\n }\n\n return { ...inject, uploaded: allOk, steps };\n}\n"],"mappings":"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","names":[]}
@@ -1,7 +1,7 @@
1
1
  // src/build/sourcemaps.ts
2
- import { existsSync, readFileSync, writeFileSync } from "fs";
2
+ import { readFileSync, writeFileSync } from "fs";
3
3
  import { randomUUID, createHash } from "crypto";
4
- import { basename, resolve } from "path";
4
+ import { basename } from "path";
5
5
  var DEFAULT_HOST = "https://api.allstak.sa";
6
6
  var DEBUG_ID_LINE_RE = /^\/\/# debugId=([0-9a-f-]{36})\s*$/m;
7
7
  function sha8(buf) {
@@ -57,15 +57,14 @@ async function uploadReactNativeSourcemap(opts) {
57
57
  const log = opts.silent ? () => void 0 : (m) => console.log(`[allstak/sourcemaps] ${m}`);
58
58
  const inject = injectReactNativeSourcemap(opts);
59
59
  log(`bundle: ${basename(opts.bundle)} debugId: ${inject.debugId} ${inject.reused ? "(reused)" : "(new)"}`);
60
- const env = loadAllStakEnv();
61
- const token = opts.token ?? env.ALLSTAK_UPLOAD_TOKEN;
60
+ const token = opts.token ?? process.env.ALLSTAK_UPLOAD_TOKEN;
62
61
  if (opts.injectOnly || !token) {
63
62
  if (!opts.injectOnly && !token) {
64
63
  log("skipping upload \u2014 no token (set ALLSTAK_UPLOAD_TOKEN or pass `token`)");
65
64
  }
66
65
  return inject;
67
66
  }
68
- const host = opts.host ?? env.ALLSTAK_HOST ?? DEFAULT_HOST;
67
+ const host = opts.host ?? process.env.ALLSTAK_HOST ?? DEFAULT_HOST;
69
68
  const stripSources = opts.stripSources ?? false;
70
69
  const steps = [];
71
70
  const mapResult = await uploadOne(
@@ -108,21 +107,6 @@ async function uploadReactNativeSourcemap(opts) {
108
107
  }
109
108
  return { ...inject, uploaded: allOk, steps };
110
109
  }
111
- function loadAllStakEnv() {
112
- const out = { ...process.env };
113
- for (const file of [".env.local", ".env"]) {
114
- const full = resolve(process.cwd(), file);
115
- if (!existsSync(full)) continue;
116
- for (const line of readFileSync(full, "utf8").split(/\r?\n/)) {
117
- const trimmed = line.trim();
118
- if (!trimmed || trimmed.startsWith("#")) continue;
119
- const match = /^([A-Z0-9_]+)\s*=\s*(.*)$/.exec(trimmed);
120
- if (!match || out[match[1]] !== void 0) continue;
121
- out[match[1]] = match[2].replace(/^['"]|['"]$/g, "");
122
- }
123
- }
124
- return out;
125
- }
126
110
  export {
127
111
  DEFAULT_HOST,
128
112
  injectReactNativeSourcemap,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/build/sourcemaps.ts"],"sourcesContent":["/**\n * Source-map upload pipeline for React Native (Metro / Hermes). Build-time only.\n *\n * Metro emits one bundle + one source map per build, so the React Native\n * flow is simpler than the React-web flow (no `dist/` walk needed). The\n * developer hands us the bundle + map paths produced by:\n *\n * npx react-native bundle \\\n * --platform ios --dev false --entry-file index.js \\\n * --bundle-output ios.bundle \\\n * --sourcemap-output ios.bundle.map\n *\n * If Hermes is enabled, the resulting `.hbc` bytecode replaces the JS\n * bundle on the device, and the user must compose the Metro map with the\n * Hermes map BEFORE uploading. We accept whatever map the user gives us\n * and inject a debug-id into both the map and the bundle.\n *\n * Usage from a build script (`scripts/upload-sourcemaps.js`):\n *\n * const { uploadReactNativeSourcemap } = require('@allstak/react-native/sourcemaps');\n *\n * await uploadReactNativeSourcemap({\n * bundle: 'ios.bundle',\n * sourcemap: 'ios.bundle.map',\n * release: 'mobile@1.2.3',\n * dist: 'ios-hermes',\n * token: process.env.ALLSTAK_UPLOAD_TOKEN,\n * });\n *\n * Or `injectOnly: true` to add the debug-id without uploading (useful in\n * CI dry-runs or when you don't have an upload token yet).\n */\n\nimport { existsSync, readFileSync, writeFileSync } from 'node:fs';\nimport { randomUUID, createHash } from 'node:crypto';\nimport { basename, resolve } from 'node:path';\n\nexport const DEFAULT_HOST = 'https://api.allstak.sa';\n\nconst DEBUG_ID_LINE_RE = /^\\/\\/# debugId=([0-9a-f-]{36})\\s*$/m;\n\nexport interface UploadReactNativeSourcemapOptions {\n /** Path to the JS/Hermes-bytecode bundle (Metro's `--bundle-output`). */\n bundle: string;\n /** Path to the matching `.map` (Metro's `--sourcemap-output`). */\n sourcemap: string;\n /** Release identifier (e.g. `mobile@1.2.3` — match `release` in AllStak.init). */\n release: string;\n /**\n * Distribution tag — recommended values: `ios-hermes`, `android-hermes`,\n * `ios-jsc`, `android-jsc`. Match the `dist` the SDK auto-detects at\n * runtime (see `src/install.ts`). Required for the symbolicator to\n * pick the right map per platform.\n */\n dist?: string;\n /** Project upload token (`aspk_…`). Defaults to `ALLSTAK_UPLOAD_TOKEN`. */\n token?: string;\n /** Override ingest host. Defaults to `ALLSTAK_HOST` or production. */\n host?: string;\n /** Drop `sourcesContent` from the map before upload (smaller payload). */\n stripSources?: boolean;\n /** Also upload the JS/HBC bundle alongside the map. Off by default. */\n uploadBundle?: boolean;\n /** Inject debug-id but skip the upload (CI dry-run). */\n injectOnly?: boolean;\n /** Pre-existing debug-id to use instead of generating one. Optional. */\n debugId?: string;\n /** Suppress per-step console output. Default false. */\n silent?: boolean;\n}\n\nexport interface UploadReactNativeSourcemapResult {\n /** The debug-id injected into the bundle + map. */\n debugId: string;\n /** True if the bundle already had a debug-id; we reused it. */\n reused: boolean;\n /** True when upload(s) succeeded — undefined when `injectOnly: true`. */\n uploaded?: boolean;\n /** Per-artifact responses, in the order we sent them. */\n steps?: Array<{ type: 'sourcemap' | 'bundle'; status: number; sha8: string; body?: string }>;\n}\n\nfunction sha8(buf: Buffer): string {\n return createHash('sha256').update(buf).digest('hex').slice(0, 8);\n}\n\n/**\n * Inject a debug-id into the bundle + map (idempotent — reuses an\n * existing id if present).\n */\nexport function injectReactNativeSourcemap(\n opts: Pick<UploadReactNativeSourcemapOptions, 'bundle' | 'sourcemap' | 'debugId'>,\n): { debugId: string; reused: boolean } {\n const bundleRaw = readFileSync(opts.bundle, 'utf8');\n const mapRaw = readFileSync(opts.sourcemap, 'utf8');\n const map = JSON.parse(mapRaw) as { debugId?: unknown; [k: string]: unknown };\n\n let debugId = opts.debugId ?? '';\n if (!debugId && typeof map.debugId === 'string') debugId = map.debugId;\n const existing = DEBUG_ID_LINE_RE.exec(bundleRaw);\n if (!debugId && existing && existing[1]) debugId = existing[1];\n const reused = !!debugId;\n if (!debugId) debugId = randomUUID();\n\n // Re-stringify map with canonical debugId field.\n map.debugId = debugId;\n writeFileSync(opts.sourcemap, JSON.stringify(map));\n\n // Append `//# debugId=…` to the bundle (idempotent — strip prior line first).\n let bundleOut = bundleRaw.replace(DEBUG_ID_LINE_RE, '');\n bundleOut = bundleOut.replace(/\\s+$/, '');\n bundleOut += `\\n//# debugId=${debugId}\\n`;\n writeFileSync(opts.bundle, bundleOut);\n\n return { debugId, reused };\n}\n\nasync function uploadOne(\n type: 'sourcemap' | 'bundle',\n filePath: string,\n debugId: string,\n release: string,\n host: string,\n token: string,\n dist: string | undefined,\n stripSources: boolean,\n): Promise<{ status: number; body: string; ok: boolean }> {\n let buf = readFileSync(filePath);\n if (type === 'sourcemap' && 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', release);\n if (dist) form.append('dist', 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(host.replace(/\\/$/, '') + '/api/v1/artifacts/upload', {\n method: 'POST',\n headers: { 'X-AllStak-Upload-Token': token },\n body: form,\n });\n return { status: res.status, body: await res.text(), ok: res.ok };\n}\n\n/**\n * Inject debug-id into the bundle + map and (optionally) upload.\n *\n * Returns the debug-id (so the caller can stash it for symbolicator\n * lookups), whether it was reused, and per-step upload statuses.\n */\nexport async function uploadReactNativeSourcemap(\n opts: UploadReactNativeSourcemapOptions,\n): Promise<UploadReactNativeSourcemapResult> {\n const log = opts.silent ? () => undefined : (m: string) => console.log(`[allstak/sourcemaps] ${m}`);\n\n const inject = injectReactNativeSourcemap(opts);\n log(`bundle: ${basename(opts.bundle)} debugId: ${inject.debugId} ${inject.reused ? '(reused)' : '(new)'}`);\n\n const env = loadAllStakEnv();\n const token = opts.token ?? env.ALLSTAK_UPLOAD_TOKEN;\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 inject;\n }\n\n const host = opts.host ?? env.ALLSTAK_HOST ?? DEFAULT_HOST;\n const stripSources = opts.stripSources ?? false;\n\n const steps: UploadReactNativeSourcemapResult['steps'] = [];\n const mapResult = await uploadOne(\n 'sourcemap', opts.sourcemap, inject.debugId, opts.release, host, token, opts.dist, stripSources,\n );\n steps.push({\n type: 'sourcemap',\n status: mapResult.status,\n sha8: sha8(readFileSync(opts.sourcemap)),\n body: mapResult.ok ? undefined : mapResult.body,\n });\n log(` sourcemap → ${mapResult.status}${mapResult.ok ? '' : ' ' + mapResult.body.slice(0, 120)}`);\n\n let allOk = mapResult.ok;\n if (opts.uploadBundle) {\n const bundleResult = await uploadOne(\n 'bundle', opts.bundle, inject.debugId, opts.release, host, token, opts.dist, false,\n );\n steps.push({\n type: 'bundle',\n status: bundleResult.status,\n sha8: sha8(readFileSync(opts.bundle)),\n body: bundleResult.ok ? undefined : bundleResult.body,\n });\n allOk = allOk && bundleResult.ok;\n log(` bundle → ${bundleResult.status}${bundleResult.ok ? '' : ' ' + bundleResult.body.slice(0, 120)}`);\n }\n\n return { ...inject, uploaded: allOk, steps };\n}\n\nfunction loadAllStakEnv(): Record<string, string | undefined> {\n const out: Record<string, string | undefined> = { ...process.env };\n for (const file of ['.env.local', '.env']) {\n const full = resolve(process.cwd(), file);\n if (!existsSync(full)) continue;\n for (const line of readFileSync(full, 'utf8').split(/\\r?\\n/)) {\n const trimmed = line.trim();\n if (!trimmed || trimmed.startsWith('#')) continue;\n const match = /^([A-Z0-9_]+)\\s*=\\s*(.*)$/.exec(trimmed);\n if (!match || out[match[1]!] !== undefined) continue;\n out[match[1]!] = match[2]!.replace(/^['\"]|['\"]$/g, '');\n }\n }\n return out;\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/build/sourcemaps.ts"],"sourcesContent":["/**\n * Source-map upload pipeline for React Native (Metro / Hermes). Build-time only.\n *\n * Metro emits one bundle + one source map per build, so the React Native\n * flow is simpler than the React-web flow (no `dist/` walk needed). The\n * developer hands us the bundle + map paths produced by:\n *\n * npx react-native bundle \\\n * --platform ios --dev false --entry-file index.js \\\n * --bundle-output ios.bundle \\\n * --sourcemap-output ios.bundle.map\n *\n * If Hermes is enabled, the resulting `.hbc` bytecode replaces the JS\n * bundle on the device, and the user must compose the Metro map with the\n * Hermes map BEFORE uploading. We accept whatever map the user gives us\n * and inject a debug-id into both the map and the bundle.\n *\n * Usage from a build script (`scripts/upload-sourcemaps.js`):\n *\n * const { uploadReactNativeSourcemap } = require('@allstak/react-native/sourcemaps');\n *\n * await uploadReactNativeSourcemap({\n * bundle: 'ios.bundle',\n * sourcemap: 'ios.bundle.map',\n * release: 'mobile@1.2.3',\n * dist: 'ios-hermes',\n * token: process.env.ALLSTAK_UPLOAD_TOKEN,\n * });\n *\n * Or `injectOnly: true` to add the debug-id without uploading (useful in\n * CI dry-runs or when you don't have an upload token yet).\n */\n\nimport { readFileSync, writeFileSync } from 'node:fs';\nimport { randomUUID, createHash } from 'node:crypto';\nimport { basename } from 'node:path';\n\nexport const DEFAULT_HOST = 'https://api.allstak.sa';\n\nconst DEBUG_ID_LINE_RE = /^\\/\\/# debugId=([0-9a-f-]{36})\\s*$/m;\n\nexport interface UploadReactNativeSourcemapOptions {\n /** Path to the JS/Hermes-bytecode bundle (Metro's `--bundle-output`). */\n bundle: string;\n /** Path to the matching `.map` (Metro's `--sourcemap-output`). */\n sourcemap: string;\n /** Release identifier (e.g. `mobile@1.2.3` — match `release` in AllStak.init). */\n release: string;\n /**\n * Distribution tag — recommended values: `ios-hermes`, `android-hermes`,\n * `ios-jsc`, `android-jsc`. Match the `dist` the SDK auto-detects at\n * runtime (see `src/install.ts`). Required for the symbolicator to\n * pick the right map per platform.\n */\n dist?: string;\n /** Project upload token (`aspk_…`). Defaults to `ALLSTAK_UPLOAD_TOKEN`. */\n token?: string;\n /** Override ingest host. Defaults to `ALLSTAK_HOST` or production. */\n host?: string;\n /** Drop `sourcesContent` from the map before upload (smaller payload). */\n stripSources?: boolean;\n /** Also upload the JS/HBC bundle alongside the map. Off by default. */\n uploadBundle?: boolean;\n /** Inject debug-id but skip the upload (CI dry-run). */\n injectOnly?: boolean;\n /** Pre-existing debug-id to use instead of generating one. Optional. */\n debugId?: string;\n /** Suppress per-step console output. Default false. */\n silent?: boolean;\n}\n\nexport interface UploadReactNativeSourcemapResult {\n /** The debug-id injected into the bundle + map. */\n debugId: string;\n /** True if the bundle already had a debug-id; we reused it. */\n reused: boolean;\n /** True when upload(s) succeeded — undefined when `injectOnly: true`. */\n uploaded?: boolean;\n /** Per-artifact responses, in the order we sent them. */\n steps?: Array<{ type: 'sourcemap' | 'bundle'; status: number; sha8: string; body?: string }>;\n}\n\nfunction sha8(buf: Buffer): string {\n return createHash('sha256').update(buf).digest('hex').slice(0, 8);\n}\n\n/**\n * Inject a debug-id into the bundle + map (idempotent — reuses an\n * existing id if present).\n */\nexport function injectReactNativeSourcemap(\n opts: Pick<UploadReactNativeSourcemapOptions, 'bundle' | 'sourcemap' | 'debugId'>,\n): { debugId: string; reused: boolean } {\n const bundleRaw = readFileSync(opts.bundle, 'utf8');\n const mapRaw = readFileSync(opts.sourcemap, 'utf8');\n const map = JSON.parse(mapRaw) as { debugId?: unknown; [k: string]: unknown };\n\n let debugId = opts.debugId ?? '';\n if (!debugId && typeof map.debugId === 'string') debugId = map.debugId;\n const existing = DEBUG_ID_LINE_RE.exec(bundleRaw);\n if (!debugId && existing && existing[1]) debugId = existing[1];\n const reused = !!debugId;\n if (!debugId) debugId = randomUUID();\n\n // Re-stringify map with canonical debugId field.\n map.debugId = debugId;\n writeFileSync(opts.sourcemap, JSON.stringify(map));\n\n // Append `//# debugId=…` to the bundle (idempotent — strip prior line first).\n let bundleOut = bundleRaw.replace(DEBUG_ID_LINE_RE, '');\n bundleOut = bundleOut.replace(/\\s+$/, '');\n bundleOut += `\\n//# debugId=${debugId}\\n`;\n writeFileSync(opts.bundle, bundleOut);\n\n return { debugId, reused };\n}\n\nasync function uploadOne(\n type: 'sourcemap' | 'bundle',\n filePath: string,\n debugId: string,\n release: string,\n host: string,\n token: string,\n dist: string | undefined,\n stripSources: boolean,\n): Promise<{ status: number; body: string; ok: boolean }> {\n let buf = readFileSync(filePath);\n if (type === 'sourcemap' && 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', release);\n if (dist) form.append('dist', 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(host.replace(/\\/$/, '') + '/api/v1/artifacts/upload', {\n method: 'POST',\n headers: { 'X-AllStak-Upload-Token': token },\n body: form,\n });\n return { status: res.status, body: await res.text(), ok: res.ok };\n}\n\n/**\n * Inject debug-id into the bundle + map and (optionally) upload.\n *\n * Returns the debug-id (so the caller can stash it for symbolicator\n * lookups), whether it was reused, and per-step upload statuses.\n */\nexport async function uploadReactNativeSourcemap(\n opts: UploadReactNativeSourcemapOptions,\n): Promise<UploadReactNativeSourcemapResult> {\n const log = opts.silent ? () => undefined : (m: string) => console.log(`[allstak/sourcemaps] ${m}`);\n\n const inject = injectReactNativeSourcemap(opts);\n log(`bundle: ${basename(opts.bundle)} debugId: ${inject.debugId} ${inject.reused ? '(reused)' : '(new)'}`);\n\n const token = opts.token ?? process.env.ALLSTAK_UPLOAD_TOKEN;\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 inject;\n }\n\n const host = opts.host ?? process.env.ALLSTAK_HOST ?? DEFAULT_HOST;\n const stripSources = opts.stripSources ?? false;\n\n const steps: UploadReactNativeSourcemapResult['steps'] = [];\n const mapResult = await uploadOne(\n 'sourcemap', opts.sourcemap, inject.debugId, opts.release, host, token, opts.dist, stripSources,\n );\n steps.push({\n type: 'sourcemap',\n status: mapResult.status,\n sha8: sha8(readFileSync(opts.sourcemap)),\n body: mapResult.ok ? undefined : mapResult.body,\n });\n log(` sourcemap → ${mapResult.status}${mapResult.ok ? '' : ' ' + mapResult.body.slice(0, 120)}`);\n\n let allOk = mapResult.ok;\n if (opts.uploadBundle) {\n const bundleResult = await uploadOne(\n 'bundle', opts.bundle, inject.debugId, opts.release, host, token, opts.dist, false,\n );\n steps.push({\n type: 'bundle',\n status: bundleResult.status,\n sha8: sha8(readFileSync(opts.bundle)),\n body: bundleResult.ok ? undefined : bundleResult.body,\n });\n allOk = allOk && bundleResult.ok;\n log(` bundle → ${bundleResult.status}${bundleResult.ok ? '' : ' ' + bundleResult.body.slice(0, 120)}`);\n }\n\n return { ...inject, uploaded: allOk, steps };\n}\n"],"mappings":"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","names":[]}
@@ -32,9 +32,13 @@ function withAllStak(config, options = {}) {
32
32
  release: options.release ?? existing.release,
33
33
  environment: options.environment ?? existing.environment,
34
34
  dist: options.dist ?? existing.dist,
35
- pluginVersion: "0.3.4"
35
+ pluginVersion: "0.3.1"
36
36
  };
37
37
  return next;
38
38
  }
39
39
  var expo_plugin_default = withAllStak;
40
+ if (typeof module !== "undefined" && module.exports) {
41
+ module.exports = withAllStak;
42
+ module.exports.default = withAllStak;
43
+ }
40
44
  //# sourceMappingURL=expo-plugin.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/expo-plugin.ts"],"sourcesContent":["/**\n * Expo config plugin.\n *\n * Apply via `app.json`:\n *\n * {\n * \"expo\": {\n * \"plugins\": [\n * [\"@allstak/react-native\", { \"release\": \"mobile@1.2.3\", \"environment\": \"production\" }]\n * ]\n * }\n * }\n *\n * The plugin runs at `expo prebuild` / EAS build time and:\n *\n * 1. Adds the AllStak iOS pod (declared in `react-native.config.js`).\n * Expo's autolinking already picks this up — we just record metadata\n * so `expo doctor` can verify the install is wired.\n * 2. Stamps the chosen `release` into `expo-constants` extras so the JS\n * layer can read it at runtime via `Constants.expoConfig.extra._allstak`\n * without the host app needing to plumb it through env vars.\n * 3. Records that `@allstak/react-native` was loaded as an Expo plugin\n * for diagnostics.\n *\n * Pure config mutation — no native code is patched here. The native iOS\n * + Android modules under ./native are linked by Expo's existing\n * autolinking flow (which honors the `react-native.config.js` manifest).\n */\n\nexport interface AllStakExpoOptions {\n /** Release identifier (e.g. `mobile@1.2.3`). Stamped into `Constants.expoConfig.extra._allstak.release`. */\n release?: string;\n /** Environment label — `production`, `staging`, etc. */\n environment?: string;\n /** Optional dist tag (e.g. `ios-hermes`). Auto-detected at runtime if unset. */\n dist?: string;\n}\n\ninterface ExpoConfig {\n name?: string;\n extra?: Record<string, unknown>;\n plugins?: any[];\n [key: string]: any;\n}\n\ninterface ExpoConfigContext {\n modResults?: any;\n modRawConfig?: ExpoConfig;\n [key: string]: any;\n}\n\n/**\n * The plugin function itself. Expo's plugin runner calls it as\n * `(config, options) => modifiedConfig`. We avoid importing\n * `@expo/config-plugins` so the package has zero hard dependencies on the\n * Expo toolchain — the type checking happens at usage site instead.\n */\nfunction withAllStak(config: ExpoConfig & ExpoConfigContext, options: AllStakExpoOptions = {}): ExpoConfig {\n const next: ExpoConfig = { ...config };\n next.extra = { ...(config.extra ?? {}) };\n\n // Embed runtime-readable metadata under a namespaced key so we never\n // collide with the host app's other extras.\n const existing = (next.extra as any)._allstak ?? {};\n (next.extra as any)._allstak = {\n ...existing,\n release: options.release ?? existing.release,\n environment: options.environment ?? existing.environment,\n dist: options.dist ?? existing.dist,\n pluginVersion: '0.3.4',\n };\n\n return next;\n}\n\nexport default withAllStak;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAyDA,SAAS,YAAY,QAAwC,UAA8B,CAAC,GAAe;AACzG,QAAM,OAAmB,EAAE,GAAG,OAAO;AACrC,OAAK,QAAQ,EAAE,GAAI,OAAO,SAAS,CAAC,EAAG;AAIvC,QAAM,WAAY,KAAK,MAAc,YAAY,CAAC;AAClD,EAAC,KAAK,MAAc,WAAW;AAAA,IAC7B,GAAG;AAAA,IACH,SAAS,QAAQ,WAAW,SAAS;AAAA,IACrC,aAAa,QAAQ,eAAe,SAAS;AAAA,IAC7C,MAAM,QAAQ,QAAQ,SAAS;AAAA,IAC/B,eAAe;AAAA,EACjB;AAEA,SAAO;AACT;AAEA,IAAO,sBAAQ;","names":[]}
1
+ {"version":3,"sources":["../src/expo-plugin.ts"],"sourcesContent":["/**\n * Expo config plugin.\n *\n * Apply via `app.json`:\n *\n * {\n * \"expo\": {\n * \"plugins\": [\n * [\"@allstak/react-native\", { \"release\": \"mobile@1.2.3\", \"environment\": \"production\" }]\n * ]\n * }\n * }\n *\n * The plugin runs at `expo prebuild` / EAS build time and:\n *\n * 1. Adds the AllStak iOS pod (declared in `react-native.config.js`).\n * Expo's autolinking already picks this up — we just record metadata\n * so `expo doctor` can verify the install is wired.\n * 2. Stamps the chosen `release` into `expo-constants` extras so the JS\n * layer can read it at runtime via `Constants.expoConfig.extra._allstak`\n * without the host app needing to plumb it through env vars.\n * 3. Records that `@allstak/react-native` was loaded as an Expo plugin\n * for diagnostics.\n *\n * Pure config mutation — no native code is patched here. The native iOS\n * + Android modules under ./native are linked by Expo's existing\n * autolinking flow (which honors the `react-native.config.js` manifest).\n */\n\nexport interface AllStakExpoOptions {\n /** Release identifier (e.g. `mobile@1.2.3`). Stamped into `Constants.expoConfig.extra._allstak.release`. */\n release?: string;\n /** Environment label — `production`, `staging`, etc. */\n environment?: string;\n /** Optional dist tag (e.g. `ios-hermes`). Auto-detected at runtime if unset. */\n dist?: string;\n}\n\ninterface ExpoConfig {\n name?: string;\n extra?: Record<string, unknown>;\n plugins?: any[];\n [key: string]: any;\n}\n\ninterface ExpoConfigContext {\n modResults?: any;\n modRawConfig?: ExpoConfig;\n [key: string]: any;\n}\n\n/**\n * The plugin function itself. Expo's plugin runner calls it as\n * `(config, options) => modifiedConfig`. We avoid importing\n * `@expo/config-plugins` so the package has zero hard dependencies on the\n * Expo toolchain — the type checking happens at usage site instead.\n */\nfunction withAllStak(config: ExpoConfig & ExpoConfigContext, options: AllStakExpoOptions = {}): ExpoConfig {\n const next: ExpoConfig = { ...config };\n next.extra = { ...(config.extra ?? {}) };\n\n // Embed runtime-readable metadata under a namespaced key so we never\n // collide with the host app's other extras.\n const existing = (next.extra as any)._allstak ?? {};\n (next.extra as any)._allstak = {\n ...existing,\n release: options.release ?? existing.release,\n environment: options.environment ?? existing.environment,\n dist: options.dist ?? existing.dist,\n pluginVersion: '0.3.1',\n };\n\n return next;\n}\n\nexport default withAllStak;\n\n// Expose as a CommonJS function so `app.plugin.js`'s `require('./dist/expo-plugin.js')`\n// returns the plugin directly. The `declare const module` keeps the TS\n// type-checker happy without dragging in @types/node.\ndeclare const module: { exports: any };\nif (typeof module !== 'undefined' && module.exports) {\n module.exports = withAllStak;\n module.exports.default = withAllStak;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAyDA,SAAS,YAAY,QAAwC,UAA8B,CAAC,GAAe;AACzG,QAAM,OAAmB,EAAE,GAAG,OAAO;AACrC,OAAK,QAAQ,EAAE,GAAI,OAAO,SAAS,CAAC,EAAG;AAIvC,QAAM,WAAY,KAAK,MAAc,YAAY,CAAC;AAClD,EAAC,KAAK,MAAc,WAAW;AAAA,IAC7B,GAAG;AAAA,IACH,SAAS,QAAQ,WAAW,SAAS;AAAA,IACrC,aAAa,QAAQ,eAAe,SAAS;AAAA,IAC7C,MAAM,QAAQ,QAAQ,SAAS;AAAA,IAC/B,eAAe;AAAA,EACjB;AAEA,SAAO;AACT;AAEA,IAAO,sBAAQ;AAMf,IAAI,OAAO,WAAW,eAAe,OAAO,SAAS;AACnD,SAAO,UAAU;AACjB,SAAO,QAAQ,UAAU;AAC3B;","names":[]}
@@ -10,11 +10,15 @@ function withAllStak(config, options = {}) {
10
10
  release: options.release ?? existing.release,
11
11
  environment: options.environment ?? existing.environment,
12
12
  dist: options.dist ?? existing.dist,
13
- pluginVersion: "0.3.4"
13
+ pluginVersion: "0.3.1"
14
14
  };
15
15
  return next;
16
16
  }
17
17
  var expo_plugin_default = withAllStak;
18
+ if (typeof module !== "undefined" && module.exports) {
19
+ module.exports = withAllStak;
20
+ module.exports.default = withAllStak;
21
+ }
18
22
  export {
19
23
  expo_plugin_default as default
20
24
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/expo-plugin.ts"],"sourcesContent":["/**\n * Expo config plugin.\n *\n * Apply via `app.json`:\n *\n * {\n * \"expo\": {\n * \"plugins\": [\n * [\"@allstak/react-native\", { \"release\": \"mobile@1.2.3\", \"environment\": \"production\" }]\n * ]\n * }\n * }\n *\n * The plugin runs at `expo prebuild` / EAS build time and:\n *\n * 1. Adds the AllStak iOS pod (declared in `react-native.config.js`).\n * Expo's autolinking already picks this up — we just record metadata\n * so `expo doctor` can verify the install is wired.\n * 2. Stamps the chosen `release` into `expo-constants` extras so the JS\n * layer can read it at runtime via `Constants.expoConfig.extra._allstak`\n * without the host app needing to plumb it through env vars.\n * 3. Records that `@allstak/react-native` was loaded as an Expo plugin\n * for diagnostics.\n *\n * Pure config mutation — no native code is patched here. The native iOS\n * + Android modules under ./native are linked by Expo's existing\n * autolinking flow (which honors the `react-native.config.js` manifest).\n */\n\nexport interface AllStakExpoOptions {\n /** Release identifier (e.g. `mobile@1.2.3`). Stamped into `Constants.expoConfig.extra._allstak.release`. */\n release?: string;\n /** Environment label — `production`, `staging`, etc. */\n environment?: string;\n /** Optional dist tag (e.g. `ios-hermes`). Auto-detected at runtime if unset. */\n dist?: string;\n}\n\ninterface ExpoConfig {\n name?: string;\n extra?: Record<string, unknown>;\n plugins?: any[];\n [key: string]: any;\n}\n\ninterface ExpoConfigContext {\n modResults?: any;\n modRawConfig?: ExpoConfig;\n [key: string]: any;\n}\n\n/**\n * The plugin function itself. Expo's plugin runner calls it as\n * `(config, options) => modifiedConfig`. We avoid importing\n * `@expo/config-plugins` so the package has zero hard dependencies on the\n * Expo toolchain — the type checking happens at usage site instead.\n */\nfunction withAllStak(config: ExpoConfig & ExpoConfigContext, options: AllStakExpoOptions = {}): ExpoConfig {\n const next: ExpoConfig = { ...config };\n next.extra = { ...(config.extra ?? {}) };\n\n // Embed runtime-readable metadata under a namespaced key so we never\n // collide with the host app's other extras.\n const existing = (next.extra as any)._allstak ?? {};\n (next.extra as any)._allstak = {\n ...existing,\n release: options.release ?? existing.release,\n environment: options.environment ?? existing.environment,\n dist: options.dist ?? existing.dist,\n pluginVersion: '0.3.4',\n };\n\n return next;\n}\n\nexport default withAllStak;\n"],"mappings":";;;AAyDA,SAAS,YAAY,QAAwC,UAA8B,CAAC,GAAe;AACzG,QAAM,OAAmB,EAAE,GAAG,OAAO;AACrC,OAAK,QAAQ,EAAE,GAAI,OAAO,SAAS,CAAC,EAAG;AAIvC,QAAM,WAAY,KAAK,MAAc,YAAY,CAAC;AAClD,EAAC,KAAK,MAAc,WAAW;AAAA,IAC7B,GAAG;AAAA,IACH,SAAS,QAAQ,WAAW,SAAS;AAAA,IACrC,aAAa,QAAQ,eAAe,SAAS;AAAA,IAC7C,MAAM,QAAQ,QAAQ,SAAS;AAAA,IAC/B,eAAe;AAAA,EACjB;AAEA,SAAO;AACT;AAEA,IAAO,sBAAQ;","names":[]}
1
+ {"version":3,"sources":["../src/expo-plugin.ts"],"sourcesContent":["/**\n * Expo config plugin.\n *\n * Apply via `app.json`:\n *\n * {\n * \"expo\": {\n * \"plugins\": [\n * [\"@allstak/react-native\", { \"release\": \"mobile@1.2.3\", \"environment\": \"production\" }]\n * ]\n * }\n * }\n *\n * The plugin runs at `expo prebuild` / EAS build time and:\n *\n * 1. Adds the AllStak iOS pod (declared in `react-native.config.js`).\n * Expo's autolinking already picks this up — we just record metadata\n * so `expo doctor` can verify the install is wired.\n * 2. Stamps the chosen `release` into `expo-constants` extras so the JS\n * layer can read it at runtime via `Constants.expoConfig.extra._allstak`\n * without the host app needing to plumb it through env vars.\n * 3. Records that `@allstak/react-native` was loaded as an Expo plugin\n * for diagnostics.\n *\n * Pure config mutation — no native code is patched here. The native iOS\n * + Android modules under ./native are linked by Expo's existing\n * autolinking flow (which honors the `react-native.config.js` manifest).\n */\n\nexport interface AllStakExpoOptions {\n /** Release identifier (e.g. `mobile@1.2.3`). Stamped into `Constants.expoConfig.extra._allstak.release`. */\n release?: string;\n /** Environment label — `production`, `staging`, etc. */\n environment?: string;\n /** Optional dist tag (e.g. `ios-hermes`). Auto-detected at runtime if unset. */\n dist?: string;\n}\n\ninterface ExpoConfig {\n name?: string;\n extra?: Record<string, unknown>;\n plugins?: any[];\n [key: string]: any;\n}\n\ninterface ExpoConfigContext {\n modResults?: any;\n modRawConfig?: ExpoConfig;\n [key: string]: any;\n}\n\n/**\n * The plugin function itself. Expo's plugin runner calls it as\n * `(config, options) => modifiedConfig`. We avoid importing\n * `@expo/config-plugins` so the package has zero hard dependencies on the\n * Expo toolchain — the type checking happens at usage site instead.\n */\nfunction withAllStak(config: ExpoConfig & ExpoConfigContext, options: AllStakExpoOptions = {}): ExpoConfig {\n const next: ExpoConfig = { ...config };\n next.extra = { ...(config.extra ?? {}) };\n\n // Embed runtime-readable metadata under a namespaced key so we never\n // collide with the host app's other extras.\n const existing = (next.extra as any)._allstak ?? {};\n (next.extra as any)._allstak = {\n ...existing,\n release: options.release ?? existing.release,\n environment: options.environment ?? existing.environment,\n dist: options.dist ?? existing.dist,\n pluginVersion: '0.3.1',\n };\n\n return next;\n}\n\nexport default withAllStak;\n\n// Expose as a CommonJS function so `app.plugin.js`'s `require('./dist/expo-plugin.js')`\n// returns the plugin directly. The `declare const module` keeps the TS\n// type-checker happy without dragging in @types/node.\ndeclare const module: { exports: any };\nif (typeof module !== 'undefined' && module.exports) {\n module.exports = withAllStak;\n module.exports.default = withAllStak;\n}\n"],"mappings":";;;AAyDA,SAAS,YAAY,QAAwC,UAA8B,CAAC,GAAe;AACzG,QAAM,OAAmB,EAAE,GAAG,OAAO;AACrC,OAAK,QAAQ,EAAE,GAAI,OAAO,SAAS,CAAC,EAAG;AAIvC,QAAM,WAAY,KAAK,MAAc,YAAY,CAAC;AAClD,EAAC,KAAK,MAAc,WAAW;AAAA,IAC7B,GAAG;AAAA,IACH,SAAS,QAAQ,WAAW,SAAS;AAAA,IACrC,aAAa,QAAQ,eAAe,SAAS;AAAA,IAC7C,MAAM,QAAQ,QAAQ,SAAS;AAAA,IAC/B,eAAe;AAAA,EACjB;AAEA,SAAO;AACT;AAEA,IAAO,sBAAQ;AAMf,IAAI,OAAO,WAAW,eAAe,OAAO,SAAS;AACnD,SAAO,UAAU;AACjB,SAAO,QAAQ,UAAU;AAC3B;","names":[]}
package/dist/index.d.mts CHANGED
@@ -33,6 +33,18 @@ declare class HttpTransport {
33
33
  private lastFlushDurationMs;
34
34
  constructor(baseUrl: string, apiKey: string, enabled?: boolean);
35
35
  send(path: string, payload: unknown): Promise<void>;
36
+ /**
37
+ * One-shot POST that resolves with the parsed JSON response body. Used
38
+ * by `captureException` to retrieve the server-assigned event id so
39
+ * follow-up attachment uploads can be linked.
40
+ *
41
+ * Fail-open: returns `null` on any error (network, non-2xx, parse).
42
+ * Respects {@link timeoutMs} via `AbortController`. Bounded retries.
43
+ */
44
+ sendAndRead<T = any>(path: string, payload: unknown, options?: {
45
+ timeoutMs?: number;
46
+ retries?: number;
47
+ }): Promise<T | null>;
36
48
  private enqueueOrDispatch;
37
49
  private dispatch;
38
50
  private doFetch;
@@ -438,9 +450,117 @@ interface ConsoleCaptureOptions {
438
450
  /** @internal — for tests. Resets the wrap-once flag. */
439
451
  declare function __resetConsoleInstrumentationFlagForTest(): void;
440
452
 
453
+ /**
454
+ * Runtime detection for React Native screenshot capture.
455
+ *
456
+ * Determines whether native screenshot APIs (specifically
457
+ * `react-native-view-shot`) are usable in the current host:
458
+ * - 'expo-go' — Expo Go sandbox; native modules cannot be added
459
+ * at runtime → screenshots silently skipped.
460
+ * - 'expo-dev-client'— Expo dev/prod build that bundles native deps.
461
+ * - 'rn-cli' — bare react-native (CLI) build.
462
+ * - 'unknown' — anything we couldn't classify; treat as allowed
463
+ * but log a __DEV__ warning.
464
+ */
465
+ type RuntimeMode = 'expo-go' | 'expo-dev-client' | 'rn-cli' | 'unknown';
466
+ /**
467
+ * Best-effort detection of the host runtime. Cached after first call.
468
+ */
469
+ declare function detectRuntimeMode(): RuntimeMode;
470
+ /** @internal — reset for tests */
471
+ declare function __resetRuntimeModeForTest(): void;
472
+ /** Whether a runtime allows attempting a native screenshot via view-shot. */
473
+ declare function runtimeAllowsScreenshot(mode?: RuntimeMode): boolean;
474
+
475
+ /**
476
+ * Flat screenshot API for `@allstak/react-native`.
477
+ *
478
+ * Mirrors the props the wizard emits (`captureScreenshotOnError`,
479
+ * `screenshotRedaction`, etc.) and orchestrates native capture via the
480
+ * optional `react-native-view-shot` peer dep.
481
+ *
482
+ * Fail-open contract: the event MUST always send even when capture is
483
+ * unavailable, throws, times out, or the upload fails. The capture
484
+ * subsystem is allowed to log in __DEV__ but must never reject into the
485
+ * host app's promise chain.
486
+ */
487
+
488
+ type ScreenshotRedactionMode = 'strict' | 'balanced' | 'custom';
489
+ type ScreenshotMaskStyle = 'solid' | 'blur';
490
+ type ScreenshotFormat = 'png' | 'jpg' | 'webp';
491
+ type ScreenshotNativeMode = 'auto' | 'view-shot' | 'disabled';
492
+ type ScreenshotFailPolicy = 'disable-screenshot' | 'send-event-only';
493
+ interface ScreenshotConfig {
494
+ captureScreenshotOnError: boolean;
495
+ screenshotRedaction: ScreenshotRedactionMode;
496
+ screenshotMaskStyle: ScreenshotMaskStyle;
497
+ screenshotMaxBytes: number;
498
+ screenshotQuality: number;
499
+ screenshotFormat: ScreenshotFormat;
500
+ screenshotSampleRate: number;
501
+ screenshotOnUnhandledOnly: boolean;
502
+ screenshotUploadTimeoutMs: number;
503
+ screenshotCaptureTimeoutMs: number;
504
+ screenshotNativeMode: ScreenshotNativeMode;
505
+ screenshotFailPolicy: ScreenshotFailPolicy;
506
+ beforeScreenshotCapture?: (ctx: ScreenshotContext) => boolean | Promise<boolean>;
507
+ beforeScreenshotUpload?: ((payload: ScreenshotUpload, meta: ScreenshotMetadata) => ScreenshotUpload | null | Promise<ScreenshotUpload | null>);
508
+ isScreenshotAllowed?: (ctx: ScreenshotContext) => boolean | Promise<boolean>;
509
+ }
510
+ interface ScreenshotContext {
511
+ error?: Error;
512
+ unhandled?: boolean;
513
+ runtimeMode: RuntimeMode;
514
+ }
515
+ interface ScreenshotUpload {
516
+ /** Base64-encoded image bytes (no data: prefix). */
517
+ dataBase64: string;
518
+ contentType: string;
519
+ width: number;
520
+ height: number;
521
+ sizeBytes: number;
522
+ }
523
+ interface ScreenshotMetadata {
524
+ captureMethod: string;
525
+ redactionMode: ScreenshotRedactionMode;
526
+ maskStyle: ScreenshotMaskStyle;
527
+ format: ScreenshotFormat;
528
+ width: number;
529
+ height: number;
530
+ sizeBytes: number;
531
+ maskedElements?: number;
532
+ privacyComponentsDetected?: number;
533
+ runtimeMode: RuntimeMode;
534
+ }
535
+ declare const DEFAULT_SCREENSHOT_CONFIG: ScreenshotConfig;
536
+ /**
537
+ * Merge partial config from `init()` / provider props with sensible
538
+ * defaults. Any `undefined` field falls back to the default. Numeric
539
+ * fields are clamped to safe ranges.
540
+ */
541
+ declare function resolveScreenshotConfig(partial: Partial<ScreenshotConfig> | undefined): ScreenshotConfig;
542
+ declare function isViewShotAvailable(): boolean;
543
+ /**
544
+ * Attempt a native screenshot via react-native-view-shot. Returns
545
+ * `null` if anything goes wrong (timeout, missing dep, no root ref).
546
+ * Never throws. Toggles `__setCapturing(true)` for the duration of the
547
+ * capture so masking primitives swap to their placeholder render.
548
+ */
549
+ declare function captureViaViewShot(config: ScreenshotConfig): Promise<ScreenshotUpload | null>;
550
+ /**
551
+ * Top-level orchestrator. Returns an upload + metadata if a screenshot
552
+ * is captured, or `null` if anything (sampling, gates, capture, etc.)
553
+ * skipped it. Never throws.
554
+ */
555
+ declare function maybeCaptureScreenshot(config: ScreenshotConfig, ctx: ScreenshotContext): Promise<{
556
+ upload: ScreenshotUpload;
557
+ metadata: ScreenshotMetadata;
558
+ } | null>;
559
+ declare function pickScreenshotConfig(source: Record<string, unknown>): Partial<ScreenshotConfig>;
560
+
441
561
  declare const INGEST_HOST = "https://api.allstak.sa";
442
562
  declare const SDK_NAME = "allstak-react-native";
443
- declare const SDK_VERSION = "0.3.4";
563
+ declare const SDK_VERSION = "0.4.1";
444
564
 
445
565
  interface AllStakConfig {
446
566
  /** Project API key (`ask_live_…`). Required. */
@@ -518,6 +638,22 @@ interface AllStakConfig {
518
638
  * hurt app navigation, JS thread responsiveness, or telemetry delivery.
519
639
  */
520
640
  screenshot?: ScreenshotCaptureOptions;
641
+ /** Enable on-error screenshot capture. Default: false. */
642
+ captureScreenshotOnError?: boolean;
643
+ screenshotRedaction?: ScreenshotRedactionMode;
644
+ screenshotMaskStyle?: ScreenshotMaskStyle;
645
+ screenshotMaxBytes?: number;
646
+ screenshotQuality?: number;
647
+ screenshotFormat?: ScreenshotFormat;
648
+ screenshotSampleRate?: number;
649
+ screenshotOnUnhandledOnly?: boolean;
650
+ screenshotUploadTimeoutMs?: number;
651
+ screenshotCaptureTimeoutMs?: number;
652
+ screenshotNativeMode?: ScreenshotNativeMode;
653
+ screenshotFailPolicy?: ScreenshotFailPolicy;
654
+ beforeScreenshotCapture?: ScreenshotConfig['beforeScreenshotCapture'];
655
+ beforeScreenshotUpload?: ScreenshotConfig['beforeScreenshotUpload'];
656
+ isScreenshotAllowed?: ScreenshotConfig['isScreenshotAllowed'];
521
657
  /** SDK identity overrides (set automatically by installReactNative). */
522
658
  sdkName?: string;
523
659
  sdkVersion?: string;
@@ -617,6 +753,14 @@ declare class AllStakClient {
617
753
  /** Snapshot of recent failed HTTP requests for error-linking. */
618
754
  getRecentFailedHttp(): readonly HttpRequestIngestItem[];
619
755
  captureException(error: Error, context?: Record<string, unknown>): void;
756
+ /**
757
+ * Flat screenshot API path. Capture first (so masking primitives can
758
+ * swap), send the event with a `screenshot.status` metadata tag, read
759
+ * back the server-assigned errorId, then upload the attachment.
760
+ *
761
+ * Fail-open at every step.
762
+ */
763
+ private runFlatScreenshotPipeline;
620
764
  /** Start a new span. Auto-parented to any currently-active span. */
621
765
  startSpan(operation: string, options?: {
622
766
  description?: string;
@@ -842,8 +986,23 @@ interface AllStakProviderProps extends ReactNativeInstallOptions {
842
986
  resetError: () => void;
843
987
  }) => React.ReactNode);
844
988
  onError?: (error: Error, componentStack?: string) => void;
989
+ captureScreenshotOnError?: boolean;
990
+ screenshotRedaction?: ScreenshotRedactionMode;
991
+ screenshotMaskStyle?: ScreenshotMaskStyle;
992
+ screenshotMaxBytes?: number;
993
+ screenshotQuality?: number;
994
+ screenshotFormat?: ScreenshotFormat;
995
+ screenshotSampleRate?: number;
996
+ screenshotOnUnhandledOnly?: boolean;
997
+ screenshotUploadTimeoutMs?: number;
998
+ screenshotCaptureTimeoutMs?: number;
999
+ screenshotNativeMode?: ScreenshotNativeMode;
1000
+ screenshotFailPolicy?: ScreenshotFailPolicy;
1001
+ beforeScreenshotCapture?: AllStakConfig['beforeScreenshotCapture'];
1002
+ beforeScreenshotUpload?: AllStakConfig['beforeScreenshotUpload'];
1003
+ isScreenshotAllowed?: AllStakConfig['isScreenshotAllowed'];
845
1004
  }
846
- declare function AllStakProvider({ children, apiKey, environment, release, host, user, tags, debug, enableHttpTracking, httpTracking, captureConsole, sampleRate, beforeSend, replay, tracesSampleRate, service, dist, destroyOnUnmount, fallback, onError, autoErrorHandler, autoPromiseRejections, autoDeviceTags, autoAppStateBreadcrumbs, autoNetworkCapture, autoFetchBreadcrumbs, autoConsoleBreadcrumbs, autoNavigationBreadcrumbs, }: AllStakProviderProps): React.ReactElement;
1005
+ declare function AllStakProvider({ children, apiKey, environment, release, host, user, tags, debug, enableHttpTracking, httpTracking, captureConsole, sampleRate, beforeSend, replay, tracesSampleRate, service, dist, destroyOnUnmount, fallback, onError, autoErrorHandler, autoPromiseRejections, autoDeviceTags, autoAppStateBreadcrumbs, autoNetworkCapture, autoFetchBreadcrumbs, autoConsoleBreadcrumbs, autoNavigationBreadcrumbs, captureScreenshotOnError, screenshotRedaction, screenshotMaskStyle, screenshotMaxBytes, screenshotQuality, screenshotFormat, screenshotSampleRate, screenshotOnUnhandledOnly, screenshotUploadTimeoutMs, screenshotCaptureTimeoutMs, screenshotNativeMode, screenshotFailPolicy, beforeScreenshotCapture, beforeScreenshotUpload, isScreenshotAllowed, }: AllStakProviderProps): React.ReactElement;
847
1006
  declare function useAllStak(): {
848
1007
  captureException: (error: Error, ctx?: Record<string, unknown>) => void;
849
1008
  captureMessage: (msg: string, level?: "fatal" | "error" | "warning" | "info") => void;
@@ -857,6 +1016,81 @@ declare function useAllStak(): {
857
1016
  /** @internal — for tests. Resets the module-level remount-guard. */
858
1017
  declare function __resetProviderInstanceForTest(): void;
859
1018
 
1019
+ /**
1020
+ * Privacy / masking primitives for AllStak React Native screenshots.
1021
+ *
1022
+ * Three concerns:
1023
+ * 1. A registry of refs / views the capture path should mask when it
1024
+ * takes a snapshot.
1025
+ * 2. An "isCapturing" flag that masking components subscribe to so they
1026
+ * can swap their children for a redacted placeholder during a capture.
1027
+ * 3. Light wrappers (`AllStakMaskedView`, `AllStakPrivacyView`,
1028
+ * `AllStakTextInput`, `AllStakSensitiveText`) that opt-in to the
1029
+ * isCapturing swap.
1030
+ *
1031
+ * All wrappers are no-ops when react-native is not installed (e.g. JS
1032
+ * test environment) — they just render their children.
1033
+ */
1034
+
1035
+ type PrivacyLevel = 'mask' | 'hide' | 'show';
1036
+ /** @internal — for tests. */
1037
+ declare function __resetPrivacyStateForTest(): void;
1038
+ /** Whether a capture is currently in progress. */
1039
+ declare function isCapturingScreenshot(): boolean;
1040
+ /** Register a sensitive ref. Returns an unregister function. */
1041
+ declare function registerSensitiveRef(ref: unknown): () => void;
1042
+ /**
1043
+ * Hook returning the privacy state — host apps can build their own
1044
+ * masking UI on top of this.
1045
+ */
1046
+ declare function useAllStakPrivacy(): {
1047
+ isCapturing: boolean;
1048
+ registerSensitiveRef: typeof registerSensitiveRef;
1049
+ };
1050
+ interface AllStakMaskedViewProps {
1051
+ children?: React.ReactNode;
1052
+ maskLabel?: string;
1053
+ maskColor?: string;
1054
+ hideScreenshot?: boolean;
1055
+ privacy?: PrivacyLevel;
1056
+ style?: any;
1057
+ [key: string]: unknown;
1058
+ }
1059
+ /**
1060
+ * Masks its children during a screenshot capture. While the SDK is
1061
+ * capturing, this component renders a solid placeholder instead of its
1062
+ * children; the rest of the time it's a transparent passthrough.
1063
+ */
1064
+ declare function AllStakMaskedView({ children, maskLabel, maskColor, hideScreenshot, privacy, style, ...rest }: AllStakMaskedViewProps): React.ReactElement;
1065
+ /**
1066
+ * Stricter variant — defaults to hiding the contents from screenshots
1067
+ * entirely. Use for credit-card fields, IBANs, passwords, etc.
1068
+ */
1069
+ declare function AllStakPrivacyView(props: AllStakMaskedViewProps): React.ReactElement;
1070
+ interface AllStakTextInputProps {
1071
+ privacy?: PrivacyLevel;
1072
+ style?: any;
1073
+ maskColor?: string;
1074
+ [key: string]: unknown;
1075
+ }
1076
+ /**
1077
+ * `TextInput` wrapper that swaps to a solid masked box during capture.
1078
+ * Always treated as sensitive unless `privacy="show"`.
1079
+ */
1080
+ declare function AllStakTextInput({ privacy, style, maskColor, ...rest }: AllStakTextInputProps): React.ReactElement;
1081
+ interface AllStakSensitiveTextProps {
1082
+ children?: React.ReactNode;
1083
+ privacy?: PrivacyLevel;
1084
+ style?: any;
1085
+ maskLabel?: string;
1086
+ [key: string]: unknown;
1087
+ }
1088
+ /**
1089
+ * Wraps a Text element so it renders the mask label during capture
1090
+ * instead of its actual children. Default privacy is `'mask'`.
1091
+ */
1092
+ declare function AllStakSensitiveText({ children, privacy, style, maskLabel, ...rest }: AllStakSensitiveTextProps): React.ReactElement;
1093
+
860
1094
  /**
861
1095
  * React Native navigation breadcrumbs — two opt-in helpers:
862
1096
  *
@@ -1029,4 +1263,4 @@ declare function __devTriggerNativeCrash(): Promise<void>;
1029
1263
  */
1030
1264
  declare function drainPendingNativeCrashes(release?: string): Promise<void>;
1031
1265
 
1032
- export { ALWAYS_REDACT_HEADERS, ALWAYS_REDACT_QUERY, AllStak, AllStakClient, type AllStakConfig, AllStakProvider, type AllStakProviderProps, type ArchitectureInfo, type Breadcrumb, type ConsoleCaptureOptions, DEFAULT_REDACT_BODY_FIELDS, type HttpRequestEvent, HttpRequestModule, type HttpTrackingOptions, INGEST_HOST, REDACTED, type ReactNativeInstallOptions, ReplaySurrogate, type ReplaySurrogateOptions, SDK_NAME, SDK_VERSION, Scope, type ScreenshotArtifact, type ScreenshotCaptureOptions, type TransportStats, __devTriggerNativeCrash, __resetAutoNavigationFlagForTest, __resetConsoleInstrumentationFlagForTest, __resetProviderInstanceForTest, __setNativeModuleForTest, applyArchitectureTags, captureBodyResult, detectArchitecture, drainPendingNativeCrashes, installReactNative, instrumentNavigationFromLinking, instrumentReactNavigation, redactUrl, sanitizeHeaders, tryAutoInstrumentNavigation, useAllStak };
1266
+ export { ALWAYS_REDACT_HEADERS, ALWAYS_REDACT_QUERY, AllStak, AllStakClient, type AllStakConfig, AllStakMaskedView, type AllStakMaskedViewProps, AllStakPrivacyView, AllStakProvider, type AllStakProviderProps, AllStakSensitiveText, type AllStakSensitiveTextProps, AllStakTextInput, type AllStakTextInputProps, type ArchitectureInfo, type Breadcrumb, type ConsoleCaptureOptions, DEFAULT_REDACT_BODY_FIELDS, DEFAULT_SCREENSHOT_CONFIG, type HttpRequestEvent, HttpRequestModule, type HttpTrackingOptions, INGEST_HOST, type PrivacyLevel, REDACTED, type ReactNativeInstallOptions, ReplaySurrogate, type ReplaySurrogateOptions, type RuntimeMode, SDK_NAME, SDK_VERSION, Scope, type ScreenshotArtifact, type ScreenshotCaptureOptions, type ScreenshotConfig, type ScreenshotContext, type ScreenshotFailPolicy, type ScreenshotFormat, type ScreenshotMaskStyle, type ScreenshotMetadata, type ScreenshotNativeMode, type ScreenshotRedactionMode, type ScreenshotUpload, type TransportStats, __devTriggerNativeCrash, __resetAutoNavigationFlagForTest, __resetConsoleInstrumentationFlagForTest, __resetPrivacyStateForTest, __resetProviderInstanceForTest, __resetRuntimeModeForTest, __setNativeModuleForTest, applyArchitectureTags, captureBodyResult, captureViaViewShot, detectArchitecture, detectRuntimeMode, drainPendingNativeCrashes, installReactNative, instrumentNavigationFromLinking, instrumentReactNavigation, isCapturingScreenshot, isViewShotAvailable, maybeCaptureScreenshot, pickScreenshotConfig, redactUrl, registerSensitiveRef, resolveScreenshotConfig, runtimeAllowsScreenshot, sanitizeHeaders, tryAutoInstrumentNavigation, useAllStak, useAllStakPrivacy };