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/auth/logout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export async function runLogout(): Promise<void> {
let revoked = false;

if (creds?.token) {
const apiUrl = creds.apiUrl ?? getApiUrl();
const apiUrl = getApiUrl(creds);
const response = await fetch(`${apiUrl}/api/cli/logout`, {
method: "POST",
headers: {
Expand Down
4 changes: 2 additions & 2 deletions src/commands/auth/status.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Command } from "commander";
import { loadCredentials } from "../../lib/config.js";
import { getApiUrl, loadCredentials } from "../../lib/config.js";
import { output } from "../../lib/output.js";

export const statusCommand = new Command("status")
Expand All @@ -11,7 +11,7 @@ export const statusCommand = new Command("status")
}
output.success({
authenticated: true,
apiUrl: creds!.apiUrl,
apiUrl: getApiUrl(creds),
deviceName: creds!.deviceName,
expiresAt: creds!.expiresAt,
});
Expand Down
2 changes: 1 addition & 1 deletion src/lib/api-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ export async function apiRequest<T>(
output.error("Token expired. Run: lucas auth login");
}

const apiUrl = creds!.apiUrl ?? getApiUrl();
const apiUrl = getApiUrl(creds);
const url = new URL(path, apiUrl);

if (queryParams) {
Expand Down
15 changes: 13 additions & 2 deletions src/lib/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,20 @@ export interface Credentials {
export const CONFIG_DIR = join(homedir(), ".config", "lucas");
const CREDENTIALS_FILE = join(CONFIG_DIR, "credentials.json");
const DEFAULT_API_URL = "https://api.lucasapp.app";
const LEGACY_PRODUCTION_API_URLS = new Set(["https://lucas.stevenacz.com"]);

export function getApiUrl(): string {
return process.env.LUCAS_API_URL ?? DEFAULT_API_URL;
export function normalizeApiUrl(apiUrl: string): string {
const normalized = apiUrl.trim().replace(/\/+$/, "");
if (LEGACY_PRODUCTION_API_URLS.has(normalized)) {
return DEFAULT_API_URL;
}
return normalized;
}

export function getApiUrl(creds?: Pick<Credentials, "apiUrl"> | null): string {
return normalizeApiUrl(
process.env.LUCAS_API_URL ?? creds?.apiUrl ?? DEFAULT_API_URL,
);
}

export function ensureConfigDir(): void {
Expand Down
27 changes: 27 additions & 0 deletions tests/lib/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";

describe("config credential storage", () => {
let tempHome: string | undefined;
const originalLucasApiUrl = process.env.LUCAS_API_URL;

beforeEach(async () => {
delete process.env.LUCAS_API_URL;
tempHome = await mkdtemp(join(tmpdir(), "lucas-cli-home-"));
vi.resetModules();
vi.doMock("os", () => ({
Expand All @@ -17,6 +19,11 @@ describe("config credential storage", () => {
afterEach(async () => {
vi.doUnmock("os");
vi.resetModules();
if (originalLucasApiUrl === undefined) {
delete process.env.LUCAS_API_URL;
} else {
process.env.LUCAS_API_URL = originalLucasApiUrl;
}
if (tempHome) {
await rm(tempHome, { recursive: true, force: true });
tempHome = undefined;
Expand All @@ -41,4 +48,24 @@ describe("config credential storage", () => {
expect(dirMode).toBe(0o700);
expect(fileMode).toBe(0o600);
});

it("normalizes legacy production API URLs from stored credentials", async () => {
const { getApiUrl } = await import("../../src/lib/config.js");

expect(getApiUrl({ apiUrl: "https://lucas.stevenacz.com" })).toBe(
"https://api.lucasapp.app",
);
expect(getApiUrl({ apiUrl: "https://lucas.stevenacz.com/" })).toBe(
"https://api.lucasapp.app",
);
});

it("lets LUCAS_API_URL override stored credentials", async () => {
process.env.LUCAS_API_URL = "http://localhost:3301";
const { getApiUrl } = await import("../../src/lib/config.js");

expect(getApiUrl({ apiUrl: "https://example.test" })).toBe(
"http://localhost:3301",
);
});
});