ant-go 0.1.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,242 @@
1
+ # ant-go CLI
2
+
3
+ Build iOS and Android apps with a single command — no complex CI/CD setup required.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install -g ant-go
9
+ ```
10
+
11
+ ## Upgrading
12
+
13
+ ```bash
14
+ npm install -g ant-go@latest
15
+ ```
16
+
17
+ To check your current version:
18
+
19
+ ```bash
20
+ ant --version
21
+ ```
22
+
23
+ > The CLI automatically notifies you when a newer version is available after each command.
24
+
25
+ ## Getting Started
26
+
27
+ ### 1. Login
28
+
29
+ ```bash
30
+ # Login with email/password
31
+ ant auth login
32
+
33
+ # Or login with Google via browser
34
+ ant auth login --browser
35
+ ```
36
+
37
+ ### 2. Add `projectId` to `app.json`
38
+
39
+ Get your Project ID from [antgo.work](https://antgo.work) after creating a project.
40
+
41
+ ```json
42
+ {
43
+ "expo": {
44
+ "extra": {
45
+ "ant": {
46
+ "projectId": "your-project-id"
47
+ }
48
+ }
49
+ }
50
+ }
51
+ ```
52
+
53
+ > The CLI automatically detects `bundleId`, `schemeName`, and `xcworkspace` from your project files. No extra configuration needed.
54
+
55
+ ### 3. Build
56
+
57
+ ```bash
58
+ ant build --platform ios
59
+ ```
60
+
61
+ ---
62
+
63
+ ## Commands
64
+
65
+ ### `ant build`
66
+
67
+ Packages your project, uploads it to the build server, and tracks progress.
68
+
69
+ ```bash
70
+ ant build --platform ios
71
+ ant build --platform ios --profile development
72
+ ant build --platform ios --auto-submit # auto-submit to TestFlight after build
73
+ ant build --platform ios --reauth # re-login to Apple Developer, clear cache
74
+ ant build --platform ios --refresh-profile # recreate Provisioning Profile
75
+ ```
76
+
77
+ | Option | Description |
78
+ |---|---|
79
+ | `--platform <platform>` | `ios` or `android` |
80
+ | `--profile <profile>` | Build profile from `ant.json` (default: `production`) |
81
+ | `--project <path>` | Path to project directory (default: current directory) |
82
+ | `--reauth` | Clear Apple Developer session cache and re-login |
83
+ | `--refresh-profile` | Recreate Provisioning Profile (use when Capabilities change) |
84
+ | `--auto-submit` | Automatically submit IPA to TestFlight after a successful build |
85
+
86
+ ---
87
+
88
+ ### `ant status <jobId>`
89
+
90
+ Check the status of a build job.
91
+
92
+ ```bash
93
+ ant status abc123xyz
94
+ ```
95
+
96
+ ```
97
+ Job ID: abc123xyz
98
+ Status: SUCCESS
99
+ Created: 4/27/2026, 10:30:00 AM
100
+ Updated: 4/27/2026, 10:45:12 AM
101
+ IPA: https://storage.googleapis.com/.../MyApp.ipa
102
+ ```
103
+
104
+ ---
105
+
106
+ ### `ant auth login`
107
+
108
+ Login to your antgo.work account. Token is stored at `~/.ant-go/config.json` and valid for 24 hours.
109
+
110
+ ```bash
111
+ ant auth login
112
+ ant auth login --browser # Google OAuth
113
+ ```
114
+
115
+ ### `ant auth logout`
116
+
117
+ Logout and revoke the current token.
118
+
119
+ ```bash
120
+ ant auth logout
121
+ ```
122
+
123
+ ### `ant auth whoami`
124
+
125
+ Display information about the currently logged-in account.
126
+
127
+ ```bash
128
+ ant auth whoami
129
+ ```
130
+
131
+ ```
132
+ Name: John Doe
133
+ Email: dev@example.com
134
+ Plan: Pro
135
+ Credits: 12 / 15
136
+ Expires: 2026-05-01 10:30:00
137
+ ```
138
+
139
+ ---
140
+
141
+ ## Build Profiles (`ant.json`)
142
+
143
+ Place `ant.json` at the root of your project (next to `app.json`). If it doesn't exist, the CLI will create it with default profiles on first run.
144
+
145
+ ```json
146
+ {
147
+ "build": {
148
+ "production": {
149
+ "distribution": "store"
150
+ },
151
+ "development": {
152
+ "developmentClient": true,
153
+ "distribution": "internal"
154
+ },
155
+ "preview": {
156
+ "distribution": "internal"
157
+ }
158
+ }
159
+ }
160
+ ```
161
+
162
+ | Profile | `distribution` | Use when |
163
+ |---|---|---|
164
+ | `production` | `store` | Releasing to App Store or distributing via TestFlight |
165
+ | `development` | `internal` | Debugging on a real device, connecting to Metro bundler |
166
+ | `preview` | `internal` | Sharing internal test builds before going to the store |
167
+
168
+ > **`distribution: internal`** requires registering device UDIDs beforehand. The CLI guides you through this automatically via QR code.
169
+
170
+ ---
171
+
172
+ ## Add Device (iOS internal build)
173
+
174
+ When building with `distribution: internal`, the CLI will ask which devices to include. To register a new device:
175
+
176
+ 1. CLI displays a **QR code** in the terminal.
177
+ 2. Scan the QR code with the **Camera app** on your iPhone (no separate app needed).
178
+ 3. iPhone downloads `.mobileconfig` → automatically sends UDID to the server.
179
+ 4. CLI receives the UDID, registers it on **Apple Developer Portal**, and creates a new Provisioning Profile.
180
+
181
+ ```bash
182
+ ant build --platform ios --profile development
183
+ ```
184
+
185
+ ---
186
+
187
+ ## Auto Submit to TestFlight
188
+
189
+ Add `--auto-submit` to automatically upload your IPA to TestFlight after a successful build (only works with `distribution: store`).
190
+
191
+ ```bash
192
+ ant build --platform ios --auto-submit
193
+ ```
194
+
195
+ The CLI will automatically:
196
+ 1. Generate and cache an **App Store Connect API Key** from your Apple Developer Portal session.
197
+ 2. Upload the key to the server for use during submission.
198
+ 3. After the build completes, the server triggers a TestFlight submission job.
199
+
200
+ ---
201
+
202
+ ## How It Works
203
+
204
+ ```
205
+ ant build --platform ios
206
+
207
+ ├── Read app.json + ant.json
208
+ ├── Login to Apple Developer (cached 24h)
209
+ ├── Create/reuse Distribution Certificate (.p12)
210
+ ├── Create/reuse Provisioning Profile
211
+ ├── POST /api/builds → receive jobId + signed upload URLs
212
+ ├── Upload project.tar.gz → GCS
213
+ ├── Upload credentials.json → GCS
214
+ ├── POST /api/builds/:id/start
215
+
216
+ └── Track at: https://antgo.work/account/app/MyApp/builds/abc123xyz
217
+
218
+ Mac Build Server (automatic):
219
+ ├── Download + extract project
220
+ ├── npm install + Fastlane + Xcode build
221
+ ├── Upload IPA → GCS
222
+ └── Update status + deduct credit → realtime dashboard
223
+ ```
224
+
225
+ ---
226
+
227
+ ## Credential Caching
228
+
229
+ The CLI caches Apple credentials at `~/.ant-go/creds-<profileName>.json` (TTL: 24h):
230
+
231
+ - Apple Developer session
232
+ - Distribution Certificate (`.p12`)
233
+ - Provisioning Profile
234
+ - App Store Connect API Key (no expiry)
235
+
236
+ Use `--reauth` to clear the cache and re-login from scratch.
237
+
238
+ ---
239
+
240
+ ## Full Documentation
241
+
242
+ 👉 **[antgo.work/docs](https://antgo.work/docs)**
package/bin/ant-go.js ADDED
@@ -0,0 +1,157 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { program } = require('commander');
4
+ const { version } = require('../package.json');
5
+ const { startUpdateCheck } = require('../src/update-check');
6
+ const { setLang, isFirstRun, markFirstRunDone } = require('../src/config');
7
+ const { t, getLang } = require('../src/i18n');
8
+ const chalk = require('chalk');
9
+
10
+ // ── First-run welcome ─────────────────────────────────────────────────────────
11
+ if (isFirstRun()) {
12
+ markFirstRunDone();
13
+ console.log('');
14
+ console.log(chalk.bold.cyan(` 🐜 ant-go CLI v${version}`));
15
+ console.log(chalk.gray(' ─────────────────────────────────'));
16
+ console.log(` ${t('firstRunWelcome')}`);
17
+ console.log('');
18
+ console.log(` ${t('firstRunGetStarted')}`);
19
+ console.log(` ${chalk.cyan('ant auth login')}`);
20
+ console.log('');
21
+ console.log(` ${t('firstRunDocs')} ${chalk.cyan('https://antgo.work/docs')}`);
22
+ console.log('');
23
+ console.log(chalk.yellow(` ${t('firstRunLangHint')}`));
24
+ console.log('');
25
+ }
26
+
27
+ // Kick off update check immediately (non-blocking)
28
+ const showUpdateHint = startUpdateCheck();
29
+
30
+ program
31
+ .name('ant')
32
+ .description('CLI to trigger iOS and Android builds')
33
+ .version(version);
34
+
35
+ // ── auth ──────────────────────────────────────────────────────────────────────
36
+ const auth = program
37
+ .command('auth')
38
+ .description('Manage account authentication');
39
+
40
+ auth
41
+ .command('login')
42
+ .description('Login to your ant-go account')
43
+ .option('--browser', 'Login with Google via browser')
44
+ .action(async (options) => {
45
+ const { loginCommand } = require('../src/commands/auth');
46
+ await loginCommand({ browser: !!options.browser });
47
+ await showUpdateHint();
48
+ process.exit(0);
49
+ });
50
+
51
+ auth
52
+ .command('logout')
53
+ .description('Logout from current account')
54
+ .action(async () => {
55
+ const { logoutCommand } = require('../src/commands/auth');
56
+ await logoutCommand();
57
+ await showUpdateHint();
58
+ });
59
+
60
+ auth
61
+ .command('whoami')
62
+ .description('Show current logged-in account info')
63
+ .action(async () => {
64
+ const { whoamiCommand } = require('../src/commands/auth');
65
+ await whoamiCommand();
66
+ await showUpdateHint();
67
+ });
68
+
69
+ // ── build ─────────────────────────────────────────────────────────────────────
70
+ program
71
+ .command('build')
72
+ .description('Trigger a build (ios or android)')
73
+ .option('--platform <platform>', 'Build platform: ios or android')
74
+ .option('--profile <profile>', 'Build profile from ant.json (default: production)', 'production')
75
+ .option('--project <path>', 'Path to project directory (default: current directory)')
76
+ .option('--reauth', 'Clear Apple Developer session cache and re-login')
77
+ .option('--refresh-profile', 'Recreate Provisioning Profile')
78
+ .option('--auto-submit', 'Auto submit IPA to TestFlight after successful build')
79
+ .action(async (options) => {
80
+ const { runBuild } = require('../src/commands/build');
81
+ await runBuild(options);
82
+ await showUpdateHint();
83
+ });
84
+
85
+ // ── status ────────────────────────────────────────────────────────────────────
86
+ program
87
+ .command('status <jobId>')
88
+ .description('Check build job status')
89
+ .action(async (jobId) => {
90
+ const { checkStatus } = require('../src/commands/status');
91
+ await checkStatus(jobId);
92
+ await showUpdateHint();
93
+ });
94
+
95
+ // ── set ───────────────────────────────────────────────────────────────────────
96
+ const set = program
97
+ .command('set')
98
+ .description('Set CLI preferences');
99
+
100
+ set
101
+ .command('lang <lang>')
102
+ .description('Set language: vi (Vietnamese) or en (English)')
103
+ .action((lang) => {
104
+ const valid = ['vi', 'en'];
105
+ if (!valid.includes(lang)) {
106
+ console.log('');
107
+ console.log(chalk.red(` ${t('langInvalid')}`));
108
+ console.log('');
109
+ process.exit(1);
110
+ }
111
+ setLang(lang);
112
+ console.log('');
113
+ console.log(chalk.green(` ${t('langSet', lang)}`));
114
+ console.log(chalk.gray(` ${t('firstRunLangHint')}`));
115
+ console.log('');
116
+ });
117
+
118
+ // ── uninstall ─────────────────────────────────────────────────────────────────
119
+ program
120
+ .command('uninstall')
121
+ .description('Uninstall ant-go CLI from this machine')
122
+ .action(async () => {
123
+ const inquirer = require('inquirer');
124
+ const { execSync } = require('child_process');
125
+ const ora = require('ora');
126
+
127
+ console.log('');
128
+ const { confirm } = await inquirer.prompt([{
129
+ type: 'confirm',
130
+ name: 'confirm',
131
+ message: t('uninstallConfirm'),
132
+ default: false,
133
+ }]);
134
+
135
+ if (!confirm) {
136
+ console.log(chalk.gray(` ${t('uninstallCancelled')}`));
137
+ console.log('');
138
+ process.exit(0);
139
+ }
140
+
141
+ const spinner = ora(t('uninstallRunning')).start();
142
+ try {
143
+ execSync('npm uninstall -g ant-go', { stdio: 'ignore' });
144
+ spinner.succeed(chalk.green(t('uninstallDone')));
145
+ console.log('');
146
+ } catch (err) {
147
+ spinner.fail(chalk.red(t('uninstallFailed', err.message)));
148
+ console.log('');
149
+ process.exit(1);
150
+ }
151
+ });
152
+
153
+ program.parse(process.argv);
154
+
155
+ if (!process.argv.slice(2).length) {
156
+ program.outputHelp();
157
+ }
@@ -0,0 +1,207 @@
1
+ # Add Device Flow — CLI
2
+
3
+ Luồng đăng ký UDID thiết bị iOS từ CLI, được kích hoạt tự động khi chạy `ant build` với profile `distribution: internal`.
4
+
5
+ ---
6
+
7
+ ## Tổng quan
8
+
9
+ ```
10
+ Developer Machine (CLI) antgo.work API iPhone
11
+ │ │ │
12
+ ├── ant build --profile development │
13
+ ├── ensureToken() │ │
14
+ │ │ │
15
+ ├── GET /api/user/me ─────────────►│ trả plan, quota, devices │
16
+ │◄── { plan, freeBuildsRemaining, │ │
17
+ │ devices: [...] } ───────────┤ │
18
+ ├── check quota (free builds?) │ │
19
+ │ │ │
20
+ ├── distribution: internal? │ │
21
+ │ └─ selectDevices() │ │
22
+ │ ├─ multi-select UI │ │
23
+ │ └─ "Thêm device mới": │ │
24
+ │ POST /device-enroll/create │
25
+ │◄── { token, enrollUrl } ─────────┤ │
26
+ │ hiện QR code │ │
27
+ │ poll status 3s │◄── iPhone quét QR ────────┤
28
+ │ │ iOS gửi UDID │
29
+ │◄── { status: registered, udid } ◄┤ │
30
+ │ POST /api/devices ►│ lưu Firestore │
31
+ │ loop lại chọn │ │
32
+ │ │ │
33
+ ├── Apple login (cache / fresh) │ │
34
+ ├── sync UDIDs → Apple Developer (Device.getAsync / createAsync)
35
+ ├── Certificate (reuse / tạo mới) │ │
36
+ ├── Provisioning Profile({ devices: [id1, id2, ...] }) │
37
+ └── createBuild → upload → start │ │
38
+ ```
39
+
40
+ ---
41
+
42
+ ## Điều kiện kích hoạt
43
+
44
+ Flow này chỉ chạy khi cả hai điều kiện đều đúng:
45
+
46
+ 1. `--platform ios`
47
+ 2. Profile được chọn có `distribution: "internal"` trong `ant.json`
48
+
49
+ ```json
50
+ {
51
+ "build": {
52
+ "development": {
53
+ "distribution": "internal",
54
+ "developmentClient": true
55
+ }
56
+ }
57
+ }
58
+ ```
59
+
60
+ Lệnh kích hoạt:
61
+ ```bash
62
+ ant build --platform ios --profile development
63
+ ```
64
+
65
+ ---
66
+
67
+ ## Các file liên quan
68
+
69
+ | File | Vai trò |
70
+ |---|---|
71
+ | `cli/src/commands/build.js` | Entry point — fetch user info, check quota, gọi `ensureAppleCreds()` |
72
+ | `cli/src/api.js` | `fetchUserInfo()`, `saveDevice()` |
73
+ | `cli/src/apple-creds.js` | `enrollDevice()`, `selectDevices()`, `ensureAppleCreds()` |
74
+ | `app/api/user/me/route.ts` | Trả user info + danh sách devices (fresh từ Firestore) |
75
+ | `app/api/device-enroll/create/route.ts` | Tạo enrollment session |
76
+ | `app/api/device-enroll/[token]/profile/route.ts` | Trả `.mobileconfig` cho iPhone |
77
+ | `app/api/device-enroll/[token]/complete/route.ts` | Nhận UDID từ iOS |
78
+ | `app/api/device-enroll/[token]/status/route.ts` | CLI poll để biết UDID về chưa |
79
+ | `app/api/devices/route.ts` | Lưu / đọc devices — chấp nhận cả Firebase ID token lẫn CLI token |
80
+
81
+ ---
82
+
83
+ ## Flow chi tiết
84
+
85
+ ### Bước 1 — Fetch user info
86
+
87
+ Ngay sau `ensureToken()`, CLI gọi `GET /api/user/me` với CLI token:
88
+
89
+ ```
90
+ Response: {
91
+ plan: "free|starter|pro|team",
92
+ planStatus: "active|past_due|canceled|null",
93
+ freeBuildsRemaining: 5,
94
+ devices: [
95
+ { udid, name, deviceProduct, deviceSerial, addedAt }
96
+ ]
97
+ }
98
+ ```
99
+
100
+ Kiểm tra quota:
101
+ - `plan === "free"` và `freeBuildsRemaining <= 0` → báo lỗi, thoát.
102
+ - `planStatus === "past_due"` → hiện cảnh báo thanh toán, tiếp tục.
103
+
104
+ ---
105
+
106
+ ### Bước 2 — Multi-select device (`selectDevices`)
107
+
108
+ Khi `distribution: internal`, CLI gọi `selectDevices(userDevices, projectId, apiClient)`.
109
+
110
+ **Trường hợp chưa có device nào:** đi thẳng vào enrollment mới.
111
+
112
+ **Trường hợp đã có device:** hiển thị checkbox multi-select:
113
+
114
+ ```
115
+ 📱 Chọn device để build (Space = chọn/bỏ, Enter = xác nhận):
116
+
117
+ ◉ My iPhone iPhone17,1 (00008110-001234...)
118
+ ◯ iPad Pro 11" iPad8,1 (00008130-000ABC...)
119
+ ─────────────────────────────────────────────────────
120
+ ◯ + Thêm device mới
121
+ ```
122
+
123
+ **Nếu user chọn "+ Thêm device mới":**
124
+
125
+ 1. Chạy `enrollDevice(projectId)`:
126
+ - `POST /api/device-enroll/create` → nhận `token`, `enrollUrl`
127
+ - Hiện QR code trong terminal
128
+ - Poll `GET /api/device-enroll/{token}/status` mỗi 3 giây (max 10 phút)
129
+ - Nhận `{ udid, deviceProduct, deviceSerial }`
130
+ 2. Prompt tên device.
131
+ 3. `POST /api/devices` → lưu vào Firestore `users/{uid}/devices/{udid}`.
132
+ 4. Thêm device mới vào danh sách, **quay lại màn hình chọn**.
133
+
134
+ Return: `string[]` — mảng các UDID được chọn.
135
+
136
+ ---
137
+
138
+ ### Bước 3 — Đăng nhập Apple Developer
139
+
140
+ `ensureAppleCreds()` xử lý cache (TTL 24h) → login → 2FA → chọn team.
141
+
142
+ ---
143
+
144
+ ### Bước 4 — Đồng bộ UDIDs lên Apple Developer Portal
145
+
146
+ Với mỗi UDID trong `selectedUdids`:
147
+
148
+ ```
149
+ Device.getAsync(authCtx)
150
+ ├── UDID đã có trên Apple? → lấy deviceId
151
+ └── Chưa có → Device.createAsync({ name, udid, platform: 'IOS' }) → lấy deviceId mới
152
+
153
+ deviceIds = [id1, id2, ...]
154
+ ```
155
+
156
+ ---
157
+
158
+ ### Bước 5 — Certificate + Provisioning Profile
159
+
160
+ - **Certificate** (Development): reuse nếu còn hợp lệ, tạo mới nếu không.
161
+ - **Provisioning Profile** (Development): tạo với **toàn bộ** `deviceIds`:
162
+ ```js
163
+ Profile.createAsync(authCtx, {
164
+ bundleId: bundleIdObj.id,
165
+ certificates: [certId],
166
+ devices: deviceIds, // tất cả device đã chọn
167
+ profileType: ProfileType.IOS_APP_DEVELOPMENT,
168
+ })
169
+ ```
170
+
171
+ ---
172
+
173
+ ### Bước 6 — Build
174
+
175
+ CLI đóng gói project, upload lên GCS và gửi build job — giống hệt flow build bình thường (xem [../../docs/build-flow.md](../../docs/build-flow.md)).
176
+
177
+ ---
178
+
179
+ ## Cache credentials
180
+
181
+ File: `~/.ant-go/creds-<profileName>.json` (TTL 24h, mode 0600)
182
+
183
+ ```json
184
+ {
185
+ "appleId": "dev@example.com",
186
+ "p12Base64": "...",
187
+ "p12Password": "...",
188
+ "mobileprovisionBase64": "...",
189
+ "teamId": "TEAMID123",
190
+ "udids": ["00008110-001234ABCDEF", "00008130-000ABC123"],
191
+ "_savedAt": 1714512345678
192
+ }
193
+ ```
194
+
195
+ **Backward compat:** cache cũ có field `udid` (string) vẫn được đọc, convert thành `udids: [udid]`.
196
+
197
+ ---
198
+
199
+ ## Lỗi thường gặp
200
+
201
+ | Lỗi | Nguyên nhân | Cách xử lý |
202
+ |---|---|---|
203
+ | `Bạn đã hết lượt build miễn phí` | `freeBuildsRemaining <= 0` | Nâng cấp plan tại antgo.work/account/billing |
204
+ | `Device enrollment timeout` | iPhone không quét QR trong 10 phút | Chạy lại lệnh, quét QR nhanh hơn |
205
+ | `Không tạo được enrollment session` | Mất kết nối đến antgo.work | Kiểm tra mạng, thử lại |
206
+ | `App ID không tồn tại trên Apple Developer` | `bundleId` chưa được tạo trên portal | Tạo App ID thủ công tại developer.apple.com |
207
+ | `Đăng nhập thất bại` | Sai Apple ID / password | Kiểm tra App-Specific Password tại appleid.apple.com |
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "ant-go",
3
+ "version": "0.1.22",
4
+ "description": "CLI to trigger iOS builds via ant-go build server",
5
+ "main": "src/index.js",
6
+ "bin": {
7
+ "ant": "bin/ant-go.js"
8
+ },
9
+ "scripts": {
10
+ "start": "node bin/ant-go.js"
11
+ },
12
+ "dependencies": {
13
+ "@expo/apple-utils": "^2.1.21",
14
+ "axios": "^1.7.2",
15
+ "chalk": "^4.1.2",
16
+ "commander": "^12.1.0",
17
+ "inquirer": "^8.2.7",
18
+ "ora": "^5.4.1",
19
+ "qrcode-terminal": "^0.12.0"
20
+ },
21
+ "engines": {
22
+ "node": ">=18.0.0"
23
+ }
24
+ }
package/src/api.js ADDED
@@ -0,0 +1,64 @@
1
+ /**
2
+ * api.js — HTTP client gọi tới ant Next.js API
3
+ */
4
+
5
+ const axios = require('axios');
6
+
7
+ function createClient(apiUrl, authToken) {
8
+ const headers = { 'Content-Type': 'application/json' };
9
+ if (authToken) headers['Authorization'] = `Bearer ${authToken}`;
10
+ return axios.create({
11
+ baseURL: apiUrl.replace(/\/$/, ''),
12
+ headers,
13
+ timeout: 30_000,
14
+ });
15
+ }
16
+
17
+ // POST /api/builds — tạo build job, nhận signed URL
18
+ async function createBuild(client, payload = {}) {
19
+ const { data } = await client.post('/api/builds', payload);
20
+ return data; // { jobId, uploadUrl }
21
+ }
22
+
23
+ // GET /api/builds/:id — lấy status
24
+ async function getBuildStatus(client, jobId) {
25
+ const { data } = await client.get(`/api/builds/${jobId}`);
26
+ return data;
27
+ }
28
+
29
+ // Lấy user info + devices list (fresh từ Firestore)
30
+ async function fetchUserInfo(client) {
31
+ const { data } = await client.get('/api/user/me');
32
+ return data;
33
+ }
34
+
35
+ // Lưu device mới vào Firestore sau khi enroll thành công
36
+ async function saveDevice(client, { udid, name, deviceProduct, deviceSerial }) {
37
+ const { data } = await client.post('/api/devices', {
38
+ udid,
39
+ name,
40
+ deviceProduct,
41
+ deviceSerial,
42
+ source: 'cli',
43
+ });
44
+ return data;
45
+ }
46
+
47
+ // Upload ASC API Key lên dashboard (per-user, per-team)
48
+ async function uploadAscKey(client, { teamId, keyId, issuerId, privateKeyP8 }) {
49
+ const { data } = await client.post('/api/user/asc-key', {
50
+ teamId,
51
+ keyId,
52
+ issuerId,
53
+ privateKeyP8,
54
+ });
55
+ return data;
56
+ }
57
+
58
+ // Lưu danh sách capabilities đang enabled vào dashboard (per-app)
59
+ async function uploadCapabilities(client, { projectId, capabilities }) {
60
+ const { data } = await client.post(`/api/apps/${projectId}/capabilities`, { capabilities });
61
+ return data;
62
+ }
63
+
64
+ module.exports = { createClient, createBuild, getBuildStatus, fetchUserInfo, saveDevice, uploadAscKey, uploadCapabilities };