inflight-cli 2.1.0 → 2.1.1

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/README.md CHANGED
@@ -16,8 +16,7 @@ That's it. The CLI installs globally, logs you in, adds the widget to your proje
16
16
  | --------------------- | -------------------------------- |
17
17
  | `inflight setup` | Set up Inflight in your project |
18
18
  | `inflight share` | Share a staging URL for feedback |
19
- | `inflight vercel` | Change your Vercel project |
20
- | `inflight workspaces` | Change your Inflight workspace |
19
+ | `inflight workspace` | Get or set your active workspace |
21
20
  | `inflight login` | Log in to your Inflight account |
22
21
  | `inflight logout` | Log out of your Inflight account |
23
22
 
@@ -55,7 +55,7 @@ export async function setupCommand() {
55
55
  const existingWorkspace = existingConfig ? workspaces.find((w) => w.id === existingConfig.workspaceId) : null;
56
56
  if (existingWorkspace) {
57
57
  workspaceId = existingWorkspace.id;
58
- p.log.success(`Workspace: ${pc.bold(existingWorkspace.name)} ${pc.dim("(change anytime with inflight workspaces)")}`);
58
+ p.log.success(`Workspace: ${pc.bold(existingWorkspace.name)} ${pc.dim("(change anytime with inflight workspace)")}`);
59
59
  }
60
60
  else if (workspaces.length === 0) {
61
61
  p.log.error("No workspaces found. Create one at " + pc.cyan("inflight.co") + " first.");
@@ -67,7 +67,7 @@ export async function setupCommand() {
67
67
  }
68
68
  else {
69
69
  const selected = await p.select({
70
- message: "Select a workspace " + pc.dim("(change anytime with inflight workspaces)"),
70
+ message: "Select a workspace " + pc.dim("(change anytime with inflight workspace)"),
71
71
  options: workspaces.map((w) => ({ value: w.id, label: w.name })),
72
72
  });
73
73
  if (p.isCancel(selected)) {
@@ -7,6 +7,7 @@ import { providers } from "../providers/index.js";
7
7
  import { apiGetMe, apiCreateVersion } from "../lib/api.js";
8
8
  export async function shareCommand(opts = {}) {
9
9
  const cwd = process.cwd();
10
+ // ── Step 1: Auth ──
10
11
  const auth = readGlobalAuth();
11
12
  if (!auth) {
12
13
  if (opts.json) {
@@ -48,37 +49,41 @@ export async function shareCommand(opts = {}) {
48
49
  await open(stagingUrl);
49
50
  return;
50
51
  }
51
- // ── Interactive path: prompt for missing inputs ──
52
- // Resolve workspace
53
- let workspaceId = opts.workspace ?? readWorkspaceConfig()?.workspaceId;
54
- if (!workspaceId) {
55
- const me = await apiGetMe(auth.apiKey).catch((e) => {
56
- p.log.error(e.message);
57
- process.exit(1);
52
+ // ── Step 2: Get workspaces ──
53
+ const me = await apiGetMe(auth.apiKey).catch((e) => {
54
+ p.log.error(e.message);
55
+ process.exit(1);
56
+ });
57
+ const workspaces = me.workspaces;
58
+ let workspaceId;
59
+ // Check if a workspace is already configured and still valid
60
+ const existingConfig = readWorkspaceConfig();
61
+ const existingWorkspace = existingConfig ? workspaces.find((w) => w.id === existingConfig.workspaceId) : null;
62
+ if (existingWorkspace) {
63
+ workspaceId = existingWorkspace.id;
64
+ }
65
+ else if (workspaces.length === 0) {
66
+ p.log.error("No workspaces found. Create one at " + pc.cyan("inflight.co") + " first.");
67
+ process.exit(1);
68
+ }
69
+ else if (me.workspaces.length === 1) {
70
+ workspaceId = me.workspaces[0].id;
71
+ p.log.success(`Workspace: ${pc.bold(workspaces[0].name)}`);
72
+ }
73
+ else {
74
+ const selected = await p.select({
75
+ message: "Select a workspace " + pc.dim("(change anytime with inflight workspace)"),
76
+ options: me.workspaces.map((w) => ({ value: w.id, label: w.name })),
58
77
  });
59
- if (me.workspaces.length === 0) {
60
- p.log.error("No workspaces found. Create one at " + pc.cyan("inflight.co") + " first.");
61
- process.exit(1);
62
- }
63
- else if (me.workspaces.length === 1) {
64
- workspaceId = me.workspaces[0].id;
65
- p.log.info(`Workspace: ${pc.bold(me.workspaces[0].name)}`);
66
- }
67
- else {
68
- const selected = await p.select({
69
- message: "Select a workspace " + pc.dim("(change anytime with inflight workspaces)"),
70
- options: me.workspaces.map((w) => ({ value: w.id, label: w.name })),
71
- });
72
- if (p.isCancel(selected)) {
73
- p.cancel("Cancelled.");
74
- process.exit(0);
75
- }
76
- workspaceId = selected;
78
+ if (p.isCancel(selected)) {
79
+ p.cancel("Cancelled.");
80
+ process.exit(0);
77
81
  }
78
- writeWorkspaceConfig({ workspaceId });
82
+ workspaceId = selected;
79
83
  }
84
+ writeWorkspaceConfig({ workspaceId });
80
85
  if (!workspaceId) {
81
- p.log.error("No workspace configured. Run " + pc.cyan("inflight workspaces") + " or " + pc.cyan("inflight setup") + ".");
86
+ p.log.error("No workspace configured. Run " + pc.cyan("inflight workspace") + " or " + pc.cyan("inflight setup") + ".");
82
87
  process.exit(1);
83
88
  }
84
89
  // Resolve staging URL
@@ -1,128 +1,68 @@
1
- import * as p from "@clack/prompts";
2
- import pc from "picocolors";
3
- import { readVercelConfig, writeVercelConfig } from "../lib/config.js";
4
- import { ensureVercelCli, ensureVercelAuth, getVercelToken, getVercelTeams, getVercelProjects, getRecentDeployments, getBranchAlias, } from "../lib/vercel.js";
5
- import { pickVercelProject } from "../providers/vercel.js";
1
+ import { readVercelConfig } from "../lib/config.js";
2
+ import { getGitInfo, parseGitRepo, getGitRoot } from "../lib/git.js";
3
+ import { getVercelToken, getRecentDeployments, getBranchAlias, readLocalVercelProject, fetchAllProjectsWithLinks, matchProjectsByRepo, } from "../lib/vercel.js";
6
4
  // --- Action handlers ---
7
- async function vercelSetup(opts) {
8
- // Fast path: non-interactive set (for agents)
9
- if (opts.team && opts.project) {
10
- const token = requireVercelToken();
11
- const teams = await getVercelTeams(token);
12
- const team = teams.find((t) => t.id === opts.team);
13
- if (!team) {
14
- const msg = `Team '${opts.team}' not found. Available: ${teams.map((t) => `${t.name} (${t.id})`).join(", ")}`;
15
- if (opts.json) {
16
- console.log(JSON.stringify({ error: "team_not_found", message: msg }));
17
- }
18
- else {
19
- p.log.error(msg);
20
- }
21
- process.exit(1);
22
- }
23
- const projects = await getVercelProjects(token, opts.team);
24
- const project = projects.find((proj) => proj.id === opts.project);
25
- if (!project) {
26
- const msg = `Project '${opts.project}' not found on team '${team.name}'.`;
27
- if (opts.json) {
28
- console.log(JSON.stringify({ error: "project_not_found", message: msg }));
29
- }
30
- else {
31
- p.log.error(msg);
32
- }
33
- process.exit(1);
34
- }
35
- writeVercelConfig({ teamId: team.id, teamName: team.name, projectId: project.id, projectName: project.name });
36
- if (opts.json) {
37
- console.log(JSON.stringify({
38
- saved: true,
39
- teamId: team.id,
40
- teamName: team.name,
41
- projectId: project.id,
42
- projectName: project.name,
43
- }));
44
- }
45
- else {
46
- p.log.success(`Saved! Using ${pc.bold(project.name)} on ${pc.bold(team.name)}.`);
47
- }
48
- return;
49
- }
50
- // Interactive path
51
- const cliOk = await ensureVercelCli((msg) => p.log.step(msg));
52
- if (!cliOk) {
53
- p.log.error("Failed to install Vercel CLI. Install manually: " + pc.cyan("npm install -g vercel"));
54
- process.exit(1);
55
- }
56
- p.log.step("Checking Vercel authentication...");
57
- const token = await ensureVercelAuth();
58
- if (!token) {
59
- p.log.error("Vercel login failed.");
60
- process.exit(1);
61
- }
62
- const current = readVercelConfig();
63
- if (current) {
64
- p.log.info(`Current: ${pc.bold(current.projectName)} on ${pc.bold(current.teamName)}`);
65
- }
66
- const config = await pickVercelProject(token);
67
- if (!config)
68
- process.exit(1);
69
- p.log.success(`Saved! ${pc.cyan("inflight share")} will now use ${pc.bold(config.projectName)} on ${pc.bold(config.teamName)}.`);
70
- }
71
- async function listTeams() {
72
- const token = requireVercelToken();
73
- const teams = await getVercelTeams(token);
74
- console.log(JSON.stringify(teams));
75
- }
76
- async function listProjects(opts) {
5
+ async function listProjects() {
77
6
  const token = requireVercelToken();
78
- const projects = await getVercelProjects(token, opts.team);
79
- console.log(JSON.stringify(projects));
7
+ const projects = await fetchAllProjectsWithLinks(token);
8
+ console.log(JSON.stringify(projects.map((p) => ({ id: p.id, name: p.name, teamId: p.teamId, teamName: p.teamName }))));
80
9
  }
81
- async function listDeployments(opts) {
10
+ /**
11
+ * Resolves teamId + projectId for subcommands.
12
+ * Priority: explicit flags → .vercel/project.json → git remote match → saved global config → error
13
+ */
14
+ async function resolveProject(opts) {
15
+ if (opts.team && opts.project) {
16
+ return { teamId: opts.team, projectId: opts.project };
17
+ }
82
18
  const token = requireVercelToken();
83
- let teamId = opts.team;
84
- let projectId = opts.project;
85
- if (!teamId || !projectId) {
86
- const config = readVercelConfig();
87
- if (!config) {
88
- console.log(JSON.stringify({
89
- error: "vercel_not_configured",
90
- message: "No Vercel project configured. Run 'inflight vercel --team=TEAM_ID --project=PROJECT_ID --json' first.",
91
- }));
92
- process.exit(1);
19
+ const cwd = process.cwd();
20
+ const gitRoot = getGitRoot(cwd);
21
+ if (gitRoot) {
22
+ const local = readLocalVercelProject(gitRoot);
23
+ if (local)
24
+ return { teamId: local.orgId, projectId: local.projectId };
25
+ }
26
+ const gitInfo = getGitInfo(cwd);
27
+ if (gitInfo.remoteUrl) {
28
+ const gitRepo = parseGitRepo(gitInfo.remoteUrl);
29
+ if (gitRepo) {
30
+ try {
31
+ const allProjects = await fetchAllProjectsWithLinks(token);
32
+ const matches = matchProjectsByRepo(allProjects, gitRepo.owner, gitRepo.name);
33
+ if (matches.length === 1) {
34
+ return { teamId: matches[0].teamId, projectId: matches[0].id };
35
+ }
36
+ }
37
+ catch { }
93
38
  }
94
- teamId = teamId ?? config.teamId;
95
- projectId = projectId ?? config.projectId;
96
39
  }
97
- const deployments = await getRecentDeployments(token, teamId, projectId, {
98
- limit: parseInt(opts.limit ?? "10"),
99
- branch: opts.branch,
100
- });
101
- console.log(JSON.stringify(deployments));
40
+ const config = readVercelConfig();
41
+ if (config)
42
+ return { teamId: config.teamId, projectId: config.projectId };
43
+ console.log(JSON.stringify({
44
+ error: "vercel_not_configured",
45
+ message: "Could not auto-detect Vercel project. Run 'inflight vercel projects' to list available projects, then pass --team and --project to 'inflight vercel deployments'.",
46
+ }));
47
+ process.exit(1);
102
48
  }
103
- async function branchUrl(opts) {
49
+ async function listDeployments(opts) {
104
50
  const token = requireVercelToken();
105
- let teamId = opts.team;
106
- let projectId = opts.project;
107
- if (!teamId || !projectId) {
108
- const config = readVercelConfig();
109
- if (!config) {
110
- console.log(JSON.stringify({
111
- error: "vercel_not_configured",
112
- message: "No Vercel project configured. Run 'inflight vercel --team=TEAM_ID --project=PROJECT_ID --json' first.",
113
- }));
114
- process.exit(1);
115
- }
116
- teamId = teamId ?? config.teamId;
117
- projectId = projectId ?? config.projectId;
118
- }
119
- const alias = await getBranchAlias(token, teamId, projectId, opts.branch);
120
- if (alias) {
121
- console.log(JSON.stringify({ url: alias.url, state: alias.state, branch: opts.branch }));
122
- }
123
- else {
124
- console.log(JSON.stringify({ url: null, branch: opts.branch, message: "No deployment found for this branch." }));
125
- }
51
+ const { teamId, projectId } = await resolveProject(opts);
52
+ const cwd = process.cwd();
53
+ const gitInfo = getGitInfo(cwd);
54
+ const currentBranch = gitInfo.branch;
55
+ const [deployments, branchAlias] = await Promise.all([
56
+ getRecentDeployments(token, teamId, projectId, {
57
+ limit: parseInt(opts.limit ?? "10"),
58
+ branch: opts.branch,
59
+ }),
60
+ currentBranch ? getBranchAlias(token, teamId, projectId, currentBranch) : Promise.resolve(null),
61
+ ]);
62
+ console.log(JSON.stringify({
63
+ branchAlias: branchAlias ? { url: branchAlias.url, state: branchAlias.state, branch: currentBranch } : null,
64
+ deployments,
65
+ }));
126
66
  }
127
67
  /** Gets token silently, exits with JSON error if unavailable. */
128
68
  function requireVercelToken() {
@@ -130,7 +70,7 @@ function requireVercelToken() {
130
70
  if (!token) {
131
71
  console.log(JSON.stringify({
132
72
  error: "vercel_not_authenticated",
133
- message: "Vercel auth expired or missing. Run 'inflight vercel' or 'vercel login' first.",
73
+ message: "Vercel auth expired or missing. Run 'vercel login' first.",
134
74
  }));
135
75
  process.exit(1);
136
76
  }
@@ -138,33 +78,14 @@ function requireVercelToken() {
138
78
  }
139
79
  // --- Command registration ---
140
80
  export function registerVercelCommand(program) {
141
- const vercel = program
142
- .command("vercel")
143
- .description("Set up or change your Vercel project")
144
- .passThroughOptions()
145
- .option("--team <id>", "Vercel team ID (non-interactive)")
146
- .option("--project <id>", "Vercel project ID (non-interactive)")
147
- .option("--json", "Output as JSON (non-interactive)")
148
- .action(vercelSetup);
149
- vercel.command("teams").description("List Vercel teams (JSON)").action(listTeams);
150
- vercel
151
- .command("projects")
152
- .description("List projects for a Vercel team (JSON)")
153
- .requiredOption("--team <id>", "Vercel team ID")
154
- .action(listProjects);
155
- vercel
156
- .command("branch-url")
157
- .description("Get the stable branch preview URL (JSON)")
158
- .requiredOption("--branch <name>", "Git branch name")
159
- .option("--team <id>", "Vercel team ID (reads from saved config if omitted)")
160
- .option("--project <id>", "Vercel project ID (reads from saved config if omitted)")
161
- .action(branchUrl);
81
+ const vercel = program.command("vercel").description("Vercel integration commands");
82
+ vercel.command("projects").description("List all Vercel projects (JSON)").action(listProjects);
162
83
  vercel
163
84
  .command("deployments")
164
- .description("List recent deployments (JSON)")
165
- .option("--team <id>", "Vercel team ID (reads from saved config if omitted)")
166
- .option("--project <id>", "Vercel project ID (reads from saved config if omitted)")
167
- .option("--branch <name>", "Filter by git branch")
85
+ .description("List recent deployments + branch alias (JSON)")
86
+ .option("--team <id>", "Vercel team ID (auto-detected if omitted)")
87
+ .option("--project <id>", "Vercel project ID (auto-detected if omitted)")
88
+ .option("--branch <name>", "Filter deployments by branch")
168
89
  .option("--limit <n>", "Number of deployments", "10")
169
90
  .action(listDeployments);
170
91
  }
package/dist/index.js CHANGED
@@ -12,7 +12,11 @@ import pkg from "../package.json" with { type: "json" };
12
12
  const { version } = pkg;
13
13
  updateNotifier({ pkg }).notify();
14
14
  const program = new Command();
15
- program.name("inflight").description("Get feedback directly on your staging URL").version(version).enablePositionalOptions();
15
+ program
16
+ .name("inflight")
17
+ .description("Get feedback directly on your staging URL")
18
+ .version(version)
19
+ .enablePositionalOptions();
16
20
  program.command("setup").description("Set up Inflight in your project").action(setupCommand);
17
21
  program.command("login").description("Authenticate with your Inflight account").action(loginCommand);
18
22
  program
@@ -22,16 +26,9 @@ program
22
26
  .option("--workspace <id>", "Workspace ID (skips workspace selection)")
23
27
  .option("--json", "Output result as JSON")
24
28
  .action((opts) => shareCommand(opts));
25
- // program
26
- // .command("preview")
27
- // .description("Preview a live component from your code")
28
- // .option("-m, --message <message>", "Pre-fill the intent prompt")
29
- // .option("--scope <mode>", "Skip scope prompt: branch, uncommitted, staged")
30
- // .option("--no-open", "Don't open result in browser")
31
- // .action((opts) => previewCommand(opts));
32
29
  program
33
- .command("workspaces")
34
- .description("List, select, or set your workspaces")
30
+ .command("workspace")
31
+ .description("Get or set your active workspace")
35
32
  .option("--json", "Output as JSON")
36
33
  .option("--set <id>", "Set the active workspace")
37
34
  .action((opts) => workspacesCommand(opts));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "inflight-cli",
3
- "version": "2.1.0",
3
+ "version": "2.1.1",
4
4
  "description": "Get feedback directly on your staging URL",
5
5
  "bin": {
6
6
  "inflight": "dist/index.js",