@ship-et/buildatlas-cli 0.0.0-stage → 0.11.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/LICENSE +21 -0
- package/README.md +81 -2
- package/dist/browser-login.d.ts +12 -0
- package/dist/browser-login.d.ts.map +1 -0
- package/dist/browser-login.js +94 -0
- package/dist/browser-login.js.map +1 -0
- package/dist/client.d.ts +33 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +115 -0
- package/dist/client.js.map +1 -0
- package/dist/client.test.d.ts +2 -0
- package/dist/client.test.d.ts.map +1 -0
- package/dist/client.test.js +102 -0
- package/dist/client.test.js.map +1 -0
- package/dist/commands/auth.d.ts +3 -0
- package/dist/commands/auth.d.ts.map +1 -0
- package/dist/commands/auth.js +88 -0
- package/dist/commands/auth.js.map +1 -0
- package/dist/commands/build.d.ts +4 -0
- package/dist/commands/build.d.ts.map +1 -0
- package/dist/commands/build.js +297 -0
- package/dist/commands/build.js.map +1 -0
- package/dist/commands/builds.d.ts +3 -0
- package/dist/commands/builds.d.ts.map +1 -0
- package/dist/commands/builds.js +102 -0
- package/dist/commands/builds.js.map +1 -0
- package/dist/commands/credentials-wizard.d.ts +25 -0
- package/dist/commands/credentials-wizard.d.ts.map +1 -0
- package/dist/commands/credentials-wizard.js +274 -0
- package/dist/commands/credentials-wizard.js.map +1 -0
- package/dist/commands/credentials.d.ts +3 -0
- package/dist/commands/credentials.d.ts.map +1 -0
- package/dist/commands/credentials.js +249 -0
- package/dist/commands/credentials.js.map +1 -0
- package/dist/commands/env.d.ts +37 -0
- package/dist/commands/env.d.ts.map +1 -0
- package/dist/commands/env.js +220 -0
- package/dist/commands/env.js.map +1 -0
- package/dist/commands/expo-tokens.d.ts +3 -0
- package/dist/commands/expo-tokens.d.ts.map +1 -0
- package/dist/commands/expo-tokens.js +103 -0
- package/dist/commands/expo-tokens.js.map +1 -0
- package/dist/commands/init.d.ts +6 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +204 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/projects.d.ts +11 -0
- package/dist/commands/projects.d.ts.map +1 -0
- package/dist/commands/projects.js +148 -0
- package/dist/commands/projects.js.map +1 -0
- package/dist/commands/submit.d.ts +10 -0
- package/dist/commands/submit.d.ts.map +1 -0
- package/dist/commands/submit.js +118 -0
- package/dist/commands/submit.js.map +1 -0
- package/dist/commands/update.d.ts +10 -0
- package/dist/commands/update.d.ts.map +1 -0
- package/dist/commands/update.js +93 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/config.d.ts +66 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +255 -0
- package/dist/config.js.map +1 -0
- package/dist/detach.d.ts +18 -0
- package/dist/detach.d.ts.map +1 -0
- package/dist/detach.js +42 -0
- package/dist/detach.js.map +1 -0
- package/dist/eas-credentials-zip.d.ts +38 -0
- package/dist/eas-credentials-zip.d.ts.map +1 -0
- package/dist/eas-credentials-zip.js +63 -0
- package/dist/eas-credentials-zip.js.map +1 -0
- package/dist/eas-json.d.ts +21 -0
- package/dist/eas-json.d.ts.map +1 -0
- package/dist/eas-json.js +32 -0
- package/dist/eas-json.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +67 -0
- package/dist/index.js.map +1 -0
- package/dist/logs.d.ts +13 -0
- package/dist/logs.d.ts.map +1 -0
- package/dist/logs.js +70 -0
- package/dist/logs.js.map +1 -0
- package/dist/packaging.test.d.ts +2 -0
- package/dist/packaging.test.d.ts.map +1 -0
- package/dist/packaging.test.js +79 -0
- package/dist/packaging.test.js.map +1 -0
- package/dist/profiles.test.d.ts +2 -0
- package/dist/profiles.test.d.ts.map +1 -0
- package/dist/profiles.test.js +56 -0
- package/dist/profiles.test.js.map +1 -0
- package/dist/project-hint.d.ts +12 -0
- package/dist/project-hint.d.ts.map +1 -0
- package/dist/project-hint.js +19 -0
- package/dist/project-hint.js.map +1 -0
- package/dist/project-type.d.ts +43 -0
- package/dist/project-type.d.ts.map +1 -0
- package/dist/project-type.js +88 -0
- package/dist/project-type.js.map +1 -0
- package/dist/project-type.test.d.ts +2 -0
- package/dist/project-type.test.d.ts.map +1 -0
- package/dist/project-type.test.js +77 -0
- package/dist/project-type.test.js.map +1 -0
- package/dist/prompt.d.ts +56 -0
- package/dist/prompt.d.ts.map +1 -0
- package/dist/prompt.js +157 -0
- package/dist/prompt.js.map +1 -0
- package/dist/tarball.d.ts +39 -0
- package/dist/tarball.d.ts.map +1 -0
- package/dist/tarball.js +208 -0
- package/dist/tarball.js.map +1 -0
- package/dist/tarball.test.d.ts +2 -0
- package/dist/tarball.test.d.ts.map +1 -0
- package/dist/tarball.test.js +97 -0
- package/dist/tarball.test.js.map +1 -0
- package/package.json +56 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Digicom
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,82 @@
|
|
|
1
|
-
#
|
|
1
|
+
# buildatlas
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
CLI for [BuildAtlas](https://github.com/YohannesTz/BuildAtlas) — a self-hosted
|
|
4
|
+
replacement for EAS Build and EAS Update that runs on a Mac you own.
|
|
5
|
+
|
|
6
|
+
This package is the client. It talks to a BuildAtlas server that you host; it is
|
|
7
|
+
not useful on its own. If nobody has set one up yet, start with
|
|
8
|
+
[self-hosting](https://github.com/YohannesTz/BuildAtlas/blob/main/docs/self-hosting.md).
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install -g buildatlas
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Installs two commands: `buildatlas`, and `ba` as a shorter alias.
|
|
15
|
+
|
|
16
|
+
## Use
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
buildatlas login --api https://builds.yourcompany.com
|
|
20
|
+
export BUILDATLAS_PROJECT=my-app
|
|
21
|
+
|
|
22
|
+
buildatlas build --platform android --profile production # → signed .aab
|
|
23
|
+
buildatlas build --platform ios --profile production # → signed .ipa
|
|
24
|
+
buildatlas update --branch production # → OTA, no rebuild
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Your existing `eas.json` profiles work unchanged — BuildAtlas reads the same
|
|
28
|
+
file, because each build runs `eas build --local` under the hood. Builds happen
|
|
29
|
+
on your hardware, artifacts land in your object storage, and signing
|
|
30
|
+
certificates never leave your network.
|
|
31
|
+
|
|
32
|
+
## Commands
|
|
33
|
+
|
|
34
|
+
| | |
|
|
35
|
+
|---|---|
|
|
36
|
+
| `login` / `logout` / `whoami` | Authentication for this machine |
|
|
37
|
+
| `build` | Queue a build and follow the log |
|
|
38
|
+
| `builds` / `logs` / `cancel` | Inspect and manage builds |
|
|
39
|
+
| `credentials` | Signing material and build-time environment variables |
|
|
40
|
+
| `expo-tokens` | Expo access tokens, which every build needs |
|
|
41
|
+
| `update` | Publish an OTA update |
|
|
42
|
+
|
|
43
|
+
Full reference: [docs/cli.md](https://github.com/YohannesTz/BuildAtlas/blob/main/docs/cli.md).
|
|
44
|
+
|
|
45
|
+
New to Expo? [Getting started](https://github.com/YohannesTz/BuildAtlas/blob/main/docs/getting-started.md)
|
|
46
|
+
walks from an empty folder to an app on a phone.
|
|
47
|
+
|
|
48
|
+
## Configuration
|
|
49
|
+
|
|
50
|
+
| | Env var | Falls back to |
|
|
51
|
+
|---|---|---|
|
|
52
|
+
| API URL | `BUILDATLAS_API_URL` | `~/.buildatlas/auth.json`, then `--api` |
|
|
53
|
+
| Token | `BUILDATLAS_TOKEN` | `~/.buildatlas/auth.json` |
|
|
54
|
+
| Project | `BUILDATLAS_PROJECT` | `--project` |
|
|
55
|
+
|
|
56
|
+
Env vars win over the file, so CI can inject a token without writing to disk.
|
|
57
|
+
The config file is created at mode 0600.
|
|
58
|
+
|
|
59
|
+
Requires Node 20.11 or newer.
|
|
60
|
+
|
|
61
|
+
## Developing against a local checkout
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
npm link -w buildatlas # from the repo root
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Prefer `npm link` over `npm install -g ./packages/cli` while iterating. A global
|
|
68
|
+
install is keyed on the version number, so npm reports "up to date" and silently
|
|
69
|
+
skips the copy when you rebuild without bumping it — leaving an old binary on your
|
|
70
|
+
PATH while the source looks correct. `npm link` symlinks instead, so a rebuild is
|
|
71
|
+
picked up immediately.
|
|
72
|
+
|
|
73
|
+
If you do use a global install, `npm rm -g buildatlas` first.
|
|
74
|
+
|
|
75
|
+
The build script sets the executable bit explicitly. `tsc` emits mode 0644, and a
|
|
76
|
+
bin symlinked into `dist/` stops being runnable after every rebuild — npm sets the
|
|
77
|
+
bit when installing from a tarball, but not for a linked workspace, which is exactly
|
|
78
|
+
how you develop against it. The symptom is `permission denied: buildatlas`.
|
|
79
|
+
|
|
80
|
+
## License
|
|
81
|
+
|
|
82
|
+
MIT
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface BrowserLogin {
|
|
2
|
+
/** Resolves with the token, or null if it timed out / was aborted. */
|
|
3
|
+
readonly done: Promise<string | null>;
|
|
4
|
+
/** The URL the user can open by hand if the browser did not launch. */
|
|
5
|
+
readonly url: string;
|
|
6
|
+
/** Stop the local server (used when a pasted token wins the race instead). */
|
|
7
|
+
abort(): void;
|
|
8
|
+
}
|
|
9
|
+
export declare function startBrowserLogin(apiUrl: string, tokenName: string, timeoutMs?: number): BrowserLogin;
|
|
10
|
+
/** Default token name for a machine: the hostname, trimmed to something sane. */
|
|
11
|
+
export declare function defaultTokenName(): string;
|
|
12
|
+
//# sourceMappingURL=browser-login.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser-login.d.ts","sourceRoot":"","sources":["../src/browser-login.ts"],"names":[],"mappings":"AAmCA,MAAM,WAAW,YAAY;IAC3B,sEAAsE;IACtE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACtC,uEAAuE;IACvE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,8EAA8E;IAC9E,KAAK,IAAI,IAAI,CAAC;CACf;AAQD,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,SAAU,GAAG,YAAY,CAsDtG;AAED,iFAAiF;AACjF,wBAAgB,gBAAgB,IAAI,MAAM,CAGzC"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { randomBytes } from 'node:crypto';
|
|
3
|
+
import { createServer } from 'node:http';
|
|
4
|
+
import { hostname } from 'node:os';
|
|
5
|
+
import pc from 'picocolors';
|
|
6
|
+
/**
|
|
7
|
+
* Browser login over a localhost callback — the "gh auth login" pattern.
|
|
8
|
+
*
|
|
9
|
+
* The CLI starts a one-shot HTTP server on 127.0.0.1, opens the dashboard's
|
|
10
|
+
* `/cli-auth` page, and waits. The page mints a token (as the logged-in user) and
|
|
11
|
+
* redirects the browser to `http://127.0.0.1:<port>/cb?token=…&state=…`; this server
|
|
12
|
+
* catches it. No copy-paste, and the token never travels anywhere but loopback.
|
|
13
|
+
*
|
|
14
|
+
* A random `state` ties the browser round-trip to this exact invocation, so a stray
|
|
15
|
+
* request to the port cannot inject a token. The callback host is loopback by
|
|
16
|
+
* construction — the page is only ever told a port, never a full URL — so a token
|
|
17
|
+
* cannot be redirected off the machine.
|
|
18
|
+
*/
|
|
19
|
+
/** Opens a URL in the default browser, best-effort. A headless box just gets the printed URL. */
|
|
20
|
+
function openBrowser(url) {
|
|
21
|
+
const [cmd, args] = process.platform === 'darwin'
|
|
22
|
+
? ['open', [url]]
|
|
23
|
+
: process.platform === 'win32'
|
|
24
|
+
? ['cmd', ['/c', 'start', '', url]]
|
|
25
|
+
: ['xdg-open', [url]];
|
|
26
|
+
try {
|
|
27
|
+
spawn(cmd, args, { stdio: 'ignore', detached: true }).unref();
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
// No browser launcher (SSH, minimal box). The URL is printed; open it elsewhere.
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
const DONE_PAGE = '<!doctype html><meta charset="utf-8"><title>BuildAtlas</title>' +
|
|
34
|
+
'<body style="font-family:system-ui,-apple-system,sans-serif;text-align:center;padding:64px 24px;color:#15122b">' +
|
|
35
|
+
'<div style="font-size:40px">✓</div><h1 style="margin:.3em 0">Connected</h1>' +
|
|
36
|
+
'<p style="color:#6f6890">You can close this tab and return to the terminal.</p></body>';
|
|
37
|
+
export function startBrowserLogin(apiUrl, tokenName, timeoutMs = 180_000) {
|
|
38
|
+
const state = randomBytes(16).toString('hex');
|
|
39
|
+
let settle;
|
|
40
|
+
const done = new Promise((r) => {
|
|
41
|
+
settle = r;
|
|
42
|
+
});
|
|
43
|
+
const server = createServer((req, res) => {
|
|
44
|
+
const reqUrl = new URL(req.url ?? '/', 'http://127.0.0.1');
|
|
45
|
+
if (reqUrl.pathname !== '/cb') {
|
|
46
|
+
res.writeHead(404).end();
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const token = reqUrl.searchParams.get('token');
|
|
50
|
+
const returned = reqUrl.searchParams.get('state');
|
|
51
|
+
if (!token || returned !== state) {
|
|
52
|
+
res
|
|
53
|
+
.writeHead(400, { 'content-type': 'text/html' })
|
|
54
|
+
.end('<!doctype html><meta charset="utf-8"><body>Login failed — you can close this tab.</body>');
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
res.writeHead(200, { 'content-type': 'text/html' }).end(DONE_PAGE);
|
|
58
|
+
finish(token);
|
|
59
|
+
});
|
|
60
|
+
let finished = false;
|
|
61
|
+
const finish = (token) => {
|
|
62
|
+
if (finished)
|
|
63
|
+
return;
|
|
64
|
+
finished = true;
|
|
65
|
+
clearTimeout(timer);
|
|
66
|
+
server.close();
|
|
67
|
+
settle(token);
|
|
68
|
+
};
|
|
69
|
+
const timer = setTimeout(() => finish(null), timeoutMs);
|
|
70
|
+
server.on('error', () => finish(null));
|
|
71
|
+
// Bind synchronously-ish: listen then compute the URL in the callback.
|
|
72
|
+
let url = '';
|
|
73
|
+
server.listen(0, '127.0.0.1', () => {
|
|
74
|
+
const addr = server.address();
|
|
75
|
+
const port = typeof addr === 'object' && addr ? addr.port : 0;
|
|
76
|
+
url = `${apiUrl.replace(/\/+$/, '')}/cli-auth?port=${port}&state=${state}&name=${encodeURIComponent(tokenName)}`;
|
|
77
|
+
console.log(`\n→ Opening ${pc.cyan(url)}`);
|
|
78
|
+
console.log(pc.dim(' Approve in the browser and the token returns here automatically.'));
|
|
79
|
+
openBrowser(url);
|
|
80
|
+
});
|
|
81
|
+
return {
|
|
82
|
+
done,
|
|
83
|
+
get url() {
|
|
84
|
+
return url;
|
|
85
|
+
},
|
|
86
|
+
abort: () => finish(null),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
/** Default token name for a machine: the hostname, trimmed to something sane. */
|
|
90
|
+
export function defaultTokenName() {
|
|
91
|
+
const host = hostname().split('.')[0] || 'cli';
|
|
92
|
+
return `cli-${host}`;
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=browser-login.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser-login.js","sourceRoot":"","sources":["../src/browser-login.ts"],"names":[],"mappings":"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"}
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { AtlasConfig } from './config.ts';
|
|
2
|
+
export declare class AtlasApiError extends Error {
|
|
3
|
+
readonly status: number;
|
|
4
|
+
readonly code: string;
|
|
5
|
+
constructor(status: number, code: string, message: string);
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Whether this request may be sent twice.
|
|
9
|
+
*
|
|
10
|
+
* It matters because the link between a developer and a self-hosted server is not the
|
|
11
|
+
* one in the demo. Measured against a server in Germany from an office in Addis Ababa,
|
|
12
|
+
* roughly one request in thirty either never connects or gets a 504 out of the reverse
|
|
13
|
+
* proxy — and with no retry at all that is one `buildatlas build` in thirty dying after
|
|
14
|
+
* the source archive has already been packed, and one `cancel` in thirty reporting
|
|
15
|
+
* "Gateway Time-out" at a person who then has no idea whether the build was cancelled.
|
|
16
|
+
*
|
|
17
|
+
* `safe` is the default for GET, and is also right for the calls that state a fact
|
|
18
|
+
* rather than add one — enqueue a build that exists, cancel a build. `once` is for
|
|
19
|
+
* creating things: a repeated POST /builds is a second build, a second queue slot and a
|
|
20
|
+
* second set of build minutes, which is worse than an error message.
|
|
21
|
+
*/
|
|
22
|
+
export type Retry = 'safe' | 'once';
|
|
23
|
+
/**
|
|
24
|
+
* Minimal API client over the platform fetch. Takes an explicit token rather
|
|
25
|
+
* than reading config itself, so `buildatlas login` can validate a token before it
|
|
26
|
+
* is ever written to disk.
|
|
27
|
+
*/
|
|
28
|
+
export declare function apiRequest<T>(config: Pick<AtlasConfig, 'apiUrl'>, path: string, init?: RequestInit & {
|
|
29
|
+
token?: string;
|
|
30
|
+
raw?: boolean;
|
|
31
|
+
retry?: Retry;
|
|
32
|
+
}): Promise<T>;
|
|
33
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/C,qBAAa,aAAc,SAAQ,KAAK;IAGtC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAEtB,YAAY,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAIxD;CACF;AAID;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,KAAK,GAAG,MAAM,GAAG,MAAM,CAAC;AAYpC;;;;GAIG;AACH,wBAAsB,UAAU,CAAC,CAAC,EAChC,MAAM,EAAE,IAAI,CAAC,WAAW,EAAE,QAAQ,CAAC,EACnC,IAAI,EAAE,MAAM,EACZ,IAAI,GAAE,WAAW,GAAG;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,KAAK,CAAA;CAAO,GACxE,OAAO,CAAC,CAAC,CAAC,CAiEZ"}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
export class AtlasApiError extends Error {
|
|
2
|
+
// Explicit fields: Node's type-stripping mode does not support parameter
|
|
3
|
+
// properties, and the built CLI runs under plain Node.
|
|
4
|
+
status;
|
|
5
|
+
code;
|
|
6
|
+
constructor(status, code, message) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.status = status;
|
|
9
|
+
this.code = code;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
13
|
+
/** Backoff between attempts. Short: a person is watching a prompt, not a daemon log. */
|
|
14
|
+
const BACKOFF_MS = [500, 1500, 3000];
|
|
15
|
+
function retryable(status) {
|
|
16
|
+
// Gateway and transport failures only. A 4xx is the server understanding the request
|
|
17
|
+
// and refusing it, and 500 is the API itself having failed — repeating either changes
|
|
18
|
+
// nothing but the wait.
|
|
19
|
+
return status === 502 || status === 503 || status === 504;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Minimal API client over the platform fetch. Takes an explicit token rather
|
|
23
|
+
* than reading config itself, so `buildatlas login` can validate a token before it
|
|
24
|
+
* is ever written to disk.
|
|
25
|
+
*/
|
|
26
|
+
export async function apiRequest(config, path, init = {}) {
|
|
27
|
+
// `raw` returns the body as text. Used for archived logs, which are plain text
|
|
28
|
+
// rather than JSON so they can be piped straight into a pager.
|
|
29
|
+
const { token, raw, retry, ...rest } = init;
|
|
30
|
+
const method = (rest.method ?? 'GET').toUpperCase();
|
|
31
|
+
const mode = retry ?? (method === 'GET' ? 'safe' : 'once');
|
|
32
|
+
let res;
|
|
33
|
+
for (let attempt = 0;; attempt += 1) {
|
|
34
|
+
try {
|
|
35
|
+
res = await fetch(new URL(`/api${path}`, config.apiUrl), {
|
|
36
|
+
...rest,
|
|
37
|
+
headers: {
|
|
38
|
+
...(token ? { authorization: `Bearer ${token}` } : {}),
|
|
39
|
+
...(rest.body ? { 'content-type': 'application/json' } : {}),
|
|
40
|
+
...rest.headers,
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
if (mode === 'safe' && retryable(res.status) && attempt < BACKOFF_MS.length) {
|
|
44
|
+
await delay(BACKOFF_MS[attempt]);
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
// No response at all: the connection failed, so for a `once` request we cannot
|
|
50
|
+
// know whether the server acted on it. Reported rather than repeated.
|
|
51
|
+
if (mode !== 'safe' || attempt >= BACKOFF_MS.length) {
|
|
52
|
+
throw new AtlasApiError(0, 'network_error', `Could not reach ${new URL(config.apiUrl).host} (${err.message}).`);
|
|
53
|
+
}
|
|
54
|
+
await delay(BACKOFF_MS[attempt]);
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
if (res.status === 204)
|
|
60
|
+
return undefined;
|
|
61
|
+
if (raw) {
|
|
62
|
+
const text = await res.text();
|
|
63
|
+
if (!res.ok) {
|
|
64
|
+
// An error is still JSON even when the success path is not — unless it came from
|
|
65
|
+
// a proxy, in which case it is an HTML page and JSON.parse would throw a
|
|
66
|
+
// SyntaxError over the top of the real failure.
|
|
67
|
+
let parsed;
|
|
68
|
+
try {
|
|
69
|
+
parsed = JSON.parse(text);
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
parsed = undefined;
|
|
73
|
+
}
|
|
74
|
+
if (!parsed?.error)
|
|
75
|
+
throw gatewayError(res, config);
|
|
76
|
+
throw new AtlasApiError(res.status, parsed.error.code, parsed.error.message);
|
|
77
|
+
}
|
|
78
|
+
return text;
|
|
79
|
+
}
|
|
80
|
+
const body = await res.json().catch(() => undefined);
|
|
81
|
+
if (!res.ok) {
|
|
82
|
+
const err = body?.error;
|
|
83
|
+
if (!err)
|
|
84
|
+
throw gatewayError(res, config);
|
|
85
|
+
throw new AtlasApiError(res.status, err.code, err.message);
|
|
86
|
+
}
|
|
87
|
+
return body;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* An error the API did not write.
|
|
91
|
+
*
|
|
92
|
+
* Every API error is JSON with a code. A reply without one came from something in
|
|
93
|
+
* between — nginx, a load balancer, Cloudflare — and `✖ Gateway Time-out (unknown)`,
|
|
94
|
+
* which is what this used to print, tells a person nothing about who timed out or
|
|
95
|
+
* whether their build was created. Name the host and say the API may never have seen
|
|
96
|
+
* the request, because that is the question they are about to ask.
|
|
97
|
+
*/
|
|
98
|
+
function gatewayError(res, config) {
|
|
99
|
+
const host = (() => {
|
|
100
|
+
try {
|
|
101
|
+
return new URL(config.apiUrl).host;
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
return config.apiUrl;
|
|
105
|
+
}
|
|
106
|
+
})();
|
|
107
|
+
const what = res.status === 413
|
|
108
|
+
? `${host} refused the request as too large (413). A reverse proxy in front of BuildAtlas ` +
|
|
109
|
+
'needs client_max_body_size raised — see docs/deployment.md.'
|
|
110
|
+
: `${host} answered ${res.status} ${res.statusText} without reaching BuildAtlas. ` +
|
|
111
|
+
'A proxy in front of the API timed out or is down, so the request may not have been ' +
|
|
112
|
+
'processed at all. Check and retry.';
|
|
113
|
+
return new AtlasApiError(res.status, 'gateway_error', what);
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.test.d.ts","sourceRoot":"","sources":["../src/client.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { createServer } from 'node:http';
|
|
3
|
+
import { afterEach, describe, it } from 'node:test';
|
|
4
|
+
import { AtlasApiError, apiRequest } from './client.js';
|
|
5
|
+
/**
|
|
6
|
+
* How the CLI behaves when the network between a developer and their server is the
|
|
7
|
+
* real one.
|
|
8
|
+
*
|
|
9
|
+
* Measured from Addis Ababa against a server in Germany, roughly one request in thirty
|
|
10
|
+
* either never connects or comes back as a 504 from the reverse proxy. With no retries
|
|
11
|
+
* that was a `buildatlas build` dying after the source archive was packed, and a
|
|
12
|
+
* `cancel` printing "Gateway Time-out (unknown)" — which does not say whether the build
|
|
13
|
+
* was cancelled, who timed out, or what to do next.
|
|
14
|
+
*/
|
|
15
|
+
let server;
|
|
16
|
+
async function serve(handler) {
|
|
17
|
+
let calls = 0;
|
|
18
|
+
server = createServer((req, res) => {
|
|
19
|
+
calls += 1;
|
|
20
|
+
const { status, body, type } = handler(calls);
|
|
21
|
+
res.writeHead(status, { 'content-type': type ?? 'application/json' });
|
|
22
|
+
res.end(body);
|
|
23
|
+
});
|
|
24
|
+
await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
|
|
25
|
+
const { port } = server.address();
|
|
26
|
+
return { apiUrl: `http://127.0.0.1:${port}`, calls: () => calls };
|
|
27
|
+
}
|
|
28
|
+
afterEach(() => {
|
|
29
|
+
server?.close();
|
|
30
|
+
server = undefined;
|
|
31
|
+
});
|
|
32
|
+
describe('apiRequest', () => {
|
|
33
|
+
it('retries a GET through a gateway failure', async () => {
|
|
34
|
+
const { apiUrl, calls } = await serve((n) => n < 3
|
|
35
|
+
? { status: 504, body: '<html><head><title>504 Gateway Time-out</title></head></html>', type: 'text/html' }
|
|
36
|
+
: { status: 200, body: JSON.stringify({ builds: [] }) });
|
|
37
|
+
const result = await apiRequest({ apiUrl }, '/projects/x/builds');
|
|
38
|
+
assert.deepEqual(result, { builds: [] });
|
|
39
|
+
assert.equal(calls(), 3);
|
|
40
|
+
});
|
|
41
|
+
it('does not retry a POST that creates something', async () => {
|
|
42
|
+
const { apiUrl, calls } = await serve(() => ({
|
|
43
|
+
status: 504,
|
|
44
|
+
body: '<html><title>504</title></html>',
|
|
45
|
+
type: 'text/html',
|
|
46
|
+
}));
|
|
47
|
+
await assert.rejects(apiRequest({ apiUrl }, '/projects/x/builds', { method: 'POST', body: '{}' }), (err) => err.code === 'gateway_error');
|
|
48
|
+
// One build requested, one build created. A retry here is a second set of build
|
|
49
|
+
// minutes for an app nobody asked to build twice.
|
|
50
|
+
assert.equal(calls(), 1);
|
|
51
|
+
});
|
|
52
|
+
it('retries a POST that only restates a fact', async () => {
|
|
53
|
+
const { apiUrl, calls } = await serve((n) => n < 2 ? { status: 502, body: 'nope', type: 'text/html' } : { status: 200, body: '{"ok":true}' });
|
|
54
|
+
await apiRequest({ apiUrl }, '/builds/b1/enqueue', { method: 'POST', retry: 'safe' });
|
|
55
|
+
assert.equal(calls(), 2);
|
|
56
|
+
});
|
|
57
|
+
it('names the host and says the API may not have seen it', async () => {
|
|
58
|
+
const { apiUrl } = await serve(() => ({
|
|
59
|
+
status: 504,
|
|
60
|
+
body: '<html><title>504 Gateway Time-out</title></html>',
|
|
61
|
+
type: 'text/html',
|
|
62
|
+
}));
|
|
63
|
+
await assert.rejects(apiRequest({ apiUrl }, '/builds/b1/cancel', { method: 'POST' }), (err) => {
|
|
64
|
+
assert.equal(err.code, 'gateway_error');
|
|
65
|
+
assert.match(err.message, /127\.0\.0\.1/);
|
|
66
|
+
assert.match(err.message, /may not have been/);
|
|
67
|
+
// The old message. It told a person nothing.
|
|
68
|
+
assert.doesNotMatch(err.message, /\(unknown\)/);
|
|
69
|
+
return true;
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
it('points a 413 at the proxy setting that causes it', async () => {
|
|
73
|
+
const { apiUrl } = await serve(() => ({ status: 413, body: '<html>too big</html>', type: 'text/html' }));
|
|
74
|
+
await assert.rejects(apiRequest({ apiUrl }, '/x', { method: 'POST' }), (err) => {
|
|
75
|
+
assert.match(err.message, /client_max_body_size/);
|
|
76
|
+
return true;
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
it('keeps the API’s own error when there is one', async () => {
|
|
80
|
+
const { apiUrl, calls } = await serve(() => ({
|
|
81
|
+
status: 404,
|
|
82
|
+
body: JSON.stringify({ error: { code: 'not_found', message: 'No such build' } }),
|
|
83
|
+
}));
|
|
84
|
+
await assert.rejects(apiRequest({ apiUrl }, '/builds/nope'), (err) => {
|
|
85
|
+
assert.equal(err.code, 'not_found');
|
|
86
|
+
assert.equal(err.message, 'No such build');
|
|
87
|
+
return true;
|
|
88
|
+
});
|
|
89
|
+
// A 404 is an answer, not a blip.
|
|
90
|
+
assert.equal(calls(), 1);
|
|
91
|
+
});
|
|
92
|
+
it('reports an unreachable host rather than a bare "fetch failed"', async () => {
|
|
93
|
+
await assert.rejects(
|
|
94
|
+
// Port 1 on the loopback: nothing listens, and the connection is refused at once.
|
|
95
|
+
apiRequest({ apiUrl: 'http://127.0.0.1:1' }, '/projects', { method: 'POST' }), (err) => {
|
|
96
|
+
assert.equal(err.code, 'network_error');
|
|
97
|
+
assert.match(err.message, /Could not reach 127\.0\.0\.1:1/);
|
|
98
|
+
return true;
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
//# sourceMappingURL=client.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.test.js","sourceRoot":"","sources":["../src/client.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/commands/auth.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA8BzC,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAoE3D"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import pc from 'picocolors';
|
|
2
|
+
import { startBrowserLogin, defaultTokenName } from '../browser-login.js';
|
|
3
|
+
import { AtlasApiError, apiRequest } from '../client.js';
|
|
4
|
+
import { DEFAULT_API_URL, configPath, loadConfig, saveConfig, storedApiUrl } from '../config.js';
|
|
5
|
+
import { withPrompter } from '../prompt.js';
|
|
6
|
+
/**
|
|
7
|
+
* Validate a token against the server, then persist it. Shared by the browser flow, a
|
|
8
|
+
* pasted token and the `--token` CI path — a dead token fails here rather than on the
|
|
9
|
+
* next command, where it is harder to diagnose.
|
|
10
|
+
*/
|
|
11
|
+
async function finishLogin(apiUrl, token) {
|
|
12
|
+
try {
|
|
13
|
+
const { user } = await apiRequest({ apiUrl }, '/auth/me', {
|
|
14
|
+
token,
|
|
15
|
+
});
|
|
16
|
+
await saveConfig({ apiUrl, token, email: user.email });
|
|
17
|
+
console.log(pc.green('✔'), `Logged in as ${pc.bold(user.email)} at ${pc.dim(apiUrl)}`);
|
|
18
|
+
console.log(pc.dim(` token stored in ${configPath()} (mode 0600)`));
|
|
19
|
+
}
|
|
20
|
+
catch (err) {
|
|
21
|
+
if (err instanceof AtlasApiError) {
|
|
22
|
+
console.error(pc.red('✖'), `Token rejected: ${err.message}`);
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
console.error(pc.red('✖'), `Could not reach ${apiUrl}: ${err.message}`);
|
|
26
|
+
}
|
|
27
|
+
process.exitCode = 1;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export function registerAuthCommands(program) {
|
|
31
|
+
program
|
|
32
|
+
.command('login')
|
|
33
|
+
.description('Log in — opens the browser, mints a token, and captures it automatically')
|
|
34
|
+
.option('--api <url>', 'API base URL (default: your instance, remembered after first login)')
|
|
35
|
+
.option('--name <name>', 'Name for the token that gets created (default: this machine)')
|
|
36
|
+
.option('--create', 'Prompt for a token name instead of defaulting to this machine')
|
|
37
|
+
.option('--token <token>', 'Use this token directly and skip the browser (for CI)')
|
|
38
|
+
.action(async (opts) => {
|
|
39
|
+
// The server: an explicit flag, then a previous login, then the instance this CLI
|
|
40
|
+
// ships pointed at. No prompt in the common case.
|
|
41
|
+
const apiUrl = (opts.api ?? (await storedApiUrl()) ?? DEFAULT_API_URL).replace(/\/+$/, '');
|
|
42
|
+
// CI path: a token was supplied, so there is nothing to open.
|
|
43
|
+
if (opts.token) {
|
|
44
|
+
await finishLogin(apiUrl, opts.token);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
await withPrompter(async (prompter) => {
|
|
48
|
+
const chosen = opts.name ?? (opts.create ? (await prompter.ask('Token name: ')).trim() : '');
|
|
49
|
+
const tokenName = chosen || defaultTokenName();
|
|
50
|
+
const login = startBrowserLogin(apiUrl, tokenName);
|
|
51
|
+
// Race the browser callback against a paste, so a box that cannot open a browser
|
|
52
|
+
// (SSH, locked down) still has a way through. An empty line is ignored — it just
|
|
53
|
+
// means "keep waiting for the browser".
|
|
54
|
+
const paste = prompter
|
|
55
|
+
.ask(pc.dim(' …or paste a token here and press enter: '))
|
|
56
|
+
.then((v) => v.trim())
|
|
57
|
+
.catch(() => '');
|
|
58
|
+
const winner = await Promise.race([
|
|
59
|
+
login.done.then((t) => ({ src: 'browser', t })),
|
|
60
|
+
paste.then((t) => ({ src: 'paste', t })),
|
|
61
|
+
]);
|
|
62
|
+
let token;
|
|
63
|
+
if (winner.src === 'paste' && winner.t) {
|
|
64
|
+
login.abort();
|
|
65
|
+
token = winner.t;
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
token = winner.src === 'browser' ? winner.t : await login.done;
|
|
69
|
+
}
|
|
70
|
+
if (!token) {
|
|
71
|
+
console.error(pc.red('✖'), 'No token received (the browser login timed out).');
|
|
72
|
+
console.error(pc.dim(' Retry, or create one at'), pc.cyan(`${apiUrl}/settings`), pc.dim('and paste it.'));
|
|
73
|
+
process.exitCode = 1;
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
await finishLogin(apiUrl, token);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
program
|
|
80
|
+
.command('logout')
|
|
81
|
+
.description('Remove the stored token')
|
|
82
|
+
.action(async () => {
|
|
83
|
+
const config = await loadConfig();
|
|
84
|
+
await saveConfig({ apiUrl: config.apiUrl });
|
|
85
|
+
console.log(pc.green('✔'), 'Token removed');
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/commands/auth.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../src/commands/build.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAgEzC,wBAAsB,mBAAmB,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAgC5F;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAiD3D"}
|