inflight-cli 2.13.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 +1 -7
- 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/commands/share.js
CHANGED
|
@@ -44,7 +44,7 @@ function appendWidgetToken(url, widgetToken) {
|
|
|
44
44
|
return parsed.toString();
|
|
45
45
|
}
|
|
46
46
|
/**
|
|
47
|
-
* Builds a next_command string carrying forward relevant opts from the current run.
|
|
47
|
+
* Builds a next_command string carrying forward relevant opts from the current run.
|
|
48
48
|
*/
|
|
49
49
|
function buildNextCommand(base, opts) {
|
|
50
50
|
const parts = [base];
|
|
@@ -52,8 +52,8 @@ function buildNextCommand(base, opts) {
|
|
|
52
52
|
parts.push(`--workspace ${opts.workspace}`);
|
|
53
53
|
if (opts.provider)
|
|
54
54
|
parts.push(`--provider ${opts.provider}`);
|
|
55
|
-
if (opts.
|
|
56
|
-
parts.push(`--
|
|
55
|
+
if (opts.url)
|
|
56
|
+
parts.push(`--url ${opts.url}`);
|
|
57
57
|
if (opts.project)
|
|
58
58
|
parts.push(`--project ${opts.project}`);
|
|
59
59
|
if (opts.versionMode)
|
|
@@ -67,376 +67,208 @@ function buildNextCommand(base, opts) {
|
|
|
67
67
|
*/
|
|
68
68
|
async function checkAndSyncGit(cwd, opts = {}) {
|
|
69
69
|
const state = getGitSyncState(cwd);
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
// After ruling out clean/detached, branch is guaranteed non-null
|
|
74
|
-
if (!state.branch)
|
|
75
|
-
return { justPushed: false };
|
|
76
|
-
const { branch } = state;
|
|
77
|
-
if (state.status === "uncommitted") {
|
|
78
|
-
// Show changed files
|
|
79
|
-
const maxFiles = 8;
|
|
80
|
-
const display = state.changedFiles.slice(0, maxFiles);
|
|
81
|
-
const lines = display.map((line) => {
|
|
82
|
-
const trimmed = line.trim();
|
|
83
|
-
// Parse git porcelain status: "M file.ts", "?? file.ts", "A file.ts", etc.
|
|
84
|
-
const match = trimmed.match(/^(\S+)\s+(.+)$/);
|
|
85
|
-
if (!match)
|
|
86
|
-
return ` ${pc.dim(trimmed)}`;
|
|
87
|
-
const status = match[1];
|
|
88
|
-
const file = match[2];
|
|
89
|
-
const label = status === "??"
|
|
90
|
-
? "new"
|
|
91
|
-
: status === "M"
|
|
92
|
-
? "modified"
|
|
93
|
-
: status === "A"
|
|
94
|
-
? "added"
|
|
95
|
-
: status === "D"
|
|
96
|
-
? "deleted"
|
|
97
|
-
: status === "R"
|
|
98
|
-
? "renamed"
|
|
99
|
-
: "changed";
|
|
100
|
-
return ` ${pc.dim(file)} ${pc.dim(`(${label})`)}`;
|
|
101
|
-
});
|
|
102
|
-
if (state.changedFiles.length > maxFiles) {
|
|
103
|
-
lines.push(` ${pc.dim(`... and ${state.changedFiles.length - maxFiles} more`)}`);
|
|
104
|
-
}
|
|
105
|
-
if (isAgent) {
|
|
106
|
-
agentActionRequired({
|
|
107
|
-
type: "git_uncommitted",
|
|
108
|
-
message: "There are uncommitted changes that won't be in the deployment. Ask the user what to do.",
|
|
109
|
-
choices: [
|
|
110
|
-
{ id: "commit_push", label: "Commit and push these changes" },
|
|
111
|
-
{ id: "continue", label: "Continue without committing" },
|
|
112
|
-
],
|
|
113
|
-
instructions: {
|
|
114
|
-
commit_push: "Stage the changes, commit with a descriptive message, and push. Then re-run with the next_command.",
|
|
115
|
-
continue: "Re-run with the next_command without committing.",
|
|
116
|
-
},
|
|
117
|
-
nextCommand: buildNextCommand("inflight share --skip-git-check", opts),
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
lines.push("", pc.yellow("Your deployment won't include these changes."));
|
|
121
|
-
p.log.warn("You have uncommitted changes:\n" + lines.join("\n"));
|
|
122
|
-
const action = await p.select({
|
|
123
|
-
message: "What would you like to do?",
|
|
124
|
-
options: [
|
|
125
|
-
{ value: "commit_push", label: "Commit & push (recommended)" },
|
|
126
|
-
{ value: "continue", label: "Continue anyway — use existing deployment" },
|
|
127
|
-
],
|
|
128
|
-
});
|
|
129
|
-
if (p.isCancel(action)) {
|
|
130
|
-
p.cancel("Cancelled.");
|
|
131
|
-
process.exit(0);
|
|
132
|
-
}
|
|
133
|
-
if (action === "continue") {
|
|
70
|
+
switch (state.status) {
|
|
71
|
+
case "clean":
|
|
72
|
+
case "detached":
|
|
134
73
|
return { justPushed: false };
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
if (!
|
|
143
|
-
return
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
const stderr = e && typeof e === "object" && "stderr" in e ? String(e.stderr).trim() : "";
|
|
159
|
-
spinner.stop("Push failed.");
|
|
160
|
-
if (stderr)
|
|
161
|
-
p.log.message(pc.dim(stderr));
|
|
162
|
-
else if (e instanceof Error)
|
|
163
|
-
p.log.message(pc.dim(e.message));
|
|
164
|
-
p.log.warn("Continuing with existing deployment.");
|
|
165
|
-
return { justPushed: false };
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
if (state.status === "unpushed") {
|
|
169
|
-
// Show unpushed commits
|
|
170
|
-
const commitLines = state.unpushedCommits.slice(0, 5).map((line) => ` ${pc.dim(line)}`);
|
|
171
|
-
if (state.unpushedCommits.length > 5) {
|
|
172
|
-
commitLines.push(` ${pc.dim(`... and ${state.unpushedCommits.length - 5} more`)}`);
|
|
173
|
-
}
|
|
174
|
-
if (isAgent) {
|
|
175
|
-
agentActionRequired({
|
|
176
|
-
type: "git_unpushed",
|
|
177
|
-
message: "There are unpushed commits that won't be in the deployment. Ask the user what to do.",
|
|
178
|
-
choices: [
|
|
179
|
-
{ id: "push", label: "Push these commits" },
|
|
180
|
-
{ id: "continue", label: "Continue without pushing" },
|
|
181
|
-
],
|
|
182
|
-
instructions: {
|
|
183
|
-
push: "Run `git push` to push the commits. Then re-run with the next_command.",
|
|
184
|
-
continue: "Re-run with the next_command without pushing.",
|
|
185
|
-
},
|
|
186
|
-
nextCommand: buildNextCommand("inflight share --skip-git-check", opts),
|
|
74
|
+
case "uncommitted": {
|
|
75
|
+
const { branch, changedFiles } = state;
|
|
76
|
+
const maxFiles = 8;
|
|
77
|
+
const display = changedFiles.slice(0, maxFiles);
|
|
78
|
+
const lines = display.map((line) => {
|
|
79
|
+
const trimmed = line.trim();
|
|
80
|
+
const match = trimmed.match(/^(\S+)\s+(.+)$/);
|
|
81
|
+
if (!match)
|
|
82
|
+
return ` ${pc.dim(trimmed)}`;
|
|
83
|
+
const status = match[1];
|
|
84
|
+
const file = match[2];
|
|
85
|
+
const label = status === "??"
|
|
86
|
+
? "new"
|
|
87
|
+
: status === "M"
|
|
88
|
+
? "modified"
|
|
89
|
+
: status === "A"
|
|
90
|
+
? "added"
|
|
91
|
+
: status === "D"
|
|
92
|
+
? "deleted"
|
|
93
|
+
: status === "R"
|
|
94
|
+
? "renamed"
|
|
95
|
+
: "changed";
|
|
96
|
+
return ` ${pc.dim(file)} ${pc.dim(`(${label})`)}`;
|
|
187
97
|
});
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
const stderr = e && typeof e === "object" && "stderr" in e ? String(e.stderr).trim() : "";
|
|
214
|
-
spinner.stop("Push failed.");
|
|
215
|
-
if (stderr)
|
|
216
|
-
p.log.message(pc.dim(stderr));
|
|
217
|
-
else if (e instanceof Error)
|
|
218
|
-
p.log.message(pc.dim(e.message));
|
|
219
|
-
p.log.warn("Continuing with existing deployment.");
|
|
220
|
-
return { justPushed: false };
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
if (state.status === "no_remote") {
|
|
224
|
-
// Working tree is clean (uncommitted was checked first in getGitSyncState).
|
|
225
|
-
// Check if this branch actually has commits ahead of the default branch —
|
|
226
|
-
// if not, pushing won't trigger a deployment.
|
|
227
|
-
if (!hasCommitsAhead(cwd)) {
|
|
228
|
-
p.log.info(`Branch ${pc.bold(branch)} has no new commits — using existing deployments.`);
|
|
229
|
-
return { justPushed: false };
|
|
230
|
-
}
|
|
231
|
-
if (isAgent) {
|
|
232
|
-
agentActionRequired({
|
|
233
|
-
type: "git_no_remote",
|
|
234
|
-
message: `Branch "${branch}" hasn't been pushed yet — no deployment exists. Ask the user what to do.`,
|
|
235
|
-
choices: [
|
|
236
|
-
{ id: "push", label: "Push to create a deployment" },
|
|
237
|
-
{ id: "continue", label: "Continue without pushing" },
|
|
98
|
+
if (changedFiles.length > maxFiles) {
|
|
99
|
+
lines.push(` ${pc.dim(`... and ${changedFiles.length - maxFiles} more`)}`);
|
|
100
|
+
}
|
|
101
|
+
if (isAgent) {
|
|
102
|
+
agentActionRequired({
|
|
103
|
+
type: "git_uncommitted",
|
|
104
|
+
message: "There are uncommitted changes that won't be in the deployment. Ask the user what to do.",
|
|
105
|
+
choices: [
|
|
106
|
+
{ id: "commit_push", label: "Commit and push these changes" },
|
|
107
|
+
{ id: "continue", label: "Continue without committing" },
|
|
108
|
+
],
|
|
109
|
+
instructions: {
|
|
110
|
+
commit_push: "Stage the changes, commit with a descriptive message, and push. Then re-run with the next_command.",
|
|
111
|
+
continue: "Re-run with the next_command without committing.",
|
|
112
|
+
},
|
|
113
|
+
nextCommand: buildNextCommand("inflight share --skip-git-check", opts),
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
lines.push("", pc.yellow("Your deployment won't include these changes."));
|
|
117
|
+
p.log.warn("You have uncommitted changes:\n" + lines.join("\n"));
|
|
118
|
+
const action = await p.select({
|
|
119
|
+
message: "What would you like to do?",
|
|
120
|
+
options: [
|
|
121
|
+
{ value: "commit_push", label: "Commit & push (recommended)" },
|
|
122
|
+
{ value: "continue", label: "Continue anyway — use existing deployment" },
|
|
238
123
|
],
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
124
|
+
});
|
|
125
|
+
if (p.isCancel(action)) {
|
|
126
|
+
p.cancel("Cancelled.");
|
|
127
|
+
process.exit(0);
|
|
128
|
+
}
|
|
129
|
+
if (action === "continue") {
|
|
130
|
+
return { justPushed: false };
|
|
131
|
+
}
|
|
132
|
+
const defaultMessage = generateCommitMessage(branch);
|
|
133
|
+
const message = await p.text({
|
|
134
|
+
message: "Commit message",
|
|
135
|
+
initialValue: defaultMessage,
|
|
136
|
+
validate: (v) => {
|
|
137
|
+
if (!v || !v.trim())
|
|
138
|
+
return "Commit message is required";
|
|
242
139
|
},
|
|
243
|
-
nextCommand: buildNextCommand("inflight share --skip-git-check", opts),
|
|
244
140
|
});
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
const stderr = e && typeof e === "object" && "stderr" in e ? String(e.stderr).trim() : "";
|
|
266
|
-
spinner.stop("Push failed.");
|
|
267
|
-
if (stderr)
|
|
268
|
-
p.log.message(pc.dim(stderr));
|
|
269
|
-
else if (e instanceof Error)
|
|
270
|
-
p.log.message(pc.dim(e.message));
|
|
271
|
-
return { justPushed: false };
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
return { justPushed: false };
|
|
275
|
-
}
|
|
276
|
-
/**
|
|
277
|
-
* Agent mode share flow.
|
|
278
|
-
* Mirrors every human prompt with action_required JSON.
|
|
279
|
-
* Polling runs normally (no spinners).
|
|
280
|
-
* Git is NOT touched — if there's dirty state, action_required is returned
|
|
281
|
-
* and the agent handles git itself, then re-runs with --skip-git-check.
|
|
282
|
-
*/
|
|
283
|
-
async function agentShareFlow(cwd, apiKey, workspaceId, opts) {
|
|
284
|
-
// ── Git sync — report state, don't touch git ──
|
|
285
|
-
if (!opts.skipGitCheck) {
|
|
286
|
-
// checkAndSyncGit will call agentActionRequired and exit if git is dirty
|
|
287
|
-
await checkAndSyncGit(cwd, opts);
|
|
288
|
-
// If we reach here, git is clean
|
|
289
|
-
}
|
|
290
|
-
const gitInfo = getGitInfo(cwd);
|
|
291
|
-
// ── Resolve staging URL ──
|
|
292
|
-
let stagingUrl;
|
|
293
|
-
if (opts.deployment) {
|
|
294
|
-
// --deployment flag provided
|
|
295
|
-
stagingUrl = opts.deployment;
|
|
296
|
-
if (!stagingUrl.startsWith("http"))
|
|
297
|
-
stagingUrl = `https://${stagingUrl}`;
|
|
298
|
-
}
|
|
299
|
-
else {
|
|
300
|
-
// Determine provider
|
|
301
|
-
let providerId = opts.provider;
|
|
302
|
-
if (!providerId) {
|
|
303
|
-
// Auto-detect from local config files
|
|
304
|
-
const gitRoot = getGitRoot(cwd);
|
|
305
|
-
if (gitRoot && readLocalVercelProject(gitRoot)) {
|
|
306
|
-
providerId = "vercel";
|
|
141
|
+
if (p.isCancel(message)) {
|
|
142
|
+
p.cancel("Cancelled.");
|
|
143
|
+
process.exit(0);
|
|
144
|
+
}
|
|
145
|
+
const spinner = p.spinner();
|
|
146
|
+
spinner.start("Committing and pushing...");
|
|
147
|
+
try {
|
|
148
|
+
commitAndPush(cwd, message, branch);
|
|
149
|
+
spinner.stop("Changes pushed!");
|
|
150
|
+
return { justPushed: true };
|
|
151
|
+
}
|
|
152
|
+
catch (e) {
|
|
153
|
+
const stderr = e && typeof e === "object" && "stderr" in e ? String(e.stderr).trim() : "";
|
|
154
|
+
spinner.stop("Push failed.");
|
|
155
|
+
if (stderr)
|
|
156
|
+
p.log.message(pc.dim(stderr));
|
|
157
|
+
else if (e instanceof Error)
|
|
158
|
+
p.log.message(pc.dim(e.message));
|
|
159
|
+
p.log.warn("Continuing with existing deployment.");
|
|
160
|
+
return { justPushed: false };
|
|
307
161
|
}
|
|
308
|
-
|
|
309
|
-
|
|
162
|
+
}
|
|
163
|
+
case "unpushed": {
|
|
164
|
+
const { branch, unpushedCommits } = state;
|
|
165
|
+
const maxCommits = 5;
|
|
166
|
+
const commitLines = unpushedCommits.slice(0, maxCommits).map((line) => ` ${pc.dim(line)}`);
|
|
167
|
+
if (unpushedCommits.length > maxCommits) {
|
|
168
|
+
commitLines.push(` ${pc.dim(`... and ${unpushedCommits.length - maxCommits} more`)}`);
|
|
310
169
|
}
|
|
311
|
-
|
|
170
|
+
if (isAgent) {
|
|
312
171
|
agentActionRequired({
|
|
313
|
-
type: "
|
|
314
|
-
message: "
|
|
172
|
+
type: "git_unpushed",
|
|
173
|
+
message: "There are unpushed commits that won't be in the deployment. Ask the user what to do.",
|
|
315
174
|
choices: [
|
|
316
|
-
{ id: "
|
|
317
|
-
{ id: "
|
|
175
|
+
{ id: "push", label: "Push these commits" },
|
|
176
|
+
{ id: "continue", label: "Continue without pushing" },
|
|
318
177
|
],
|
|
319
|
-
|
|
178
|
+
instructions: {
|
|
179
|
+
push: "Run `git push` to push the commits. Then re-run with the next_command.",
|
|
180
|
+
continue: "Re-run with the next_command without pushing.",
|
|
181
|
+
},
|
|
182
|
+
nextCommand: buildNextCommand("inflight share --skip-git-check", opts),
|
|
320
183
|
});
|
|
321
184
|
}
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
}
|
|
330
|
-
stagingUrl = (await provider.resolve(cwd, gitInfo, {})) ?? undefined;
|
|
331
|
-
if (stagingUrl && !stagingUrl.startsWith("http")) {
|
|
332
|
-
stagingUrl = `https://${stagingUrl}`;
|
|
333
|
-
}
|
|
334
|
-
if (!stagingUrl) {
|
|
335
|
-
agentError({
|
|
336
|
-
type: "no_deployment",
|
|
337
|
-
message: "Could not find a deployment URL. Provide one with --url.",
|
|
338
|
-
suggestion: "inflight share --url <staging-url>",
|
|
339
|
-
});
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
// At this point stagingUrl is guaranteed set — provider.resolve returned a value or agentError exited
|
|
343
|
-
const resolvedUrl = stagingUrl;
|
|
344
|
-
// ── Resolve project ──
|
|
345
|
-
let selectedProjectId;
|
|
346
|
-
let overrideVersionId;
|
|
347
|
-
if (opts.project) {
|
|
348
|
-
if (opts.project !== "new") {
|
|
349
|
-
selectedProjectId = opts.project;
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
else {
|
|
353
|
-
const { projects } = await apiGetRecentProjects(apiKey, workspaceId, 20).catch(() => ({
|
|
354
|
-
projects: [],
|
|
355
|
-
}));
|
|
356
|
-
if (projects.length > 0) {
|
|
357
|
-
const currentBranch = gitInfo.branch;
|
|
358
|
-
agentActionRequired({
|
|
359
|
-
type: "choose_project",
|
|
360
|
-
message: "Ask the user which project to share into. Present all choices below as a numbered list.",
|
|
361
|
-
choices: [
|
|
362
|
-
{ id: "new", label: "Start a new project" },
|
|
363
|
-
...projects.map((proj) => ({
|
|
364
|
-
id: proj.projectId,
|
|
365
|
-
label: proj.latestVersion.title,
|
|
366
|
-
hint: [
|
|
367
|
-
proj.latestVersion.branch === currentBranch
|
|
368
|
-
? "current branch"
|
|
369
|
-
: proj.latestVersion.branch,
|
|
370
|
-
proj.latestVersion.branch === currentBranch ? "(recommended)" : undefined,
|
|
371
|
-
]
|
|
372
|
-
.filter(Boolean)
|
|
373
|
-
.join(" ") || undefined,
|
|
374
|
-
})),
|
|
185
|
+
commitLines.push("", pc.yellow("Your deployment won't include these commits."));
|
|
186
|
+
p.log.warn("You have unpushed commits:\n" + commitLines.join("\n"));
|
|
187
|
+
const action = await p.select({
|
|
188
|
+
message: "What would you like to do?",
|
|
189
|
+
options: [
|
|
190
|
+
{ value: "push", label: "Push (recommended)" },
|
|
191
|
+
{ value: "continue", label: "Continue anyway — use existing deployment" },
|
|
375
192
|
],
|
|
376
|
-
nextCommand: `inflight share --skip-git-check --deployment ${resolvedUrl} --project <ID>`,
|
|
377
193
|
});
|
|
194
|
+
if (p.isCancel(action)) {
|
|
195
|
+
p.cancel("Cancelled.");
|
|
196
|
+
process.exit(0);
|
|
197
|
+
}
|
|
198
|
+
if (action === "continue") {
|
|
199
|
+
return { justPushed: false };
|
|
200
|
+
}
|
|
201
|
+
const spinner = p.spinner();
|
|
202
|
+
spinner.start("Pushing...");
|
|
203
|
+
try {
|
|
204
|
+
pushBranch(cwd, branch);
|
|
205
|
+
spinner.stop("Changes pushed!");
|
|
206
|
+
return { justPushed: true };
|
|
207
|
+
}
|
|
208
|
+
catch (e) {
|
|
209
|
+
const stderr = e && typeof e === "object" && "stderr" in e ? String(e.stderr).trim() : "";
|
|
210
|
+
spinner.stop("Push failed.");
|
|
211
|
+
if (stderr)
|
|
212
|
+
p.log.message(pc.dim(stderr));
|
|
213
|
+
else if (e instanceof Error)
|
|
214
|
+
p.log.message(pc.dim(e.message));
|
|
215
|
+
p.log.warn("Continuing with existing deployment.");
|
|
216
|
+
return { justPushed: false };
|
|
217
|
+
}
|
|
378
218
|
}
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
},
|
|
396
|
-
{
|
|
397
|
-
|
|
398
|
-
label: "Add a new version",
|
|
399
|
-
hint: "keep both in version history",
|
|
219
|
+
case "no_remote": {
|
|
220
|
+
const { branch } = state;
|
|
221
|
+
if (!hasCommitsAhead(cwd)) {
|
|
222
|
+
if (isAgent) {
|
|
223
|
+
agentError({
|
|
224
|
+
type: "no_changes",
|
|
225
|
+
message: `No changes on branch "${branch}" yet. Make some changes and push before sharing.`,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
p.log.warn("You haven't made any changes on this branch yet. There's nothing to deploy.");
|
|
229
|
+
process.exit(0);
|
|
230
|
+
}
|
|
231
|
+
if (isAgent) {
|
|
232
|
+
agentActionRequired({
|
|
233
|
+
type: "git_no_remote",
|
|
234
|
+
message: `Branch "${branch}" hasn't been pushed yet. Push it to create a deployment.`,
|
|
235
|
+
choices: [{ id: "push", label: "Push to create a deployment" }],
|
|
236
|
+
instructions: {
|
|
237
|
+
push: `Run \`git push -u origin ${branch}\`. Then re-run with the next_command.`,
|
|
400
238
|
},
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
239
|
+
nextCommand: buildNextCommand("inflight share --skip-git-check", opts),
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
p.log.warn("Your changes haven't been pushed yet — there's no deployment to share.");
|
|
243
|
+
const confirm = await p.confirm({
|
|
244
|
+
message: "Push now?",
|
|
407
245
|
});
|
|
246
|
+
if (p.isCancel(confirm) || !confirm) {
|
|
247
|
+
p.cancel("Push your changes first, then run inflight share again.");
|
|
248
|
+
process.exit(0);
|
|
249
|
+
}
|
|
250
|
+
const spinner = p.spinner();
|
|
251
|
+
spinner.start("Pushing...");
|
|
252
|
+
try {
|
|
253
|
+
pushBranch(cwd, branch);
|
|
254
|
+
spinner.stop("Changes pushed!");
|
|
255
|
+
return { justPushed: true };
|
|
256
|
+
}
|
|
257
|
+
catch (e) {
|
|
258
|
+
const stderr = e && typeof e === "object" && "stderr" in e ? String(e.stderr).trim() : "";
|
|
259
|
+
spinner.stop("Push failed.");
|
|
260
|
+
if (stderr)
|
|
261
|
+
p.log.message(pc.dim(stderr));
|
|
262
|
+
else if (e instanceof Error)
|
|
263
|
+
p.log.message(pc.dim(e.message));
|
|
264
|
+
p.log.info("Ask your AI agent to push your changes, then run inflight share again.");
|
|
265
|
+
process.exit(1);
|
|
266
|
+
}
|
|
408
267
|
}
|
|
409
268
|
}
|
|
410
|
-
if (opts.versionMode === "override" && selectedProjectId) {
|
|
411
|
-
const { projects } = await apiGetRecentProjects(apiKey, workspaceId, 20).catch(() => ({
|
|
412
|
-
projects: [],
|
|
413
|
-
}));
|
|
414
|
-
const selectedProject = projects.find((proj) => proj.projectId === selectedProjectId);
|
|
415
|
-
if (selectedProject) {
|
|
416
|
-
overrideVersionId = selectedProject.latestVersion.id;
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
// ── Create version ──
|
|
420
|
-
const result = await apiCreateVersion({
|
|
421
|
-
apiKey,
|
|
422
|
-
workspaceId,
|
|
423
|
-
stagingUrl: resolvedUrl,
|
|
424
|
-
gitInfo,
|
|
425
|
-
...(selectedProjectId && { projectId: selectedProjectId }),
|
|
426
|
-
...(overrideVersionId && { overrideVersionId }),
|
|
427
|
-
}).catch((e) => {
|
|
428
|
-
agentError({ type: "create_failed", message: e.message });
|
|
429
|
-
});
|
|
430
|
-
await open(appendWidgetToken(resolvedUrl, result.widgetToken));
|
|
431
|
-
agentSuccess({
|
|
432
|
-
stagingUrl: resolvedUrl,
|
|
433
|
-
...result,
|
|
434
|
-
isOverride: !!overrideVersionId,
|
|
435
|
-
});
|
|
436
269
|
}
|
|
437
270
|
export async function shareCommand(opts = {}) {
|
|
438
271
|
const cwd = process.cwd();
|
|
439
|
-
// TODO: Add a step to login if not authenticated
|
|
440
272
|
// ── Step 1: Auth ──
|
|
441
273
|
const auth = readGlobalAuth();
|
|
442
274
|
if (!auth) {
|
|
@@ -456,6 +288,14 @@ export async function shareCommand(opts = {}) {
|
|
|
456
288
|
process.exit(1);
|
|
457
289
|
}
|
|
458
290
|
let gitInfo = getGitInfo(cwd);
|
|
291
|
+
const gitRoot = getGitRoot(cwd);
|
|
292
|
+
if (!gitInfo || !gitRoot) {
|
|
293
|
+
if (isAgent) {
|
|
294
|
+
agentError({ type: "not_git_repo", message: "Not a git repository." });
|
|
295
|
+
}
|
|
296
|
+
p.log.error("Not a git repository.");
|
|
297
|
+
process.exit(1);
|
|
298
|
+
}
|
|
459
299
|
// ── Step 2: Resolve workspace ──
|
|
460
300
|
const me = await apiGetMe(auth.apiKey).catch((e) => {
|
|
461
301
|
if (isAgent)
|
|
@@ -472,85 +312,97 @@ export async function shareCommand(opts = {}) {
|
|
|
472
312
|
explicitId: opts.workspace,
|
|
473
313
|
commandForNext: "inflight share",
|
|
474
314
|
});
|
|
475
|
-
// ──
|
|
315
|
+
// ── Step 3: Resolve staging URL ──
|
|
316
|
+
let stagingUrl = null;
|
|
317
|
+
let justPushed = false;
|
|
318
|
+
let provider;
|
|
476
319
|
if (opts.url) {
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
}
|
|
490
|
-
else {
|
|
491
|
-
p.log.error(message);
|
|
320
|
+
// --url flag provided (agent re-invocation or scripting)
|
|
321
|
+
stagingUrl = opts.url;
|
|
322
|
+
}
|
|
323
|
+
else if (opts.provider) {
|
|
324
|
+
// --provider flag explicitly passed — validate it
|
|
325
|
+
provider = providers.find((prov) => prov.id === opts.provider);
|
|
326
|
+
if (!provider) {
|
|
327
|
+
if (isAgent) {
|
|
328
|
+
agentError({
|
|
329
|
+
type: "invalid_provider",
|
|
330
|
+
message: `Unknown provider "${opts.provider}". Use "vercel" or "netlify".`,
|
|
331
|
+
});
|
|
492
332
|
}
|
|
333
|
+
p.log.error(`Unknown provider "${opts.provider}".`);
|
|
493
334
|
process.exit(1);
|
|
494
335
|
}
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
336
|
+
}
|
|
337
|
+
else {
|
|
338
|
+
// No URL or provider — detect or prompt
|
|
339
|
+
let providerId;
|
|
340
|
+
// Auto-detect from local config files
|
|
341
|
+
const hasVercel = !!readLocalVercelProject(gitRoot);
|
|
342
|
+
const hasNetlify = !!readLocalNetlifySite(gitRoot);
|
|
343
|
+
if (hasVercel && !hasNetlify) {
|
|
344
|
+
providerId = "vercel";
|
|
345
|
+
}
|
|
346
|
+
else if (hasNetlify && !hasVercel) {
|
|
347
|
+
providerId = "netlify";
|
|
348
|
+
}
|
|
349
|
+
else if (isAgent) {
|
|
350
|
+
// Both exist (monorepo), neither exists, or can't determine — ask agent
|
|
351
|
+
agentActionRequired({
|
|
352
|
+
type: "choose_provider",
|
|
353
|
+
message: "Could not auto-detect deployment provider. Ask the user which one they use.",
|
|
354
|
+
choices: [
|
|
355
|
+
{ id: "vercel", label: "Vercel" },
|
|
356
|
+
{ id: "netlify", label: "Netlify" },
|
|
357
|
+
{ id: "manual", label: "Paste a URL" },
|
|
358
|
+
],
|
|
359
|
+
instructions: {
|
|
360
|
+
vercel: buildNextCommand("inflight share --skip-git-check --provider vercel", opts),
|
|
361
|
+
netlify: buildNextCommand("inflight share --skip-git-check --provider netlify", opts),
|
|
362
|
+
manual: "Ask the user for their staging URL, then re-run with: inflight share --url <staging-url>",
|
|
363
|
+
},
|
|
364
|
+
nextCommand: buildNextCommand("inflight share --skip-git-check --provider <ID>", opts),
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
else {
|
|
368
|
+
// Can't auto-detect — prompt user
|
|
369
|
+
const providerChoice = await p.select({
|
|
370
|
+
message: "Where is your staging URL hosted?",
|
|
371
|
+
options: [
|
|
372
|
+
...providers.map((prov) => ({ value: prov.id, label: prov.label })),
|
|
373
|
+
{ value: "manual", label: "Paste a URL" },
|
|
374
|
+
],
|
|
375
|
+
});
|
|
376
|
+
if (p.isCancel(providerChoice)) {
|
|
377
|
+
p.cancel("Cancelled.");
|
|
378
|
+
process.exit(0);
|
|
506
379
|
}
|
|
507
|
-
|
|
508
|
-
|
|
380
|
+
if (providerChoice !== "manual") {
|
|
381
|
+
providerId = providerChoice;
|
|
509
382
|
}
|
|
510
|
-
process.exit(1);
|
|
511
|
-
});
|
|
512
|
-
if (isAgent) {
|
|
513
|
-
await open(appendWidgetToken(stagingUrl, result.widgetToken));
|
|
514
|
-
agentSuccess({ stagingUrl, ...result });
|
|
515
383
|
}
|
|
516
|
-
if (
|
|
517
|
-
|
|
518
|
-
}
|
|
519
|
-
else {
|
|
520
|
-
p.log.info(`Staging URL: ${pc.cyan(stagingUrl)}`);
|
|
521
|
-
p.outro(pc.green("✓ Inflight added to your staging URL") + " — opening in browser...");
|
|
384
|
+
if (providerId) {
|
|
385
|
+
provider = providers.find((prov) => prov.id === providerId);
|
|
522
386
|
}
|
|
523
|
-
await open(appendWidgetToken(stagingUrl, result.widgetToken));
|
|
524
|
-
return;
|
|
525
|
-
}
|
|
526
|
-
// ── Agent mode: structured flow with action_required for every choice ──
|
|
527
|
-
if (isAgent) {
|
|
528
|
-
await agentShareFlow(cwd, auth.apiKey, workspaceId, opts);
|
|
529
|
-
return;
|
|
530
|
-
}
|
|
531
|
-
// Resolve staging URL
|
|
532
|
-
const providerChoice = await p.select({
|
|
533
|
-
message: "Where is your staging URL hosted?",
|
|
534
|
-
options: [
|
|
535
|
-
...providers.map((prov) => ({ value: prov.id, label: prov.label })),
|
|
536
|
-
{ value: "manual", label: "Paste a URL" },
|
|
537
|
-
],
|
|
538
|
-
});
|
|
539
|
-
if (p.isCancel(providerChoice)) {
|
|
540
|
-
p.cancel("Cancelled.");
|
|
541
|
-
process.exit(0);
|
|
542
387
|
}
|
|
543
|
-
|
|
544
|
-
const provider = providers.find((prov) => prov.id === providerChoice);
|
|
388
|
+
// Resolve URL from provider
|
|
545
389
|
if (provider) {
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
390
|
+
if (!opts.skipGitCheck) {
|
|
391
|
+
({ justPushed } = await checkAndSyncGit(cwd, opts));
|
|
392
|
+
if (justPushed) {
|
|
393
|
+
gitInfo = getGitInfo(cwd) ?? gitInfo;
|
|
394
|
+
}
|
|
550
395
|
}
|
|
551
|
-
stagingUrl =
|
|
396
|
+
stagingUrl = await provider.resolve(gitRoot, gitInfo, { justPushed });
|
|
552
397
|
}
|
|
553
398
|
if (!stagingUrl) {
|
|
399
|
+
if (isAgent) {
|
|
400
|
+
agentError({
|
|
401
|
+
type: "no_deployment",
|
|
402
|
+
message: "Could not find a deployment URL. Provide one with --url.",
|
|
403
|
+
suggestion: "inflight share --url <staging-url>",
|
|
404
|
+
});
|
|
405
|
+
}
|
|
554
406
|
const input = await p.text({
|
|
555
407
|
message: "Staging URL",
|
|
556
408
|
placeholder: "my-branch.vercel.app",
|
|
@@ -582,68 +434,127 @@ export async function shareCommand(opts = {}) {
|
|
|
582
434
|
// ── Step 4: Project selection ──
|
|
583
435
|
let selectedProjectId;
|
|
584
436
|
let overrideVersionId;
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
:
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
437
|
+
if (opts.project) {
|
|
438
|
+
// --project flag provided (agent re-invocation)
|
|
439
|
+
if (opts.project !== "new") {
|
|
440
|
+
selectedProjectId = opts.project;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
else {
|
|
444
|
+
const { projects } = await apiGetRecentProjects(auth.apiKey, workspaceId, 20).catch(() => ({
|
|
445
|
+
projects: [],
|
|
446
|
+
}));
|
|
447
|
+
if (projects.length > 0) {
|
|
448
|
+
const branchMatches = gitInfo.branch
|
|
449
|
+
? projects.filter((proj) => proj.latestVersion.branch === gitInfo.branch)
|
|
450
|
+
: [];
|
|
451
|
+
const rest = projects.filter((proj) => !branchMatches.includes(proj));
|
|
452
|
+
const sorted = [...branchMatches, ...rest];
|
|
453
|
+
if (isAgent) {
|
|
454
|
+
agentActionRequired({
|
|
455
|
+
type: "choose_project",
|
|
456
|
+
message: projects.length > 5
|
|
457
|
+
? "Ask the user which project to share into. Show the first 5 options. If the user wants more, show the rest."
|
|
458
|
+
: "Ask the user which project to share into.",
|
|
459
|
+
choices: [
|
|
460
|
+
{ id: "new", label: "Start a new project" },
|
|
461
|
+
...sorted.map((proj) => ({
|
|
462
|
+
id: proj.projectId,
|
|
463
|
+
label: proj.latestVersion.title,
|
|
464
|
+
hint: branchMatches.includes(proj)
|
|
465
|
+
? `${formatRelativeTime(proj.latestVersion.createdAt)} · current branch (${gitInfo.branch})`
|
|
466
|
+
: formatRelativeTime(proj.latestVersion.createdAt),
|
|
467
|
+
})),
|
|
468
|
+
],
|
|
469
|
+
nextCommand: `inflight share --skip-git-check --url ${stagingUrl} --project <ID>`,
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
const firstBranchMatch = branchMatches[0];
|
|
473
|
+
const projectChoice = await scrollableSelect({
|
|
474
|
+
message: "Create a new project in Inflight, or update an existing one?",
|
|
475
|
+
maxItems: 5,
|
|
476
|
+
...(firstBranchMatch && { initialValue: firstBranchMatch.projectId }),
|
|
477
|
+
options: [
|
|
478
|
+
{ value: "new", label: "Start a new project" },
|
|
479
|
+
...sorted.map((proj) => ({
|
|
480
|
+
value: proj.projectId,
|
|
481
|
+
label: `"${proj.latestVersion.title}"`,
|
|
482
|
+
hint: branchMatches.includes(proj)
|
|
483
|
+
? `${formatRelativeTime(proj.latestVersion.createdAt)} · current branch (${gitInfo.branch})`
|
|
484
|
+
: formatRelativeTime(proj.latestVersion.createdAt),
|
|
485
|
+
})),
|
|
486
|
+
],
|
|
487
|
+
});
|
|
488
|
+
if (p.isCancel(projectChoice)) {
|
|
489
|
+
p.cancel("Cancelled.");
|
|
490
|
+
process.exit(0);
|
|
491
|
+
}
|
|
492
|
+
if (projectChoice !== "new") {
|
|
493
|
+
selectedProjectId = projectChoice;
|
|
494
|
+
}
|
|
616
495
|
}
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
496
|
+
}
|
|
497
|
+
// ── Step 5: Override check ──
|
|
498
|
+
if (selectedProjectId && opts.versionMode !== "new") {
|
|
499
|
+
const { projects } = await apiGetRecentProjects(auth.apiKey, workspaceId, 20).catch(() => ({
|
|
500
|
+
projects: [],
|
|
501
|
+
}));
|
|
502
|
+
const selectedProject = projects.find((proj) => proj.projectId === selectedProjectId);
|
|
503
|
+
if (opts.versionMode === "override") {
|
|
504
|
+
if (selectedProject) {
|
|
505
|
+
overrideVersionId = selectedProject.latestVersion.id;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
else if (selectedProject && selectedProject.latestVersion.commentCount === 0) {
|
|
509
|
+
// No explicit mode and latest version has no feedback — ask
|
|
510
|
+
if (isAgent) {
|
|
511
|
+
agentActionRequired({
|
|
512
|
+
type: "choose_override",
|
|
513
|
+
message: `"${selectedProject.latestVersion.title}" has no feedback yet. Ask the user whether to update it or create a new version.`,
|
|
514
|
+
choices: [
|
|
625
515
|
{
|
|
626
|
-
|
|
516
|
+
id: "override",
|
|
627
517
|
label: "Update its staging URL",
|
|
628
518
|
hint: `replace with ${new URL(stagingUrl).hostname}`,
|
|
629
519
|
},
|
|
630
520
|
{
|
|
631
|
-
|
|
521
|
+
id: "new_version",
|
|
632
522
|
label: "Add a new version",
|
|
633
523
|
hint: "keep both in version history",
|
|
634
524
|
},
|
|
635
525
|
],
|
|
526
|
+
instructions: {
|
|
527
|
+
override: `Re-run with: inflight share --skip-git-check --url ${stagingUrl} --project ${selectedProjectId} --version-mode override`,
|
|
528
|
+
new_version: `Re-run with: inflight share --skip-git-check --url ${stagingUrl} --project ${selectedProjectId} --version-mode new`,
|
|
529
|
+
},
|
|
530
|
+
nextCommand: `inflight share --skip-git-check --url ${stagingUrl} --project ${selectedProjectId} --version-mode <override|new>`,
|
|
636
531
|
});
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
532
|
+
}
|
|
533
|
+
const overrideChoice = await p.select({
|
|
534
|
+
message: `"${selectedProject.latestVersion.title}" has no feedback yet.`,
|
|
535
|
+
options: [
|
|
536
|
+
{
|
|
537
|
+
value: "override",
|
|
538
|
+
label: "Update its staging URL",
|
|
539
|
+
hint: `replace with ${new URL(stagingUrl).hostname}`,
|
|
540
|
+
},
|
|
541
|
+
{
|
|
542
|
+
value: "new_version",
|
|
543
|
+
label: "Add a new version",
|
|
544
|
+
hint: "keep both in version history",
|
|
545
|
+
},
|
|
546
|
+
],
|
|
547
|
+
});
|
|
548
|
+
if (p.isCancel(overrideChoice)) {
|
|
549
|
+
p.cancel("Cancelled.");
|
|
550
|
+
process.exit(0);
|
|
551
|
+
}
|
|
552
|
+
if (overrideChoice === "override") {
|
|
553
|
+
overrideVersionId = selectedProject.latestVersion.id;
|
|
644
554
|
}
|
|
645
555
|
}
|
|
646
556
|
}
|
|
557
|
+
// ── Step 6: Create version ──
|
|
647
558
|
const result = await apiCreateVersion({
|
|
648
559
|
apiKey: auth.apiKey,
|
|
649
560
|
workspaceId,
|
|
@@ -652,9 +563,19 @@ export async function shareCommand(opts = {}) {
|
|
|
652
563
|
...(selectedProjectId && { projectId: selectedProjectId }),
|
|
653
564
|
...(overrideVersionId && { overrideVersionId }),
|
|
654
565
|
}).catch((e) => {
|
|
566
|
+
if (isAgent)
|
|
567
|
+
agentError({ type: "create_failed", message: e.message });
|
|
655
568
|
p.log.error(e.message);
|
|
656
569
|
process.exit(1);
|
|
657
570
|
});
|
|
571
|
+
await open(appendWidgetToken(stagingUrl, result.widgetToken));
|
|
572
|
+
if (isAgent) {
|
|
573
|
+
agentSuccess({
|
|
574
|
+
stagingUrl,
|
|
575
|
+
...result,
|
|
576
|
+
isOverride: !!overrideVersionId,
|
|
577
|
+
});
|
|
578
|
+
}
|
|
658
579
|
p.log.info(`Staging URL: ${pc.cyan(stagingUrl)}`);
|
|
659
580
|
if (overrideVersionId) {
|
|
660
581
|
p.outro(pc.green("✓ Staging URL updated") + " — opening in browser...");
|
|
@@ -662,5 +583,4 @@ export async function shareCommand(opts = {}) {
|
|
|
662
583
|
else {
|
|
663
584
|
p.outro(pc.green("✓ Inflight added to your staging URL") + " — opening in browser...");
|
|
664
585
|
}
|
|
665
|
-
await open(appendWidgetToken(stagingUrl, result.widgetToken));
|
|
666
586
|
}
|