@allstak/react-native 0.3.3 → 0.3.4

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.
@@ -36,6 +36,7 @@
36
36
  const fs = require('node:fs');
37
37
  const path = require('node:path');
38
38
 
39
+ loadDotEnv();
39
40
  const args = parseArgs(process.argv.slice(2));
40
41
 
41
42
  const RELEASE = process.env.ALLSTAK_RELEASE;
@@ -173,3 +174,17 @@ function guessDist(platform) {
173
174
  if (platform === 'android') return 'android-hermes';
174
175
  return platform;
175
176
  }
177
+
178
+ function loadDotEnv() {
179
+ for (const file of ['.env.local', '.env']) {
180
+ const full = path.resolve(process.cwd(), file);
181
+ if (!fs.existsSync(full)) continue;
182
+ for (const line of fs.readFileSync(full, 'utf8').split(/\r?\n/)) {
183
+ const trimmed = line.trim();
184
+ if (!trimmed || trimmed.startsWith('#')) continue;
185
+ const match = /^([A-Z0-9_]+)\s*=\s*(.*)$/.exec(trimmed);
186
+ if (!match || process.env[match[1]] !== undefined) continue;
187
+ process.env[match[1]] = match[2].replace(/^['"]|['"]$/g, '');
188
+ }
189
+ }
190
+ }
@@ -83,14 +83,15 @@ 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 token = opts.token ?? process.env.ALLSTAK_UPLOAD_TOKEN;
86
+ const env = loadAllStakEnv();
87
+ const token = opts.token ?? env.ALLSTAK_UPLOAD_TOKEN;
87
88
  if (opts.injectOnly || !token) {
88
89
  if (!opts.injectOnly && !token) {
89
90
  log("skipping upload \u2014 no token (set ALLSTAK_UPLOAD_TOKEN or pass `token`)");
90
91
  }
91
92
  return inject;
92
93
  }
93
- const host = opts.host ?? process.env.ALLSTAK_HOST ?? DEFAULT_HOST;
94
+ const host = opts.host ?? env.ALLSTAK_HOST ?? DEFAULT_HOST;
94
95
  const stripSources = opts.stripSources ?? false;
95
96
  const steps = [];
96
97
  const mapResult = await uploadOne(
@@ -133,6 +134,21 @@ async function uploadReactNativeSourcemap(opts) {
133
134
  }
134
135
  return { ...inject, uploaded: allOk, steps };
135
136
  }
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
+ }
136
152
  // Annotate the CommonJS export names for ESM import in node:
137
153
  0 && (module.exports = {
138
154
  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 { 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
+ {"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,7 +1,7 @@
1
1
  // src/build/sourcemaps.ts
2
- import { readFileSync, writeFileSync } from "fs";
2
+ import { existsSync, readFileSync, writeFileSync } from "fs";
3
3
  import { randomUUID, createHash } from "crypto";
4
- import { basename } from "path";
4
+ import { basename, resolve } 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,14 +57,15 @@ 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 token = opts.token ?? process.env.ALLSTAK_UPLOAD_TOKEN;
60
+ const env = loadAllStakEnv();
61
+ const token = opts.token ?? env.ALLSTAK_UPLOAD_TOKEN;
61
62
  if (opts.injectOnly || !token) {
62
63
  if (!opts.injectOnly && !token) {
63
64
  log("skipping upload \u2014 no token (set ALLSTAK_UPLOAD_TOKEN or pass `token`)");
64
65
  }
65
66
  return inject;
66
67
  }
67
- const host = opts.host ?? process.env.ALLSTAK_HOST ?? DEFAULT_HOST;
68
+ const host = opts.host ?? env.ALLSTAK_HOST ?? DEFAULT_HOST;
68
69
  const stripSources = opts.stripSources ?? false;
69
70
  const steps = [];
70
71
  const mapResult = await uploadOne(
@@ -107,6 +108,21 @@ async function uploadReactNativeSourcemap(opts) {
107
108
  }
108
109
  return { ...inject, uploaded: allOk, steps };
109
110
  }
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
+ }
110
126
  export {
111
127
  DEFAULT_HOST,
112
128
  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 { 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":";AAiCA,SAAS,cAAc,qBAAqB;AAC5C,SAAS,YAAY,kBAAkB;AACvC,SAAS,gBAAgB;AAElB,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,QAAQ,KAAK,SAAS,QAAQ,IAAI;AACxC,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,QAAQ,IAAI,gBAAgB;AACtD,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;","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 { 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":[]}
@@ -32,7 +32,7 @@ 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.3"
35
+ pluginVersion: "0.3.4"
36
36
  };
37
37
  return next;
38
38
  }
@@ -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.3',\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.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":[]}
@@ -10,7 +10,7 @@ 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.3"
13
+ pluginVersion: "0.3.4"
14
14
  };
15
15
  return next;
16
16
  }
@@ -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.3',\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.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":[]}
package/dist/index.d.mts CHANGED
@@ -440,7 +440,7 @@ declare function __resetConsoleInstrumentationFlagForTest(): void;
440
440
 
441
441
  declare const INGEST_HOST = "https://api.allstak.sa";
442
442
  declare const SDK_NAME = "allstak-react-native";
443
- declare const SDK_VERSION = "0.3.3";
443
+ declare const SDK_VERSION = "0.3.4";
444
444
 
445
445
  interface AllStakConfig {
446
446
  /** Project API key (`ask_live_…`). Required. */
package/dist/index.d.ts CHANGED
@@ -440,7 +440,7 @@ declare function __resetConsoleInstrumentationFlagForTest(): void;
440
440
 
441
441
  declare const INGEST_HOST = "https://api.allstak.sa";
442
442
  declare const SDK_NAME = "allstak-react-native";
443
- declare const SDK_VERSION = "0.3.3";
443
+ declare const SDK_VERSION = "0.3.4";
444
444
 
445
445
  interface AllStakConfig {
446
446
  /** Project API key (`ask_live_…`). Required. */
package/dist/index.js CHANGED
@@ -1446,7 +1446,7 @@ function unbindHttpInstrumentation() {
1446
1446
  // src/client.ts
1447
1447
  var INGEST_HOST = "https://api.allstak.sa";
1448
1448
  var SDK_NAME = "allstak-react-native";
1449
- var SDK_VERSION = "0.3.3";
1449
+ var SDK_VERSION = "0.3.4";
1450
1450
  var ERRORS_PATH = "/ingest/v1/errors";
1451
1451
  var LOGS_PATH = "/ingest/v1/logs";
1452
1452
  var VALID_BREADCRUMB_TYPES = /* @__PURE__ */ new Set(["http", "log", "ui", "navigation", "query", "default"]);