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 +242 -0
- package/bin/ant-go.js +157 -0
- package/docs/add-device-flow.md +207 -0
- package/package.json +24 -0
- package/src/api.js +64 -0
- package/src/apple-creds.js +687 -0
- package/src/commands/auth.js +293 -0
- package/src/commands/build.js +516 -0
- package/src/commands/configure.js +31 -0
- package/src/commands/status.js +50 -0
- package/src/config.js +68 -0
- package/src/i18n.js +584 -0
- package/src/logger.js +14 -0
- package/src/update-check.js +106 -0
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* auth.js — `ant auth` commands
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const http = require('http');
|
|
6
|
+
const crypto = require('crypto');
|
|
7
|
+
const { exec } = require('child_process');
|
|
8
|
+
const axios = require('axios');
|
|
9
|
+
const chalk = require('chalk');
|
|
10
|
+
const ora = require('ora');
|
|
11
|
+
const inquirer = require('inquirer');
|
|
12
|
+
|
|
13
|
+
const { API_URL, getAuth, setAuth, clearAuth, isLoggedIn } = require('../config');
|
|
14
|
+
const { createClient, fetchUserInfo } = require('../api');
|
|
15
|
+
const { t, tError } = require('../i18n');
|
|
16
|
+
|
|
17
|
+
// ── login ─────────────────────────────────────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
async function loginCommand({ browser = false } = {}) {
|
|
20
|
+
if (isLoggedIn()) {
|
|
21
|
+
const session = getAuth();
|
|
22
|
+
console.log('');
|
|
23
|
+
console.log(chalk.yellow(t('alreadyLoggedIn', session.email)));
|
|
24
|
+
console.log(chalk.gray(' ' + t('logoutHint')));
|
|
25
|
+
console.log('');
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
if (browser) return browserLoginFlow();
|
|
29
|
+
return emailLoginFlow();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function emailLoginFlow() {
|
|
33
|
+
console.log('');
|
|
34
|
+
console.log(chalk.bold(t('loginTitle')));
|
|
35
|
+
console.log(chalk.gray(' ' + t('loginBrowserHint')));
|
|
36
|
+
console.log('');
|
|
37
|
+
|
|
38
|
+
const { email, password } = await inquirer.prompt([
|
|
39
|
+
{
|
|
40
|
+
type: 'input',
|
|
41
|
+
name: 'email',
|
|
42
|
+
message: t('loginEmailLabel'),
|
|
43
|
+
validate: (v) => (v.includes('@') ? true : t('loginEmailInvalid')),
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
type: 'password',
|
|
47
|
+
name: 'password',
|
|
48
|
+
message: t('loginPasswordLabel'),
|
|
49
|
+
mask: '•',
|
|
50
|
+
},
|
|
51
|
+
]);
|
|
52
|
+
|
|
53
|
+
const spinner = ora(t('loginLoading')).start();
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
const { data } = await axios.post(`${API_URL}/api/auth/cli-login`, { email, password });
|
|
57
|
+
|
|
58
|
+
setAuth({
|
|
59
|
+
token: data.cliToken,
|
|
60
|
+
refreshToken: data.refreshToken,
|
|
61
|
+
expiresAt: data.expiresAt,
|
|
62
|
+
uid: data.uid,
|
|
63
|
+
email: data.email,
|
|
64
|
+
displayName: data.displayName,
|
|
65
|
+
photoURL: data.photoURL,
|
|
66
|
+
plan: data.plan,
|
|
67
|
+
builds: data.builds,
|
|
68
|
+
credits: data.credits,
|
|
69
|
+
planCredits: data.planCredits,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
spinner.succeed(t('loginSuccess'));
|
|
73
|
+
printUserInfo(data);
|
|
74
|
+
} catch (err) {
|
|
75
|
+
spinner.fail(t('loginFailed'));
|
|
76
|
+
const raw = err.response?.data?.error ?? err.message;
|
|
77
|
+
console.error(chalk.red(` ✖ ${tError(raw)}`));
|
|
78
|
+
console.log('');
|
|
79
|
+
console.log(chalk.yellow(` ${t('loginNoAccount')}`));
|
|
80
|
+
console.log(` ${t('loginRegisterAt')} ${chalk.cyan('https://antgo.work/register')}`);
|
|
81
|
+
console.log('');
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function browserLoginFlow() {
|
|
87
|
+
const state = crypto.randomBytes(16).toString('hex');
|
|
88
|
+
const port = await findAvailablePort(9005);
|
|
89
|
+
|
|
90
|
+
return new Promise((resolve, reject) => {
|
|
91
|
+
const timeout = setTimeout(() => {
|
|
92
|
+
server.close();
|
|
93
|
+
reject(new Error(t('browserTimeout')));
|
|
94
|
+
}, 5 * 60 * 1000);
|
|
95
|
+
|
|
96
|
+
const server = http.createServer((req, res) => {
|
|
97
|
+
const url = new URL(req.url, `http://localhost:${port}`);
|
|
98
|
+
if (url.pathname !== '/callback') { res.writeHead(404); res.end('Not found'); return; }
|
|
99
|
+
|
|
100
|
+
const receivedState = url.searchParams.get('state');
|
|
101
|
+
if (receivedState !== state) { res.writeHead(400); res.end('Invalid state'); return; }
|
|
102
|
+
|
|
103
|
+
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
104
|
+
res.end(`
|
|
105
|
+
<html><body style="font-family:sans-serif;text-align:center;padding:60px;background:#0f0c29;color:#fff">
|
|
106
|
+
<h2 style="color:#a78bfa">${t('browserSuccess')}</h2>
|
|
107
|
+
<p style="color:#9ca3af">${t('browserSuccessClose')}</p>
|
|
108
|
+
</body></html>
|
|
109
|
+
`);
|
|
110
|
+
|
|
111
|
+
// Destroy socket so server.close() can finish immediately
|
|
112
|
+
res.socket?.destroy();
|
|
113
|
+
|
|
114
|
+
clearTimeout(timeout);
|
|
115
|
+
server.closeAllConnections?.();
|
|
116
|
+
server.close();
|
|
117
|
+
|
|
118
|
+
setAuth({
|
|
119
|
+
token: url.searchParams.get('token'),
|
|
120
|
+
refreshToken: url.searchParams.get('refreshToken') || null,
|
|
121
|
+
expiresAt: url.searchParams.get('expiresAt'),
|
|
122
|
+
uid: url.searchParams.get('uid'),
|
|
123
|
+
email: url.searchParams.get('email'),
|
|
124
|
+
displayName: url.searchParams.get('displayName') || null,
|
|
125
|
+
photoURL: url.searchParams.get('photoURL') || null,
|
|
126
|
+
plan: url.searchParams.get('plan') || 'free',
|
|
127
|
+
builds: parseInt(url.searchParams.get('builds') || '0', 10),
|
|
128
|
+
credits: parseFloat(url.searchParams.get('credits') || '15'),
|
|
129
|
+
planCredits: parseInt(url.searchParams.get('planCredits') || '15', 10),
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
const session = getAuth();
|
|
133
|
+
console.log('');
|
|
134
|
+
console.log(chalk.green(t('browserSuccess')));
|
|
135
|
+
printUserInfo(session);
|
|
136
|
+
resolve();
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
server.listen(port, () => {
|
|
140
|
+
const loginUrl = `${API_URL}/auth/cli?port=${port}&state=${state}`;
|
|
141
|
+
console.log('');
|
|
142
|
+
console.log(t('browserOpening'));
|
|
143
|
+
console.log(chalk.gray(` ${t('browserManual')} ${loginUrl}`));
|
|
144
|
+
openBrowser(loginUrl);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
server.on('error', reject);
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// ── logout ────────────────────────────────────────────────────────────────────
|
|
152
|
+
|
|
153
|
+
async function logoutCommand() {
|
|
154
|
+
const session = getAuth();
|
|
155
|
+
if (!session?.token) {
|
|
156
|
+
console.log('');
|
|
157
|
+
console.log(chalk.yellow(t('notLoggedIn')));
|
|
158
|
+
console.log('');
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
try {
|
|
163
|
+
await axios.delete(`${API_URL}/api/auth/cli-token`, {
|
|
164
|
+
headers: { Authorization: `Bearer ${session.token}` },
|
|
165
|
+
});
|
|
166
|
+
} catch {}
|
|
167
|
+
|
|
168
|
+
clearAuth();
|
|
169
|
+
console.log('');
|
|
170
|
+
console.log(chalk.green(t('logoutSuccess')));
|
|
171
|
+
console.log('');
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// ── whoami ────────────────────────────────────────────────────────────────────
|
|
175
|
+
|
|
176
|
+
async function whoamiCommand() {
|
|
177
|
+
const session = getAuth();
|
|
178
|
+
if (!session?.token) {
|
|
179
|
+
console.log('');
|
|
180
|
+
console.log(chalk.yellow(t('notLoggedIn')));
|
|
181
|
+
console.log(chalk.gray(' ' + t('runLoginHint')));
|
|
182
|
+
console.log('');
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Fetch fresh từ server — không dùng cache
|
|
187
|
+
let info;
|
|
188
|
+
try {
|
|
189
|
+
const client = createClient(API_URL, session.token);
|
|
190
|
+
info = await fetchUserInfo(client);
|
|
191
|
+
// Cập nhật cache credits sau khi fetch
|
|
192
|
+
setAuth({ ...session, credits: info.credits, planCredits: info.planCredits, plan: info.plan });
|
|
193
|
+
} catch (err) {
|
|
194
|
+
const msg = tError(err.response?.data?.error ?? err.message);
|
|
195
|
+
console.log('');
|
|
196
|
+
console.log(chalk.red(`✖ ${msg}`));
|
|
197
|
+
console.log('');
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const isExpired = session.expiresAt && new Date(session.expiresAt) < new Date();
|
|
202
|
+
|
|
203
|
+
console.log('');
|
|
204
|
+
console.log(chalk.bold(t('whoamiTitle')));
|
|
205
|
+
console.log('');
|
|
206
|
+
console.log(` ${chalk.gray(t('whoamiEmail'))} ${info.email ?? session.email ?? '-'}`);
|
|
207
|
+
if (session.displayName) {
|
|
208
|
+
console.log(` ${chalk.gray(t('whoamiName'))} ${session.displayName}`);
|
|
209
|
+
}
|
|
210
|
+
console.log(` ${chalk.gray(t('whoamiPlan'))} ${chalk.cyan(info.plan ?? 'free')}`);
|
|
211
|
+
console.log(` ${chalk.gray(t('whoamiCredits'))} ${info.credits} / ${info.planCredits ?? '-'}`);
|
|
212
|
+
if (isExpired) {
|
|
213
|
+
console.log(` ${chalk.gray(t('whoamiExpires'))} ${chalk.red(t('whoamiExpired'))}`);
|
|
214
|
+
} else if (session.expiresAt) {
|
|
215
|
+
console.log(` ${chalk.gray(t('whoamiExpires'))} ${new Date(session.expiresAt).toLocaleString()}`);
|
|
216
|
+
}
|
|
217
|
+
console.log('');
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// ── ensureToken ───────────────────────────────────────────────────────────────
|
|
221
|
+
|
|
222
|
+
async function ensureToken() {
|
|
223
|
+
if (isLoggedIn()) return getAuth().token;
|
|
224
|
+
|
|
225
|
+
const session = getAuth();
|
|
226
|
+
|
|
227
|
+
if (session?.refreshToken) {
|
|
228
|
+
const spinner = ora(t('sessionRenewing')).start();
|
|
229
|
+
try {
|
|
230
|
+
const { data } = await axios.post(`${API_URL}/api/auth/cli-refresh`, {
|
|
231
|
+
refreshToken: session.refreshToken,
|
|
232
|
+
});
|
|
233
|
+
setAuth({
|
|
234
|
+
token: data.cliToken,
|
|
235
|
+
refreshToken: data.refreshToken,
|
|
236
|
+
expiresAt: data.expiresAt,
|
|
237
|
+
uid: data.uid,
|
|
238
|
+
email: data.email,
|
|
239
|
+
displayName: data.displayName,
|
|
240
|
+
photoURL: data.photoURL,
|
|
241
|
+
plan: data.plan,
|
|
242
|
+
builds: data.builds,
|
|
243
|
+
credits: data.credits,
|
|
244
|
+
planCredits: data.planCredits,
|
|
245
|
+
});
|
|
246
|
+
spinner.succeed(t('sessionRenewed'));
|
|
247
|
+
return data.cliToken;
|
|
248
|
+
} catch {
|
|
249
|
+
spinner.fail(t('sessionRenewFailed'));
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
console.log('');
|
|
254
|
+
console.log(chalk.red(`✖ ${t('sessionExpired')}`));
|
|
255
|
+
console.log(chalk.gray(` ${t('runLoginHint')}`));
|
|
256
|
+
console.log('');
|
|
257
|
+
process.exit(1);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
261
|
+
|
|
262
|
+
function printUserInfo(data) {
|
|
263
|
+
console.log('');
|
|
264
|
+
console.log(` ${chalk.gray(t('whoamiEmail'))} ${data.email ?? '-'}`);
|
|
265
|
+
if (data.displayName) {
|
|
266
|
+
console.log(` ${chalk.gray(t('whoamiName'))} ${data.displayName}`);
|
|
267
|
+
}
|
|
268
|
+
console.log(` ${chalk.gray(t('whoamiPlan'))} ${chalk.cyan(data.plan ?? 'free')}`);
|
|
269
|
+
if (data.credits != null) {
|
|
270
|
+
console.log(` ${chalk.gray(t('whoamiCredits'))} ${data.credits} / ${data.planCredits ?? '-'}`);
|
|
271
|
+
}
|
|
272
|
+
console.log('');
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function openBrowser(url) {
|
|
276
|
+
const cmd =
|
|
277
|
+
process.platform === 'darwin' ? `open "${url}"` :
|
|
278
|
+
process.platform === 'win32' ? `start "" "${url}"` :
|
|
279
|
+
`xdg-open "${url}"`;
|
|
280
|
+
exec(cmd, (err) => {
|
|
281
|
+
if (err) console.log(chalk.gray(` ${t('browserManual')} ${url}`));
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function findAvailablePort(preferred) {
|
|
286
|
+
return new Promise((resolve) => {
|
|
287
|
+
const srv = http.createServer();
|
|
288
|
+
srv.listen(preferred, () => srv.close(() => resolve(preferred)));
|
|
289
|
+
srv.on('error', () => resolve(preferred + Math.floor(Math.random() * 100) + 1));
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
module.exports = { loginCommand, logoutCommand, whoamiCommand, ensureToken };
|