@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.
- package/CHANGELOG.md +53 -0
- package/README.md +398 -95
- package/app.plugin.js +1 -3
- package/build-hooks/upload-sourcemaps.js +0 -15
- package/dist/build/sourcemaps.js +2 -18
- package/dist/build/sourcemaps.js.map +1 -1
- package/dist/build/sourcemaps.mjs +4 -20
- package/dist/build/sourcemaps.mjs.map +1 -1
- package/dist/expo-plugin.js +5 -1
- package/dist/expo-plugin.js.map +1 -1
- package/dist/expo-plugin.mjs +5 -1
- package/dist/expo-plugin.mjs.map +1 -1
- package/dist/index.d.mts +237 -3
- package/dist/index.d.ts +237 -3
- package/dist/index.js +578 -18
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +578 -18
- package/dist/index.mjs.map +1 -1
- package/package.json +7 -3
package/dist/build/sourcemaps.js
CHANGED
|
@@ -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
|
|
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":"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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":[]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/build/sourcemaps.ts
|
|
2
|
-
import {
|
|
2
|
+
import { readFileSync, writeFileSync } from "fs";
|
|
3
3
|
import { randomUUID, createHash } from "crypto";
|
|
4
|
-
import { basename
|
|
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
|
|
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":";AAiCA,SAAS,YAAY,cAAc,qBAAqB;AACxD,SAAS,YAAY,kBAAkB;AACvC,SAAS,UAAU,eAAe;AAE3B,IAAM,eAAe;AAE5B,IAAM,mBAAmB;AA2CzB,SAAS,KAAK,KAAqB;AACjC,SAAO,WAAW,QAAQ,EAAE,OAAO,GAAG,EAAE,OAAO,KAAK,EAAE,MAAM,GAAG,CAAC;AAClE;AAMO,SAAS,2BACd,MACsC;AACtC,QAAM,YAAY,aAAa,KAAK,QAAQ,MAAM;AAClD,QAAM,SAAS,aAAa,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,WAAU,WAAW;AAGnC,MAAI,UAAU;AACd,gBAAc,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,gBAAc,KAAK,QAAQ,SAAS;AAEpC,SAAO,EAAE,SAAS,OAAO;AAC3B;AAEA,eAAe,UACb,MACA,UACA,SACA,SACA,MACA,OACA,MACA,cACwD;AACxD,MAAI,MAAM,aAAa,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,IACD,SAAS,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,WAAW,SAAS,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,KAAK,aAAa,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,KAAK,aAAa,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,OAAO,QAAQ,QAAQ,IAAI,GAAG,IAAI;AACxC,QAAI,CAAC,WAAW,IAAI,EAAG;AACvB,eAAW,QAAQ,aAAa,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":[]}
|
package/dist/expo-plugin.js
CHANGED
|
@@ -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.
|
|
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
|
package/dist/expo-plugin.js.map
CHANGED
|
@@ -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.
|
|
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":[]}
|
package/dist/expo-plugin.mjs
CHANGED
|
@@ -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.
|
|
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
|
};
|
package/dist/expo-plugin.mjs.map
CHANGED
|
@@ -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.
|
|
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.
|
|
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 };
|