inflight-cli 2.1.6 → 2.1.7
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.js +79 -3
- package/dist/lib/api.d.ts +15 -0
- package/dist/lib/api.js +12 -0
- package/dist/providers/vercel.js +31 -35
- package/package.json +1 -1
package/dist/commands/share.js
CHANGED
|
@@ -1,10 +1,27 @@
|
|
|
1
1
|
import * as p from "@clack/prompts";
|
|
2
2
|
import pc from "picocolors";
|
|
3
3
|
import { readGlobalAuth, readWorkspaceConfig, writeWorkspaceConfig } from "../lib/config.js";
|
|
4
|
-
import { getGitInfo, getGitSyncState, generateCommitMessage, commitAndPush, pushBranch, hasCommitsAhead } from "../lib/git.js";
|
|
4
|
+
import { getGitInfo, getGitSyncState, generateCommitMessage, commitAndPush, pushBranch, hasCommitsAhead, } from "../lib/git.js";
|
|
5
5
|
import open from "open";
|
|
6
6
|
import { providers } from "../providers/index.js";
|
|
7
|
-
import { apiGetMe, apiCreateVersion } from "../lib/api.js";
|
|
7
|
+
import { apiGetMe, apiCreateVersion, apiGetRecentProjects } from "../lib/api.js";
|
|
8
|
+
function formatRelativeTime(timestampMs) {
|
|
9
|
+
const seconds = Math.floor((Date.now() - timestampMs) / 1000);
|
|
10
|
+
if (seconds < 60)
|
|
11
|
+
return "just now";
|
|
12
|
+
const minutes = Math.floor(seconds / 60);
|
|
13
|
+
if (minutes < 60)
|
|
14
|
+
return `${minutes}m ago`;
|
|
15
|
+
const hours = Math.floor(minutes / 60);
|
|
16
|
+
if (hours < 24)
|
|
17
|
+
return `${hours}h ago`;
|
|
18
|
+
const days = Math.floor(hours / 24);
|
|
19
|
+
if (days === 1)
|
|
20
|
+
return "yesterday";
|
|
21
|
+
if (days < 7)
|
|
22
|
+
return `${days}d ago`;
|
|
23
|
+
return new Date(timestampMs).toLocaleDateString("en-US", { month: "short", day: "numeric" });
|
|
24
|
+
}
|
|
8
25
|
/**
|
|
9
26
|
* Checks local git state and prompts the user to commit/push if needed.
|
|
10
27
|
* Returns { justPushed: true } if changes were pushed, { justPushed: false } otherwise.
|
|
@@ -303,16 +320,75 @@ export async function shareCommand(opts = {}) {
|
|
|
303
320
|
if (!stagingUrl.startsWith("http")) {
|
|
304
321
|
stagingUrl = `https://${stagingUrl}`;
|
|
305
322
|
}
|
|
323
|
+
// ── Step 4: Project selection ──
|
|
324
|
+
let selectedProjectId;
|
|
325
|
+
let overrideVersionId;
|
|
326
|
+
const { projects: recentProjects } = await apiGetRecentProjects(auth.apiKey, workspaceId).catch(() => ({
|
|
327
|
+
projects: [],
|
|
328
|
+
}));
|
|
329
|
+
if (recentProjects.length > 0) {
|
|
330
|
+
const projectChoice = await p.select({
|
|
331
|
+
message: "Add to an existing version or start fresh?",
|
|
332
|
+
options: [
|
|
333
|
+
...recentProjects.map((proj) => ({
|
|
334
|
+
value: proj.projectId,
|
|
335
|
+
label: `"${proj.latestVersion.title}"`,
|
|
336
|
+
hint: `created ${formatRelativeTime(proj.latestVersion.createdAt)}`,
|
|
337
|
+
})),
|
|
338
|
+
{ value: "new", label: "Start fresh" },
|
|
339
|
+
],
|
|
340
|
+
});
|
|
341
|
+
if (p.isCancel(projectChoice)) {
|
|
342
|
+
p.cancel("Cancelled.");
|
|
343
|
+
process.exit(0);
|
|
344
|
+
}
|
|
345
|
+
if (projectChoice !== "new") {
|
|
346
|
+
selectedProjectId = projectChoice;
|
|
347
|
+
// Check for override opportunity — only when latest version has no feedback
|
|
348
|
+
const selectedProject = recentProjects.find((proj) => proj.projectId === selectedProjectId);
|
|
349
|
+
if (selectedProject && selectedProject.latestVersion.commentCount === 0) {
|
|
350
|
+
const overrideChoice = await p.select({
|
|
351
|
+
message: `"${selectedProject.latestVersion.title}" has no feedback yet.`,
|
|
352
|
+
options: [
|
|
353
|
+
{
|
|
354
|
+
value: "override",
|
|
355
|
+
label: "Update its staging URL",
|
|
356
|
+
hint: `replace with ${new URL(stagingUrl).hostname}`,
|
|
357
|
+
},
|
|
358
|
+
{
|
|
359
|
+
value: "new_version",
|
|
360
|
+
label: "Add a new version",
|
|
361
|
+
hint: "keep both in version history",
|
|
362
|
+
},
|
|
363
|
+
],
|
|
364
|
+
});
|
|
365
|
+
if (p.isCancel(overrideChoice)) {
|
|
366
|
+
p.cancel("Cancelled.");
|
|
367
|
+
process.exit(0);
|
|
368
|
+
}
|
|
369
|
+
if (overrideChoice === "override") {
|
|
370
|
+
overrideVersionId = selectedProject.latestVersion.id;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
306
375
|
await apiCreateVersion({
|
|
307
376
|
apiKey: auth.apiKey,
|
|
308
377
|
workspaceId,
|
|
309
378
|
stagingUrl,
|
|
310
379
|
gitInfo,
|
|
380
|
+
...(selectedProjectId && { projectId: selectedProjectId }),
|
|
381
|
+
...(overrideVersionId && { overrideVersionId }),
|
|
311
382
|
}).catch((e) => {
|
|
312
383
|
p.log.error(e.message);
|
|
313
384
|
process.exit(1);
|
|
314
385
|
});
|
|
315
386
|
p.log.info(`Staging URL: ${pc.cyan(stagingUrl)}`);
|
|
316
|
-
|
|
387
|
+
if (overrideVersionId) {
|
|
388
|
+
p.outro(pc.green("✓ Staging URL updated") + " — opening in browser...");
|
|
389
|
+
}
|
|
390
|
+
else {
|
|
391
|
+
p.outro(pc.green("✓ Inflight added to your staging URL") + " — opening in browser...");
|
|
392
|
+
}
|
|
317
393
|
await open(stagingUrl);
|
|
318
394
|
}
|
package/dist/lib/api.d.ts
CHANGED
|
@@ -9,16 +9,31 @@ export interface CreateVersionResult {
|
|
|
9
9
|
versionId: string;
|
|
10
10
|
inflightUrl: string;
|
|
11
11
|
}
|
|
12
|
+
export interface RecentProject {
|
|
13
|
+
projectId: string;
|
|
14
|
+
latestVersion: {
|
|
15
|
+
id: string;
|
|
16
|
+
title: string;
|
|
17
|
+
stagingUrl: string | null;
|
|
18
|
+
commentCount: number;
|
|
19
|
+
createdAt: number;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
12
22
|
export declare function apiGetMe(apiKey: string): Promise<{
|
|
13
23
|
name: string | null;
|
|
14
24
|
email: string | null;
|
|
15
25
|
workspaces: Workspace[];
|
|
16
26
|
}>;
|
|
27
|
+
export declare function apiGetRecentProjects(apiKey: string, workspaceId: string): Promise<{
|
|
28
|
+
projects: RecentProject[];
|
|
29
|
+
}>;
|
|
17
30
|
export declare function apiCreateVersion(opts: {
|
|
18
31
|
apiKey: string;
|
|
19
32
|
workspaceId: string;
|
|
20
33
|
stagingUrl: string;
|
|
21
34
|
gitInfo: GitInfo;
|
|
35
|
+
projectId?: string;
|
|
36
|
+
overrideVersionId?: string;
|
|
22
37
|
}): Promise<CreateVersionResult>;
|
|
23
38
|
export interface WidgetLocationResult {
|
|
24
39
|
file: string | null;
|
package/dist/lib/api.js
CHANGED
|
@@ -7,6 +7,16 @@ export async function apiGetMe(apiKey) {
|
|
|
7
7
|
throw new Error("Invalid API key");
|
|
8
8
|
return res.json();
|
|
9
9
|
}
|
|
10
|
+
export async function apiGetRecentProjects(apiKey, workspaceId) {
|
|
11
|
+
const res = await fetch(`${API_URL}/api/cli/projects/recent?workspaceId=${encodeURIComponent(workspaceId)}`, {
|
|
12
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
13
|
+
});
|
|
14
|
+
if (!res.ok) {
|
|
15
|
+
const body = await res.json().catch(() => ({ error: res.statusText }));
|
|
16
|
+
throw new Error(body.error ?? `API error ${res.status}`);
|
|
17
|
+
}
|
|
18
|
+
return res.json();
|
|
19
|
+
}
|
|
10
20
|
export async function apiCreateVersion(opts) {
|
|
11
21
|
const res = await fetch(`${API_URL}/api/cli/version/create`, {
|
|
12
22
|
method: "POST",
|
|
@@ -18,6 +28,8 @@ export async function apiCreateVersion(opts) {
|
|
|
18
28
|
workspaceId: opts.workspaceId,
|
|
19
29
|
stagingUrl: opts.stagingUrl,
|
|
20
30
|
gitInfo: opts.gitInfo,
|
|
31
|
+
...(opts.projectId && { projectId: opts.projectId }),
|
|
32
|
+
...(opts.overrideVersionId && { overrideVersionId: opts.overrideVersionId }),
|
|
21
33
|
}),
|
|
22
34
|
});
|
|
23
35
|
if (!res.ok) {
|
package/dist/providers/vercel.js
CHANGED
|
@@ -3,7 +3,7 @@ import pc from "picocolors";
|
|
|
3
3
|
import { execSync } from "child_process";
|
|
4
4
|
import { parseGitRepo, getGitRoot } from "../lib/git.js";
|
|
5
5
|
import { writeVercelConfig } from "../lib/config.js";
|
|
6
|
-
import { ensureVercelCli, ensureVercelAuth, readLocalVercelProject, writeLocalVercelProject, getVercelProjectDetail, fetchAllProjectsWithLinks, matchProjectsByRepo, createVercelProject,
|
|
6
|
+
import { ensureVercelCli, ensureVercelAuth, readLocalVercelProject, writeLocalVercelProject, getVercelProjectDetail, fetchAllProjectsWithLinks, matchProjectsByRepo, createVercelProject, getRecentDeployments, } from "../lib/vercel.js";
|
|
7
7
|
// --- Auto-detection ---
|
|
8
8
|
/**
|
|
9
9
|
* Auto-detect the Vercel project for the current git repo.
|
|
@@ -29,7 +29,7 @@ async function autoDetectProject(cwd, gitInfo, token) {
|
|
|
29
29
|
if (localProject) {
|
|
30
30
|
const detail = await getVercelProjectDetail(token, localProject.projectId, localProject.orgId);
|
|
31
31
|
if (detail) {
|
|
32
|
-
p.log.info(`Detected Vercel project: ${pc.bold(detail.name)}`);
|
|
32
|
+
// p.log.info(`Detected Vercel project: ${pc.bold(detail.name)}`);
|
|
33
33
|
return { teamId: localProject.orgId, projectId: detail.id, projectName: detail.name };
|
|
34
34
|
}
|
|
35
35
|
}
|
|
@@ -196,61 +196,57 @@ export async function resolveVercelUrl(cwd, gitInfo, opts) {
|
|
|
196
196
|
const project = await autoDetectProject(cwd, gitInfo, token);
|
|
197
197
|
if (!project)
|
|
198
198
|
return null;
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
199
|
+
const commitSha = gitInfo.commitShort?.slice(0, 7) ?? null;
|
|
200
|
+
// Try to find a deployment matching the current commit
|
|
201
|
+
let deployments = await getRecentDeployments(token, project.teamId, project.projectId, {
|
|
202
|
+
branch: gitInfo.branch ?? undefined,
|
|
203
|
+
});
|
|
204
|
+
let commitDeploy = deployments.find((d) => d.commitSha === commitSha);
|
|
205
|
+
// If just pushed, poll until the commit deployment appears
|
|
206
|
+
if (!commitDeploy && opts?.justPushed) {
|
|
202
207
|
const pollSpinner = p.spinner();
|
|
203
208
|
pollSpinner.start("Waiting for Vercel deployment...");
|
|
204
209
|
for (let i = 0; i < 30; i++) {
|
|
205
210
|
await new Promise((r) => setTimeout(r, 2000));
|
|
206
|
-
|
|
207
|
-
|
|
211
|
+
deployments = await getRecentDeployments(token, project.teamId, project.projectId, {
|
|
212
|
+
branch: gitInfo.branch ?? undefined,
|
|
213
|
+
});
|
|
214
|
+
commitDeploy = deployments.find((d) => d.commitSha === commitSha);
|
|
215
|
+
if (commitDeploy)
|
|
208
216
|
break;
|
|
209
217
|
}
|
|
210
|
-
if (
|
|
218
|
+
if (commitDeploy) {
|
|
211
219
|
pollSpinner.stop("Deployment found!");
|
|
212
220
|
}
|
|
213
221
|
else {
|
|
214
222
|
pollSpinner.stop("Deployment is still building...");
|
|
215
223
|
}
|
|
216
224
|
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
const
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
{ value: "manual", label: "Paste a URL manually" },
|
|
226
|
-
],
|
|
227
|
-
});
|
|
228
|
-
if (p.isCancel(choice)) {
|
|
229
|
-
p.cancel("Cancelled.");
|
|
230
|
-
process.exit(0);
|
|
231
|
-
}
|
|
232
|
-
if (choice === "branch")
|
|
233
|
-
return branchAlias.url;
|
|
234
|
-
if (choice === "manual")
|
|
235
|
-
return null;
|
|
225
|
+
// If we found a commit-specific deployment, use it automatically
|
|
226
|
+
if (commitDeploy) {
|
|
227
|
+
const stateLabel = commitDeploy.state !== "READY" ? ` ${pc.yellow(`(${commitDeploy.state.toLowerCase()})`)}` : "";
|
|
228
|
+
const message = commitDeploy.commitMessage
|
|
229
|
+
? pc.dim(` — ${truncate(commitDeploy.commitMessage.split("\n")[0], 50)}`)
|
|
230
|
+
: "";
|
|
231
|
+
p.log.info(`Deployment for ${pc.bold(commitSha)}${message}:\n → ${pc.cyan(commitDeploy.url)}${stateLabel}`);
|
|
232
|
+
return commitDeploy.url;
|
|
236
233
|
}
|
|
237
|
-
//
|
|
238
|
-
|
|
239
|
-
if (recent.length === 0) {
|
|
234
|
+
// Fallback: no commit deployment found — let user pick from recent or paste manually
|
|
235
|
+
if (deployments.length === 0) {
|
|
240
236
|
p.log.warn("No deployments found. Paste a URL instead.");
|
|
241
237
|
return null;
|
|
242
238
|
}
|
|
243
|
-
const maxBranch = Math.max(...
|
|
239
|
+
const maxBranch = Math.max(...deployments.map((d) => (d.branch ?? "unknown").length));
|
|
244
240
|
const selected = await p.select({
|
|
245
|
-
message: "
|
|
241
|
+
message: "No deployment found for current commit. Select one:",
|
|
246
242
|
options: [
|
|
247
|
-
...
|
|
243
|
+
...deployments.map((d) => {
|
|
248
244
|
const branch = (d.branch ?? "unknown").padEnd(maxBranch);
|
|
249
245
|
const ago = timeAgo(d.createdAt).padEnd(8);
|
|
250
246
|
const state = d.state !== "READY" ? ` ${pc.yellow(`(${d.state.toLowerCase()})`)}` : "";
|
|
251
247
|
const firstLine = (d.commitMessage ?? "No commit message").split("\n")[0];
|
|
252
|
-
const
|
|
253
|
-
return { value: d.url, label: `${branch} ${ago}${state} ${pc.dim(
|
|
248
|
+
const msg = truncate(firstLine, 55);
|
|
249
|
+
return { value: d.url, label: `${branch} ${ago}${state} ${pc.dim(msg)}` };
|
|
254
250
|
}),
|
|
255
251
|
{ value: "manual", label: "Paste a URL manually" },
|
|
256
252
|
],
|