@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/dist/index.cjs ADDED
@@ -0,0 +1,8 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
+ const require_uploadSourcemaps = require('./uploadSourcemaps-D6J5FWDO.cjs');
3
+ const require_version = require('./version-Z9pawNvw.cjs');
4
+ const require_babel = require('./babel.cjs');
5
+
6
+ exports.flareSourcemapsBabelPlugin = require_babel;
7
+ exports.resolveVersion = require_version.resolveVersion;
8
+ exports.uploadSourcemaps = require_uploadSourcemaps.uploadSourcemaps;
@@ -0,0 +1,33 @@
1
+ import flareSourcemapsBabelPlugin from "./babel.cjs";
2
+
3
+ //#region src/uploadSourcemaps.d.ts
4
+ type UploadSourcemapsOptions = {
5
+ /** Flare project API key. */apiKey: string; /** Path to the composed `.map` file to upload. */
6
+ sourcemap: string;
7
+ /**
8
+ * The `relative_filename` the backend matches against runtime stack frames.
9
+ * Defaults to the map's basename minus `.map` (e.g. `index.android.bundle`).
10
+ */
11
+ bundleFilename?: string; /** Explicit version; otherwise resolved from env/package.json. */
12
+ version?: string; /** Override the Flare sourcemaps endpoint. */
13
+ apiEndpoint?: string;
14
+ };
15
+ declare function uploadSourcemaps(options: UploadSourcemapsOptions): Promise<void>;
16
+ //#endregion
17
+ //#region src/version.d.ts
18
+ type ResolveVersionOptions = {
19
+ /** Explicit version (e.g. CLI `--version`). Highest precedence. */version?: string; /** Directory whose package.json is read as the last-resort fallback. */
20
+ cwd?: string;
21
+ };
22
+ /**
23
+ * Resolve the sourcemap version shared by the Babel plugin and the CLI.
24
+ * Precedence: explicit `version` > `FLARE_SOURCEMAP_VERSION` env > package.json
25
+ * `version` (with a warning). Never a random value — a random default would
26
+ * silently desync the inlined runtime value from the uploaded `version_id`.
27
+ */
28
+ declare function resolveVersion({
29
+ version,
30
+ cwd
31
+ }?: ResolveVersionOptions): string;
32
+ //#endregion
33
+ export { type ResolveVersionOptions, type UploadSourcemapsOptions, flareSourcemapsBabelPlugin, resolveVersion, uploadSourcemaps };
@@ -0,0 +1,33 @@
1
+ import flareSourcemapsBabelPlugin from "./babel.mjs";
2
+
3
+ //#region src/uploadSourcemaps.d.ts
4
+ type UploadSourcemapsOptions = {
5
+ /** Flare project API key. */apiKey: string; /** Path to the composed `.map` file to upload. */
6
+ sourcemap: string;
7
+ /**
8
+ * The `relative_filename` the backend matches against runtime stack frames.
9
+ * Defaults to the map's basename minus `.map` (e.g. `index.android.bundle`).
10
+ */
11
+ bundleFilename?: string; /** Explicit version; otherwise resolved from env/package.json. */
12
+ version?: string; /** Override the Flare sourcemaps endpoint. */
13
+ apiEndpoint?: string;
14
+ };
15
+ declare function uploadSourcemaps(options: UploadSourcemapsOptions): Promise<void>;
16
+ //#endregion
17
+ //#region src/version.d.ts
18
+ type ResolveVersionOptions = {
19
+ /** Explicit version (e.g. CLI `--version`). Highest precedence. */version?: string; /** Directory whose package.json is read as the last-resort fallback. */
20
+ cwd?: string;
21
+ };
22
+ /**
23
+ * Resolve the sourcemap version shared by the Babel plugin and the CLI.
24
+ * Precedence: explicit `version` > `FLARE_SOURCEMAP_VERSION` env > package.json
25
+ * `version` (with a warning). Never a random value — a random default would
26
+ * silently desync the inlined runtime value from the uploaded `version_id`.
27
+ */
28
+ declare function resolveVersion({
29
+ version,
30
+ cwd
31
+ }?: ResolveVersionOptions): string;
32
+ //#endregion
33
+ export { type ResolveVersionOptions, type UploadSourcemapsOptions, flareSourcemapsBabelPlugin, resolveVersion, uploadSourcemaps };
package/dist/index.mjs ADDED
@@ -0,0 +1,5 @@
1
+ import { t as uploadSourcemaps } from "./uploadSourcemaps-C2EGBxe1.mjs";
2
+ import { n as resolveVersion } from "./version-DnFqflPl.mjs";
3
+ import flareSourcemapsBabelPlugin from "./babel.mjs";
4
+
5
+ export { flareSourcemapsBabelPlugin, resolveVersion, uploadSourcemaps };
@@ -0,0 +1,18 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
+
3
+ //#region src/runtime.ts
4
+ /**
5
+ * The build-time sourcemap version, for `flare.configure({ sourcemapVersionId })`.
6
+ *
7
+ * `@flareapp/react-native-sourcemaps/babel` replaces every reference to this
8
+ * binding with the resolved version string literal at bundle time, then drops the
9
+ * import. Without that Babel plugin it stays an empty string (meaning "no version"),
10
+ * which is harmless because sourcemaps are only uploaded for release builds.
11
+ *
12
+ * This module is runtime-safe: it has no Node imports, so it is safe to import into
13
+ * app code that Metro bundles (unlike the package root, which pulls in the uploader).
14
+ */
15
+ const flareSourcemapVersion = "";
16
+
17
+ //#endregion
18
+ exports.flareSourcemapVersion = flareSourcemapVersion;
@@ -0,0 +1,15 @@
1
+ //#region src/runtime.d.ts
2
+ /**
3
+ * The build-time sourcemap version, for `flare.configure({ sourcemapVersionId })`.
4
+ *
5
+ * `@flareapp/react-native-sourcemaps/babel` replaces every reference to this
6
+ * binding with the resolved version string literal at bundle time, then drops the
7
+ * import. Without that Babel plugin it stays an empty string (meaning "no version"),
8
+ * which is harmless because sourcemaps are only uploaded for release builds.
9
+ *
10
+ * This module is runtime-safe: it has no Node imports, so it is safe to import into
11
+ * app code that Metro bundles (unlike the package root, which pulls in the uploader).
12
+ */
13
+ declare const flareSourcemapVersion: string;
14
+ //#endregion
15
+ export { flareSourcemapVersion };
@@ -0,0 +1,15 @@
1
+ //#region src/runtime.d.ts
2
+ /**
3
+ * The build-time sourcemap version, for `flare.configure({ sourcemapVersionId })`.
4
+ *
5
+ * `@flareapp/react-native-sourcemaps/babel` replaces every reference to this
6
+ * binding with the resolved version string literal at bundle time, then drops the
7
+ * import. Without that Babel plugin it stays an empty string (meaning "no version"),
8
+ * which is harmless because sourcemaps are only uploaded for release builds.
9
+ *
10
+ * This module is runtime-safe: it has no Node imports, so it is safe to import into
11
+ * app code that Metro bundles (unlike the package root, which pulls in the uploader).
12
+ */
13
+ declare const flareSourcemapVersion: string;
14
+ //#endregion
15
+ export { flareSourcemapVersion };
@@ -0,0 +1,16 @@
1
+ //#region src/runtime.ts
2
+ /**
3
+ * The build-time sourcemap version, for `flare.configure({ sourcemapVersionId })`.
4
+ *
5
+ * `@flareapp/react-native-sourcemaps/babel` replaces every reference to this
6
+ * binding with the resolved version string literal at bundle time, then drops the
7
+ * import. Without that Babel plugin it stays an empty string (meaning "no version"),
8
+ * which is harmless because sourcemaps are only uploaded for release builds.
9
+ *
10
+ * This module is runtime-safe: it has no Node imports, so it is safe to import into
11
+ * app code that Metro bundles (unlike the package root, which pulls in the uploader).
12
+ */
13
+ const flareSourcemapVersion = "";
14
+
15
+ //#endregion
16
+ export { flareSourcemapVersion };
@@ -0,0 +1,91 @@
1
+ import { n as resolveVersion, r as LOG_PREFIX } from "./version-DnFqflPl.mjs";
2
+ import { readFileSync } from "node:fs";
3
+ import { basename } from "node:path";
4
+ import { deflateRawSync } from "node:zlib";
5
+
6
+ //#region ../flare-api/dist/index.mjs
7
+ var FlareApiError = class extends Error {
8
+ constructor(message) {
9
+ super(message);
10
+ this.name = "FlareApiError";
11
+ }
12
+ };
13
+ const RETRIABLE_STATUS_CODES = new Set([
14
+ 429,
15
+ 502,
16
+ 503,
17
+ 504
18
+ ]);
19
+ var FlareApi = class {
20
+ constructor(endpoint, key, version) {
21
+ this.endpoint = endpoint;
22
+ this.key = key;
23
+ this.version = version;
24
+ }
25
+ uploadSourcemap(sourcemap) {
26
+ const base64GzipSourcemap = deflateRawSync(sourcemap.content).toString("base64");
27
+ return this.postWithRetry({
28
+ key: this.key,
29
+ version_id: this.version,
30
+ relative_filename: sourcemap.originalFile,
31
+ sourcemap: base64GzipSourcemap
32
+ });
33
+ }
34
+ async postWithRetry(data, maxRetries = 3) {
35
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
36
+ try {
37
+ const response = await fetch(this.endpoint, {
38
+ method: "POST",
39
+ headers: { "Content-Type": "application/json" },
40
+ body: JSON.stringify(data)
41
+ });
42
+ if (response.ok) return;
43
+ if (!RETRIABLE_STATUS_CODES.has(response.status)) {
44
+ const body = await response.text();
45
+ throw new FlareApiError(`Flare API returned ${response.status}: ${body}`);
46
+ }
47
+ if (attempt === maxRetries) throw new FlareApiError(`Flare API returned ${response.status} after ${maxRetries} attempts`);
48
+ } catch (error) {
49
+ if (error instanceof FlareApiError) throw error;
50
+ if (attempt === maxRetries) {
51
+ const message = error instanceof Error ? error.message : String(error);
52
+ throw new Error(`Network error after ${maxRetries} attempts: ${message}`, { cause: error });
53
+ }
54
+ }
55
+ await this.delay(Math.pow(2, attempt - 1) * 1e3);
56
+ }
57
+ }
58
+ delay(ms) {
59
+ return new Promise((resolve) => setTimeout(resolve, ms));
60
+ }
61
+ };
62
+
63
+ //#endregion
64
+ //#region src/uploadSourcemaps.ts
65
+ const DEFAULT_ENDPOINT = "https://flareapp.io/api/sourcemaps";
66
+ async function uploadSourcemaps(options) {
67
+ const { apiKey, sourcemap, apiEndpoint = DEFAULT_ENDPOINT } = options;
68
+ if (!apiKey) {
69
+ console.warn(`${LOG_PREFIX}: No Flare API key provided, not uploading sourcemaps.`);
70
+ return;
71
+ }
72
+ const version = resolveVersion({ version: options.version });
73
+ const bundleFilename = options.bundleFilename ?? defaultBundleFilename(sourcemap);
74
+ const content = readFileSync(sourcemap, "utf8");
75
+ const flare = new FlareApi(apiEndpoint, apiKey, version);
76
+ log(`Uploading sourcemap "${bundleFilename}" (version ${version}) to Flare.`);
77
+ await flare.uploadSourcemap({
78
+ originalFile: bundleFilename,
79
+ content
80
+ });
81
+ log("Successfully uploaded sourcemap to Flare.");
82
+ }
83
+ function defaultBundleFilename(sourcemapPath) {
84
+ return basename(sourcemapPath).replace(/\.map$/, "");
85
+ }
86
+ function log(message) {
87
+ console.log(`${LOG_PREFIX}: ${message}`);
88
+ }
89
+
90
+ //#endregion
91
+ export { uploadSourcemaps as t };
@@ -0,0 +1,96 @@
1
+ const require_version = require('./version-Z9pawNvw.cjs');
2
+ let node_fs = require("node:fs");
3
+ let node_path = require("node:path");
4
+ let node_zlib = require("node:zlib");
5
+
6
+ //#region ../flare-api/dist/index.mjs
7
+ var FlareApiError = class extends Error {
8
+ constructor(message) {
9
+ super(message);
10
+ this.name = "FlareApiError";
11
+ }
12
+ };
13
+ const RETRIABLE_STATUS_CODES = new Set([
14
+ 429,
15
+ 502,
16
+ 503,
17
+ 504
18
+ ]);
19
+ var FlareApi = class {
20
+ constructor(endpoint, key, version) {
21
+ this.endpoint = endpoint;
22
+ this.key = key;
23
+ this.version = version;
24
+ }
25
+ uploadSourcemap(sourcemap) {
26
+ const base64GzipSourcemap = (0, node_zlib.deflateRawSync)(sourcemap.content).toString("base64");
27
+ return this.postWithRetry({
28
+ key: this.key,
29
+ version_id: this.version,
30
+ relative_filename: sourcemap.originalFile,
31
+ sourcemap: base64GzipSourcemap
32
+ });
33
+ }
34
+ async postWithRetry(data, maxRetries = 3) {
35
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
36
+ try {
37
+ const response = await fetch(this.endpoint, {
38
+ method: "POST",
39
+ headers: { "Content-Type": "application/json" },
40
+ body: JSON.stringify(data)
41
+ });
42
+ if (response.ok) return;
43
+ if (!RETRIABLE_STATUS_CODES.has(response.status)) {
44
+ const body = await response.text();
45
+ throw new FlareApiError(`Flare API returned ${response.status}: ${body}`);
46
+ }
47
+ if (attempt === maxRetries) throw new FlareApiError(`Flare API returned ${response.status} after ${maxRetries} attempts`);
48
+ } catch (error) {
49
+ if (error instanceof FlareApiError) throw error;
50
+ if (attempt === maxRetries) {
51
+ const message = error instanceof Error ? error.message : String(error);
52
+ throw new Error(`Network error after ${maxRetries} attempts: ${message}`, { cause: error });
53
+ }
54
+ }
55
+ await this.delay(Math.pow(2, attempt - 1) * 1e3);
56
+ }
57
+ }
58
+ delay(ms) {
59
+ return new Promise((resolve) => setTimeout(resolve, ms));
60
+ }
61
+ };
62
+
63
+ //#endregion
64
+ //#region src/uploadSourcemaps.ts
65
+ const DEFAULT_ENDPOINT = "https://flareapp.io/api/sourcemaps";
66
+ async function uploadSourcemaps(options) {
67
+ const { apiKey, sourcemap, apiEndpoint = DEFAULT_ENDPOINT } = options;
68
+ if (!apiKey) {
69
+ console.warn(`${require_version.LOG_PREFIX}: No Flare API key provided, not uploading sourcemaps.`);
70
+ return;
71
+ }
72
+ const version = require_version.resolveVersion({ version: options.version });
73
+ const bundleFilename = options.bundleFilename ?? defaultBundleFilename(sourcemap);
74
+ const content = (0, node_fs.readFileSync)(sourcemap, "utf8");
75
+ const flare = new FlareApi(apiEndpoint, apiKey, version);
76
+ log(`Uploading sourcemap "${bundleFilename}" (version ${version}) to Flare.`);
77
+ await flare.uploadSourcemap({
78
+ originalFile: bundleFilename,
79
+ content
80
+ });
81
+ log("Successfully uploaded sourcemap to Flare.");
82
+ }
83
+ function defaultBundleFilename(sourcemapPath) {
84
+ return (0, node_path.basename)(sourcemapPath).replace(/\.map$/, "");
85
+ }
86
+ function log(message) {
87
+ console.log(`${require_version.LOG_PREFIX}: ${message}`);
88
+ }
89
+
90
+ //#endregion
91
+ Object.defineProperty(exports, 'uploadSourcemaps', {
92
+ enumerable: true,
93
+ get: function () {
94
+ return uploadSourcemaps;
95
+ }
96
+ });
@@ -0,0 +1,49 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { resolve } from "node:path";
3
+
4
+ //#region src/constants.ts
5
+ const LOG_PREFIX = "@flareapp/react-native-sourcemaps";
6
+
7
+ //#endregion
8
+ //#region src/version.ts
9
+ /**
10
+ * Resolve the sourcemap version shared by the Babel plugin and the CLI.
11
+ * Precedence: explicit `version` > `FLARE_SOURCEMAP_VERSION` env > package.json
12
+ * `version` (with a warning). Never a random value — a random default would
13
+ * silently desync the inlined runtime value from the uploaded `version_id`.
14
+ */
15
+ function resolveVersion({ version, cwd = process.cwd() } = {}) {
16
+ if (version) return version;
17
+ const envVersion = process.env.FLARE_SOURCEMAP_VERSION;
18
+ if (envVersion) return envVersion;
19
+ const pkgVersion = readPackageVersion(cwd);
20
+ if (pkgVersion) {
21
+ console.warn(`${LOG_PREFIX}: No version provided (--version or FLARE_SOURCEMAP_VERSION). Falling back to package.json version "${pkgVersion}". Use the same version when building the bundle and uploading the map, or symbolication will silently fail.`);
22
+ return pkgVersion;
23
+ }
24
+ throw new Error(`${LOG_PREFIX}: Could not resolve a sourcemap version. Pass --version, set FLARE_SOURCEMAP_VERSION, or ensure package.json has a "version".`);
25
+ }
26
+ /**
27
+ * Version resolution for the AUTOMATIC native upload path. Unlike resolveVersion it
28
+ * NEVER falls back to package.json: the hook runs in android/ or ios/, a different
29
+ * cwd than Metro, so a package.json fallback would read a different (or missing)
30
+ * file and silently desync from the version the Babel plugin inlined. The only
31
+ * input guaranteed identical to both halves is FLARE_SOURCEMAP_VERSION. Returns
32
+ * null when unresolved so the caller can skip-with-banner rather than upload a
33
+ * guaranteed-mismatched map.
34
+ */
35
+ function resolveAutoVersion(version) {
36
+ if (version) return version;
37
+ const envVersion = process.env.FLARE_SOURCEMAP_VERSION;
38
+ return envVersion ? envVersion : null;
39
+ }
40
+ function readPackageVersion(cwd) {
41
+ try {
42
+ return JSON.parse(readFileSync(resolve(cwd, "package.json"), "utf8")).version ?? null;
43
+ } catch {
44
+ return null;
45
+ }
46
+ }
47
+
48
+ //#endregion
49
+ export { resolveVersion as n, LOG_PREFIX as r, resolveAutoVersion as t };
@@ -0,0 +1,66 @@
1
+ let node_fs = require("node:fs");
2
+ let node_path = require("node:path");
3
+
4
+ //#region src/constants.ts
5
+ const LOG_PREFIX = "@flareapp/react-native-sourcemaps";
6
+
7
+ //#endregion
8
+ //#region src/version.ts
9
+ /**
10
+ * Resolve the sourcemap version shared by the Babel plugin and the CLI.
11
+ * Precedence: explicit `version` > `FLARE_SOURCEMAP_VERSION` env > package.json
12
+ * `version` (with a warning). Never a random value — a random default would
13
+ * silently desync the inlined runtime value from the uploaded `version_id`.
14
+ */
15
+ function resolveVersion({ version, cwd = process.cwd() } = {}) {
16
+ if (version) return version;
17
+ const envVersion = process.env.FLARE_SOURCEMAP_VERSION;
18
+ if (envVersion) return envVersion;
19
+ const pkgVersion = readPackageVersion(cwd);
20
+ if (pkgVersion) {
21
+ console.warn(`${LOG_PREFIX}: No version provided (--version or FLARE_SOURCEMAP_VERSION). Falling back to package.json version "${pkgVersion}". Use the same version when building the bundle and uploading the map, or symbolication will silently fail.`);
22
+ return pkgVersion;
23
+ }
24
+ throw new Error(`${LOG_PREFIX}: Could not resolve a sourcemap version. Pass --version, set FLARE_SOURCEMAP_VERSION, or ensure package.json has a "version".`);
25
+ }
26
+ /**
27
+ * Version resolution for the AUTOMATIC native upload path. Unlike resolveVersion it
28
+ * NEVER falls back to package.json: the hook runs in android/ or ios/, a different
29
+ * cwd than Metro, so a package.json fallback would read a different (or missing)
30
+ * file and silently desync from the version the Babel plugin inlined. The only
31
+ * input guaranteed identical to both halves is FLARE_SOURCEMAP_VERSION. Returns
32
+ * null when unresolved so the caller can skip-with-banner rather than upload a
33
+ * guaranteed-mismatched map.
34
+ */
35
+ function resolveAutoVersion(version) {
36
+ if (version) return version;
37
+ const envVersion = process.env.FLARE_SOURCEMAP_VERSION;
38
+ return envVersion ? envVersion : null;
39
+ }
40
+ function readPackageVersion(cwd) {
41
+ try {
42
+ return JSON.parse((0, node_fs.readFileSync)((0, node_path.resolve)(cwd, "package.json"), "utf8")).version ?? null;
43
+ } catch {
44
+ return null;
45
+ }
46
+ }
47
+
48
+ //#endregion
49
+ Object.defineProperty(exports, 'LOG_PREFIX', {
50
+ enumerable: true,
51
+ get: function () {
52
+ return LOG_PREFIX;
53
+ }
54
+ });
55
+ Object.defineProperty(exports, 'resolveAutoVersion', {
56
+ enumerable: true,
57
+ get: function () {
58
+ return resolveAutoVersion;
59
+ }
60
+ });
61
+ Object.defineProperty(exports, 'resolveVersion', {
62
+ enumerable: true,
63
+ get: function () {
64
+ return resolveVersion;
65
+ }
66
+ });
package/flare.gradle ADDED
@@ -0,0 +1,103 @@
1
+ // Flare automatic sourcemap upload for bare React Native (Android).
2
+ //
3
+ // Apply from android/app/build.gradle:
4
+ // apply from: "../../node_modules/@flareapp/react-native-sourcemaps/flare.gradle"
5
+ //
6
+ // Hooks the release JS-bundle task and, after it runs, uploads the Hermes-composed
7
+ // sourcemap to Flare. Never fails the build: the CLI exits 0 in --auto mode and the
8
+ // exec ignores its exit value as a belt-and-braces guard.
9
+
10
+ def flareProjectRoot = rootProject.projectDir.parentFile // android/ -> project root
11
+ def flareConfig = new File(flareProjectRoot, "flare.json")
12
+ // Prefer the resolved bin over `npx` (avoids npx's PATH ambiguity and any network
13
+ // fetch), matching the iOS hook. The no-extension unix bin is checked here; on
14
+ // Windows it is a `.cmd` this misses, so we fall back to `npx` there.
15
+ def flareCli = new File(flareProjectRoot, "node_modules/.bin/flare-rn-sourcemaps")
16
+
17
+ gradle.projectsEvaluated {
18
+ tasks.configureEach { task ->
19
+ def name = task.name
20
+ def isBundleTask = (name.startsWith("createBundle") || name.startsWith("bundle")) &&
21
+ name.endsWith("JsAndAssets")
22
+ // Don't require the variant name to contain "release". Brownfield/bare apps
23
+ // rename the release build type (Production, AppStore, ...), so requiring
24
+ // "release" would silently skip the upload on exactly the builds that ship.
25
+ // Skip only the dev (debug) variants, matched case-insensitively as a
26
+ // substring (symmetric with the iOS hook); the "no sourcemap -> skip" guard
27
+ // below then handles any non-bundling variant for free.
28
+ def isDebug = name.toLowerCase().contains("debug")
29
+ if (!isBundleTask || isDebug) {
30
+ return
31
+ }
32
+
33
+ task.doLast {
34
+ // Variant subdir (e.g. "release", "stagingRelease"), derived from the task
35
+ // name rather than hardcoded "release" so a renamed build type (Production,
36
+ // ...) still resolves. createBundle<Variant>JsAndAssets /
37
+ // bundle<Variant>JsAndAssets -> <variant>, decapitalised to match Gradle's
38
+ // camelCase variant name. `project` is the module this file is applied from;
39
+ // the documented usage is android/app/build.gradle, so project.projectDir is
40
+ // android/app.
41
+ def raw = name.replaceFirst(/^(createBundle|bundle)/, "").replaceFirst(/JsAndAssets$/, "")
42
+ def variant = raw.isEmpty() ? raw : raw.substring(0, 1).toLowerCase() + raw.substring(1)
43
+
44
+ // With Hermes, the map that resolves against the shipped bytecode is the
45
+ // COMPOSED map at build/generated/sourcemaps/react/<variant>/index.android.bundle.map.
46
+ // The packager map exposed via task.sourcemapOutput is the pre-Hermes metro
47
+ // map and would NOT line up, so prefer the composed map whenever it exists.
48
+ // Only fall back to sourcemapOutput for a non-Hermes (JSC) build, where the
49
+ // packager map IS the final map.
50
+ def composed = new File(
51
+ project.projectDir,
52
+ "build/generated/sourcemaps/react/${variant}/index.android.bundle.map"
53
+ )
54
+ def sourcemapPath
55
+ if (composed.exists()) {
56
+ sourcemapPath = composed.absolutePath
57
+ } else if (task.hasProperty("sourcemapOutput") && task.sourcemapOutput != null) {
58
+ sourcemapPath = task.sourcemapOutput.toString()
59
+ } else {
60
+ sourcemapPath = composed.absolutePath
61
+ }
62
+
63
+ if (!new File(sourcemapPath).exists()) {
64
+ logger.warn("@flareapp/react-native-sourcemaps: no sourcemap at ${sourcemapPath}, skipping upload.")
65
+ return
66
+ }
67
+
68
+ // Prefer the resolved .bin; fall back to npx (Windows, or a missing bin).
69
+ def baseCommand = flareCli.exists() ? [flareCli.absolutePath] : ["npx", "flare-rn-sourcemaps"]
70
+ def command = baseCommand + [
71
+ "upload",
72
+ "--sourcemap", sourcemapPath,
73
+ "--config", flareConfig.absolutePath,
74
+ "--auto",
75
+ ]
76
+
77
+ // Run via ProcessBuilder, NOT Gradle's `exec {}`. Inside a task action the
78
+ // closure delegate is the task, so a bare `exec {}` fails to resolve ("Could
79
+ // not find method exec()"), and `project.exec` is flagged by the
80
+ // configuration cache. ProcessBuilder has neither problem and inherits the
81
+ // build environment (FLARE_SOURCEMAP_VERSION / FLARE_API_KEY flow through; the
82
+ // CLI also auto-loads them from .env.local at the project root). The CLI exits
83
+ // 0 on every upload failure mode, so this can never break the build; the catch
84
+ // guards only a failure to launch the process (e.g. node not on PATH).
85
+ try {
86
+ def process = new ProcessBuilder(command)
87
+ .directory(flareProjectRoot)
88
+ .redirectErrorStream(true)
89
+ .start()
90
+ process.inputStream.eachLine { line -> logger.lifecycle(line) }
91
+ def exitCode = process.waitFor()
92
+ // The CLI always exits 0 in --auto mode (failures print the banner and
93
+ // return). A non-zero here means that contract regressed; surface it as
94
+ // a warning rather than swallowing it silently. Still never fails the build.
95
+ if (exitCode != 0) {
96
+ logger.warn("@flareapp/react-native-sourcemaps: upload CLI exited ${exitCode} (expected 0 in --auto mode); see the log above.")
97
+ }
98
+ } catch (Exception e) {
99
+ logger.warn("@flareapp/react-native-sourcemaps: could not run the upload CLI (${e.message}); skipping. Your release will report minified stack traces until the sourcemap is uploaded.")
100
+ }
101
+ }
102
+ }
103
+ }
package/package.json ADDED
@@ -0,0 +1,100 @@
1
+ {
2
+ "name": "@flareapp/react-native-sourcemaps",
3
+ "version": "2.6.0",
4
+ "description": "Upload React Native (Metro) sourcemaps to Flare",
5
+ "homepage": "https://flareapp.io",
6
+ "bugs": "https://github.com/spatie/flare-client-js/issues",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/spatie/flare-client-js.git"
10
+ },
11
+ "license": "MIT",
12
+ "author": {
13
+ "name": "Spatie",
14
+ "email": "info@spatie.be"
15
+ },
16
+ "files": [
17
+ "dist",
18
+ "flare.gradle",
19
+ "scripts"
20
+ ],
21
+ "main": "./dist/index.cjs",
22
+ "module": "./dist/index.mjs",
23
+ "types": "./dist/index.d.cts",
24
+ "bin": {
25
+ "flare-rn-sourcemaps": "./dist/bin.cjs"
26
+ },
27
+ "exports": {
28
+ ".": {
29
+ "import": {
30
+ "types": "./dist/index.d.mts",
31
+ "default": "./dist/index.mjs"
32
+ },
33
+ "require": {
34
+ "types": "./dist/index.d.cts",
35
+ "default": "./dist/index.cjs"
36
+ }
37
+ },
38
+ "./babel": {
39
+ "import": {
40
+ "types": "./dist/babel.d.mts",
41
+ "default": "./dist/babel.mjs"
42
+ },
43
+ "require": {
44
+ "types": "./dist/babel.d.cts",
45
+ "default": "./dist/babel.cjs"
46
+ }
47
+ },
48
+ "./runtime": {
49
+ "import": {
50
+ "types": "./dist/runtime.d.mts",
51
+ "default": "./dist/runtime.mjs"
52
+ },
53
+ "require": {
54
+ "types": "./dist/runtime.d.cts",
55
+ "default": "./dist/runtime.cjs"
56
+ }
57
+ },
58
+ "./expo": {
59
+ "types": "./dist/expo.d.cts",
60
+ "require": "./dist/expo.cjs",
61
+ "default": "./dist/expo.cjs"
62
+ },
63
+ "./package.json": "./package.json"
64
+ },
65
+ "engines": {
66
+ "node": ">=18"
67
+ },
68
+ "scripts": {
69
+ "prepublishOnly": "npm run build",
70
+ "build": "tsdown",
71
+ "test": "vitest run",
72
+ "typescript": "tsc --noEmit",
73
+ "release": "release-it"
74
+ },
75
+ "devDependencies": {
76
+ "@babel/core": "^7.0.0",
77
+ "@expo/config-plugins": "^56.0.9",
78
+ "@flareapp/flare-api": "*",
79
+ "@types/babel__core": "^7.0.0",
80
+ "tsdown": "^0.20.3",
81
+ "typescript": "^5.7.0",
82
+ "vitest": "^4.0.0",
83
+ "xcode": "^3.0.1"
84
+ },
85
+ "peerDependencies": {
86
+ "@babel/core": ">=7.0.0",
87
+ "@expo/config-plugins": ">=7.0.0"
88
+ },
89
+ "peerDependenciesMeta": {
90
+ "@babel/core": {
91
+ "optional": true
92
+ },
93
+ "@expo/config-plugins": {
94
+ "optional": true
95
+ }
96
+ },
97
+ "publishConfig": {
98
+ "access": "public"
99
+ }
100
+ }