@observerkit/metro 0.2.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 ADDED
@@ -0,0 +1,118 @@
1
+ # @observerkit/metro
2
+
3
+ Metro plugin for ObserverKit: injects debug IDs into React Native bundles and uploads source maps so production stack traces symbolicate, including Hermes builds.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install --save-dev @observerkit/metro
9
+ ```
10
+
11
+ ## 1. Wrap your Metro config
12
+
13
+ ```js
14
+ // metro.config.js
15
+ const { getDefaultConfig } = require("@react-native/metro-config")
16
+ const withObserverkit = require("@observerkit/metro")
17
+
18
+ module.exports = withObserverkit(getDefaultConfig(__dirname))
19
+ ```
20
+
21
+ Debug-id injection only runs for release bundles; dev builds are untouched.
22
+
23
+ ## 2. Upload source maps from your native builds
24
+
25
+ The `observerkit-upload` CLI reads the project key from `--project-key` or the `OBSERVERKIT_PROJECT_KEY` environment variable.
26
+
27
+ ### Expo
28
+
29
+ Add the config plugin; prebuild wires both native builds automatically:
30
+
31
+ ```json
32
+ {
33
+ "expo": {
34
+ "plugins": [["@observerkit/metro/expo", { "projectKey": "YOUR_PROJECT_KEY" }]]
35
+ }
36
+ }
37
+ ```
38
+
39
+ For EAS Update bundles, upload after export:
40
+
41
+ ```bash
42
+ npx expo export && npx observerkit-upload --dir dist
43
+ ```
44
+
45
+ ### Bare React Native: iOS
46
+
47
+ Edit the "Bundle React Native code and images" build phase so a source map is emitted and uploaded:
48
+
49
+ ```sh
50
+ export SOURCEMAP_FILE="$DERIVED_FILE_DIR/main.jsbundle.map"
51
+ set -e
52
+ WITH_ENVIRONMENT="../node_modules/react-native/scripts/xcode/with-environment.sh"
53
+ REACT_NATIVE_XCODE="../node_modules/react-native/scripts/react-native-xcode.sh"
54
+ /bin/sh -c "$WITH_ENVIRONMENT $REACT_NATIVE_XCODE"
55
+ if [ -f "$SOURCEMAP_FILE" ]; then
56
+ npx observerkit-upload --map "$SOURCEMAP_FILE" --bundle "$CONFIGURATION_BUILD_DIR/main.jsbundle" --project-key "YOUR_PROJECT_KEY" || echo "[ObserverKit] source map upload failed"
57
+ fi
58
+ ```
59
+
60
+ `--bundle` points at the pre-Hermes JS bundle, which is where `observerkit-upload` finds the debugId on iOS (see Hermes below). The `|| echo` makes sure a failed upload never fails the archive, since this phase runs under `set -e`.
61
+
62
+ ### Bare React Native: Android
63
+
64
+ Append to `android/app/build.gradle`:
65
+
66
+ ```groovy
67
+ tasks.configureEach { task ->
68
+ def matcher = task.name =~ /^createBundle(\w*)ReleaseJsAndAssets$/
69
+ if (matcher.matches()) {
70
+ task.doLast {
71
+ def flavor = matcher.group(1)
72
+ def variant = flavor.isEmpty()
73
+ ? "release"
74
+ : flavor.substring(0, 1).toLowerCase() + flavor.substring(1) + "Release"
75
+ def mapFile = file("$buildDir/generated/sourcemaps/react/" + variant + "/index.android.bundle.map")
76
+ def packagerMapFile = file("$buildDir/intermediates/sourcemaps/react/" + variant + "/index.android.bundle.packager.map")
77
+ if (mapFile.exists()) {
78
+ def result = exec {
79
+ workingDir rootProject.projectDir.parentFile
80
+ environment "OBSERVERKIT_PROJECT_KEY", "YOUR_PROJECT_KEY"
81
+ commandLine "npx", "observerkit-upload", "--map", mapFile.absolutePath, "--packager-map", packagerMapFile.absolutePath
82
+ ignoreExitValue true
83
+ }
84
+ if (result.exitValue != 0) {
85
+ logger.warn("[ObserverKit] source map upload failed")
86
+ }
87
+ } else {
88
+ logger.warn("[ObserverKit] No source map found at " + mapFile + ", skipping upload")
89
+ }
90
+ }
91
+ }
92
+ }
93
+ ```
94
+
95
+ The composed map lands in `generated/sourcemaps/react/<variant>/`, while the pre-compose packager map (needed to restore the `debugId`, see Hermes below) stays behind in `intermediates/sourcemaps/react/<variant>/`. `ignoreExitValue` plus the logged warning make sure a failed upload never fails `assembleRelease`.
96
+
97
+ ## Hermes
98
+
99
+ Symbolication support targets Hermes, the React Native default JS engine; JSC is not supported.
100
+
101
+ Hermes release builds compose the Metro source map with the bytecode map, which drops the `debugId` field. `observerkit-upload` restores it automatically:
102
+
103
+ - On Android, from the packager map (`<bundle>.packager.map` next to the final map by convention, or pass `--packager-map` explicitly). The Gradle plugin keeps this file around after composing.
104
+ - On iOS, `react-native-xcode.sh` deletes the packager map once the final map is composed, so there is nothing to restore it from there. Instead `observerkit-upload` reads the trailing `//# debugId=<uuid>` comment the ObserverKit serializer appends to the pre-Hermes JS bundle (the final map path with `.map` stripped, or pass `--bundle` explicitly).
105
+
106
+ ## CLI reference
107
+
108
+ ```
109
+ observerkit-upload --map <path> [--packager-map <path>] [--bundle <path>]
110
+ observerkit-upload --dir <path>
111
+
112
+ --project-key <key> Defaults to $OBSERVERKIT_PROJECT_KEY
113
+ --endpoint <url> Defaults to $OBSERVERKIT_ENDPOINT or https://ingest.observerkit.com
114
+ ```
115
+
116
+ ## pnpm note
117
+
118
+ If your app uses pnpm and Metro is not a direct dependency, add it (`pnpm add -D metro`) so the plugin can load Metro's default serializer.
@@ -0,0 +1,199 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } }// ../plugin-core/src/upload.ts
2
+ var _promises = require('fs/promises');
3
+ var _path = require('path');
4
+
5
+ // ../plugin-core/src/json.ts
6
+ function isJsonObject(value) {
7
+ return typeof value === "object" && value !== null && !Array.isArray(value);
8
+ }
9
+
10
+ // ../plugin-core/src/upload.ts
11
+ var DEFAULT_MAX_FILE_BYTES = 50 * 1024 * 1024;
12
+ var DEFAULT_MAX_BATCH_BYTES = 100 * 1024 * 1024;
13
+ function hasDebugId(content) {
14
+ if (!content.includes('"debugId"')) return false;
15
+ try {
16
+ const parsed = JSON.parse(content.toString("utf-8"));
17
+ return isJsonObject(parsed) && typeof parsed["debugId"] === "string";
18
+ } catch (e) {
19
+ return false;
20
+ }
21
+ }
22
+ function batchBySize(entries, maxBatchBytes) {
23
+ const batches = [];
24
+ let current = [];
25
+ let currentBytes = 0;
26
+ for (const entry of entries) {
27
+ if (current.length > 0 && currentBytes + entry.byteLength > maxBatchBytes) {
28
+ batches.push(current);
29
+ current = [];
30
+ currentBytes = 0;
31
+ }
32
+ current.push(entry);
33
+ currentBytes += entry.byteLength;
34
+ }
35
+ if (current.length > 0) batches.push(current);
36
+ return batches;
37
+ }
38
+ async function deleteMapFiles(directory, mapFiles) {
39
+ const results = await Promise.allSettled(
40
+ mapFiles.map((mapFile) => _promises.unlink.call(void 0, _path.join.call(void 0, directory, mapFile)))
41
+ );
42
+ const failures = results.filter((r) => r.status === "rejected");
43
+ if (failures.length > 0) {
44
+ console.warn(
45
+ `[ObserverKit] Failed to delete ${failures.length}/${mapFiles.length} .map files`
46
+ );
47
+ }
48
+ console.log(
49
+ `[ObserverKit] Deleted ${mapFiles.length - failures.length} .map files from output`
50
+ );
51
+ }
52
+ async function uploadBatch(batch, options) {
53
+ const formData = new FormData();
54
+ for (const [index, entry] of batch.entries()) {
55
+ const content = await _promises.readFile.call(void 0, _path.join.call(void 0, options.directory, entry.mapFile));
56
+ formData.append(
57
+ `file-${index}`,
58
+ new Blob([new Uint8Array(content)], { type: "application/json" }),
59
+ entry.mapFile
60
+ );
61
+ }
62
+ const response = await fetch(options.url, {
63
+ method: "POST",
64
+ headers: {
65
+ "X-ObserverKit-Key": options.projectKey,
66
+ "X-ObserverKit-Plugin-Version": options.pluginVersion,
67
+ "X-ObserverKit-Plugin-Name": options.pluginName
68
+ },
69
+ body: formData
70
+ });
71
+ if (!response.ok) {
72
+ const text = await response.text();
73
+ throw new Error(
74
+ `[ObserverKit] Source map upload failed: ${response.status} ${text}`
75
+ );
76
+ }
77
+ }
78
+ var JS_SOURCE_MAPPING_URL = /^\/\/[#@] sourceMappingURL=\S+[ \t]*$/gm;
79
+ var CSS_SOURCE_MAPPING_URL = /^\/\*# sourceMappingURL=\S+[ \t]*\*\/[ \t]*$/gm;
80
+ function isChunkFile(name) {
81
+ return name.endsWith(".js") || name.endsWith(".mjs") || name.endsWith(".cjs") || name.endsWith(".css");
82
+ }
83
+ async function stripSourceMappingUrls(directory, files) {
84
+ let stripped = 0;
85
+ for (const file of files.filter(isChunkFile)) {
86
+ const path = _path.join.call(void 0, directory, file);
87
+ try {
88
+ const content = await _promises.readFile.call(void 0, path, "utf-8");
89
+ if (!content.includes("sourceMappingURL=")) continue;
90
+ const next = content.replace(JS_SOURCE_MAPPING_URL, "").replace(CSS_SOURCE_MAPPING_URL, "");
91
+ if (next !== content) {
92
+ await _promises.writeFile.call(void 0, path, next);
93
+ stripped += 1;
94
+ }
95
+ } catch (e2) {
96
+ }
97
+ }
98
+ if (stripped > 0) {
99
+ console.log(
100
+ `[ObserverKit] Stripped sourceMappingURL comments from ${stripped} file(s)`
101
+ );
102
+ }
103
+ }
104
+ async function uploadSourceMaps(options) {
105
+ const { projectKey, directory, endpoint, deleteAfterUpload, pluginVersion, pluginName } = options;
106
+ const maxFileBytes = _nullishCoalesce(options.maxFileBytes, () => ( DEFAULT_MAX_FILE_BYTES));
107
+ const maxBatchBytes = _nullishCoalesce(options.maxBatchBytes, () => ( DEFAULT_MAX_BATCH_BYTES));
108
+ const allFiles = options.files ? [...options.files] : await _promises.readdir.call(void 0, directory, { recursive: true, encoding: "utf-8" });
109
+ const allMapFiles = allFiles.filter((f) => f.endsWith(".map"));
110
+ if (allMapFiles.length === 0) {
111
+ console.log("[ObserverKit] No .map files found, skipping source map upload");
112
+ return;
113
+ }
114
+ try {
115
+ const uploadable = [];
116
+ let skippedNoDebugId = 0;
117
+ let skippedOversize = 0;
118
+ let skippedUnreadable = 0;
119
+ for (const mapFile of allMapFiles) {
120
+ let content;
121
+ try {
122
+ content = await _promises.readFile.call(void 0, _path.join.call(void 0, directory, mapFile));
123
+ } catch (e3) {
124
+ skippedUnreadable += 1;
125
+ continue;
126
+ }
127
+ if (!hasDebugId(content)) {
128
+ skippedNoDebugId += 1;
129
+ continue;
130
+ }
131
+ if (content.byteLength > maxFileBytes) {
132
+ skippedOversize += 1;
133
+ continue;
134
+ }
135
+ uploadable.push({ mapFile, byteLength: content.byteLength });
136
+ }
137
+ if (skippedNoDebugId > 0) {
138
+ console.log(
139
+ `[ObserverKit] Skipping ${skippedNoDebugId} source map(s) without a debugId field`
140
+ );
141
+ }
142
+ if (skippedOversize > 0) {
143
+ console.warn(
144
+ `[ObserverKit] Skipping ${skippedOversize} source map(s) over the per-file ingest limit`
145
+ );
146
+ }
147
+ if (skippedUnreadable > 0) {
148
+ console.warn(
149
+ `[ObserverKit] Skipping ${skippedUnreadable} unreadable source map(s)`
150
+ );
151
+ }
152
+ if (uploadable.length === 0) {
153
+ console.log(
154
+ "[ObserverKit] No uploadable source maps found, skipping upload"
155
+ );
156
+ return;
157
+ }
158
+ const url = `${endpoint.replace(/\/+$/, "")}/v1/sourcemaps`;
159
+ const batches = batchBySize(uploadable, maxBatchBytes);
160
+ for (const [index, batch] of batches.entries()) {
161
+ await uploadBatch(batch, { directory, url, projectKey, pluginVersion, pluginName });
162
+ console.log(
163
+ `[ObserverKit] Uploaded batch ${index + 1}/${batches.length} (${batch.length} source maps)`
164
+ );
165
+ }
166
+ } catch (err) {
167
+ if (deleteAfterUpload) {
168
+ console.warn(
169
+ "[ObserverKit] Upload failed \u2014 deleting .map files anyway so they don't ship (set deleteAfterUpload: false to keep them)"
170
+ );
171
+ }
172
+ throw err;
173
+ } finally {
174
+ if (deleteAfterUpload) {
175
+ await deleteMapFiles(directory, allMapFiles);
176
+ await stripSourceMappingUrls(directory, allFiles);
177
+ }
178
+ }
179
+ }
180
+
181
+ // ../plugin-core/src/debug-id.ts
182
+ var _uuid = require('uuid');
183
+ var OBSERVERKIT_NAMESPACE = "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d";
184
+ function generateDebugId(content) {
185
+ return _uuid.v5.call(void 0, content, OBSERVERKIT_NAMESPACE);
186
+ }
187
+
188
+ // ../plugin-core/src/snippet.ts
189
+ function buildStackKeyedDebugIdSnippet(debugId) {
190
+ const snippet = `globalThis._debugIds = globalThis._debugIds || {}; try { globalThis._debugIds[new Error().stack] = ${JSON.stringify(debugId)}; } catch (e) {}`;
191
+ return { snippet, lineCount: 1 };
192
+ }
193
+
194
+
195
+
196
+
197
+
198
+
199
+ exports.generateDebugId = generateDebugId; exports.isJsonObject = isJsonObject; exports.buildStackKeyedDebugIdSnippet = buildStackKeyedDebugIdSnippet; exports.uploadSourceMaps = uploadSourceMaps;
@@ -0,0 +1,7 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true});// ../../node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.6_tsx@4.21.0_typescript@5.9.2_yaml@2.9.0/node_modules/tsup/assets/cjs_shims.js
2
+ var getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${__filename}`).href : document.currentScript && document.currentScript.tagName.toUpperCase() === "SCRIPT" ? document.currentScript.src : new URL("main.js", document.baseURI).href;
3
+ var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
4
+
5
+
6
+
7
+ exports.importMetaUrl = importMetaUrl;
@@ -0,0 +1,199 @@
1
+ // ../plugin-core/src/upload.ts
2
+ import { readdir, readFile, unlink, writeFile } from "fs/promises";
3
+ import { join } from "path";
4
+
5
+ // ../plugin-core/src/json.ts
6
+ function isJsonObject(value) {
7
+ return typeof value === "object" && value !== null && !Array.isArray(value);
8
+ }
9
+
10
+ // ../plugin-core/src/upload.ts
11
+ var DEFAULT_MAX_FILE_BYTES = 50 * 1024 * 1024;
12
+ var DEFAULT_MAX_BATCH_BYTES = 100 * 1024 * 1024;
13
+ function hasDebugId(content) {
14
+ if (!content.includes('"debugId"')) return false;
15
+ try {
16
+ const parsed = JSON.parse(content.toString("utf-8"));
17
+ return isJsonObject(parsed) && typeof parsed["debugId"] === "string";
18
+ } catch {
19
+ return false;
20
+ }
21
+ }
22
+ function batchBySize(entries, maxBatchBytes) {
23
+ const batches = [];
24
+ let current = [];
25
+ let currentBytes = 0;
26
+ for (const entry of entries) {
27
+ if (current.length > 0 && currentBytes + entry.byteLength > maxBatchBytes) {
28
+ batches.push(current);
29
+ current = [];
30
+ currentBytes = 0;
31
+ }
32
+ current.push(entry);
33
+ currentBytes += entry.byteLength;
34
+ }
35
+ if (current.length > 0) batches.push(current);
36
+ return batches;
37
+ }
38
+ async function deleteMapFiles(directory, mapFiles) {
39
+ const results = await Promise.allSettled(
40
+ mapFiles.map((mapFile) => unlink(join(directory, mapFile)))
41
+ );
42
+ const failures = results.filter((r) => r.status === "rejected");
43
+ if (failures.length > 0) {
44
+ console.warn(
45
+ `[ObserverKit] Failed to delete ${failures.length}/${mapFiles.length} .map files`
46
+ );
47
+ }
48
+ console.log(
49
+ `[ObserverKit] Deleted ${mapFiles.length - failures.length} .map files from output`
50
+ );
51
+ }
52
+ async function uploadBatch(batch, options) {
53
+ const formData = new FormData();
54
+ for (const [index, entry] of batch.entries()) {
55
+ const content = await readFile(join(options.directory, entry.mapFile));
56
+ formData.append(
57
+ `file-${index}`,
58
+ new Blob([new Uint8Array(content)], { type: "application/json" }),
59
+ entry.mapFile
60
+ );
61
+ }
62
+ const response = await fetch(options.url, {
63
+ method: "POST",
64
+ headers: {
65
+ "X-ObserverKit-Key": options.projectKey,
66
+ "X-ObserverKit-Plugin-Version": options.pluginVersion,
67
+ "X-ObserverKit-Plugin-Name": options.pluginName
68
+ },
69
+ body: formData
70
+ });
71
+ if (!response.ok) {
72
+ const text = await response.text();
73
+ throw new Error(
74
+ `[ObserverKit] Source map upload failed: ${response.status} ${text}`
75
+ );
76
+ }
77
+ }
78
+ var JS_SOURCE_MAPPING_URL = /^\/\/[#@] sourceMappingURL=\S+[ \t]*$/gm;
79
+ var CSS_SOURCE_MAPPING_URL = /^\/\*# sourceMappingURL=\S+[ \t]*\*\/[ \t]*$/gm;
80
+ function isChunkFile(name) {
81
+ return name.endsWith(".js") || name.endsWith(".mjs") || name.endsWith(".cjs") || name.endsWith(".css");
82
+ }
83
+ async function stripSourceMappingUrls(directory, files) {
84
+ let stripped = 0;
85
+ for (const file of files.filter(isChunkFile)) {
86
+ const path = join(directory, file);
87
+ try {
88
+ const content = await readFile(path, "utf-8");
89
+ if (!content.includes("sourceMappingURL=")) continue;
90
+ const next = content.replace(JS_SOURCE_MAPPING_URL, "").replace(CSS_SOURCE_MAPPING_URL, "");
91
+ if (next !== content) {
92
+ await writeFile(path, next);
93
+ stripped += 1;
94
+ }
95
+ } catch {
96
+ }
97
+ }
98
+ if (stripped > 0) {
99
+ console.log(
100
+ `[ObserverKit] Stripped sourceMappingURL comments from ${stripped} file(s)`
101
+ );
102
+ }
103
+ }
104
+ async function uploadSourceMaps(options) {
105
+ const { projectKey, directory, endpoint, deleteAfterUpload, pluginVersion, pluginName } = options;
106
+ const maxFileBytes = options.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES;
107
+ const maxBatchBytes = options.maxBatchBytes ?? DEFAULT_MAX_BATCH_BYTES;
108
+ const allFiles = options.files ? [...options.files] : await readdir(directory, { recursive: true, encoding: "utf-8" });
109
+ const allMapFiles = allFiles.filter((f) => f.endsWith(".map"));
110
+ if (allMapFiles.length === 0) {
111
+ console.log("[ObserverKit] No .map files found, skipping source map upload");
112
+ return;
113
+ }
114
+ try {
115
+ const uploadable = [];
116
+ let skippedNoDebugId = 0;
117
+ let skippedOversize = 0;
118
+ let skippedUnreadable = 0;
119
+ for (const mapFile of allMapFiles) {
120
+ let content;
121
+ try {
122
+ content = await readFile(join(directory, mapFile));
123
+ } catch {
124
+ skippedUnreadable += 1;
125
+ continue;
126
+ }
127
+ if (!hasDebugId(content)) {
128
+ skippedNoDebugId += 1;
129
+ continue;
130
+ }
131
+ if (content.byteLength > maxFileBytes) {
132
+ skippedOversize += 1;
133
+ continue;
134
+ }
135
+ uploadable.push({ mapFile, byteLength: content.byteLength });
136
+ }
137
+ if (skippedNoDebugId > 0) {
138
+ console.log(
139
+ `[ObserverKit] Skipping ${skippedNoDebugId} source map(s) without a debugId field`
140
+ );
141
+ }
142
+ if (skippedOversize > 0) {
143
+ console.warn(
144
+ `[ObserverKit] Skipping ${skippedOversize} source map(s) over the per-file ingest limit`
145
+ );
146
+ }
147
+ if (skippedUnreadable > 0) {
148
+ console.warn(
149
+ `[ObserverKit] Skipping ${skippedUnreadable} unreadable source map(s)`
150
+ );
151
+ }
152
+ if (uploadable.length === 0) {
153
+ console.log(
154
+ "[ObserverKit] No uploadable source maps found, skipping upload"
155
+ );
156
+ return;
157
+ }
158
+ const url = `${endpoint.replace(/\/+$/, "")}/v1/sourcemaps`;
159
+ const batches = batchBySize(uploadable, maxBatchBytes);
160
+ for (const [index, batch] of batches.entries()) {
161
+ await uploadBatch(batch, { directory, url, projectKey, pluginVersion, pluginName });
162
+ console.log(
163
+ `[ObserverKit] Uploaded batch ${index + 1}/${batches.length} (${batch.length} source maps)`
164
+ );
165
+ }
166
+ } catch (err) {
167
+ if (deleteAfterUpload) {
168
+ console.warn(
169
+ "[ObserverKit] Upload failed \u2014 deleting .map files anyway so they don't ship (set deleteAfterUpload: false to keep them)"
170
+ );
171
+ }
172
+ throw err;
173
+ } finally {
174
+ if (deleteAfterUpload) {
175
+ await deleteMapFiles(directory, allMapFiles);
176
+ await stripSourceMappingUrls(directory, allFiles);
177
+ }
178
+ }
179
+ }
180
+
181
+ // ../plugin-core/src/debug-id.ts
182
+ import { v5 } from "uuid";
183
+ var OBSERVERKIT_NAMESPACE = "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d";
184
+ function generateDebugId(content) {
185
+ return v5(content, OBSERVERKIT_NAMESPACE);
186
+ }
187
+
188
+ // ../plugin-core/src/snippet.ts
189
+ function buildStackKeyedDebugIdSnippet(debugId) {
190
+ const snippet = `globalThis._debugIds = globalThis._debugIds || {}; try { globalThis._debugIds[new Error().stack] = ${JSON.stringify(debugId)}; } catch (e) {}`;
191
+ return { snippet, lineCount: 1 };
192
+ }
193
+
194
+ export {
195
+ generateDebugId,
196
+ isJsonObject,
197
+ buildStackKeyedDebugIdSnippet,
198
+ uploadSourceMaps
199
+ };
package/dist/cli.cjs ADDED
@@ -0,0 +1,156 @@
1
+ #!/usr/bin/env node
2
+ "use strict"; function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
3
+
4
+
5
+ var _chunk6RJOM2COcjs = require('./chunk-6RJOM2CO.cjs');
6
+ require('./chunk-AUOVSTQX.cjs');
7
+
8
+ // src/cli.ts
9
+ var _util = require('util');
10
+ var _path = require('path');
11
+
12
+ // src/repair.ts
13
+ var _promises = require('fs/promises');
14
+ var DEBUG_ID_COMMENT_PATTERN = /\/\/# debugId=([0-9a-f-]{36})/;
15
+ function defaultPackagerMapPath(mapPath) {
16
+ return mapPath.endsWith(".map") ? `${mapPath.slice(0, -".map".length)}.packager.map` : `${mapPath}.packager.map`;
17
+ }
18
+ function defaultBundlePath(mapPath) {
19
+ return mapPath.endsWith(".map") ? mapPath.slice(0, -".map".length) : mapPath;
20
+ }
21
+ async function readDebugIdFromMap(path) {
22
+ let content;
23
+ try {
24
+ content = await _promises.readFile.call(void 0, path, "utf-8");
25
+ } catch (e) {
26
+ return null;
27
+ }
28
+ let parsed;
29
+ try {
30
+ parsed = JSON.parse(content);
31
+ } catch (e2) {
32
+ return null;
33
+ }
34
+ if (!_chunk6RJOM2COcjs.isJsonObject.call(void 0, parsed)) return null;
35
+ return typeof parsed["debugId"] === "string" ? parsed["debugId"] : null;
36
+ }
37
+ async function readDebugIdFromBundle(path) {
38
+ let content;
39
+ try {
40
+ content = await _promises.readFile.call(void 0, path, "utf-8");
41
+ } catch (e3) {
42
+ return null;
43
+ }
44
+ const match = DEBUG_ID_COMMENT_PATTERN.exec(content);
45
+ return _nullishCoalesce(_optionalChain([match, 'optionalAccess', _ => _[1]]), () => ( null));
46
+ }
47
+ async function repairDebugId(mapPath, packagerMapPath, bundlePath) {
48
+ const content = await _promises.readFile.call(void 0, mapPath, "utf-8");
49
+ let parsed;
50
+ try {
51
+ parsed = JSON.parse(content);
52
+ } catch (e4) {
53
+ throw new Error(`[ObserverKit] ${mapPath} is not valid JSON`);
54
+ }
55
+ if (!_chunk6RJOM2COcjs.isJsonObject.call(void 0, parsed)) {
56
+ throw new Error(`[ObserverKit] ${mapPath} is not a source map object`);
57
+ }
58
+ if (typeof parsed["debugId"] === "string") return;
59
+ const packagerCandidate = _nullishCoalesce(packagerMapPath, () => ( defaultPackagerMapPath(mapPath)));
60
+ let debugId = await readDebugIdFromMap(packagerCandidate);
61
+ const bundleCandidate = _nullishCoalesce(bundlePath, () => ( defaultBundlePath(mapPath)));
62
+ if (!debugId) {
63
+ debugId = await readDebugIdFromBundle(bundleCandidate);
64
+ }
65
+ if (!debugId) {
66
+ throw new Error(
67
+ `[ObserverKit] ${mapPath} has no debugId and none was found in ${packagerCandidate} or ${bundleCandidate}. Is withObserverkit applied in metro.config.js? For Hermes iOS builds, pass --bundle pointing at the pre-Hermes JS bundle (e.g. $CONFIGURATION_BUILD_DIR/main.jsbundle).`
68
+ );
69
+ }
70
+ await _promises.writeFile.call(void 0, mapPath, JSON.stringify({ ...parsed, debugId }));
71
+ }
72
+
73
+ // src/cli.ts
74
+ var PLUGIN_VERSION = "0.2.0";
75
+ var USAGE = `Usage:
76
+ observerkit-upload --map <path> [--packager-map <path>] [--bundle <path>]
77
+ observerkit-upload --dir <path>
78
+
79
+ Options:
80
+ --map <path> Final source map to upload (repairs debugId first)
81
+ --packager-map <path> Pre-compose Metro map to copy the debugId from
82
+ --bundle <path> Pre-Hermes JS bundle to read a trailing
83
+ "//# debugId=<uuid>" comment from, used when no
84
+ packager map is available (Hermes iOS builds)
85
+ --dir <path> Upload every debug-id source map in a directory
86
+ (e.g. an expo export output)
87
+ --project-key <key> Defaults to $OBSERVERKIT_PROJECT_KEY
88
+ --endpoint <url> Defaults to $OBSERVERKIT_ENDPOINT or
89
+ https://ingest.observerkit.com`;
90
+ async function main() {
91
+ const { values } = _util.parseArgs.call(void 0, {
92
+ options: {
93
+ map: { type: "string" },
94
+ "packager-map": { type: "string" },
95
+ bundle: { type: "string" },
96
+ dir: { type: "string" },
97
+ "project-key": { type: "string" },
98
+ endpoint: { type: "string" }
99
+ }
100
+ });
101
+ const projectKey = _nullishCoalesce(values["project-key"], () => ( process.env["OBSERVERKIT_PROJECT_KEY"]));
102
+ if (!projectKey) {
103
+ console.error(
104
+ "[ObserverKit] Missing project key: pass --project-key or set OBSERVERKIT_PROJECT_KEY"
105
+ );
106
+ process.exit(1);
107
+ }
108
+ const endpoint = _nullishCoalesce(_nullishCoalesce(values.endpoint, () => ( process.env["OBSERVERKIT_ENDPOINT"])), () => ( "https://ingest.observerkit.com"));
109
+ if (!values.map && !values.dir) {
110
+ console.error(USAGE);
111
+ process.exit(1);
112
+ }
113
+ if (values.dir && (values["packager-map"] || values.bundle)) {
114
+ console.error(
115
+ "[ObserverKit] --packager-map and --bundle are ignored when --dir is set"
116
+ );
117
+ }
118
+ if (values.map) {
119
+ if (!values.map.endsWith(".map")) {
120
+ console.error(`[ObserverKit] --map must point at a .map file, got "${values.map}"`);
121
+ process.exit(1);
122
+ }
123
+ const mapPath = _path.resolve.call(void 0, values.map);
124
+ const packagerMap = values["packager-map"];
125
+ const bundle = values.bundle;
126
+ await repairDebugId(
127
+ mapPath,
128
+ packagerMap ? _path.resolve.call(void 0, packagerMap) : void 0,
129
+ bundle ? _path.resolve.call(void 0, bundle) : void 0
130
+ );
131
+ await _chunk6RJOM2COcjs.uploadSourceMaps.call(void 0, {
132
+ projectKey,
133
+ directory: _path.dirname.call(void 0, mapPath),
134
+ endpoint,
135
+ deleteAfterUpload: false,
136
+ pluginVersion: PLUGIN_VERSION,
137
+ pluginName: "metro",
138
+ files: [_path.basename.call(void 0, mapPath)]
139
+ });
140
+ return;
141
+ }
142
+ if (values.dir) {
143
+ await _chunk6RJOM2COcjs.uploadSourceMaps.call(void 0, {
144
+ projectKey,
145
+ directory: _path.resolve.call(void 0, values.dir),
146
+ endpoint,
147
+ deleteAfterUpload: false,
148
+ pluginVersion: PLUGIN_VERSION,
149
+ pluginName: "metro"
150
+ });
151
+ }
152
+ }
153
+ main().catch((err) => {
154
+ console.error(err instanceof Error ? err.message : err);
155
+ process.exit(1);
156
+ });
package/dist/cli.d.cts ADDED
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/cli.d.ts ADDED
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node