@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.
- package/AllStakRN.podspec +25 -0
- package/README.md +393 -64
- package/app.plugin.js +6 -0
- package/build-hooks/allstak-sourcemaps.gradle +132 -0
- package/build-hooks/eas-post-bundle.js +127 -0
- package/build-hooks/upload-sourcemaps.js +175 -0
- package/build-hooks/xcode-build-phase.sh +90 -0
- package/dist/build/sourcemaps.d.mts +94 -0
- package/dist/build/sourcemaps.d.ts +94 -0
- package/dist/build/sourcemaps.js +142 -0
- package/dist/build/sourcemaps.js.map +1 -0
- package/dist/build/sourcemaps.mjs +115 -0
- package/dist/build/sourcemaps.mjs.map +1 -0
- package/dist/chunk-BJTO5JO5.mjs +11 -0
- package/dist/chunk-BJTO5JO5.mjs.map +1 -0
- package/dist/expo-plugin.d.mts +56 -0
- package/dist/expo-plugin.d.ts +56 -0
- package/dist/expo-plugin.js +44 -0
- package/dist/expo-plugin.js.map +1 -0
- package/dist/expo-plugin.mjs +25 -0
- package/dist/expo-plugin.mjs.map +1 -0
- package/dist/index.d.mts +873 -1
- package/dist/index.d.ts +873 -1
- package/dist/index.js +2159 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2150 -2
- package/dist/index.mjs.map +1 -1
- package/native/android/build.gradle +41 -0
- package/native/android/src/main/AndroidManifest.xml +2 -0
- package/native/android/src/main/java/io/allstak/rn/AllStakRNModule.java +17 -0
- package/native/android/src/main/java/io/allstak/rn/AllStakRNPackage.java +36 -0
- package/native/ios/AllStakRN.podspec +21 -0
- package/native/ios/AllStakRNModule.m +16 -0
- package/package.json +24 -9
- package/react-native.config.js +28 -0
|
@@ -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 };
|