Skip to content
Open
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
124 changes: 124 additions & 0 deletions extensions/cli/src/onboarding.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,130 @@ name: "Incomplete Config"
});
});

describe("onboarding local config handling", () => {
let tempDir: string;
let originalContinueGlobalDir: string | undefined;
let originalNodeEnv: string | undefined;
let originalCi: string | undefined;
let originalVitest: string | undefined;
let originalGithubActions: string | undefined;
let originalIsTTY: boolean;

beforeEach(() => {
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "continue-home-"));
originalContinueGlobalDir = process.env.CONTINUE_GLOBAL_DIR;
originalNodeEnv = process.env.NODE_ENV;
originalCi = process.env.CI;
originalVitest = process.env.VITEST;
originalGithubActions = process.env.GITHUB_ACTIONS;
originalIsTTY = process.stdin.isTTY;

process.env.CONTINUE_GLOBAL_DIR = tempDir;
vi.resetModules();
vi.doMock("./auth/workos.js", () => ({
login: vi.fn(),
}));
vi.doMock("./config.js", () => ({
getApiClient: vi.fn(() => ({})),
}));
vi.doMock("./configLoader.js", () => ({
loadConfiguration: vi.fn(),
}));
});

afterEach(() => {
if (fs.existsSync(tempDir)) {
fs.rmSync(tempDir, {
force: true,
maxRetries: 3,
recursive: true,
retryDelay: 100,
});
}

if (originalContinueGlobalDir === undefined) {
delete process.env.CONTINUE_GLOBAL_DIR;
} else {
process.env.CONTINUE_GLOBAL_DIR = originalContinueGlobalDir;
}

if (originalNodeEnv === undefined) {
delete process.env.NODE_ENV;
} else {
process.env.NODE_ENV = originalNodeEnv;
}

if (originalCi === undefined) {
delete process.env.CI;
} else {
process.env.CI = originalCi;
}

if (originalVitest === undefined) {
delete process.env.VITEST;
} else {
process.env.VITEST = originalVitest;
}

if (originalGithubActions === undefined) {
delete process.env.GITHUB_ACTIONS;
} else {
process.env.GITHUB_ACTIONS = originalGithubActions;
}

process.stdin.isTTY = originalIsTTY;

vi.doUnmock("./auth/workos.js");
vi.doUnmock("./config.js");
vi.doUnmock("./configLoader.js");
vi.doUnmock("./util/prompt.js");
vi.resetModules();
});

test("should skip interactive onboarding when default config.yaml exists", async () => {
fs.writeFileSync(path.join(tempDir, "config.yaml"), "name: Local Config\n");

delete process.env.NODE_ENV;
delete process.env.CI;
delete process.env.VITEST;
delete process.env.GITHUB_ACTIONS;
process.stdin.isTTY = true;

const questionWithChoices = vi
.fn()
.mockRejectedValue(new Error("prompted"));
vi.doMock("./util/prompt.js", () => ({
question: vi.fn(),
questionWithChoices,
}));

const { runOnboardingFlow } = await import("./onboarding.js");

await expect(runOnboardingFlow(undefined)).resolves.toBe(false);
expect(questionWithChoices).not.toHaveBeenCalled();
});

test("should mark onboarding complete after a successful --config load", async () => {
const configPath = path.join(tempDir, "custom-config.yaml");
const flagPath = path.join(tempDir, ".onboarding_complete");
const loadConfiguration = vi.fn().mockResolvedValue({
config: { name: "Custom Config" },
source: { path: configPath, type: "cli-flag" },
});

vi.doMock("./configLoader.js", () => ({
loadConfiguration,
}));

const { initializeWithOnboarding } = await import("./onboarding.js");

await initializeWithOnboarding(null, configPath);

expect(loadConfiguration).toHaveBeenCalledOnce();
expect(fs.existsSync(flagPath)).toBe(true);
});
});

// Separate describe block with its own mocking for BEDROCK tests
describe("CONTINUE_USE_BEDROCK environment variable", () => {
const mockConsoleLog = vi.fn();
Expand Down
17 changes: 14 additions & 3 deletions extensions/cli/src/onboarding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,15 +56,20 @@ export async function runOnboardingFlow(
return false;
}

// Step 2: Check for CONTINUE_USE_BEDROCK environment variable first (before test env check)
// Step 2: Existing local config means the user is already configured
if (fs.existsSync(CONFIG_PATH)) {
return false;
}

// Step 3: Check for CONTINUE_USE_BEDROCK environment variable first (before test env check)
if (process.env.CONTINUE_USE_BEDROCK === "1") {
console.log(
chalk.blue("✓ Using AWS Bedrock (CONTINUE_USE_BEDROCK detected)"),
);
return true;
}

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

// Step 4: Present user with two options
// Step 5: Present user with two options
console.log(chalk.yellow("How do you want to get started?"));
console.log(chalk.white("1. ⏩ Log in with Continue"));
console.log(chalk.white("2. 🔑 Enter your Anthropic API key"));
Expand Down Expand Up @@ -156,6 +161,12 @@ export async function initializeWithOnboarding(
`Failed to load config from "${configPath}": ${errorMessage}`,
);
}

if (firstTime && configPath) {
await markOnboardingComplete();
}

return;
}

if (!firstTime) return;
Expand Down
Loading