inflight-cli 2.1.6 → 2.2.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/setup.js +9 -5
- package/dist/commands/share.js +79 -3
- package/dist/lib/api.d.ts +15 -0
- package/dist/lib/api.js +12 -0
- package/dist/lib/framework.d.ts +2 -1
- package/dist/lib/framework.js +12 -9
- package/dist/providers/vercel.js +31 -35
- package/package.json +1 -1
package/dist/commands/setup.js
CHANGED
|
@@ -5,7 +5,7 @@ import { readGlobalAuth, readWorkspaceConfig, writeWorkspaceConfig } from "../li
|
|
|
5
5
|
import { apiGetMe, apiDetectWidgetLocation } from "../lib/api.js";
|
|
6
6
|
import { loginCommand } from "./login.js";
|
|
7
7
|
import { shareCommand } from "./share.js";
|
|
8
|
-
import { gatherProjectContext,
|
|
8
|
+
import { gatherProjectContext, insertWidgetScript } from "../lib/framework.js";
|
|
9
9
|
import { isGitRepo } from "../lib/git.js";
|
|
10
10
|
import { installSkill } from "../lib/skill.js";
|
|
11
11
|
function execSyncErrorDetail(err) {
|
|
@@ -83,7 +83,10 @@ export async function setupCommand() {
|
|
|
83
83
|
process.exit(1);
|
|
84
84
|
}
|
|
85
85
|
// ── Step 4: Add widget script tag ──
|
|
86
|
-
|
|
86
|
+
const hasWidget = (fileContents) => Object.values(fileContents).some((c) => c.includes("inflight.co/widget.js"));
|
|
87
|
+
const context = gatherProjectContext(cwd);
|
|
88
|
+
const alreadyHasWidget = hasWidget(context.fileContents);
|
|
89
|
+
if (alreadyHasWidget) {
|
|
87
90
|
// Already present — move on silently
|
|
88
91
|
}
|
|
89
92
|
else {
|
|
@@ -91,14 +94,13 @@ export async function setupCommand() {
|
|
|
91
94
|
spinner.start("Detecting framework...");
|
|
92
95
|
let inserted = false;
|
|
93
96
|
try {
|
|
94
|
-
const context = gatherProjectContext(cwd);
|
|
95
97
|
const location = await apiDetectWidgetLocation({
|
|
96
98
|
apiKey: auth.apiKey,
|
|
97
99
|
fileTree: context.fileTree,
|
|
98
100
|
fileContents: context.fileContents,
|
|
99
101
|
});
|
|
100
102
|
if (location.file && location.insertAfter && location.confidence === "high") {
|
|
101
|
-
const result = insertWidgetScript(
|
|
103
|
+
const result = insertWidgetScript(context.root, location.file, location.insertAfter, widgetId);
|
|
102
104
|
if (result) {
|
|
103
105
|
spinner.stop(`Detected ${pc.bold(location.framework ?? "framework")} — widget script tag added to ${pc.cyan(location.file)}`);
|
|
104
106
|
inserted = true;
|
|
@@ -126,7 +128,9 @@ export async function setupCommand() {
|
|
|
126
128
|
p.cancel("Cancelled.");
|
|
127
129
|
process.exit(0);
|
|
128
130
|
}
|
|
129
|
-
if
|
|
131
|
+
// Re-scan to check if user added the widget manually
|
|
132
|
+
const rescan = gatherProjectContext(cwd);
|
|
133
|
+
if (!hasWidget(rescan.fileContents)) {
|
|
130
134
|
const skip = await p.confirm({
|
|
131
135
|
message: "Widget script not detected. Continue anyway?",
|
|
132
136
|
initialValue: false,
|
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/lib/framework.d.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
export declare function gatherProjectContext(cwd: string): {
|
|
6
6
|
fileTree: string[];
|
|
7
7
|
fileContents: Record<string, string>;
|
|
8
|
+
root: string;
|
|
8
9
|
};
|
|
9
10
|
/**
|
|
10
11
|
* Returns true if any candidate file in the project already has the Inflight widget script.
|
|
@@ -14,4 +15,4 @@ export declare function hasInflightWidget(cwd: string): boolean;
|
|
|
14
15
|
* Inserts the script tag into a file at the specified location.
|
|
15
16
|
* Returns the file path that was modified, or null if insertion failed.
|
|
16
17
|
*/
|
|
17
|
-
export declare function insertWidgetScript(
|
|
18
|
+
export declare function insertWidgetScript(root: string, file: string, insertAfter: string, widgetId: string): string | null;
|
package/dist/lib/framework.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { existsSync, readFileSync, writeFileSync, readdirSync, statSync } from "fs";
|
|
2
2
|
import { join, relative } from "path";
|
|
3
|
+
import { getGitRoot } from "./git.js";
|
|
3
4
|
/** File patterns that are likely layout/root files where a script tag belongs */
|
|
4
5
|
const CANDIDATE_PATTERNS = [
|
|
5
6
|
"package.json",
|
|
@@ -27,10 +28,12 @@ const CANDIDATE_PATTERNS = [
|
|
|
27
28
|
export function gatherProjectContext(cwd) {
|
|
28
29
|
const fileTree = [];
|
|
29
30
|
const fileContents = {};
|
|
30
|
-
|
|
31
|
+
// Use git root so we scan the full repo, not just wherever the user ran the CLI from
|
|
32
|
+
const root = getGitRoot(cwd) ?? cwd;
|
|
33
|
+
const dirsToScan = [root];
|
|
31
34
|
// If monorepo, add common subdirectories
|
|
32
35
|
for (const dir of ["apps", "packages", "projects", "services", "libs"]) {
|
|
33
|
-
const baseDir = join(
|
|
36
|
+
const baseDir = join(root, dir);
|
|
34
37
|
if (!existsSync(baseDir))
|
|
35
38
|
continue;
|
|
36
39
|
try {
|
|
@@ -49,7 +52,7 @@ export function gatherProjectContext(cwd) {
|
|
|
49
52
|
// Add shallow file listing (1 level deep + key subdirs)
|
|
50
53
|
try {
|
|
51
54
|
for (const entry of readdirSync(dir)) {
|
|
52
|
-
fileTree.push(relative(
|
|
55
|
+
fileTree.push(relative(root, join(dir, entry)));
|
|
53
56
|
}
|
|
54
57
|
// Also list key subdirectories
|
|
55
58
|
for (const sub of ["app", "src", "src/app", "src/layouts", "pages", "src/pages"]) {
|
|
@@ -57,7 +60,7 @@ export function gatherProjectContext(cwd) {
|
|
|
57
60
|
if (!existsSync(subDir))
|
|
58
61
|
continue;
|
|
59
62
|
for (const entry of readdirSync(subDir)) {
|
|
60
|
-
fileTree.push(relative(
|
|
63
|
+
fileTree.push(relative(root, join(subDir, entry)));
|
|
61
64
|
}
|
|
62
65
|
}
|
|
63
66
|
}
|
|
@@ -67,7 +70,7 @@ export function gatherProjectContext(cwd) {
|
|
|
67
70
|
// Read candidate files
|
|
68
71
|
for (const pattern of CANDIDATE_PATTERNS) {
|
|
69
72
|
const filePath = join(dir, pattern);
|
|
70
|
-
const relPath = relative(
|
|
73
|
+
const relPath = relative(root, filePath);
|
|
71
74
|
if (existsSync(filePath) && !fileContents[relPath]) {
|
|
72
75
|
try {
|
|
73
76
|
const content = readFileSync(filePath, "utf-8");
|
|
@@ -85,7 +88,7 @@ export function gatherProjectContext(cwd) {
|
|
|
85
88
|
try {
|
|
86
89
|
for (const f of readdirSync(layoutsDir).filter((f) => f.endsWith(".astro"))) {
|
|
87
90
|
const filePath = join(layoutsDir, f);
|
|
88
|
-
const relPath = relative(
|
|
91
|
+
const relPath = relative(root, filePath);
|
|
89
92
|
if (!fileContents[relPath]) {
|
|
90
93
|
const content = readFileSync(filePath, "utf-8");
|
|
91
94
|
fileContents[relPath] = content.length > 3000 ? content.slice(0, 3000) + "\n... (truncated)" : content;
|
|
@@ -97,7 +100,7 @@ export function gatherProjectContext(cwd) {
|
|
|
97
100
|
}
|
|
98
101
|
}
|
|
99
102
|
}
|
|
100
|
-
return { fileTree, fileContents };
|
|
103
|
+
return { fileTree, fileContents, root };
|
|
101
104
|
}
|
|
102
105
|
/**
|
|
103
106
|
* Returns true if any candidate file in the project already has the Inflight widget script.
|
|
@@ -110,9 +113,9 @@ export function hasInflightWidget(cwd) {
|
|
|
110
113
|
* Inserts the script tag into a file at the specified location.
|
|
111
114
|
* Returns the file path that was modified, or null if insertion failed.
|
|
112
115
|
*/
|
|
113
|
-
export function insertWidgetScript(
|
|
116
|
+
export function insertWidgetScript(root, file, insertAfter, widgetId) {
|
|
114
117
|
try {
|
|
115
|
-
const filePath = join(
|
|
118
|
+
const filePath = join(root, file);
|
|
116
119
|
const content = readFileSync(filePath, "utf-8");
|
|
117
120
|
if (content.includes("inflight.co/widget.js")) {
|
|
118
121
|
return file; // Already present
|
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
|
],
|