From 4e6919b6f5be747fdc613be842c9f352e4c6b8f7 Mon Sep 17 00:00:00 2001 From: Kinfe123 Date: Mon, 25 May 2026 23:29:35 +0300 Subject: [PATCH] fix: cloud api key for preview --- packages/docs/src/cli/cloud.test.ts | 22 ++++++++++ packages/docs/src/cli/cloud.ts | 68 +++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+) diff --git a/packages/docs/src/cli/cloud.test.ts b/packages/docs/src/cli/cloud.test.ts index 0fb7e5ab..d0d994cb 100644 --- a/packages/docs/src/cli/cloud.test.ts +++ b/packages/docs/src/cli/cloud.test.ts @@ -1,4 +1,5 @@ import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { execFileSync } from "node:child_process"; import os from "node:os"; import path from "node:path"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; @@ -166,6 +167,12 @@ void missing; it("validates the configured API key and prints the preview URL", async () => { writePackageJson(); + execFileSync("git", ["init"], { cwd: tmpDir, stdio: "ignore" }); + execFileSync("git", ["checkout", "-b", "preview-branch"], { cwd: tmpDir, stdio: "ignore" }); + execFileSync("git", ["remote", "add", "origin", "git@github.com:acme/docs-app.git"], { + cwd: tmpDir, + stdio: "ignore", + }); writeFileSync( path.join(tmpDir, ".env.local"), "CUSTOM_DOCS_KEY=docs_cloud_test_key\n", @@ -199,6 +206,21 @@ void missing; } if (url === "https://cloud.example.com/api/cloud/preview") { + const requestBody = JSON.parse(String(init?.body)) as { + repository?: { + owner?: string; + name?: string; + branch?: string; + rootDirectory?: string; + }; + }; + expect(requestBody.repository).toMatchObject({ + owner: "acme", + name: "docs-app", + branch: "preview-branch", + rootDirectory: ".", + }); + return new Response(JSON.stringify({ url: "https://docs-cloud-acme.preview.test" }), { status: 200, headers: { "content-type": "application/json" }, diff --git a/packages/docs/src/cli/cloud.ts b/packages/docs/src/cli/cloud.ts index ab6231a2..8c2e767c 100644 --- a/packages/docs/src/cli/cloud.ts +++ b/packages/docs/src/cli/cloud.ts @@ -1,5 +1,6 @@ import fs from "node:fs"; import path from "node:path"; +import { execFileSync } from "node:child_process"; import pc from "picocolors"; import type { DocsCloudConfig, DocsConfig } from "../types.js"; import { @@ -56,6 +57,14 @@ type ManagedDocsJson = { cloud?: DocsCloudConfig; }; +type GitRepositoryMetadata = { + owner: string; + name: string; + branch?: string; + rootDirectory: string; + remoteUrl?: string; +}; + export interface CloudCommandOptions { configPath?: string; apiBaseUrl?: string; @@ -113,6 +122,64 @@ function readPackageName(rootDir: string): string | undefined { } } +function runGit(rootDir: string, args: string[]): string | undefined { + try { + return execFileSync("git", ["-C", rootDir, ...args], { + encoding: "utf-8", + stdio: ["ignore", "pipe", "ignore"], + }).trim(); + } catch { + return undefined; + } +} + +function parseGitHubRemote(remoteUrl: string): { owner: string; name: string } | null { + const trimmed = remoteUrl.trim(); + const match = + trimmed.match(/^git@github\.com:([^/]+)\/(.+?)(?:\.git)?$/) ?? + trimmed.match(/^https:\/\/github\.com\/([^/]+)\/(.+?)(?:\.git)?(?:\/)?$/); + + if (!match?.[1] || !match[2]) { + return null; + } + + return { + owner: match[1], + name: match[2].replace(/\.git$/i, ""), + }; +} + +function resolveGitRepositoryMetadata(rootDir: string): GitRepositoryMetadata | undefined { + const gitRoot = runGit(rootDir, ["rev-parse", "--show-toplevel"]); + const remoteUrl = runGit(rootDir, ["remote", "get-url", "origin"]); + if (!gitRoot || !remoteUrl) { + return undefined; + } + + const parsedRemote = parseGitHubRemote(remoteUrl); + if (!parsedRemote) { + return undefined; + } + + const branch = + runGit(rootDir, ["branch", "--show-current"]) ?? + runGit(rootDir, ["rev-parse", "--abbrev-ref", "HEAD"]); + const resolvedGitRoot = fs.realpathSync(gitRoot); + const resolvedRootDir = fs.realpathSync(rootDir); + const rootDirectory = path + .relative(resolvedGitRoot, resolvedRootDir) + .split(path.sep) + .filter(Boolean) + .join("/"); + + return { + ...parsedRemote, + branch: branch && branch !== "HEAD" ? branch : undefined, + rootDirectory: rootDirectory || ".", + remoteUrl, + }; +} + function titleFromPackageName(name: string | undefined): string | undefined { if (!name) return undefined; const normalized = name @@ -579,6 +646,7 @@ async function requestPreview(params: { body: JSON.stringify({ config: params.config, configPath: path.relative(params.rootDir, params.configPath) || DOCS_JSON_FILE, + repository: resolveGitRepositoryMetadata(params.rootDir), }), }, });