inflight-cli 2.5.0 → 2.7.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.
@@ -7,7 +7,6 @@ import { loginCommand } from "./login.js";
7
7
  import { shareCommand } from "./share.js";
8
8
  import { gatherProjectContext, insertWidgetScript } from "../lib/framework.js";
9
9
  import { isGitRepo } from "../lib/git.js";
10
- import { installSkill } from "../lib/skill.js";
11
10
  function execSyncErrorDetail(err) {
12
11
  if (err !== null && typeof err === "object" && "stderr" in err) {
13
12
  const b = err.stderr;
@@ -24,11 +23,14 @@ function execSyncErrorDetail(err) {
24
23
  }
25
24
  export async function setupCommand() {
26
25
  const cwd = process.cwd();
27
- // ── Step 1: Install agent skill ──
28
- p.log.step("Installing agent skill...");
29
- await installSkill();
30
- // ── Step 2: Authenticate ──
26
+ // ── Pre-flight: warn if not a git repo ──
27
+ if (!isGitRepo(cwd)) {
28
+ p.log.warn("This directory is not a git repository.\n" +
29
+ " Inflight works best inside a git repo so it can track branches and commits.");
30
+ }
31
+ // ── Step 1: Authenticate ──
31
32
  let auth = readGlobalAuth();
33
+ const alreadyLoggedIn = !!auth;
32
34
  if (!auth) {
33
35
  await loginCommand();
34
36
  auth = readGlobalAuth();
@@ -37,17 +39,14 @@ export async function setupCommand() {
37
39
  process.exit(1);
38
40
  }
39
41
  }
40
- else {
41
- const me = await apiGetMe(auth.apiKey).catch(() => null);
42
- if (me?.email) {
43
- p.log.success(`Logged in as ${pc.bold(me.email)}`);
44
- }
45
- }
46
42
  // ── Step 3: Resolve workspace ──
47
43
  const me = await apiGetMe(auth.apiKey).catch((e) => {
48
44
  p.log.error(e.message);
49
45
  process.exit(1);
50
46
  });
47
+ if (alreadyLoggedIn && me.email) {
48
+ p.log.success(`Logged in as ${pc.bold(me.email)}`);
49
+ }
51
50
  const workspaces = me.workspaces;
52
51
  let workspaceId;
53
52
  // Check if a workspace is already configured and still valid
@@ -87,7 +86,7 @@ export async function setupCommand() {
87
86
  const context = gatherProjectContext(cwd);
88
87
  const alreadyHasWidget = hasWidget(context.fileContents);
89
88
  if (alreadyHasWidget) {
90
- // Already present move on silently
89
+ p.log.success("Widget script tag already installed!");
91
90
  }
92
91
  else {
93
92
  const spinner = p.spinner();
@@ -113,33 +112,36 @@ export async function setupCommand() {
113
112
  spinner.stop("Could not auto-detect where to add the widget.");
114
113
  }
115
114
  }
116
- catch {
115
+ catch (e) {
117
116
  spinner.stop("Could not analyze your project.");
117
+ if (e instanceof Error)
118
+ p.log.message(pc.dim(e.message));
118
119
  }
119
120
  if (!inserted) {
120
- p.log.message(`Add this snippet to your root HTML layout, just before ${pc.cyan("</body>")}:\n\n` +
121
- pc.dim(` <script src="https://www.inflight.co/widget.js" data-workspace="${widgetId}" async></script>`));
122
- const waited = await p.text({
123
- message: "Press enter when you've added it",
124
- defaultValue: "",
125
- placeholder: "",
121
+ const scriptTag = `<script src="https://www.inflight.co/widget.js" data-workspace="${widgetId}" async></script>`;
122
+ const agentPrompt = `Add the Inflight widget to this project. Insert this script tag into the root layout file, just before </body> (or as the last child of <body> in JSX): ${scriptTag}`;
123
+ p.log.message(`Paste this into your AI agent (Cursor, Claude Code, etc.):\n\n` + pc.dim(` ${agentPrompt}`));
124
+ const added = await p.confirm({
125
+ message: "Have you added the script tag?",
126
126
  });
127
- if (p.isCancel(waited)) {
127
+ if (p.isCancel(added)) {
128
128
  p.cancel("Cancelled.");
129
129
  process.exit(0);
130
130
  }
131
- // Re-scan to check if user added the widget manually
132
- const rescan = gatherProjectContext(cwd);
133
- if (!hasWidget(rescan.fileContents)) {
134
- const skip = await p.confirm({
135
- message: "Widget script not detected. Continue anyway?",
136
- initialValue: false,
137
- });
138
- if (p.isCancel(skip) || !skip) {
139
- p.cancel("Add the widget script and run setup again.");
140
- process.exit(0);
131
+ if (added) {
132
+ // Re-scan to verify
133
+ const rescan = gatherProjectContext(cwd);
134
+ if (!hasWidget(rescan.fileContents)) {
135
+ p.log.warn("Widget script not found in your project files.\n" +
136
+ " Make sure the snippet is saved and includes " +
137
+ pc.cyan("inflight.co/widget.js") +
138
+ ".");
141
139
  }
142
140
  }
141
+ else {
142
+ p.log.info("No worries — add the snippet later and run " + pc.cyan("inflight setup") + " again.");
143
+ process.exit(0);
144
+ }
143
145
  }
144
146
  }
145
147
  // ── Step 5: Commit and push (only files containing the widget script) ──
@@ -174,8 +176,8 @@ export async function setupCommand() {
174
176
  catch (err) {
175
177
  const detail = execSyncErrorDetail(err);
176
178
  p.log.warn(detail
177
- ? `Commit or push failed:\n${pc.dim(detail)}\n\nPush manually before sharing.`
178
- : "Commit or push failed. Push manually before sharing.");
179
+ ? `Push failed:\n${pc.dim(detail)}\n\nPaste the error above into your AI agent to fix it, or run ${pc.cyan("git push")} manually.`
180
+ : `Push failed. Run ${pc.cyan("git push")} manually or ask your AI agent for help.`);
179
181
  }
180
182
  }
181
183
  else {
@@ -112,7 +112,12 @@ async function checkAndSyncGit(cwd) {
112
112
  return { justPushed: true };
113
113
  }
114
114
  catch (e) {
115
- spinner.stop(`Push failed: ${e.message}`);
115
+ const stderr = e && typeof e === "object" && "stderr" in e ? String(e.stderr).trim() : "";
116
+ spinner.stop("Push failed.");
117
+ if (stderr)
118
+ p.log.message(pc.dim(stderr));
119
+ else if (e instanceof Error)
120
+ p.log.message(pc.dim(e.message));
116
121
  p.log.warn("Continuing with existing deployment.");
117
122
  return { justPushed: false };
118
123
  }
@@ -147,7 +152,12 @@ async function checkAndSyncGit(cwd) {
147
152
  return { justPushed: true };
148
153
  }
149
154
  catch (e) {
150
- spinner.stop(`Push failed: ${e.message}`);
155
+ const stderr = e && typeof e === "object" && "stderr" in e ? String(e.stderr).trim() : "";
156
+ spinner.stop("Push failed.");
157
+ if (stderr)
158
+ p.log.message(pc.dim(stderr));
159
+ else if (e instanceof Error)
160
+ p.log.message(pc.dim(e.message));
151
161
  p.log.warn("Continuing with existing deployment.");
152
162
  return { justPushed: false };
153
163
  }
@@ -179,7 +189,12 @@ async function checkAndSyncGit(cwd) {
179
189
  return { justPushed: true };
180
190
  }
181
191
  catch (e) {
182
- spinner.stop(`Push failed: ${e.message}`);
192
+ const stderr = e && typeof e === "object" && "stderr" in e ? String(e.stderr).trim() : "";
193
+ spinner.stop("Push failed.");
194
+ if (stderr)
195
+ p.log.message(pc.dim(stderr));
196
+ else if (e instanceof Error)
197
+ p.log.message(pc.dim(e.message));
183
198
  return { justPushed: false };
184
199
  }
185
200
  }
@@ -359,21 +374,22 @@ export async function shareCommand(opts = {}) {
359
374
  const branchMatch = currentBranch
360
375
  ? projects.find((proj) => proj.latestVersion.branch === currentBranch)
361
376
  : undefined;
377
+ // Move branch-matched project to the top of the list
378
+ const sortedProjects = branchMatch
379
+ ? [branchMatch, ...projects.filter((proj) => proj.projectId !== branchMatch.projectId)]
380
+ : projects;
362
381
  const projectChoice = await scrollableSelect({
363
382
  message: "Create a new project in Inflight, or update an existing one?",
364
383
  maxItems: 5,
365
- initialValue: branchMatch?.projectId,
384
+ ...(branchMatch && { initialValue: branchMatch.projectId }),
366
385
  options: [
367
386
  { value: "new", label: "Start a new project" },
368
- ...projects.map((proj) => ({
387
+ ...sortedProjects.map((proj) => ({
369
388
  value: proj.projectId,
370
389
  label: `"${proj.latestVersion.title}"`,
371
- hint: [
372
- formatRelativeTime(proj.latestVersion.createdAt),
373
- proj.latestVersion.branch === currentBranch ? "← current branch" : "",
374
- ]
375
- .filter(Boolean)
376
- .join(" · "),
390
+ hint: proj.latestVersion.branch === currentBranch
391
+ ? `${formatRelativeTime(proj.latestVersion.createdAt)} · current branch (${currentBranch})`
392
+ : formatRelativeTime(proj.latestVersion.createdAt),
377
393
  })),
378
394
  ],
379
395
  });
@@ -5,6 +5,7 @@ import { getGitRoot } from "./git.js";
5
5
  const CANDIDATE_PATTERNS = [
6
6
  "package.json",
7
7
  "index.html",
8
+ "src/index.html",
8
9
  "app/layout.tsx",
9
10
  "app/layout.jsx",
10
11
  "src/app/layout.tsx",
@@ -16,8 +17,13 @@ const CANDIDATE_PATTERNS = [
16
17
  "app/root.tsx",
17
18
  "app/root.jsx",
18
19
  "src/app.html",
20
+ "src/routes/+layout.svelte",
19
21
  "nuxt.config.ts",
20
22
  "nuxt.config.js",
23
+ "app.vue",
24
+ "layouts/default.vue",
25
+ "gatsby-ssr.tsx",
26
+ "gatsby-ssr.jsx",
21
27
  "src/layouts/Layout.astro",
22
28
  "src/layouts/BaseLayout.astro",
23
29
  ];
@@ -55,7 +61,7 @@ export function gatherProjectContext(cwd) {
55
61
  fileTree.push(relative(root, join(dir, entry)));
56
62
  }
57
63
  // Also list key subdirectories
58
- for (const sub of ["app", "src", "src/app", "src/layouts", "pages", "src/pages"]) {
64
+ for (const sub of ["app", "src", "src/app", "src/layouts", "src/routes", "pages", "src/pages", "layouts"]) {
59
65
  const subDir = join(dir, sub);
60
66
  if (!existsSync(subDir))
61
67
  continue;
@@ -1,4 +1,7 @@
1
- export declare function ensureNetlifyCli(log?: (msg: string) => void): Promise<boolean>;
1
+ export declare function ensureNetlifyCli(log?: (msg: string) => void): Promise<{
2
+ ok: boolean;
3
+ error?: string;
4
+ }>;
2
5
  /**
3
6
  * Gets the Netlify auth token non-interactively.
4
7
  * Priority: NETLIFY_AUTH_TOKEN env var → CLI config file.
@@ -16,14 +16,19 @@ function hasNetlifyCli() {
16
16
  }
17
17
  export async function ensureNetlifyCli(log) {
18
18
  if (hasNetlifyCli())
19
- return true;
19
+ return { ok: true };
20
20
  log?.("Installing Netlify CLI...");
21
21
  try {
22
22
  await execAsync("npm install -g netlify-cli");
23
- return true;
23
+ return { ok: true };
24
24
  }
25
- catch {
26
- return false;
25
+ catch (e) {
26
+ const stderr = e && typeof e === "object" && "stderr" in e && typeof e.stderr === "string"
27
+ ? e.stderr
28
+ : e instanceof Error
29
+ ? e.message
30
+ : "Unknown error";
31
+ return { ok: false, error: stderr };
27
32
  }
28
33
  }
29
34
  /**
@@ -1,5 +1,8 @@
1
1
  /** Ensures the Vercel CLI is available — installs globally if missing. */
2
- export declare function ensureVercelCli(log?: (msg: string) => void): Promise<boolean>;
2
+ export declare function ensureVercelCli(log?: (msg: string) => void): Promise<{
3
+ ok: boolean;
4
+ error?: string;
5
+ }>;
3
6
  /**
4
7
  * Gets a valid Vercel token. Refreshes silently via `vercel whoami` if expired.
5
8
  * Does NOT prompt for login — returns null if no valid token available.
@@ -18,14 +18,19 @@ function hasVercelCli() {
18
18
  /** Ensures the Vercel CLI is available — installs globally if missing. */
19
19
  export async function ensureVercelCli(log) {
20
20
  if (hasVercelCli())
21
- return true;
21
+ return { ok: true };
22
22
  log?.("Installing Vercel CLI...");
23
23
  try {
24
24
  await execAsync("npm install -g vercel");
25
- return true;
25
+ return { ok: true };
26
26
  }
27
- catch {
28
- return false;
27
+ catch (e) {
28
+ const stderr = e && typeof e === "object" && "stderr" in e && typeof e.stderr === "string"
29
+ ? e.stderr
30
+ : e instanceof Error
31
+ ? e.message
32
+ : "Unknown error";
33
+ return { ok: false, error: stderr };
29
34
  }
30
35
  }
31
36
  /** Returns the Vercel CLI config directory for the current platform. */
@@ -43,8 +43,10 @@ async function autoDetectSite(cwd, gitInfo, token) {
43
43
  try {
44
44
  allSites = await getNetlifySites(token);
45
45
  }
46
- catch {
46
+ catch (e) {
47
47
  spinner.stop("Could not fetch Netlify sites.");
48
+ if (e instanceof Error)
49
+ p.log.message(pc.dim(e.message));
48
50
  return null;
49
51
  }
50
52
  if (allSites.length === 0) {
@@ -81,9 +83,7 @@ async function pickFromList(sites) {
81
83
  message: "Select a Netlify site",
82
84
  options: sites.map((s) => ({
83
85
  value: s,
84
- label: s.account_name
85
- ? `${s.name.padEnd(maxName)} ${pc.dim(`(${s.account_name})`)}`
86
- : s.name,
86
+ label: s.account_name ? `${s.name.padEnd(maxName)} ${pc.dim(`(${s.account_name})`)}` : s.name,
87
87
  })),
88
88
  });
89
89
  if (p.isCancel(selected)) {
@@ -91,7 +91,12 @@ async function pickFromList(sites) {
91
91
  process.exit(0);
92
92
  }
93
93
  const match = selected;
94
- return { siteId: match.id, siteName: match.name, teamSlug: match.account_slug, repoPath: match.build_settings?.repo_path ?? null };
94
+ return {
95
+ siteId: match.id,
96
+ siteName: match.name,
97
+ teamSlug: match.account_slug,
98
+ repoPath: match.build_settings?.repo_path ?? null,
99
+ };
95
100
  }
96
101
  // --- Manual picker (used by `inflight netlify` command) ---
97
102
  export async function pickNetlifySite(token) {
@@ -122,10 +127,14 @@ export async function pickNetlifySite(token) {
122
127
  }
123
128
  // --- Main resolve function ---
124
129
  export async function resolveNetlifyUrl(cwd, gitInfo, opts) {
125
- const cliOk = await ensureNetlifyCli((msg) => p.log.step(msg));
126
- if (!cliOk) {
127
- p.log.error("Failed to install Netlify CLI. Install manually: " + pc.cyan("npm install -g netlify-cli"));
128
- return null;
130
+ const cli = await ensureNetlifyCli((msg) => p.log.step(msg));
131
+ if (!cli.ok) {
132
+ p.log.error("Could not install the Netlify CLI automatically.");
133
+ if (cli.error) {
134
+ p.log.message(pc.dim(cli.error.trim()));
135
+ }
136
+ p.log.info(`Paste the error above into your AI agent — it can fix this for you.\n\nThen re-run ${pc.cyan("inflight share")}.`);
137
+ process.exit(0);
129
138
  }
130
139
  const token = await ensureNetlifyAuth();
131
140
  if (!token) {
@@ -142,27 +151,27 @@ export async function resolveNetlifyUrl(cwd, gitInfo, opts) {
142
151
  const repoMatches = site.repoPath && localRepoPath ? site.repoPath.toLowerCase() === localRepoPath : false;
143
152
  // Only filter by branch if we're in the matching repo — otherwise show all deploys
144
153
  let deploys = await getNetlifyDeploys(token, site.siteId, site.siteName, {
145
- branch: repoMatches ? (gitInfo.branch ?? undefined) : undefined,
154
+ branch: repoMatches ? gitInfo.branch ?? undefined : undefined,
146
155
  });
147
156
  let commitDeploy = repoMatches ? deploys.find((d) => d.commitRef === commitSha) : undefined;
148
157
  // If just pushed, poll until the commit deployment appears
149
158
  if (!commitDeploy && repoMatches && opts?.justPushed) {
150
- const pollSpinner = p.spinner();
151
- pollSpinner.start("Waiting for Netlify deployment...");
159
+ const pollSpinner = p.spinner({ indicator: "timer" });
160
+ pollSpinner.start("Waiting for Netlify to pick up your push...");
152
161
  for (let i = 0; i < 30; i++) {
153
162
  await new Promise((r) => setTimeout(r, 2000));
154
163
  deploys = await getNetlifyDeploys(token, site.siteId, site.siteName, {
155
- branch: repoMatches ? (gitInfo.branch ?? undefined) : undefined,
164
+ branch: repoMatches ? gitInfo.branch ?? undefined : undefined,
156
165
  });
157
166
  commitDeploy = deploys.find((d) => d.commitRef === commitSha);
158
167
  if (commitDeploy)
159
168
  break;
160
169
  }
161
170
  if (commitDeploy) {
162
- pollSpinner.stop("Netlify deployment found!");
171
+ pollSpinner.clear();
163
172
  }
164
173
  else {
165
- pollSpinner.stop("Netlify deployment is still building...");
174
+ pollSpinner.stop("No deployment detected yet — Netlify may still be processing.");
166
175
  }
167
176
  }
168
177
  // If we found a commit-specific deployment, handle based on state
@@ -184,32 +193,23 @@ export async function resolveNetlifyUrl(cwd, gitInfo, opts) {
184
193
  const maxWaitMs = 120_000;
185
194
  const pollIntervalMs = 5_000;
186
195
  let resolved = false;
187
- const spinner = p.spinner();
188
- const updateMessage = () => {
189
- const elapsed = Math.floor((Date.now() - startTime) / 1000);
190
- spinner.message(`Waiting for Netlify to finish building... (${elapsed}s)`);
191
- };
192
- spinner.start("Waiting for Netlify to finish building... (0s)");
193
- const ticker = setInterval(updateMessage, 1000);
196
+ const spinner = p.spinner({ indicator: "timer" });
197
+ spinner.start("Waiting for Netlify to finish building...");
194
198
  try {
195
199
  while (Date.now() - startTime < maxWaitMs) {
196
200
  await new Promise((r) => setTimeout(r, pollIntervalMs));
197
- updateMessage();
198
201
  const freshDeps = await getNetlifyDeploys(token, site.siteId, site.siteName, {
199
- branch: repoMatches ? (gitInfo.branch ?? undefined) : undefined,
202
+ branch: repoMatches ? gitInfo.branch ?? undefined : undefined,
200
203
  });
201
204
  const fresh = freshDeps.find((d) => d.commitRef === commitSha);
202
205
  if (!fresh)
203
206
  break;
204
207
  if (fresh.state === "ready") {
205
- clearInterval(ticker);
206
- const elapsed = Math.floor((Date.now() - startTime) / 1000);
207
- spinner.stop(`Netlify deployment ready! (${elapsed}s)`);
208
+ spinner.stop("Netlify deployment ready!");
208
209
  p.log.info(`Netlify deployment for ${commitLabel}${message}:\n → ${pc.cyan(fresh.deploySslUrl)}`);
209
210
  return fresh.deploySslUrl;
210
211
  }
211
212
  if (fresh.state === "error") {
212
- clearInterval(ticker);
213
213
  spinner.stop(pc.red("Netlify deployment failed."));
214
214
  p.log.error(`Netlify deployment for ${commitLabel}${message} ${pc.red("failed")}.\n Fix the build and push again, or select a different deployment below.`);
215
215
  commitDeploy = undefined;
@@ -218,9 +218,7 @@ export async function resolveNetlifyUrl(cwd, gitInfo, opts) {
218
218
  }
219
219
  }
220
220
  if (!resolved) {
221
- clearInterval(ticker);
222
- const elapsed = Math.floor((Date.now() - startTime) / 1000);
223
- spinner.stop(`Still building after ${elapsed}s.`);
221
+ spinner.stop("Still building...");
224
222
  const action = await p.select({
225
223
  message: "Netlify deployment is still building.",
226
224
  options: [
@@ -240,9 +238,10 @@ export async function resolveNetlifyUrl(cwd, gitInfo, opts) {
240
238
  // Fall through to picker
241
239
  }
242
240
  }
243
- catch {
244
- clearInterval(ticker);
241
+ catch (e) {
245
242
  spinner.stop("Error checking deployment status.");
243
+ if (e instanceof Error)
244
+ p.log.message(pc.dim(e.message));
246
245
  }
247
246
  }
248
247
  else {
@@ -253,7 +252,7 @@ export async function resolveNetlifyUrl(cwd, gitInfo, opts) {
253
252
  }
254
253
  // Refresh deployments so the picker shows current states
255
254
  deploys = await getNetlifyDeploys(token, site.siteId, site.siteName, {
256
- branch: repoMatches ? (gitInfo.branch ?? undefined) : undefined,
255
+ branch: repoMatches ? gitInfo.branch ?? undefined : undefined,
257
256
  });
258
257
  // Fallback: no commit deployment — let user pick from recent or paste manually
259
258
  if (deploys.length === 0) {
@@ -46,8 +46,10 @@ async function autoDetectProject(cwd, gitInfo, token) {
46
46
  try {
47
47
  allProjects = await getVercelProjects(token);
48
48
  }
49
- catch {
49
+ catch (e) {
50
50
  spinner.stop("Could not fetch Vercel projects.");
51
+ if (e instanceof Error)
52
+ p.log.message(pc.dim(e.message));
51
53
  return null;
52
54
  }
53
55
  if (allProjects.length === 0) {
@@ -182,10 +184,14 @@ async function createProjectFlow(projects, ctx) {
182
184
  }
183
185
  // --- Main resolve function ---
184
186
  export async function resolveVercelUrl(cwd, gitInfo, opts) {
185
- const cliOk = await ensureVercelCli((msg) => p.log.step(msg));
186
- if (!cliOk) {
187
- p.log.error("Failed to install Vercel CLI. Install manually: " + pc.cyan("npm install -g vercel"));
188
- return null;
187
+ const cli = await ensureVercelCli((msg) => p.log.step(msg));
188
+ if (!cli.ok) {
189
+ p.log.error("Could not install the Vercel CLI automatically.");
190
+ if (cli.error) {
191
+ p.log.message(pc.dim(cli.error.trim()));
192
+ }
193
+ p.log.info(`Paste the error above into your AI agent — it can fix this for you.\n\nThen re-run ${pc.cyan("inflight share")}.`);
194
+ process.exit(0);
189
195
  }
190
196
  const token = await ensureVercelAuth();
191
197
  if (!token) {
@@ -209,8 +215,8 @@ export async function resolveVercelUrl(cwd, gitInfo, opts) {
209
215
  let commitDeploy = repoMatches ? deployments.find((d) => d.commitSha === commitSha) : undefined;
210
216
  // If just pushed, poll until the commit deployment appears
211
217
  if (!commitDeploy && repoMatches && opts?.justPushed) {
212
- const pollSpinner = p.spinner();
213
- pollSpinner.start("Waiting for Vercel deployment...");
218
+ const pollSpinner = p.spinner({ indicator: "timer" });
219
+ pollSpinner.start("Waiting for Vercel to pick up your push");
214
220
  for (let i = 0; i < 30; i++) {
215
221
  await new Promise((r) => setTimeout(r, 2000));
216
222
  deployments = await getVercelDeployments(token, project.teamId, project.projectId, {
@@ -221,10 +227,10 @@ export async function resolveVercelUrl(cwd, gitInfo, opts) {
221
227
  break;
222
228
  }
223
229
  if (commitDeploy) {
224
- pollSpinner.stop("Vercel deployment found!");
230
+ pollSpinner.clear();
225
231
  }
226
232
  else {
227
- pollSpinner.stop("Vercel deployment is still building...");
233
+ pollSpinner.stop("No deployment detected yet — Vercel may still be processing.");
228
234
  }
229
235
  }
230
236
  // If we found a commit-specific deployment, handle based on state
@@ -247,19 +253,13 @@ export async function resolveVercelUrl(cwd, gitInfo, opts) {
247
253
  const buildingUrl = commitDeploy.url;
248
254
  const startTime = Date.now();
249
255
  const maxWaitMs = 120_000;
250
- const pollIntervalMs = 5_000;
256
+ const pollIntervalMs = 3_000;
251
257
  let resolved = false;
252
- const spinner = p.spinner();
253
- const updateMessage = () => {
254
- const elapsed = Math.floor((Date.now() - startTime) / 1000);
255
- spinner.message(`Waiting for Vercel to finish building... (${elapsed}s)`);
256
- };
257
- spinner.start("Waiting for Vercel to finish building... (0s) ");
258
- const ticker = setInterval(updateMessage, 1000);
258
+ const spinner = p.spinner({ indicator: "timer" });
259
+ spinner.start("Waiting for Vercel to finish building");
259
260
  try {
260
261
  while (Date.now() - startTime < maxWaitMs) {
261
262
  await new Promise((r) => setTimeout(r, pollIntervalMs));
262
- updateMessage();
263
263
  const freshDeps = await getVercelDeployments(token, project.teamId, project.projectId, {
264
264
  branch: repoMatches ? gitInfo.branch ?? undefined : undefined,
265
265
  });
@@ -267,14 +267,10 @@ export async function resolveVercelUrl(cwd, gitInfo, opts) {
267
267
  if (!fresh)
268
268
  break;
269
269
  if (fresh.state === "READY") {
270
- clearInterval(ticker);
271
- const elapsed = Math.floor((Date.now() - startTime) / 1000);
272
- spinner.stop(`Vercel deployment ready! (${elapsed}s)`);
273
- p.log.info(`Vercel deployment for ${commitLabel}${message}:\n → ${pc.cyan(fresh.url)}`);
270
+ spinner.stop("Vercel deployment ready!");
274
271
  return fresh.url;
275
272
  }
276
273
  if (fresh.state === "ERROR" || fresh.state === "CANCELED") {
277
- clearInterval(ticker);
278
274
  spinner.stop(pc.red("Vercel deployment failed."));
279
275
  p.log.error(`Vercel deployment for ${commitLabel}${message} ${pc.red("failed")}.\n Fix the build and push again, or select a different deployment below.`);
280
276
  commitDeploy = undefined;
@@ -283,9 +279,7 @@ export async function resolveVercelUrl(cwd, gitInfo, opts) {
283
279
  }
284
280
  }
285
281
  if (!resolved) {
286
- clearInterval(ticker);
287
- const elapsed = Math.floor((Date.now() - startTime) / 1000);
288
- spinner.stop(`Still building after ${elapsed}s.`);
282
+ spinner.stop("Still building...");
289
283
  const action = await p.select({
290
284
  message: "Vercel deployment is still building.",
291
285
  options: [
@@ -305,14 +299,15 @@ export async function resolveVercelUrl(cwd, gitInfo, opts) {
305
299
  // Fall through to picker
306
300
  }
307
301
  }
308
- catch {
309
- clearInterval(ticker);
302
+ catch (e) {
310
303
  spinner.stop("Error checking deployment status.");
304
+ if (e instanceof Error)
305
+ p.log.message(pc.dim(e.message));
311
306
  }
312
307
  }
313
308
  else {
314
309
  // READY or any other terminal state — use it
315
- p.log.info(`Vercel deployment for ${commitLabel}${message}:\n → ${pc.cyan(commitDeploy.url)}`);
310
+ p.log.info(`Vercel deployment for ${commitLabel}${message}`);
316
311
  return commitDeploy.url;
317
312
  }
318
313
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "inflight-cli",
3
- "version": "2.5.0",
3
+ "version": "2.7.0",
4
4
  "description": "Get feedback directly on your staging URL",
5
5
  "bin": {
6
6
  "inflight": "dist/index.js",