inflight-cli 2.6.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.
- package/dist/commands/setup.js +34 -32
- package/dist/commands/share.js +22 -9
- package/dist/lib/framework.js +7 -1
- package/dist/lib/netlify.d.ts +4 -1
- package/dist/lib/netlify.js +9 -4
- package/dist/lib/vercel.d.ts +4 -1
- package/dist/lib/vercel.js +9 -4
- package/dist/providers/netlify.js +33 -34
- package/dist/providers/vercel.js +24 -29
- package/package.json +1 -1
package/dist/commands/setup.js
CHANGED
|
@@ -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
|
-
// ──
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
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
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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(
|
|
127
|
+
if (p.isCancel(added)) {
|
|
128
128
|
p.cancel("Cancelled.");
|
|
129
129
|
process.exit(0);
|
|
130
130
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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
|
-
? `
|
|
178
|
-
:
|
|
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 {
|
package/dist/commands/share.js
CHANGED
|
@@ -112,7 +112,12 @@ async function checkAndSyncGit(cwd) {
|
|
|
112
112
|
return { justPushed: true };
|
|
113
113
|
}
|
|
114
114
|
catch (e) {
|
|
115
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|
|
@@ -366,17 +381,15 @@ export async function shareCommand(opts = {}) {
|
|
|
366
381
|
const projectChoice = await scrollableSelect({
|
|
367
382
|
message: "Create a new project in Inflight, or update an existing one?",
|
|
368
383
|
maxItems: 5,
|
|
384
|
+
...(branchMatch && { initialValue: branchMatch.projectId }),
|
|
369
385
|
options: [
|
|
370
386
|
{ value: "new", label: "Start a new project" },
|
|
371
387
|
...sortedProjects.map((proj) => ({
|
|
372
388
|
value: proj.projectId,
|
|
373
389
|
label: `"${proj.latestVersion.title}"`,
|
|
374
|
-
hint:
|
|
375
|
-
formatRelativeTime(proj.latestVersion.createdAt)
|
|
376
|
-
proj.latestVersion.
|
|
377
|
-
]
|
|
378
|
-
.filter(Boolean)
|
|
379
|
-
.join(" · "),
|
|
390
|
+
hint: proj.latestVersion.branch === currentBranch
|
|
391
|
+
? `${formatRelativeTime(proj.latestVersion.createdAt)} · current branch (${currentBranch})`
|
|
392
|
+
: formatRelativeTime(proj.latestVersion.createdAt),
|
|
380
393
|
})),
|
|
381
394
|
],
|
|
382
395
|
});
|
package/dist/lib/framework.js
CHANGED
|
@@ -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;
|
package/dist/lib/netlify.d.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
export declare function ensureNetlifyCli(log?: (msg: string) => void): Promise<
|
|
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.
|
package/dist/lib/netlify.js
CHANGED
|
@@ -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
|
-
|
|
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
|
/**
|
package/dist/lib/vercel.d.ts
CHANGED
|
@@ -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<
|
|
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.
|
package/dist/lib/vercel.js
CHANGED
|
@@ -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
|
-
|
|
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 {
|
|
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
|
|
126
|
-
if (!
|
|
127
|
-
p.log.error("
|
|
128
|
-
|
|
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 ?
|
|
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
|
|
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 ?
|
|
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.
|
|
171
|
+
pollSpinner.clear();
|
|
163
172
|
}
|
|
164
173
|
else {
|
|
165
|
-
pollSpinner.stop("
|
|
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
|
-
|
|
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 ?
|
|
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
|
-
|
|
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
|
-
|
|
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 ?
|
|
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) {
|
package/dist/providers/vercel.js
CHANGED
|
@@ -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
|
|
186
|
-
if (!
|
|
187
|
-
p.log.error("
|
|
188
|
-
|
|
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
|
|
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.
|
|
230
|
+
pollSpinner.clear();
|
|
225
231
|
}
|
|
226
232
|
else {
|
|
227
|
-
pollSpinner.stop("
|
|
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 =
|
|
256
|
+
const pollIntervalMs = 3_000;
|
|
251
257
|
let resolved = false;
|
|
252
|
-
const spinner = p.spinner();
|
|
253
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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}
|
|
310
|
+
p.log.info(`Vercel deployment for ${commitLabel}${message}`);
|
|
316
311
|
return commitDeploy.url;
|
|
317
312
|
}
|
|
318
313
|
}
|