blodemd 0.0.6 → 0.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dev-server/app/favicon.ico +0 -0
- package/dev-server/next-env.d.ts +5 -0
- package/dev-server/package.json +1 -1
- package/dev-server/tsconfig.json +3 -2
- package/dist/cli.mjs +209 -53
- package/dist/cli.mjs.map +1 -1
- package/docs/app/globals.css +4 -2
- package/docs/components/docs/doc-header.tsx +35 -17
- package/docs/components/docs/doc-shell.tsx +46 -22
- package/docs/components/docs/doc-sidebar.tsx +13 -8
- package/docs/components/docs/mobile-nav.tsx +150 -152
- package/docs/components/icons/doc-icon.tsx +96 -0
- package/docs/components/mdx/card.tsx +60 -54
- package/docs/components/mdx/icon.tsx +2 -46
- package/docs/components/mdx/index.tsx +12 -1
- package/docs/components/mdx/tree.tsx +7 -7
- package/docs/components/ui/search.tsx +11 -7
- package/docs/lib/mdx.ts +2 -5
- package/docs/lib/navigation.ts +2 -2
- package/docs/lib/routes.ts +34 -0
- package/docs/lib/shiki.ts +6 -1
- package/package.json +13 -5
- package/packages/@repo/contracts/dist/tenant.d.ts +12 -0
- package/packages/@repo/contracts/dist/tenant.d.ts.map +1 -1
- package/packages/@repo/contracts/dist/tenant.js +20 -0
- package/packages/@repo/contracts/src/tenant.ts +38 -0
- package/packages/@repo/previewing/dist/fs-source.d.ts.map +1 -1
- package/packages/@repo/previewing/dist/fs-source.js +1 -8
- package/packages/@repo/previewing/src/fs-source.ts +1 -8
- package/packages/@repo/validation/src/mintlify-docs-schema.json +1 -1
- package/scripts/prepare-package.mjs +39 -0
- package/packages/@repo/common/src/common.unit.test.ts +0 -55
- package/packages/@repo/previewing/src/index.unit.test.ts +0 -290
package/dist/cli.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.mjs","names":["CONFIG_FILE","fileExists","resolveDocsRoot","normalizeRelativePath","parsePositiveInteger","fileExists","delay","resolveDocsRoot"],"sources":["../src/constants.ts","../src/jwt.ts","../src/errors.ts","../src/oauth-token.ts","../src/storage.ts","../src/supabase.ts","../src/auth-session.ts","../src/dev/resolve-root.ts","../src/dev/watcher.ts","../src/dev/command.ts","../src/oauth-callback.ts","../src/pkce.ts","../src/cli.ts"],"sourcesContent":["import { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\nexport const CLI_NAME = \"blodemd\";\n\nexport const BLODE_TOKEN_ENV = \"BLODEMD_API_KEY\";\nexport const BLODE_API_URL_ENV = \"BLODEMD_API_URL\";\nexport const BLODE_PROJECT_ENV = \"BLODEMD_PROJECT\";\nexport const BLODE_BRANCH_ENV = \"BLODEMD_BRANCH\";\nexport const BLODE_COMMIT_MESSAGE_ENV = \"BLODEMD_COMMIT_MESSAGE\";\n\nexport const DEFAULT_API_URL = \"https://api.blode.md\";\nexport const DEFAULT_SUPABASE_URL = \"https://bwnxwgkgyklzzmpbzuoz.supabase.co\";\n\nexport const OAUTH_CLIENT_ID = \"6b5f9860-fe96-4a83-b1ad-266260523c91\";\n\nexport const DEFAULT_OAUTH_CALLBACK_PORT = 8787;\nexport const DEFAULT_OAUTH_CALLBACK_PATH = \"/auth/callback\";\nexport const DEFAULT_OAUTH_TIMEOUT_SECONDS = 180;\n\nconst getDefaultConfigBaseDir = (): string => {\n if (process.platform === \"win32\") {\n return process.env.APPDATA ?? join(homedir(), \"AppData\", \"Roaming\");\n }\n\n return process.env.XDG_CONFIG_HOME ?? join(homedir(), \".config\");\n};\n\nconst configBaseDir = getDefaultConfigBaseDir();\n\nexport const CONFIG_DIR = join(configBaseDir, CLI_NAME);\nexport const CREDENTIALS_FILE = join(CONFIG_DIR, \"credentials.json\");\n","export interface JwtClaims {\n exp?: number;\n email?: string;\n sub?: string;\n}\n\nconst parseJwtBase64Url = (input: string): string => {\n const normalized = input.replaceAll(\"-\", \"+\").replaceAll(\"_\", \"/\");\n const padded = normalized.padEnd(Math.ceil(normalized.length / 4) * 4, \"=\");\n return Buffer.from(padded, \"base64\").toString(\"utf8\");\n};\n\nexport const parseJwtClaims = (token: string): JwtClaims | null => {\n const parts = token.split(\".\");\n const payloadPart = parts.at(1);\n\n if (!payloadPart) {\n return null;\n }\n\n try {\n const payload = parseJwtBase64Url(payloadPart);\n const parsed = JSON.parse(payload) as unknown;\n\n if (typeof parsed !== \"object\" || parsed === null) {\n return null;\n }\n\n const claims = parsed as Record<string, unknown>;\n\n return {\n email: typeof claims.email === \"string\" ? claims.email : undefined,\n exp: typeof claims.exp === \"number\" ? claims.exp : undefined,\n sub: typeof claims.sub === \"string\" ? claims.sub : undefined,\n };\n } catch {\n return null;\n }\n};\n","export const EXIT_CODES = {\n AUTH_REQUIRED: 4,\n CANCELLED: 2,\n ERROR: 1,\n NETWORK: 5,\n SUCCESS: 0,\n VALIDATION: 3,\n} as const;\n\ntype ExitCode = (typeof EXIT_CODES)[keyof typeof EXIT_CODES];\n\nexport class CliError extends Error {\n readonly exitCode: ExitCode;\n readonly hint: string | null;\n\n constructor(\n message: string,\n exitCode: ExitCode = EXIT_CODES.ERROR,\n hint?: string\n ) {\n super(message);\n this.name = \"CliError\";\n this.exitCode = exitCode;\n this.hint = hint ?? null;\n }\n}\n\nexport const toCliError = (error: unknown): CliError => {\n if (error instanceof CliError) {\n return error;\n }\n\n if (error instanceof Error) {\n if (error instanceof TypeError && error.message.includes(\"fetch\")) {\n return new CliError(\n \"Cannot connect to Blode.md API.\",\n EXIT_CODES.NETWORK,\n \"Check your internet connection and API URL configuration.\"\n );\n }\n\n if (error.name === \"TimeoutError\" || error.name === \"AbortError\") {\n return new CliError(\n \"Request timed out.\",\n EXIT_CODES.NETWORK,\n \"The API may be unavailable. Try again later.\"\n );\n }\n\n return new CliError(error.message, EXIT_CODES.ERROR);\n }\n\n return new CliError(\"Unknown error\", EXIT_CODES.ERROR);\n};\n","import { CliError, EXIT_CODES } from \"./errors.js\";\n\nexport interface OAuthTokenConfig {\n tokenUrl: string;\n clientId: string;\n}\n\nexport interface OAuthTokenResponse {\n access_token: string;\n refresh_token?: string;\n token_type: string;\n expires_in: number;\n}\n\nconst postTokenRequest = async (\n url: string,\n body: URLSearchParams\n): Promise<OAuthTokenResponse> => {\n const response = await fetch(url, {\n body: body.toString(),\n headers: { \"content-type\": \"application/x-www-form-urlencoded\" },\n method: \"POST\",\n });\n\n if (!response.ok) {\n const text = await response.text().catch(() => \"\");\n throw new CliError(\n `OAuth token request failed (${response.status}): ${text}`,\n EXIT_CODES.AUTH_REQUIRED\n );\n }\n\n return (await response.json()) as OAuthTokenResponse;\n};\n\nexport const exchangeAuthorizationCode = (\n config: OAuthTokenConfig,\n code: string,\n codeVerifier: string,\n redirectUri: string\n): Promise<OAuthTokenResponse> => {\n const body = new URLSearchParams({\n client_id: config.clientId,\n code,\n code_verifier: codeVerifier,\n grant_type: \"authorization_code\",\n redirect_uri: redirectUri,\n });\n\n return postTokenRequest(config.tokenUrl, body);\n};\n\nexport const refreshAccessToken = (\n config: OAuthTokenConfig,\n refreshToken: string\n): Promise<OAuthTokenResponse> => {\n const body = new URLSearchParams({\n client_id: config.clientId,\n grant_type: \"refresh_token\",\n refresh_token: refreshToken,\n });\n\n return postTokenRequest(config.tokenUrl, body);\n};\n","import { mkdir, readFile, rm, writeFile } from \"node:fs/promises\";\n\nimport { CONFIG_DIR, CREDENTIALS_FILE } from \"./constants.js\";\nimport { CliError, EXIT_CODES } from \"./errors.js\";\nimport type {\n ApiKeyCredentials,\n AuthFileData,\n StoredAuthSession,\n} from \"./types.js\";\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null;\n\nconst parseStoredAuthSession = (value: unknown): StoredAuthSession | null => {\n if (!isRecord(value)) {\n return null;\n }\n\n if (typeof value.accessToken !== \"string\") {\n return null;\n }\n\n if (value.refreshToken !== null && typeof value.refreshToken !== \"string\") {\n return null;\n }\n\n if (value.expiresAt !== null && typeof value.expiresAt !== \"string\") {\n return null;\n }\n\n const { user } = value;\n if (\n user !== null &&\n (!isRecord(user) ||\n typeof user.id !== \"string\" ||\n (user.email !== null && typeof user.email !== \"string\"))\n ) {\n return null;\n }\n\n if (typeof value.createdAt !== \"string\") {\n return null;\n }\n\n const parsedUser =\n user === null || !isRecord(user)\n ? null\n : {\n email: (user.email as string | null) ?? null,\n id: user.id as string,\n };\n\n return {\n accessToken: value.accessToken,\n createdAt: value.createdAt,\n expiresAt: (value.expiresAt as string | null) ?? null,\n refreshToken: (value.refreshToken as string | null) ?? null,\n user: parsedUser,\n };\n};\n\nconst parseApiKeyCredentials = (value: unknown): ApiKeyCredentials | null => {\n if (!isRecord(value)) {\n return null;\n }\n\n if (typeof value.apiKey !== \"string\") {\n return null;\n }\n\n return { apiKey: value.apiKey, type: \"api-key\" };\n};\n\nexport const readAuthFile = async (): Promise<AuthFileData | null> => {\n try {\n const raw = await readFile(CREDENTIALS_FILE, \"utf8\");\n const parsed = JSON.parse(raw) as unknown;\n\n if (!isRecord(parsed) || parsed.version !== 1) {\n throw new CliError(\n `Invalid credentials format in ${CREDENTIALS_FILE}`,\n EXIT_CODES.ERROR\n );\n }\n\n return {\n apiKey: parseApiKeyCredentials(parsed.apiKey) ?? undefined,\n session: parseStoredAuthSession(parsed.session) ?? undefined,\n version: 1,\n };\n } catch (error) {\n if (isRecord(error) && error.code === \"ENOENT\") {\n return null;\n }\n\n if (error instanceof CliError) {\n throw error;\n }\n\n return null;\n }\n};\n\nexport const readStoredAuthSession =\n async (): Promise<StoredAuthSession | null> => {\n const data = await readAuthFile();\n return data?.session ?? null;\n };\n\nexport const readStoredApiKey = async (): Promise<ApiKeyCredentials | null> => {\n const data = await readAuthFile();\n return data?.apiKey ?? null;\n};\n\nconst writeAuthFile = async (data: AuthFileData): Promise<void> => {\n await mkdir(CONFIG_DIR, { mode: 0o700, recursive: true });\n await writeFile(CREDENTIALS_FILE, `${JSON.stringify(data, null, 2)}\\n`, {\n encoding: \"utf8\",\n mode: 0o600,\n });\n};\n\nexport const writeStoredAuthSession = async (\n session: StoredAuthSession\n): Promise<void> => {\n await writeAuthFile({\n session,\n version: 1,\n });\n};\n\nexport const writeStoredApiKey = async (\n apiKey: ApiKeyCredentials\n): Promise<void> => {\n await writeAuthFile({\n apiKey,\n version: 1,\n });\n};\n\nexport const clearStoredCredentials = async (): Promise<void> => {\n await rm(CREDENTIALS_FILE, { force: true });\n};\n","import { DEFAULT_SUPABASE_URL } from \"./constants.js\";\nimport { parseJwtClaims } from \"./jwt.js\";\nimport type { OAuthTokenResponse } from \"./oauth-token.js\";\nimport type { StoredAuthSession, SupabaseConfig } from \"./types.js\";\n\nexport const resolveSupabaseConfig = (): SupabaseConfig => {\n const url =\n process.env.SUPABASE_URL ??\n process.env.NEXT_PUBLIC_SUPABASE_URL ??\n DEFAULT_SUPABASE_URL;\n\n return { url };\n};\n\nexport const buildOAuthUrls = (\n config: SupabaseConfig\n): {\n authorizeUrl: string;\n tokenUrl: string;\n} => ({\n authorizeUrl: `${config.url}/auth/v1/oauth/authorize`,\n tokenUrl: `${config.url}/auth/v1/oauth/token`,\n});\n\nexport const tokenResponseToStoredSession = (\n response: OAuthTokenResponse\n): StoredAuthSession => {\n const claims = parseJwtClaims(response.access_token);\n\n let expiresAt: string | null = null;\n if (typeof claims?.exp === \"number\") {\n expiresAt = new Date(claims.exp * 1000).toISOString();\n } else if (response.expires_in > 0) {\n expiresAt = new Date(Date.now() + response.expires_in * 1000).toISOString();\n }\n\n return {\n accessToken: response.access_token,\n createdAt: new Date().toISOString(),\n expiresAt,\n refreshToken: response.refresh_token ?? null,\n user:\n claims?.sub || claims?.email\n ? {\n email: claims.email ?? null,\n id: claims.sub ?? \"unknown\",\n }\n : null,\n };\n};\n","import { BLODE_TOKEN_ENV, OAUTH_CLIENT_ID } from \"./constants.js\";\nimport { parseJwtClaims } from \"./jwt.js\";\nimport { refreshAccessToken } from \"./oauth-token.js\";\nimport {\n clearStoredCredentials,\n readAuthFile,\n writeStoredAuthSession,\n} from \"./storage.js\";\nimport {\n buildOAuthUrls,\n resolveSupabaseConfig,\n tokenResponseToStoredSession,\n} from \"./supabase.js\";\nimport type { ResolvedAuthToken, StoredAuthSession } from \"./types.js\";\n\nconst expiresInMs = (session: StoredAuthSession): number | null => {\n if (!session.expiresAt) {\n return null;\n }\n\n const expiresAtMs = Date.parse(session.expiresAt);\n\n if (Number.isNaN(expiresAtMs)) {\n return null;\n }\n\n return expiresAtMs - Date.now();\n};\n\nconst isExpired = (session: StoredAuthSession): boolean => {\n const ms = expiresInMs(session);\n return ms !== null && ms <= 0;\n};\n\nconst shouldRefresh = (session: StoredAuthSession): boolean => {\n const ms = expiresInMs(session);\n return ms !== null && ms <= 60_000;\n};\n\nconst tokenFromRaw = (\n token: string,\n source: ResolvedAuthToken[\"source\"]\n): ResolvedAuthToken => {\n const claims = parseJwtClaims(token);\n\n const expiresAt =\n typeof claims?.exp === \"number\"\n ? new Date(claims.exp * 1000).toISOString()\n : null;\n\n return {\n expiresAt,\n source,\n token,\n user:\n claims?.sub || claims?.email\n ? { email: claims.email ?? null, id: claims.sub ?? \"unknown\" }\n : null,\n };\n};\n\nconst sessionToResolvedToken = (\n session: StoredAuthSession\n): ResolvedAuthToken => ({\n expiresAt: session.expiresAt,\n source: \"stored\",\n token: session.accessToken,\n user: session.user,\n});\n\nexport const resolveAuthToken = async (\n optApiKey?: string\n): Promise<ResolvedAuthToken | null> => {\n const envToken = (optApiKey ?? process.env[BLODE_TOKEN_ENV])?.trim();\n\n if (envToken) {\n return tokenFromRaw(envToken, optApiKey ? \"flag\" : \"environment\");\n }\n\n const data = await readAuthFile();\n const session = data?.session;\n\n if (session) {\n if (!(shouldRefresh(session) || isExpired(session))) {\n return sessionToResolvedToken(session);\n }\n\n if (session.refreshToken) {\n try {\n const config = resolveSupabaseConfig();\n const { tokenUrl } = buildOAuthUrls(config);\n const tokenResponse = await refreshAccessToken(\n { clientId: OAUTH_CLIENT_ID, tokenUrl },\n session.refreshToken\n );\n const updatedSession = tokenResponseToStoredSession(tokenResponse);\n await writeStoredAuthSession(updatedSession);\n\n return sessionToResolvedToken(updatedSession);\n } catch {\n // Refresh failed — fall through to expiry check\n }\n }\n\n if (isExpired(session)) {\n await clearStoredCredentials();\n return null;\n }\n\n return sessionToResolvedToken(session);\n }\n\n if (data?.apiKey) {\n return {\n expiresAt: null,\n source: \"stored\",\n token: data.apiKey.apiKey,\n user: null,\n };\n }\n\n return null;\n};\n\nexport const resolveTokenStatus = (\n token: ResolvedAuthToken\n): {\n expiresInSeconds: number | null;\n expired: boolean;\n} => {\n if (!token.expiresAt) {\n return { expired: false, expiresInSeconds: null };\n }\n\n const expiresAtMs = Date.parse(token.expiresAt);\n\n if (Number.isNaN(expiresAtMs)) {\n return { expired: false, expiresInSeconds: null };\n }\n\n const expiresInSeconds = Math.floor((expiresAtMs - Date.now()) / 1000);\n\n return {\n expired: expiresInSeconds <= 0,\n expiresInSeconds,\n };\n};\n","import fs from \"node:fs/promises\";\nimport path from \"node:path\";\n\nimport { createFsSource, loadSiteConfig } from \"@repo/previewing\";\n\nimport { CliError, EXIT_CODES } from \"../errors.js\";\n\nconst CONFIG_FILE = \"docs.json\";\n\nconst fileExists = async (filePath: string): Promise<boolean> => {\n try {\n await fs.access(filePath);\n return true;\n } catch {\n return false;\n }\n};\n\nexport const resolveDocsRoot = async (dir?: string): Promise<string> => {\n if (dir) {\n return path.resolve(process.cwd(), dir);\n }\n\n const candidates = [\n process.cwd(),\n path.join(process.cwd(), \"docs\"),\n path.join(process.cwd(), \"apps/docs\"),\n ];\n\n for (const candidate of candidates) {\n if (await fileExists(path.join(candidate, CONFIG_FILE))) {\n return candidate;\n }\n }\n\n return process.cwd();\n};\n\nexport const validateDocsRoot = async (root: string) => {\n const result = await loadSiteConfig(createFsSource(root));\n\n if (!result.ok) {\n throw new CliError(\n result.errors.join(\"\\n\"),\n EXIT_CODES.VALIDATION,\n `Make sure ${CONFIG_FILE} exists and is valid JSON.`\n );\n }\n\n return result;\n};\n","import path from \"node:path\";\n\nimport { log } from \"@clack/prompts\";\nimport { watch } from \"chokidar\";\n\nconst INVALIDATE_ENDPOINT = \"/blodemd-dev/invalidate\";\nconst WATCH_DEBOUNCE_MS = 100;\n\nconst normalizeRelativePath = (root: string, filePath: string) =>\n path.relative(root, filePath).split(path.sep).join(\"/\");\n\nconst isDirectoryEvent = (event: string) =>\n event === \"addDir\" || event === \"unlinkDir\";\n\nexport const createDevWatcher = ({\n port,\n root,\n}: {\n port: number;\n root: string;\n}) => {\n const watcher = watch(root, {\n ignoreInitial: true,\n ignored: [\"**/.git/**\", \"**/.next/**\", \"**/dist/**\", \"**/node_modules/**\"],\n });\n\n let flushTimer: NodeJS.Timeout | null = null;\n let pendingKind: \"config\" | \"content\" = \"content\";\n const pendingPaths = new Set<string>();\n\n const flush = async () => {\n flushTimer = null;\n\n const paths = [...pendingPaths];\n const kind = pendingKind;\n pendingPaths.clear();\n pendingKind = \"content\";\n\n if (!paths.length) {\n return;\n }\n\n try {\n const response = await fetch(\n `http://127.0.0.1:${port}${INVALIDATE_ENDPOINT}`,\n {\n body: JSON.stringify({ kind, paths }),\n headers: {\n \"Content-Type\": \"application/json\",\n },\n method: \"POST\",\n }\n );\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}`);\n }\n } catch (error) {\n log.error(\n `Failed to invalidate preview cache: ${\n error instanceof Error ? error.message : \"unknown error\"\n }`\n );\n }\n };\n\n watcher.on(\"all\", (event, changedPath) => {\n if (isDirectoryEvent(event)) {\n return;\n }\n\n const relativePath = normalizeRelativePath(root, changedPath);\n pendingPaths.add(relativePath);\n\n if (path.basename(changedPath) === \"docs.json\") {\n pendingKind = \"config\";\n }\n\n if (flushTimer) {\n clearTimeout(flushTimer);\n }\n\n flushTimer = setTimeout(() => {\n flush();\n }, WATCH_DEBOUNCE_MS);\n });\n\n return {\n async close() {\n if (flushTimer) {\n clearTimeout(flushTimer);\n await flush();\n }\n\n await watcher.close();\n },\n };\n};\n","import { spawn } from \"node:child_process\";\nimport { once } from \"node:events\";\nimport fs from \"node:fs/promises\";\nimport { createRequire } from \"node:module\";\nimport path from \"node:path\";\nimport { setTimeout as delay } from \"node:timers/promises\";\nimport { fileURLToPath } from \"node:url\";\n\nimport { intro, log } from \"@clack/prompts\";\nimport chalk from \"chalk\";\nimport open from \"open\";\n\nimport { CliError, EXIT_CODES, toCliError } from \"../errors.js\";\nimport { resolveDocsRoot, validateDocsRoot } from \"./resolve-root.js\";\nimport { createDevWatcher } from \"./watcher.js\";\n\nconst DEV_READY_ENDPOINT = \"/blodemd-dev/version\";\nconst DEV_READY_TIMEOUT_MS = 45_000;\n\nconst parsePositiveInteger = (value: string, label: string): number => {\n const parsed = Number.parseInt(value, 10);\n\n if (!Number.isInteger(parsed) || parsed <= 0) {\n throw new CliError(\n `${label} must be a positive integer.`,\n EXIT_CODES.VALIDATION\n );\n }\n\n return parsed;\n};\n\nconst fileExists = async (filePath: string): Promise<boolean> => {\n try {\n await fs.access(filePath);\n return true;\n } catch {\n return false;\n }\n};\n\n// --- Dev-server resolution ---\n\ninterface StandaloneServer {\n mode: \"standalone\";\n devServerDir: string;\n packagesDir: string;\n}\n\ninterface MonorepoServer {\n mode: \"monorepo\";\n repoRoot: string;\n}\n\ntype DevServerResolution = StandaloneServer | MonorepoServer;\n\n/**\n * Derive the CLI npm package root from the running script path.\n * The CLI entry point is at `<pkg-root>/dist/cli.mjs`.\n */\nconst resolveCliPackageRoot = (cliFilePath: string): string =>\n path.dirname(path.dirname(cliFilePath));\n\n/**\n * Check if a shipped dev-server exists alongside the CLI (npm-installed mode).\n * Verifies both the dev-server directory AND that `next` is resolvable\n * (it's a dependency when npm-installed, but not in the monorepo).\n */\nconst findStandaloneDevServer = async (\n cliPackageRoot: string\n): Promise<StandaloneServer | null> => {\n const devServerDir = path.join(cliPackageRoot, \"dev-server\");\n if (!(await fileExists(path.join(devServerDir, \"next.config.js\")))) {\n return null;\n }\n\n // Verify `next` is resolvable — this distinguishes npm-installed from\n // a monorepo checkout that happens to have dev-server/ from prepare-dist.\n try {\n createRequire(path.join(cliPackageRoot, \"package.json\")).resolve(\n \"next/package.json\"\n );\n } catch {\n return null;\n }\n\n return {\n devServerDir,\n mode: \"standalone\",\n packagesDir: path.join(cliPackageRoot, \"packages\"),\n };\n};\n\n/**\n * Resolve the `next` CLI binary from the blodemd package's own dependencies.\n */\nconst resolveNextBin = (cliPackageRoot: string): string => {\n const require = createRequire(path.join(cliPackageRoot, \"package.json\"));\n const nextPkgPath = require.resolve(\"next/package.json\");\n return path.join(path.dirname(nextPkgPath), \"dist\", \"bin\", \"next\");\n};\n\nconst findMonorepoRoot = async (start: string): Promise<string> => {\n let current = start;\n\n while (true) {\n const packageJsonPath = path.join(current, \"package.json\");\n if (await fileExists(packageJsonPath)) {\n const raw = await fs.readFile(packageJsonPath, \"utf8\");\n const parsed = JSON.parse(raw) as { workspaces?: string[] };\n const workspaces = parsed.workspaces ?? [];\n\n if (workspaces.includes(\"apps/*\") && workspaces.includes(\"packages/*\")) {\n return current;\n }\n }\n\n const parent = path.dirname(current);\n if (parent === current) {\n break;\n }\n current = parent;\n }\n\n throw new CliError(\n \"Could not locate the blodemd dev server.\",\n EXIT_CODES.ERROR,\n \"Make sure blodemd is installed correctly (npm i blodemd).\"\n );\n};\n\nconst resolveDevServer = async (\n cliFilePath: string\n): Promise<DevServerResolution> => {\n const cliPackageRoot = resolveCliPackageRoot(cliFilePath);\n\n // Try standalone mode first (npm-installed)\n const standalone = await findStandaloneDevServer(cliPackageRoot);\n if (standalone) {\n return standalone;\n }\n\n // Fall back to monorepo mode (development)\n const repoRoot = await findMonorepoRoot(path.dirname(cliFilePath));\n return { mode: \"monorepo\", repoRoot };\n};\n\nconst spawnDevServer = (\n server: DevServerResolution,\n { root, port }: { root: string; port: number }\n): ReturnType<typeof spawn> => {\n if (server.mode === \"standalone\") {\n // devServerDir is <pkg-root>/dev-server, so parent is the package root\n const cliPackageRoot = path.dirname(server.devServerDir);\n const nextBin = resolveNextBin(cliPackageRoot);\n\n return spawn(process.execPath, [nextBin, \"dev\"], {\n cwd: server.devServerDir,\n env: {\n ...process.env,\n BLODEMD_PACKAGES_DIR: server.packagesDir,\n DOCS_ROOT: root,\n // NODE_PATH lets require.resolve (used by Next.js transpilePackages)\n // find @repo/* packages from our shipped packages/ directory.\n NODE_PATH: [server.packagesDir, process.env.NODE_PATH]\n .filter(Boolean)\n .join(path.delimiter),\n PORT: String(port),\n },\n stdio: \"inherit\",\n });\n }\n\n const npmCommand = process.platform === \"win32\" ? \"npm.cmd\" : \"npm\";\n return spawn(npmCommand, [\"run\", \"dev\", \"--workspace=dev-server\"], {\n cwd: server.repoRoot,\n env: {\n ...process.env,\n DOCS_ROOT: root,\n PORT: String(port),\n },\n stdio: \"inherit\",\n });\n};\n\n// --- Server readiness ---\n\nconst waitForServer = async ({\n child,\n port,\n}: {\n child: ReturnType<typeof spawn>;\n port: number;\n}) => {\n const url = `http://localhost:${port}${DEV_READY_ENDPOINT}`;\n const startedAt = Date.now();\n\n while (Date.now() - startedAt < DEV_READY_TIMEOUT_MS) {\n if (child.exitCode !== null) {\n throw new CliError(\n \"The local dev server exited before it became ready.\",\n EXIT_CODES.ERROR\n );\n }\n\n try {\n const response = await fetch(url, {\n cache: \"no-store\",\n headers: {\n accept: \"application/json\",\n },\n });\n\n if (response.ok) {\n return;\n }\n } catch {\n // Server is still starting.\n }\n\n await delay(500);\n }\n\n throw new CliError(\n \"Timed out waiting for the local dev server to start.\",\n EXIT_CODES.ERROR\n );\n};\n\n// --- Main command ---\n\nexport const devCommand = async ({\n dir,\n openBrowser,\n port: portValue,\n}: {\n dir?: string;\n openBrowser: boolean;\n port: string;\n}) => {\n intro(chalk.bold(\"blodemd dev\"));\n\n try {\n const port = parsePositiveInteger(portValue, \"Port\");\n const root = await resolveDocsRoot(dir);\n await validateDocsRoot(root);\n\n const cliFilePath = fileURLToPath(import.meta.url);\n const server = await resolveDevServer(cliFilePath);\n const localUrl = `http://localhost:${port}`;\n\n log.info(`Docs root: ${chalk.cyan(root)}`);\n\n const child = spawnDevServer(server, { port, root });\n\n let watcher: Awaited<ReturnType<typeof createDevWatcher>> | null = null;\n let shuttingDown = false;\n\n const closeAll = async () => {\n if (shuttingDown) {\n return;\n }\n shuttingDown = true;\n\n if (watcher) {\n await watcher.close();\n watcher = null;\n }\n\n if (child.exitCode === null && !child.killed) {\n child.kill(\"SIGTERM\");\n }\n };\n\n process.once(\"SIGINT\", closeAll);\n process.once(\"SIGTERM\", closeAll);\n\n try {\n await waitForServer({ child, port });\n\n watcher = await createDevWatcher({ port, root });\n log.success(`Dev server running at ${chalk.cyan(localUrl)}`);\n\n if (openBrowser) {\n await open(localUrl);\n }\n\n const [code, signal] = (await once(child, \"exit\")) as [\n number | null,\n NodeJS.Signals | null,\n ];\n\n if (shuttingDown || signal === \"SIGINT\" || signal === \"SIGTERM\") {\n return;\n }\n\n if (code !== 0) {\n throw new CliError(\n `The local dev server exited with code ${code ?? \"unknown\"}.`,\n EXIT_CODES.ERROR\n );\n }\n } finally {\n await closeAll();\n process.removeListener(\"SIGINT\", closeAll);\n process.removeListener(\"SIGTERM\", closeAll);\n }\n } catch (error) {\n const cliError = toCliError(error);\n\n log.error(cliError.message);\n if (cliError.hint) {\n log.info(cliError.hint);\n }\n\n process.exitCode = cliError.exitCode;\n }\n};\n","// oxlint-disable no-use-before-define -- circular reference in callback pattern\nimport { createServer } from \"node:http\";\nimport type { Socket } from \"node:net\";\n\nimport { CliError, EXIT_CODES } from \"./errors.js\";\n\ninterface OAuthCallbackOptions {\n redirectUrl: URL;\n expectedState: string;\n timeoutMs: number;\n}\n\nconst SUCCESS_HTML =\n '<!doctype html><html><head><meta charset=\"utf-8\"/><title>Blode.md CLI</title></head><body><h2>Logged in! You can close this tab.</h2></body></html>';\n\nconst escapeHtml = (text: string): string =>\n text\n .replaceAll(\"&\", \"&\")\n .replaceAll(\"<\", \"<\")\n .replaceAll(\">\", \">\")\n .replaceAll('\"', \""\");\n\nconst errorHtml = (message: string): string =>\n `<!doctype html><html><head><meta charset=\"utf-8\"/><title>Blode.md CLI</title></head><body><h2>Login failed</h2><p>${escapeHtml(message)}</p></body></html>`;\n\nexport const waitForOAuthCode = (\n options: OAuthCallbackOptions\n): Promise<string> => {\n const host = options.redirectUrl.hostname;\n const port = Number(options.redirectUrl.port);\n const { pathname } = options.redirectUrl;\n\n if (!Number.isInteger(port) || port <= 0) {\n return Promise.reject(\n new CliError(\n \"OAuth redirect URL requires an explicit port\",\n EXIT_CODES.ERROR\n )\n );\n }\n\n // oxlint-disable-next-line eslint-plugin-promise/avoid-new -- wrapping callback-based HTTP server\n return new Promise<string>((resolve, reject) => {\n let settled = false;\n const sockets = new Set<Socket>();\n\n const settle = (ok: boolean, value: string | CliError): void => {\n if (settled) {\n return;\n }\n\n settled = true;\n clearTimeout(timer);\n\n httpServer.close(() => {\n if (ok) {\n resolve(value as string);\n } else {\n reject(value);\n }\n });\n\n // Destroy kept-alive connections so httpServer.close() can finish\n for (const socket of sockets) {\n socket.destroy();\n }\n };\n\n const httpServer = createServer((request, response) => {\n if (!request.url) {\n response.writeHead(400, { \"content-type\": \"text/html; charset=utf-8\" });\n response.end(errorHtml(\"Missing request URL\"));\n settle(\n false,\n new CliError(\n \"OAuth callback is missing a request URL\",\n EXIT_CODES.ERROR\n )\n );\n return;\n }\n\n const url = new URL(request.url, options.redirectUrl.origin);\n\n if (url.pathname !== pathname) {\n response.writeHead(404, { \"content-type\": \"text/html; charset=utf-8\" });\n response.end(errorHtml(\"Invalid callback path\"));\n return;\n }\n\n const providerError = url.searchParams.get(\"error\");\n if (providerError) {\n const description =\n url.searchParams.get(\"error_description\") ?? providerError;\n\n response.writeHead(400, { \"content-type\": \"text/html; charset=utf-8\" });\n response.end(errorHtml(description));\n\n settle(\n false,\n new CliError(\n `OAuth provider returned an error: ${description}`,\n EXIT_CODES.ERROR\n )\n );\n return;\n }\n\n const state = url.searchParams.get(\"state\");\n if (state !== options.expectedState) {\n response.writeHead(400, { \"content-type\": \"text/html; charset=utf-8\" });\n response.end(errorHtml(\"State verification failed\"));\n\n settle(\n false,\n new CliError(\"OAuth state verification failed\", EXIT_CODES.ERROR)\n );\n return;\n }\n\n const code = url.searchParams.get(\"code\");\n if (!code) {\n response.writeHead(400, { \"content-type\": \"text/html; charset=utf-8\" });\n response.end(errorHtml(\"Authorization code was missing\"));\n\n settle(\n false,\n new CliError(\n \"OAuth callback is missing an authorization code\",\n EXIT_CODES.ERROR\n )\n );\n return;\n }\n\n response.writeHead(200, { \"content-type\": \"text/html; charset=utf-8\" });\n response.end(SUCCESS_HTML);\n settle(true, code);\n });\n\n httpServer.on(\"connection\", (socket) => {\n sockets.add(socket);\n socket.once(\"close\", () => sockets.delete(socket));\n });\n\n httpServer.on(\"error\", (error) => {\n settle(\n false,\n new CliError(\n `Failed to start callback server on ${host}:${port}: ${error.message}`,\n EXIT_CODES.ERROR\n )\n );\n });\n\n const timer = setTimeout(() => {\n settle(\n false,\n new CliError(\"Login timed out. Please try again.\", EXIT_CODES.CANCELLED)\n );\n }, options.timeoutMs);\n\n httpServer.listen(port, host);\n });\n};\n","import { createHash, randomBytes } from \"node:crypto\";\n\nexport const createOAuthState = (): string => randomBytes(24).toString(\"hex\");\n\nexport const createCodeVerifier = (): string =>\n randomBytes(64).toString(\"base64url\");\n\nexport const createCodeChallenge = (verifier: string): string =>\n createHash(\"sha256\").update(verifier).digest().toString(\"base64url\");\n","import { spawnSync } from \"node:child_process\";\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\n\nimport {\n confirm,\n intro,\n isCancel,\n log,\n password,\n spinner,\n} from \"@clack/prompts\";\nimport chalk from \"chalk\";\nimport { Command } from \"commander\";\nimport open from \"open\";\n\nimport { resolveAuthToken, resolveTokenStatus } from \"./auth-session.js\";\nimport {\n BLODE_API_URL_ENV,\n BLODE_BRANCH_ENV,\n BLODE_COMMIT_MESSAGE_ENV,\n BLODE_PROJECT_ENV,\n DEFAULT_API_URL,\n DEFAULT_OAUTH_CALLBACK_PATH,\n DEFAULT_OAUTH_CALLBACK_PORT,\n DEFAULT_OAUTH_TIMEOUT_SECONDS,\n OAUTH_CLIENT_ID,\n} from \"./constants.js\";\nimport { devCommand } from \"./dev/command.js\";\nimport { CliError, EXIT_CODES, toCliError } from \"./errors.js\";\nimport { waitForOAuthCode } from \"./oauth-callback.js\";\nimport { exchangeAuthorizationCode } from \"./oauth-token.js\";\nimport {\n createCodeChallenge,\n createCodeVerifier,\n createOAuthState,\n} from \"./pkce.js\";\nimport {\n clearStoredCredentials,\n readAuthFile,\n writeStoredApiKey,\n writeStoredAuthSession,\n} from \"./storage.js\";\nimport {\n buildOAuthUrls,\n resolveSupabaseConfig,\n tokenResponseToStoredSession,\n} from \"./supabase.js\";\nimport type { DeploymentResponse } from \"./types.js\";\n\nconst CONFIG_FILE = \"docs.json\";\n\nconst TEXT_CONTENT_TYPES: Record<string, string> = {\n \".css\": \"text/css; charset=utf-8\",\n \".html\": \"text/html; charset=utf-8\",\n \".js\": \"text/javascript; charset=utf-8\",\n \".json\": \"application/json; charset=utf-8\",\n \".md\": \"text/markdown; charset=utf-8\",\n \".mdx\": \"text/markdown; charset=utf-8\",\n \".svg\": \"image/svg+xml\",\n \".txt\": \"text/plain; charset=utf-8\",\n \".yaml\": \"application/yaml; charset=utf-8\",\n \".yml\": \"application/yaml; charset=utf-8\",\n};\n\n// --- File helpers ---\n\nconst ensureFile = async (filePath: string, content: string): Promise<void> => {\n try {\n await fs.writeFile(filePath, content, { flag: \"wx\" });\n } catch {\n // File already exists\n }\n};\n\nconst fileExists = async (filePath: string): Promise<boolean> => {\n try {\n await fs.access(filePath);\n return true;\n } catch {\n return false;\n }\n};\n\nconst readConfig = async (\n root: string\n): Promise<{ name?: string; raw: string }> => {\n const raw = await fs.readFile(path.join(root, CONFIG_FILE), \"utf8\");\n const parsed = JSON.parse(raw) as { name?: string };\n return { name: parsed.name, raw };\n};\n\nconst resolveDocsRoot = async (dir?: string): Promise<string> => {\n if (dir) {\n return path.resolve(process.cwd(), dir);\n }\n\n const candidates = [\n process.cwd(),\n path.join(process.cwd(), \"docs\"),\n path.join(process.cwd(), \"apps/docs\"),\n ];\n\n for (const candidate of candidates) {\n if (await fileExists(path.join(candidate, CONFIG_FILE))) {\n return candidate;\n }\n }\n\n return process.cwd();\n};\n\nconst readGitValue = (gitArgs: string[]): string | undefined => {\n const result = spawnSync(\"git\", gitArgs, {\n encoding: \"utf8\",\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n });\n\n if (result.status !== 0) {\n return;\n }\n\n const value = result.stdout.trim();\n return value || undefined;\n};\n\nconst normalizeRelativePath = (root: string, filePath: string): string =>\n path.relative(root, filePath).split(path.sep).join(\"/\");\n\nconst shouldSkipEntry = (name: string): boolean =>\n name.startsWith(\".\") || name === \"node_modules\";\n\nconst collectFiles = async (root: string): Promise<string[]> => {\n const entries = await fs.readdir(root, { withFileTypes: true });\n const files: string[] = [];\n\n for (const entry of entries) {\n if (shouldSkipEntry(entry.name)) {\n continue;\n }\n\n const absolutePath = path.join(root, entry.name);\n if (entry.isDirectory()) {\n files.push(...(await collectFiles(absolutePath)));\n continue;\n }\n\n if (entry.isFile()) {\n files.push(absolutePath);\n }\n }\n\n return files.toSorted((left, right) => left.localeCompare(right));\n};\n\nconst getContentType = (filePath: string): string =>\n TEXT_CONTENT_TYPES[path.extname(filePath).toLowerCase()] ??\n \"application/octet-stream\";\n\nconst readJson = async (response: Response): Promise<unknown> => {\n const text = await response.text();\n if (!text) {\n return null;\n }\n\n try {\n return JSON.parse(text) as unknown;\n } catch {\n return text;\n }\n};\n\nconst requestJson = async <T>(\n url: string,\n init: RequestInit,\n message: string\n): Promise<T> => {\n const response = await fetch(url, init);\n const data = await readJson(response);\n if (!response.ok) {\n const detail =\n typeof data === \"string\" ? data : JSON.stringify(data ?? {}, null, 2);\n throw new Error(`${message}: ${response.status} ${detail}`);\n }\n\n return data as T;\n};\n\nconst parsePositiveInteger = (value: string, label: string): number => {\n const parsed = Number.parseInt(value, 10);\n\n if (!Number.isInteger(parsed) || parsed <= 0) {\n throw new CliError(\n `${label} must be a positive integer.`,\n EXIT_CODES.VALIDATION\n );\n }\n\n return parsed;\n};\n\nconst reportCommandError = (prefix: string, error: unknown): void => {\n const cliError = toCliError(error);\n\n log.error(`${prefix}: ${cliError.message}`);\n if (cliError.hint) {\n log.info(cliError.hint);\n }\n log.info(\"Failed\");\n process.exitCode = cliError.exitCode;\n};\n\n// --- Auth helpers ---\n\nconst fetchUserEmail = async (\n apiUrl: string,\n token: string\n): Promise<string | null> => {\n try {\n const user = await requestJson<{ email: string }>(\n `${apiUrl}/auth/me`,\n { headers: { Authorization: `Bearer ${token}` } },\n \"Failed to fetch user info\"\n );\n return user.email;\n } catch {\n return null;\n }\n};\n\n// --- Push helpers ---\n\ninterface PushConfig {\n project: string;\n apiUrl: string;\n authToken: string;\n branch: string;\n commitMessage?: string;\n}\n\nconst resolvePushConfig = async (\n config: { name?: string },\n options: {\n apiKey?: string;\n apiUrl?: string;\n branch?: string;\n message?: string;\n project?: string;\n }\n): Promise<PushConfig> => {\n const project =\n options.project ?? process.env[BLODE_PROJECT_ENV] ?? config.name;\n const apiUrl =\n options.apiUrl ?? process.env[BLODE_API_URL_ENV] ?? DEFAULT_API_URL;\n\n const resolved = await resolveAuthToken(options.apiKey);\n const authToken = resolved?.token;\n\n const branch =\n options.branch ??\n process.env[BLODE_BRANCH_ENV] ??\n process.env.GITHUB_REF_NAME ??\n readGitValue([\"rev-parse\", \"--abbrev-ref\", \"HEAD\"]) ??\n \"main\";\n const commitMessage =\n options.message ??\n process.env[BLODE_COMMIT_MESSAGE_ENV] ??\n readGitValue([\"log\", \"-1\", \"--pretty=%s\"]);\n\n if (!project) {\n throw new Error(\n 'Missing project slug. Set \"name\" in docs.json, pass --project, or set BLODEMD_PROJECT.'\n );\n }\n if (!authToken) {\n throw new Error(\n 'Missing credentials. Run \"blodemd login\", pass --api-key, or set BLODEMD_API_KEY.'\n );\n }\n\n return { apiUrl, authToken, branch, commitMessage, project };\n};\n\nconst autoCreateProject = async (\n project: string,\n apiUrl: string,\n headers: Record<string, string>\n): Promise<boolean> => {\n const authData = await readAuthFile();\n if (!authData?.session) {\n throw new Error(\n `Project \"${project}\" not found. Create it at blode.md or login with \"blodemd login\" to auto-create.`\n );\n }\n\n const shouldCreate = await confirm({\n message: `Project \"${project}\" doesn't exist. Create it?`,\n });\n\n if (isCancel(shouldCreate) || !shouldCreate) {\n return false;\n }\n\n const createResult = await requestJson<{\n project: { id: string; slug: string };\n token: string;\n }>(\n new URL(\"/projects\", apiUrl).toString(),\n {\n body: JSON.stringify({ name: project, slug: project }),\n headers,\n method: \"POST\",\n },\n \"Failed to create project\"\n );\n\n log.success(`Project ${chalk.cyan(createResult.project.slug)} created`);\n log.info(`API key for CI: ${chalk.dim(createResult.token)}`);\n return true;\n};\n\n// 4 MB limit keeps each batch well under Vercel's 4.5 MB serverless body cap\nconst MAX_BATCH_BYTES = 4 * 1024 * 1024;\n\nconst uploadFiles = async (\n files: string[],\n root: string,\n apiPath: (suffix: string) => string,\n deploymentId: string,\n headers: Record<string, string>,\n s: ReturnType<typeof spinner>\n) => {\n s.start(`Uploading ${files.length} files`);\n\n const items = await Promise.all(\n files.map(async (filePath) => {\n const content = await fs.readFile(filePath);\n return {\n contentBase64: content.toString(\"base64\"),\n contentType: getContentType(filePath),\n path: normalizeRelativePath(root, filePath),\n };\n })\n );\n\n // Split into size-bounded batches\n const batches: (typeof items)[] = [];\n let current: typeof items = [];\n let currentBytes = 0;\n\n for (const item of items) {\n const itemBytes = item.contentBase64.length + item.path.length + 64;\n if (current.length > 0 && currentBytes + itemBytes > MAX_BATCH_BYTES) {\n batches.push(current);\n current = [];\n currentBytes = 0;\n }\n current.push(item);\n currentBytes += itemBytes;\n }\n if (current.length > 0) {\n batches.push(current);\n }\n\n let uploaded = 0;\n for (const batch of batches) {\n await requestJson(\n apiPath(`/${deploymentId}/files/batch`),\n {\n body: JSON.stringify({ files: batch }),\n headers,\n method: \"POST\",\n },\n \"Failed to upload files\"\n );\n uploaded += batch.length;\n s.message(`Uploading files (${uploaded}/${files.length})`);\n }\n\n s.stop(`Uploaded ${chalk.cyan(String(files.length))} files`);\n};\n\n// --- CLI ---\n\nconst program = new Command();\n\nprogram.name(\"blodemd\").description(\"Blode.md CLI\").version(\"0.0.3\");\n\n// login\n\nprogram\n .command(\"login\")\n .description(\"Authenticate with Blode.md\")\n .option(\"--token\", \"Paste an API key instead of using browser login\")\n .option(\n \"--port <port>\",\n \"Loopback callback port\",\n String(DEFAULT_OAUTH_CALLBACK_PORT)\n )\n .option(\n \"--timeout <seconds>\",\n \"OAuth timeout in seconds\",\n String(DEFAULT_OAUTH_TIMEOUT_SECONDS)\n )\n .option(\"--no-open\", \"Print URL instead of opening the browser\")\n .action(\n async (options: {\n token?: boolean;\n port: string;\n timeout: string;\n open: boolean;\n }) => {\n intro(chalk.bold(\"blodemd login\"));\n\n try {\n if (options.token) {\n const apiKey = await password({\n message: \"Enter your API key\",\n validate: (value) => {\n if (!value) {\n return \"API key is required.\";\n }\n },\n });\n\n if (isCancel(apiKey)) {\n log.warn(\"Cancelled\");\n return;\n }\n\n await writeStoredApiKey({ apiKey, type: \"api-key\" });\n\n const prefix = apiKey.split(\".\")[0] ?? apiKey.slice(0, 12);\n log.success(`Authenticated as ${chalk.cyan(prefix)}`);\n log.info(\"Done\");\n return;\n }\n\n // OAuth 2.1 authorization code flow with PKCE\n const config = resolveSupabaseConfig();\n const { authorizeUrl, tokenUrl } = buildOAuthUrls(config);\n const clientId = OAUTH_CLIENT_ID;\n\n const port = parsePositiveInteger(options.port, \"Port\");\n const timeoutSeconds = parsePositiveInteger(options.timeout, \"Timeout\");\n const redirectUrl = new URL(\n `http://127.0.0.1:${port}${DEFAULT_OAUTH_CALLBACK_PATH}`\n );\n\n const state = createOAuthState();\n const codeVerifier = createCodeVerifier();\n const codeChallenge = createCodeChallenge(codeVerifier);\n\n const authUrl = new URL(authorizeUrl);\n authUrl.searchParams.set(\"response_type\", \"code\");\n authUrl.searchParams.set(\"client_id\", clientId);\n authUrl.searchParams.set(\"redirect_uri\", redirectUrl.toString());\n authUrl.searchParams.set(\"code_challenge\", codeChallenge);\n authUrl.searchParams.set(\"code_challenge_method\", \"S256\");\n authUrl.searchParams.set(\"state\", state);\n authUrl.searchParams.set(\"scope\", \"openid email profile\");\n\n const callbackPromise = waitForOAuthCode({\n expectedState: state,\n redirectUrl,\n timeoutMs: timeoutSeconds * 1000,\n });\n\n if (options.open) {\n log.info(\"Opening browser for authentication...\");\n log.info(\n `If the browser doesn't open, visit: ${chalk.cyan(authUrl.toString())}`\n );\n await open(authUrl.toString());\n } else {\n log.info(\"Open this URL to continue authentication:\");\n log.info(chalk.cyan(authUrl.toString()));\n }\n\n const code = await callbackPromise;\n\n const tokenResponse = await exchangeAuthorizationCode(\n { clientId, tokenUrl },\n code,\n codeVerifier,\n redirectUrl.toString()\n );\n\n const storedSession = tokenResponseToStoredSession(tokenResponse);\n await writeStoredAuthSession(storedSession);\n\n const email =\n storedSession.user?.email ??\n (await fetchUserEmail(\n process.env[BLODE_API_URL_ENV] ?? DEFAULT_API_URL,\n storedSession.accessToken\n ));\n\n if (email) {\n log.success(`Logged in as ${chalk.cyan(email)}`);\n } else {\n log.success(\"Logged in successfully.\");\n }\n\n log.info(\"Done\");\n } catch (error: unknown) {\n reportCommandError(\"Login failed\", error);\n }\n }\n );\n\n// logout\n\nprogram\n .command(\"logout\")\n .description(\"Remove stored credentials\")\n .action(async () => {\n intro(chalk.bold(\"blodemd logout\"));\n\n try {\n const existing = await readAuthFile();\n await clearStoredCredentials();\n\n if (existing?.session || existing?.apiKey) {\n log.success(\"Credentials removed.\");\n } else {\n log.info(\"No stored credentials found.\");\n }\n log.info(\"Done\");\n } catch (error: unknown) {\n reportCommandError(\"Logout failed\", error);\n }\n });\n\n// whoami\n\nprogram\n .command(\"whoami\")\n .description(\"Show current authentication\")\n .action(async () => {\n try {\n const resolved = await resolveAuthToken();\n\n if (!resolved) {\n log.warn('Not logged in. Run \"blodemd login\" to authenticate.');\n return;\n }\n\n if (resolved.source === \"environment\") {\n log.info(\"Authenticated via BLODEMD_API_KEY environment variable\");\n return;\n }\n\n // API keys have no expiry and no user info from JWT\n if (!resolved.expiresAt && !resolved.user) {\n const prefix =\n resolved.token.split(\".\")[0] ?? resolved.token.slice(0, 12);\n log.info(`Logged in with API key ${chalk.cyan(prefix)}`);\n return;\n }\n\n const status = resolveTokenStatus(resolved);\n\n const email =\n resolved.user?.email ??\n (await fetchUserEmail(\n process.env[BLODE_API_URL_ENV] ?? DEFAULT_API_URL,\n resolved.token\n ));\n\n if (email) {\n log.info(`Logged in as ${chalk.cyan(email)}`);\n } else {\n log.info(\"Logged in (could not fetch user details).\");\n }\n\n if (resolved.expiresAt && status.expired) {\n log.warn(\n 'Session has expired. Run \"blodemd login\" to re-authenticate.'\n );\n }\n } catch (error: unknown) {\n reportCommandError(\"Whoami failed\", error);\n }\n });\n\n// init\n\nprogram\n .command(\"init\")\n .description(\"Scaffold a docs folder\")\n .argument(\"[dir]\", \"target directory\", \"docs\")\n .action(async (dir: string) => {\n intro(chalk.bold(\"blodemd init\"));\n\n try {\n const root = path.resolve(process.cwd(), dir);\n await fs.mkdir(root, { recursive: true });\n\n const docsJson = {\n $schema: \"https://mintlify.com/docs.json\",\n colors: { primary: \"#0D9373\" },\n name: \"my-project\",\n navigation: {\n groups: [{ group: \"Getting Started\", pages: [\"index\"] }],\n },\n theme: \"mint\",\n };\n\n await ensureFile(\n path.join(root, CONFIG_FILE),\n `${JSON.stringify(docsJson, null, 2)}\\n`\n );\n await ensureFile(\n path.join(root, \"index.mdx\"),\n \"---\\ntitle: Welcome\\n---\\n\\nStart writing your docs here.\\n\"\n );\n\n log.success(`Docs scaffolded in ${chalk.cyan(root)}`);\n log.info(`Set ${chalk.cyan(\"name\")} in docs.json to your project slug.`);\n log.info(\"Done\");\n } catch (error: unknown) {\n reportCommandError(\"Init failed\", error);\n }\n });\n\n// validate\n\nprogram\n .command(\"validate\")\n .description(\"Validate docs.json\")\n .argument(\"[dir]\", \"docs directory\")\n .action(async (dir?: string) => {\n intro(chalk.bold(\"blodemd validate\"));\n\n try {\n const root = await resolveDocsRoot(dir);\n await readConfig(root);\n log.success(`${chalk.cyan(CONFIG_FILE)} is valid.`);\n log.info(\"Done\");\n } catch (error: unknown) {\n reportCommandError(\"Validation failed\", error);\n }\n });\n\n// push\n\nprogram\n .command(\"push\")\n .description(\"Deploy docs\")\n .argument(\"[dir]\", \"docs directory\")\n .option(\"--project <slug>\", \"project slug (env: BLODEMD_PROJECT)\")\n .option(\"--api-url <url>\", \"API URL (env: BLODEMD_API_URL)\")\n .option(\"--api-key <token>\", \"API key (env: BLODEMD_API_KEY)\")\n .option(\"--branch <name>\", \"git branch (env: BLODEMD_BRANCH)\")\n .option(\"--message <msg>\", \"deploy message (env: BLODEMD_COMMIT_MESSAGE)\")\n .action(\n async (\n dir: string | undefined,\n options: {\n apiKey?: string;\n apiUrl?: string;\n branch?: string;\n message?: string;\n project?: string;\n }\n ) => {\n intro(chalk.bold(\"blodemd push\"));\n const s = spinner();\n\n try {\n const root = await resolveDocsRoot(dir);\n\n s.start(\"Validating configuration\");\n const config = await readConfig(root);\n s.stop(\"Configuration valid\");\n\n const { project, apiUrl, authToken, branch, commitMessage } =\n await resolvePushConfig(config, options);\n\n s.start(\"Collecting files\");\n const files = await collectFiles(root);\n if (files.length === 0) {\n throw new Error(\"No files found to deploy.\");\n }\n s.stop(`Found ${chalk.cyan(String(files.length))} files`);\n\n const headers = {\n Authorization: `Bearer ${authToken}`,\n \"Content-Type\": \"application/json\",\n };\n\n const apiPath = (suffix: string): string =>\n new URL(\n `/projects/slug/${project}/deployments${suffix}`,\n apiUrl\n ).toString();\n\n const createDeploymentBody = JSON.stringify({ branch, commitMessage });\n\n // Try creating the deployment — if 404, offer to create the project\n s.start(\"Creating deployment\");\n let deployment: DeploymentResponse;\n try {\n deployment = await requestJson<DeploymentResponse>(\n apiPath(\"\"),\n { body: createDeploymentBody, headers, method: \"POST\" },\n \"Failed to create deployment\"\n );\n } catch (error: unknown) {\n const errorMessage = error instanceof Error ? error.message : \"\";\n if (!errorMessage.includes(\"404\")) {\n throw error;\n }\n\n s.stop(\"Project not found\");\n\n const created = await autoCreateProject(project, apiUrl, headers);\n if (!created) {\n log.info(\"Cancelled\");\n return;\n }\n\n s.start(\"Creating deployment\");\n deployment = await requestJson<DeploymentResponse>(\n apiPath(\"\"),\n { body: createDeploymentBody, headers, method: \"POST\" },\n \"Failed to create deployment\"\n );\n }\n s.stop(`Deployment ${chalk.cyan(deployment.id)} created`);\n\n await uploadFiles(files, root, apiPath, deployment.id, headers, s);\n\n s.start(\"Finalizing deployment\");\n const finalized = await requestJson<DeploymentResponse>(\n apiPath(`/${deployment.id}/finalize`),\n {\n body: JSON.stringify({ promote: true }),\n headers,\n method: \"POST\",\n },\n \"Failed to finalize deployment\"\n );\n s.stop(\"Deployment finalized\");\n\n log.success(`Published ${chalk.cyan(finalized.id)}`);\n if (finalized.manifestUrl) {\n log.info(`Manifest: ${finalized.manifestUrl}`);\n }\n if (typeof finalized.fileCount === \"number\") {\n log.info(`Files: ${finalized.fileCount}`);\n }\n\n log.info(\"Done\");\n } catch (error: unknown) {\n s.stop(\"Failed\");\n reportCommandError(\"Push failed\", error);\n }\n }\n );\n\n// dev\n\nprogram\n .command(\"dev\")\n .description(\"Start the local docs dev server\")\n .option(\"-p, --port <port>\", \"Port number\", \"3030\")\n .option(\"-d, --dir <dir>\", \"Docs directory\")\n .option(\"--no-open\", \"Don't open browser\")\n .action(\n async (options: { dir?: string; open?: boolean; port: string }) =>\n await devCommand({\n dir: options.dir,\n openBrowser: options.open ?? true,\n port: options.port,\n })\n );\n\nprogram.parse();\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"cli.mjs","names":["CONFIG_FILE","CONFIG_FILE","fileExists","normalizeRelativePath","parsePositiveInteger","delay","createServer"],"sources":["../src/constants.ts","../src/jwt.ts","../src/errors.ts","../src/oauth-token.ts","../src/storage.ts","../src/supabase.ts","../src/auth-session.ts","../src/site-config.ts","../src/dev/resolve-root.ts","../src/dev/watcher.ts","../src/dev/command.ts","../src/oauth-callback.ts","../src/pkce.ts","../src/runtime.ts","../src/cli.ts"],"sourcesContent":["import { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\nexport const CLI_NAME = \"blodemd\";\n\nexport const BLODE_TOKEN_ENV = \"BLODEMD_API_KEY\";\nexport const BLODE_API_URL_ENV = \"BLODEMD_API_URL\";\nexport const BLODE_PROJECT_ENV = \"BLODEMD_PROJECT\";\nexport const BLODE_BRANCH_ENV = \"BLODEMD_BRANCH\";\nexport const BLODE_COMMIT_MESSAGE_ENV = \"BLODEMD_COMMIT_MESSAGE\";\n\nexport const DEFAULT_API_URL = \"https://api.blode.md\";\nexport const DEFAULT_SUPABASE_URL = \"https://bwnxwgkgyklzzmpbzuoz.supabase.co\";\n\nexport const OAUTH_CLIENT_ID = \"6b5f9860-fe96-4a83-b1ad-266260523c91\";\n\nexport const DEFAULT_OAUTH_CALLBACK_PORT = 8787;\nexport const DEFAULT_OAUTH_CALLBACK_PATH = \"/auth/callback\";\nexport const DEFAULT_OAUTH_TIMEOUT_SECONDS = 180;\n\nconst getDefaultConfigBaseDir = (): string => {\n if (process.platform === \"win32\") {\n return process.env.APPDATA ?? join(homedir(), \"AppData\", \"Roaming\");\n }\n\n return process.env.XDG_CONFIG_HOME ?? join(homedir(), \".config\");\n};\n\nconst configBaseDir = getDefaultConfigBaseDir();\n\nexport const CONFIG_DIR = join(configBaseDir, CLI_NAME);\nexport const CREDENTIALS_FILE = join(CONFIG_DIR, \"credentials.json\");\n","export interface JwtClaims {\n exp?: number;\n email?: string;\n sub?: string;\n}\n\nconst parseJwtBase64Url = (input: string): string => {\n const normalized = input.replaceAll(\"-\", \"+\").replaceAll(\"_\", \"/\");\n const padded = normalized.padEnd(Math.ceil(normalized.length / 4) * 4, \"=\");\n return Buffer.from(padded, \"base64\").toString(\"utf8\");\n};\n\nexport const parseJwtClaims = (token: string): JwtClaims | null => {\n const parts = token.split(\".\");\n const payloadPart = parts.at(1);\n\n if (!payloadPart) {\n return null;\n }\n\n try {\n const payload = parseJwtBase64Url(payloadPart);\n const parsed = JSON.parse(payload) as unknown;\n\n if (typeof parsed !== \"object\" || parsed === null) {\n return null;\n }\n\n const claims = parsed as Record<string, unknown>;\n\n return {\n email: typeof claims.email === \"string\" ? claims.email : undefined,\n exp: typeof claims.exp === \"number\" ? claims.exp : undefined,\n sub: typeof claims.sub === \"string\" ? claims.sub : undefined,\n };\n } catch {\n return null;\n }\n};\n","export const EXIT_CODES = {\n AUTH_REQUIRED: 4,\n CANCELLED: 2,\n ERROR: 1,\n NETWORK: 5,\n SUCCESS: 0,\n VALIDATION: 3,\n} as const;\n\ntype ExitCode = (typeof EXIT_CODES)[keyof typeof EXIT_CODES];\n\nexport class CliError extends Error {\n readonly exitCode: ExitCode;\n readonly hint: string | null;\n\n constructor(\n message: string,\n exitCode: ExitCode = EXIT_CODES.ERROR,\n hint?: string\n ) {\n super(message);\n this.name = \"CliError\";\n this.exitCode = exitCode;\n this.hint = hint ?? null;\n }\n}\n\nexport const toCliError = (error: unknown): CliError => {\n if (error instanceof CliError) {\n return error;\n }\n\n if (error instanceof Error) {\n if (error instanceof TypeError && error.message.includes(\"fetch\")) {\n return new CliError(\n \"Cannot connect to Blode.md API.\",\n EXIT_CODES.NETWORK,\n \"Check your internet connection and API URL configuration.\"\n );\n }\n\n if (error.name === \"TimeoutError\" || error.name === \"AbortError\") {\n return new CliError(\n \"Request timed out.\",\n EXIT_CODES.NETWORK,\n \"The API may be unavailable. Try again later.\"\n );\n }\n\n return new CliError(error.message, EXIT_CODES.ERROR);\n }\n\n return new CliError(\"Unknown error\", EXIT_CODES.ERROR);\n};\n","import { CliError, EXIT_CODES } from \"./errors.js\";\n\nexport interface OAuthTokenConfig {\n tokenUrl: string;\n clientId: string;\n}\n\nexport interface OAuthTokenResponse {\n access_token: string;\n refresh_token?: string;\n token_type: string;\n expires_in: number;\n}\n\nconst postTokenRequest = async (\n url: string,\n body: URLSearchParams\n): Promise<OAuthTokenResponse> => {\n const response = await fetch(url, {\n body: body.toString(),\n headers: { \"content-type\": \"application/x-www-form-urlencoded\" },\n method: \"POST\",\n });\n\n if (!response.ok) {\n const text = await response.text().catch(() => \"\");\n throw new CliError(\n `OAuth token request failed (${response.status}): ${text}`,\n EXIT_CODES.AUTH_REQUIRED\n );\n }\n\n return (await response.json()) as OAuthTokenResponse;\n};\n\nexport const exchangeAuthorizationCode = (\n config: OAuthTokenConfig,\n code: string,\n codeVerifier: string,\n redirectUri: string\n): Promise<OAuthTokenResponse> => {\n const body = new URLSearchParams({\n client_id: config.clientId,\n code,\n code_verifier: codeVerifier,\n grant_type: \"authorization_code\",\n redirect_uri: redirectUri,\n });\n\n return postTokenRequest(config.tokenUrl, body);\n};\n\nexport const refreshAccessToken = (\n config: OAuthTokenConfig,\n refreshToken: string\n): Promise<OAuthTokenResponse> => {\n const body = new URLSearchParams({\n client_id: config.clientId,\n grant_type: \"refresh_token\",\n refresh_token: refreshToken,\n });\n\n return postTokenRequest(config.tokenUrl, body);\n};\n","import { mkdir, readFile, rm, writeFile } from \"node:fs/promises\";\n\nimport { CONFIG_DIR, CREDENTIALS_FILE } from \"./constants.js\";\nimport { CliError, EXIT_CODES } from \"./errors.js\";\nimport type {\n ApiKeyCredentials,\n AuthFileData,\n StoredAuthSession,\n} from \"./types.js\";\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null;\n\nconst parseStoredAuthSession = (value: unknown): StoredAuthSession | null => {\n if (!isRecord(value)) {\n return null;\n }\n\n if (typeof value.accessToken !== \"string\") {\n return null;\n }\n\n if (value.refreshToken !== null && typeof value.refreshToken !== \"string\") {\n return null;\n }\n\n if (value.expiresAt !== null && typeof value.expiresAt !== \"string\") {\n return null;\n }\n\n const { user } = value;\n if (\n user !== null &&\n (!isRecord(user) ||\n typeof user.id !== \"string\" ||\n (user.email !== null && typeof user.email !== \"string\"))\n ) {\n return null;\n }\n\n if (typeof value.createdAt !== \"string\") {\n return null;\n }\n\n const parsedUser =\n user === null || !isRecord(user)\n ? null\n : {\n email: (user.email as string | null) ?? null,\n id: user.id as string,\n };\n\n return {\n accessToken: value.accessToken,\n createdAt: value.createdAt,\n expiresAt: (value.expiresAt as string | null) ?? null,\n refreshToken: (value.refreshToken as string | null) ?? null,\n user: parsedUser,\n };\n};\n\nconst parseApiKeyCredentials = (value: unknown): ApiKeyCredentials | null => {\n if (!isRecord(value)) {\n return null;\n }\n\n if (typeof value.apiKey !== \"string\") {\n return null;\n }\n\n return { apiKey: value.apiKey, type: \"api-key\" };\n};\n\nexport const readAuthFile = async (): Promise<AuthFileData | null> => {\n try {\n const raw = await readFile(CREDENTIALS_FILE, \"utf8\");\n const parsed = JSON.parse(raw) as unknown;\n\n if (!isRecord(parsed) || parsed.version !== 1) {\n throw new CliError(\n `Invalid credentials format in ${CREDENTIALS_FILE}`,\n EXIT_CODES.ERROR\n );\n }\n\n return {\n apiKey: parseApiKeyCredentials(parsed.apiKey) ?? undefined,\n session: parseStoredAuthSession(parsed.session) ?? undefined,\n version: 1,\n };\n } catch (error) {\n if (isRecord(error) && error.code === \"ENOENT\") {\n return null;\n }\n\n if (error instanceof CliError) {\n throw error;\n }\n\n return null;\n }\n};\n\nexport const readStoredAuthSession =\n async (): Promise<StoredAuthSession | null> => {\n const data = await readAuthFile();\n return data?.session ?? null;\n };\n\nexport const readStoredApiKey = async (): Promise<ApiKeyCredentials | null> => {\n const data = await readAuthFile();\n return data?.apiKey ?? null;\n};\n\nconst writeAuthFile = async (data: AuthFileData): Promise<void> => {\n await mkdir(CONFIG_DIR, { mode: 0o700, recursive: true });\n await writeFile(CREDENTIALS_FILE, `${JSON.stringify(data, null, 2)}\\n`, {\n encoding: \"utf8\",\n mode: 0o600,\n });\n};\n\nexport const writeStoredAuthSession = async (\n session: StoredAuthSession\n): Promise<void> => {\n await writeAuthFile({\n session,\n version: 1,\n });\n};\n\nexport const writeStoredApiKey = async (\n apiKey: ApiKeyCredentials\n): Promise<void> => {\n await writeAuthFile({\n apiKey,\n version: 1,\n });\n};\n\nexport const clearStoredCredentials = async (): Promise<void> => {\n await rm(CREDENTIALS_FILE, { force: true });\n};\n","import { DEFAULT_SUPABASE_URL } from \"./constants.js\";\nimport { parseJwtClaims } from \"./jwt.js\";\nimport type { OAuthTokenResponse } from \"./oauth-token.js\";\nimport type { StoredAuthSession, SupabaseConfig } from \"./types.js\";\n\nexport const resolveSupabaseConfig = (): SupabaseConfig => {\n const url =\n process.env.SUPABASE_URL ??\n process.env.NEXT_PUBLIC_SUPABASE_URL ??\n DEFAULT_SUPABASE_URL;\n\n return { url };\n};\n\nexport const buildOAuthUrls = (\n config: SupabaseConfig\n): {\n authorizeUrl: string;\n tokenUrl: string;\n} => ({\n authorizeUrl: `${config.url}/auth/v1/oauth/authorize`,\n tokenUrl: `${config.url}/auth/v1/oauth/token`,\n});\n\nexport const tokenResponseToStoredSession = (\n response: OAuthTokenResponse\n): StoredAuthSession => {\n const claims = parseJwtClaims(response.access_token);\n\n let expiresAt: string | null = null;\n if (typeof claims?.exp === \"number\") {\n expiresAt = new Date(claims.exp * 1000).toISOString();\n } else if (response.expires_in > 0) {\n expiresAt = new Date(Date.now() + response.expires_in * 1000).toISOString();\n }\n\n return {\n accessToken: response.access_token,\n createdAt: new Date().toISOString(),\n expiresAt,\n refreshToken: response.refresh_token ?? null,\n user:\n claims?.sub || claims?.email\n ? {\n email: claims.email ?? null,\n id: claims.sub ?? \"unknown\",\n }\n : null,\n };\n};\n","import { BLODE_TOKEN_ENV, OAUTH_CLIENT_ID } from \"./constants.js\";\nimport { parseJwtClaims } from \"./jwt.js\";\nimport { refreshAccessToken } from \"./oauth-token.js\";\nimport {\n clearStoredCredentials,\n readAuthFile,\n writeStoredAuthSession,\n} from \"./storage.js\";\nimport {\n buildOAuthUrls,\n resolveSupabaseConfig,\n tokenResponseToStoredSession,\n} from \"./supabase.js\";\nimport type { ResolvedAuthToken, StoredAuthSession } from \"./types.js\";\n\nconst expiresInMs = (session: StoredAuthSession): number | null => {\n if (!session.expiresAt) {\n return null;\n }\n\n const expiresAtMs = Date.parse(session.expiresAt);\n\n if (Number.isNaN(expiresAtMs)) {\n return null;\n }\n\n return expiresAtMs - Date.now();\n};\n\nconst isExpired = (session: StoredAuthSession): boolean => {\n const ms = expiresInMs(session);\n return ms !== null && ms <= 0;\n};\n\nconst shouldRefresh = (session: StoredAuthSession): boolean => {\n const ms = expiresInMs(session);\n return ms !== null && ms <= 60_000;\n};\n\nconst tokenFromRaw = (\n token: string,\n source: ResolvedAuthToken[\"source\"]\n): ResolvedAuthToken => {\n const claims = parseJwtClaims(token);\n\n const expiresAt =\n typeof claims?.exp === \"number\"\n ? new Date(claims.exp * 1000).toISOString()\n : null;\n\n return {\n expiresAt,\n source,\n token,\n user:\n claims?.sub || claims?.email\n ? { email: claims.email ?? null, id: claims.sub ?? \"unknown\" }\n : null,\n };\n};\n\nconst sessionToResolvedToken = (\n session: StoredAuthSession\n): ResolvedAuthToken => ({\n expiresAt: session.expiresAt,\n source: \"stored\",\n token: session.accessToken,\n user: session.user,\n});\n\nexport const resolveAuthToken = async (\n optApiKey?: string\n): Promise<ResolvedAuthToken | null> => {\n const envToken = (optApiKey ?? process.env[BLODE_TOKEN_ENV])?.trim();\n\n if (envToken) {\n return tokenFromRaw(envToken, optApiKey ? \"flag\" : \"environment\");\n }\n\n const data = await readAuthFile();\n const session = data?.session;\n\n if (session) {\n if (!(shouldRefresh(session) || isExpired(session))) {\n return sessionToResolvedToken(session);\n }\n\n if (session.refreshToken) {\n try {\n const config = resolveSupabaseConfig();\n const { tokenUrl } = buildOAuthUrls(config);\n const tokenResponse = await refreshAccessToken(\n { clientId: OAUTH_CLIENT_ID, tokenUrl },\n session.refreshToken\n );\n const updatedSession = tokenResponseToStoredSession(tokenResponse);\n await writeStoredAuthSession(updatedSession);\n\n return sessionToResolvedToken(updatedSession);\n } catch {\n // Refresh failed — fall through to expiry check\n }\n }\n\n if (isExpired(session)) {\n await clearStoredCredentials();\n return null;\n }\n\n return sessionToResolvedToken(session);\n }\n\n if (data?.apiKey) {\n return {\n expiresAt: null,\n source: \"stored\",\n token: data.apiKey.apiKey,\n user: null,\n };\n }\n\n return null;\n};\n\nexport const resolveTokenStatus = (\n token: ResolvedAuthToken\n): {\n expiresInSeconds: number | null;\n expired: boolean;\n} => {\n if (!token.expiresAt) {\n return { expired: false, expiresInSeconds: null };\n }\n\n const expiresAtMs = Date.parse(token.expiresAt);\n\n if (Number.isNaN(expiresAtMs)) {\n return { expired: false, expiresInSeconds: null };\n }\n\n const expiresInSeconds = Math.floor((expiresAtMs - Date.now()) / 1000);\n\n return {\n expired: expiresInSeconds <= 0,\n expiresInSeconds,\n };\n};\n","import type { SiteConfig } from \"@repo/models\";\nimport { createFsSource, loadSiteConfig } from \"@repo/previewing\";\n\nimport { CliError, EXIT_CODES } from \"./errors.js\";\n\nconst CONFIG_FILE = \"docs.json\";\n\nexport interface ValidatedSiteConfigResult {\n config: SiteConfig;\n warnings: string[];\n}\n\nexport const loadValidatedSiteConfig = async (\n root: string\n): Promise<ValidatedSiteConfigResult> => {\n const result = await loadSiteConfig(createFsSource(root));\n\n if (!result.ok) {\n throw new CliError(\n result.errors.join(\"\\n\"),\n EXIT_CODES.VALIDATION,\n `Make sure ${CONFIG_FILE} exists and is valid JSON.`\n );\n }\n\n return {\n config: result.config,\n warnings: result.warnings,\n };\n};\n","import fs from \"node:fs/promises\";\nimport path from \"node:path\";\n\nimport { loadValidatedSiteConfig } from \"../site-config.js\";\n\nconst CONFIG_FILE = \"docs.json\";\n\nconst fileExists = async (filePath: string): Promise<boolean> => {\n try {\n await fs.access(filePath);\n return true;\n } catch {\n return false;\n }\n};\n\nexport const resolveDocsRoot = async (dir?: string): Promise<string> => {\n if (dir) {\n return path.resolve(process.cwd(), dir);\n }\n\n const candidates = [\n process.cwd(),\n path.join(process.cwd(), \"docs\"),\n path.join(process.cwd(), \"apps/docs\"),\n ];\n\n for (const candidate of candidates) {\n if (await fileExists(path.join(candidate, CONFIG_FILE))) {\n return candidate;\n }\n }\n\n return process.cwd();\n};\n\nexport const validateDocsRoot = async (root: string) =>\n await loadValidatedSiteConfig(root);\n","import path from \"node:path\";\n\nimport { log } from \"@clack/prompts\";\nimport { watch } from \"chokidar\";\n\nconst INVALIDATE_ENDPOINT = \"/blodemd-dev/invalidate\";\nconst WATCH_DEBOUNCE_MS = 100;\n\nconst normalizeRelativePath = (root: string, filePath: string) =>\n path.relative(root, filePath).split(path.sep).join(\"/\");\n\nconst isDirectoryEvent = (event: string) =>\n event === \"addDir\" || event === \"unlinkDir\";\n\nexport const createDevWatcher = ({\n port,\n root,\n}: {\n port: number;\n root: string;\n}) => {\n const watcher = watch(root, {\n ignoreInitial: true,\n ignored: [\"**/.git/**\", \"**/.next/**\", \"**/dist/**\", \"**/node_modules/**\"],\n });\n\n let flushTimer: NodeJS.Timeout | null = null;\n let pendingKind: \"config\" | \"content\" = \"content\";\n const pendingPaths = new Set<string>();\n\n const flush = async () => {\n flushTimer = null;\n\n const paths = [...pendingPaths];\n const kind = pendingKind;\n pendingPaths.clear();\n pendingKind = \"content\";\n\n if (!paths.length) {\n return;\n }\n\n try {\n const response = await fetch(\n `http://127.0.0.1:${port}${INVALIDATE_ENDPOINT}`,\n {\n body: JSON.stringify({ kind, paths }),\n headers: {\n \"Content-Type\": \"application/json\",\n },\n method: \"POST\",\n }\n );\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}`);\n }\n } catch (error) {\n log.error(\n `Failed to invalidate preview cache: ${\n error instanceof Error ? error.message : \"unknown error\"\n }`\n );\n }\n };\n\n watcher.on(\"all\", (event, changedPath) => {\n if (isDirectoryEvent(event)) {\n return;\n }\n\n const relativePath = normalizeRelativePath(root, changedPath);\n pendingPaths.add(relativePath);\n\n if (path.basename(changedPath) === \"docs.json\") {\n pendingKind = \"config\";\n }\n\n if (flushTimer) {\n clearTimeout(flushTimer);\n }\n\n flushTimer = setTimeout(() => {\n flush();\n }, WATCH_DEBOUNCE_MS);\n });\n\n return {\n async close() {\n if (flushTimer) {\n clearTimeout(flushTimer);\n await flush();\n }\n\n await watcher.close();\n },\n };\n};\n","import { spawn } from \"node:child_process\";\nimport { once } from \"node:events\";\nimport fs from \"node:fs/promises\";\nimport { createRequire } from \"node:module\";\nimport { createServer } from \"node:net\";\nimport path from \"node:path\";\nimport { setTimeout as delay } from \"node:timers/promises\";\nimport { fileURLToPath } from \"node:url\";\n\nimport { intro, log } from \"@clack/prompts\";\nimport chalk from \"chalk\";\nimport open from \"open\";\n\nimport { CONFIG_DIR } from \"../constants.js\";\nimport { CliError, EXIT_CODES, toCliError } from \"../errors.js\";\nimport { resolveDocsRoot, validateDocsRoot } from \"./resolve-root.js\";\nimport { createDevWatcher } from \"./watcher.js\";\n\nconst DEV_READY_ENDPOINT = \"/blodemd-dev/version\";\nconst DEV_READY_TIMEOUT_MS = 45_000;\nconst DEV_PORT_SCAN_LIMIT = 10;\nconst DEV_SHUTDOWN_TIMEOUT_MS = 5000;\nconst LOCALHOST = \"127.0.0.1\";\nconst RUNTIME_EXCLUDE_DIRS = new Set([\".next\", \".turbo\", \"node_modules\"]);\n\nconst parsePositiveInteger = (value: string, label: string): number => {\n const parsed = Number.parseInt(value, 10);\n\n if (!Number.isInteger(parsed) || parsed <= 0) {\n throw new CliError(\n `${label} must be a positive integer.`,\n EXIT_CODES.VALIDATION\n );\n }\n\n return parsed;\n};\n\nconst fileExists = async (filePath: string): Promise<boolean> => {\n try {\n await fs.access(filePath);\n return true;\n } catch {\n return false;\n }\n};\n\ntype PortAvailabilityProbe = (port: number) => Promise<boolean>;\n\nconst probePortAvailability: PortAvailabilityProbe = async (port) => {\n const server = createServer();\n\n const listening = (async () => {\n await once(server, \"listening\");\n return { kind: \"listening\" as const };\n })();\n const errored = (async () => {\n const [error] = await once(server, \"error\");\n return {\n error: error as NodeJS.ErrnoException,\n kind: \"error\" as const,\n };\n })();\n\n server.listen({ exclusive: true, host: LOCALHOST, port });\n\n const outcome = await Promise.race([listening, errored]);\n\n if (outcome.kind === \"error\") {\n if (\n outcome.error.code === \"EADDRINUSE\" ||\n outcome.error.code === \"EACCES\"\n ) {\n return false;\n }\n\n throw outcome.error;\n }\n\n server.close();\n await once(server, \"close\");\n return true;\n};\n\nexport const resolveDevPort = async (\n requestedPort: number,\n probePort: PortAvailabilityProbe = probePortAvailability\n): Promise<number> => {\n for (let offset = 0; offset < DEV_PORT_SCAN_LIMIT; offset += 1) {\n const candidate = requestedPort + offset;\n if (candidate > 65_535) {\n break;\n }\n\n if (await probePort(candidate)) {\n return candidate;\n }\n }\n\n throw new CliError(\n `No available port found within ${DEV_PORT_SCAN_LIMIT} attempts starting at ${requestedPort}.`,\n EXIT_CODES.ERROR,\n \"Close the process using the port or pass a different --port value.\"\n );\n};\n\nexport const shutdownChildProcess = async (\n child: ReturnType<typeof spawn>,\n timeoutMs: number = DEV_SHUTDOWN_TIMEOUT_MS\n): Promise<void> => {\n if (child.exitCode !== null) {\n return;\n }\n\n const timer = setTimeout(() => {\n if (child.exitCode === null) {\n child.kill(\"SIGKILL\");\n }\n }, timeoutMs);\n\n const exitPromise = once(child, \"exit\");\n\n try {\n child.kill(\"SIGTERM\");\n } catch (error) {\n clearTimeout(timer);\n\n const killError = error as NodeJS.ErrnoException;\n if (killError.code === \"ESRCH\") {\n return;\n }\n\n throw error;\n }\n\n await exitPromise.finally(() => {\n clearTimeout(timer);\n });\n};\n\n// --- Dev-server resolution ---\n\ninterface StandaloneServer {\n mode: \"standalone\";\n devServerDir: string;\n nextPackageRoot: string;\n packagesDir: string;\n}\n\ninterface MonorepoServer {\n mode: \"monorepo\";\n repoRoot: string;\n}\n\ntype DevServerResolution = StandaloneServer | MonorepoServer;\n\n/**\n * Derive the CLI npm package root from the running script path.\n * The CLI entry point is at `<pkg-root>/dist/cli.mjs`.\n */\nconst resolveCliPackageRoot = (cliFilePath: string): string =>\n path.dirname(path.dirname(cliFilePath));\n\nconst copyStandaloneTree = async (\n sourceDir: string,\n targetDir: string\n): Promise<void> => {\n await fs.cp(sourceDir, targetDir, {\n filter: (source) => {\n const relative = path.relative(sourceDir, source);\n if (!relative) {\n return true;\n }\n\n const topSegment = relative.split(path.sep)[0] ?? \"\";\n return !RUNTIME_EXCLUDE_DIRS.has(topSegment);\n },\n recursive: true,\n });\n};\n\nconst isStandaloneCliInstall = async (\n cliPackageRoot: string\n): Promise<boolean> => {\n try {\n const realRoot = await fs.realpath(cliPackageRoot);\n return realRoot.split(path.sep).includes(\"node_modules\");\n } catch {\n return cliPackageRoot.split(path.sep).includes(\"node_modules\");\n }\n};\n\nconst materializeStandaloneRuntime = async (\n cliPackageRoot: string\n): Promise<{ devServerDir: string; packagesDir: string }> => {\n const runtimeRoot = path.join(CONFIG_DIR, \"standalone-runtime\");\n await fs.rm(runtimeRoot, { force: true, recursive: true });\n await fs.mkdir(runtimeRoot, { recursive: true });\n\n for (const dir of [\"dev-server\", \"docs\", \"packages\"]) {\n await copyStandaloneTree(\n path.join(cliPackageRoot, dir),\n path.join(runtimeRoot, dir)\n );\n }\n\n await fs.symlink(\n path.join(cliPackageRoot, \"node_modules\"),\n path.join(runtimeRoot, \"node_modules\"),\n process.platform === \"win32\" ? \"junction\" : \"dir\"\n );\n\n await fs.writeFile(\n path.join(runtimeRoot, \"dev-server\", \"package.json\"),\n `${JSON.stringify(\n {\n dependencies: {\n next: \"16.2.1\",\n react: \"^19.2.0\",\n \"react-dom\": \"^19.2.0\",\n },\n devDependencies: {\n \"@types/node\": \"^22.19.15\",\n \"@types/react\": \"19.2.14\",\n \"@types/react-dom\": \"19.2.3\",\n typescript: \"6.0.2\",\n },\n name: \"blodemd-dev-server\",\n private: true,\n type: \"module\",\n },\n null,\n 2\n )}\\n`\n );\n\n return {\n devServerDir: path.join(runtimeRoot, \"dev-server\"),\n packagesDir: path.join(runtimeRoot, \"packages\"),\n };\n};\n\n/**\n * Check if a shipped dev-server exists alongside the CLI (npm-installed mode).\n * Verifies both the dev-server directory AND that `next` is resolvable\n * (it's a dependency when npm-installed, but not in the monorepo).\n */\nconst findStandaloneDevServer = async (\n cliPackageRoot: string\n): Promise<StandaloneServer | null> => {\n const devServerDir = path.join(cliPackageRoot, \"dev-server\");\n if (!(await fileExists(path.join(devServerDir, \"next.config.js\")))) {\n return null;\n }\n\n if (!(await isStandaloneCliInstall(cliPackageRoot))) {\n return null;\n }\n\n // Verify `next` is resolvable — this distinguishes npm-installed from\n // a monorepo checkout that happens to have dev-server/ from prepare-dist.\n try {\n createRequire(path.join(cliPackageRoot, \"package.json\")).resolve(\n \"next/package.json\"\n );\n } catch {\n return null;\n }\n\n const runtime = await materializeStandaloneRuntime(cliPackageRoot);\n\n return {\n devServerDir: runtime.devServerDir,\n mode: \"standalone\",\n nextPackageRoot: cliPackageRoot,\n packagesDir: runtime.packagesDir,\n };\n};\n\n/**\n * Resolve the `next` CLI binary from the blodemd package's own dependencies.\n */\nconst resolveNextBin = (cliPackageRoot: string): string => {\n const require = createRequire(path.join(cliPackageRoot, \"package.json\"));\n const nextPkgPath = require.resolve(\"next/package.json\");\n return path.join(path.dirname(nextPkgPath), \"dist\", \"bin\", \"next\");\n};\n\nconst findMonorepoRoot = async (start: string): Promise<string> => {\n let current = start;\n\n while (true) {\n const packageJsonPath = path.join(current, \"package.json\");\n if (await fileExists(packageJsonPath)) {\n const raw = await fs.readFile(packageJsonPath, \"utf8\");\n const parsed = JSON.parse(raw) as { workspaces?: string[] };\n const workspaces = parsed.workspaces ?? [];\n\n if (workspaces.includes(\"apps/*\") && workspaces.includes(\"packages/*\")) {\n return current;\n }\n }\n\n const parent = path.dirname(current);\n if (parent === current) {\n break;\n }\n current = parent;\n }\n\n throw new CliError(\n \"Could not locate the blodemd dev server.\",\n EXIT_CODES.ERROR,\n \"Make sure blodemd is installed correctly (npm i blodemd).\"\n );\n};\n\nconst resolveDevServer = async (\n cliFilePath: string\n): Promise<DevServerResolution> => {\n const cliPackageRoot = resolveCliPackageRoot(cliFilePath);\n\n // Try standalone mode first (npm-installed)\n const standalone = await findStandaloneDevServer(cliPackageRoot);\n if (standalone) {\n return standalone;\n }\n\n // Fall back to monorepo mode (development)\n const repoRoot = await findMonorepoRoot(path.dirname(cliFilePath));\n return { mode: \"monorepo\", repoRoot };\n};\n\nconst spawnDevServer = (\n server: DevServerResolution,\n { root, port }: { root: string; port: number }\n): ReturnType<typeof spawn> => {\n if (server.mode === \"standalone\") {\n const nextBin = resolveNextBin(server.nextPackageRoot);\n\n return spawn(process.execPath, [nextBin, \"dev\", \"--webpack\"], {\n cwd: server.devServerDir,\n env: {\n ...process.env,\n BLODEMD_PACKAGES_DIR: server.packagesDir,\n DOCS_ROOT: root,\n // NODE_PATH lets require.resolve (used by Next.js transpilePackages)\n // find @repo/* packages from our shipped packages/ directory.\n NODE_PATH: [server.packagesDir, process.env.NODE_PATH]\n .filter(Boolean)\n .join(path.delimiter),\n PORT: String(port),\n },\n stdio: \"inherit\",\n });\n }\n\n const npmCommand = process.platform === \"win32\" ? \"npm.cmd\" : \"npm\";\n return spawn(npmCommand, [\"run\", \"dev\", \"--workspace=dev-server\"], {\n cwd: server.repoRoot,\n env: {\n ...process.env,\n DOCS_ROOT: root,\n PORT: String(port),\n },\n stdio: \"inherit\",\n });\n};\n\n// --- Server readiness ---\n\nconst waitForServer = async ({\n child,\n port,\n}: {\n child: ReturnType<typeof spawn>;\n port: number;\n}) => {\n const url = `http://localhost:${port}${DEV_READY_ENDPOINT}`;\n const startedAt = Date.now();\n\n while (Date.now() - startedAt < DEV_READY_TIMEOUT_MS) {\n if (child.exitCode !== null) {\n throw new CliError(\n \"The local dev server exited before it became ready.\",\n EXIT_CODES.ERROR\n );\n }\n\n try {\n const response = await fetch(url, {\n cache: \"no-store\",\n headers: {\n accept: \"application/json\",\n },\n });\n\n if (response.ok) {\n return;\n }\n } catch {\n // Server is still starting.\n }\n\n await delay(500);\n }\n\n throw new CliError(\n \"Timed out waiting for the local dev server to start.\",\n EXIT_CODES.ERROR\n );\n};\n\n// --- Main command ---\n\nexport const devCommand = async ({\n dir,\n openBrowser,\n port: portValue,\n}: {\n dir?: string;\n openBrowser: boolean;\n port: string;\n}) => {\n intro(chalk.bold(\"blodemd dev\"));\n\n try {\n const port = parsePositiveInteger(portValue, \"Port\");\n const resolvedPort = await resolveDevPort(port);\n const root = await resolveDocsRoot(dir);\n await validateDocsRoot(root);\n\n const cliFilePath = fileURLToPath(import.meta.url);\n const server = await resolveDevServer(cliFilePath);\n const localUrl = `http://localhost:${resolvedPort}`;\n\n log.info(`Docs root: ${chalk.cyan(root)}`);\n\n const child = spawnDevServer(server, { port: resolvedPort, root });\n\n let watcher: Awaited<ReturnType<typeof createDevWatcher>> | null = null;\n let shuttingDown = false;\n\n const closeAll = async () => {\n if (shuttingDown) {\n return;\n }\n shuttingDown = true;\n\n if (watcher) {\n await watcher.close();\n watcher = null;\n }\n\n await shutdownChildProcess(child);\n };\n\n process.once(\"SIGINT\", closeAll);\n process.once(\"SIGTERM\", closeAll);\n\n try {\n await waitForServer({ child, port: resolvedPort });\n\n watcher = await createDevWatcher({ port: resolvedPort, root });\n log.success(`Dev server running at ${chalk.cyan(localUrl)}`);\n\n if (openBrowser) {\n await open(localUrl);\n }\n\n const [code, signal] = (await once(child, \"exit\")) as [\n number | null,\n NodeJS.Signals | null,\n ];\n\n if (shuttingDown || signal === \"SIGINT\" || signal === \"SIGTERM\") {\n return;\n }\n\n if (code !== 0) {\n throw new CliError(\n `The local dev server exited with code ${code ?? \"unknown\"}.`,\n EXIT_CODES.ERROR\n );\n }\n } finally {\n await closeAll();\n process.removeListener(\"SIGINT\", closeAll);\n process.removeListener(\"SIGTERM\", closeAll);\n }\n } catch (error) {\n const cliError = toCliError(error);\n\n log.error(cliError.message);\n if (cliError.hint) {\n log.info(cliError.hint);\n }\n\n process.exitCode = cliError.exitCode;\n }\n};\n","// oxlint-disable no-use-before-define -- circular reference in callback pattern\nimport { createServer } from \"node:http\";\nimport type { Socket } from \"node:net\";\n\nimport { CliError, EXIT_CODES } from \"./errors.js\";\n\ninterface OAuthCallbackOptions {\n redirectUrl: URL;\n expectedState: string;\n timeoutMs: number;\n}\n\nconst SUCCESS_HTML =\n '<!doctype html><html><head><meta charset=\"utf-8\"/><title>Blode.md CLI</title></head><body><h2>Logged in! You can close this tab.</h2></body></html>';\n\nconst escapeHtml = (text: string): string =>\n text\n .replaceAll(\"&\", \"&\")\n .replaceAll(\"<\", \"<\")\n .replaceAll(\">\", \">\")\n .replaceAll('\"', \""\");\n\nconst errorHtml = (message: string): string =>\n `<!doctype html><html><head><meta charset=\"utf-8\"/><title>Blode.md CLI</title></head><body><h2>Login failed</h2><p>${escapeHtml(message)}</p></body></html>`;\n\nexport const waitForOAuthCode = (\n options: OAuthCallbackOptions\n): Promise<string> => {\n const host = options.redirectUrl.hostname;\n const port = Number(options.redirectUrl.port);\n const { pathname } = options.redirectUrl;\n\n if (!Number.isInteger(port) || port <= 0) {\n return Promise.reject(\n new CliError(\n \"OAuth redirect URL requires an explicit port\",\n EXIT_CODES.ERROR\n )\n );\n }\n\n // oxlint-disable-next-line eslint-plugin-promise/avoid-new -- wrapping callback-based HTTP server\n return new Promise<string>((resolve, reject) => {\n let settled = false;\n const sockets = new Set<Socket>();\n\n const settle = (ok: boolean, value: string | CliError): void => {\n if (settled) {\n return;\n }\n\n settled = true;\n clearTimeout(timer);\n\n httpServer.close(() => {\n if (ok) {\n resolve(value as string);\n } else {\n reject(value);\n }\n });\n\n // Destroy kept-alive connections so httpServer.close() can finish\n for (const socket of sockets) {\n socket.destroy();\n }\n };\n\n const httpServer = createServer((request, response) => {\n if (!request.url) {\n response.writeHead(400, { \"content-type\": \"text/html; charset=utf-8\" });\n response.end(errorHtml(\"Missing request URL\"));\n settle(\n false,\n new CliError(\n \"OAuth callback is missing a request URL\",\n EXIT_CODES.ERROR\n )\n );\n return;\n }\n\n const url = new URL(request.url, options.redirectUrl.origin);\n\n if (url.pathname !== pathname) {\n response.writeHead(404, { \"content-type\": \"text/html; charset=utf-8\" });\n response.end(errorHtml(\"Invalid callback path\"));\n return;\n }\n\n const providerError = url.searchParams.get(\"error\");\n if (providerError) {\n const description =\n url.searchParams.get(\"error_description\") ?? providerError;\n\n response.writeHead(400, { \"content-type\": \"text/html; charset=utf-8\" });\n response.end(errorHtml(description));\n\n settle(\n false,\n new CliError(\n `OAuth provider returned an error: ${description}`,\n EXIT_CODES.ERROR\n )\n );\n return;\n }\n\n const state = url.searchParams.get(\"state\");\n if (state !== options.expectedState) {\n response.writeHead(400, { \"content-type\": \"text/html; charset=utf-8\" });\n response.end(errorHtml(\"State verification failed\"));\n\n settle(\n false,\n new CliError(\"OAuth state verification failed\", EXIT_CODES.ERROR)\n );\n return;\n }\n\n const code = url.searchParams.get(\"code\");\n if (!code) {\n response.writeHead(400, { \"content-type\": \"text/html; charset=utf-8\" });\n response.end(errorHtml(\"Authorization code was missing\"));\n\n settle(\n false,\n new CliError(\n \"OAuth callback is missing an authorization code\",\n EXIT_CODES.ERROR\n )\n );\n return;\n }\n\n response.writeHead(200, { \"content-type\": \"text/html; charset=utf-8\" });\n response.end(SUCCESS_HTML);\n settle(true, code);\n });\n\n httpServer.on(\"connection\", (socket) => {\n sockets.add(socket);\n socket.once(\"close\", () => sockets.delete(socket));\n });\n\n httpServer.on(\"error\", (error) => {\n settle(\n false,\n new CliError(\n `Failed to start callback server on ${host}:${port}: ${error.message}`,\n EXIT_CODES.ERROR\n )\n );\n });\n\n const timer = setTimeout(() => {\n settle(\n false,\n new CliError(\"Login timed out. Please try again.\", EXIT_CODES.CANCELLED)\n );\n }, options.timeoutMs);\n\n httpServer.listen(port, host);\n });\n};\n","import { createHash, randomBytes } from \"node:crypto\";\n\nexport const createOAuthState = (): string => randomBytes(24).toString(\"hex\");\n\nexport const createCodeVerifier = (): string =>\n randomBytes(64).toString(\"base64url\");\n\nexport const createCodeChallenge = (verifier: string): string =>\n createHash(\"sha256\").update(verifier).digest().toString(\"base64url\");\n","import { readFileSync } from \"node:fs\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nimport { CliError, EXIT_CODES } from \"./errors.js\";\n\nconst MIN_SUPPORTED_NODE_VERSION = [20, 17, 0] as const;\nconst MAX_SUPPORTED_NODE_MAJOR = 25;\n\nexport const SUPPORTED_NODE_RANGE = \">=20.17.0 <25\";\n\nconst parseVersion = (input: string): [number, number, number] | null => {\n const match = /^v?(\\d+)\\.(\\d+)\\.(\\d+)/.exec(input.trim());\n if (!match) {\n return null;\n }\n\n const [, majorText = \"\", minorText = \"\", patchText = \"\"] = match;\n if (!majorText || !minorText || !patchText) {\n return null;\n }\n\n const major = Number.parseInt(majorText, 10);\n const minor = Number.parseInt(minorText, 10);\n const patch = Number.parseInt(patchText, 10);\n\n if ([major, minor, patch].some((value) => Number.isNaN(value))) {\n return null;\n }\n\n return [major, minor, patch];\n};\n\nexport const isSupportedNodeVersion = (version: string): boolean => {\n const parsed = parseVersion(version);\n if (!parsed) {\n return false;\n }\n\n const [major, minor, patch] = parsed;\n const [minMajor, minMinor, minPatch] = MIN_SUPPORTED_NODE_VERSION;\n\n if (major >= MAX_SUPPORTED_NODE_MAJOR) {\n return false;\n }\n\n if (major !== minMajor) {\n return major > minMajor;\n }\n\n if (minor !== minMinor) {\n return minor > minMinor;\n }\n\n return patch >= minPatch;\n};\n\nexport const assertSupportedNodeVersion = (\n version = process.versions.node\n): void => {\n if (isSupportedNodeVersion(version)) {\n return;\n }\n\n throw new CliError(\n `blodemd requires Node.js ${SUPPORTED_NODE_RANGE}. Current version: ${version}.`,\n EXIT_CODES.VALIDATION,\n \"Install a supported Node.js version and try again.\"\n );\n};\n\nexport const readCliVersion = (moduleUrl: string): string => {\n const moduleDir = path.dirname(fileURLToPath(moduleUrl));\n const packageJsonPath = path.resolve(moduleDir, \"..\", \"package.json\");\n const raw = readFileSync(packageJsonPath, \"utf8\");\n const parsed = JSON.parse(raw) as { version?: string };\n\n return parsed.version ?? \"0.0.0\";\n};\n","import { spawnSync } from \"node:child_process\";\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\n\nimport {\n confirm,\n intro,\n isCancel,\n log,\n password,\n spinner,\n} from \"@clack/prompts\";\nimport chalk from \"chalk\";\nimport { Command } from \"commander\";\nimport open from \"open\";\n\nimport { resolveAuthToken, resolveTokenStatus } from \"./auth-session.js\";\nimport {\n BLODE_API_URL_ENV,\n BLODE_BRANCH_ENV,\n BLODE_COMMIT_MESSAGE_ENV,\n BLODE_PROJECT_ENV,\n DEFAULT_API_URL,\n DEFAULT_OAUTH_CALLBACK_PATH,\n DEFAULT_OAUTH_CALLBACK_PORT,\n DEFAULT_OAUTH_TIMEOUT_SECONDS,\n OAUTH_CLIENT_ID,\n} from \"./constants.js\";\nimport { devCommand } from \"./dev/command.js\";\nimport { resolveDocsRoot } from \"./dev/resolve-root.js\";\nimport { CliError, EXIT_CODES, toCliError } from \"./errors.js\";\nimport { waitForOAuthCode } from \"./oauth-callback.js\";\nimport { exchangeAuthorizationCode } from \"./oauth-token.js\";\nimport {\n createCodeChallenge,\n createCodeVerifier,\n createOAuthState,\n} from \"./pkce.js\";\nimport { assertSupportedNodeVersion, readCliVersion } from \"./runtime.js\";\nimport { loadValidatedSiteConfig } from \"./site-config.js\";\nimport {\n clearStoredCredentials,\n readAuthFile,\n writeStoredApiKey,\n writeStoredAuthSession,\n} from \"./storage.js\";\nimport {\n buildOAuthUrls,\n resolveSupabaseConfig,\n tokenResponseToStoredSession,\n} from \"./supabase.js\";\nimport type { DeploymentResponse } from \"./types.js\";\n\nconst CONFIG_FILE = \"docs.json\";\n\nconst TEXT_CONTENT_TYPES: Record<string, string> = {\n \".css\": \"text/css; charset=utf-8\",\n \".html\": \"text/html; charset=utf-8\",\n \".js\": \"text/javascript; charset=utf-8\",\n \".json\": \"application/json; charset=utf-8\",\n \".md\": \"text/markdown; charset=utf-8\",\n \".mdx\": \"text/markdown; charset=utf-8\",\n \".svg\": \"image/svg+xml\",\n \".txt\": \"text/plain; charset=utf-8\",\n \".yaml\": \"application/yaml; charset=utf-8\",\n \".yml\": \"application/yaml; charset=utf-8\",\n};\n\n// --- File helpers ---\n\nconst ensureFile = async (filePath: string, content: string): Promise<void> => {\n try {\n await fs.writeFile(filePath, content, { flag: \"wx\" });\n } catch {\n // File already exists\n }\n};\n\nconst readGitValue = (gitArgs: string[]): string | undefined => {\n const result = spawnSync(\"git\", gitArgs, {\n encoding: \"utf8\",\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n });\n\n if (result.status !== 0) {\n return;\n }\n\n const value = result.stdout.trim();\n return value || undefined;\n};\n\nconst normalizeRelativePath = (root: string, filePath: string): string =>\n path.relative(root, filePath).split(path.sep).join(\"/\");\n\nconst shouldSkipEntry = (name: string): boolean =>\n name.startsWith(\".\") || name === \"node_modules\";\n\nconst collectFiles = async (root: string): Promise<string[]> => {\n const entries = await fs.readdir(root, { withFileTypes: true });\n const files: string[] = [];\n\n for (const entry of entries) {\n if (shouldSkipEntry(entry.name)) {\n continue;\n }\n\n const absolutePath = path.join(root, entry.name);\n if (entry.isDirectory()) {\n files.push(...(await collectFiles(absolutePath)));\n continue;\n }\n\n if (entry.isFile()) {\n files.push(absolutePath);\n }\n }\n\n return files.toSorted((left, right) => left.localeCompare(right));\n};\n\nconst getContentType = (filePath: string): string =>\n TEXT_CONTENT_TYPES[path.extname(filePath).toLowerCase()] ??\n \"application/octet-stream\";\n\nconst readJson = async (response: Response): Promise<unknown> => {\n const text = await response.text();\n if (!text) {\n return null;\n }\n\n try {\n return JSON.parse(text) as unknown;\n } catch {\n return text;\n }\n};\n\nconst requestJson = async <T>(\n url: string,\n init: RequestInit,\n message: string\n): Promise<T> => {\n const response = await fetch(url, init);\n const data = await readJson(response);\n if (!response.ok) {\n const detail =\n typeof data === \"string\" ? data : JSON.stringify(data ?? {}, null, 2);\n throw new Error(`${message}: ${response.status} ${detail}`);\n }\n\n return data as T;\n};\n\nconst parsePositiveInteger = (value: string, label: string): number => {\n const parsed = Number.parseInt(value, 10);\n\n if (!Number.isInteger(parsed) || parsed <= 0) {\n throw new CliError(\n `${label} must be a positive integer.`,\n EXIT_CODES.VALIDATION\n );\n }\n\n return parsed;\n};\n\nconst reportCommandError = (prefix: string, error: unknown): void => {\n const cliError = toCliError(error);\n\n log.error(`${prefix}: ${cliError.message}`);\n if (cliError.hint) {\n log.info(cliError.hint);\n }\n log.info(\"Failed\");\n process.exitCode = cliError.exitCode;\n};\n\n// --- Auth helpers ---\n\nconst fetchUserEmail = async (\n apiUrl: string,\n token: string\n): Promise<string | null> => {\n try {\n const user = await requestJson<{ email: string }>(\n `${apiUrl}/auth/me`,\n { headers: { Authorization: `Bearer ${token}` } },\n \"Failed to fetch user info\"\n );\n return user.email;\n } catch {\n return null;\n }\n};\n\n// --- Push helpers ---\n\ninterface PushConfig {\n project: string;\n apiUrl: string;\n authToken: string;\n branch: string;\n commitMessage?: string;\n}\n\nconst resolvePushConfig = async (\n config: { name?: string },\n options: {\n apiKey?: string;\n apiUrl?: string;\n branch?: string;\n message?: string;\n project?: string;\n }\n): Promise<PushConfig> => {\n const project =\n options.project ?? process.env[BLODE_PROJECT_ENV] ?? config.name;\n const apiUrl =\n options.apiUrl ?? process.env[BLODE_API_URL_ENV] ?? DEFAULT_API_URL;\n\n const resolved = await resolveAuthToken(options.apiKey);\n const authToken = resolved?.token;\n\n const branch =\n options.branch ??\n process.env[BLODE_BRANCH_ENV] ??\n process.env.GITHUB_REF_NAME ??\n readGitValue([\"rev-parse\", \"--abbrev-ref\", \"HEAD\"]) ??\n \"main\";\n const commitMessage =\n options.message ??\n process.env[BLODE_COMMIT_MESSAGE_ENV] ??\n readGitValue([\"log\", \"-1\", \"--pretty=%s\"]);\n\n if (!project) {\n throw new Error(\n 'Missing project slug. Set \"name\" in docs.json, pass --project, or set BLODEMD_PROJECT.'\n );\n }\n if (!authToken) {\n throw new Error(\n 'Missing credentials. Run \"blodemd login\", pass --api-key, or set BLODEMD_API_KEY.'\n );\n }\n\n return { apiUrl, authToken, branch, commitMessage, project };\n};\n\nconst autoCreateProject = async (\n project: string,\n apiUrl: string,\n headers: Record<string, string>\n): Promise<boolean> => {\n const authData = await readAuthFile();\n if (!authData?.session) {\n throw new Error(\n `Project \"${project}\" not found. Create it at blode.md or login with \"blodemd login\" to auto-create.`\n );\n }\n\n const shouldCreate = await confirm({\n message: `Project \"${project}\" doesn't exist. Create it?`,\n });\n\n if (isCancel(shouldCreate) || !shouldCreate) {\n return false;\n }\n\n const createResult = await requestJson<{\n project: { id: string; slug: string };\n token: string;\n }>(\n new URL(\"/projects\", apiUrl).toString(),\n {\n body: JSON.stringify({ name: project, slug: project }),\n headers,\n method: \"POST\",\n },\n \"Failed to create project\"\n );\n\n log.success(`Project ${chalk.cyan(createResult.project.slug)} created`);\n log.info(`API key for CI: ${chalk.dim(createResult.token)}`);\n return true;\n};\n\n// 4 MB limit keeps each batch well under Vercel's 4.5 MB serverless body cap\nconst MAX_BATCH_BYTES = 4 * 1024 * 1024;\n\nconst uploadFiles = async (\n files: string[],\n root: string,\n apiPath: (suffix: string) => string,\n deploymentId: string,\n headers: Record<string, string>,\n s: ReturnType<typeof spinner>\n) => {\n s.start(`Uploading ${files.length} files`);\n\n const items = await Promise.all(\n files.map(async (filePath) => {\n const content = await fs.readFile(filePath);\n return {\n contentBase64: content.toString(\"base64\"),\n contentType: getContentType(filePath),\n path: normalizeRelativePath(root, filePath),\n };\n })\n );\n\n // Split into size-bounded batches\n const batches: (typeof items)[] = [];\n let current: typeof items = [];\n let currentBytes = 0;\n\n for (const item of items) {\n const itemBytes = item.contentBase64.length + item.path.length + 64;\n if (current.length > 0 && currentBytes + itemBytes > MAX_BATCH_BYTES) {\n batches.push(current);\n current = [];\n currentBytes = 0;\n }\n current.push(item);\n currentBytes += itemBytes;\n }\n if (current.length > 0) {\n batches.push(current);\n }\n\n let uploaded = 0;\n for (const batch of batches) {\n await requestJson(\n apiPath(`/${deploymentId}/files/batch`),\n {\n body: JSON.stringify({ files: batch }),\n headers,\n method: \"POST\",\n },\n \"Failed to upload files\"\n );\n uploaded += batch.length;\n s.message(`Uploading files (${uploaded}/${files.length})`);\n }\n\n s.stop(`Uploaded ${chalk.cyan(String(files.length))} files`);\n};\n\n// --- CLI ---\n\nconst program = new Command();\nconst cliVersion = readCliVersion(import.meta.url);\n\nprogram.name(\"blodemd\").description(\"Blode.md CLI\").version(cliVersion);\nprogram.hook(\"preAction\", () => {\n assertSupportedNodeVersion();\n});\n\n// login\n\nprogram\n .command(\"login\")\n .description(\"Authenticate with Blode.md\")\n .option(\"--token\", \"Paste an API key instead of using browser login\")\n .option(\n \"--port <port>\",\n \"Loopback callback port\",\n String(DEFAULT_OAUTH_CALLBACK_PORT)\n )\n .option(\n \"--timeout <seconds>\",\n \"OAuth timeout in seconds\",\n String(DEFAULT_OAUTH_TIMEOUT_SECONDS)\n )\n .option(\"--no-open\", \"Print URL instead of opening the browser\")\n .action(\n async (options: {\n token?: boolean;\n port: string;\n timeout: string;\n open: boolean;\n }) => {\n intro(chalk.bold(\"blodemd login\"));\n\n try {\n if (options.token) {\n const apiKey = await password({\n message: \"Enter your API key\",\n validate: (value) => {\n if (!value) {\n return \"API key is required.\";\n }\n },\n });\n\n if (isCancel(apiKey)) {\n log.warn(\"Cancelled\");\n return;\n }\n\n await writeStoredApiKey({ apiKey, type: \"api-key\" });\n\n const prefix = apiKey.split(\".\")[0] ?? apiKey.slice(0, 12);\n log.success(`Authenticated as ${chalk.cyan(prefix)}`);\n log.info(\"Done\");\n return;\n }\n\n // OAuth 2.1 authorization code flow with PKCE\n const config = resolveSupabaseConfig();\n const { authorizeUrl, tokenUrl } = buildOAuthUrls(config);\n const clientId = OAUTH_CLIENT_ID;\n\n const port = parsePositiveInteger(options.port, \"Port\");\n const timeoutSeconds = parsePositiveInteger(options.timeout, \"Timeout\");\n const redirectUrl = new URL(\n `http://127.0.0.1:${port}${DEFAULT_OAUTH_CALLBACK_PATH}`\n );\n\n const state = createOAuthState();\n const codeVerifier = createCodeVerifier();\n const codeChallenge = createCodeChallenge(codeVerifier);\n\n const authUrl = new URL(authorizeUrl);\n authUrl.searchParams.set(\"response_type\", \"code\");\n authUrl.searchParams.set(\"client_id\", clientId);\n authUrl.searchParams.set(\"redirect_uri\", redirectUrl.toString());\n authUrl.searchParams.set(\"code_challenge\", codeChallenge);\n authUrl.searchParams.set(\"code_challenge_method\", \"S256\");\n authUrl.searchParams.set(\"state\", state);\n authUrl.searchParams.set(\"scope\", \"openid email profile\");\n\n const callbackPromise = waitForOAuthCode({\n expectedState: state,\n redirectUrl,\n timeoutMs: timeoutSeconds * 1000,\n });\n\n if (options.open) {\n log.info(\"Opening browser for authentication...\");\n log.info(\n `If the browser doesn't open, visit: ${chalk.cyan(authUrl.toString())}`\n );\n await open(authUrl.toString());\n } else {\n log.info(\"Open this URL to continue authentication:\");\n log.info(chalk.cyan(authUrl.toString()));\n }\n\n const code = await callbackPromise;\n\n const tokenResponse = await exchangeAuthorizationCode(\n { clientId, tokenUrl },\n code,\n codeVerifier,\n redirectUrl.toString()\n );\n\n const storedSession = tokenResponseToStoredSession(tokenResponse);\n await writeStoredAuthSession(storedSession);\n\n const email =\n storedSession.user?.email ??\n (await fetchUserEmail(\n process.env[BLODE_API_URL_ENV] ?? DEFAULT_API_URL,\n storedSession.accessToken\n ));\n\n if (email) {\n log.success(`Logged in as ${chalk.cyan(email)}`);\n } else {\n log.success(\"Logged in successfully.\");\n }\n\n log.info(\"Done\");\n } catch (error: unknown) {\n reportCommandError(\"Login failed\", error);\n }\n }\n );\n\n// logout\n\nprogram\n .command(\"logout\")\n .description(\"Remove stored credentials\")\n .action(async () => {\n intro(chalk.bold(\"blodemd logout\"));\n\n try {\n const existing = await readAuthFile();\n await clearStoredCredentials();\n\n if (existing?.session || existing?.apiKey) {\n log.success(\"Credentials removed.\");\n } else {\n log.info(\"No stored credentials found.\");\n }\n log.info(\"Done\");\n } catch (error: unknown) {\n reportCommandError(\"Logout failed\", error);\n }\n });\n\n// whoami\n\nprogram\n .command(\"whoami\")\n .description(\"Show current authentication\")\n .action(async () => {\n try {\n const resolved = await resolveAuthToken();\n\n if (!resolved) {\n log.warn('Not logged in. Run \"blodemd login\" to authenticate.');\n return;\n }\n\n if (resolved.source === \"environment\") {\n log.info(\"Authenticated via BLODEMD_API_KEY environment variable\");\n return;\n }\n\n // API keys have no expiry and no user info from JWT\n if (!resolved.expiresAt && !resolved.user) {\n const prefix =\n resolved.token.split(\".\")[0] ?? resolved.token.slice(0, 12);\n log.info(`Logged in with API key ${chalk.cyan(prefix)}`);\n return;\n }\n\n const status = resolveTokenStatus(resolved);\n\n const email =\n resolved.user?.email ??\n (await fetchUserEmail(\n process.env[BLODE_API_URL_ENV] ?? DEFAULT_API_URL,\n resolved.token\n ));\n\n if (email) {\n log.info(`Logged in as ${chalk.cyan(email)}`);\n } else {\n log.info(\"Logged in (could not fetch user details).\");\n }\n\n if (resolved.expiresAt && status.expired) {\n log.warn(\n 'Session has expired. Run \"blodemd login\" to re-authenticate.'\n );\n }\n } catch (error: unknown) {\n reportCommandError(\"Whoami failed\", error);\n }\n });\n\n// init\n\nprogram\n .command(\"init\")\n .description(\"Scaffold a docs folder\")\n .argument(\"[dir]\", \"target directory\", \"docs\")\n .action(async (dir: string) => {\n intro(chalk.bold(\"blodemd init\"));\n\n try {\n const root = path.resolve(process.cwd(), dir);\n await fs.mkdir(root, { recursive: true });\n\n const docsJson = {\n $schema: \"https://docs.blode.md/docs.json\",\n colors: { primary: \"#0D9373\" },\n name: \"my-project\",\n navigation: {\n groups: [{ group: \"Getting Started\", pages: [\"index\"] }],\n },\n theme: \"mint\",\n };\n\n await ensureFile(\n path.join(root, CONFIG_FILE),\n `${JSON.stringify(docsJson, null, 2)}\\n`\n );\n await ensureFile(\n path.join(root, \"index.mdx\"),\n \"---\\ntitle: Welcome\\n---\\n\\nStart writing your docs here.\\n\"\n );\n\n log.success(`Docs scaffolded in ${chalk.cyan(root)}`);\n log.info(`Set ${chalk.cyan(\"name\")} in docs.json to your project slug.`);\n log.info(\"Done\");\n } catch (error: unknown) {\n reportCommandError(\"Init failed\", error);\n }\n });\n\n// validate\n\nprogram\n .command(\"validate\")\n .description(\"Validate docs.json\")\n .argument(\"[dir]\", \"docs directory\")\n .action(async (dir?: string) => {\n intro(chalk.bold(\"blodemd validate\"));\n\n try {\n const root = await resolveDocsRoot(dir);\n const { warnings } = await loadValidatedSiteConfig(root);\n for (const warning of warnings) {\n log.warn(warning);\n }\n log.success(`${chalk.cyan(CONFIG_FILE)} is valid.`);\n log.info(\"Done\");\n } catch (error: unknown) {\n reportCommandError(\"Validation failed\", error);\n }\n });\n\n// push\n\nprogram\n .command(\"push\")\n .description(\"Deploy docs\")\n .argument(\"[dir]\", \"docs directory\")\n .option(\"--project <slug>\", \"project slug (env: BLODEMD_PROJECT)\")\n .option(\"--api-url <url>\", \"API URL (env: BLODEMD_API_URL)\")\n .option(\"--api-key <token>\", \"API key (env: BLODEMD_API_KEY)\")\n .option(\"--branch <name>\", \"git branch (env: BLODEMD_BRANCH)\")\n .option(\"--message <msg>\", \"deploy message (env: BLODEMD_COMMIT_MESSAGE)\")\n .action(\n async (\n dir: string | undefined,\n options: {\n apiKey?: string;\n apiUrl?: string;\n branch?: string;\n message?: string;\n project?: string;\n }\n ) => {\n intro(chalk.bold(\"blodemd push\"));\n const s = spinner();\n\n try {\n const root = await resolveDocsRoot(dir);\n\n s.start(\"Validating configuration\");\n const { config, warnings } = await loadValidatedSiteConfig(root);\n s.stop(\"Configuration valid\");\n for (const warning of warnings) {\n log.warn(warning);\n }\n\n const { project, apiUrl, authToken, branch, commitMessage } =\n await resolvePushConfig(config, options);\n\n s.start(\"Collecting files\");\n const files = await collectFiles(root);\n if (files.length === 0) {\n throw new Error(\"No files found to deploy.\");\n }\n s.stop(`Found ${chalk.cyan(String(files.length))} files`);\n\n const headers = {\n Authorization: `Bearer ${authToken}`,\n \"Content-Type\": \"application/json\",\n };\n\n const apiPath = (suffix: string): string =>\n new URL(\n `/projects/slug/${project}/deployments${suffix}`,\n apiUrl\n ).toString();\n\n const createDeploymentBody = JSON.stringify({ branch, commitMessage });\n\n // Try creating the deployment — if 404, offer to create the project\n s.start(\"Creating deployment\");\n let deployment: DeploymentResponse;\n try {\n deployment = await requestJson<DeploymentResponse>(\n apiPath(\"\"),\n { body: createDeploymentBody, headers, method: \"POST\" },\n \"Failed to create deployment\"\n );\n } catch (error: unknown) {\n const errorMessage = error instanceof Error ? error.message : \"\";\n if (!errorMessage.includes(\"404\")) {\n throw error;\n }\n\n s.stop(\"Project not found\");\n\n const created = await autoCreateProject(project, apiUrl, headers);\n if (!created) {\n log.info(\"Cancelled\");\n return;\n }\n\n s.start(\"Creating deployment\");\n deployment = await requestJson<DeploymentResponse>(\n apiPath(\"\"),\n { body: createDeploymentBody, headers, method: \"POST\" },\n \"Failed to create deployment\"\n );\n }\n s.stop(`Deployment ${chalk.cyan(deployment.id)} created`);\n\n await uploadFiles(files, root, apiPath, deployment.id, headers, s);\n\n s.start(\"Finalizing deployment\");\n const finalized = await requestJson<DeploymentResponse>(\n apiPath(`/${deployment.id}/finalize`),\n {\n body: JSON.stringify({ promote: true }),\n headers,\n method: \"POST\",\n },\n \"Failed to finalize deployment\"\n );\n s.stop(\"Deployment finalized\");\n\n log.success(`Published ${chalk.cyan(finalized.id)}`);\n if (finalized.manifestUrl) {\n log.info(`Manifest: ${finalized.manifestUrl}`);\n }\n if (typeof finalized.fileCount === \"number\") {\n log.info(`Files: ${finalized.fileCount}`);\n }\n\n log.info(\"Done\");\n } catch (error: unknown) {\n s.stop(\"Failed\");\n reportCommandError(\"Push failed\", error);\n }\n }\n );\n\n// dev\n\nprogram\n .command(\"dev\")\n .description(\"Start the local docs dev server\")\n .option(\"-p, --port <port>\", \"Port number\", \"3030\")\n .option(\"-d, --dir <dir>\", \"Docs directory\")\n .option(\"--no-open\", \"Don't open browser\")\n .action(\n async (options: { dir?: string; open?: boolean; port: string }) =>\n await devCommand({\n dir: options.dir,\n openBrowser: options.open ?? true,\n port: options.port,\n })\n );\n\nprogram.parse();\n"],"mappings":"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"}
|
package/docs/app/globals.css
CHANGED
|
@@ -394,7 +394,8 @@
|
|
|
394
394
|
background-color: var(--color-code-highlight);
|
|
395
395
|
}
|
|
396
396
|
|
|
397
|
-
[data-line]
|
|
397
|
+
[data-line],
|
|
398
|
+
.shiki .line {
|
|
398
399
|
display: inline-block;
|
|
399
400
|
width: 100%;
|
|
400
401
|
min-height: calc(var(--spacing) * 1);
|
|
@@ -402,7 +403,8 @@
|
|
|
402
403
|
padding-bottom: calc(var(--spacing) * 0.5);
|
|
403
404
|
}
|
|
404
405
|
|
|
405
|
-
[data-line] span
|
|
406
|
+
[data-line] span,
|
|
407
|
+
.shiki .line span {
|
|
406
408
|
color: var(--shiki-light);
|
|
407
409
|
|
|
408
410
|
@variant dark {
|
|
@@ -6,15 +6,17 @@ import { MobileNav } from "@/components/docs/mobile-nav";
|
|
|
6
6
|
import { Search } from "@/components/ui/search";
|
|
7
7
|
import { ThemeToggle } from "@/components/ui/theme-toggle";
|
|
8
8
|
import type { NavEntry, NavTab } from "@/lib/navigation";
|
|
9
|
-
import { toDocHref } from "@/lib/routes";
|
|
9
|
+
import { isExternalHref, resolveHref, toDocHref } from "@/lib/routes";
|
|
10
10
|
import { cn } from "@/lib/utils";
|
|
11
11
|
|
|
12
12
|
const Dropdown = ({
|
|
13
13
|
label,
|
|
14
14
|
items,
|
|
15
|
+
basePath,
|
|
15
16
|
}: {
|
|
16
17
|
label: string;
|
|
17
18
|
items: { label: string; url: string }[];
|
|
19
|
+
basePath: string;
|
|
18
20
|
}) => {
|
|
19
21
|
if (!items.length) {
|
|
20
22
|
return null;
|
|
@@ -25,15 +27,22 @@ const Dropdown = ({
|
|
|
25
27
|
{label}
|
|
26
28
|
</summary>
|
|
27
29
|
<div className="absolute right-0 top-11 z-20 grid min-w-36 overflow-hidden rounded-xl border border-border bg-popover shadow-popover">
|
|
28
|
-
{items.map((item) =>
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
30
|
+
{items.map((item) => {
|
|
31
|
+
const href = resolveHref(item.url, basePath);
|
|
32
|
+
const isExternal = isExternalHref(item.url);
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<Link
|
|
36
|
+
className="px-3 py-2 hover:bg-accent"
|
|
37
|
+
href={href}
|
|
38
|
+
key={item.label}
|
|
39
|
+
rel={isExternal ? "noopener noreferrer" : undefined}
|
|
40
|
+
target={isExternal ? "_blank" : undefined}
|
|
41
|
+
>
|
|
42
|
+
{item.label}
|
|
43
|
+
</Link>
|
|
44
|
+
);
|
|
45
|
+
})}
|
|
37
46
|
</div>
|
|
38
47
|
</details>
|
|
39
48
|
);
|
|
@@ -55,14 +64,14 @@ const HeaderTabs = ({
|
|
|
55
64
|
{tabs.map((tab, index) => {
|
|
56
65
|
const isActive = index === activeTabIndex;
|
|
57
66
|
const href =
|
|
58
|
-
tab.href ??
|
|
67
|
+
(tab.href ? resolveHref(tab.href, basePath) : undefined) ??
|
|
59
68
|
(tab.slugPrefix ? toDocHref(tab.slugPrefix, basePath) : undefined);
|
|
60
69
|
|
|
61
70
|
if (!href) {
|
|
62
71
|
return null;
|
|
63
72
|
}
|
|
64
73
|
|
|
65
|
-
const isExternal = tab.href
|
|
74
|
+
const isExternal = Boolean(tab.href && isExternalHref(tab.href));
|
|
66
75
|
|
|
67
76
|
return (
|
|
68
77
|
<Link
|
|
@@ -111,11 +120,12 @@ export const DocHeader = ({
|
|
|
111
120
|
|
|
112
121
|
return (
|
|
113
122
|
<header className="sticky top-0 z-50 w-full bg-background">
|
|
114
|
-
<div className="container-wrapper px-
|
|
115
|
-
<div className="flex h-(--header-height) items-center">
|
|
123
|
+
<div className="container-wrapper px-4 lg:px-8">
|
|
124
|
+
<div className="flex h-(--header-height) items-center gap-2">
|
|
116
125
|
<MobileNav
|
|
117
126
|
activeTabIndex={activeTabIndex}
|
|
118
127
|
basePath={basePath}
|
|
128
|
+
className="flex lg:hidden"
|
|
119
129
|
entries={nav}
|
|
120
130
|
globalLinks={globalLinks}
|
|
121
131
|
tabs={tabs}
|
|
@@ -161,7 +171,7 @@ export const DocHeader = ({
|
|
|
161
171
|
) : null}
|
|
162
172
|
<nav
|
|
163
173
|
aria-label="External links"
|
|
164
|
-
className="hidden items-center gap-0 text-sm text-muted-foreground lg:flex"
|
|
174
|
+
className="ml-1 hidden items-center gap-0 text-sm text-muted-foreground lg:flex"
|
|
165
175
|
>
|
|
166
176
|
{globalLinks.map((link) => (
|
|
167
177
|
<a
|
|
@@ -178,10 +188,18 @@ export const DocHeader = ({
|
|
|
178
188
|
<div className="ml-auto flex items-center gap-2 md:flex-1 md:justify-end">
|
|
179
189
|
{searchDisabled ? null : <Search basePath={basePath} />}
|
|
180
190
|
{primaryVersion ? (
|
|
181
|
-
<Dropdown
|
|
191
|
+
<Dropdown
|
|
192
|
+
basePath={basePath}
|
|
193
|
+
items={versions}
|
|
194
|
+
label={primaryVersion.label}
|
|
195
|
+
/>
|
|
182
196
|
) : null}
|
|
183
197
|
{primaryLanguage ? (
|
|
184
|
-
<Dropdown
|
|
198
|
+
<Dropdown
|
|
199
|
+
basePath={basePath}
|
|
200
|
+
items={languages}
|
|
201
|
+
label={primaryLanguage.label}
|
|
202
|
+
/>
|
|
185
203
|
) : null}
|
|
186
204
|
{themeToggleDisabled ? null : <ThemeToggle />}
|
|
187
205
|
</div>
|