@ship-et/buildatlas-cli 0.0.0-stage → 0.12.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.
Files changed (115) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +81 -2
  3. package/dist/browser-login.d.ts +12 -0
  4. package/dist/browser-login.d.ts.map +1 -0
  5. package/dist/browser-login.js +94 -0
  6. package/dist/browser-login.js.map +1 -0
  7. package/dist/client.d.ts +33 -0
  8. package/dist/client.d.ts.map +1 -0
  9. package/dist/client.js +164 -0
  10. package/dist/client.js.map +1 -0
  11. package/dist/client.test.d.ts +2 -0
  12. package/dist/client.test.d.ts.map +1 -0
  13. package/dist/client.test.js +158 -0
  14. package/dist/client.test.js.map +1 -0
  15. package/dist/commands/auth.d.ts +3 -0
  16. package/dist/commands/auth.d.ts.map +1 -0
  17. package/dist/commands/auth.js +88 -0
  18. package/dist/commands/auth.js.map +1 -0
  19. package/dist/commands/build.d.ts +4 -0
  20. package/dist/commands/build.d.ts.map +1 -0
  21. package/dist/commands/build.js +362 -0
  22. package/dist/commands/build.js.map +1 -0
  23. package/dist/commands/builds.d.ts +3 -0
  24. package/dist/commands/builds.d.ts.map +1 -0
  25. package/dist/commands/builds.js +102 -0
  26. package/dist/commands/builds.js.map +1 -0
  27. package/dist/commands/credentials-wizard.d.ts +25 -0
  28. package/dist/commands/credentials-wizard.d.ts.map +1 -0
  29. package/dist/commands/credentials-wizard.js +274 -0
  30. package/dist/commands/credentials-wizard.js.map +1 -0
  31. package/dist/commands/credentials.d.ts +3 -0
  32. package/dist/commands/credentials.d.ts.map +1 -0
  33. package/dist/commands/credentials.js +249 -0
  34. package/dist/commands/credentials.js.map +1 -0
  35. package/dist/commands/env.d.ts +37 -0
  36. package/dist/commands/env.d.ts.map +1 -0
  37. package/dist/commands/env.js +220 -0
  38. package/dist/commands/env.js.map +1 -0
  39. package/dist/commands/expo-tokens.d.ts +3 -0
  40. package/dist/commands/expo-tokens.d.ts.map +1 -0
  41. package/dist/commands/expo-tokens.js +103 -0
  42. package/dist/commands/expo-tokens.js.map +1 -0
  43. package/dist/commands/init.d.ts +6 -0
  44. package/dist/commands/init.d.ts.map +1 -0
  45. package/dist/commands/init.js +204 -0
  46. package/dist/commands/init.js.map +1 -0
  47. package/dist/commands/projects.d.ts +11 -0
  48. package/dist/commands/projects.d.ts.map +1 -0
  49. package/dist/commands/projects.js +148 -0
  50. package/dist/commands/projects.js.map +1 -0
  51. package/dist/commands/submit.d.ts +10 -0
  52. package/dist/commands/submit.d.ts.map +1 -0
  53. package/dist/commands/submit.js +118 -0
  54. package/dist/commands/submit.js.map +1 -0
  55. package/dist/commands/update.d.ts +10 -0
  56. package/dist/commands/update.d.ts.map +1 -0
  57. package/dist/commands/update.js +93 -0
  58. package/dist/commands/update.js.map +1 -0
  59. package/dist/config.d.ts +66 -0
  60. package/dist/config.d.ts.map +1 -0
  61. package/dist/config.js +255 -0
  62. package/dist/config.js.map +1 -0
  63. package/dist/detach.d.ts +18 -0
  64. package/dist/detach.d.ts.map +1 -0
  65. package/dist/detach.js +42 -0
  66. package/dist/detach.js.map +1 -0
  67. package/dist/eas-credentials-zip.d.ts +38 -0
  68. package/dist/eas-credentials-zip.d.ts.map +1 -0
  69. package/dist/eas-credentials-zip.js +63 -0
  70. package/dist/eas-credentials-zip.js.map +1 -0
  71. package/dist/eas-json.d.ts +21 -0
  72. package/dist/eas-json.d.ts.map +1 -0
  73. package/dist/eas-json.js +32 -0
  74. package/dist/eas-json.js.map +1 -0
  75. package/dist/index.d.ts +3 -0
  76. package/dist/index.d.ts.map +1 -0
  77. package/dist/index.js +67 -0
  78. package/dist/index.js.map +1 -0
  79. package/dist/logs.d.ts +13 -0
  80. package/dist/logs.d.ts.map +1 -0
  81. package/dist/logs.js +70 -0
  82. package/dist/logs.js.map +1 -0
  83. package/dist/packaging.test.d.ts +2 -0
  84. package/dist/packaging.test.d.ts.map +1 -0
  85. package/dist/packaging.test.js +79 -0
  86. package/dist/packaging.test.js.map +1 -0
  87. package/dist/profiles.test.d.ts +2 -0
  88. package/dist/profiles.test.d.ts.map +1 -0
  89. package/dist/profiles.test.js +56 -0
  90. package/dist/profiles.test.js.map +1 -0
  91. package/dist/project-hint.d.ts +12 -0
  92. package/dist/project-hint.d.ts.map +1 -0
  93. package/dist/project-hint.js +19 -0
  94. package/dist/project-hint.js.map +1 -0
  95. package/dist/project-type.d.ts +43 -0
  96. package/dist/project-type.d.ts.map +1 -0
  97. package/dist/project-type.js +88 -0
  98. package/dist/project-type.js.map +1 -0
  99. package/dist/project-type.test.d.ts +2 -0
  100. package/dist/project-type.test.d.ts.map +1 -0
  101. package/dist/project-type.test.js +77 -0
  102. package/dist/project-type.test.js.map +1 -0
  103. package/dist/prompt.d.ts +56 -0
  104. package/dist/prompt.d.ts.map +1 -0
  105. package/dist/prompt.js +157 -0
  106. package/dist/prompt.js.map +1 -0
  107. package/dist/tarball.d.ts +39 -0
  108. package/dist/tarball.d.ts.map +1 -0
  109. package/dist/tarball.js +208 -0
  110. package/dist/tarball.js.map +1 -0
  111. package/dist/tarball.test.d.ts +2 -0
  112. package/dist/tarball.test.d.ts.map +1 -0
  113. package/dist/tarball.test.js +97 -0
  114. package/dist/tarball.test.js.map +1 -0
  115. package/package.json +55 -4
package/dist/config.js ADDED
@@ -0,0 +1,255 @@
1
+ import { execFile as execFileCb } from 'node:child_process';
2
+ import { chmod, mkdir, readFile, writeFile } from 'node:fs/promises';
3
+ import { homedir } from 'node:os';
4
+ import { dirname, join } from 'node:path';
5
+ import { promisify } from 'node:util';
6
+ import pc from 'picocolors';
7
+ import { z } from 'zod';
8
+ const execFile = promisify(execFileCb);
9
+ /**
10
+ * The instance this CLI ships pointed at, so a first-time user does not have to type a
11
+ * URL. Overridden by a stored login, `--api`, or BUILDATLAS_API_URL — this is only the
12
+ * fallback when nothing else says otherwise. Baked at publish via BUILDATLAS_DEFAULT_API_URL
13
+ * for a differently-hosted build.
14
+ */
15
+ export const DEFAULT_API_URL = process.env.BUILDATLAS_DEFAULT_API_URL ?? 'https://buildatlas.ship.et';
16
+ /**
17
+ * Credentials live in ~/.buildatlas/auth.json at mode 0600. Env vars win over the
18
+ * file so CI can inject a token without writing to disk.
19
+ */
20
+ const configSchema = z.object({
21
+ apiUrl: z.string().default(DEFAULT_API_URL),
22
+ token: z.string().optional(),
23
+ email: z.string().optional(),
24
+ });
25
+ export const configPath = () => join(homedir(), '.buildatlas', 'auth.json');
26
+ /**
27
+ * Where the CLI used to keep its config, back when the command was `atlas`.
28
+ * Read as a fallback so an existing install does not silently become logged out
29
+ * after an upgrade; never written to.
30
+ */
31
+ const legacyConfigPath = () => join(homedir(), '.atlas', 'auth.json');
32
+ /**
33
+ * The API URL that was actually stored, as opposed to the localhost default.
34
+ *
35
+ * `login` needs the distinction: someone who installed this from npm and runs
36
+ * `buildatlas login` with no `--api` should be asked where their server is, not
37
+ * quietly pointed at localhost and then shown a connection error.
38
+ */
39
+ export async function storedApiUrl() {
40
+ for (const path of [configPath(), legacyConfigPath()]) {
41
+ try {
42
+ const parsed = configSchema.partial().safeParse(JSON.parse(await readFile(path, 'utf8')));
43
+ if (parsed.success && parsed.data.apiUrl)
44
+ return parsed.data.apiUrl;
45
+ }
46
+ catch {
47
+ // Missing or unreadable; the caller treats that as "not configured".
48
+ }
49
+ }
50
+ return process.env.BUILDATLAS_API_URL ?? process.env.ATLAS_API_URL;
51
+ }
52
+ export async function loadConfig() {
53
+ let fromFile = {};
54
+ for (const path of [configPath(), legacyConfigPath()]) {
55
+ try {
56
+ fromFile = JSON.parse(await readFile(path, 'utf8'));
57
+ break;
58
+ }
59
+ catch {
60
+ // No config yet is normal — a fresh machine or a CI runner.
61
+ }
62
+ }
63
+ const parsed = configSchema.safeParse(fromFile);
64
+ const base = parsed.success ? parsed.data : configSchema.parse({});
65
+ return {
66
+ // BUILDATLAS_* is the current spelling; ATLAS_* still works so existing CI
67
+ // jobs keep running after the rename.
68
+ apiUrl: process.env.BUILDATLAS_API_URL ?? process.env.ATLAS_API_URL ?? base.apiUrl,
69
+ token: process.env.BUILDATLAS_TOKEN ?? process.env.ATLAS_TOKEN ?? base.token,
70
+ email: base.email,
71
+ };
72
+ }
73
+ export async function saveConfig(config) {
74
+ const path = configPath();
75
+ await mkdir(dirname(path), { recursive: true, mode: 0o700 });
76
+ await writeFile(path, `${JSON.stringify(config, null, 2)}\n`, { mode: 0o600 });
77
+ // writeFile only applies the mode when creating the file; enforce it for the
78
+ // overwrite case too so an existing world-readable file gets locked down.
79
+ await chmod(path, 0o600);
80
+ }
81
+ /**
82
+ * The project slug from the environment.
83
+ *
84
+ * BUILDATLAS_PROJECT is the current spelling; ATLAS_PROJECT is still honoured so
85
+ * CI jobs written before the CLI was renamed keep working.
86
+ */
87
+ export function defaultProject() {
88
+ return process.env.BUILDATLAS_PROJECT ?? process.env.ATLAS_PROJECT;
89
+ }
90
+ /**
91
+ * Works out which project a command applies to.
92
+ *
93
+ * Order: `--project`, then the environment, then the app you are standing in.
94
+ * Reading it off disk is the point — you are already in the project directory, so
95
+ * requiring an env var to say so is make-work, and forgetting it produced "No
96
+ * project specified" for a project that was right there.
97
+ *
98
+ * `expo.extra.buildatlas.project` wins over `expo.slug`, for the case where the
99
+ * BuildAtlas project is named differently from the Expo one.
100
+ *
101
+ * Inference is reported by callers rather than applied silently: guessing wrong
102
+ * and uploading a keystore to another project is worse than an error.
103
+ */
104
+ /**
105
+ * The app config as Expo itself resolves it.
106
+ *
107
+ * `npx expo config --type public` is Expo's own resolver, so a dynamic
108
+ * `app.config.js` — plugins, environment switches, computed identifiers and all —
109
+ * comes back fully evaluated. That is the only way to read a dynamic config
110
+ * correctly; anything else is guessing at what the JavaScript would have returned.
111
+ *
112
+ * An earlier version of this CLI refused to do this on the grounds that it means
113
+ * running the project's own code. That was the wrong line to draw: `buildatlas
114
+ * build` already runs that code on the runner during prebuild and bundling, this
115
+ * runs in the developer's own project at their own request, and it is the exact
116
+ * command Expo's documentation tells people to run. Refusing only meant every
117
+ * command needed `--project` spelled out.
118
+ *
119
+ * Slow — a few seconds, because it loads the project's config chain — so it is only
120
+ * reached when there is no static `app.json` to read instead.
121
+ */
122
+ async function readExpoConfig(dir) {
123
+ const dynamic = await dynamicConfigIn(dir);
124
+ if (!dynamic)
125
+ return undefined;
126
+ // Printed because it is noticeably slower than reading a file, and silence here
127
+ // looks like a hang.
128
+ process.stderr.write(pc.dim(` reading ${dynamic} via \`expo config\`…\n`));
129
+ try {
130
+ const { stdout } = await execFile('npx', ['--yes', 'expo', 'config', '--type', 'public', '--json'], {
131
+ cwd: dir,
132
+ // Expo prints notices before the JSON, and a large config is a large payload.
133
+ maxBuffer: 32 * 1024 * 1024,
134
+ timeout: 120_000,
135
+ });
136
+ // Anything before the first brace is Expo's own chatter.
137
+ const start = stdout.indexOf('{');
138
+ if (start === -1)
139
+ return undefined;
140
+ const parsed = JSON.parse(stdout.slice(start));
141
+ return {
142
+ slug: parsed.extra?.buildatlas?.project ?? parsed.slug,
143
+ name: parsed.name,
144
+ version: parsed.version,
145
+ owner: parsed.owner,
146
+ easProjectId: parsed.extra?.eas?.projectId,
147
+ bundleIdentifier: parsed.ios?.bundleIdentifier,
148
+ androidPackage: parsed.android?.package,
149
+ fromDynamicConfig: dynamic,
150
+ };
151
+ }
152
+ catch (err) {
153
+ // Not fatal: the caller falls back to asking for --slug, and the reason is worth
154
+ // showing because "expo config" failing usually means dependencies are not
155
+ // installed, which the build would trip over too.
156
+ process.stderr.write(pc.dim(` (could not evaluate ${dynamic}: ${err.message.split('\n')[0]})\n`));
157
+ return undefined;
158
+ }
159
+ }
160
+ /** Everything a caller might want from the app config, static or dynamic. */
161
+ export async function readResolvedAppConfig(dir) {
162
+ try {
163
+ const raw = await readFile(join(dir, 'app.json'), 'utf8');
164
+ const expo = JSON.parse(raw).expo ?? {};
165
+ return {
166
+ slug: expo.extra?.buildatlas?.project ?? expo.slug,
167
+ name: expo.name,
168
+ version: expo.version,
169
+ owner: expo.owner,
170
+ easProjectId: expo.extra?.eas?.projectId,
171
+ bundleIdentifier: expo.ios?.bundleIdentifier,
172
+ androidPackage: expo.android?.package,
173
+ };
174
+ }
175
+ catch {
176
+ // No static config; ask Expo.
177
+ }
178
+ return readExpoConfig(dir);
179
+ }
180
+ export async function resolveProject(explicit, dir = process.cwd()) {
181
+ if (explicit)
182
+ return { slug: explicit, source: 'flag' };
183
+ const fromEnv = defaultProject();
184
+ if (fromEnv)
185
+ return { slug: fromEnv, source: 'env' };
186
+ // Only app.json. Evaluating a dynamic app.config.js means running the project's
187
+ // own JavaScript just to learn a name, which is not a trade worth making.
188
+ try {
189
+ const raw = await readFile(join(dir, 'app.json'), 'utf8');
190
+ const expo = JSON.parse(raw).expo ?? {};
191
+ const configured = expo.extra?.buildatlas?.project;
192
+ if (typeof configured === 'string' && configured) {
193
+ return { slug: configured, source: 'config' };
194
+ }
195
+ if (typeof expo.slug === 'string' && expo.slug) {
196
+ return { slug: expo.slug, source: 'app.json' };
197
+ }
198
+ }
199
+ catch {
200
+ // No app.json, or unparseable. Fall through to the dynamic config below.
201
+ }
202
+ // Only now, because it costs seconds where reading a file costs nothing.
203
+ const dynamic = await readExpoConfig(dir);
204
+ if (dynamic?.slug) {
205
+ return { slug: dynamic.slug, source: 'app.config.js' };
206
+ }
207
+ throw new Error('No project specified. Pass --project <slug>, set BUILDATLAS_PROJECT, or run this ' +
208
+ `from a directory whose app.json has an "expo.slug".\n${await noProjectAdvice(dir)}`);
209
+ }
210
+ /**
211
+ * The dynamic config filenames Expo accepts, in its own resolution order.
212
+ *
213
+ * Used only to explain a failure. A project with one of these has no app.json and
214
+ * never will, so telling its owner to go and find one wastes their afternoon — which
215
+ * is exactly what the old message did.
216
+ */
217
+ const DYNAMIC_CONFIGS = ['app.config.ts', 'app.config.js', 'app.config.mjs', 'app.config.cjs'];
218
+ async function dynamicConfigIn(dir) {
219
+ for (const name of DYNAMIC_CONFIGS) {
220
+ try {
221
+ await readFile(join(dir, name), 'utf8');
222
+ return name;
223
+ }
224
+ catch {
225
+ // Not this one.
226
+ }
227
+ }
228
+ return undefined;
229
+ }
230
+ /**
231
+ * What to tell someone whose directory has no usable static config.
232
+ *
233
+ * Shared so `build`, `projects create` and everything else give the same answer,
234
+ * and so the two genuinely different causes get genuinely different advice: a
235
+ * dynamic config, which we will not evaluate, versus an app that lives in a
236
+ * subdirectory.
237
+ */
238
+ export async function noProjectAdvice(dir) {
239
+ const dynamic = await dynamicConfigIn(dir);
240
+ if (dynamic) {
241
+ // Reaching here means `expo config` was tried and did not yield a slug — so the
242
+ // advice is about why that failed, not about passing flags instead.
243
+ return (`Found ${dynamic}, but evaluating it with \`expo config\` produced no slug.\n` +
244
+ ` Check it runs at all: npx expo config --type public\n` +
245
+ ` A failure there is usually missing dependencies (run your install first) or an ` +
246
+ `error thrown by the config itself.\n` +
247
+ ` To skip evaluation entirely, pass --project <slug>, or put an app.json next to it ` +
248
+ `with just "expo": { "extra": { "buildatlas": { "project": "<slug>" } } } — that is read ` +
249
+ `directly and does not conflict with ${dynamic}.`);
250
+ }
251
+ return (`No app.json or app.config.js in ${dir}. If the app lives in a subdirectory — common in a ` +
252
+ `monorepo — point at it with --dir <path> for 'projects create', or run the command from ` +
253
+ `there.`);
254
+ }
255
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Makes Ctrl-C detach from a build rather than look like it stopped one.
3
+ *
4
+ * Nothing in the CLI ever cancelled a build on Ctrl-C — the signal only killed the
5
+ * local process, and the build carried on. But it exited in silence, which is
6
+ * indistinguishable from having stopped it, so people reasonably assume they just
7
+ * killed their build.
8
+ *
9
+ * Cancelling is a deliberate act with a cost: it throws away however many minutes
10
+ * of compute have already gone in, and on a shared runner it is someone else's
11
+ * queue position too. So it stays an explicit command, never a keystroke, and this
12
+ * says so at the moment the question arises.
13
+ *
14
+ * A second Ctrl-C exits immediately, because a person pressing it twice wants out
15
+ * of the terminal, not another paragraph.
16
+ */
17
+ export declare function detachOnInterrupt(buildId: string): () => void;
18
+ //# sourceMappingURL=detach.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detach.d.ts","sourceRoot":"","sources":["../src/detach.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,IAAI,CA8B7D"}
package/dist/detach.js ADDED
@@ -0,0 +1,42 @@
1
+ import pc from 'picocolors';
2
+ /**
3
+ * Makes Ctrl-C detach from a build rather than look like it stopped one.
4
+ *
5
+ * Nothing in the CLI ever cancelled a build on Ctrl-C — the signal only killed the
6
+ * local process, and the build carried on. But it exited in silence, which is
7
+ * indistinguishable from having stopped it, so people reasonably assume they just
8
+ * killed their build.
9
+ *
10
+ * Cancelling is a deliberate act with a cost: it throws away however many minutes
11
+ * of compute have already gone in, and on a shared runner it is someone else's
12
+ * queue position too. So it stays an explicit command, never a keystroke, and this
13
+ * says so at the moment the question arises.
14
+ *
15
+ * A second Ctrl-C exits immediately, because a person pressing it twice wants out
16
+ * of the terminal, not another paragraph.
17
+ */
18
+ export function detachOnInterrupt(buildId) {
19
+ let warned = false;
20
+ const onInterrupt = () => {
21
+ if (warned) {
22
+ // 130 is the conventional "terminated by SIGINT" status.
23
+ process.exit(130);
24
+ }
25
+ warned = true;
26
+ console.log(`\n${pc.yellow('detached')} — the build is still running on the server.\n` +
27
+ ` follow again : ${pc.bold(`buildatlas logs ${buildId}`)}\n` +
28
+ ` stop it : ${pc.bold(`buildatlas cancel ${buildId}`)}\n` +
29
+ pc.dim(' (press Ctrl-C again to leave immediately)'));
30
+ // Exit cleanly rather than non-zero: detaching is not a build failure, and a
31
+ // non-zero status here would fail a CI step that was only being tidied up.
32
+ process.exit(0);
33
+ };
34
+ process.on('SIGINT', onInterrupt);
35
+ // SIGTERM too: a closed terminal or a `kill` should read the same way.
36
+ process.on('SIGTERM', onInterrupt);
37
+ return () => {
38
+ process.off('SIGINT', onInterrupt);
39
+ process.off('SIGTERM', onInterrupt);
40
+ };
41
+ }
42
+ //# sourceMappingURL=detach.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detach.js","sourceRoot":"","sources":["../src/detach.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,YAAY,CAAC;AAE5B;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,iBAAiB,CAAC,OAAe;IAC/C,IAAI,MAAM,GAAG,KAAK,CAAC;IAEnB,MAAM,WAAW,GAAG,GAAG,EAAE;QACvB,IAAI,MAAM,EAAE,CAAC;YACX,yDAAyD;YACzD,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACpB,CAAC;QACD,MAAM,GAAG,IAAI,CAAC;QAEd,OAAO,CAAC,GAAG,CACT,KAAK,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,gDAAgD;YACxE,oBAAoB,EAAE,CAAC,IAAI,CAAC,mBAAmB,OAAO,EAAE,CAAC,IAAI;YAC7D,oBAAoB,EAAE,CAAC,IAAI,CAAC,qBAAqB,OAAO,EAAE,CAAC,IAAI;YAC/D,EAAE,CAAC,GAAG,CAAC,6CAA6C,CAAC,CACxD,CAAC;QAEF,6EAA6E;QAC7E,2EAA2E;QAC3E,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC;IAEF,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;IAClC,uEAAuE;IACvE,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC;IAEnC,OAAO,GAAG,EAAE;QACV,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;QACnC,OAAO,CAAC,GAAG,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC;IACtC,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Reading the zip `eas credentials` hands you when you download a distribution
3
+ * certificate.
4
+ *
5
+ * The archive looks like this:
6
+ *
7
+ * DistCert_C8FL4SPYK4.p12 the certificate *and* private key
8
+ * DistCertPrivateKey_C8FL4SPYK4.txt the same key again, as PEM
9
+ * DistCertCredentials_C8FL4SPYK4.md prose, containing the .p12 password
10
+ *
11
+ * The password is machine-generated and 24 characters long, so transcribing it by
12
+ * hand is both tedious and the most likely place to introduce a typo — one that then
13
+ * surfaces as a keychain error mid-build rather than as "wrong password". Since the
14
+ * archive already contains it, read it.
15
+ *
16
+ * `unzip -p` rather than a zip library: it is present on macOS and every Linux that
17
+ * can run a build, and this avoids a dependency for one file format. Entries are
18
+ * streamed to stdout, never written to disk, so nothing has to be cleaned up
19
+ * afterwards and a crash cannot leave a private key in a temp directory.
20
+ */
21
+ export interface EasCertificateBundle {
22
+ p12: Buffer;
23
+ password?: string;
24
+ /** For the label, and so the log can say which file it came from. */
25
+ p12Name: string;
26
+ /** Present when the archive also ships the key separately, which EAS's does. */
27
+ hasSeparateKey: boolean;
28
+ }
29
+ /**
30
+ * Pulls the certificate and its password out of an EAS credentials zip.
31
+ *
32
+ * Returns undefined when the archive is not one of these, so the caller can fall
33
+ * back to treating the path as a plain `.p12` rather than guessing.
34
+ */
35
+ export declare function readEasCertificateZip(zipPath: string): Promise<EasCertificateBundle | undefined>;
36
+ /** Whether a path is worth trying to read as a credentials archive. */
37
+ export declare function looksLikeZip(path: string): boolean;
38
+ //# sourceMappingURL=eas-credentials-zip.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"eas-credentials-zip.d.ts","sourceRoot":"","sources":["../src/eas-credentials-zip.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,MAAM,WAAW,oBAAoB;IACnC,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,qEAAqE;IACrE,OAAO,EAAE,MAAM,CAAC;IAChB,gFAAgF;IAChF,cAAc,EAAE,OAAO,CAAC;CACzB;AAkBD;;;;;GAKG;AACH,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,oBAAoB,GAAG,SAAS,CAAC,CAoC3C;AAED,uEAAuE;AACvE,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAElD"}
@@ -0,0 +1,63 @@
1
+ import { execFile as execFileCb } from 'node:child_process';
2
+ import { promisify } from 'node:util';
3
+ const execFile = promisify(execFileCb);
4
+ async function listEntries(zipPath) {
5
+ const { stdout } = await execFile('unzip', ['-Z', '-1', zipPath], { maxBuffer: 1024 * 1024 });
6
+ return stdout
7
+ .split('\n')
8
+ .map((l) => l.trim())
9
+ .filter(Boolean);
10
+ }
11
+ async function readEntry(zipPath, entry) {
12
+ const { stdout } = await execFile('unzip', ['-p', zipPath, entry], {
13
+ encoding: 'buffer',
14
+ maxBuffer: 32 * 1024 * 1024,
15
+ });
16
+ return stdout;
17
+ }
18
+ /**
19
+ * Pulls the certificate and its password out of an EAS credentials zip.
20
+ *
21
+ * Returns undefined when the archive is not one of these, so the caller can fall
22
+ * back to treating the path as a plain `.p12` rather than guessing.
23
+ */
24
+ export async function readEasCertificateZip(zipPath) {
25
+ let entries;
26
+ try {
27
+ entries = await listEntries(zipPath);
28
+ }
29
+ catch {
30
+ // Not a readable zip, or no `unzip` on this machine. Either way the caller's
31
+ // plain-file path is the right fallback.
32
+ return undefined;
33
+ }
34
+ const p12Name = entries.find((e) => e.toLowerCase().endsWith('.p12'));
35
+ if (!p12Name)
36
+ return undefined;
37
+ const p12 = await readEntry(zipPath, p12Name);
38
+ // The password sits in a markdown file as a labelled line:
39
+ // - Apple Distribution Certificate password: <24 chars>
40
+ // Matched on the label rather than the filename, since only the label is
41
+ // documented behaviour and the filename carries a per-certificate suffix.
42
+ let password;
43
+ const notes = entries.filter((e) => e.toLowerCase().endsWith('.md'));
44
+ for (const note of notes) {
45
+ const text = (await readEntry(zipPath, note)).toString('utf8');
46
+ const match = text.match(/password:\s*(\S+)/i);
47
+ if (match?.[1]) {
48
+ password = match[1];
49
+ break;
50
+ }
51
+ }
52
+ return {
53
+ p12,
54
+ password,
55
+ p12Name,
56
+ hasSeparateKey: entries.some((e) => /privatekey.*\.txt$/i.test(e)),
57
+ };
58
+ }
59
+ /** Whether a path is worth trying to read as a credentials archive. */
60
+ export function looksLikeZip(path) {
61
+ return path.toLowerCase().endsWith('.zip');
62
+ }
63
+ //# sourceMappingURL=eas-credentials-zip.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"eas-credentials-zip.js","sourceRoot":"","sources":["../src/eas-credentials-zip.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,IAAI,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAC5D,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEtC,MAAM,QAAQ,GAAG,SAAS,CAAC,UAAU,CAAC,CAAC;AAgCvC,KAAK,UAAU,WAAW,CAAC,OAAe;IACxC,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,QAAQ,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,GAAG,IAAI,EAAE,CAAC,CAAC;IAC9F,OAAO,MAAM;SACV,KAAK,CAAC,IAAI,CAAC;SACX,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;SACpB,MAAM,CAAC,OAAO,CAAC,CAAC;AACrB,CAAC;AAED,KAAK,UAAU,SAAS,CAAC,OAAe,EAAE,KAAa;IACrD,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,QAAQ,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE;QACjE,QAAQ,EAAE,QAAQ;QAClB,SAAS,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI;KAC5B,CAAC,CAAC;IACH,OAAO,MAA2B,CAAC;AACrC,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,OAAe;IAEf,IAAI,OAAiB,CAAC;IACtB,IAAI,CAAC;QACH,OAAO,GAAG,MAAM,WAAW,CAAC,OAAO,CAAC,CAAC;IACvC,CAAC;IAAC,MAAM,CAAC;QACP,6EAA6E;QAC7E,yCAAyC;QACzC,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;IACtE,IAAI,CAAC,OAAO;QAAE,OAAO,SAAS,CAAC;IAE/B,MAAM,GAAG,GAAG,MAAM,SAAS,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAE9C,2DAA2D;IAC3D,0DAA0D;IAC1D,yEAAyE;IACzE,0EAA0E;IAC1E,IAAI,QAA4B,CAAC;IACjC,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;IACrE,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,IAAI,GAAG,CAAC,MAAM,SAAS,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QAC/D,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,oBAAoB,CAAC,CAAC;QAC/C,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACf,QAAQ,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;YACpB,MAAM;QACR,CAAC;IACH,CAAC;IAED,OAAO;QACL,GAAG;QACH,QAAQ;QACR,OAAO;QACP,cAAc,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,qBAAqB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;KACnE,CAAC;AACJ,CAAC;AAED,uEAAuE;AACvE,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,OAAO,IAAI,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;AAC7C,CAAC"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Deliberately untyped/loose — this is a read-only, best-effort peek at eas.json
3
+ * from the CLI side. Full validation belongs to the worker (`@buildatlas/shared`'s
4
+ * `eas-config.ts`), which the CLI does not depend on: it ships as a standalone
5
+ * published npm package, and `@buildatlas/shared` is a private workspace package
6
+ * that would not resolve for anyone who `npm install`s `buildatlas`.
7
+ */
8
+ export interface ParsedEasJson {
9
+ build?: Record<string, {
10
+ extends?: string;
11
+ env?: Record<string, string>;
12
+ }>;
13
+ }
14
+ export declare function readEasJson(projectDir: string): Promise<ParsedEasJson>;
15
+ /**
16
+ * Flattens a profile's `env` block by walking its `extends` chain root-first —
17
+ * the same precedence eas-cli (and BuildAtlas's worker, see
18
+ * `packages/shared/src/eas-config.ts`'s `resolveProfile`) use.
19
+ */
20
+ export declare function resolveProfileEnv(easJson: ParsedEasJson, profileName: string): Record<string, string>;
21
+ //# sourceMappingURL=eas-json.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"eas-json.d.ts","sourceRoot":"","sources":["../src/eas-json.ts"],"names":[],"mappings":"AAGA;;;;;;GAMG;AACH,MAAM,WAAW,aAAa;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,CAAC,CAAC;CAC5E;AAED,wBAAsB,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAG5E;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,aAAa,EACtB,WAAW,EAAE,MAAM,GAClB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAoBxB"}
@@ -0,0 +1,32 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ export async function readEasJson(projectDir) {
4
+ const raw = await readFile(join(projectDir, 'eas.json'), 'utf8');
5
+ return JSON.parse(raw);
6
+ }
7
+ /**
8
+ * Flattens a profile's `env` block by walking its `extends` chain root-first —
9
+ * the same precedence eas-cli (and BuildAtlas's worker, see
10
+ * `packages/shared/src/eas-config.ts`'s `resolveProfile`) use.
11
+ */
12
+ export function resolveProfileEnv(easJson, profileName) {
13
+ const profiles = easJson.build ?? {};
14
+ const chain = [];
15
+ const seen = new Set();
16
+ let cursor = profileName;
17
+ while (cursor) {
18
+ // A circular `extends` here is a pre-existing eas.json problem; the actual
19
+ // build will fail loudly on it later, so this just stops walking rather than
20
+ // duplicating that validation.
21
+ if (seen.has(cursor))
22
+ break;
23
+ seen.add(cursor);
24
+ const profile = profiles[cursor];
25
+ if (!profile)
26
+ break;
27
+ chain.unshift(profile.env ?? {});
28
+ cursor = profile.extends;
29
+ }
30
+ return chain.reduce((acc, env) => ({ ...acc, ...env }), {});
31
+ }
32
+ //# sourceMappingURL=eas-json.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"eas-json.js","sourceRoot":"","sources":["../src/eas-json.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAajC,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,UAAkB;IAClD,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,UAAU,EAAE,UAAU,CAAC,EAAE,MAAM,CAAC,CAAC;IACjE,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAkB,CAAC;AAC1C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAC/B,OAAsB,EACtB,WAAmB;IAEnB,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC;IACrC,MAAM,KAAK,GAA6B,EAAE,CAAC;IAC3C,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAE/B,IAAI,MAAM,GAAuB,WAAW,CAAC;IAC7C,OAAO,MAAM,EAAE,CAAC;QACd,2EAA2E;QAC3E,6EAA6E;QAC7E,+BAA+B;QAC/B,IAAI,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC;YAAE,MAAM;QAC5B,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACjB,MAAM,OAAO,GACX,QAAQ,CAAC,MAAM,CAAC,CAAC;QACnB,IAAI,CAAC,OAAO;YAAE,MAAM;QACpB,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC;QACjC,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAC3B,CAAC;IAED,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,GAAG,EAAE,GAAG,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;AAC9D,CAAC"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
package/dist/index.js ADDED
@@ -0,0 +1,67 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from 'node:module';
3
+ import { Command } from 'commander';
4
+ import pc from 'picocolors';
5
+ import { registerAuthCommands } from './commands/auth.js';
6
+ import { registerBuildCommand } from './commands/build.js';
7
+ import { registerBuildQueryCommands } from './commands/builds.js';
8
+ import { registerCredentialCommands } from './commands/credentials.js';
9
+ import { registerEnvCommand } from './commands/env.js';
10
+ import { registerExpoTokenCommands } from './commands/expo-tokens.js';
11
+ import { registerInitCommand } from './commands/init.js';
12
+ import { registerProjectCommands } from './commands/projects.js';
13
+ import { registerSubmitCommand } from './commands/submit.js';
14
+ import { registerUpdateCommand } from './commands/update.js';
15
+ import { AtlasApiError, apiRequest } from './client.js';
16
+ import { loadConfig } from './config.js';
17
+ // Read the version rather than repeating it. It was hardcoded here, drifted from
18
+ // package.json, and a CLI whose --version lies is worse than one that has none:
19
+ // it makes "did my install actually update?" unanswerable.
20
+ //
21
+ // '../package.json' resolves to the same file from src/index.ts in dev and from
22
+ // dist/index.js once built.
23
+ const { version } = createRequire(import.meta.url)('../package.json');
24
+ const program = new Command();
25
+ program
26
+ .name('buildatlas')
27
+ .description('BuildAtlas CLI — self-hosted builds for Expo, React Native, iOS and Android.')
28
+ .version(version);
29
+ registerAuthCommands(program);
30
+ registerInitCommand(program);
31
+ registerBuildCommand(program);
32
+ registerBuildQueryCommands(program);
33
+ registerCredentialCommands(program);
34
+ registerSubmitCommand(program);
35
+ registerEnvCommand(program);
36
+ registerExpoTokenCommands(program);
37
+ registerProjectCommands(program);
38
+ registerUpdateCommand(program);
39
+ program
40
+ .command('whoami')
41
+ .description('Show the authenticated user')
42
+ .action(async () => {
43
+ const config = await loadConfig();
44
+ if (!config.token) {
45
+ console.error(pc.red('Not logged in.'), 'Run', pc.bold('buildatlas login'));
46
+ process.exitCode = 1;
47
+ return;
48
+ }
49
+ // Ask the server rather than reporting what is in the config file. A revoked
50
+ // token still leaves an email on disk, and "whoami says I am logged in but
51
+ // every other command fails" is a genuinely confusing place to start
52
+ // debugging.
53
+ try {
54
+ const { user } = await apiRequest(config, '/auth/me', {
55
+ token: config.token,
56
+ });
57
+ console.log(`${user.email} @ ${config.apiUrl}`);
58
+ }
59
+ catch (err) {
60
+ const reason = err instanceof AtlasApiError ? err.message : err.message;
61
+ console.error(pc.red('✖'), `Stored token is not usable: ${reason}`);
62
+ console.error(pc.dim(` re-run: buildatlas login --api ${config.apiUrl}`));
63
+ process.exitCode = 1;
64
+ }
65
+ });
66
+ await program.parseAsync(process.argv);
67
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/logs.d.ts ADDED
@@ -0,0 +1,13 @@
1
+ import type { AtlasConfig } from './config.ts';
2
+ /**
3
+ * Follows a build's log over SSE and resolves with its terminal status.
4
+ *
5
+ * Reconnects on a dropped connection, resuming from the last sequence number
6
+ * seen. That cursor is the whole reason the server numbers chunks: a build can run
7
+ * 20 minutes, and a laptop closing its lid mid-build should not truncate the log
8
+ * or replay it from the top.
9
+ */
10
+ export declare function streamLogs(config: Pick<AtlasConfig, 'apiUrl' | 'token'>, buildId: string, { quiet }?: {
11
+ quiet?: boolean | undefined;
12
+ }): Promise<string>;
13
+ //# sourceMappingURL=logs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logs.d.ts","sourceRoot":"","sources":["../src/logs.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAS/C;;;;;;;GAOG;AACH,wBAAsB,UAAU,CAC9B,MAAM,EAAE,IAAI,CAAC,WAAW,EAAE,QAAQ,GAAG,OAAO,CAAC,EAC7C,OAAO,EAAE,MAAM,EACf,EAAE,KAAa,EAAE;;CAAK,GACrB,OAAO,CAAC,MAAM,CAAC,CA+DjB"}