@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 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
- Never fails the build unless you pass `--strict`.
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 resolveTarget(o) {
8
- const res = await fetch(`${o.host}/api/0/rootnode/targets/${encodeURIComponent(o.target)}`, {
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
- const runs = outs.flatMap(runsFor);
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
- const ok = execute(runs, (0, plan_1.uploaderEnv)(o, await resolveTarget(o)));
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
- const info = plist((0, path_1.join)(arch, 'Info.plist'))?.ApplicationProperties;
55
- if (info?.CFBundleIdentifier && info?.CFBundleShortVersionString && info?.CFBundleVersion) {
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) => (o.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.0",
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ı",