@flareapp/react-native-sourcemaps 2.6.0
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/README.md +257 -0
- package/dist/babel.cjs +52 -0
- package/dist/babel.d.cts +18 -0
- package/dist/babel.d.mts +19 -0
- package/dist/babel.mjs +52 -0
- package/dist/bin.cjs +251 -0
- package/dist/bin.d.cts +1 -0
- package/dist/bin.d.mts +1 -0
- package/dist/bin.mjs +252 -0
- package/dist/expo.cjs +254 -0
- package/dist/expo.d.cts +11 -0
- package/dist/index-legacy-DeTFfC6B.d.mts +2557 -0
- package/dist/index-legacy-aO8l3fYt.d.cts +2557 -0
- package/dist/index.cjs +8 -0
- package/dist/index.d.cts +33 -0
- package/dist/index.d.mts +33 -0
- package/dist/index.mjs +5 -0
- package/dist/runtime.cjs +18 -0
- package/dist/runtime.d.cts +15 -0
- package/dist/runtime.d.mts +15 -0
- package/dist/runtime.mjs +16 -0
- package/dist/uploadSourcemaps-C2EGBxe1.mjs +91 -0
- package/dist/uploadSourcemaps-D6J5FWDO.cjs +96 -0
- package/dist/version-DnFqflPl.mjs +49 -0
- package/dist/version-Z9pawNvw.cjs +66 -0
- package/flare.gradle +103 -0
- package/package.json +100 -0
- package/scripts/flare-xcode.sh +77 -0
package/README.md
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
# @flareapp/react-native-sourcemaps
|
|
2
|
+
|
|
3
|
+
Upload React Native (Metro) JavaScript sourcemaps to Flare so production stack
|
|
4
|
+
traces are symbolicated.
|
|
5
|
+
|
|
6
|
+
It has two halves that share one version string:
|
|
7
|
+
|
|
8
|
+
1. A **Babel plugin** that inlines a build-time version into your app bundle.
|
|
9
|
+
2. A **CLI** (`flare-rn-sourcemaps upload`) that uploads the `.map` under that
|
|
10
|
+
same version.
|
|
11
|
+
|
|
12
|
+
> The manual CLI flow below works everywhere. For **bare React Native** you can also
|
|
13
|
+
> wire the upload into the native release build so it happens automatically — see
|
|
14
|
+
> "Automatic upload (bare React Native)" near the end. The Expo config plugin ships
|
|
15
|
+
> separately.
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install --save-dev @flareapp/react-native-sourcemaps
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## 1. Inline the version into your app
|
|
24
|
+
|
|
25
|
+
Add the Babel plugin to `babel.config.js`:
|
|
26
|
+
|
|
27
|
+
```js
|
|
28
|
+
module.exports = {
|
|
29
|
+
presets: ['module:@react-native/babel-preset'],
|
|
30
|
+
plugins: ['@flareapp/react-native-sourcemaps/babel'],
|
|
31
|
+
};
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Then pass the inlined version when configuring Flare. Import `flareSourcemapVersion`
|
|
35
|
+
from the package's runtime entry — it's a typed `string`, so there is no `process`
|
|
36
|
+
global to type and no `@types/node` to add:
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
import { flare } from '@flareapp/react-native';
|
|
40
|
+
import { flareSourcemapVersion } from '@flareapp/react-native-sourcemaps/runtime';
|
|
41
|
+
|
|
42
|
+
flare.light(FLARE_KEY).configure({ sourcemapVersionId: flareSourcemapVersion });
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
The plugin replaces every reference to `flareSourcemapVersion` with the version it
|
|
46
|
+
resolves at bundle time (and removes the import, so nothing of this package ships in
|
|
47
|
+
your app bundle). Without the plugin the value is an empty string, which is harmless.
|
|
48
|
+
Set the version in your build environment:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
export FLARE_SOURCEMAP_VERSION="$(git rev-parse --short HEAD)"
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
If unset, it falls back to your app's `package.json` version (with a warning). Use the
|
|
55
|
+
**same** version when you build the bundle and when you upload the map. If the two
|
|
56
|
+
differ (for example the env var is set during the build but not during a later upload
|
|
57
|
+
step, so one side falls back to `package.json`), the map will not match and
|
|
58
|
+
symbolication silently fails.
|
|
59
|
+
|
|
60
|
+
If your Babel config adds a generic `process.env` inliner (such as
|
|
61
|
+
`babel-plugin-transform-inline-environment-variables`), list this plugin before it so
|
|
62
|
+
the version token is not rewritten first. The default React Native and Expo presets do
|
|
63
|
+
not inline `process.env`, so no ordering change is needed for a stock setup.
|
|
64
|
+
|
|
65
|
+
## 2. Upload the sourcemap
|
|
66
|
+
|
|
67
|
+
Generate a bundle + map, then upload the map under the **same** version:
|
|
68
|
+
|
|
69
|
+
> Release builds use Hermes, which compiles your JS to bytecode. The map that
|
|
70
|
+
> symbolicates production frames is the **Hermes-composed** map, not the plain Metro JS
|
|
71
|
+
> map. Point `--sourcemap` at the composed `.map` your build emits (the one next to the
|
|
72
|
+
> shipped bundle), not an intermediate Metro map.
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
# Example: Android, Hermes-composed map produced by your build
|
|
76
|
+
npx flare-rn-sourcemaps upload \
|
|
77
|
+
--api-key "$FLARE_KEY" \
|
|
78
|
+
--sourcemap android/app/build/.../index.android.bundle.map \
|
|
79
|
+
--bundle-filename index.android.bundle \
|
|
80
|
+
--version "$FLARE_SOURCEMAP_VERSION"
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Flags:
|
|
84
|
+
|
|
85
|
+
| Flag | Required | Description |
|
|
86
|
+
| ------------------- | -------- | ---------------------------------------------------------------------------------------------- |
|
|
87
|
+
| `--sourcemap` | yes | Path to the composed `.map` file. |
|
|
88
|
+
| `--api-key` | yes\* | Flare API key. Falls back to `FLARE_API_KEY`. |
|
|
89
|
+
| `--bundle-filename` | no | `relative_filename` matched against runtime frames. Defaults to the map basename minus `.map`. |
|
|
90
|
+
| `--version` | no | Defaults to `FLARE_SOURCEMAP_VERSION`, then `package.json` version. |
|
|
91
|
+
| `--api-endpoint` | no | Defaults to `https://flareapp.io/api/sourcemaps`. |
|
|
92
|
+
|
|
93
|
+
\* Without a key the command warns and does nothing.
|
|
94
|
+
|
|
95
|
+
## Automatic upload (bare React Native)
|
|
96
|
+
|
|
97
|
+
Instead of running the CLI by hand, wire it into your native release build. Both
|
|
98
|
+
hooks read a committed `flare.json` at your project root:
|
|
99
|
+
|
|
100
|
+
```json
|
|
101
|
+
{
|
|
102
|
+
"apiKey": "your-flare-api-key"
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
To keep the key out of version control, drop it in a gitignored `.env.local` (or
|
|
107
|
+
`.env`) next to `flare.json` instead — the hooks auto-load `FLARE_API_KEY` from there
|
|
108
|
+
at build time:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
# .env.local (gitignored)
|
|
112
|
+
FLARE_API_KEY=your-flare-api-key
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Precedence: shell env > `.env.local` > `.env` > `flare.json`. An explicit
|
|
116
|
+
`FLARE_API_KEY` in the build environment wins over all of them.
|
|
117
|
+
|
|
118
|
+
The version is taken **only** from `FLARE_SOURCEMAP_VERSION` here (the same variable
|
|
119
|
+
the Babel plugin reads), so the inlined version and the uploaded version always match.
|
|
120
|
+
Set it in your build environment:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
export FLARE_SOURCEMAP_VERSION="$(git rev-parse --short HEAD)"
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
If the key or `FLARE_SOURCEMAP_VERSION` is missing, the upload is **skipped with a
|
|
127
|
+
large warning banner** — your build never fails because of a sourcemap problem.
|
|
128
|
+
|
|
129
|
+
### Android
|
|
130
|
+
|
|
131
|
+
Add this line to `android/app/build.gradle`. It can go anywhere in the file — the script
|
|
132
|
+
hooks the build lazily, so the order doesn't matter:
|
|
133
|
+
|
|
134
|
+
```gradle
|
|
135
|
+
apply from: "../../node_modules/@flareapp/react-native-sourcemaps/flare.gradle"
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
This uploads the Hermes-composed map after every `release` JS-bundle task.
|
|
139
|
+
|
|
140
|
+
### iOS
|
|
141
|
+
|
|
142
|
+
1. Tell the stock React Native bundle phase to emit a sourcemap by adding this line
|
|
143
|
+
to `ios/.xcode.env`:
|
|
144
|
+
|
|
145
|
+
```sh
|
|
146
|
+
export SOURCEMAP_FILE="$TARGET_TEMP_DIR/main.jsbundle.map"
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Use `$TARGET_TEMP_DIR`, **not** `$CONFIGURATION_BUILD_DIR`. With Hermes,
|
|
150
|
+
`react-native-xcode.sh` writes an intermediate map to
|
|
151
|
+
`$CONFIGURATION_BUILD_DIR/main.jsbundle.map` and then deletes it after composing the
|
|
152
|
+
final map — so pointing `SOURCEMAP_FILE` there would have the composed map deleted
|
|
153
|
+
out from under the upload.
|
|
154
|
+
|
|
155
|
+
2. In Xcode, add a new "Run Script" build phase named **Upload Flare sourcemaps**,
|
|
156
|
+
placed **after** "Bundle React Native code and images", with this script:
|
|
157
|
+
|
|
158
|
+
```sh
|
|
159
|
+
set -e
|
|
160
|
+
WITH_ENVIRONMENT="../node_modules/react-native/scripts/xcode/with-environment.sh"
|
|
161
|
+
FLARE_XCODE="../node_modules/@flareapp/react-native-sourcemaps/scripts/flare-xcode.sh"
|
|
162
|
+
/bin/sh "$WITH_ENVIRONMENT" "$FLARE_XCODE"
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
The `with-environment.sh` wrapper is required so the phase sees `SOURCEMAP_FILE`
|
|
166
|
+
(and any `FLARE_*` vars) exported by `.xcode.env`.
|
|
167
|
+
|
|
168
|
+
3. In that build phase, **uncheck "Based on Dependency Analysis"**. With no input/output
|
|
169
|
+
files, Xcode otherwise warns that the script has ambiguous dependencies and runs on
|
|
170
|
+
every build — which is what you want for a release upload. (The Expo config plugin
|
|
171
|
+
sets this automatically.)
|
|
172
|
+
|
|
173
|
+
> The key comes from `flare.json` or the auto-loaded `.env.local`, so it's there even
|
|
174
|
+
> for a GUI build. The **version** (`FLARE_SOURCEMAP_VERSION`) is the one variable the
|
|
175
|
+
> phase still reads from the **build's** environment, inherited from whatever launched
|
|
176
|
+
> the build. `react-native run-ios`, `xcodebuild`, or Fastlane from a terminal that
|
|
177
|
+
> exported it works; a build from the Xcode GUI (including Product > Archive) doesn't
|
|
178
|
+
> have it, so archive from the command line or in CI for releases. The upload skips with
|
|
179
|
+
> the banner if it's missing, and the build still succeeds.
|
|
180
|
+
|
|
181
|
+
#### Custom build configurations (bare / brownfield)
|
|
182
|
+
|
|
183
|
+
Your build configuration doesn't have to be called `Release`.
|
|
184
|
+
|
|
185
|
+
The hooks upload whenever a build makes a JavaScript bundle. They skip a build only when
|
|
186
|
+
its name contains `debug` (any casing).
|
|
187
|
+
|
|
188
|
+
- Uploads: `Release`, `Staging`, `Production`, `AppStore`, your own Android build type
|
|
189
|
+
- Skipped: `Debug`, `debug`, `StagingDebug`
|
|
190
|
+
|
|
191
|
+
A debug build runs from Metro and makes no bundle, so there's nothing to upload. The hook
|
|
192
|
+
skips it and your dev builds stay fast.
|
|
193
|
+
|
|
194
|
+
Want a bundling config to skip the upload anyway? Put `debug` in its name. Or remove the
|
|
195
|
+
Flare setup you added above — the `apply from "…/flare.gradle"` line in
|
|
196
|
+
`android/app/build.gradle` on Android, or the **Upload Flare sourcemaps** build phase in
|
|
197
|
+
Xcode on iOS — which turns the automatic upload off completely.
|
|
198
|
+
|
|
199
|
+
### Expo (CNG / managed)
|
|
200
|
+
|
|
201
|
+
For an Expo project that uses prebuild (CNG), add the config plugin to `app.json` —
|
|
202
|
+
it injects the same native wiring on every prebuild, so it survives regeneration:
|
|
203
|
+
|
|
204
|
+
```json
|
|
205
|
+
{
|
|
206
|
+
"expo": {
|
|
207
|
+
"plugins": [["@flareapp/react-native-sourcemaps/expo", { "apiKey": "YOUR PROJECT KEY" }]]
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
You still add the Babel plugin and pass `flareSourcemapVersion` (steps above), and you
|
|
213
|
+
still set `FLARE_SOURCEMAP_VERSION` in the build environment (locally, or in
|
|
214
|
+
`eas.json`'s `build.<profile>.env` for EAS Build). The plugin creates a `flare.json` at
|
|
215
|
+
your project root and adds it to `.gitignore`. That's expected. You don't edit it; it's
|
|
216
|
+
generated from your `app.json`.
|
|
217
|
+
|
|
218
|
+
**Releasing without EAS?** You don't have to. The key comes from `flare.json` or the
|
|
219
|
+
auto-loaded `.env.local`; the upload reads `FLARE_SOURCEMAP_VERSION` from the build's
|
|
220
|
+
environment, inherited from whatever **launches** the build. So EAS Build (`eas.json`
|
|
221
|
+
env), `eas build --local`, or a command-line archive (`xcodebuild` / Fastlane on iOS,
|
|
222
|
+
`./gradlew bundleRelease` on Android) all work, as long as you exported the version in
|
|
223
|
+
that shell.
|
|
224
|
+
|
|
225
|
+
> The one exception is the version under the **Xcode / Android Studio GUI** (including
|
|
226
|
+
> Product > Archive): it was launched by the OS, not your shell, so it doesn't have
|
|
227
|
+
> `FLARE_SOURCEMAP_VERSION` and the upload skips with the banner. Archive from the
|
|
228
|
+
> command line, or use EAS, for releases.
|
|
229
|
+
|
|
230
|
+
> **`expo run:android` / `expo run:ios` are not a reliable upload path.** Both
|
|
231
|
+
> eager-bundle the JS to a temp dir, and the native bundle task our hook runs after can
|
|
232
|
+
> be reported **up-to-date** and skipped — so you get an installed app but no upload.
|
|
233
|
+
> Changing only `FLARE_SOURCEMAP_VERSION` doesn't re-trigger it (it isn't a build
|
|
234
|
+
> input). Use `expo run:*` for development, but **release and verify** through
|
|
235
|
+
> `eas build` / `eas build --local` or a direct native build (`./gradlew
|
|
236
|
+
:app:assembleRelease`, `xcodebuild`). When testing locally, add `--rerun-tasks` to the
|
|
237
|
+
> Gradle command to force a fresh bundle and upload.
|
|
238
|
+
|
|
239
|
+
> **OTA / EAS Update is not covered.** The plugin only runs during a native build
|
|
240
|
+
> (`expo run:*`, EAS Build). `eas update` ships JS via `expo export` with no native
|
|
241
|
+
> build phase, so it uploads no map. For OTA releases, upload the map yourself with
|
|
242
|
+
> `flare-rn-sourcemaps upload` under the **same** `FLARE_SOURCEMAP_VERSION` you exported
|
|
243
|
+
> with.
|
|
244
|
+
|
|
245
|
+
## Expo (`expo export`)
|
|
246
|
+
|
|
247
|
+
```bash
|
|
248
|
+
FLARE_SOURCEMAP_VERSION="$(git rev-parse --short HEAD)" npx expo export
|
|
249
|
+
npx flare-rn-sourcemaps upload \
|
|
250
|
+
--api-key "$FLARE_KEY" \
|
|
251
|
+
--sourcemap dist/_expo/static/js/ios/index-*.hbc.map \
|
|
252
|
+
--bundle-filename main.jsbundle \
|
|
253
|
+
--version "$FLARE_SOURCEMAP_VERSION"
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
> `--bundle-filename` must match how frames appear in your Flare reports. If
|
|
257
|
+
> traces are not symbolicating, that is the value to adjust.
|
package/dist/babel.cjs
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
const require_version = require('./version-Z9pawNvw.cjs');
|
|
2
|
+
|
|
3
|
+
//#region src/babel.ts
|
|
4
|
+
const RUNTIME_SOURCE = "@flareapp/react-native-sourcemaps/runtime";
|
|
5
|
+
const EXPORT_NAME = "flareSourcemapVersion";
|
|
6
|
+
/**
|
|
7
|
+
* Babel plugin that inlines `flareSourcemapVersion` (imported from
|
|
8
|
+
* `@flareapp/react-native-sourcemaps/runtime`) with the resolved version string at
|
|
9
|
+
* bundle time, then removes the now-dead import so nothing of this package ships at
|
|
10
|
+
* runtime. The version is resolved once per file (lazily, only when the import is
|
|
11
|
+
* present) via the shared `resolveVersion()`.
|
|
12
|
+
*/
|
|
13
|
+
function flareSourcemapsBabelPlugin({ types: t }) {
|
|
14
|
+
let version;
|
|
15
|
+
return {
|
|
16
|
+
name: "@flareapp/react-native-sourcemaps",
|
|
17
|
+
pre() {
|
|
18
|
+
version = void 0;
|
|
19
|
+
},
|
|
20
|
+
visitor: { ImportDeclaration(path) {
|
|
21
|
+
if (path.node.source.value !== RUNTIME_SOURCE) return;
|
|
22
|
+
const remaining = [];
|
|
23
|
+
let inlinedAny = false;
|
|
24
|
+
for (const specifier of path.node.specifiers) {
|
|
25
|
+
if (!isFlareVersionSpecifier(specifier, t)) {
|
|
26
|
+
remaining.push(specifier);
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
const binding = path.scope.getBinding(specifier.local.name);
|
|
30
|
+
if (!binding) {
|
|
31
|
+
remaining.push(specifier);
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (version === void 0) version = require_version.resolveVersion();
|
|
35
|
+
for (const reference of binding.referencePaths) reference.replaceWith(t.stringLiteral(version));
|
|
36
|
+
inlinedAny = true;
|
|
37
|
+
}
|
|
38
|
+
if (!inlinedAny) return;
|
|
39
|
+
if (remaining.length === 0) path.remove();
|
|
40
|
+
else path.node.specifiers = remaining;
|
|
41
|
+
} }
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/** Matches `import { flareSourcemapVersion } from '.../runtime'` (named, possibly aliased). */
|
|
45
|
+
function isFlareVersionSpecifier(specifier, t) {
|
|
46
|
+
if (!t.isImportSpecifier(specifier)) return false;
|
|
47
|
+
const imported = specifier.imported;
|
|
48
|
+
return (t.isIdentifier(imported) ? imported.name : imported.value) === EXPORT_NAME;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
//#endregion
|
|
52
|
+
module.exports = flareSourcemapsBabelPlugin;
|
package/dist/babel.d.cts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { t as index_legacy_d_exports } from "./index-legacy-aO8l3fYt.cjs";
|
|
2
|
+
import * as Babel from "@babel/core";
|
|
3
|
+
|
|
4
|
+
//#region src/babel.d.ts
|
|
5
|
+
type Types = typeof index_legacy_d_exports;
|
|
6
|
+
/**
|
|
7
|
+
* Babel plugin that inlines `flareSourcemapVersion` (imported from
|
|
8
|
+
* `@flareapp/react-native-sourcemaps/runtime`) with the resolved version string at
|
|
9
|
+
* bundle time, then removes the now-dead import so nothing of this package ships at
|
|
10
|
+
* runtime. The version is resolved once per file (lazily, only when the import is
|
|
11
|
+
* present) via the shared `resolveVersion()`.
|
|
12
|
+
*/
|
|
13
|
+
declare function flareSourcemapsBabelPlugin({
|
|
14
|
+
types: t
|
|
15
|
+
}: {
|
|
16
|
+
types: Types;
|
|
17
|
+
}): Babel.PluginObj;
|
|
18
|
+
export = flareSourcemapsBabelPlugin;
|
package/dist/babel.d.mts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { t as index_legacy_d_exports } from "./index-legacy-DeTFfC6B.mjs";
|
|
2
|
+
import * as Babel from "@babel/core";
|
|
3
|
+
|
|
4
|
+
//#region src/babel.d.ts
|
|
5
|
+
type Types = typeof index_legacy_d_exports;
|
|
6
|
+
/**
|
|
7
|
+
* Babel plugin that inlines `flareSourcemapVersion` (imported from
|
|
8
|
+
* `@flareapp/react-native-sourcemaps/runtime`) with the resolved version string at
|
|
9
|
+
* bundle time, then removes the now-dead import so nothing of this package ships at
|
|
10
|
+
* runtime. The version is resolved once per file (lazily, only when the import is
|
|
11
|
+
* present) via the shared `resolveVersion()`.
|
|
12
|
+
*/
|
|
13
|
+
declare function flareSourcemapsBabelPlugin({
|
|
14
|
+
types: t
|
|
15
|
+
}: {
|
|
16
|
+
types: Types;
|
|
17
|
+
}): Babel.PluginObj;
|
|
18
|
+
//#endregion
|
|
19
|
+
export { flareSourcemapsBabelPlugin as default };
|
package/dist/babel.mjs
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { n as resolveVersion } from "./version-DnFqflPl.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/babel.ts
|
|
4
|
+
const RUNTIME_SOURCE = "@flareapp/react-native-sourcemaps/runtime";
|
|
5
|
+
const EXPORT_NAME = "flareSourcemapVersion";
|
|
6
|
+
/**
|
|
7
|
+
* Babel plugin that inlines `flareSourcemapVersion` (imported from
|
|
8
|
+
* `@flareapp/react-native-sourcemaps/runtime`) with the resolved version string at
|
|
9
|
+
* bundle time, then removes the now-dead import so nothing of this package ships at
|
|
10
|
+
* runtime. The version is resolved once per file (lazily, only when the import is
|
|
11
|
+
* present) via the shared `resolveVersion()`.
|
|
12
|
+
*/
|
|
13
|
+
function flareSourcemapsBabelPlugin({ types: t }) {
|
|
14
|
+
let version;
|
|
15
|
+
return {
|
|
16
|
+
name: "@flareapp/react-native-sourcemaps",
|
|
17
|
+
pre() {
|
|
18
|
+
version = void 0;
|
|
19
|
+
},
|
|
20
|
+
visitor: { ImportDeclaration(path) {
|
|
21
|
+
if (path.node.source.value !== RUNTIME_SOURCE) return;
|
|
22
|
+
const remaining = [];
|
|
23
|
+
let inlinedAny = false;
|
|
24
|
+
for (const specifier of path.node.specifiers) {
|
|
25
|
+
if (!isFlareVersionSpecifier(specifier, t)) {
|
|
26
|
+
remaining.push(specifier);
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
const binding = path.scope.getBinding(specifier.local.name);
|
|
30
|
+
if (!binding) {
|
|
31
|
+
remaining.push(specifier);
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (version === void 0) version = resolveVersion();
|
|
35
|
+
for (const reference of binding.referencePaths) reference.replaceWith(t.stringLiteral(version));
|
|
36
|
+
inlinedAny = true;
|
|
37
|
+
}
|
|
38
|
+
if (!inlinedAny) return;
|
|
39
|
+
if (remaining.length === 0) path.remove();
|
|
40
|
+
else path.node.specifiers = remaining;
|
|
41
|
+
} }
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/** Matches `import { flareSourcemapVersion } from '.../runtime'` (named, possibly aliased). */
|
|
45
|
+
function isFlareVersionSpecifier(specifier, t) {
|
|
46
|
+
if (!t.isImportSpecifier(specifier)) return false;
|
|
47
|
+
const imported = specifier.imported;
|
|
48
|
+
return (t.isIdentifier(imported) ? imported.name : imported.value) === EXPORT_NAME;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
//#endregion
|
|
52
|
+
export { flareSourcemapsBabelPlugin as default };
|
package/dist/bin.cjs
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const require_uploadSourcemaps = require('./uploadSourcemaps-D6J5FWDO.cjs');
|
|
3
|
+
const require_version = require('./version-Z9pawNvw.cjs');
|
|
4
|
+
let node_fs = require("node:fs");
|
|
5
|
+
let node_path = require("node:path");
|
|
6
|
+
|
|
7
|
+
//#region src/banner.ts
|
|
8
|
+
const BORDER = "=".repeat(60);
|
|
9
|
+
/**
|
|
10
|
+
* Mask an API key for display in a build log, which CI commonly archives. A key
|
|
11
|
+
* long enough to stay unguessable keeps a short head/tail so the user can still
|
|
12
|
+
* recognise WHICH key it was; a short key is fully masked. Never returns the key
|
|
13
|
+
* in full, so the secret cannot leak through the failure banner.
|
|
14
|
+
*/
|
|
15
|
+
function maskApiKey(apiKey) {
|
|
16
|
+
if (apiKey.length <= 12) return "*".repeat(apiKey.length);
|
|
17
|
+
return `${apiKey.slice(0, 4)}${"*".repeat(8)}${apiKey.slice(-4)}`;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* The deliberately large failure banner. A one-line "failed to upload" is too easy
|
|
21
|
+
* to miss in a long native-build log, so this is a bordered block surrounded by
|
|
22
|
+
* blank lines. Resolved values are interpolated into the re-run command so the
|
|
23
|
+
* user can copy-paste it; unknown values show a labelled placeholder (in the
|
|
24
|
+
* version-unset case `version` stays a placeholder — it is the value to supply).
|
|
25
|
+
* The API key is the one exception: it is MASKED, never printed in full, because
|
|
26
|
+
* this banner lands in the native build log.
|
|
27
|
+
*/
|
|
28
|
+
function formatFailureBanner(info) {
|
|
29
|
+
const sourcemap = info.sourcemap ?? "<path-to-map>";
|
|
30
|
+
const bundleFilename = info.bundleFilename ?? "<bundle-filename>";
|
|
31
|
+
const version = info.version && info.version.length > 0 ? info.version : "<flare-sourcemap-version>";
|
|
32
|
+
const hasApiKey = !!(info.apiKey && info.apiKey.length > 0);
|
|
33
|
+
const apiKey = hasApiKey ? maskApiKey(info.apiKey) : "<your-flare-api-key>";
|
|
34
|
+
const endpointFlag = info.apiEndpoint ? ` --api-endpoint ${info.apiEndpoint}` : "";
|
|
35
|
+
const lines = [
|
|
36
|
+
"",
|
|
37
|
+
BORDER,
|
|
38
|
+
" FLARE SOURCEMAP UPLOAD FAILED",
|
|
39
|
+
` Reason: ${info.reason}`,
|
|
40
|
+
" Your release will report minified stack traces until the",
|
|
41
|
+
" sourcemap is uploaded. Re-run manually:",
|
|
42
|
+
` npx flare-rn-sourcemaps upload --sourcemap ${sourcemap} \\`,
|
|
43
|
+
` --bundle-filename ${bundleFilename} --version ${version} --api-key ${apiKey}${endpointFlag}`
|
|
44
|
+
];
|
|
45
|
+
if (hasApiKey) lines.push(" (the --api-key above is masked; pass your full Flare key, or set FLARE_API_KEY, when re-running)");
|
|
46
|
+
lines.push(BORDER, "");
|
|
47
|
+
return lines.join("\n");
|
|
48
|
+
}
|
|
49
|
+
function printFailureBanner(info) {
|
|
50
|
+
console.error(formatFailureBanner(info));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
//#endregion
|
|
54
|
+
//#region src/config.ts
|
|
55
|
+
/**
|
|
56
|
+
* Read flare.json from an EXPLICIT path. The hooks run from android/ or ios/, so
|
|
57
|
+
* resolving relative to process.cwd() would read the wrong file — callers always
|
|
58
|
+
* pass --config. Missing or malformed file → empty config, so resolution falls
|
|
59
|
+
* through to env, then to the no-key skip-with-banner. Any `version` key is
|
|
60
|
+
* deliberately ignored: in the auto path the version flows only through
|
|
61
|
+
* FLARE_SOURCEMAP_VERSION (see resolveAutoVersion / the design doc).
|
|
62
|
+
*/
|
|
63
|
+
function readFlareConfig(configPath) {
|
|
64
|
+
if (!configPath) return {};
|
|
65
|
+
try {
|
|
66
|
+
const raw = JSON.parse((0, node_fs.readFileSync)(configPath, "utf8"));
|
|
67
|
+
const config = {};
|
|
68
|
+
const apiKey = asString(raw.apiKey);
|
|
69
|
+
const apiEndpoint = asString(raw.apiEndpoint);
|
|
70
|
+
if (apiKey !== void 0) config.apiKey = apiKey;
|
|
71
|
+
if (apiEndpoint !== void 0) config.apiEndpoint = apiEndpoint;
|
|
72
|
+
return config;
|
|
73
|
+
} catch {
|
|
74
|
+
return {};
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function asString(value) {
|
|
78
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
//#endregion
|
|
82
|
+
//#region src/env.ts
|
|
83
|
+
const ENV_FILES = [".env.local", ".env"];
|
|
84
|
+
/**
|
|
85
|
+
* Load FLARE_* variables from .env.local / .env in `rootDir` into process.env,
|
|
86
|
+
* without overwriting anything already set.
|
|
87
|
+
*
|
|
88
|
+
* The native build hooks run in a fresh process whose environment often does NOT
|
|
89
|
+
* carry the upload key: the app's .env files are loaded by Metro at JS runtime, not
|
|
90
|
+
* at native build time, and a build launched from an IDE — or served by a reused
|
|
91
|
+
* Gradle daemon with a stale environment — won't see a key you exported in your
|
|
92
|
+
* shell. Reading the key from the FILE here makes "drop FLARE_API_KEY in .env.local"
|
|
93
|
+
* work the way developers expect, on both platforms, without re-exporting it per
|
|
94
|
+
* build.
|
|
95
|
+
*
|
|
96
|
+
* Only FLARE_-prefixed keys are read, so this never pulls unrelated secrets from the
|
|
97
|
+
* file into the process. Minimal parser (no dependency): `KEY=value`, an optional
|
|
98
|
+
* `export ` prefix, surrounding single/double quotes stripped, blank and `#` comment
|
|
99
|
+
* lines ignored. Inline comments are NOT stripped (a `#` is treated as part of the
|
|
100
|
+
* value), which is fine for the alphanumeric keys Flare issues.
|
|
101
|
+
*/
|
|
102
|
+
function loadEnvFiles(rootDir) {
|
|
103
|
+
for (const file of ENV_FILES) {
|
|
104
|
+
let raw;
|
|
105
|
+
try {
|
|
106
|
+
raw = (0, node_fs.readFileSync)((0, node_path.join)(rootDir, file), "utf8");
|
|
107
|
+
} catch {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
for (const [key, value] of parseFlareEnv(raw)) if (process.env[key] === void 0) process.env[key] = value;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
function parseFlareEnv(raw) {
|
|
114
|
+
const out = [];
|
|
115
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
116
|
+
const trimmed = line.trim();
|
|
117
|
+
if (trimmed === "" || trimmed.startsWith("#")) continue;
|
|
118
|
+
const body = trimmed.startsWith("export ") ? trimmed.slice(7).trim() : trimmed;
|
|
119
|
+
const eq = body.indexOf("=");
|
|
120
|
+
if (eq === -1) continue;
|
|
121
|
+
const key = body.slice(0, eq).trim();
|
|
122
|
+
if (!key.startsWith("FLARE_")) continue;
|
|
123
|
+
let value = body.slice(eq + 1).trim();
|
|
124
|
+
if ((value.startsWith("\"") && value.endsWith("\"") || value.startsWith("'") && value.endsWith("'")) && value.length >= 2) value = value.slice(1, -1);
|
|
125
|
+
out.push([key, value]);
|
|
126
|
+
}
|
|
127
|
+
return out;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
//#endregion
|
|
131
|
+
//#region src/cli.ts
|
|
132
|
+
const USAGE = "Usage: flare-rn-sourcemaps upload --sourcemap <path> [--api-key <key>] [--bundle-filename <name>] [--version <v>] [--api-endpoint <url>] [--config <flare.json>] [--auto]";
|
|
133
|
+
/** Minimal `--flag value` / `--flag` parser. No external dependency. */
|
|
134
|
+
function parseArgs(argv) {
|
|
135
|
+
const [command, ...rest] = argv;
|
|
136
|
+
const flags = {};
|
|
137
|
+
for (let i = 0; i < rest.length; i++) {
|
|
138
|
+
const arg = rest[i];
|
|
139
|
+
if (!arg.startsWith("--")) continue;
|
|
140
|
+
const body = arg.slice(2);
|
|
141
|
+
const eq = body.indexOf("=");
|
|
142
|
+
if (eq !== -1) {
|
|
143
|
+
flags[body.slice(0, eq)] = body.slice(eq + 1);
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
const key = body;
|
|
147
|
+
const next = rest[i + 1];
|
|
148
|
+
if (next !== void 0 && !next.startsWith("--")) {
|
|
149
|
+
flags[key] = next;
|
|
150
|
+
i++;
|
|
151
|
+
} else flags[key] = "true";
|
|
152
|
+
}
|
|
153
|
+
return {
|
|
154
|
+
command,
|
|
155
|
+
flags
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
async function runCli(argv) {
|
|
159
|
+
const { command, flags } = parseArgs(argv);
|
|
160
|
+
if (command !== "upload") {
|
|
161
|
+
console.error(`${require_version.LOG_PREFIX}: Unknown command "${command ?? ""}".\n${USAGE}`);
|
|
162
|
+
process.exitCode = 1;
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
const sourcemap = flags.sourcemap;
|
|
166
|
+
if (!sourcemap) {
|
|
167
|
+
console.error(`${require_version.LOG_PREFIX}: --sourcemap is required.\n${USAGE}`);
|
|
168
|
+
process.exitCode = 1;
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
if (flags.auto === "true") loadEnvFiles(flags.config ? (0, node_path.dirname)(flags.config) : process.cwd());
|
|
172
|
+
const config = readFlareConfig(flags.config);
|
|
173
|
+
const apiKey = flags["api-key"] ?? process.env.FLARE_API_KEY ?? config.apiKey ?? "";
|
|
174
|
+
const apiEndpoint = flags["api-endpoint"] ?? process.env.FLARE_API_ENDPOINT ?? config.apiEndpoint;
|
|
175
|
+
const bundleFilename = flags["bundle-filename"];
|
|
176
|
+
if (flags.auto === "true") {
|
|
177
|
+
await runAutoUpload({
|
|
178
|
+
apiKey,
|
|
179
|
+
sourcemap,
|
|
180
|
+
bundleFilename,
|
|
181
|
+
apiEndpoint,
|
|
182
|
+
version: flags.version
|
|
183
|
+
});
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
await require_uploadSourcemaps.uploadSourcemaps({
|
|
187
|
+
apiKey,
|
|
188
|
+
sourcemap,
|
|
189
|
+
bundleFilename,
|
|
190
|
+
version: flags.version,
|
|
191
|
+
apiEndpoint
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* The build-hook upload path. It NEVER throws and NEVER sets a non-zero exit code
|
|
196
|
+
* (a Gradle doLast / Xcode run-script phase would abort the build on a non-zero
|
|
197
|
+
* child). Every failure mode — no key, unresolved version, upload error — prints the
|
|
198
|
+
* loud banner and returns, leaving the build green. Only arg misuse (handled in
|
|
199
|
+
* runCli before we get here) exits non-zero.
|
|
200
|
+
*/
|
|
201
|
+
async function runAutoUpload(options) {
|
|
202
|
+
const { apiKey, sourcemap, bundleFilename, apiEndpoint } = options;
|
|
203
|
+
if (!apiKey) {
|
|
204
|
+
printFailureBanner({
|
|
205
|
+
reason: "No Flare API key. Set FLARE_API_KEY (shell, .env.local, or .env) or add \"apiKey\" to flare.json.",
|
|
206
|
+
sourcemap,
|
|
207
|
+
bundleFilename,
|
|
208
|
+
apiKey,
|
|
209
|
+
apiEndpoint
|
|
210
|
+
});
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
const version = require_version.resolveAutoVersion(options.version);
|
|
214
|
+
if (!version) {
|
|
215
|
+
printFailureBanner({
|
|
216
|
+
reason: "FLARE_SOURCEMAP_VERSION is not set (required for the automatic upload).",
|
|
217
|
+
sourcemap,
|
|
218
|
+
bundleFilename,
|
|
219
|
+
apiKey,
|
|
220
|
+
apiEndpoint
|
|
221
|
+
});
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
try {
|
|
225
|
+
await require_uploadSourcemaps.uploadSourcemaps({
|
|
226
|
+
apiKey,
|
|
227
|
+
sourcemap,
|
|
228
|
+
bundleFilename,
|
|
229
|
+
version,
|
|
230
|
+
apiEndpoint
|
|
231
|
+
});
|
|
232
|
+
} catch (error) {
|
|
233
|
+
printFailureBanner({
|
|
234
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
235
|
+
sourcemap,
|
|
236
|
+
bundleFilename,
|
|
237
|
+
version,
|
|
238
|
+
apiKey,
|
|
239
|
+
apiEndpoint
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
//#endregion
|
|
245
|
+
//#region src/bin.ts
|
|
246
|
+
runCli(process.argv.slice(2)).catch((error) => {
|
|
247
|
+
console.error(error);
|
|
248
|
+
process.exitCode = 1;
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
//#endregion
|
package/dist/bin.d.cts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
package/dist/bin.d.mts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|