Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/commands/cloud/connect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -484,7 +484,7 @@ async function browserConnect(
return 'connected';
}
const check = canWaitForBrowser(config) && baseline ? baseline.check : null;
await openOrPrintInstallUrl(config, cliConnectUrl(config, provider, workspaceId), label, noBrowser);
await openOrPrintInstallUrl(config, cliConnectUrl(config, provider, workspaceId, reconnect), label, noBrowser);
return confirmBrowserConnect(config, check, `${label} to connect`);
}

Expand Down
5 changes: 3 additions & 2 deletions src/commands/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -265,8 +265,9 @@ export function secretStep(
// marks the browser session as CLI-initiated (cookie), generates the provider
// install URL, and forwards to it — so the flow ends on the console's
// "go back to your terminal" page instead of the workspace dashboard.
export function cliConnectUrl(config: Config, flow: string, workspaceId: string): string {
return `${consoleBaseUrl(config)}/cli/connect?flow=${encodeURIComponent(flow)}&workspace=${encodeURIComponent(workspaceId)}`;
export function cliConnectUrl(config: Config, flow: string, workspaceId: string, reconnect = false): string {
const url = `${consoleBaseUrl(config)}/cli/connect?flow=${encodeURIComponent(flow)}&workspace=${encodeURIComponent(workspaceId)}`;
return reconnect ? `${url}&reconnect=1` : url;
}

// After a connect flow hands off to the browser, the terminal session should
Expand Down
8 changes: 4 additions & 4 deletions src/commands/integration/connect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,8 @@ export function prReviewsFlagsGiven(args: Record<string, unknown>): boolean {

// The console's /cli/connect page carries the answer across the GitHub install round-trip
// and hands it to the API, which records it on the integration before the repository sync.
export function githubConnectUrl(config: Config, workspaceId: string, prReviews: PrReviewsChoice | undefined): string {
const url = cliConnectUrl(config, 'github', workspaceId);
export function githubConnectUrl(config: Config, workspaceId: string, prReviews: PrReviewsChoice | undefined, reconnect = false): string {
const url = cliConnectUrl(config, 'github', workspaceId, reconnect);
return prReviews ? `${url}&pr_reviews=${prReviews}` : url;
}

Expand Down Expand Up @@ -1148,7 +1148,7 @@ async function connectType(
return 'connected';
}
const check = canWaitForBrowser(config) && baseline ? baseline.check : null;
let url = cliConnectUrl(config, type, workspaceId);
let url = cliConnectUrl(config, type, workspaceId, reconnect);
if (type !== 'github' && prReviewsFlagsGiven(args) && config.output !== 'json') {
process.stderr.write('--pr-reviews / --no-pr-reviews only apply to --type github; ignored.\n');
}
Expand All @@ -1159,7 +1159,7 @@ async function connectType(
if (answer === BACK) return BACK;
prReviews = answer;
}
url = githubConnectUrl(config, workspaceId, prReviews);
url = githubConnectUrl(config, workspaceId, prReviews, reconnect);
}
await openOrPrintInstallUrl(config, url, labels[type], noBrowser);
const outcome = await confirmBrowserConnect(config, check, names[type]);
Expand Down
53 changes: 53 additions & 0 deletions test/browser-reconnect-handoff.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { describe, it } from 'node:test';
import assert from 'node:assert/strict';
import { cloudConnectCommand } from '../src/commands/cloud/connect';
import { cliConnectUrl } from '../src/commands/helpers';
import { integrationConnectCommand } from '../src/commands/integration/connect';
import { mockConfig } from './helpers/config';

describe('browser reconnect handoff', () => {
it('encodes explicit reconnect without changing an ordinary connect URL', () => {
const config = mockConfig({ domain: 'api.baseberry.cc' });
const normal = new URL(cliConnectUrl(config, 'posthog', 'ws_one&two'));
const reconnect = new URL(cliConnectUrl(config, 'posthog', 'ws_one&two', true));
assert.equal(normal.searchParams.has('reconnect'), false);
assert.equal(reconnect.searchParams.get('workspace'), 'ws_one&two');
assert.equal(reconnect.searchParams.get('reconnect'), '1');
});

for (const type of ['github', 'slack', 'sentry', 'posthog']) {
it(`forwards --reconnect through ${type} integration connect`, async (t) => {
const writes: string[] = [];
t.mock.method(process.stdout, 'write', (chunk) => {
writes.push(String(chunk));
return true;
});
await integrationConnectCommand.execute(
mockConfig({ domain: 'api.baseberry.cc', workspaceId: 'ws_one', dryRun: true, output: 'text', quiet: true }),
{},
{ type, reconnect: true, noBrowser: true }
);
const url = new URL(writes.join('').trim());
assert.equal(url.searchParams.get('flow'), type);
assert.equal(url.searchParams.get('reconnect'), '1');
});
}

for (const provider of ['vercel', 'planetscale', 'supabase']) {
it(`forwards --reconnect through ${provider} cloud connect`, async (t) => {
const writes: string[] = [];
t.mock.method(process.stdout, 'write', (chunk) => {
writes.push(String(chunk));
return true;
});
await cloudConnectCommand.execute(
mockConfig({ domain: 'api.baseberry.cc', workspaceId: 'ws_one', dryRun: true, output: 'text', quiet: true }),
{},
{ provider, reconnect: true, noBrowser: true }
);
const url = new URL(writes.join('').trim());
assert.equal(url.searchParams.get('flow'), provider);
assert.equal(url.searchParams.get('reconnect'), '1');
});
}
});
Loading