inflight-cli 2.12.0 → 2.14.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.
- package/dist/commands/share.d.ts +0 -1
- package/dist/commands/share.js +386 -466
- package/dist/commands/workspace.d.ts +1 -2
- package/dist/commands/workspace.js +47 -47
- package/dist/index.js +22 -8
- package/dist/lib/git.d.ts +20 -14
- package/dist/lib/git.js +14 -28
- package/dist/lib/netlify.d.ts +4 -5
- package/dist/lib/netlify.js +22 -10
- package/dist/lib/vercel.d.ts +6 -9
- package/dist/lib/vercel.js +26 -33
- package/dist/providers/index.d.ts +1 -1
- package/dist/providers/index.js +11 -4
- package/dist/providers/netlify.d.ts +1 -3
- package/dist/providers/netlify.js +75 -272
- package/dist/providers/shared.d.ts +39 -0
- package/dist/providers/shared.js +249 -0
- package/dist/providers/vercel.d.ts +1 -1
- package/dist/providers/vercel.js +88 -259
- package/package.json +1 -1
package/dist/providers/vercel.js
CHANGED
|
@@ -1,9 +1,27 @@
|
|
|
1
1
|
import * as p from "@clack/prompts";
|
|
2
2
|
import pc from "picocolors";
|
|
3
3
|
import { execSync } from "child_process";
|
|
4
|
-
import { parseGitRepo
|
|
4
|
+
import { parseGitRepo } from "../lib/git.js";
|
|
5
5
|
import { isAgent } from "../lib/agent.js";
|
|
6
|
-
import { ensureVercelCli, ensureVercelAuth, readLocalVercelProject, writeLocalVercelProject, getVercelProjectById, getVercelProjects, matchVercelProjectsByRepo, createVercelProject, getVercelDeployments, } from "../lib/vercel.js";
|
|
6
|
+
import { ensureVercelCli, ensureVercelAuth, readLocalVercelProject, writeLocalVercelProject, getVercelProjectById, getVercelProjects, matchVercelProjectsByRepo, createVercelProject, getVercelDeployments, getVercelDeploymentById, } from "../lib/vercel.js";
|
|
7
|
+
import { resolveDeploymentUrl } from "./shared.js";
|
|
8
|
+
// --- Vercel state normalization ---
|
|
9
|
+
function normalizeVercelState(state) {
|
|
10
|
+
switch (state) {
|
|
11
|
+
case "READY":
|
|
12
|
+
return "ready";
|
|
13
|
+
case "ERROR":
|
|
14
|
+
case "CANCELED":
|
|
15
|
+
return "error";
|
|
16
|
+
case "BUILDING":
|
|
17
|
+
case "INITIALIZING":
|
|
18
|
+
return "building";
|
|
19
|
+
case "QUEUED":
|
|
20
|
+
return "queued";
|
|
21
|
+
default:
|
|
22
|
+
return "building";
|
|
23
|
+
}
|
|
24
|
+
}
|
|
7
25
|
// --- Auto-detection ---
|
|
8
26
|
/**
|
|
9
27
|
* Auto-detect the Vercel project for the current git repo.
|
|
@@ -11,33 +29,25 @@ import { ensureVercelCli, ensureVercelAuth, readLocalVercelProject, writeLocalVe
|
|
|
11
29
|
*
|
|
12
30
|
* 1. `.vercel/project.json` at git root (instant, no API)
|
|
13
31
|
* 2. Fetch all projects, exact match on git remote
|
|
14
|
-
* 3. Multiple matches
|
|
15
|
-
* 4. Zero matches
|
|
32
|
+
* 3. Multiple matches -> pick from matches
|
|
33
|
+
* 4. Zero matches -> pick from all projects
|
|
16
34
|
*/
|
|
17
|
-
async function autoDetectProject(
|
|
18
|
-
const gitRoot = getGitRoot(cwd);
|
|
35
|
+
async function autoDetectProject(gitRoot, gitInfo, token) {
|
|
19
36
|
// Cache result to .vercel/project.json so subsequent runs skip API calls
|
|
20
37
|
const cacheResult = (project) => {
|
|
21
|
-
|
|
22
|
-
writeLocalVercelProject(gitRoot, project.teamId, project.projectId);
|
|
23
|
-
}
|
|
38
|
+
writeLocalVercelProject(gitRoot, project.teamId, project.id);
|
|
24
39
|
return project;
|
|
25
40
|
};
|
|
26
41
|
// --- Fast path: .vercel/project.json ---
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
projectName: detail.name,
|
|
37
|
-
repoOwner: detail.link?.org ?? null,
|
|
38
|
-
repoName: detail.link?.repo ?? null,
|
|
39
|
-
};
|
|
40
|
-
}
|
|
42
|
+
const localProject = readLocalVercelProject(gitRoot);
|
|
43
|
+
if (localProject) {
|
|
44
|
+
const detail = await getVercelProjectById(token, localProject.projectId, localProject.orgId);
|
|
45
|
+
if (detail) {
|
|
46
|
+
return {
|
|
47
|
+
teamId: localProject.orgId,
|
|
48
|
+
id: detail.id,
|
|
49
|
+
name: detail.name,
|
|
50
|
+
};
|
|
41
51
|
}
|
|
42
52
|
}
|
|
43
53
|
// --- Fetch all projects ---
|
|
@@ -66,10 +76,8 @@ async function autoDetectProject(cwd, gitInfo, token) {
|
|
|
66
76
|
spinner?.stop(`Detected Vercel project: ${pc.bold(matches[0].name)}`);
|
|
67
77
|
return cacheResult({
|
|
68
78
|
teamId: matches[0].teamId,
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
repoOwner: matches[0].link?.org ?? null,
|
|
72
|
-
repoName: matches[0].link?.repo ?? null,
|
|
79
|
+
id: matches[0].id,
|
|
80
|
+
name: matches[0].name,
|
|
73
81
|
});
|
|
74
82
|
}
|
|
75
83
|
if (matches.length > 1) {
|
|
@@ -110,12 +118,11 @@ async function pickFromList(projects, createCtx) {
|
|
|
110
118
|
const match = selected;
|
|
111
119
|
return {
|
|
112
120
|
teamId: match.teamId,
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
repoOwner: match.link?.org ?? null,
|
|
116
|
-
repoName: match.link?.repo ?? null,
|
|
121
|
+
id: match.id,
|
|
122
|
+
name: match.name,
|
|
117
123
|
};
|
|
118
124
|
}
|
|
125
|
+
// --- Create project flow ---
|
|
119
126
|
async function createProjectFlow(projects, ctx) {
|
|
120
127
|
if (isAgent)
|
|
121
128
|
return null;
|
|
@@ -151,10 +158,8 @@ async function createProjectFlow(projects, ctx) {
|
|
|
151
158
|
p.log.info("Push to git to trigger your first deployment.");
|
|
152
159
|
return {
|
|
153
160
|
teamId,
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
repoOwner: gitRepo.owner,
|
|
157
|
-
repoName: gitRepo.name,
|
|
161
|
+
id: created.id,
|
|
162
|
+
name: created.name,
|
|
158
163
|
};
|
|
159
164
|
}
|
|
160
165
|
// Poll until a deployment appears
|
|
@@ -167,20 +172,16 @@ async function createProjectFlow(projects, ctx) {
|
|
|
167
172
|
pollSpinner.stop("Vercel deployment started.");
|
|
168
173
|
return {
|
|
169
174
|
teamId,
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
repoOwner: gitRepo.owner,
|
|
173
|
-
repoName: gitRepo.name,
|
|
175
|
+
id: created.id,
|
|
176
|
+
name: created.name,
|
|
174
177
|
};
|
|
175
178
|
}
|
|
176
179
|
}
|
|
177
180
|
pollSpinner.stop("Vercel deployment may take a moment to appear.");
|
|
178
181
|
return {
|
|
179
182
|
teamId,
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
repoOwner: gitRepo.owner,
|
|
183
|
-
repoName: gitRepo.name,
|
|
183
|
+
id: created.id,
|
|
184
|
+
name: created.name,
|
|
184
185
|
};
|
|
185
186
|
}
|
|
186
187
|
catch (e) {
|
|
@@ -188,221 +189,49 @@ async function createProjectFlow(projects, ctx) {
|
|
|
188
189
|
return null;
|
|
189
190
|
}
|
|
190
191
|
}
|
|
191
|
-
// ---
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
: false;
|
|
222
|
-
// Only filter by branch if we're in the matching repo — otherwise show all deployments
|
|
223
|
-
let deployments = await getVercelDeployments(token, project.teamId, project.projectId, {
|
|
224
|
-
branch: repoMatches ? gitInfo.branch ?? undefined : undefined,
|
|
225
|
-
});
|
|
226
|
-
let commitDeploy = repoMatches ? deployments.find((d) => d.commitSha === commitSha) : undefined;
|
|
227
|
-
// If just pushed, poll until the commit deployment appears
|
|
228
|
-
if (!commitDeploy && repoMatches && opts?.justPushed) {
|
|
229
|
-
const pollSpinner = !isAgent ? p.spinner({ indicator: "timer" }) : null;
|
|
230
|
-
pollSpinner?.start("Waiting for deploy");
|
|
231
|
-
for (let i = 0; i < 30; i++) {
|
|
232
|
-
await new Promise((r) => setTimeout(r, 2000));
|
|
233
|
-
deployments = await getVercelDeployments(token, project.teamId, project.projectId, {
|
|
234
|
-
branch: repoMatches ? gitInfo.branch ?? undefined : undefined,
|
|
235
|
-
});
|
|
236
|
-
commitDeploy = deployments.find((d) => d.commitSha === commitSha);
|
|
237
|
-
if (commitDeploy)
|
|
238
|
-
break;
|
|
239
|
-
}
|
|
240
|
-
if (commitDeploy) {
|
|
241
|
-
pollSpinner?.clear();
|
|
242
|
-
}
|
|
243
|
-
else {
|
|
244
|
-
pollSpinner?.stop("No deployment detected yet — Vercel may still be processing.");
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
// If we found a commit-specific deployment, handle based on state
|
|
248
|
-
if (commitDeploy) {
|
|
249
|
-
const commitLabel = pc.bold(commitSha);
|
|
250
|
-
const message = commitDeploy.commitMessage
|
|
251
|
-
? pc.dim(` — ${truncate(commitDeploy.commitMessage.split("\n")[0], 50)}`)
|
|
252
|
-
: "";
|
|
253
|
-
if (commitDeploy.state === "ERROR" || commitDeploy.state === "CANCELED") {
|
|
254
|
-
if (!isAgent)
|
|
255
|
-
p.log.error(`Vercel deployment for ${commitLabel}${message} ${pc.red("failed")}.\n Fix the build and push again, or select a different deployment below.`);
|
|
256
|
-
commitDeploy = undefined;
|
|
257
|
-
// Fall through to the picker
|
|
258
|
-
}
|
|
259
|
-
else if (commitDeploy.state === "BUILDING" ||
|
|
260
|
-
commitDeploy.state === "QUEUED" ||
|
|
261
|
-
commitDeploy.state === "INITIALIZING") {
|
|
262
|
-
// Show what we're waiting on
|
|
263
|
-
if (!isAgent)
|
|
264
|
-
p.log.info(`Vercel deployment for ${commitLabel}${message} — ${pc.yellow(commitDeploy.state.toLowerCase())}`);
|
|
265
|
-
// Poll with a ticking timer for up to ~2 minutes
|
|
266
|
-
const buildingUrl = commitDeploy.url;
|
|
267
|
-
const startTime = Date.now();
|
|
268
|
-
const maxWaitMs = 120_000;
|
|
269
|
-
const pollIntervalMs = 3_000;
|
|
270
|
-
let resolved = false;
|
|
271
|
-
const spinner = !isAgent ? p.spinner({ indicator: "timer" }) : null;
|
|
272
|
-
spinner?.start("Waiting for build");
|
|
273
|
-
try {
|
|
274
|
-
while (Date.now() - startTime < maxWaitMs) {
|
|
275
|
-
await new Promise((r) => setTimeout(r, pollIntervalMs));
|
|
276
|
-
const freshDeps = await getVercelDeployments(token, project.teamId, project.projectId, {
|
|
277
|
-
branch: repoMatches ? gitInfo.branch ?? undefined : undefined,
|
|
278
|
-
});
|
|
279
|
-
const fresh = freshDeps.find((d) => d.commitSha === commitSha);
|
|
280
|
-
if (!fresh)
|
|
281
|
-
break;
|
|
282
|
-
if (fresh.state === "READY") {
|
|
283
|
-
spinner?.stop("Vercel deployment ready!");
|
|
284
|
-
return fresh.url;
|
|
285
|
-
}
|
|
286
|
-
if (fresh.state === "ERROR" || fresh.state === "CANCELED") {
|
|
287
|
-
spinner?.stop(pc.red("Vercel deployment failed."));
|
|
288
|
-
if (!isAgent)
|
|
289
|
-
p.log.error(`Vercel deployment for ${commitLabel}${message} ${pc.red("failed")}.\n Fix the build and push again, or select a different deployment below.`);
|
|
290
|
-
commitDeploy = undefined;
|
|
291
|
-
resolved = true;
|
|
292
|
-
break;
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
if (!resolved) {
|
|
296
|
-
if (isAgent)
|
|
297
|
-
return buildingUrl;
|
|
298
|
-
spinner?.stop("Still building...");
|
|
299
|
-
const action = await p.select({
|
|
300
|
-
message: "Vercel deployment is still building.",
|
|
301
|
-
options: [
|
|
302
|
-
{ value: "continue", label: "Use it anyway", hint: "URL may not be ready yet" },
|
|
303
|
-
{ value: "pick", label: "Select a different deployment" },
|
|
304
|
-
],
|
|
305
|
-
});
|
|
306
|
-
if (p.isCancel(action)) {
|
|
307
|
-
p.cancel("Cancelled.");
|
|
308
|
-
process.exit(0);
|
|
309
|
-
}
|
|
310
|
-
if (action === "continue") {
|
|
311
|
-
if (!isAgent)
|
|
312
|
-
p.log.info(`Vercel deployment for ${commitLabel}${message}:\n → ${pc.cyan(buildingUrl)} ${pc.yellow("(building)")}`);
|
|
313
|
-
return buildingUrl;
|
|
314
|
-
}
|
|
315
|
-
commitDeploy = undefined;
|
|
316
|
-
// Fall through to picker
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
catch (e) {
|
|
320
|
-
spinner?.stop("Error checking deployment status.");
|
|
321
|
-
if (e instanceof Error)
|
|
322
|
-
if (!isAgent)
|
|
323
|
-
p.log.message(pc.dim(e.message));
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
else {
|
|
327
|
-
// READY or any other terminal state — use it
|
|
328
|
-
if (!isAgent)
|
|
329
|
-
p.log.info(`Vercel deployment for ${commitLabel}${message}`);
|
|
330
|
-
return commitDeploy.url;
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
// Refresh deployments so the picker shows current states
|
|
334
|
-
deployments = await getVercelDeployments(token, project.teamId, project.projectId, {
|
|
335
|
-
branch: repoMatches ? gitInfo.branch ?? undefined : undefined,
|
|
336
|
-
});
|
|
337
|
-
// Fallback: no commit deployment found — let user pick from recent or paste manually
|
|
338
|
-
if (deployments.length === 0) {
|
|
339
|
-
if (!isAgent)
|
|
340
|
-
p.log.warn("No deployments found. Paste a URL instead.");
|
|
341
|
-
return null;
|
|
342
|
-
}
|
|
343
|
-
const hasWorkingDeployment = deployments.some((d) => d.state !== "ERROR" && d.state !== "CANCELED");
|
|
344
|
-
if (!hasWorkingDeployment) {
|
|
345
|
-
if (!isAgent)
|
|
346
|
-
p.log.warn("All recent deployments failed. Fix the build and push again, or paste a URL.");
|
|
347
|
-
return null;
|
|
348
|
-
}
|
|
349
|
-
if (isAgent) {
|
|
350
|
-
const ready = deployments.find((d) => d.state === "READY");
|
|
351
|
-
return ready?.url ?? deployments[0]?.url ?? null;
|
|
352
|
-
}
|
|
353
|
-
const maxBranchPick = Math.max(...deployments.map((d) => (d.branch ?? "unknown").length));
|
|
354
|
-
const selected = await p.select({
|
|
355
|
-
message: "No deployment found for current commit. Select one:",
|
|
356
|
-
options: [
|
|
357
|
-
...deployments.map((d) => {
|
|
358
|
-
const isFailed = d.state === "ERROR" || d.state === "CANCELED";
|
|
359
|
-
const branch = (d.branch ?? "unknown").padEnd(maxBranchPick);
|
|
360
|
-
const ago = timeAgo(d.createdAt).padEnd(8);
|
|
361
|
-
const firstLine = (d.commitMessage ?? "No commit message").split("\n")[0];
|
|
362
|
-
const msg = truncate(firstLine, 55);
|
|
363
|
-
if (isFailed) {
|
|
364
|
-
return {
|
|
365
|
-
value: d.url,
|
|
366
|
-
label: pc.dim(`${branch} ${ago} ${pc.red("(failed)")} ${msg}`),
|
|
367
|
-
};
|
|
368
|
-
}
|
|
369
|
-
const state = d.state !== "READY" ? ` ${pc.yellow(`(${d.state.toLowerCase()})`)}` : "";
|
|
370
|
-
return { value: d.url, label: `${branch} ${ago}${state} ${pc.dim(msg)}` };
|
|
371
|
-
}),
|
|
372
|
-
{ value: "manual", label: "Paste a URL manually" },
|
|
373
|
-
],
|
|
374
|
-
});
|
|
375
|
-
if (p.isCancel(selected)) {
|
|
376
|
-
p.cancel("Cancelled.");
|
|
377
|
-
process.exit(0);
|
|
378
|
-
}
|
|
379
|
-
if (selected === "manual")
|
|
380
|
-
return null;
|
|
381
|
-
// Warn if the user picked a failed deployment
|
|
382
|
-
const pickedDeploy = deployments.find((d) => d.url === selected);
|
|
383
|
-
if (pickedDeploy && (pickedDeploy.state === "ERROR" || pickedDeploy.state === "CANCELED")) {
|
|
384
|
-
if (isAgent)
|
|
385
|
-
return null;
|
|
386
|
-
p.log.warn("This deployment failed — the URL may not load.");
|
|
387
|
-
const confirm = await p.confirm({ message: "Use it anyway?" });
|
|
388
|
-
if (p.isCancel(confirm) || !confirm)
|
|
192
|
+
// --- Adapter ---
|
|
193
|
+
const vercelAdapter = {
|
|
194
|
+
name: "Vercel",
|
|
195
|
+
async ensureCli(log) {
|
|
196
|
+
return ensureVercelCli(log);
|
|
197
|
+
},
|
|
198
|
+
async ensureAuth() {
|
|
199
|
+
return ensureVercelAuth();
|
|
200
|
+
},
|
|
201
|
+
async detectProject(gitRoot, gitInfo, token) {
|
|
202
|
+
return autoDetectProject(gitRoot, gitInfo, token);
|
|
203
|
+
},
|
|
204
|
+
async getDeployments(token, project, opts) {
|
|
205
|
+
const raw = await getVercelDeployments(token, project.teamId, project.id, {
|
|
206
|
+
branch: opts?.branch,
|
|
207
|
+
sha: opts?.sha,
|
|
208
|
+
});
|
|
209
|
+
return raw.map((d) => ({
|
|
210
|
+
id: d.id,
|
|
211
|
+
url: d.url,
|
|
212
|
+
state: normalizeVercelState(d.state),
|
|
213
|
+
branch: d.branch,
|
|
214
|
+
commitSha: d.commitSha,
|
|
215
|
+
commitMessage: d.commitMessage,
|
|
216
|
+
createdAt: d.createdAt,
|
|
217
|
+
}));
|
|
218
|
+
},
|
|
219
|
+
async getDeploymentById(token, project, deploymentId) {
|
|
220
|
+
const raw = await getVercelDeploymentById(token, deploymentId, project.teamId);
|
|
221
|
+
if (!raw)
|
|
389
222
|
return null;
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
if (hours < 24)
|
|
405
|
-
return `${hours}h ago`;
|
|
406
|
-
const days = Math.floor(hours / 24);
|
|
407
|
-
return `${days}d ago`;
|
|
223
|
+
return {
|
|
224
|
+
id: raw.id,
|
|
225
|
+
url: raw.url,
|
|
226
|
+
state: normalizeVercelState(raw.state),
|
|
227
|
+
branch: raw.branch,
|
|
228
|
+
commitSha: raw.commitSha,
|
|
229
|
+
commitMessage: raw.commitMessage,
|
|
230
|
+
createdAt: raw.createdAt,
|
|
231
|
+
};
|
|
232
|
+
},
|
|
233
|
+
};
|
|
234
|
+
// --- Public API ---
|
|
235
|
+
export async function resolveVercelUrl(gitRoot, gitInfo, opts) {
|
|
236
|
+
return resolveDeploymentUrl(vercelAdapter, gitRoot, gitInfo, opts);
|
|
408
237
|
}
|