Skip to content

Commit b9846dc

Browse files
tonypan2claude
andauthored
fix(cli): Require product slug for multi-product integrations in non-TTY (#15047)
Fixes: [MKT-2775](https://linear.app/vercel/issue/MKT-2775/remove-interactive-product-selection-from-cli) ## Summary - In the auto-provision path (`add-auto-provision.ts`), require a product slug for multi-product integrations in **non-TTY** mode (CI, agents) — error with actionable guidance listing available products in slash syntax - In **TTY** mode (human terminal), keep the interactive product selector so humans can still pick interactively - Legacy `add` path is unchanged ### Non-TTY (agents/CI): actionable error ``` $ echo "" | FF_AUTO_PROVISION_INSTALL=1 vc integration add upstash Error: Integration "upstash" has multiple products. Specify one with: upstash/upstash-qstash upstash/upstash-search upstash/upstash-vector upstash/upstash-kv Example: vercel integration add upstash/upstash-qstash ``` ### TTY (humans): interactive selector preserved ``` $ FF_AUTO_PROVISION_INSTALL=1 vc integration add upstash ? Select a product (Use arrow keys) ❯ Upstash QStash/Workflow Upstash Search Upstash Vector Upstash for Redis ``` ## Test plan ### Unit tests ``` $ cd packages/cli && pnpm vitest run test/unit/commands/integration/add-auto-provision.test.ts ✓ test/unit/commands/integration/add-auto-provision.test.ts (60 tests) 3535ms Test Files 1 passed (1) Tests 60 passed (60) ``` ### Manual testing | Scenario | Command | Expected | Actual | |----------|---------|----------|--------| | FF=1, multi-product, no slug, TTY | `FF_AUTO_PROVISION_INSTALL=1 vc integration add upstash` | Interactive product selector | ✅ `Select a product` prompt shown with 4 products | | FF=1, multi-product, no slug, non-TTY | `echo "" \| FF_AUTO_PROVISION_INSTALL=1 vc integration add upstash` | Error with product list | ✅ Error with slash syntax choices | | FF=1, multi-product, slash syntax | `FF_AUTO_PROVISION_INSTALL=1 vc integration add upstash/upstash-kv` | Selects product, proceeds | ✅ `Installing Upstash for Redis by Upstash...` | ### Code paths covered | Path | Verified by | |------|------------| | Auto-provision: multiple products + no slug + non-TTY → error | Unit + manual | | Auto-provision: multiple products + no slug + TTY → interactive selector | Unit + manual | | Auto-provision: multiple products + slash syntax → direct select | Unit + manual | | Auto-provision: single product + no slug → auto-select | Unit | | Legacy: multiple products → interactive prompt (unchanged) | Unit | 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- VADE_RISK_START --> > [!NOTE] > Low Risk Change > > This PR adds a defensive check that rejects multi-product integrations in non-TTY mode when no product slug is specified, requiring explicit user input rather than allowing ambiguous behavior. > > - Adds non-TTY check to error out when multiple products exist without explicit slug > - Error message lists available products in slash syntax format > - Test updates verify new non-TTY error behavior > > <sup>Risk assessment for [commit 4acdc8d](https://github.com/vercel/vercel/commit/4acdc8d5993ad0ad990d1ea935adff4d1c4a1b3b).</sup> <!-- VADE_RISK_END --> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 4633951 commit b9846dc

3 files changed

Lines changed: 35 additions & 8 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"vercel": patch
3+
---
4+
5+
Require slash syntax for multi-product integrations in non-TTY mode, keep interactive product selector for TTY

packages/cli/src/commands/integration/add-auto-provision.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,21 @@ export async function addAutoProvision(
7070
return 1;
7171
}
7272

73-
// 3. Select product (by slug, single auto-select, or interactive prompt)
73+
// 3. Select product (by slug, single auto-select, or interactive prompt in TTY)
74+
if (
75+
!options.productSlug &&
76+
integration.products.length > 1 &&
77+
!client.stdin.isTTY
78+
) {
79+
const choices = integration.products
80+
.map(p => ` ${integrationSlug}/${p.slug}`)
81+
.join('\n');
82+
output.error(
83+
`Integration "${integrationSlug}" has multiple products. Specify one with:\n\n${choices}\n\nExample: vercel integration add ${integrationSlug}/${integration.products[0].slug}`
84+
);
85+
return 1;
86+
}
87+
7488
const product = await selectProduct(
7589
client,
7690
integration.products,

packages/cli/test/unit/commands/integration/add-auto-provision.test.ts

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -945,7 +945,18 @@ describe('integration add (auto-provision)', () => {
945945
useAutoProvision({ responseKey: 'provisioned' });
946946
});
947947

948-
it('should prompt for product selection when multiple products', async () => {
948+
it('should error when multiple products and no slug specified in non-TTY', async () => {
949+
client.setArgv('integration', 'add', 'acme-two-products');
950+
(client.stdin as any).isTTY = false;
951+
const exitCode = await integrationCommand(client);
952+
expect(exitCode).toEqual(1);
953+
const stderr = client.stderr.getFullOutput();
954+
expect(stderr).toContain('has multiple products');
955+
expect(stderr).toContain('acme-two-products/acme-a');
956+
expect(stderr).toContain('acme-two-products/acme-b');
957+
});
958+
959+
it('should prompt for product selection when multiple products in TTY', async () => {
949960
client.setArgv('integration', 'add', 'acme-two-products');
950961
const exitCodePromise = integrationCommand(client);
951962

@@ -1035,22 +1046,19 @@ describe('integration add (auto-provision)', () => {
10351046
expect(exitCode).toEqual(0);
10361047
});
10371048

1038-
it('should accept multiple metadata flags', async () => {
1049+
it('should accept multiple metadata flags with slash syntax', async () => {
10391050
client.setArgv(
10401051
'integration',
10411052
'add',
1042-
'acme-two-products',
1053+
'acme-two-products/acme-a',
10431054
'--metadata',
10441055
'version=5.4',
10451056
'--metadata',
10461057
'region=pdx1'
10471058
);
10481059
const exitCodePromise = integrationCommand(client);
10491060

1050-
await expect(client.stderr).toOutput('Select a product');
1051-
client.stdin.write('\n'); // Select first product (uses metadataSchema2)
1052-
1053-
// Auto-generated name, --metadata provides metadata — no prompts
1061+
// Slash syntax selects product, --metadata provides metadata — no prompts
10541062
await expect(client.stderr).toOutput('successfully provisioned');
10551063

10561064
const exitCode = await exitCodePromise;

0 commit comments

Comments
 (0)