@glash/cli 0.2.35 → 0.2.37
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/commands/deploy.mjs +5 -2
- package/commands/login.mjs +95 -2
- package/package.json +1 -1
package/commands/deploy.mjs
CHANGED
|
@@ -711,9 +711,12 @@ function sourceUploadPartSize(value) {
|
|
|
711
711
|
}
|
|
712
712
|
|
|
713
713
|
function sourceUploadConcurrency(flags = {}) {
|
|
714
|
-
|
|
714
|
+
// Default 3: fewer simultaneous part uploads means each connection gets more of
|
|
715
|
+
// a constrained uplink and finishes before it can be reset mid-stream. Override
|
|
716
|
+
// with --source-concurrency or GLASH_SOURCE_UPLOAD_CONCURRENCY.
|
|
717
|
+
const raw = flags['source-concurrency'] ?? process.env.GLASH_SOURCE_UPLOAD_CONCURRENCY ?? '3';
|
|
715
718
|
const parsed = Number(raw);
|
|
716
|
-
if (!Number.isFinite(parsed) || parsed < 1) return
|
|
719
|
+
if (!Number.isFinite(parsed) || parsed < 1) return 3;
|
|
717
720
|
return Math.max(1, Math.min(8, Math.floor(parsed)));
|
|
718
721
|
}
|
|
719
722
|
|
package/commands/login.mjs
CHANGED
|
@@ -1,14 +1,107 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { platform } from 'node:process';
|
|
3
|
+
import { hostname } from 'node:os';
|
|
1
4
|
import { apiPost } from '../lib/api.mjs';
|
|
2
5
|
import { writeCreds } from '../lib/config.mjs';
|
|
3
|
-
import { prompt, ok, fail } from '../lib/ui.mjs';
|
|
6
|
+
import { prompt, ok, fail, info, warn, color } from '../lib/ui.mjs';
|
|
7
|
+
|
|
8
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
9
|
+
|
|
10
|
+
function openBrowser(url) {
|
|
11
|
+
try {
|
|
12
|
+
const opener = platform === 'darwin' ? 'open' : platform === 'win32' ? 'cmd' : 'xdg-open';
|
|
13
|
+
const args = platform === 'win32' ? ['/c', 'start', '', url] : [url];
|
|
14
|
+
spawn(opener, args, { stdio: 'ignore', detached: true }).unref();
|
|
15
|
+
return true;
|
|
16
|
+
} catch {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
4
20
|
|
|
5
21
|
export async function login({ flags }) {
|
|
22
|
+
// Explicit password login (headless/CI). Otherwise use browser-based approval.
|
|
23
|
+
if (flags.email || flags.password) {
|
|
24
|
+
return passwordLogin(flags);
|
|
25
|
+
}
|
|
26
|
+
return browserLogin(flags);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function browserLogin(flags) {
|
|
30
|
+
let start;
|
|
31
|
+
try {
|
|
32
|
+
start = await apiPost('/auth/cli/device/start', { deviceName: hostname() });
|
|
33
|
+
} catch (e) {
|
|
34
|
+
// Older backend without device auth — fall back to password login.
|
|
35
|
+
warn('Browser login is unavailable; falling back to email/password.');
|
|
36
|
+
return passwordLogin(flags);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const { deviceCode, userCode, verificationUri, verificationUriComplete, interval = 5, expiresIn = 600 } = start;
|
|
40
|
+
const approveUrl = verificationUriComplete || verificationUri;
|
|
41
|
+
|
|
42
|
+
info('');
|
|
43
|
+
info(` To sign in, approve this request in your browser:`);
|
|
44
|
+
info(` ${color.cyan(approveUrl)}`);
|
|
45
|
+
info(` Verification code: ${color.bold(userCode)}`);
|
|
46
|
+
info('');
|
|
47
|
+
|
|
48
|
+
if (flags['no-browser']) {
|
|
49
|
+
info(' Open the link above to continue.');
|
|
50
|
+
} else if (openBrowser(approveUrl)) {
|
|
51
|
+
info(color.dim(' Opened your browser… waiting for approval.'));
|
|
52
|
+
} else {
|
|
53
|
+
info(' Could not open a browser automatically — open the link above.');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const deadline = Date.now() + expiresIn * 1000;
|
|
57
|
+
let delay = Math.max(2, interval) * 1000;
|
|
58
|
+
try {
|
|
59
|
+
while (Date.now() < deadline) {
|
|
60
|
+
await sleep(delay);
|
|
61
|
+
let r;
|
|
62
|
+
try {
|
|
63
|
+
r = await apiPost('/auth/cli/device/poll', { deviceCode });
|
|
64
|
+
} catch (e) {
|
|
65
|
+
return fail(e.message);
|
|
66
|
+
}
|
|
67
|
+
if (r?.status === 'approved' && r.accessToken) {
|
|
68
|
+
await writeCreds({ token: r.accessToken, email: r.user?.email });
|
|
69
|
+
return ok(`Signed in as ${r.user?.email ?? 'your account'}`);
|
|
70
|
+
}
|
|
71
|
+
if (r?.status === 'denied') return fail('Sign-in was denied in the browser.');
|
|
72
|
+
if (r?.status === 'expired') return fail('Sign-in request expired. Run `glash login` again.');
|
|
73
|
+
// status === 'pending' → keep polling.
|
|
74
|
+
}
|
|
75
|
+
return fail('Timed out waiting for approval. Run `glash login` again.');
|
|
76
|
+
} catch (e) {
|
|
77
|
+
return fail(e.message);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function passwordLogin(flags) {
|
|
6
82
|
const email = flags.email || (await prompt('Email: '));
|
|
7
83
|
const password = flags.password || (await prompt('Password: ', { mask: true }));
|
|
8
84
|
if (!email || !password) return fail('email and password required');
|
|
9
85
|
|
|
10
86
|
try {
|
|
11
|
-
|
|
87
|
+
let r = await apiPost('/auth/login', { email, password });
|
|
88
|
+
|
|
89
|
+
// Second factor is always required. Complete the email-OTP challenge.
|
|
90
|
+
if (!r?.accessToken && r?.secondFactorRequired && !r?.challengeId) {
|
|
91
|
+
r = await apiPost('/auth/login', { email, password, secondFactorMethod: 'email_otp' });
|
|
92
|
+
}
|
|
93
|
+
if (!r?.accessToken && (r?.otpRequired || r?.challengeId)) {
|
|
94
|
+
const code = flags.otp || (await prompt('Enter the 6-digit code sent to your email: '));
|
|
95
|
+
if (!code) throw new Error('A verification code is required to sign in.');
|
|
96
|
+
r = await apiPost('/auth/login', {
|
|
97
|
+
email,
|
|
98
|
+
password,
|
|
99
|
+
emailOtpCode: String(code).trim(),
|
|
100
|
+
loginOtpChallengeId: r.challengeId,
|
|
101
|
+
secondFactorMethod: 'email_otp',
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
12
105
|
if (!r?.accessToken || !r?.user?.email) {
|
|
13
106
|
throw new Error('Login response was missing a session token. Please try again.');
|
|
14
107
|
}
|
package/package.json
CHANGED