insta 0.0.82 → 0.0.83
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/dist/build-logs.js +32 -8
- package/dist/deploy-archive.js +64 -55
- package/package.json +1 -1
package/dist/build-logs.js
CHANGED
|
@@ -24,7 +24,20 @@ export async function readBuildLogs(api, projectId, source, buildId, signal = Ab
|
|
|
24
24
|
if (++requests > 200)
|
|
25
25
|
throw new Error('build logs exceed the per-read page limit');
|
|
26
26
|
const params = new URLSearchParams({ ...(step ? { step } : {}), ...(cursor ? { cursor } : {}) });
|
|
27
|
-
|
|
27
|
+
signal.throwIfAborted();
|
|
28
|
+
const request = new AbortController();
|
|
29
|
+
const abort = () => request.abort(signal.reason);
|
|
30
|
+
signal.addEventListener('abort', abort, { once: true });
|
|
31
|
+
const timeout = setTimeout(() => request.abort(new DOMException('build log request timed out', 'TimeoutError')), 20_000);
|
|
32
|
+
let body;
|
|
33
|
+
try {
|
|
34
|
+
const response = await api.rawRequest('GET', `/projects/${projectId}/builds/${source}/${encodeURIComponent(buildId)}/logs?${params}`, undefined, { signal: request.signal });
|
|
35
|
+
body = response.body;
|
|
36
|
+
}
|
|
37
|
+
finally {
|
|
38
|
+
clearTimeout(timeout);
|
|
39
|
+
signal.removeEventListener('abort', abort);
|
|
40
|
+
}
|
|
28
41
|
if (!body || !['ready', 'pending', 'unsupported', 'unavailable'].includes(body.state) || !Array.isArray(body.steps) || !Array.isArray(body.entries))
|
|
29
42
|
throw new Error('invalid build log response');
|
|
30
43
|
bytes += Buffer.byteLength(JSON.stringify(body));
|
|
@@ -143,20 +156,24 @@ export function archiveLogWatcher(api, projectId, write, wait = sleep) {
|
|
|
143
156
|
const follow = { tails: new Map(), emit: snapshot => printer.print(snapshot, write) };
|
|
144
157
|
let warned = '';
|
|
145
158
|
let unavailable = false;
|
|
146
|
-
return async (buildId, finished, remainingMs = 30_000) => {
|
|
159
|
+
return async (buildId, finished, remainingMs = 30_000, cancelSignal) => {
|
|
147
160
|
if (unavailable)
|
|
148
161
|
return;
|
|
149
|
-
const
|
|
162
|
+
const started = Date.now();
|
|
163
|
+
const deadline = started + remainingMs;
|
|
164
|
+
const refreshDeadline = started + Math.min(remainingMs, finished ? 18_000 : remainingMs);
|
|
150
165
|
for (let attempt = 0; attempt < (finished ? 6 : 1); attempt++) {
|
|
151
166
|
if (attempt > 0)
|
|
152
|
-
await wait(Math.min(3000, Math.max(0,
|
|
153
|
-
const
|
|
167
|
+
await wait(Math.min(3000, Math.max(0, refreshDeadline - Date.now())));
|
|
168
|
+
const finalRead = finished && (attempt === 5 || Date.now() >= refreshDeadline);
|
|
169
|
+
const remaining = Math.min(deadline - Date.now(), finalRead ? 30_000 : refreshDeadline - Date.now());
|
|
154
170
|
if (remaining <= 0)
|
|
155
171
|
return;
|
|
156
|
-
if (finished && (attempt === 0 ||
|
|
172
|
+
if (finished && (attempt === 0 || finalRead))
|
|
157
173
|
follow.tails.clear();
|
|
174
|
+
const signal = cancelSignal ?? AbortSignal.timeout(remaining);
|
|
158
175
|
try {
|
|
159
|
-
const snapshot = await readBuildLogs(api, projectId, 'archive', buildId,
|
|
176
|
+
const snapshot = await readBuildLogs(api, projectId, 'archive', buildId, signal, follow);
|
|
160
177
|
if (snapshot.state === 'unsupported') {
|
|
161
178
|
printer.finishLine(write);
|
|
162
179
|
write('Build logs are not supported for this build provider.\n');
|
|
@@ -168,14 +185,21 @@ export function archiveLogWatcher(api, projectId, write, wait = sleep) {
|
|
|
168
185
|
warned = '';
|
|
169
186
|
}
|
|
170
187
|
catch (error) {
|
|
188
|
+
if (cancelSignal?.aborted)
|
|
189
|
+
return;
|
|
190
|
+
if (finished && signal.aborted && !finalRead && Date.now() < deadline)
|
|
191
|
+
continue;
|
|
171
192
|
if (error instanceof ApiError && error.status === 400)
|
|
172
193
|
follow.tails.clear();
|
|
173
194
|
printer.finishLine(write);
|
|
174
|
-
const
|
|
195
|
+
const reason = signal.aborted || (error instanceof Error && error.name === 'TimeoutError') ? ' (timed out)' : error instanceof ApiError ? ` (HTTP ${error.status})` : '';
|
|
196
|
+
const message = `Could not read build logs${reason}. Retry with: insta build-logs ${buildId}\n`;
|
|
175
197
|
if (warned !== message)
|
|
176
198
|
write(message);
|
|
177
199
|
warned = message;
|
|
178
200
|
}
|
|
201
|
+
if (finalRead)
|
|
202
|
+
return;
|
|
179
203
|
}
|
|
180
204
|
};
|
|
181
205
|
}
|
package/dist/deploy-archive.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { setTimeout as delay } from 'node:timers/promises';
|
|
1
2
|
import { handleApproval } from './util.js';
|
|
2
3
|
export function archiveBuildSpec(hasDockerfile) {
|
|
3
4
|
return hasDockerfile ? { type: 'dockerfile' } : { type: 'nixpacks' };
|
|
@@ -84,64 +85,72 @@ export async function deployArchive(api, projectId, ref, branch, opts, now = Dat
|
|
|
84
85
|
log('resuming the deploy this archive already started');
|
|
85
86
|
const deadline = now() + DEPLOY_DEADLINE_MS;
|
|
86
87
|
const overdue = () => new Error(`the deploy did not finish within ${Math.round(DEPLOY_DEADLINE_MS / 60000)} minutes — check \`insta status\` or re-run`);
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
const remaining = deadline - now();
|
|
93
|
-
if (remaining <= 0)
|
|
94
|
-
throw overdue();
|
|
95
|
-
const res = await api.rawRequest('GET', `/projects/${projectId}/archive-deploys/${encodeURIComponent(operationId)}`, undefined, {
|
|
96
|
-
signal: AbortSignal.timeout(Math.min(remaining, POLL_REQUEST_TIMEOUT_MS)),
|
|
97
|
-
}).catch((e) => {
|
|
98
|
-
if (!isAbort(e))
|
|
99
|
-
throw e;
|
|
100
|
-
throw remaining <= POLL_REQUEST_TIMEOUT_MS ? overdue() : new Error(`the platform did not answer a status poll within ${POLL_REQUEST_TIMEOUT_MS / 1000}s — check \`insta status\` or re-run`);
|
|
101
|
-
});
|
|
102
|
-
const state = res.body?.state;
|
|
103
|
-
await watchLogs?.(operationId, state === 'failed' || state === 'live', Math.max(0, deadline - now()));
|
|
104
|
-
// A failed operation is an ANSWER, not a transport error: the poll worked, and the sentence
|
|
105
|
-
// it carries (usually the gateway's own, e.g. "no Dockerfile at ./api") is the one to show.
|
|
106
|
-
if (state === 'failed') {
|
|
107
|
-
// `||` would let a non-string through and the CLI would print "[object Object]" for the one
|
|
108
|
-
// sentence that explains the failure. Only a non-empty string is a message.
|
|
109
|
-
const error = res.body?.error;
|
|
110
|
-
return { failed: typeof error === 'string' && error ? error : 'the deploy failed' };
|
|
88
|
+
const logController = new AbortController();
|
|
89
|
+
const logTask = watchLogs ? (async () => {
|
|
90
|
+
while (!logController.signal.aborted && now() < deadline) {
|
|
91
|
+
await watchLogs(operationId, false, Math.max(0, deadline - now()), logController.signal);
|
|
92
|
+
await delay(Math.min(POLL_MS, Math.max(0, deadline - now())), undefined, { signal: logController.signal });
|
|
111
93
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
94
|
+
})().catch(() => {
|
|
95
|
+
if (!logController.signal.aborted)
|
|
96
|
+
log(`Could not follow build logs. Retry with: insta build-logs ${operationId}`);
|
|
97
|
+
}) : undefined;
|
|
98
|
+
let last = '';
|
|
99
|
+
try {
|
|
100
|
+
for (;;) {
|
|
101
|
+
const remaining = deadline - now();
|
|
102
|
+
if (remaining <= 0)
|
|
103
|
+
throw overdue();
|
|
104
|
+
const res = await api.rawRequest('GET', `/projects/${projectId}/archive-deploys/${encodeURIComponent(operationId)}`, undefined, {
|
|
105
|
+
signal: AbortSignal.timeout(Math.min(remaining, POLL_REQUEST_TIMEOUT_MS)),
|
|
106
|
+
}).catch((e) => {
|
|
107
|
+
if (!isAbort(e))
|
|
108
|
+
throw e;
|
|
109
|
+
throw remaining <= POLL_REQUEST_TIMEOUT_MS ? overdue() : new Error(`the platform did not answer a status poll within ${POLL_REQUEST_TIMEOUT_MS / 1000}s — check \`insta status\` or re-run`);
|
|
110
|
+
});
|
|
111
|
+
const state = res.body?.state;
|
|
112
|
+
if (state === 'failed' || state === 'live') {
|
|
113
|
+
logController.abort();
|
|
114
|
+
await logTask;
|
|
115
|
+
await watchLogs?.(operationId, true, Math.max(0, deadline - now()));
|
|
117
116
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
117
|
+
if (state === 'failed') {
|
|
118
|
+
const error = res.body?.error;
|
|
119
|
+
return { failed: typeof error === 'string' && error ? error : 'the deploy failed' };
|
|
120
|
+
}
|
|
121
|
+
if (state === 'live') {
|
|
122
|
+
const image = res.body?.imageRef;
|
|
123
|
+
const url = res.body?.url;
|
|
124
|
+
if (typeof image !== 'string' || !image || typeof url !== 'string' || !url) {
|
|
125
|
+
throw new Error('the deploy finished but the platform returned no image or URL for it — check `insta status`');
|
|
126
|
+
}
|
|
127
|
+
const optionalString = (field, v) => {
|
|
128
|
+
if (v === undefined || v === null)
|
|
129
|
+
return undefined;
|
|
130
|
+
if (typeof v !== 'string')
|
|
131
|
+
throw new Error(`the platform returned a non-string ${field} for the deploy — upgrade with \`insta upgrade\``);
|
|
132
|
+
return v;
|
|
133
|
+
};
|
|
134
|
+
return {
|
|
135
|
+
image, url,
|
|
136
|
+
branch: optionalString('branch', res.body.branch) ?? branch,
|
|
137
|
+
group: optionalString('group', res.body.group) ?? opts.group ?? '',
|
|
138
|
+
machineId: optionalString('machineId', res.body.machineId),
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
if (state !== 'queued' && state !== 'building' && state !== 'deploying') {
|
|
142
|
+
throw new Error(`the platform reported an unknown deploy state (${JSON.stringify(state)}) — upgrade with \`insta upgrade\``);
|
|
143
|
+
}
|
|
144
|
+
if (state !== last) {
|
|
145
|
+
log(state === 'deploying' ? 'image built, deploying it' : `${state}…`);
|
|
146
|
+
last = state;
|
|
147
|
+
}
|
|
148
|
+
await wait(POLL_MS);
|
|
143
149
|
}
|
|
144
|
-
|
|
150
|
+
}
|
|
151
|
+
finally {
|
|
152
|
+
logController.abort();
|
|
153
|
+
await logTask;
|
|
145
154
|
}
|
|
146
155
|
}
|
|
147
156
|
//# sourceMappingURL=deploy-archive.js.map
|