diff --git a/src/commands/login.ts b/src/commands/login.ts index 5af1e0d4..a2b9c9d5 100644 --- a/src/commands/login.ts +++ b/src/commands/login.ts @@ -11,15 +11,15 @@ import { isTlsError, tlsHintMessage } from "../helpers/tls"; export function registerLoginCommand(program: Command) { const login = program .command("login") - .description("Authenticate with GitHub to access archgate plugins"); + .description("Authenticate with GitHub and register for archgate plugins"); login.action(async () => { try { - // Check if already logged in + // Check if already registered const existing = await loadCredentials(); if (existing) { logInfo( - `Already logged in as ${styleText("bold", existing.github_user)}.`, + `Already registered as ${styleText("bold", existing.github_user)}.`, "Run `archgate login refresh` to re-authenticate." ); return; @@ -43,21 +43,21 @@ export function registerLoginCommand(program: Command) { login .command("status") - .description("Show current authentication status") + .description("Show current registration status") .action(async () => { const creds = await loadCredentials(); if (creds) { console.log( - `Logged in as ${styleText("bold", creds.github_user)} (since ${creds.created_at})` + `Registered as ${styleText("bold", creds.github_user)} (since ${creds.created_at})` ); } else { - console.log("Not logged in. Run `archgate login` to authenticate."); + console.log("Not registered. Run `archgate login` to sign up."); } }); login .command("logout") - .description("Remove stored credentials") + .description("Remove stored registration info") .action(async () => { await clearCredentials(); console.log("Logged out successfully."); @@ -65,7 +65,7 @@ export function registerLoginCommand(program: Command) { login .command("refresh") - .description("Re-authenticate and claim a new token") + .description("Re-authenticate and update registration") .action(async () => { try { await clearCredentials(); diff --git a/src/commands/plugin/install.ts b/src/commands/plugin/install.ts index a93b50a2..172d330e 100644 --- a/src/commands/plugin/install.ts +++ b/src/commands/plugin/install.ts @@ -3,7 +3,6 @@ import { styleText } from "node:util"; import type { Command } from "@commander-js/extra-typings"; import { Option } from "@commander-js/extra-typings"; -import { loadCredentials } from "../../helpers/auth"; import { EDITOR_LABELS } from "../../helpers/init-project"; import { logError, logInfo, logWarn } from "../../helpers/log"; import { findProjectRoot } from "../../helpers/paths"; @@ -28,25 +27,16 @@ export function registerPluginInstallCommand(plugin: Command) { .description("Install the archgate plugin for the specified editor") .addOption(editorOption) .action(async (opts) => { - const credentials = await loadCredentials(); - if (!credentials) { - logError( - "Not logged in.", - "Run `archgate login` first to authenticate." - ); - process.exit(1); - } - const label = EDITOR_LABELS[opts.editor]; try { switch (opts.editor) { case "claude": { if (await isClaudeCliAvailable()) { - await installClaudePlugin(credentials); + await installClaudePlugin(); logInfo(`Archgate plugin installed for ${label}.`); } else { - const url = buildMarketplaceUrl(credentials); + const url = buildMarketplaceUrl(); logWarn( "Claude CLI not found. To install the plugin manually, run:" ); @@ -56,16 +46,22 @@ export function registerPluginInstallCommand(plugin: Command) { console.log( ` ${styleText("bold", "claude plugin install")} archgate@archgate` ); + console.log( + `\nNote: Your git credentials must be configured for plugins.archgate.dev.` + ); + console.log( + `Run ${styleText("bold", "gh auth login")} if you use GitHub CLI.` + ); } break; } case "copilot": { if (await isCopilotCliAvailable()) { - await installCopilotPlugin(credentials); + await installCopilotPlugin(); logInfo(`Archgate plugin installed for ${label}.`); } else { - const url = buildMarketplaceUrl(credentials); + const url = buildMarketplaceUrl(); logWarn( "Copilot CLI not found. To install the plugin manually, run:" ); @@ -77,10 +73,34 @@ export function registerPluginInstallCommand(plugin: Command) { } case "cursor": { + logWarn( + "Cursor plugin installation requires GitHub credentials." + ); + logInfo( + "Please provide your GitHub personal access token (PAT) for authentication." + ); + + const { default: inquirer } = await import("inquirer"); + const { username } = await inquirer.prompt({ + type: "input", + name: "username", + message: "GitHub username:", + validate: (v: string) => + v.trim().length > 0 || "Username is required", + }); + const { token } = await inquirer.prompt({ + type: "password", + name: "token", + message: "GitHub token (PAT):", + validate: (v: string) => + v.trim().length > 0 || "Token is required", + }); + const projectRoot = findProjectRoot() ?? process.cwd(); const files = await installCursorPlugin( projectRoot, - credentials.token + username.trim(), + token.trim() ); logInfo( `Archgate plugin installed for ${label}.`, @@ -90,14 +110,15 @@ export function registerPluginInstallCommand(plugin: Command) { } case "vscode": { - const url = buildVscodeMarketplaceUrl(credentials); + const url = buildVscodeMarketplaceUrl(); await configureVscodeSettings( findProjectRoot() ?? process.cwd(), url ); logInfo( `Archgate plugin configured for ${label}.`, - "Marketplace URL added to VS Code user settings." + "Marketplace URL added to VS Code user settings.", + "Note: Your git credentials must be configured for plugins.archgate.dev." ); break; } diff --git a/src/commands/plugin/url.ts b/src/commands/plugin/url.ts index b381fc13..8c0cbe30 100644 --- a/src/commands/plugin/url.ts +++ b/src/commands/plugin/url.ts @@ -1,7 +1,6 @@ import type { Command } from "@commander-js/extra-typings"; import { Option } from "@commander-js/extra-typings"; -import { loadCredentials } from "../../helpers/auth"; import { logError } from "../../helpers/log"; import { buildMarketplaceUrl, @@ -16,24 +15,15 @@ export function registerPluginUrlCommand(plugin: Command) { plugin .command("url") .description( - "Print the authenticated plugin repository URL for manual configuration" + "Print the plugin repository URL for manual configuration" ) .addOption(editorOption) - .action(async (opts) => { + .action((opts) => { try { - const credentials = await loadCredentials(); - if (!credentials) { - logError( - "Not logged in.", - "Run `archgate login` first to authenticate." - ); - process.exit(1); - } - const url = opts.editor === "vscode" - ? buildVscodeMarketplaceUrl(credentials) - : buildMarketplaceUrl(credentials); + ? buildVscodeMarketplaceUrl() + : buildMarketplaceUrl(); console.log(url); } catch (err) { diff --git a/src/helpers/auth.ts b/src/helpers/auth.ts index a1ed845c..1ac05342 100644 --- a/src/helpers/auth.ts +++ b/src/helpers/auth.ts @@ -1,21 +1,20 @@ /** - * auth.ts — GitHub Device Flow authentication and archgate token management. + * auth.ts — GitHub Device Flow authentication and registration management. * - * Implements RFC 8628 (OAuth 2.0 Device Authorization Grant) for CLI login, - * plus local storage of the archgate plugin token in ~/.archgate/credentials. + * Implements RFC 8628 (OAuth 2.0 Device Authorization Grant) for CLI login. + * Users authenticate via GitHub credentials (git credential helpers) — no + * archgate-specific tokens are needed. The login flow registers the user + * and stores their GitHub username in ~/.archgate/credentials. */ import { chmodSync, unlinkSync } from "node:fs"; import { logDebug } from "./log"; import { internalPath, createPathIfNotExists } from "./paths"; -import { SignupRequiredError, isSignupRequiredError } from "./signup"; - // --------------------------------------------------------------------------- // Constants // --------------------------------------------------------------------------- -const PLUGINS_API = "https://plugins.archgate.dev"; const GITHUB_DEVICE_CODE_URL = "https://github.com/login/device/code"; const GITHUB_DEVICE_TOKEN_URL = "https://github.com/login/oauth/access_token"; const CREDENTIALS_FILE = "credentials"; @@ -60,7 +59,6 @@ type DeviceTokenResponse = | DeviceTokenErrorResponse; export interface StoredCredentials { - token: string; github_user: string; created_at: string; } @@ -182,49 +180,8 @@ export async function getGitHubUser( return { login: data.login, email: data.email ?? null }; } -// --------------------------------------------------------------------------- -// Token Claim -// --------------------------------------------------------------------------- - -/** - * Exchange a GitHub access token for an archgate plugin token - * via POST /api/token/claim on the plugins service. - */ -export async function claimArchgateToken(githubToken: string): Promise { - const response = await fetch(`${PLUGINS_API}/api/token/claim`, { - method: "POST", - headers: { - "Content-Type": "application/json", - "User-Agent": "archgate-cli", - }, - body: JSON.stringify({ github_token: githubToken }), - signal: AbortSignal.timeout(15_000), - redirect: "error", - }); - - if (!response.ok) { - const body = (await response.json().catch(() => ({}))) as { - error?: string; - }; - - if (isSignupRequiredError(body.error)) { - throw new SignupRequiredError(); - } - - const message = - body.error ?? `Token claim failed (HTTP ${response.status})`; - throw new Error(message); - } - - const data = (await response.json()) as { token?: string }; - if (!data.token) { - throw new Error("Plugins service did not return a token"); - } - return data.token; -} - // Re-export for consumers that import from auth.ts -export { SignupRequiredError } from "./signup"; +export { SignupRequiredError, isSignupRequiredError } from "./signup"; // --------------------------------------------------------------------------- // Credential Storage @@ -235,7 +192,8 @@ function credentialsPath(): string { } /** - * Persist archgate credentials to ~/.archgate/credentials (JSON). + * Persist registration info to ~/.archgate/credentials (JSON). + * Only stores GitHub username — no tokens (users authenticate via git credentials). */ export async function saveCredentials( credentials: StoredCredentials @@ -252,7 +210,7 @@ export async function saveCredentials( } /** - * Load stored archgate credentials, or null if none exist. + * Load stored credentials, or null if none exist. */ export async function loadCredentials(): Promise { const file = Bun.file(credentialsPath()); @@ -262,7 +220,7 @@ export async function loadCredentials(): Promise { try { const data = (await file.json()) as StoredCredentials; - if (!data.token || !data.github_user) { + if (!data.github_user) { return null; } return data; diff --git a/src/helpers/init-project.ts b/src/helpers/init-project.ts index 2cac1e94..4b80c6a7 100644 --- a/src/helpers/init-project.ts +++ b/src/helpers/init-project.ts @@ -139,7 +139,7 @@ async function configureEditorSettings( const { loadCredentials } = await import("./auth"); const creds = await loadCredentials(); const marketplaceUrl = creds - ? (await import("./plugin-install")).buildVscodeMarketplaceUrl(creds) + ? (await import("./plugin-install")).buildVscodeMarketplaceUrl() : undefined; return configureVscodeSettings(projectRoot, marketplaceUrl); } @@ -227,7 +227,7 @@ async function ensureEslintrcOverride(projectRoot: string): Promise { * Returns null-safe result — never throws. */ async function tryInstallPlugin( - projectRoot: string, + _projectRoot: string, editor: EditorTarget ): Promise { const { loadCredentials } = await import("./auth"); @@ -237,13 +237,9 @@ async function tryInstallPlugin( } if (editor === "cursor") { - const { installCursorPlugin } = await import("./plugin-install"); - const files = await installCursorPlugin(projectRoot, credentials.token); - return { - installed: true, - autoInstalled: true, - detail: `Extracted ${files.length} files to .cursor/`, - }; + // Cursor requires GitHub credentials for download — skip auto-install during init. + // Users can run `archgate plugin install --editor cursor` separately. + return { installed: false }; } if (editor === "vscode") { @@ -262,14 +258,14 @@ async function tryInstallPlugin( if (await isCopilotCliAvailable()) { try { - await installCopilotPlugin(credentials); + await installCopilotPlugin(); return { installed: true, autoInstalled: true }; } catch { // Fall through to manual instructions } } - const url = buildMarketplaceUrl(credentials); + const url = buildMarketplaceUrl(); return { installed: true, detail: url }; } @@ -279,13 +275,13 @@ async function tryInstallPlugin( if (await isClaudeCliAvailable()) { try { - await installClaudePlugin(credentials); + await installClaudePlugin(); return { installed: true, autoInstalled: true }; } catch { // Fall through to manual instructions } } - const url = buildMarketplaceUrl(credentials); + const url = buildMarketplaceUrl(); return { installed: true, detail: url }; } diff --git a/src/helpers/login-flow.ts b/src/helpers/login-flow.ts index 0d9e2b0d..f0e5a824 100644 --- a/src/helpers/login-flow.ts +++ b/src/helpers/login-flow.ts @@ -1,6 +1,11 @@ /** * login-flow.ts — Shared GitHub device flow + signup logic * used by both `login` and `init` commands. + * + * The login flow authenticates the user via GitHub Device Flow and + * registers them on the archgate plugins platform. No archgate-specific + * tokens are created — users authenticate to the plugin service using + * their existing git credentials (GitHub PAT or OAuth token). */ import { styleText } from "node:util"; @@ -11,11 +16,10 @@ import { requestDeviceCode, pollForAccessToken, getGitHubUser, - claimArchgateToken, saveCredentials, } from "./auth"; import { logError, logInfo } from "./log"; -import { SignupRequiredError, requestSignup } from "./signup"; +import { requestSignup } from "./signup"; export interface LoginFlowOptions { /** @@ -26,17 +30,17 @@ export interface LoginFlowOptions { } export interface LoginFlowResult { - /** Whether credentials were obtained. */ + /** Whether registration was successful. */ ok: boolean; /** GitHub username, if login succeeded. */ githubUser?: string; } /** - * Run the full GitHub device flow: authenticate, claim token (or sign up - * if the user is unregistered), and store credentials. + * Run the full GitHub device flow: authenticate, register (or verify + * registration), and store the GitHub username locally. * - * Returns `{ ok: true }` when credentials are stored, `{ ok: false }` on + * Returns `{ ok: true }` when registration is confirmed, `{ ok: false }` on * failure (error is already printed). */ export async function runLoginFlow( @@ -63,50 +67,35 @@ export async function runLoginFlow( await getGitHubUser(githubToken); logInfo(`GitHub user: ${styleText("bold", githubUser)}`); - logInfo("Claiming archgate plugin token..."); - let archgateToken: string; - try { - archgateToken = await claimArchgateToken(githubToken); - } catch (err) { - if (!(err instanceof SignupRequiredError)) throw err; - - console.log( - `\nYour GitHub account ${styleText("bold", githubUser)} is not yet registered.` - ); - console.log("Let's sign you up now.\n"); - - const result = await runSignupPrompt( - githubUser, - githubToken, - githubEmail, - options?.editor - ); - if (!result) return { ok: false }; - archgateToken = result; - } + // Try to register — the signup endpoint auto-approves + logInfo("Checking registration status..."); + const signupResult = await runSignupFlow( + githubUser, + githubEmail, + options?.editor + ); + if (!signupResult) return { ok: false }; await saveCredentials({ - token: archgateToken, github_user: githubUser, created_at: new Date().toISOString().split("T")[0], }); logInfo( - `Authenticated as ${styleText("bold", githubUser)}. Plugin access is now available.` + `Registered as ${styleText("bold", githubUser)}. Plugin access is now available via your git credentials.` ); return { ok: true, githubUser }; } /** - * Prompt for signup details, submit the request, and return the token. - * Returns null on failure (error is already printed). + * Register the user on the archgate platform. + * Returns true on success, null on failure (error is already printed). */ -async function runSignupPrompt( +async function runSignupFlow( githubUser: string, - githubToken: string, githubEmail: string | null, preselectedEditor?: string -): Promise { +): Promise { const { email } = await inquirer.prompt({ type: "input", name: "email", @@ -161,8 +150,5 @@ async function runSignupPrompt( return null; } - if (result.token) return result.token; - - logInfo("Claiming archgate plugin token..."); - return claimArchgateToken(githubToken); + return true; } diff --git a/src/helpers/plugin-install.ts b/src/helpers/plugin-install.ts index c99ee7ad..8d84429a 100644 --- a/src/helpers/plugin-install.ts +++ b/src/helpers/plugin-install.ts @@ -1,16 +1,19 @@ /** * plugin-install.ts — Download and install the archgate plugin for supported editors. * + * Authentication relies on the user's existing git credential helpers + * (e.g., `gh auth login`, macOS Keychain, git-credential-store). + * No archgate-specific tokens are embedded in URLs. + * * - Claude Code: auto-installs via `claude` CLI, or prints manual commands as fallback * - VS Code: marketplace URL for manual user-settings configuration (application-scoped) * - Copilot CLI: auto-installs via `copilot` CLI, or prints manual commands as fallback - * - Cursor: downloads cursor.tar.gz from the plugins service and extracts it + * - Cursor: downloads cursor.tar.gz from the plugins service using GitHub credentials */ import { mkdirSync, unlinkSync } from "node:fs"; import { join } from "node:path"; -import type { StoredCredentials } from "./auth"; import { logDebug } from "./log"; import { resolveCommand } from "./platform"; @@ -39,25 +42,19 @@ async function run( // --------------------------------------------------------------------------- /** - * Build the authenticated git marketplace URL for Claude Code & Copilot CLI plugin installation. - * Claude Code and Copilot CLI both use the .claude-plugin/ manifest format. + * Build the git marketplace URL for Claude Code & Copilot CLI plugin installation. + * No credentials are embedded — Claude Code uses git credential helpers. */ -export function buildMarketplaceUrl(credentials: StoredCredentials): string { - const user = encodeURIComponent(credentials.github_user); - const token = encodeURIComponent(credentials.token); - return `https://${user}:${token}@plugins.archgate.dev/archgate.git`; +export function buildMarketplaceUrl(): string { + return `https://plugins.archgate.dev/archgate.git`; } /** - * Build the authenticated git marketplace URL for VS Code plugin installation. - * VS Code Copilot uses the .github/plugin/ manifest format, served from a separate repo. + * Build the git marketplace URL for VS Code plugin installation. + * No credentials are embedded — git credential helpers provide authentication. */ -export function buildVscodeMarketplaceUrl( - credentials: StoredCredentials -): string { - const user = encodeURIComponent(credentials.github_user); - const token = encodeURIComponent(credentials.token); - return `https://${user}:${token}@plugins.archgate.dev/archgate-vscode.git`; +export function buildVscodeMarketplaceUrl(): string { + return `https://plugins.archgate.dev/archgate-vscode.git`; } /** @@ -73,15 +70,14 @@ export async function isClaudeCliAvailable(): Promise { * Install the archgate plugin via the `claude` CLI. * * Runs: - * claude plugin marketplace add + * claude plugin marketplace add * claude plugin install archgate@archgate * + * Authentication is handled by Claude Code's git credential helpers. * Throws on failure so the caller can fall back to manual instructions. */ -export async function installClaudePlugin( - credentials: StoredCredentials -): Promise { - const url = buildMarketplaceUrl(credentials); +export async function installClaudePlugin(): Promise { + const url = buildMarketplaceUrl(); const cmd = (await resolveCommand("claude")) ?? "claude"; logDebug("Adding archgate marketplace to claude CLI"); @@ -113,20 +109,26 @@ export async function installClaudePlugin( /** * Download the cursor.tar.gz from the plugins service and extract it to the project root. * Creates/overwrites .cursor/ folder contents with the pre-built agent and skills. + * + * Authentication uses the user's GitHub username and token via HTTP Basic Auth. */ export async function installCursorPlugin( projectRoot: string, - token: string + githubUser: string, + githubToken: string ): Promise { const response = await fetch(`${PLUGINS_API}/api/cursor`, { - headers: { Authorization: `Bearer ${token}`, "User-Agent": "archgate-cli" }, + headers: { + Authorization: `Basic ${btoa(`${githubUser}:${githubToken}`)}`, + "User-Agent": "archgate-cli", + }, signal: AbortSignal.timeout(30_000), redirect: "error", }); if (response.status === 401) { throw new Error( - "Plugin download unauthorized. Your token may have expired — run `archgate login refresh`." + "Plugin download unauthorized. Ensure your GitHub credentials are valid." ); } @@ -167,14 +169,13 @@ export async function isCopilotCliAvailable(): Promise { * Install the archgate plugin via the `copilot` CLI. * * Runs: - * copilot plugin install + * copilot plugin install * + * Authentication is handled by git credential helpers. * Throws on failure so the caller can fall back to manual instructions. */ -export async function installCopilotPlugin( - credentials: StoredCredentials -): Promise { - const url = buildMarketplaceUrl(credentials); +export async function installCopilotPlugin(): Promise { + const url = buildMarketplaceUrl(); const cmd = (await resolveCommand("copilot")) ?? "copilot"; logDebug("Installing archgate plugin via copilot CLI"); diff --git a/src/helpers/signup.ts b/src/helpers/signup.ts index d909eb07..95fc572e 100644 --- a/src/helpers/signup.ts +++ b/src/helpers/signup.ts @@ -28,14 +28,12 @@ export function isSignupRequiredError(message?: string): boolean { export interface SignupResult { ok: boolean; - /** Token returned by the API when signup is auto-approved. */ - token: string | null; } /** * Submit a signup request to the archgate plugins platform. - * On auto-approved signups the API returns the token directly, - * avoiding a separate claim round-trip. + * The API auto-approves signups — no tokens are returned. + * Users authenticate via their existing git credentials. */ export async function requestSignup( github: string, @@ -56,9 +54,8 @@ export async function requestSignup( }); if (response.status !== 201) { - return { ok: false, token: null }; + return { ok: false }; } - const data = (await response.json().catch(() => ({}))) as { token?: string }; - return { ok: true, token: data.token ?? null }; + return { ok: true }; } diff --git a/tests/helpers/auth.test.ts b/tests/helpers/auth.test.ts index f7e242bc..f4d199fd 100644 --- a/tests/helpers/auth.test.ts +++ b/tests/helpers/auth.test.ts @@ -29,14 +29,12 @@ describe("auth", () => { await import("../../src/helpers/auth"); await saveCredentials({ - token: "ag_beta_abc123", github_user: "testuser", created_at: "2026-01-15", }); const loaded = await loadCredentials(); expect(loaded).not.toBeNull(); - expect(loaded!.token).toBe("ag_beta_abc123"); expect(loaded!.github_user).toBe("testuser"); expect(loaded!.created_at).toBe("2026-01-15"); }); @@ -66,7 +64,7 @@ describe("auth", () => { const credPath = join(tempDir, ".archgate", "credentials"); const { mkdirSync } = await import("node:fs"); mkdirSync(join(tempDir, ".archgate"), { recursive: true }); - await Bun.write(credPath, JSON.stringify({ token: "abc" })); + await Bun.write(credPath, JSON.stringify({ created_at: "2026-01-01" })); const result = await loadCredentials(); expect(result).toBeNull(); @@ -79,7 +77,6 @@ describe("auth", () => { await import("../../src/helpers/auth"); await saveCredentials({ - token: "ag_beta_abc123", github_user: "testuser", created_at: "2026-01-15", }); @@ -193,79 +190,4 @@ describe("auth", () => { } }); }); - - describe("claimArchgateToken", () => { - test("returns token from plugins service", async () => { - const { claimArchgateToken } = await import("../../src/helpers/auth"); - - const originalFetch = globalThis.fetch; - mockFetch(() => - Promise.resolve(Response.json({ token: "ag_beta_claimed_token" })) - ); - - try { - const token = await claimArchgateToken("gho_github_token"); - expect(token).toBe("ag_beta_claimed_token"); - } finally { - globalThis.fetch = originalFetch; - } - }); - - test("throws SignupRequiredError on 403 with no approved signup", async () => { - const { claimArchgateToken, SignupRequiredError } = - await import("../../src/helpers/auth"); - - const originalFetch = globalThis.fetch; - mockFetch(() => - Promise.resolve( - Response.json( - { error: "No approved signup found for this GitHub account" }, - { status: 403 } - ) - ) - ); - - try { - await expect(claimArchgateToken("gho_token")).rejects.toBeInstanceOf( - SignupRequiredError - ); - } finally { - globalThis.fetch = originalFetch; - } - }); - - test("throws generic error on non-signup 403", async () => { - const { claimArchgateToken } = await import("../../src/helpers/auth"); - - const originalFetch = globalThis.fetch; - mockFetch(() => - Promise.resolve( - Response.json({ error: "Account suspended" }, { status: 403 }) - ) - ); - - try { - await expect(claimArchgateToken("gho_token")).rejects.toThrow( - "Account suspended" - ); - } finally { - globalThis.fetch = originalFetch; - } - }); - - test("throws when token missing from successful response", async () => { - const { claimArchgateToken } = await import("../../src/helpers/auth"); - - const originalFetch = globalThis.fetch; - mockFetch(() => Promise.resolve(Response.json({}))); - - try { - await expect(claimArchgateToken("gho_token")).rejects.toThrow( - "Plugins service did not return a token" - ); - } finally { - globalThis.fetch = originalFetch; - } - }); - }); }); diff --git a/tests/helpers/plugin-install.test.ts b/tests/helpers/plugin-install.test.ts index 24d66bed..c1411161 100644 --- a/tests/helpers/plugin-install.test.ts +++ b/tests/helpers/plugin-install.test.ts @@ -9,61 +9,35 @@ import { describe("plugin-install", () => { describe("buildMarketplaceUrl", () => { - test("builds URL with credentials embedded", () => { - const url = buildMarketplaceUrl({ - token: "ag_beta_abc123def456", - github_user: "octocat", - created_at: "2026-01-15", - }); - expect(url).toBe( - "https://octocat:ag_beta_abc123def456@plugins.archgate.dev/archgate.git" - ); + test("builds bare URL without credentials", () => { + const url = buildMarketplaceUrl(); + expect(url).toBe("https://plugins.archgate.dev/archgate.git"); }); - test("uses github_user as the URL username", () => { - const url = buildMarketplaceUrl({ - token: "ag_beta_token", - github_user: "my-handle", - created_at: "2026-02-01", - }); - expect(url).toContain("my-handle:"); - expect(url).toContain(":ag_beta_token@"); + test("does not contain user credentials", () => { + const url = buildMarketplaceUrl(); + expect(url).not.toContain("@"); + expect(url).not.toContain("ag_beta_"); }); }); describe("buildVscodeMarketplaceUrl", () => { - test("builds URL pointing to archgate-vscode.git", () => { - const url = buildVscodeMarketplaceUrl({ - token: "ag_beta_abc123def456", - github_user: "octocat", - created_at: "2026-01-15", - }); + test("builds bare URL pointing to archgate-vscode.git", () => { + const url = buildVscodeMarketplaceUrl(); expect(url).toBe( - "https://octocat:ag_beta_abc123def456@plugins.archgate.dev/archgate-vscode.git" + "https://plugins.archgate.dev/archgate-vscode.git" ); }); - test("embeds credentials correctly", () => { - const url = buildVscodeMarketplaceUrl({ - token: "ag_beta_token", - github_user: "my-handle", - created_at: "2026-02-01", - }); - expect(url).toContain("my-handle:"); - expect(url).toContain(":ag_beta_token@"); + test("does not contain user credentials", () => { + const url = buildVscodeMarketplaceUrl(); + expect(url).not.toContain("@"); + expect(url).not.toContain("ag_beta_"); }); test("uses archgate-vscode.git repo (not archgate.git)", () => { - const vscodeUrl = buildVscodeMarketplaceUrl({ - token: "tok", - github_user: "user", - created_at: "2026-01-01", - }); - const claudeUrl = buildMarketplaceUrl({ - token: "tok", - github_user: "user", - created_at: "2026-01-01", - }); + const vscodeUrl = buildVscodeMarketplaceUrl(); + const claudeUrl = buildMarketplaceUrl(); expect(vscodeUrl).toContain("archgate-vscode.git"); expect(claudeUrl).not.toContain("archgate-vscode.git"); expect(claudeUrl).toContain("archgate.git"); diff --git a/tests/helpers/signup.test.ts b/tests/helpers/signup.test.ts index baf28b97..25034fa6 100644 --- a/tests/helpers/signup.test.ts +++ b/tests/helpers/signup.test.ts @@ -51,11 +51,11 @@ describe("isSignupRequiredError", () => { }); describe("requestSignup", () => { - test("returns ok=true and token on 201 with token", async () => { + test("returns ok=true on 201", async () => { const originalFetch = globalThis.fetch; mockFetch(() => Promise.resolve( - Response.json({ token: "ag_beta_auto_approved" }, { status: 201 }) + Response.json({ ok: true }, { status: 201 }) ) ); @@ -66,30 +66,12 @@ describe("requestSignup", () => { "testing" ); expect(result.ok).toBe(true); - expect(result.token).toBe("ag_beta_auto_approved"); } finally { globalThis.fetch = originalFetch; } }); - test("returns ok=true and token=null on 201 without token (manual approval)", async () => { - const originalFetch = globalThis.fetch; - mockFetch(() => Promise.resolve(Response.json({}, { status: 201 }))); - - try { - const result = await requestSignup( - "octocat", - "octo@example.com", - "testing" - ); - expect(result.ok).toBe(true); - expect(result.token).toBeNull(); - } finally { - globalThis.fetch = originalFetch; - } - }); - - test("returns ok=false and token=null on non-201 status", async () => { + test("returns ok=false on non-201 status", async () => { const originalFetch = globalThis.fetch; mockFetch(() => Promise.resolve(new Response("Conflict", { status: 409 }))); @@ -100,13 +82,12 @@ describe("requestSignup", () => { "testing" ); expect(result.ok).toBe(false); - expect(result.token).toBeNull(); } finally { globalThis.fetch = originalFetch; } }); - test("returns ok=true and token=null when response.json() throws", async () => { + test("returns ok=true even when response is not JSON", async () => { const originalFetch = globalThis.fetch; mockFetch(() => Promise.resolve(new Response("not-json", { status: 201 }))); @@ -117,7 +98,6 @@ describe("requestSignup", () => { "testing" ); expect(result.ok).toBe(true); - expect(result.token).toBeNull(); } finally { globalThis.fetch = originalFetch; } @@ -131,7 +111,7 @@ describe("requestSignup", () => { (_input: string | URL | Request, init?: RequestInit) => { capturedBody = init?.body as string; return Promise.resolve( - Response.json({ token: "ag_beta_tok" }, { status: 201 }) + Response.json({ ok: true }, { status: 201 }) ); } ) as unknown as typeof fetch;