insta 0.0.82 → 0.1.0

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.
@@ -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' };
@@ -79,69 +80,77 @@ export async function deployArchive(api, projectId, ref, branch, opts, now = Dat
79
80
  const operationId = started.body?.operationId;
80
81
  if (typeof operationId !== 'string' || !operationId)
81
82
  throw new Error('the platform accepted the deploy but returned no operation id — re-run the deploy');
82
- log(`build logs: insta build-logs ${operationId}`);
83
+ log(`build logs: insta build logs ${operationId}`);
83
84
  if (started.body?.resumed === true)
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
- let last = '';
88
- for (;;) {
89
- // The deadline bounds the wall clock, not the number of answers: it is checked before each poll,
90
- // and each poll is itself bounded by what remains, so a stalled endpoint cannot hold the CLI
91
- // past it, and an answer that would arrive after it is not waited for.
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
- if (state === 'live') {
113
- const image = res.body?.imageRef;
114
- const url = res.body?.url;
115
- if (typeof image !== 'string' || !image || typeof url !== 'string' || !url) {
116
- throw new Error('the deploy finished but the platform returned no image or URL for it — check `insta status`');
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
- // Optional strings, validated as such. String() would have coerced a protocol error into a
119
- // plausible-looking branch or group and reported a target the deploy never named. An omitted
120
- // field falls back to what was requested; a field of the wrong type is a broken contract.
121
- const optionalString = (field, v) => {
122
- if (v === undefined || v === null)
123
- return undefined;
124
- if (typeof v !== 'string')
125
- throw new Error(`the platform returned a non-string ${field} for the deploy — upgrade with \`insta upgrade\``);
126
- return v;
127
- };
128
- return {
129
- image, url,
130
- branch: optionalString('branch', res.body.branch) ?? branch,
131
- group: optionalString('group', res.body.group) ?? opts.group ?? '',
132
- machineId: optionalString('machineId', res.body.machineId),
133
- };
134
- }
135
- // Only the platform's own in-flight states keep the loop going. An absent or unknown state
136
- // would otherwise spend the whole deadline looking like a slow build.
137
- if (state !== 'queued' && state !== 'building' && state !== 'deploying') {
138
- throw new Error(`the platform reported an unknown deploy state (${JSON.stringify(state)}) — upgrade with \`insta upgrade\``);
139
- }
140
- if (state !== last) {
141
- log(state === 'deploying' ? 'image built, deploying it' : `${state}…`);
142
- last = state;
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
- await wait(POLL_MS);
150
+ }
151
+ finally {
152
+ logController.abort();
153
+ await logTask;
145
154
  }
146
155
  }
147
156
  //# sourceMappingURL=deploy-archive.js.map
@@ -27,7 +27,7 @@ const GITIGNORE_COMMENT = '# InstaCloud: agent skills installed by `npx skills a
27
27
  // existing AI_AGENT (e.g. running inside another agent) rather than clobbering it.
28
28
  const defaultRunner = (cmdIn, argsIn, inherit = false) => new Promise((resolve) => {
29
29
  // resolveSpawnable: on Windows `npx` is a .cmd shim spawn() refuses without a shell —
30
- // re-enter npm's CLI script via node instead (same treatment as `insta setup agent`).
30
+ // re-enter npm's CLI script via node instead (same treatment as `insta agent setup`).
31
31
  const { cmd, args } = resolveSpawnable(cmdIn, argsIn);
32
32
  const env = { ...process.env, AI_AGENT: process.env.AI_AGENT || 'insta' };
33
33
  // npx exports its flags as npm_config_* to children; npm_config_package would pin the inner