@allstak/react-native 0.1.3 → 0.3.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.
@@ -0,0 +1,132 @@
1
+ // AllStak source-map upload — Gradle helper.
2
+ //
3
+ // Drop this file into your Android project at:
4
+ // allstak-react-native/build-hooks/allstak-sourcemaps.gradle
5
+ // (or any path you like — just point apply-from at it)
6
+ //
7
+ // Then add ONE line to android/app/build.gradle, after the
8
+ // react-native-gradle-plugin apply at the top:
9
+ //
10
+ // apply from: file('../node_modules/@allstak/react-native/build-hooks/allstak-sourcemaps.gradle')
11
+ //
12
+ // Set these in ~/.gradle/gradle.properties or as `-PallstakRelease=…`
13
+ // on the command line:
14
+ //
15
+ // allstakRelease=mobile@1.2.3+5
16
+ // allstakUploadToken=aspk_... # only required for actual upload
17
+ // allstakHost=https://api.allstak.sa # optional override
18
+ //
19
+ // What it does:
20
+ // • Hooks every variant whose name ends with "Release" (e.g.
21
+ // bundleRelease, assembleRelease) and runs the AllStak uploader
22
+ // on the Metro bundle + sourcemap that variant produced.
23
+ // • Falls back to inject-only mode when no upload token is set.
24
+ // • Never fails the build over a sourcemap glitch — logs and moves on.
25
+ //
26
+ // Output path conventions (Metro/Hermes default):
27
+ // build/generated/assets/createBundleReleaseJsAndAssets/index.android.bundle
28
+ // build/generated/sourcemaps/react/release/index.android.bundle.map
29
+
30
+ import org.gradle.api.tasks.Exec
31
+ import java.nio.file.Files
32
+ import java.nio.file.Paths
33
+
34
+ afterEvaluate {
35
+ android.applicationVariants.all { variant ->
36
+ def variantName = variant.name
37
+ if (!variantName.toLowerCase().endsWith('release')) return
38
+
39
+ def capitalized = variantName.capitalize()
40
+ // The bundle/sourcemap-emitting tasks are created by the React
41
+ // Native gradle plugin during configuration. Hook them by name
42
+ // so we run *after* the bundle is on disk.
43
+ def bundleTaskName = "createBundle${capitalized}JsAndAssets"
44
+ def parent = tasks.findByName(bundleTaskName)
45
+ if (parent == null) {
46
+ project.logger.lifecycle("[allstak] no ${bundleTaskName} task found — skipping sourcemap upload for ${variantName}")
47
+ return
48
+ }
49
+
50
+ def uploadTask = tasks.create("allstakUpload${capitalized}Sourcemaps", Exec) {
51
+ group = 'AllStak'
52
+ description = "Inject debug-id and (optionally) upload Hermes/Metro sourcemap for the ${variantName} variant."
53
+
54
+ // Metro+Hermes paths the React Native gradle plugin uses by default.
55
+ // We accept the conventional defaults; users can override via project props.
56
+ def jsBundleDir = project.findProperty('allstakBundleDir') ?:
57
+ file("${buildDir}/generated/assets/${bundleTaskName}").absolutePath
58
+ def mapDir = project.findProperty('allstakMapDir') ?:
59
+ file("${buildDir}/generated/sourcemaps/react/${variantName}").absolutePath
60
+
61
+ // Pick the right pair (Hermes emits .bundle + .bundle.map after compose).
62
+ doFirst {
63
+ def bundleCandidates = [
64
+ Paths.get(jsBundleDir.toString(), 'index.android.bundle'),
65
+ Paths.get(jsBundleDir.toString(), 'index.android.jsbundle'),
66
+ ]
67
+ def mapCandidates = [
68
+ Paths.get(mapDir.toString(), 'index.android.bundle.map'),
69
+ Paths.get(mapDir.toString(), 'index.android.bundle.composed.map'),
70
+ ]
71
+ def bundle = bundleCandidates.find { Files.exists(it) }
72
+ def mapFile = mapCandidates.find { Files.exists(it) }
73
+
74
+ if (bundle == null || mapFile == null) {
75
+ project.logger.warn("[allstak] no bundle/sourcemap pair found for ${variantName}: " +
76
+ "tried ${bundleCandidates}, ${mapCandidates}")
77
+ enabled = false
78
+ return
79
+ }
80
+
81
+ def releaseProp = project.findProperty('allstakRelease') ?:
82
+ System.getenv('ALLSTAK_RELEASE')
83
+ def tokenProp = project.findProperty('allstakUploadToken') ?:
84
+ System.getenv('ALLSTAK_UPLOAD_TOKEN')
85
+ def hostProp = project.findProperty('allstakHost') ?:
86
+ System.getenv('ALLSTAK_HOST')
87
+ def distProp = project.findProperty('allstakDist') ?:
88
+ System.getenv('ALLSTAK_DIST_OVERRIDE') ?:
89
+ 'android-hermes'
90
+
91
+ if (releaseProp == null) {
92
+ project.logger.warn("[allstak] no allstakRelease / ALLSTAK_RELEASE set — skipping sourcemap step")
93
+ enabled = false
94
+ return
95
+ }
96
+
97
+ // Resolve the helper script that ships inside the SDK package.
98
+ def hookScript = project.findProperty('allstakHookScript') ?:
99
+ project.rootDir.toPath()
100
+ .resolve('../node_modules/@allstak/react-native/build-hooks/upload-sourcemaps.js')
101
+ .normalize()
102
+ .toString()
103
+
104
+ if (!Files.exists(Paths.get(hookScript))) {
105
+ project.logger.warn("[allstak] hook script not found at ${hookScript} — is @allstak/react-native installed?")
106
+ enabled = false
107
+ return
108
+ }
109
+
110
+ environment 'ALLSTAK_RELEASE', releaseProp
111
+ if (tokenProp) environment 'ALLSTAK_UPLOAD_TOKEN', tokenProp
112
+ if (hostProp) environment 'ALLSTAK_HOST', hostProp
113
+
114
+ commandLine 'node', hookScript,
115
+ '--bundle', bundle.toString(),
116
+ '--sourcemap', mapFile.toString(),
117
+ '--platform', 'android',
118
+ '--dist', distProp
119
+
120
+ project.logger.lifecycle("[allstak] uploading sourcemap for ${variantName}: ${bundle} (release=${releaseProp})")
121
+ }
122
+
123
+ // Don't fail the build — log and continue.
124
+ ignoreExitValue = true
125
+ }
126
+
127
+ // Run AFTER the bundle exists. We hook the bundle task's
128
+ // doLast so the artifact is on disk; finalizedBy ensures we
129
+ // run even if the user invoked just :app:bundleRelease.
130
+ parent.finalizedBy(uploadTask)
131
+ }
132
+ }
@@ -0,0 +1,127 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * AllStak source-map upload — Expo / EAS Build hook.
4
+ *
5
+ * EAS Build calls per-build hooks defined in `eas.json` under
6
+ * `build.<profile>.hooks` (e.g. `eas-build-on-success`) or via package.json
7
+ * scripts (`eas-build-pre-install`, `eas-build-on-success`, …).
8
+ *
9
+ * Drop-in setup:
10
+ * 1. Add a script in package.json:
11
+ *
12
+ * "scripts": {
13
+ * "eas-build-on-success":
14
+ * "node node_modules/@allstak/react-native/build-hooks/eas-post-bundle.js"
15
+ * }
16
+ *
17
+ * 2. Set these in eas.json secrets (or as EAS env vars):
18
+ *
19
+ * ALLSTAK_RELEASE — required (e.g. "mobile@1.2.3+5")
20
+ * ALLSTAK_UPLOAD_TOKEN — required for actual upload
21
+ * ALLSTAK_HOST — optional override
22
+ *
23
+ * What this hook does:
24
+ * • Probes EAS's standard output paths for both iOS and Android Hermes
25
+ * bundles + composed source maps.
26
+ * • Calls the SDK's uploader for each platform that produced artifacts.
27
+ * • Falls back to inject-only mode when no token is set.
28
+ * • Always exits 0 — never fails an EAS build over a sourcemap glitch.
29
+ *
30
+ * If you build outside of EAS (bare RN, custom CI), use
31
+ * `upload-sourcemaps.js` directly instead.
32
+ */
33
+
34
+ 'use strict';
35
+
36
+ const fs = require('node:fs');
37
+ const path = require('node:path');
38
+ const { spawn } = require('node:child_process');
39
+
40
+ const RELEASE = process.env.ALLSTAK_RELEASE;
41
+ const TOKEN = process.env.ALLSTAK_UPLOAD_TOKEN;
42
+
43
+ if (!RELEASE) {
44
+ console.warn('[allstak] ALLSTAK_RELEASE not set — skipping sourcemap upload');
45
+ process.exit(0);
46
+ }
47
+
48
+ if (!TOKEN) {
49
+ console.warn('[allstak] ALLSTAK_UPLOAD_TOKEN not set — running in inject-only mode');
50
+ }
51
+
52
+ // EAS sets EAS_BUILD_WORKINGDIR to the per-build working directory.
53
+ // Outside of EAS it falls back to cwd.
54
+ const root = process.env.EAS_BUILD_WORKINGDIR || process.cwd();
55
+
56
+ // Probe locations for each platform. EAS runs the standard
57
+ // `npx react-native bundle` in a temp dir under android/ios — these
58
+ // are the conventional output names.
59
+ const probes = [
60
+ // iOS
61
+ { platform: 'ios', dist: 'ios-hermes',
62
+ bundle: path.join(root, 'ios', 'main.jsbundle'),
63
+ sourcemap: path.join(root, 'ios', 'main.jsbundle.map') },
64
+ { platform: 'ios', dist: 'ios-hermes',
65
+ bundle: path.join(root, 'ios.bundle'),
66
+ sourcemap: path.join(root, 'ios.bundle.map') },
67
+ // Android
68
+ { platform: 'android', dist: 'android-hermes',
69
+ bundle: path.join(root, 'android', 'app', 'build', 'generated', 'assets',
70
+ 'createBundleReleaseJsAndAssets', 'index.android.bundle'),
71
+ sourcemap: path.join(root, 'android', 'app', 'build', 'generated', 'sourcemaps',
72
+ 'react', 'release', 'index.android.bundle.map') },
73
+ { platform: 'android', dist: 'android-hermes',
74
+ bundle: path.join(root, 'android.bundle'),
75
+ sourcemap: path.join(root, 'android.bundle.map') },
76
+ ];
77
+
78
+ const targets = [];
79
+ const seen = new Set();
80
+ for (const p of probes) {
81
+ if (fs.existsSync(p.bundle) && fs.existsSync(p.sourcemap)) {
82
+ const key = p.platform;
83
+ if (seen.has(key)) continue;
84
+ seen.add(key);
85
+ targets.push(p);
86
+ }
87
+ }
88
+
89
+ if (targets.length === 0) {
90
+ console.warn('[allstak] no bundle+sourcemap pairs found under', root);
91
+ console.warn(' Searched standard EAS / RN paths for both iOS and Android.');
92
+ console.warn(' If your build writes to a different location, run');
93
+ console.warn(' `node node_modules/@allstak/react-native/build-hooks/upload-sourcemaps.js`');
94
+ console.warn(' with explicit --bundle and --sourcemap flags.');
95
+ process.exit(0); // not a failure
96
+ }
97
+
98
+ const hookScript = path.join(__dirname, 'upload-sourcemaps.js');
99
+
100
+ (async () => {
101
+ for (const t of targets) {
102
+ console.log(`[allstak] EAS hook: ${t.platform} bundle ${t.bundle}`);
103
+ await new Promise((resolve) => {
104
+ const child = spawn('node', [
105
+ hookScript,
106
+ '--bundle', t.bundle,
107
+ '--sourcemap', t.sourcemap,
108
+ '--platform', t.platform,
109
+ '--dist', t.dist,
110
+ ], {
111
+ stdio: 'inherit',
112
+ env: process.env,
113
+ });
114
+ child.on('close', (code) => {
115
+ if (code !== 0) {
116
+ console.warn(`[allstak] EAS hook: ${t.platform} returned exit ${code} — continuing`);
117
+ }
118
+ resolve();
119
+ });
120
+ child.on('error', (e) => {
121
+ console.warn(`[allstak] EAS hook: ${t.platform} error — ${e.message}`);
122
+ resolve();
123
+ });
124
+ });
125
+ }
126
+ process.exit(0);
127
+ })();
@@ -0,0 +1,175 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * One-line source-map upload hook for React Native (Metro / Hermes).
4
+ *
5
+ * Drop this file into your repo at `scripts/upload-sourcemaps.js` (or
6
+ * any path you like). It auto-detects the bundle + map written by
7
+ * `npx react-native bundle` for both platforms, calls the SDK's
8
+ * uploader, and exits non-zero if any platform's upload fails.
9
+ *
10
+ * Usage in CI / Xcode build phase / Gradle task:
11
+ *
12
+ * ALLSTAK_RELEASE='mobile@1.2.3+5' \
13
+ * ALLSTAK_UPLOAD_TOKEN='aspk_…' \
14
+ * node scripts/upload-sourcemaps.js [path/to/build-output-dir]
15
+ *
16
+ * Env vars consulted:
17
+ * ALLSTAK_RELEASE — required, your release identifier
18
+ * ALLSTAK_UPLOAD_TOKEN — required, project-scoped upload token
19
+ * ALLSTAK_HOST — optional, defaults to https://api.allstak.sa
20
+ * ALLSTAK_DIST_OVERRIDE — optional, override auto-detected dist
21
+ *
22
+ * Bundle/map paths it looks for, in order:
23
+ * 1. CLI arg: explicit `--bundle` / `--sourcemap` / `--platform` / `--dist`
24
+ * 2. Build dir from CLI positional arg (default: cwd)
25
+ * 3. Conventional names: <platform>.bundle / <platform>.bundle.map
26
+ *
27
+ * Exits:
28
+ * 0 — at least one platform uploaded OK (or injectOnly with no token)
29
+ * 1 — required env missing or all platforms failed
30
+ *
31
+ * No deps beyond Node 18+ and the SDK itself.
32
+ */
33
+
34
+ 'use strict';
35
+
36
+ const fs = require('node:fs');
37
+ const path = require('node:path');
38
+
39
+ const args = parseArgs(process.argv.slice(2));
40
+
41
+ const RELEASE = process.env.ALLSTAK_RELEASE;
42
+ const TOKEN = process.env.ALLSTAK_UPLOAD_TOKEN;
43
+ const HOST = process.env.ALLSTAK_HOST;
44
+ const DIST_OVERRIDE = process.env.ALLSTAK_DIST_OVERRIDE;
45
+ const INJECT_ONLY = !TOKEN; // gracefully fall back to inject-only if no token
46
+
47
+ if (!RELEASE) {
48
+ console.error('[allstak] ALLSTAK_RELEASE env var is required (e.g. "mobile@1.2.3+5")');
49
+ process.exit(1);
50
+ }
51
+
52
+ if (INJECT_ONLY && !args.injectOnly) {
53
+ console.warn('[allstak] no ALLSTAK_UPLOAD_TOKEN — running in inject-only mode (debug-ids written, no upload)');
54
+ }
55
+
56
+ (async () => {
57
+ let uploader;
58
+ try {
59
+ uploader = require('@allstak/react-native/sourcemaps').uploadReactNativeSourcemap;
60
+ } catch (e) {
61
+ console.error('[allstak] could not require @allstak/react-native/sourcemaps — is the package installed?');
62
+ console.error(e?.message ?? e);
63
+ process.exit(1);
64
+ }
65
+
66
+ const baseDir = path.resolve(args.dir || process.cwd());
67
+
68
+ // Targets to try. If --bundle/--sourcemap are supplied, treat that as
69
+ // a single explicit target; otherwise probe both ios + android in the
70
+ // build dir.
71
+ const targets = [];
72
+ if (args.bundle && args.sourcemap) {
73
+ targets.push({
74
+ platform: args.platform || guessPlatform(args.bundle),
75
+ bundle: path.resolve(args.bundle),
76
+ sourcemap: path.resolve(args.sourcemap),
77
+ dist: args.dist || guessDist(args.platform || guessPlatform(args.bundle)),
78
+ });
79
+ } else {
80
+ for (const platform of ['ios', 'android']) {
81
+ const probe = pickBundle(baseDir, platform);
82
+ if (probe) targets.push({
83
+ platform,
84
+ bundle: probe.bundle,
85
+ sourcemap: probe.sourcemap,
86
+ dist: args.dist || (DIST_OVERRIDE || guessDist(platform)),
87
+ });
88
+ }
89
+ }
90
+
91
+ if (targets.length === 0) {
92
+ console.error(`[allstak] no bundle+sourcemap pair found under ${baseDir}.`);
93
+ console.error(' Expected: <platform>.bundle + <platform>.bundle.map');
94
+ console.error(' Or pass --bundle <path> --sourcemap <path>.');
95
+ process.exit(1);
96
+ }
97
+
98
+ let okCount = 0;
99
+ for (const t of targets) {
100
+ try {
101
+ console.log(`[allstak] ${t.platform}: ${path.relative(baseDir, t.bundle)} + .map (dist=${t.dist})`);
102
+ const result = await uploader({
103
+ bundle: t.bundle,
104
+ sourcemap: t.sourcemap,
105
+ release: RELEASE,
106
+ dist: t.dist,
107
+ token: TOKEN,
108
+ host: HOST,
109
+ injectOnly: INJECT_ONLY || args.injectOnly,
110
+ stripSources: args.stripSources,
111
+ uploadBundle: args.uploadBundle,
112
+ });
113
+ if (INJECT_ONLY || args.injectOnly) {
114
+ console.log(`[allstak] ${t.platform}: debug-id ${result.debugId} ${result.reused ? '(reused)' : '(new)'} — inject-only`);
115
+ okCount += 1;
116
+ } else if (result.uploaded) {
117
+ console.log(`[allstak] ${t.platform}: uploaded debug-id ${result.debugId}`);
118
+ okCount += 1;
119
+ } else {
120
+ const last = result.steps?.[result.steps.length - 1];
121
+ console.error(`[allstak] ${t.platform}: FAIL status=${last?.status ?? '?'} body=${(last?.body ?? '').slice(0, 200)}`);
122
+ }
123
+ } catch (e) {
124
+ console.error(`[allstak] ${t.platform}: error — ${e?.message ?? e}`);
125
+ }
126
+ }
127
+
128
+ process.exit(okCount > 0 ? 0 : 1);
129
+ })();
130
+
131
+ // ────────────────────────────────────────────────────────────────────
132
+
133
+ function parseArgs(argv) {
134
+ const out = { _: [] };
135
+ for (let i = 0; i < argv.length; i++) {
136
+ const a = argv[i];
137
+ if (a === '--bundle') out.bundle = argv[++i];
138
+ else if (a === '--sourcemap') out.sourcemap = argv[++i];
139
+ else if (a === '--platform') out.platform = argv[++i];
140
+ else if (a === '--dist') out.dist = argv[++i];
141
+ else if (a === '--inject-only') out.injectOnly = true;
142
+ else if (a === '--strip-sources') out.stripSources = true;
143
+ else if (a === '--upload-bundle') out.uploadBundle = true;
144
+ else if (a.startsWith('--')) console.warn(`[allstak] unknown flag: ${a}`);
145
+ else out._.push(a);
146
+ }
147
+ if (out._.length > 0 && !out.dir) out.dir = out._[0];
148
+ return out;
149
+ }
150
+
151
+ function pickBundle(dir, platform) {
152
+ const candidates = [
153
+ [path.join(dir, `${platform}.bundle`), path.join(dir, `${platform}.bundle.map`)],
154
+ [path.join(dir, `main.jsbundle`), path.join(dir, `main.jsbundle.map`)], // iOS Xcode default
155
+ [path.join(dir, `index.${platform}.bundle`), path.join(dir, `index.${platform}.bundle.map`)],
156
+ ];
157
+ for (const [b, m] of candidates) {
158
+ if (fs.existsSync(b) && fs.existsSync(m)) return { bundle: b, sourcemap: m };
159
+ }
160
+ return null;
161
+ }
162
+
163
+ function guessPlatform(bundlePath) {
164
+ const f = path.basename(bundlePath).toLowerCase();
165
+ if (f.includes('ios')) return 'ios';
166
+ if (f.includes('android')) return 'android';
167
+ return 'unknown';
168
+ }
169
+
170
+ function guessDist(platform) {
171
+ // Match the SDK's runtime dist auto-detection (`<os>-<engine>`).
172
+ if (platform === 'ios') return 'ios-hermes';
173
+ if (platform === 'android') return 'android-hermes';
174
+ return platform;
175
+ }
@@ -0,0 +1,90 @@
1
+ #!/usr/bin/env bash
2
+ # AllStak source-map upload — iOS Xcode build phase.
3
+ #
4
+ # Add a new "Run Script" build phase to your iOS app target in Xcode,
5
+ # AFTER the React Native "Bundle React Native code and images" phase,
6
+ # with this exact body:
7
+ #
8
+ # "$SRCROOT/../node_modules/@allstak/react-native/build-hooks/xcode-build-phase.sh"
9
+ #
10
+ # Then set the env vars in the same Run Script panel (or via .xcconfig
11
+ # / your CI shell):
12
+ #
13
+ # ALLSTAK_RELEASE = mobile@1.2.3+5
14
+ # ALLSTAK_UPLOAD_TOKEN = aspk_… # only required for upload
15
+ # ALLSTAK_HOST = https://api.allstak.sa (optional)
16
+ # ALLSTAK_DIST_OVERRIDE = ios-hermes (optional override)
17
+ #
18
+ # This script auto-detects the bundle + sourcemap that the React Native
19
+ # Xcode build phase wrote and runs the SDK's uploader on them. It only
20
+ # runs in Release builds — Debug builds are no-ops.
21
+ #
22
+ # Exits 0 even on upload failure so a flaky CI step doesn't fail your
23
+ # archive. The hook script logs everything to the build log.
24
+
25
+ set -e
26
+
27
+ if [[ "${CONFIGURATION}" != "Release" ]]; then
28
+ echo "[allstak] CONFIGURATION=${CONFIGURATION} — skipping sourcemap upload (Release-only)"
29
+ exit 0
30
+ fi
31
+
32
+ if [[ -z "${ALLSTAK_RELEASE}" ]]; then
33
+ echo "[allstak] ALLSTAK_RELEASE not set — skipping sourcemap upload"
34
+ exit 0
35
+ fi
36
+
37
+ # Locate the bundle + map relative to standard React Native output paths.
38
+ # The RN Xcode build phase writes:
39
+ # ${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/main.jsbundle
40
+ # ${CONFIGURATION_BUILD_DIR}/main.jsbundle.map
41
+ BUNDLE="${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/main.jsbundle"
42
+ MAP="${CONFIGURATION_BUILD_DIR}/main.jsbundle.map"
43
+
44
+ if [[ ! -f "${BUNDLE}" ]]; then
45
+ echo "[allstak] bundle not found at ${BUNDLE} — skipping"
46
+ exit 0
47
+ fi
48
+ if [[ ! -f "${MAP}" ]]; then
49
+ echo "[allstak] sourcemap not found at ${MAP} — skipping (set SOURCEMAP_FILE in your bundle build phase)"
50
+ exit 0
51
+ fi
52
+
53
+ # Resolve the hook script. Default location: node_modules in the JS root
54
+ # above the iOS project. Override via ALLSTAK_HOOK_SCRIPT.
55
+ HOOK_SCRIPT="${ALLSTAK_HOOK_SCRIPT:-${SRCROOT}/../node_modules/@allstak/react-native/build-hooks/upload-sourcemaps.js}"
56
+ if [[ ! -f "${HOOK_SCRIPT}" ]]; then
57
+ echo "[allstak] hook script not found at ${HOOK_SCRIPT} — is @allstak/react-native installed?"
58
+ exit 0
59
+ fi
60
+
61
+ # Find a usable Node binary. Xcode build phases don't inherit the user's
62
+ # PATH, so check the common locations.
63
+ if [[ -z "${NODE_BINARY}" ]]; then
64
+ for candidate in \
65
+ "$(command -v node 2>/dev/null)" \
66
+ "/usr/local/bin/node" \
67
+ "/opt/homebrew/bin/node" \
68
+ "/usr/bin/node" ; do
69
+ if [[ -x "${candidate}" ]]; then
70
+ NODE_BINARY="${candidate}"
71
+ break
72
+ fi
73
+ done
74
+ fi
75
+ if [[ -z "${NODE_BINARY}" || ! -x "${NODE_BINARY}" ]]; then
76
+ echo "[allstak] could not locate a Node binary — set NODE_BINARY in your build phase"
77
+ exit 0
78
+ fi
79
+
80
+ DIST="${ALLSTAK_DIST_OVERRIDE:-ios-hermes}"
81
+
82
+ echo "[allstak] uploading sourcemap for ${BUNDLE} (release=${ALLSTAK_RELEASE} dist=${DIST})"
83
+
84
+ # Don't fail the archive if the upload errors. Log and continue.
85
+ "${NODE_BINARY}" "${HOOK_SCRIPT}" \
86
+ --bundle "${BUNDLE}" \
87
+ --sourcemap "${MAP}" \
88
+ --platform ios \
89
+ --dist "${DIST}" \
90
+ || echo "[allstak] sourcemap upload reported non-zero — see log above"
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Source-map upload pipeline for React Native (Metro / Hermes). Build-time only.
3
+ *
4
+ * Metro emits one bundle + one source map per build, so the React Native
5
+ * flow is simpler than the React-web flow (no `dist/` walk needed). The
6
+ * developer hands us the bundle + map paths produced by:
7
+ *
8
+ * npx react-native bundle \
9
+ * --platform ios --dev false --entry-file index.js \
10
+ * --bundle-output ios.bundle \
11
+ * --sourcemap-output ios.bundle.map
12
+ *
13
+ * If Hermes is enabled, the resulting `.hbc` bytecode replaces the JS
14
+ * bundle on the device, and the user must compose the Metro map with the
15
+ * Hermes map BEFORE uploading. We accept whatever map the user gives us
16
+ * and inject a debug-id into both the map and the bundle.
17
+ *
18
+ * Usage from a build script (`scripts/upload-sourcemaps.js`):
19
+ *
20
+ * const { uploadReactNativeSourcemap } = require('@allstak/react-native/sourcemaps');
21
+ *
22
+ * await uploadReactNativeSourcemap({
23
+ * bundle: 'ios.bundle',
24
+ * sourcemap: 'ios.bundle.map',
25
+ * release: 'mobile@1.2.3',
26
+ * dist: 'ios-hermes',
27
+ * token: process.env.ALLSTAK_UPLOAD_TOKEN,
28
+ * });
29
+ *
30
+ * Or `injectOnly: true` to add the debug-id without uploading (useful in
31
+ * CI dry-runs or when you don't have an upload token yet).
32
+ */
33
+ declare const DEFAULT_HOST = "https://api.allstak.sa";
34
+ interface UploadReactNativeSourcemapOptions {
35
+ /** Path to the JS/Hermes-bytecode bundle (Metro's `--bundle-output`). */
36
+ bundle: string;
37
+ /** Path to the matching `.map` (Metro's `--sourcemap-output`). */
38
+ sourcemap: string;
39
+ /** Release identifier (e.g. `mobile@1.2.3` — match `release` in AllStak.init). */
40
+ release: string;
41
+ /**
42
+ * Distribution tag — recommended values: `ios-hermes`, `android-hermes`,
43
+ * `ios-jsc`, `android-jsc`. Match the `dist` the SDK auto-detects at
44
+ * runtime (see `src/install.ts`). Required for the symbolicator to
45
+ * pick the right map per platform.
46
+ */
47
+ dist?: string;
48
+ /** Project upload token (`aspk_…`). Defaults to `ALLSTAK_UPLOAD_TOKEN`. */
49
+ token?: string;
50
+ /** Override ingest host. Defaults to `ALLSTAK_HOST` or production. */
51
+ host?: string;
52
+ /** Drop `sourcesContent` from the map before upload (smaller payload). */
53
+ stripSources?: boolean;
54
+ /** Also upload the JS/HBC bundle alongside the map. Off by default. */
55
+ uploadBundle?: boolean;
56
+ /** Inject debug-id but skip the upload (CI dry-run). */
57
+ injectOnly?: boolean;
58
+ /** Pre-existing debug-id to use instead of generating one. Optional. */
59
+ debugId?: string;
60
+ /** Suppress per-step console output. Default false. */
61
+ silent?: boolean;
62
+ }
63
+ interface UploadReactNativeSourcemapResult {
64
+ /** The debug-id injected into the bundle + map. */
65
+ debugId: string;
66
+ /** True if the bundle already had a debug-id; we reused it. */
67
+ reused: boolean;
68
+ /** True when upload(s) succeeded — undefined when `injectOnly: true`. */
69
+ uploaded?: boolean;
70
+ /** Per-artifact responses, in the order we sent them. */
71
+ steps?: Array<{
72
+ type: 'sourcemap' | 'bundle';
73
+ status: number;
74
+ sha8: string;
75
+ body?: string;
76
+ }>;
77
+ }
78
+ /**
79
+ * Inject a debug-id into the bundle + map (idempotent — reuses an
80
+ * existing id if present).
81
+ */
82
+ declare function injectReactNativeSourcemap(opts: Pick<UploadReactNativeSourcemapOptions, 'bundle' | 'sourcemap' | 'debugId'>): {
83
+ debugId: string;
84
+ reused: boolean;
85
+ };
86
+ /**
87
+ * Inject debug-id into the bundle + map and (optionally) upload.
88
+ *
89
+ * Returns the debug-id (so the caller can stash it for symbolicator
90
+ * lookups), whether it was reused, and per-step upload statuses.
91
+ */
92
+ declare function uploadReactNativeSourcemap(opts: UploadReactNativeSourcemapOptions): Promise<UploadReactNativeSourcemapResult>;
93
+
94
+ export { DEFAULT_HOST, type UploadReactNativeSourcemapOptions, type UploadReactNativeSourcemapResult, injectReactNativeSourcemap, uploadReactNativeSourcemap };