@glash/cli 0.2.21 → 0.2.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/commands/deploy.mjs +1 -0
- package/lib/api.mjs +32 -7
- package/lib/version.mjs +1 -1
- package/package.json +1 -1
package/commands/deploy.mjs
CHANGED
|
@@ -153,6 +153,7 @@ export async function deploy({ flags }) {
|
|
|
153
153
|
formData.append('gitBranch', gitBranch || '');
|
|
154
154
|
formData.append('target', target);
|
|
155
155
|
|
|
156
|
+
info(color.dim('→ uploading bundle to glashDB…'));
|
|
156
157
|
dep = await apiPost('/deployments/upload', formData);
|
|
157
158
|
await rm(zipPath, { force: true }).catch(() => {});
|
|
158
159
|
} catch (e) {
|
package/lib/api.mjs
CHANGED
|
@@ -28,6 +28,12 @@ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
|
28
28
|
// proxies across machines.
|
|
29
29
|
const RETRYABLE_STATUS = new Set([408, 425, 429, 500, 502, 503, 504, 522, 524]);
|
|
30
30
|
const DEFAULT_RETRIES = 4;
|
|
31
|
+
// Per-request wall-clock ceiling. Without this a contended/wedged backend
|
|
32
|
+
// leaves `glash deploy` hanging forever after "source bundle X MB" with no
|
|
33
|
+
// output. On timeout we abort and retry instead of hanging. Uploads (FormData)
|
|
34
|
+
// stream a bundle and register a deployment, so they get a longer ceiling.
|
|
35
|
+
const DEFAULT_TIMEOUT_MS = 60_000;
|
|
36
|
+
const UPLOAD_TIMEOUT_MS = 180_000;
|
|
31
37
|
|
|
32
38
|
function backoffMs(attempt, retryAfter) {
|
|
33
39
|
if (retryAfter && Number.isFinite(retryAfter)) return Math.min(Math.max(retryAfter, 1000), 60000);
|
|
@@ -37,7 +43,7 @@ function backoffMs(attempt, retryAfter) {
|
|
|
37
43
|
return Math.round(jitter);
|
|
38
44
|
}
|
|
39
45
|
|
|
40
|
-
export async function api(method, path, { body, token, query, retries = DEFAULT_RETRIES, signal } = {}) {
|
|
46
|
+
export async function api(method, path, { body, token, query, retries = DEFAULT_RETRIES, signal, timeoutMs } = {}) {
|
|
41
47
|
const headers = {
|
|
42
48
|
Accept: 'application/json',
|
|
43
49
|
'User-Agent': `glash-cli/${VERSION}`,
|
|
@@ -62,25 +68,44 @@ export async function api(method, path, { body, token, query, retries = DEFAULT_
|
|
|
62
68
|
// re-streamable — fetch reads them fresh on each attempt, which lets us
|
|
63
69
|
// retry uploads without re-zipping.
|
|
64
70
|
const requestBody = isForm ? body : (body === undefined ? undefined : JSON.stringify(body));
|
|
71
|
+
const effectiveTimeout = timeoutMs ?? (isForm ? UPLOAD_TIMEOUT_MS : DEFAULT_TIMEOUT_MS);
|
|
65
72
|
|
|
66
73
|
let attempt = 0;
|
|
67
74
|
// eslint-disable-next-line no-constant-condition
|
|
68
75
|
while (true) {
|
|
76
|
+
// Each attempt gets a fresh AbortController so the timeout can fire
|
|
77
|
+
// independently. If the caller passed their own signal, chain it.
|
|
78
|
+
const ctrl = new AbortController();
|
|
79
|
+
let timedOut = false;
|
|
80
|
+
const timer = setTimeout(() => { timedOut = true; ctrl.abort(); }, effectiveTimeout);
|
|
81
|
+
const onCallerAbort = () => ctrl.abort();
|
|
82
|
+
if (signal) {
|
|
83
|
+
if (signal.aborted) ctrl.abort();
|
|
84
|
+
else signal.addEventListener('abort', onCallerAbort, { once: true });
|
|
85
|
+
}
|
|
86
|
+
|
|
69
87
|
let res;
|
|
70
88
|
try {
|
|
71
|
-
res = await fetch(url, { method, headers, body: requestBody, signal });
|
|
89
|
+
res = await fetch(url, { method, headers, body: requestBody, signal: ctrl.signal });
|
|
72
90
|
} catch (e) {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
//
|
|
76
|
-
|
|
91
|
+
clearTimeout(timer);
|
|
92
|
+
signal?.removeEventListener?.('abort', onCallerAbort);
|
|
93
|
+
// A caller-initiated cancel (not our timeout) must not be retried.
|
|
94
|
+
if (signal?.aborted && !timedOut) throw e;
|
|
95
|
+
const detail = timedOut
|
|
96
|
+
? `timed out after ${Math.round(effectiveTimeout / 1000)}s`
|
|
97
|
+
: (e?.cause?.message || e?.message || String(e));
|
|
98
|
+
// Network failure or timeout: the request did not complete, so retrying
|
|
99
|
+
// is safe. Covers DNS, TLS, connection-reset, and wedged-server hangs.
|
|
77
100
|
if (attempt < retries) {
|
|
78
101
|
await sleep(backoffMs(attempt));
|
|
79
102
|
attempt += 1;
|
|
80
103
|
continue;
|
|
81
104
|
}
|
|
82
|
-
throw new GlashApiError(0, `
|
|
105
|
+
throw new GlashApiError(0, `Request failed while calling ${method} ${path}: ${detail}`);
|
|
83
106
|
}
|
|
107
|
+
clearTimeout(timer);
|
|
108
|
+
signal?.removeEventListener?.('abort', onCallerAbort);
|
|
84
109
|
|
|
85
110
|
if (!res.ok) {
|
|
86
111
|
const retryAfter = retryAfterMs(res.headers);
|
package/lib/version.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION = '0.2.
|
|
1
|
+
export const VERSION = '0.2.22';
|
package/package.json
CHANGED