@rootnode-app/cli 0.1.0 → 0.1.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/README.md +3 -1
- package/dist/bin.js +33 -5
- package/dist/discover.js +5 -3
- package/dist/plan.d.ts +9 -1
- package/dist/plan.js +16 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -18,7 +18,9 @@ key; nothing else to configure.
|
|
|
18
18
|
After every successful EAS build it finds that build's dSYMs, mapping, bundle
|
|
19
19
|
and source map and uploads them under the release the SDK reports
|
|
20
20
|
(`bundleId@version+build`). Uses `EXPO_PUBLIC_ROOTNODE_KEY` (or `ROOTNODE_KEY`).
|
|
21
|
-
|
|
21
|
+
Uploads go to the app's target for that build's platform (`ios` / `android`)
|
|
22
|
+
and to its `react-native` target, whichever exist. Never fails the build
|
|
23
|
+
unless you pass `--strict`.
|
|
22
24
|
|
|
23
25
|
## Any build
|
|
24
26
|
|
package/dist/bin.js
CHANGED
|
@@ -4,17 +4,25 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
4
4
|
const child_process_1 = require("child_process");
|
|
5
5
|
const discover_1 = require("./discover");
|
|
6
6
|
const plan_1 = require("./plan");
|
|
7
|
-
async function
|
|
8
|
-
const res = await fetch(`${o.host}/api/0/rootnode/targets/${encodeURIComponent(
|
|
7
|
+
async function lookupTarget(o, name) {
|
|
8
|
+
const res = await fetch(`${o.host}/api/0/rootnode/targets/${encodeURIComponent(name)}`, {
|
|
9
9
|
headers: { Authorization: `Bearer ${o.key}` },
|
|
10
10
|
});
|
|
11
11
|
const body = (await res.json().catch(() => ({})));
|
|
12
12
|
if (res.status === 401)
|
|
13
13
|
throw new Error('the key was rejected — use the app\'s analytics key (rn_…)');
|
|
14
|
+
if (res.status === 404)
|
|
15
|
+
return null;
|
|
14
16
|
if (!res.ok || !body.organization || !body.project)
|
|
15
17
|
throw new Error(body.detail ?? `target lookup failed (http ${res.status})`);
|
|
16
18
|
return { organization: body.organization, project: body.project };
|
|
17
19
|
}
|
|
20
|
+
async function resolveTarget(o) {
|
|
21
|
+
const t = await lookupTarget(o, o.target);
|
|
22
|
+
if (!t)
|
|
23
|
+
throw new Error(`the app has no "${o.target}" target — pass --target with one of its targets (see Rootnode → Setup)`);
|
|
24
|
+
return t;
|
|
25
|
+
}
|
|
18
26
|
function uploaderPath() {
|
|
19
27
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
20
28
|
return require('@sentry/cli').getPath();
|
|
@@ -58,13 +66,33 @@ async function main(argv) {
|
|
|
58
66
|
for (const out of outs)
|
|
59
67
|
for (const n of out.notes)
|
|
60
68
|
console.log(`rootnode: ${out.platform}: ${n}`);
|
|
61
|
-
|
|
62
|
-
if (!runs.length) {
|
|
69
|
+
if (!outs.some((out) => runsFor(out).length)) {
|
|
63
70
|
console.log('rootnode: nothing to upload');
|
|
64
71
|
return 0;
|
|
65
72
|
}
|
|
66
73
|
try {
|
|
67
|
-
|
|
74
|
+
let ok = true;
|
|
75
|
+
for (const out of outs) {
|
|
76
|
+
const runs = runsFor(out);
|
|
77
|
+
if (!runs.length)
|
|
78
|
+
continue;
|
|
79
|
+
const names = (0, plan_1.easTargetCandidates)(out.platform, o);
|
|
80
|
+
const found = [];
|
|
81
|
+
for (const name of names) {
|
|
82
|
+
const t = await lookupTarget(o, name);
|
|
83
|
+
if (t)
|
|
84
|
+
found.push({ name, t });
|
|
85
|
+
}
|
|
86
|
+
if (!found.length) {
|
|
87
|
+
console.error(`rootnode: ${out.platform}: the app has none of these targets: ${names.join(', ')} — add one in Rootnode → Setup or pass --target`);
|
|
88
|
+
ok = false;
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
for (const { name, t } of found) {
|
|
92
|
+
console.log(`rootnode: ${out.platform} → target ${name}`);
|
|
93
|
+
ok = execute(runs, (0, plan_1.uploaderEnv)(o, t)) && ok;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
68
96
|
return ok || !o.strict ? 0 : 1;
|
|
69
97
|
}
|
|
70
98
|
catch (e) {
|
package/dist/discover.js
CHANGED
|
@@ -51,13 +51,15 @@ function discoverIos(root, plist = exports.readPlist, home = (0, os_1.homedir)()
|
|
|
51
51
|
out.map = first((0, path_1.join)(buildDir, 'main.jsbundle.map'));
|
|
52
52
|
if (out.bundle && !out.map)
|
|
53
53
|
out.notes.push('no ios/build/main.jsbundle.map — add the "@rootnode-app/react-native" plugin to app.json so the build writes it');
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
// The archive's own Info.plist usually carries ApplicationProperties; the app bundle's always does.
|
|
55
|
+
const complete = (i) => (i?.CFBundleIdentifier && i?.CFBundleShortVersionString && i?.CFBundleVersion ? i : null);
|
|
56
|
+
const info = complete(plist((0, path_1.join)(arch, 'Info.plist'))?.ApplicationProperties) ?? (appName ? complete(plist((0, path_1.join)(apps, appName, 'Info.plist'))) : null);
|
|
57
|
+
if (info) {
|
|
56
58
|
out.release = (0, plan_1.releaseName)(info.CFBundleIdentifier, info.CFBundleShortVersionString, String(info.CFBundleVersion));
|
|
57
59
|
out.dist = String(info.CFBundleVersion);
|
|
58
60
|
}
|
|
59
61
|
else
|
|
60
|
-
out.notes.push('could not read the version from the archive Info.plist');
|
|
62
|
+
out.notes.push('could not read the version from the archive or app Info.plist — dSYMs still upload, the JS source map is skipped');
|
|
61
63
|
return out;
|
|
62
64
|
}
|
|
63
65
|
/** Android: R8 mapping, the release bundle + its (Hermes-composed) source map, version from build.gradle. */
|
package/dist/plan.d.ts
CHANGED
|
@@ -7,6 +7,8 @@ export interface Options {
|
|
|
7
7
|
key: string | null;
|
|
8
8
|
host: string;
|
|
9
9
|
target: string;
|
|
10
|
+
/** --target or ROOTNODE_TARGET was given — no automatic target choice. */
|
|
11
|
+
targetExplicit: boolean;
|
|
10
12
|
release: string | null;
|
|
11
13
|
dist: string | null;
|
|
12
14
|
bundle: string | null;
|
|
@@ -34,6 +36,12 @@ export declare function uploaderEnv(o: Options, t: UploadTarget): Record<string,
|
|
|
34
36
|
export declare function symbolRuns(paths: string[]): Run[];
|
|
35
37
|
/** A Hermes/JS bundle with its source map, tied to the release the SDK reports. */
|
|
36
38
|
export declare function sourcemapRun(bundle: string, map: string, release: string, dist: string | null): Run;
|
|
39
|
+
/**
|
|
40
|
+
* Which app targets one EAS build uploads to. A React Native app may report
|
|
41
|
+
* crashes through its per-platform targets (ios / android DSNs) or one
|
|
42
|
+
* react-native target — symbols go to every one of these that exists.
|
|
43
|
+
*/
|
|
44
|
+
export declare function easTargetCandidates(platform: 'ios' | 'android', o: Options): string[];
|
|
37
45
|
/** The release name the React Native SDK reports: bundleId@version+build. */
|
|
38
46
|
export declare const releaseName: (id: string, version: string, build: string) => string;
|
|
39
|
-
export declare const HELP = "rootnode \u2014 upload symbols so release crashes show real code lines\n\n rootnode eas\n After an EAS build (package.json \"eas-build-on-success\": \"rootnode eas\"):\n finds this build's dSYMs, ProGuard mapping and JS bundle + source map and\n uploads them. Never fails the build unless --strict.\n\n rootnode upload-symbols <path...>\n dSYM folders, .xcarchive, unstripped .so folders, mapping.txt.\n\n rootnode upload-sourcemaps --bundle <file> --map <file> --release <name> [--dist <build>]\n\nOptions\n --key <rn_\u2026> the app's analytics key (default: ROOTNODE_KEY or EXPO_PUBLIC_ROOTNODE_KEY)\n --target <name> which target of the app: react-native (default), ios, android\u2026\n --host <url> default https://rootnode.app\n --strict exit non-zero when an upload fails";
|
|
47
|
+
export declare const HELP = "rootnode \u2014 upload symbols so release crashes show real code lines\n\n rootnode eas\n After an EAS build (package.json \"eas-build-on-success\": \"npx @rootnode-app/cli eas\"):\n finds this build's dSYMs, ProGuard mapping and JS bundle + source map and\n uploads them. Never fails the build unless --strict.\n\n rootnode upload-symbols <path...>\n dSYM folders, .xcarchive, unstripped .so folders, mapping.txt.\n\n rootnode upload-sourcemaps --bundle <file> --map <file> --release <name> [--dist <build>]\n\nOptions\n --key <rn_\u2026> the app's analytics key (default: ROOTNODE_KEY or EXPO_PUBLIC_ROOTNODE_KEY)\n --target <name> which target of the app: react-native (default; eas picks ios/android + react-native), ios, android\u2026\n --host <url> default https://rootnode.app\n --strict exit non-zero when an upload fails";
|
package/dist/plan.js
CHANGED
|
@@ -10,6 +10,7 @@ exports.parseArgs = parseArgs;
|
|
|
10
10
|
exports.uploaderEnv = uploaderEnv;
|
|
11
11
|
exports.symbolRuns = symbolRuns;
|
|
12
12
|
exports.sourcemapRun = sourcemapRun;
|
|
13
|
+
exports.easTargetCandidates = easTargetCandidates;
|
|
13
14
|
exports.DEFAULT_HOST = 'https://rootnode.app';
|
|
14
15
|
function parseArgs(argv, env) {
|
|
15
16
|
const [name = 'help', ...rest] = argv;
|
|
@@ -17,6 +18,7 @@ function parseArgs(argv, env) {
|
|
|
17
18
|
key: env.ROOTNODE_KEY ?? env.EXPO_PUBLIC_ROOTNODE_KEY ?? null,
|
|
18
19
|
host: (env.ROOTNODE_HOST ?? exports.DEFAULT_HOST).replace(/\/$/, ''),
|
|
19
20
|
target: env.ROOTNODE_TARGET ?? 'react-native',
|
|
21
|
+
targetExplicit: !!env.ROOTNODE_TARGET,
|
|
20
22
|
release: null,
|
|
21
23
|
dist: null,
|
|
22
24
|
bundle: null,
|
|
@@ -27,7 +29,10 @@ function parseArgs(argv, env) {
|
|
|
27
29
|
const flags = {
|
|
28
30
|
'--key': (v) => (o.key = v),
|
|
29
31
|
'--host': (v) => (o.host = v.replace(/\/$/, '')),
|
|
30
|
-
'--target': (v) =>
|
|
32
|
+
'--target': (v) => {
|
|
33
|
+
o.target = v;
|
|
34
|
+
o.targetExplicit = true;
|
|
35
|
+
},
|
|
31
36
|
'--release': (v) => (o.release = v),
|
|
32
37
|
'--dist': (v) => (o.dist = v),
|
|
33
38
|
'--bundle': (v) => (o.bundle = v),
|
|
@@ -82,13 +87,21 @@ function sourcemapRun(bundle, map, release, dist) {
|
|
|
82
87
|
args: ['sourcemaps', 'upload', '--release', release, ...(dist ? ['--dist', dist] : []), '--bundle', bundle, '--bundle-sourcemap', map],
|
|
83
88
|
};
|
|
84
89
|
}
|
|
90
|
+
/**
|
|
91
|
+
* Which app targets one EAS build uploads to. A React Native app may report
|
|
92
|
+
* crashes through its per-platform targets (ios / android DSNs) or one
|
|
93
|
+
* react-native target — symbols go to every one of these that exists.
|
|
94
|
+
*/
|
|
95
|
+
function easTargetCandidates(platform, o) {
|
|
96
|
+
return o.targetExplicit ? [o.target] : [platform, 'react-native'];
|
|
97
|
+
}
|
|
85
98
|
/** The release name the React Native SDK reports: bundleId@version+build. */
|
|
86
99
|
const releaseName = (id, version, build) => `${id}@${version}+${build}`;
|
|
87
100
|
exports.releaseName = releaseName;
|
|
88
101
|
exports.HELP = `rootnode — upload symbols so release crashes show real code lines
|
|
89
102
|
|
|
90
103
|
rootnode eas
|
|
91
|
-
After an EAS build (package.json "eas-build-on-success": "rootnode eas"):
|
|
104
|
+
After an EAS build (package.json "eas-build-on-success": "npx @rootnode-app/cli eas"):
|
|
92
105
|
finds this build's dSYMs, ProGuard mapping and JS bundle + source map and
|
|
93
106
|
uploads them. Never fails the build unless --strict.
|
|
94
107
|
|
|
@@ -99,6 +112,6 @@ exports.HELP = `rootnode — upload symbols so release crashes show real code li
|
|
|
99
112
|
|
|
100
113
|
Options
|
|
101
114
|
--key <rn_…> the app's analytics key (default: ROOTNODE_KEY or EXPO_PUBLIC_ROOTNODE_KEY)
|
|
102
|
-
--target <name> which target of the app: react-native (default), ios, android…
|
|
115
|
+
--target <name> which target of the app: react-native (default; eas picks ios/android + react-native), ios, android…
|
|
103
116
|
--host <url> default ${exports.DEFAULT_HOST}
|
|
104
117
|
--strict exit non-zero when an upload fails`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rootnode-app/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Rootnode command line: upload dSYMs, ProGuard mappings, native debug files and source maps so release crashes show real code lines.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Doğan Aktı",
|