Skip to content

Commit adf565f

Browse files
committed
fix(cli): respect existing config during onboarding
1 parent cb27309 commit adf565f

2 files changed

Lines changed: 138 additions & 3 deletions

File tree

extensions/cli/src/onboarding.test.ts

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,130 @@ name: "Incomplete Config"
162162
});
163163
});
164164

165+
describe("onboarding local config handling", () => {
166+
let tempDir: string;
167+
let originalContinueGlobalDir: string | undefined;
168+
let originalNodeEnv: string | undefined;
169+
let originalCi: string | undefined;
170+
let originalVitest: string | undefined;
171+
let originalGithubActions: string | undefined;
172+
let originalIsTTY: boolean;
173+
174+
beforeEach(() => {
175+
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "continue-home-"));
176+
originalContinueGlobalDir = process.env.CONTINUE_GLOBAL_DIR;
177+
originalNodeEnv = process.env.NODE_ENV;
178+
originalCi = process.env.CI;
179+
originalVitest = process.env.VITEST;
180+
originalGithubActions = process.env.GITHUB_ACTIONS;
181+
originalIsTTY = process.stdin.isTTY;
182+
183+
process.env.CONTINUE_GLOBAL_DIR = tempDir;
184+
vi.resetModules();
185+
vi.doMock("./auth/workos.js", () => ({
186+
login: vi.fn(),
187+
}));
188+
vi.doMock("./config.js", () => ({
189+
getApiClient: vi.fn(() => ({})),
190+
}));
191+
vi.doMock("./configLoader.js", () => ({
192+
loadConfiguration: vi.fn(),
193+
}));
194+
});
195+
196+
afterEach(() => {
197+
if (fs.existsSync(tempDir)) {
198+
fs.rmSync(tempDir, {
199+
force: true,
200+
maxRetries: 3,
201+
recursive: true,
202+
retryDelay: 100,
203+
});
204+
}
205+
206+
if (originalContinueGlobalDir === undefined) {
207+
delete process.env.CONTINUE_GLOBAL_DIR;
208+
} else {
209+
process.env.CONTINUE_GLOBAL_DIR = originalContinueGlobalDir;
210+
}
211+
212+
if (originalNodeEnv === undefined) {
213+
delete process.env.NODE_ENV;
214+
} else {
215+
process.env.NODE_ENV = originalNodeEnv;
216+
}
217+
218+
if (originalCi === undefined) {
219+
delete process.env.CI;
220+
} else {
221+
process.env.CI = originalCi;
222+
}
223+
224+
if (originalVitest === undefined) {
225+
delete process.env.VITEST;
226+
} else {
227+
process.env.VITEST = originalVitest;
228+
}
229+
230+
if (originalGithubActions === undefined) {
231+
delete process.env.GITHUB_ACTIONS;
232+
} else {
233+
process.env.GITHUB_ACTIONS = originalGithubActions;
234+
}
235+
236+
process.stdin.isTTY = originalIsTTY;
237+
238+
vi.doUnmock("./auth/workos.js");
239+
vi.doUnmock("./config.js");
240+
vi.doUnmock("./configLoader.js");
241+
vi.doUnmock("./util/prompt.js");
242+
vi.resetModules();
243+
});
244+
245+
test("should skip interactive onboarding when default config.yaml exists", async () => {
246+
fs.writeFileSync(path.join(tempDir, "config.yaml"), "name: Local Config\n");
247+
248+
delete process.env.NODE_ENV;
249+
delete process.env.CI;
250+
delete process.env.VITEST;
251+
delete process.env.GITHUB_ACTIONS;
252+
process.stdin.isTTY = true;
253+
254+
const questionWithChoices = vi
255+
.fn()
256+
.mockRejectedValue(new Error("prompted"));
257+
vi.doMock("./util/prompt.js", () => ({
258+
question: vi.fn(),
259+
questionWithChoices,
260+
}));
261+
262+
const { runOnboardingFlow } = await import("./onboarding.js");
263+
264+
await expect(runOnboardingFlow(undefined)).resolves.toBe(false);
265+
expect(questionWithChoices).not.toHaveBeenCalled();
266+
});
267+
268+
test("should mark onboarding complete after a successful --config load", async () => {
269+
const configPath = path.join(tempDir, "custom-config.yaml");
270+
const flagPath = path.join(tempDir, ".onboarding_complete");
271+
const loadConfiguration = vi.fn().mockResolvedValue({
272+
config: { name: "Custom Config" },
273+
source: { path: configPath, type: "cli-flag" },
274+
});
275+
276+
vi.doMock("./configLoader.js", () => ({
277+
loadConfiguration,
278+
}));
279+
280+
const { initializeWithOnboarding } = await import("./onboarding.js");
281+
282+
await initializeWithOnboarding(null, configPath);
283+
284+
expect(loadConfiguration).toHaveBeenCalledOnce();
285+
expect(fs.existsSync(flagPath)).toBe(true);
286+
});
287+
});
288+
165289
// Separate describe block with its own mocking for BEDROCK tests
166290
describe("CONTINUE_USE_BEDROCK environment variable", () => {
167291
const mockConsoleLog = vi.fn();

extensions/cli/src/onboarding.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,15 +56,20 @@ export async function runOnboardingFlow(
5656
return false;
5757
}
5858

59-
// Step 2: Check for CONTINUE_USE_BEDROCK environment variable first (before test env check)
59+
// Step 2: Existing local config means the user is already configured
60+
if (fs.existsSync(CONFIG_PATH)) {
61+
return false;
62+
}
63+
64+
// Step 3: Check for CONTINUE_USE_BEDROCK environment variable first (before test env check)
6065
if (process.env.CONTINUE_USE_BEDROCK === "1") {
6166
console.log(
6267
chalk.blue("✓ Using AWS Bedrock (CONTINUE_USE_BEDROCK detected)"),
6368
);
6469
return true;
6570
}
6671

67-
// Step 3: Check if we're in a test/CI environment - if so, skip interactive prompts
72+
// Step 4: Check if we're in a test/CI environment - if so, skip interactive prompts
6873
const isTestEnv =
6974
process.env.NODE_ENV === "test" ||
7075
process.env.CI === "true" ||
@@ -85,7 +90,7 @@ export async function runOnboardingFlow(
8590
return false;
8691
}
8792

88-
// Step 4: Present user with two options
93+
// Step 5: Present user with two options
8994
console.log(chalk.yellow("How do you want to get started?"));
9095
console.log(chalk.white("1. ⏩ Log in with Continue"));
9196
console.log(chalk.white("2. 🔑 Enter your Anthropic API key"));
@@ -156,6 +161,12 @@ export async function initializeWithOnboarding(
156161
`Failed to load config from "${configPath}": ${errorMessage}`,
157162
);
158163
}
164+
165+
if (firstTime && configPath) {
166+
await markOnboardingComplete();
167+
}
168+
169+
return;
159170
}
160171

161172
if (!firstTime) return;

0 commit comments

Comments
 (0)