@kidd-cli/core 0.1.0

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.
Files changed (54) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +214 -0
  3. package/dist/config-BvGapuFJ.js +282 -0
  4. package/dist/config-BvGapuFJ.js.map +1 -0
  5. package/dist/create-store-BQUX0tAn.js +197 -0
  6. package/dist/create-store-BQUX0tAn.js.map +1 -0
  7. package/dist/index.d.ts +73 -0
  8. package/dist/index.d.ts.map +1 -0
  9. package/dist/index.js +1034 -0
  10. package/dist/index.js.map +1 -0
  11. package/dist/lib/config.d.ts +64 -0
  12. package/dist/lib/config.d.ts.map +1 -0
  13. package/dist/lib/config.js +4 -0
  14. package/dist/lib/logger.d.ts +2 -0
  15. package/dist/lib/logger.js +55 -0
  16. package/dist/lib/logger.js.map +1 -0
  17. package/dist/lib/output.d.ts +62 -0
  18. package/dist/lib/output.d.ts.map +1 -0
  19. package/dist/lib/output.js +276 -0
  20. package/dist/lib/output.js.map +1 -0
  21. package/dist/lib/project.d.ts +59 -0
  22. package/dist/lib/project.d.ts.map +1 -0
  23. package/dist/lib/project.js +3 -0
  24. package/dist/lib/prompts.d.ts +24 -0
  25. package/dist/lib/prompts.d.ts.map +1 -0
  26. package/dist/lib/prompts.js +3 -0
  27. package/dist/lib/store.d.ts +56 -0
  28. package/dist/lib/store.d.ts.map +1 -0
  29. package/dist/lib/store.js +4 -0
  30. package/dist/logger-BkQQej8h.d.ts +76 -0
  31. package/dist/logger-BkQQej8h.d.ts.map +1 -0
  32. package/dist/middleware/auth.d.ts +22 -0
  33. package/dist/middleware/auth.d.ts.map +1 -0
  34. package/dist/middleware/auth.js +759 -0
  35. package/dist/middleware/auth.js.map +1 -0
  36. package/dist/middleware/http.d.ts +87 -0
  37. package/dist/middleware/http.d.ts.map +1 -0
  38. package/dist/middleware/http.js +255 -0
  39. package/dist/middleware/http.js.map +1 -0
  40. package/dist/middleware-D3psyhYo.js +54 -0
  41. package/dist/middleware-D3psyhYo.js.map +1 -0
  42. package/dist/project-NPtYX2ZX.js +181 -0
  43. package/dist/project-NPtYX2ZX.js.map +1 -0
  44. package/dist/prompts-lLfUSgd6.js +63 -0
  45. package/dist/prompts-lLfUSgd6.js.map +1 -0
  46. package/dist/types-CqKJhsYk.d.ts +135 -0
  47. package/dist/types-CqKJhsYk.d.ts.map +1 -0
  48. package/dist/types-Cz9h927W.d.ts +23 -0
  49. package/dist/types-Cz9h927W.d.ts.map +1 -0
  50. package/dist/types-DFtYg5uZ.d.ts +26 -0
  51. package/dist/types-DFtYg5uZ.d.ts.map +1 -0
  52. package/dist/types-kjpRau0U.d.ts +382 -0
  53. package/dist/types-kjpRau0U.d.ts.map +1 -0
  54. package/package.json +94 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","names":["attempt","match","match"],"sources":["../../src/middleware/auth/constants.ts","../../src/middleware/auth/resolve-dotenv.ts","../../src/middleware/auth/resolve-env.ts","../../src/middleware/auth/schema.ts","../../src/middleware/auth/resolve-file.ts","../../src/middleware/auth/resolve-oauth.ts","../../src/middleware/auth/resolve-prompt.ts","../../src/middleware/auth/resolve-credentials.ts","../../src/middleware/auth/create-auth-context.ts","../../src/middleware/auth/auth.ts"],"sourcesContent":["/**\n * Default store key used by the auth middleware to store credentials.\n */\nexport const DEFAULT_AUTH_STORE_KEY = 'auth' as const\n\n/**\n * Default filename for file-based credential storage.\n */\nexport const DEFAULT_AUTH_FILENAME = 'auth.json' as const\n\n/**\n * Suffix appended to the derived token environment variable name.\n */\nexport const TOKEN_VAR_SUFFIX = '_TOKEN' as const\n\n/**\n * Derive the default environment variable name from a CLI name.\n *\n * Converts kebab-case to SCREAMING_SNAKE_CASE and appends `_TOKEN`.\n * Example: `my-app` → `MY_APP_TOKEN`\n *\n * @param cliName - The CLI name.\n * @returns The derived environment variable name.\n */\nexport function deriveTokenVar(cliName: string): string {\n return `${cliName.replaceAll('-', '_').toUpperCase()}${TOKEN_VAR_SUFFIX}`\n}\n","import { readFileSync } from 'node:fs'\n\nimport { parse } from 'dotenv'\nimport { attempt } from 'es-toolkit'\n\nimport type { AuthCredential } from './types.js'\n\n/**\n * Resolve a bearer credential from a `.env` file without mutating `process.env`.\n *\n * Reads the file and parses it with `dotenv.parse`. If the target variable\n * is present, returns a bearer credential. Otherwise returns null.\n *\n * Skips a separate existence check to avoid a TOCTOU race — if the file\n * does not exist, `readFileSync` throws and `attempt` captures the error.\n *\n * @param options - Options with the env variable name and file path.\n * @returns A bearer credential if found, null otherwise.\n */\nexport function resolveFromDotenv(options: {\n readonly tokenVar: string\n readonly path: string\n}): AuthCredential | null {\n const [readError, content] = attempt(() => readFileSync(options.path, 'utf8'))\n\n if (readError || content === null) {\n return null\n }\n\n const parsed = parse(content)\n const token = parsed[options.tokenVar]\n\n if (!token) {\n return null\n }\n\n return { token, type: 'bearer' }\n}\n","import type { AuthCredential } from './types.js'\n\n/**\n * Resolve a bearer credential from a process environment variable.\n *\n * @param options - Options containing the environment variable name.\n * @returns A bearer credential if the variable is set, null otherwise.\n */\nexport function resolveFromEnv(options: { readonly tokenVar: string }): AuthCredential | null {\n const token = process.env[options.tokenVar]\n\n if (!token) {\n return null\n }\n\n return { token, type: 'bearer' }\n}\n","import { z } from 'zod'\n\n/**\n * Zod schema for bearer credentials.\n */\nexport const bearerCredentialSchema = z.object({\n token: z.string().min(1),\n type: z.literal('bearer'),\n})\n\n/**\n * Zod schema for basic auth credentials.\n */\nexport const basicCredentialSchema = z.object({\n password: z.string().min(1),\n type: z.literal('basic'),\n username: z.string().min(1),\n})\n\n/**\n * Zod schema for API key credentials.\n */\nexport const apiKeyCredentialSchema = z.object({\n headerName: z.string().min(1),\n key: z.string().min(1),\n type: z.literal('api-key'),\n})\n\n/**\n * Zod schema for custom header credentials.\n */\nexport const customCredentialSchema = z.object({\n headers: z.record(z.string(), z.string()),\n type: z.literal('custom'),\n})\n\n/**\n * Zod discriminated union schema for validating auth.json credential payloads.\n * Validates against all four credential types using the `type` field as discriminator.\n */\nexport const authCredentialSchema = z.discriminatedUnion('type', [\n bearerCredentialSchema,\n basicCredentialSchema,\n apiKeyCredentialSchema,\n customCredentialSchema,\n])\n","import { createStore } from '@/lib/store/create-store.js'\n\nimport { authCredentialSchema } from './schema.js'\nimport type { AuthCredential } from './types.js'\n\n/**\n * Resolve credentials from a JSON file on disk.\n *\n * Uses the file-backed store with local-then-global resolution to find\n * the credentials file, then validates its contents against the auth\n * credential schema.\n *\n * @param options - Options with the filename and directory name.\n * @returns A validated auth credential, or null if not found or invalid.\n */\nexport function resolveFromFile(options: {\n readonly filename: string\n readonly dirName: string\n}): AuthCredential | null {\n const store = createStore({ dirName: options.dirName })\n const data = store.load(options.filename)\n\n if (data === null) {\n return null\n }\n\n const result = authCredentialSchema.safeParse(data)\n\n if (!result.success) {\n return null\n }\n\n return result.data\n}\n","import { execFile } from 'node:child_process'\nimport { randomBytes } from 'node:crypto'\nimport { createServer } from 'node:http'\nimport type { IncomingMessage, Server, ServerResponse } from 'node:http'\nimport type { Socket } from 'node:net'\nimport { platform } from 'node:os'\n\nimport { match } from 'ts-pattern'\n\nimport type { AuthCredential } from './types.js'\n\n/**\n * Maximum request body size in bytes (16 KB).\n *\n * Limits memory consumption from the local OAuth callback server\n * to prevent resource exhaustion from oversized payloads.\n *\n * @private\n */\nconst MAX_BODY_BYTES = 16_384\n\nconst CLOSE_PAGE_HTML = [\n '<!DOCTYPE html>',\n '<html>',\n '<body><p>Authentication complete. You can close this tab.</p></body>',\n '</html>',\n].join('\\n')\n\n/**\n * Resolve a bearer credential via an OAuth browser flow.\n *\n * Starts a minimal HTTP server on a local port, opens the user's browser\n * to the auth URL with a callback parameter, and waits for the token\n * to arrive via POST body.\n *\n * Only POST requests with a JSON body containing a `token` field are\n * accepted. Query-string tokens are rejected to avoid leaking credentials\n * in server logs, browser history, and referrer headers.\n *\n * @param options - OAuth flow configuration.\n * @returns A bearer credential on success, null on timeout.\n */\nexport async function resolveFromOAuth(options: {\n readonly authUrl: string\n readonly port: number\n readonly callbackPath: string\n readonly timeout: number\n}): Promise<AuthCredential | null> {\n const controller = new AbortController()\n const state = randomBytes(32).toString('hex')\n\n const timeout = createTimeout(options.timeout)\n\n const tokenPromise = listenForToken({\n callbackPath: options.callbackPath,\n port: options.port,\n signal: controller.signal,\n state,\n })\n\n const timeoutPromise = timeout.promise.then((): null => {\n controller.abort()\n return null\n })\n\n const serverPort = await getServerPort(tokenPromise)\n\n if (serverPort === null) {\n controller.abort()\n timeout.clear()\n return null\n }\n\n const callbackUrl = `http://127.0.0.1:${String(serverPort)}${options.callbackPath}`\n const fullAuthUrl = `${options.authUrl}?callback_url=${encodeURIComponent(callbackUrl)}&state=${encodeURIComponent(state)}`\n openBrowser(fullAuthUrl)\n\n const result = await Promise.race([tokenPromise.result, timeoutPromise])\n\n timeout.clear()\n controller.abort()\n\n return result\n}\n\n// ---------------------------------------------------------------------------\n// Private helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Token listener result with port information.\n *\n * @private\n */\ninterface TokenListener {\n readonly port: Promise<number | null>\n readonly result: Promise<AuthCredential | null>\n}\n\n/**\n * Start an HTTP server that listens for an OAuth callback token.\n *\n * The server accepts POST requests with a JSON body `{ \"token\": \"...\" }`\n * on the configured callback path. All other requests receive a 400.\n *\n * @private\n * @param options - Listener configuration.\n * @returns A TokenListener with port and result promises.\n */\nfunction listenForToken(options: {\n readonly callbackPath: string\n readonly port: number\n readonly signal: AbortSignal\n readonly state: string\n}): TokenListener {\n const portResolvers = createDeferred<number | null>()\n const resultResolvers = createDeferred<AuthCredential | null>()\n\n // Mutable socket set required for resource cleanup.\n // Server API is stateful — tracking sockets is the only way to destroy keep-alive connections.\n const sockets = new Set<Socket>()\n\n const server = createServer((req, res) => {\n extractTokenFromBody(req, options.callbackPath, options.state, (token) => {\n if (!token) {\n res.writeHead(400)\n res.end()\n return\n }\n\n sendSuccessPage(res)\n destroyServer(server, sockets)\n resultResolvers.resolve({ token, type: 'bearer' })\n })\n })\n\n trackConnections(server, sockets)\n\n server.on('error', () => {\n destroyServer(server, sockets)\n portResolvers.resolve(null)\n resultResolvers.resolve(null)\n })\n\n options.signal.addEventListener('abort', () => {\n destroyServer(server, sockets)\n resultResolvers.resolve(null)\n })\n\n server.listen(options.port, '127.0.0.1', () => {\n const addr = server.address()\n\n if (addr === null || typeof addr === 'string') {\n destroyServer(server, sockets)\n portResolvers.resolve(null)\n resultResolvers.resolve(null)\n return\n }\n\n portResolvers.resolve(addr.port)\n })\n\n return {\n port: portResolvers.promise,\n result: resultResolvers.promise,\n }\n}\n\n/**\n * Track socket connections on a server so they can be destroyed on close.\n *\n * Mutates the provided socket set — this is an intentional exception to\n * immutability rules because the HTTP server API is inherently stateful.\n *\n * @private\n * @param server - The HTTP server.\n * @param sockets - The set to track sockets in.\n */\nfunction trackConnections(server: Server, sockets: Set<Socket>): void {\n server.on('connection', (socket: Socket) => {\n sockets.add(socket)\n socket.on('close', () => {\n sockets.delete(socket)\n })\n })\n}\n\n/**\n * Close a server and destroy all active connections immediately.\n *\n * `server.close()` only stops accepting new connections — existing\n * keep-alive connections hold the event loop open. This helper\n * destroys every tracked socket so the process can exit cleanly.\n *\n * @private\n * @param server - The HTTP server to close.\n * @param sockets - The set of tracked sockets.\n */\nfunction destroyServer(server: Server, sockets: Set<Socket>): void {\n server.close()\n Array.from(sockets, (socket) => socket.destroy())\n sockets.clear()\n}\n\n/**\n * Create a deferred promise with externally accessible resolve.\n *\n * Uses a mutable state container to capture the promise resolver —\n * this is an intentional exception to immutability rules because the\n * Promise constructor API requires synchronous resolver capture.\n *\n * @private\n * @returns A deferred object with promise and resolve.\n */\nfunction createDeferred<T>(): {\n readonly promise: Promise<T>\n readonly resolve: (value: T) => void\n} {\n const state: { resolve: ((value: T) => void) | null } = { resolve: null }\n\n const promise = new Promise<T>((resolve) => {\n state.resolve = resolve\n })\n\n return {\n promise,\n resolve: (value: T): void => {\n if (state.resolve) {\n state.resolve(value)\n }\n },\n }\n}\n\n/**\n * Clearable timeout that does not keep the event loop alive after cancellation.\n *\n * @private\n */\ninterface Timeout {\n readonly promise: Promise<void>\n readonly clear: () => void\n}\n\n/**\n * Create a clearable timeout.\n *\n * Returns a promise that resolves after `ms` milliseconds and a `clear`\n * function that cancels the timer so it does not hold the event loop open.\n *\n * Uses a mutable state container to capture the timer id — this is an\n * intentional exception to immutability rules because `setTimeout`\n * returns an opaque handle that must be stored for later cancellation.\n *\n * @private\n * @param ms - Duration in milliseconds.\n * @returns A Timeout with `promise` and `clear`.\n */\nfunction createTimeout(ms: number): Timeout {\n const state: { id: ReturnType<typeof setTimeout> | null } = { id: null }\n\n const promise = new Promise<void>((resolve) => {\n state.id = setTimeout(resolve, ms)\n })\n\n return {\n clear: (): void => {\n if (state.id !== null) {\n clearTimeout(state.id)\n state.id = null\n }\n },\n promise,\n }\n}\n\n/**\n * Get the server port from a token listener.\n *\n * @private\n * @param listener - The token listener.\n * @returns The port number, or null if the server failed to start.\n */\nasync function getServerPort(listener: TokenListener): Promise<number | null> {\n return listener.port\n}\n\n/**\n * Extract a token from the POST body of an incoming HTTP request.\n *\n * Only POST requests to the callback path with `application/json`\n * Content-Type and a JSON body containing `token` and matching `state`\n * fields are accepted. Query-string tokens are intentionally rejected\n * to prevent credential leakage through browser history, server logs,\n * and referrer headers.\n *\n * The `Content-Type` check prevents CORS-safelisted simple requests\n * (which skip preflight) from delivering forged payloads — `text/plain`\n * is safelisted, but `application/json` is not (Fetch Standard §2.2.2).\n *\n * Body size is capped at {@link MAX_BODY_BYTES} to prevent resource\n * exhaustion from oversized payloads.\n *\n * @private\n * @param req - The incoming request.\n * @param callbackPath - The expected callback path.\n * @param expectedState - The state nonce to validate against.\n * @param callback - Called with the extracted token or null.\n */\nfunction extractTokenFromBody(\n req: IncomingMessage,\n callbackPath: string,\n expectedState: string,\n callback: (token: string | null) => void\n): void {\n const reqUrl = new URL(req.url ?? '/', 'http://localhost')\n\n if (reqUrl.pathname !== callbackPath) {\n callback(null)\n return\n }\n\n if (req.method !== 'POST') {\n callback(null)\n return\n }\n\n const contentType = req.headers['content-type'] ?? ''\n\n if (!contentType.startsWith('application/json')) {\n callback(null)\n return\n }\n\n const chunks: Buffer[] = []\n\n // Mutable byte counter — streams must be checked incrementally\n // Before the full payload is buffered to prevent resource exhaustion.\n const received = { bytes: 0 }\n\n req.on('data', (chunk: Buffer) => {\n received.bytes += chunk.length\n\n if (received.bytes > MAX_BODY_BYTES) {\n req.destroy()\n callback(null)\n return\n }\n\n chunks.push(chunk)\n })\n\n req.on('end', () => {\n const body = Buffer.concat(chunks).toString('utf8')\n\n const token = parseTokenFromJson(body, expectedState)\n callback(token)\n })\n\n req.on('error', () => {\n callback(null)\n })\n}\n\n/**\n * Parse a token string from a JSON body and validate the state nonce.\n *\n * Expects `{ \"token\": \"<value>\", \"state\": \"<value>\" }`. Returns null\n * for invalid JSON, missing/empty token fields, or mismatched state.\n *\n * @private\n * @param body - The raw request body string.\n * @param expectedState - The state nonce that must match.\n * @returns The token string or null.\n */\nfunction parseTokenFromJson(body: string, expectedState: string): string | null {\n try {\n const parsed: unknown = JSON.parse(body)\n\n if (typeof parsed !== 'object' || parsed === null) {\n return null\n }\n\n const record = parsed as Record<string, unknown>\n\n if (typeof record.token !== 'string' || record.token === '') {\n return null\n }\n\n if (record.state !== expectedState) {\n return null\n }\n\n return record.token\n } catch {\n return null\n }\n}\n\n/**\n * Send an HTML success page and end the response.\n *\n * @private\n * @param res - The server response object.\n */\nfunction sendSuccessPage(res: ServerResponse): void {\n res.writeHead(200, { 'Content-Type': 'text/html' })\n res.end(CLOSE_PAGE_HTML)\n}\n\n/**\n * Open a URL in the user's default browser using a platform-specific command.\n *\n * On Windows, `start` is a `cmd.exe` built-in — not a standalone executable —\n * so it must be invoked via `cmd /c start \"\" <url>`. The empty string argument\n * prevents `cmd` from interpreting the URL as a window title.\n *\n * @private\n * @param url - The URL to open.\n */\nfunction openBrowser(url: string): void {\n const { command, args } = match(platform())\n .with('darwin', () => ({ args: [url], command: 'open' }))\n .with('win32', () => ({ args: ['/c', 'start', '', url], command: 'cmd' }))\n .otherwise(() => ({ args: [url], command: 'xdg-open' }))\n execFile(command, args)\n}\n","import type { Prompts } from '@/context/types.js'\n\nimport type { AuthCredential } from './types.js'\n\n/**\n * Resolve a bearer credential by interactively prompting the user.\n *\n * Uses `prompts.password()` to ask for an API key or token. Returns\n * null if the user cancels the prompt or provides an empty value.\n *\n * Should be placed last in the resolver chain as a fallback.\n *\n * @param options - Options with the prompt message and prompts instance.\n * @returns A bearer credential on input, null on cancellation.\n */\nexport async function resolveFromPrompt(options: {\n readonly message: string\n readonly prompts: Prompts\n}): Promise<AuthCredential | null> {\n try {\n const token = await options.prompts.password({ message: options.message })\n\n if (!token) {\n return null\n }\n\n return { token, type: 'bearer' }\n } catch {\n return null\n }\n}\n","import { join } from 'node:path'\n\nimport { match } from 'ts-pattern'\n\nimport type { Prompts } from '@/context/types.js'\n\nimport { DEFAULT_AUTH_FILENAME, deriveTokenVar } from './constants.js'\nimport { resolveFromDotenv } from './resolve-dotenv.js'\nimport { resolveFromEnv } from './resolve-env.js'\nimport { resolveFromFile } from './resolve-file.js'\nimport { resolveFromOAuth } from './resolve-oauth.js'\nimport { resolveFromPrompt } from './resolve-prompt.js'\nimport type { AuthCredential, ResolverConfig } from './types.js'\n\nconst DEFAULT_OAUTH_PORT = 0\nconst DEFAULT_OAUTH_CALLBACK_PATH = '/callback'\nconst DEFAULT_OAUTH_TIMEOUT = 120_000\nconst DEFAULT_PROMPT_MESSAGE = 'Enter your API key'\n\n/**\n * Chain credential resolvers, returning the first non-null result.\n *\n * Walks the resolver list in order, dispatching each config to the\n * appropriate resolver function via pattern matching. Short-circuits\n * on the first successful resolution.\n *\n * @param options - Options with resolvers, CLI name, and prompts instance.\n * @returns The first resolved credential, or null if all resolvers fail.\n */\nexport async function resolveCredentials(options: {\n readonly resolvers: readonly ResolverConfig[]\n readonly cliName: string\n readonly prompts: Prompts\n}): Promise<AuthCredential | null> {\n const defaultTokenVar = deriveTokenVar(options.cliName)\n\n return tryResolvers(options.resolvers, 0, defaultTokenVar, options)\n}\n\n// ---------------------------------------------------------------------------\n// Private helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Recursively try resolvers until one returns a credential or the list is exhausted.\n *\n * @private\n * @param configs - The resolver configs.\n * @param index - The current index.\n * @param defaultTokenVar - The derived default token env var name.\n * @param context - The resolve options for prompts access.\n * @returns The first resolved credential, or null.\n */\nasync function tryResolvers(\n configs: readonly ResolverConfig[],\n index: number,\n defaultTokenVar: string,\n context: {\n readonly cliName: string\n readonly prompts: Prompts\n }\n): Promise<AuthCredential | null> {\n if (index >= configs.length) {\n return null\n }\n\n const config = configs[index]\n\n if (config === undefined) {\n return null\n }\n\n const credential = await dispatchResolver(config, defaultTokenVar, context)\n\n if (credential) {\n return credential\n }\n\n return tryResolvers(configs, index + 1, defaultTokenVar, context)\n}\n\n/**\n * Dispatch a single resolver config to its implementation.\n *\n * @private\n * @param config - The resolver config to dispatch.\n * @param defaultTokenVar - The derived default token env var name.\n * @param context - The resolve options for prompts access.\n * @returns The resolved credential, or null.\n */\nasync function dispatchResolver(\n config: ResolverConfig,\n defaultTokenVar: string,\n context: {\n readonly cliName: string\n readonly prompts: Prompts\n }\n): Promise<AuthCredential | null> {\n return match(config)\n .with({ source: 'env' }, (c): AuthCredential | null =>\n resolveFromEnv({\n tokenVar: resolveOptionalString(c.tokenVar, defaultTokenVar),\n })\n )\n .with({ source: 'dotenv' }, (c): AuthCredential | null =>\n resolveFromDotenv({\n path: resolveOptionalString(c.path, join(process.cwd(), '.env')),\n tokenVar: resolveOptionalString(c.tokenVar, defaultTokenVar),\n })\n )\n .with({ source: 'file' }, (c): AuthCredential | null =>\n resolveFromFile({\n dirName: resolveOptionalString(c.dirName, `.${context.cliName}`),\n filename: resolveOptionalString(c.filename, DEFAULT_AUTH_FILENAME),\n })\n )\n .with(\n { source: 'oauth' },\n (c): Promise<AuthCredential | null> =>\n resolveFromOAuth({\n authUrl: c.authUrl,\n callbackPath: resolveOptionalString(c.callbackPath, DEFAULT_OAUTH_CALLBACK_PATH),\n port: resolveOptionalNumber(c.port, DEFAULT_OAUTH_PORT),\n timeout: resolveOptionalNumber(c.timeout, DEFAULT_OAUTH_TIMEOUT),\n })\n )\n .with(\n { source: 'prompt' },\n (c): Promise<AuthCredential | null> =>\n resolveFromPrompt({\n message: resolveOptionalString(c.message, DEFAULT_PROMPT_MESSAGE),\n prompts: context.prompts,\n })\n )\n .with({ source: 'custom' }, (c): Promise<AuthCredential | null> | AuthCredential | null =>\n c.resolver()\n )\n .exhaustive()\n}\n\n/**\n * Resolve an optional string value, falling back to a default.\n *\n * @private\n * @param value - The optional value.\n * @param fallback - The default value.\n * @returns The resolved string.\n */\nfunction resolveOptionalString(value: string | undefined, fallback: string): string {\n if (value !== undefined) {\n return value\n }\n return fallback\n}\n\n/**\n * Resolve an optional number value, falling back to a default.\n *\n * @private\n * @param value - The optional value.\n * @param fallback - The default value.\n * @returns The resolved number.\n */\nfunction resolveOptionalNumber(value: number | undefined, fallback: number): number {\n if (value !== undefined) {\n return value\n }\n return fallback\n}\n","/**\n * Factory for the {@link AuthContext} object decorated onto `ctx.auth`.\n *\n * Closes over the middleware's resolver config, CLI name, prompts, and\n * a credential resolver function so that `authenticate()` can run\n * interactive resolvers and persist the result.\n *\n * @module\n */\n\nimport type { AsyncResult, Result } from '@kidd-cli/utils/fp'\nimport { ok } from '@kidd-cli/utils/fp'\n\nimport type { Prompts } from '@/context/types.js'\nimport { createStore } from '@/lib/store/create-store.js'\n\nimport { DEFAULT_AUTH_FILENAME } from './constants.js'\nimport { resolveCredentials } from './resolve-credentials.js'\nimport type { AuthContext, AuthCredential, LoginError, ResolverConfig } from './types.js'\n\n/**\n * Options for {@link createAuthContext}.\n */\nexport interface CreateAuthContextOptions {\n readonly resolvers: readonly ResolverConfig[]\n readonly cliName: string\n readonly prompts: Prompts\n readonly resolveCredential: () => AuthCredential | null\n}\n\n/**\n * Create an {@link AuthContext} value for `ctx.auth`.\n *\n * No credential data is stored on the returned object. `credential()`\n * resolves passively on every call, `authenticated()` checks existence,\n * and `authenticate()` runs the configured interactive resolvers, saves\n * the credential to the global file store, and returns a Result.\n *\n * @param options - Factory options.\n * @returns An AuthContext instance.\n */\nexport function createAuthContext(options: CreateAuthContextOptions): AuthContext {\n const { resolvers, cliName, prompts, resolveCredential } = options\n\n /**\n * Resolve the current credential from passive sources (file, env).\n *\n * @private\n * @returns The credential, or null when none exists.\n */\n function credential(): AuthCredential | null {\n return resolveCredential()\n }\n\n /**\n * Check whether a credential is available from passive sources.\n *\n * @private\n * @returns True when a credential exists.\n */\n function authenticated(): boolean {\n return resolveCredential() !== null\n }\n\n /**\n * Run configured resolvers interactively and persist the credential.\n *\n * @private\n * @returns A Result with the credential on success or a LoginError on failure.\n */\n async function authenticate(): AsyncResult<AuthCredential, LoginError> {\n const resolved = await resolveCredentials({ cliName, prompts, resolvers })\n\n if (resolved === null) {\n return loginError({ message: 'No credential resolved from any source', type: 'no_credential' })\n }\n\n const store = createStore({ dirName: `.${cliName}` })\n const [saveError] = store.save(DEFAULT_AUTH_FILENAME, resolved)\n\n if (saveError) {\n return loginError({\n message: `Failed to save credential: ${saveError.message}`,\n type: 'save_failed',\n })\n }\n\n return ok(resolved)\n }\n\n return { authenticate, authenticated, credential }\n}\n\n// ---------------------------------------------------------------------------\n// Private helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Construct a failure Result tuple with a {@link LoginError}.\n *\n * @private\n * @param error - The login error.\n * @returns A Result tuple `[LoginError, null]`.\n */\nfunction loginError(error: LoginError): Result<never, LoginError> {\n return [error, null] as const\n}\n","/**\n * Auth middleware factory.\n *\n * Decorates `ctx.auth` with functions to resolve credentials on demand\n * and run interactive authentication.\n *\n * @module\n */\n\nimport { decorateContext } from '@/context/decorate.js'\nimport { middleware } from '@/middleware.js'\nimport type { Middleware } from '@/types.js'\n\nimport { DEFAULT_AUTH_FILENAME, deriveTokenVar } from './constants.js'\nimport { createAuthContext } from './create-auth-context.js'\nimport { resolveFromEnv } from './resolve-env.js'\nimport { resolveFromFile } from './resolve-file.js'\nimport type { AuthCredential, AuthOptions, ResolverConfig } from './types.js'\n\n/**\n * Create an auth middleware that decorates `ctx.auth`.\n *\n * No credential data is stored on the context. `ctx.auth.credential()`\n * resolves passively from two sources on every call:\n * 1. File — `~/.cli-name/auth.json`\n * 2. Env — `CLI_NAME_TOKEN`\n *\n * Interactive resolvers (OAuth, prompt, custom) only run when the\n * command handler explicitly calls `ctx.auth.authenticate()`.\n *\n * @param options - Auth middleware configuration.\n * @returns A Middleware that decorates ctx.auth.\n */\nexport function auth(options: AuthOptions): Middleware {\n const { resolvers } = options\n\n return middleware((ctx, next) => {\n const cliName = ctx.meta.name\n\n const authContext = createAuthContext({\n cliName,\n prompts: ctx.prompts,\n resolveCredential: () => resolvePassive(cliName, resolvers),\n resolvers,\n })\n\n decorateContext(ctx, 'auth', authContext)\n\n return next()\n })\n}\n\n// ---------------------------------------------------------------------------\n// Private helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Attempt to resolve a credential from passive (non-interactive) sources.\n *\n * Checks the file store first, then falls back to the environment variable.\n * Scans the resolver list for `env` and `file` source configs to respect\n * user-configured overrides (e.g. a custom `tokenVar` or `dirName`).\n *\n * @private\n * @param cliName - The CLI name, used to derive paths and env var names.\n * @param resolvers - The configured resolver list for extracting overrides.\n * @returns The resolved credential, or null.\n */\nfunction resolvePassive(\n cliName: string,\n resolvers: readonly ResolverConfig[]\n): AuthCredential | null {\n const fileConfig = findResolverBySource(resolvers, 'file')\n const envConfig = findResolverBySource(resolvers, 'env')\n\n const fromFile = resolveFromFile({\n dirName: resolveFileDir(fileConfig, cliName),\n filename: resolveFileFilename(fileConfig),\n })\n\n if (fromFile) {\n return fromFile\n }\n\n return resolveFromEnv({\n tokenVar: resolveEnvTokenVar(envConfig, cliName),\n })\n}\n\n/**\n * Find the first resolver config matching a given source type.\n *\n * @private\n * @param resolvers - The resolver config list.\n * @param source - The source type to find.\n * @returns The matching config, or undefined.\n */\nfunction findResolverBySource<TSource extends ResolverConfig['source']>(\n resolvers: readonly ResolverConfig[],\n source: TSource\n): Extract<ResolverConfig, { readonly source: TSource }> | undefined {\n return resolvers.find(\n (r): r is Extract<ResolverConfig, { readonly source: TSource }> => r.source === source\n )\n}\n\n/**\n * Resolve the file store directory name from a file resolver config.\n *\n * @private\n * @param config - The file resolver config, or undefined.\n * @param cliName - The CLI name for deriving the default.\n * @returns The directory name.\n */\nfunction resolveFileDir(\n config: Extract<ResolverConfig, { readonly source: 'file' }> | undefined,\n cliName: string\n): string {\n if (config !== undefined && config.dirName !== undefined) {\n return config.dirName\n }\n\n return `.${cliName}`\n}\n\n/**\n * Resolve the file store filename from a file resolver config.\n *\n * @private\n * @param config - The file resolver config, or undefined.\n * @returns The filename.\n */\nfunction resolveFileFilename(\n config: Extract<ResolverConfig, { readonly source: 'file' }> | undefined\n): string {\n if (config !== undefined && config.filename !== undefined) {\n return config.filename\n }\n\n return DEFAULT_AUTH_FILENAME\n}\n\n/**\n * Resolve the environment variable name from an env resolver config.\n *\n * @private\n * @param config - The env resolver config, or undefined.\n * @param cliName - The CLI name for deriving the default.\n * @returns The token variable name.\n */\nfunction resolveEnvTokenVar(\n config: Extract<ResolverConfig, { readonly source: 'env' }> | undefined,\n cliName: string\n): string {\n if (config !== undefined && config.tokenVar !== undefined) {\n return config.tokenVar\n }\n\n return deriveTokenVar(cliName)\n}\n"],"mappings":"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"}
@@ -0,0 +1,87 @@
1
+ import { s as Middleware } from "../types-kjpRau0U.js";
2
+ import { n as AuthCredential } from "../types-CqKJhsYk.js";
3
+
4
+ //#region src/middleware/http/types.d.ts
5
+ /**
6
+ * HTTP middleware types.
7
+ *
8
+ * Defines the typed HTTP client interface, request/response wrappers,
9
+ * and configuration options for the {@link http} middleware factory.
10
+ *
11
+ * @module
12
+ */
13
+ /**
14
+ * Typed response wrapper returned by all client methods.
15
+ *
16
+ * @typeParam TData - The parsed JSON body type.
17
+ */
18
+ interface TypedResponse<TData> {
19
+ readonly data: TData;
20
+ readonly status: number;
21
+ readonly headers: Headers;
22
+ readonly ok: boolean;
23
+ readonly raw: Response;
24
+ }
25
+ /**
26
+ * Options for individual HTTP requests.
27
+ *
28
+ * @typeParam TBody - Constrains the body type for methods that accept a body.
29
+ */
30
+ interface RequestOptions<TBody = unknown> {
31
+ readonly body?: TBody;
32
+ readonly headers?: Readonly<Record<string, string>>;
33
+ readonly params?: Readonly<Record<string, string>>;
34
+ readonly signal?: AbortSignal;
35
+ }
36
+ /**
37
+ * Typed HTTP client with method-level generics for response and body types.
38
+ */
39
+ interface HttpClient {
40
+ get<TResponse = unknown>(path: string, options?: RequestOptions): Promise<TypedResponse<TResponse>>;
41
+ post<TResponse = unknown, TBody = unknown>(path: string, options?: RequestOptions<TBody>): Promise<TypedResponse<TResponse>>;
42
+ put<TResponse = unknown, TBody = unknown>(path: string, options?: RequestOptions<TBody>): Promise<TypedResponse<TResponse>>;
43
+ patch<TResponse = unknown, TBody = unknown>(path: string, options?: RequestOptions<TBody>): Promise<TypedResponse<TResponse>>;
44
+ delete<TResponse = unknown>(path: string, options?: RequestOptions): Promise<TypedResponse<TResponse>>;
45
+ }
46
+ /**
47
+ * Options for the {@link http} middleware factory.
48
+ */
49
+ interface HttpOptions {
50
+ readonly namespace: string;
51
+ readonly baseUrl: string;
52
+ readonly defaultHeaders?: Readonly<Record<string, string>>;
53
+ }
54
+ //#endregion
55
+ //#region src/middleware/http/http.d.ts
56
+ /**
57
+ * Create an HTTP client middleware that decorates the context
58
+ * with a typed client.
59
+ *
60
+ * Reads auth credentials from `ctx.auth.credential()` (set by the auth
61
+ * middleware), builds a typed {@link HttpClient}, and attaches it to
62
+ * `ctx[namespace]`.
63
+ *
64
+ * @param options - HTTP middleware configuration.
65
+ * @returns A Middleware that adds an HttpClient to ctx[namespace].
66
+ */
67
+ declare function http(options: HttpOptions): Middleware;
68
+ //#endregion
69
+ //#region src/middleware/http/create-http-client.d.ts
70
+ /**
71
+ * Options for creating an HTTP client.
72
+ */
73
+ interface CreateHttpClientOptions {
74
+ readonly baseUrl: string;
75
+ readonly credential?: AuthCredential;
76
+ readonly defaultHeaders?: Readonly<Record<string, string>>;
77
+ }
78
+ /**
79
+ * Create a typed HTTP client with pre-configured base URL, auth, and headers.
80
+ *
81
+ * @param options - Client configuration.
82
+ * @returns An HttpClient instance.
83
+ */
84
+ declare function createHttpClient(options: CreateHttpClientOptions): HttpClient;
85
+ //#endregion
86
+ export { type HttpClient, type HttpOptions, type RequestOptions, type TypedResponse, createHttpClient, http };
87
+ //# sourceMappingURL=http.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.d.ts","names":[],"sources":["../../src/middleware/http/types.ts","../../src/middleware/http/http.ts","../../src/middleware/http/create-http-client.ts"],"mappings":";;;;;;;;;AAkBA;;;;;;;;UAAiB,aAAA;EAAA,SACN,IAAA,EAAM,KAAA;EAAA,SACN,MAAA;EAAA,SACA,OAAA,EAAS,OAAA;EAAA,SACT,EAAA;EAAA,SACA,GAAA,EAAK,QAAA;AAAA;;;;;AAYhB;UAAiB,cAAA;EAAA,SACN,IAAA,GAAO,KAAA;EAAA,SACP,OAAA,GAAU,QAAA,CAAS,MAAA;EAAA,SACnB,MAAA,GAAS,QAAA,CAAS,MAAA;EAAA,SAClB,MAAA,GAAS,WAAA;AAAA;;;;UAUH,UAAA;EACf,GAAA,sBACE,IAAA,UACA,OAAA,GAAU,cAAA,GACT,OAAA,CAAQ,aAAA,CAAc,SAAA;EAEzB,IAAA,uCACE,IAAA,UACA,OAAA,GAAU,cAAA,CAAe,KAAA,IACxB,OAAA,CAAQ,aAAA,CAAc,SAAA;EAEzB,GAAA,uCACE,IAAA,UACA,OAAA,GAAU,cAAA,CAAe,KAAA,IACxB,OAAA,CAAQ,aAAA,CAAc,SAAA;EAEzB,KAAA,uCACE,IAAA,UACA,OAAA,GAAU,cAAA,CAAe,KAAA,IACxB,OAAA,CAAQ,aAAA,CAAc,SAAA;EAEzB,MAAA,sBACE,IAAA,UACA,OAAA,GAAU,cAAA,GACT,OAAA,CAAQ,aAAA,CAAc,SAAA;AAAA;;;;UAUV,WAAA;EAAA,SACN,SAAA;EAAA,SACA,OAAA;EAAA,SACA,cAAA,GAAiB,QAAA,CAAS,MAAA;AAAA;;;;;;AApErC;;;;;;;;iBCWgB,IAAA,CAAK,OAAA,EAAS,WAAA,GAAc,UAAA;;;;;;UCVlC,uBAAA;EAAA,SACC,OAAA;EAAA,SACA,UAAA,GAAa,cAAA;EAAA,SACb,cAAA,GAAiB,QAAA,CAAS,MAAA;AAAA;;;;;;;iBASrB,gBAAA,CAAiB,OAAA,EAAS,uBAAA,GAA0B,UAAA"}
@@ -0,0 +1,255 @@
1
+ import { n as decorateContext, t as middleware } from "../middleware-D3psyhYo.js";
2
+ import { attemptAsync } from "@kidd-cli/utils/fp";
3
+ import { match as match$1 } from "ts-pattern";
4
+ import { Buffer } from "node:buffer";
5
+
6
+ //#region src/middleware/http/build-auth-headers.ts
7
+ /**
8
+ * Convert auth credentials into HTTP headers.
9
+ *
10
+ * Uses exhaustive pattern matching to map each credential variant to
11
+ * the appropriate header format.
12
+ *
13
+ * @module
14
+ */
15
+ /**
16
+ * Convert an auth credential into HTTP headers.
17
+ *
18
+ * @param credential - The credential to convert.
19
+ * @returns A record of header name to header value.
20
+ */
21
+ function buildAuthHeaders(credential) {
22
+ return match$1(credential).with({ type: "bearer" }, (c) => ({ Authorization: `Bearer ${c.token}` })).with({ type: "basic" }, (c) => ({ Authorization: `Basic ${Buffer.from(`${c.username}:${c.password}`).toString("base64")}` })).with({ type: "api-key" }, (c) => ({ [c.headerName]: c.key })).with({ type: "custom" }, (c) => ({ ...c.headers })).exhaustive();
23
+ }
24
+
25
+ //#endregion
26
+ //#region src/middleware/http/create-http-client.ts
27
+ /**
28
+ * Typed HTTP client factory.
29
+ *
30
+ * Creates a closure-based {@link HttpClient} with pre-configured base URL,
31
+ * auth credentials, and default headers. All methods delegate to a shared
32
+ * request executor.
33
+ *
34
+ * @module
35
+ */
36
+ /**
37
+ * Create a typed HTTP client with pre-configured base URL, auth, and headers.
38
+ *
39
+ * @param options - Client configuration.
40
+ * @returns An HttpClient instance.
41
+ */
42
+ function createHttpClient(options) {
43
+ const { baseUrl, credential, defaultHeaders } = options;
44
+ return {
45
+ delete: (path, requestOptions) => executeRequest(baseUrl, "DELETE", path, credential, defaultHeaders, requestOptions),
46
+ get: (path, requestOptions) => executeRequest(baseUrl, "GET", path, credential, defaultHeaders, requestOptions),
47
+ patch: (path, requestOptions) => executeRequest(baseUrl, "PATCH", path, credential, defaultHeaders, requestOptions),
48
+ post: (path, requestOptions) => executeRequest(baseUrl, "POST", path, credential, defaultHeaders, requestOptions),
49
+ put: (path, requestOptions) => executeRequest(baseUrl, "PUT", path, credential, defaultHeaders, requestOptions)
50
+ };
51
+ }
52
+ /**
53
+ * Build the full URL from base, path, and optional query params.
54
+ *
55
+ * @private
56
+ * @param baseUrl - The base URL.
57
+ * @param path - The request path.
58
+ * @param params - Optional query parameters.
59
+ * @returns The fully qualified URL string.
60
+ */
61
+ function buildUrl(baseUrl, path, params) {
62
+ const url = new URL(path, baseUrl);
63
+ if (params !== void 0) url.search = new URLSearchParams(params).toString();
64
+ return url.toString();
65
+ }
66
+ /**
67
+ * Resolve auth headers from a credential, returning an empty record
68
+ * when no credential is provided.
69
+ *
70
+ * @private
71
+ * @param credential - Optional auth credential.
72
+ * @returns A record of auth headers.
73
+ */
74
+ function resolveAuthHeaders(credential) {
75
+ if (credential !== void 0) return buildAuthHeaders(credential);
76
+ return {};
77
+ }
78
+ /**
79
+ * Merge auth, default, and per-request headers into a single record.
80
+ *
81
+ * Per-request headers take highest priority, then default headers,
82
+ * then auth headers.
83
+ *
84
+ * @private
85
+ * @param credential - Optional auth credential.
86
+ * @param defaultHeaders - Optional default headers.
87
+ * @param requestHeaders - Optional per-request headers.
88
+ * @returns The merged headers record.
89
+ */
90
+ function mergeHeaders(credential, defaultHeaders, requestHeaders) {
91
+ return {
92
+ ...resolveAuthHeaders(credential),
93
+ ...defaultHeaders,
94
+ ...requestHeaders
95
+ };
96
+ }
97
+ /**
98
+ * Extract the params field from request options if present.
99
+ *
100
+ * @private
101
+ * @param options - Optional per-request options.
102
+ * @returns The params record or undefined.
103
+ */
104
+ function extractParams(options) {
105
+ if (options !== void 0) return options.params;
106
+ }
107
+ /**
108
+ * Extract the headers field from request options if present.
109
+ *
110
+ * @private
111
+ * @param options - Optional per-request options.
112
+ * @returns The headers record or undefined.
113
+ */
114
+ function extractHeaders(options) {
115
+ if (options !== void 0) return options.headers;
116
+ }
117
+ /**
118
+ * Extract the signal field from request options if present.
119
+ *
120
+ * @private
121
+ * @param options - Optional per-request options.
122
+ * @returns The AbortSignal or undefined.
123
+ */
124
+ function extractSignal(options) {
125
+ if (options !== void 0) return options.signal;
126
+ }
127
+ /**
128
+ * Resolve the serialized body string and content-type header mutation.
129
+ *
130
+ * @private
131
+ * @param options - Optional per-request options.
132
+ * @returns The serialized body string or undefined.
133
+ */
134
+ function resolveBody(options) {
135
+ if (options !== void 0 && options.body !== void 0) return JSON.stringify(options.body);
136
+ }
137
+ /**
138
+ * Build the fetch init options from resolved values.
139
+ *
140
+ * @private
141
+ * @param method - The HTTP method.
142
+ * @param headers - The merged headers.
143
+ * @param body - The serialized body or undefined.
144
+ * @param signal - The abort signal or undefined.
145
+ * @returns The RequestInit for fetch.
146
+ */
147
+ function buildFetchInit(method, headers, body, signal) {
148
+ if (body !== void 0) return {
149
+ body,
150
+ headers: {
151
+ ...headers,
152
+ "Content-Type": "application/json"
153
+ },
154
+ method,
155
+ signal
156
+ };
157
+ return {
158
+ headers,
159
+ method,
160
+ signal
161
+ };
162
+ }
163
+ /**
164
+ * Execute an HTTP request and wrap the response.
165
+ *
166
+ * @private
167
+ * @param baseUrl - The base URL.
168
+ * @param method - The HTTP method.
169
+ * @param path - The request path.
170
+ * @param credential - Optional auth credential.
171
+ * @param defaultHeaders - Optional default headers.
172
+ * @param options - Optional per-request options.
173
+ * @returns A typed response wrapper.
174
+ */
175
+ async function executeRequest(baseUrl, method, path, credential, defaultHeaders, options) {
176
+ const url = buildUrl(baseUrl, path, extractParams(options));
177
+ const init = buildFetchInit(method, mergeHeaders(credential, defaultHeaders, extractHeaders(options)), resolveBody(options), extractSignal(options));
178
+ const response = await fetch(url, init);
179
+ return {
180
+ data: await parseResponseBody(response),
181
+ headers: response.headers,
182
+ ok: response.ok,
183
+ raw: response,
184
+ status: response.status
185
+ };
186
+ }
187
+ /**
188
+ * Parse the response body as JSON, returning null on failure.
189
+ *
190
+ * Wraps `response.json()` with `attemptAsync` so malformed API
191
+ * responses do not crash the command. Returns `null as TResponse`
192
+ * when parsing fails.
193
+ *
194
+ * @private
195
+ * @param response - The fetch Response.
196
+ * @returns The parsed body or null.
197
+ */
198
+ async function parseResponseBody(response) {
199
+ const [error, data] = await attemptAsync(() => response.json());
200
+ if (error) return null;
201
+ return data;
202
+ }
203
+
204
+ //#endregion
205
+ //#region src/middleware/http/http.ts
206
+ /**
207
+ * HTTP client middleware factory.
208
+ *
209
+ * Creates a middleware that decorates the context with a typed
210
+ * {@link HttpClient} bound to a base URL and optional auth credentials.
211
+ *
212
+ * @module
213
+ */
214
+ /**
215
+ * Create an HTTP client middleware that decorates the context
216
+ * with a typed client.
217
+ *
218
+ * Reads auth credentials from `ctx.auth.credential()` (set by the auth
219
+ * middleware), builds a typed {@link HttpClient}, and attaches it to
220
+ * `ctx[namespace]`.
221
+ *
222
+ * @param options - HTTP middleware configuration.
223
+ * @returns A Middleware that adds an HttpClient to ctx[namespace].
224
+ */
225
+ function http(options) {
226
+ const { namespace, baseUrl, defaultHeaders } = options;
227
+ return middleware(async (ctx, next) => {
228
+ decorateContext(ctx, namespace, createHttpClient({
229
+ baseUrl,
230
+ credential: resolveCredential(ctx),
231
+ defaultHeaders
232
+ }));
233
+ await next();
234
+ });
235
+ }
236
+ /**
237
+ * Resolve the auth credential from the context.
238
+ *
239
+ * Calls `ctx.auth.credential()` when the auth middleware has run.
240
+ * Returns undefined when no auth context is available.
241
+ *
242
+ * @private
243
+ * @param ctx - The context object.
244
+ * @returns The credential or undefined.
245
+ */
246
+ function resolveCredential(ctx) {
247
+ if (ctx.auth === void 0) return;
248
+ const cred = ctx.auth.credential();
249
+ if (cred === null) return;
250
+ return cred;
251
+ }
252
+
253
+ //#endregion
254
+ export { createHttpClient, http };
255
+ //# sourceMappingURL=http.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.js","names":["match"],"sources":["../../src/middleware/http/build-auth-headers.ts","../../src/middleware/http/create-http-client.ts","../../src/middleware/http/http.ts"],"sourcesContent":["/**\n * Convert auth credentials into HTTP headers.\n *\n * Uses exhaustive pattern matching to map each credential variant to\n * the appropriate header format.\n *\n * @module\n */\n\nimport { Buffer } from 'node:buffer'\n\nimport { match } from 'ts-pattern'\n\nimport type { AuthCredential } from '../auth/types.js'\n\n/**\n * Convert an auth credential into HTTP headers.\n *\n * @param credential - The credential to convert.\n * @returns A record of header name to header value.\n */\nexport function buildAuthHeaders(credential: AuthCredential): Readonly<Record<string, string>> {\n return match(credential)\n .with({ type: 'bearer' }, (c) => ({\n Authorization: `Bearer ${c.token}`,\n }))\n .with({ type: 'basic' }, (c) => ({\n Authorization: `Basic ${Buffer.from(`${c.username}:${c.password}`).toString('base64')}`,\n }))\n .with({ type: 'api-key' }, (c) => ({\n [c.headerName]: c.key,\n }))\n .with({ type: 'custom' }, (c) => ({ ...c.headers }))\n .exhaustive()\n}\n","/**\n * Typed HTTP client factory.\n *\n * Creates a closure-based {@link HttpClient} with pre-configured base URL,\n * auth credentials, and default headers. All methods delegate to a shared\n * request executor.\n *\n * @module\n */\n\nimport { attemptAsync } from '@kidd-cli/utils/fp'\n\nimport type { AuthCredential } from '../auth/types.js'\nimport { buildAuthHeaders } from './build-auth-headers.js'\nimport type { HttpClient, RequestOptions, TypedResponse } from './types.js'\n\n/**\n * Options for creating an HTTP client.\n */\ninterface CreateHttpClientOptions {\n readonly baseUrl: string\n readonly credential?: AuthCredential\n readonly defaultHeaders?: Readonly<Record<string, string>>\n}\n\n/**\n * Create a typed HTTP client with pre-configured base URL, auth, and headers.\n *\n * @param options - Client configuration.\n * @returns An HttpClient instance.\n */\nexport function createHttpClient(options: CreateHttpClientOptions): HttpClient {\n const { baseUrl, credential, defaultHeaders } = options\n\n return {\n delete: <TResponse = unknown>(path: string, requestOptions?: RequestOptions) =>\n executeRequest<TResponse>(\n baseUrl,\n 'DELETE',\n path,\n credential,\n defaultHeaders,\n requestOptions\n ),\n\n get: <TResponse = unknown>(path: string, requestOptions?: RequestOptions) =>\n executeRequest<TResponse>(baseUrl, 'GET', path, credential, defaultHeaders, requestOptions),\n\n patch: <TResponse = unknown, TBody = unknown>(\n path: string,\n requestOptions?: RequestOptions<TBody>\n ) =>\n executeRequest<TResponse>(baseUrl, 'PATCH', path, credential, defaultHeaders, requestOptions),\n\n post: <TResponse = unknown, TBody = unknown>(\n path: string,\n requestOptions?: RequestOptions<TBody>\n ) =>\n executeRequest<TResponse>(baseUrl, 'POST', path, credential, defaultHeaders, requestOptions),\n\n put: <TResponse = unknown, TBody = unknown>(\n path: string,\n requestOptions?: RequestOptions<TBody>\n ) =>\n executeRequest<TResponse>(baseUrl, 'PUT', path, credential, defaultHeaders, requestOptions),\n }\n}\n\n// ---------------------------------------------------------------------------\n// Private helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Build the full URL from base, path, and optional query params.\n *\n * @private\n * @param baseUrl - The base URL.\n * @param path - The request path.\n * @param params - Optional query parameters.\n * @returns The fully qualified URL string.\n */\nfunction buildUrl(\n baseUrl: string,\n path: string,\n params: Readonly<Record<string, string>> | undefined\n): string {\n const url = new URL(path, baseUrl)\n\n if (params !== undefined) {\n const searchParams = new URLSearchParams(params)\n url.search = searchParams.toString()\n }\n\n return url.toString()\n}\n\n/**\n * Resolve auth headers from a credential, returning an empty record\n * when no credential is provided.\n *\n * @private\n * @param credential - Optional auth credential.\n * @returns A record of auth headers.\n */\nfunction resolveAuthHeaders(\n credential: AuthCredential | undefined\n): Readonly<Record<string, string>> {\n if (credential !== undefined) {\n return buildAuthHeaders(credential)\n }\n\n return {}\n}\n\n/**\n * Merge auth, default, and per-request headers into a single record.\n *\n * Per-request headers take highest priority, then default headers,\n * then auth headers.\n *\n * @private\n * @param credential - Optional auth credential.\n * @param defaultHeaders - Optional default headers.\n * @param requestHeaders - Optional per-request headers.\n * @returns The merged headers record.\n */\nfunction mergeHeaders(\n credential: AuthCredential | undefined,\n defaultHeaders: Readonly<Record<string, string>> | undefined,\n requestHeaders: Readonly<Record<string, string>> | undefined\n): Readonly<Record<string, string>> {\n const authHeaders = resolveAuthHeaders(credential)\n\n return {\n ...authHeaders,\n ...defaultHeaders,\n ...requestHeaders,\n }\n}\n\n/**\n * Extract the params field from request options if present.\n *\n * @private\n * @param options - Optional per-request options.\n * @returns The params record or undefined.\n */\nfunction extractParams(\n options: RequestOptions | undefined\n): Readonly<Record<string, string>> | undefined {\n if (options !== undefined) {\n return options.params\n }\n\n return undefined\n}\n\n/**\n * Extract the headers field from request options if present.\n *\n * @private\n * @param options - Optional per-request options.\n * @returns The headers record or undefined.\n */\nfunction extractHeaders(\n options: RequestOptions | undefined\n): Readonly<Record<string, string>> | undefined {\n if (options !== undefined) {\n return options.headers\n }\n\n return undefined\n}\n\n/**\n * Extract the signal field from request options if present.\n *\n * @private\n * @param options - Optional per-request options.\n * @returns The AbortSignal or undefined.\n */\nfunction extractSignal(options: RequestOptions | undefined): AbortSignal | undefined {\n if (options !== undefined) {\n return options.signal\n }\n\n return undefined\n}\n\n/**\n * Resolve the serialized body string and content-type header mutation.\n *\n * @private\n * @param options - Optional per-request options.\n * @returns The serialized body string or undefined.\n */\nfunction resolveBody(options: RequestOptions | undefined): string | undefined {\n if (options !== undefined && options.body !== undefined) {\n return JSON.stringify(options.body)\n }\n\n return undefined\n}\n\n/**\n * Build the fetch init options from resolved values.\n *\n * @private\n * @param method - The HTTP method.\n * @param headers - The merged headers.\n * @param body - The serialized body or undefined.\n * @param signal - The abort signal or undefined.\n * @returns The RequestInit for fetch.\n */\nfunction buildFetchInit(\n method: string,\n headers: Readonly<Record<string, string>>,\n body: string | undefined,\n signal: AbortSignal | undefined\n): RequestInit {\n if (body !== undefined) {\n return {\n body,\n headers: { ...headers, 'Content-Type': 'application/json' },\n method,\n signal,\n }\n }\n\n return {\n headers,\n method,\n signal,\n }\n}\n\n/**\n * Execute an HTTP request and wrap the response.\n *\n * @private\n * @param baseUrl - The base URL.\n * @param method - The HTTP method.\n * @param path - The request path.\n * @param credential - Optional auth credential.\n * @param defaultHeaders - Optional default headers.\n * @param options - Optional per-request options.\n * @returns A typed response wrapper.\n */\nasync function executeRequest<TResponse>(\n baseUrl: string,\n method: string,\n path: string,\n credential: AuthCredential | undefined,\n defaultHeaders: Readonly<Record<string, string>> | undefined,\n options: RequestOptions | undefined\n): Promise<TypedResponse<TResponse>> {\n const url = buildUrl(baseUrl, path, extractParams(options))\n const headers = mergeHeaders(credential, defaultHeaders, extractHeaders(options))\n const body = resolveBody(options)\n const signal = extractSignal(options)\n const init = buildFetchInit(method, headers, body, signal)\n\n const response = await fetch(url, init)\n const data = await parseResponseBody<TResponse>(response)\n\n return {\n data,\n headers: response.headers,\n ok: response.ok,\n raw: response,\n status: response.status,\n }\n}\n\n/**\n * Parse the response body as JSON, returning null on failure.\n *\n * Wraps `response.json()` with `attemptAsync` so malformed API\n * responses do not crash the command. Returns `null as TResponse`\n * when parsing fails.\n *\n * @private\n * @param response - The fetch Response.\n * @returns The parsed body or null.\n */\nasync function parseResponseBody<TResponse>(response: Response): Promise<TResponse> {\n const [error, data] = await attemptAsync(() => response.json() as Promise<TResponse>)\n\n if (error) {\n return null as TResponse\n }\n\n return data as TResponse\n}\n","/**\n * HTTP client middleware factory.\n *\n * Creates a middleware that decorates the context with a typed\n * {@link HttpClient} bound to a base URL and optional auth credentials.\n *\n * @module\n */\n\nimport { decorateContext } from '@/context/decorate.js'\nimport type { Context } from '@/context/types.js'\nimport { middleware } from '@/middleware.js'\nimport type { Middleware } from '@/types.js'\n\nimport type { AuthCredential } from '../auth/types.js'\nimport { createHttpClient } from './create-http-client.js'\nimport type { HttpOptions } from './types.js'\n\n/**\n * Create an HTTP client middleware that decorates the context\n * with a typed client.\n *\n * Reads auth credentials from `ctx.auth.credential()` (set by the auth\n * middleware), builds a typed {@link HttpClient}, and attaches it to\n * `ctx[namespace]`.\n *\n * @param options - HTTP middleware configuration.\n * @returns A Middleware that adds an HttpClient to ctx[namespace].\n */\nexport function http(options: HttpOptions): Middleware {\n const { namespace, baseUrl, defaultHeaders } = options\n\n return middleware(async (ctx, next) => {\n const credential = resolveCredential(ctx)\n\n const client = createHttpClient({\n baseUrl,\n credential,\n defaultHeaders,\n })\n\n decorateContext(ctx, namespace, client)\n\n await next()\n })\n}\n\n// ---------------------------------------------------------------------------\n// Private helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve the auth credential from the context.\n *\n * Calls `ctx.auth.credential()` when the auth middleware has run.\n * Returns undefined when no auth context is available.\n *\n * @private\n * @param ctx - The context object.\n * @returns The credential or undefined.\n */\nfunction resolveCredential(ctx: Context): AuthCredential | undefined {\n if (ctx.auth === undefined) {\n return undefined\n }\n\n const cred = ctx.auth.credential()\n\n if (cred === null) {\n return undefined\n }\n\n return cred\n}\n"],"mappings":"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"}
@@ -0,0 +1,54 @@
1
+ import { withTag } from "@kidd-cli/utils/tag";
2
+
3
+ //#region src/context/decorate.ts
4
+ /**
5
+ * Add a typed, immutable property to a context instance.
6
+ *
7
+ * Middleware authors use this to extend ctx with custom properties.
8
+ * Pair with module augmentation on Context for type safety:
9
+ *
10
+ * ```ts
11
+ * declare module '@kidd-cli/core' {
12
+ * interface Context {
13
+ * readonly github: HttpClient
14
+ * }
15
+ * }
16
+ * ```
17
+ *
18
+ * **Note:** This function mutates the context object via
19
+ * `Object.defineProperty`. The added property is non-writable and
20
+ * non-configurable, making it effectively frozen after assignment.
21
+ * Mutation is intentional here — the context is assembled incrementally
22
+ * across middleware, and copying the entire object on each decoration
23
+ * would break the single-reference threading model used by the runner.
24
+ *
25
+ * @param ctx - The context instance to decorate (mutated in place).
26
+ * @param key - The property name.
27
+ * @param value - The property value (frozen after assignment).
28
+ * @returns The same ctx reference, now carrying the new property.
29
+ */
30
+ function decorateContext(ctx, key, value) {
31
+ Object.defineProperty(ctx, key, {
32
+ configurable: false,
33
+ enumerable: true,
34
+ value,
35
+ writable: false
36
+ });
37
+ return ctx;
38
+ }
39
+
40
+ //#endregion
41
+ //#region src/middleware.ts
42
+ /**
43
+ * Create a typed middleware that runs before command handlers.
44
+ *
45
+ * @param handler - The middleware function receiving ctx and next.
46
+ * @returns A Middleware object for use in the cli() middleware stack.
47
+ */
48
+ function middleware(handler) {
49
+ return withTag({ handler }, "Middleware");
50
+ }
51
+
52
+ //#endregion
53
+ export { decorateContext as n, middleware as t };
54
+ //# sourceMappingURL=middleware-D3psyhYo.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"middleware-D3psyhYo.js","names":[],"sources":["../src/context/decorate.ts","../src/middleware.ts"],"sourcesContent":["import type { Context } from './types.js'\n\n/**\n * Add a typed, immutable property to a context instance.\n *\n * Middleware authors use this to extend ctx with custom properties.\n * Pair with module augmentation on Context for type safety:\n *\n * ```ts\n * declare module '@kidd-cli/core' {\n * interface Context {\n * readonly github: HttpClient\n * }\n * }\n * ```\n *\n * **Note:** This function mutates the context object via\n * `Object.defineProperty`. The added property is non-writable and\n * non-configurable, making it effectively frozen after assignment.\n * Mutation is intentional here — the context is assembled incrementally\n * across middleware, and copying the entire object on each decoration\n * would break the single-reference threading model used by the runner.\n *\n * @param ctx - The context instance to decorate (mutated in place).\n * @param key - The property name.\n * @param value - The property value (frozen after assignment).\n * @returns The same ctx reference, now carrying the new property.\n */\nexport function decorateContext<TKey extends string, TValue>(\n ctx: Context,\n key: TKey,\n value: TValue\n): Context {\n Object.defineProperty(ctx, key, { configurable: false, enumerable: true, value, writable: false })\n return ctx\n}\n","import { withTag } from '@kidd-cli/utils/tag'\n\nimport type { Middleware, MiddlewareFn } from './types.js'\n\n/**\n * Create a typed middleware that runs before command handlers.\n *\n * @param handler - The middleware function receiving ctx and next.\n * @returns A Middleware object for use in the cli() middleware stack.\n */\nexport function middleware<TConfig extends Record<string, unknown> = Record<string, unknown>>(\n handler: MiddlewareFn<TConfig>\n): Middleware<TConfig> {\n return withTag({ handler }, 'Middleware')\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BA,SAAgB,gBACd,KACA,KACA,OACS;AACT,QAAO,eAAe,KAAK,KAAK;EAAE,cAAc;EAAO,YAAY;EAAM;EAAO,UAAU;EAAO,CAAC;AAClG,QAAO;;;;;;;;;;;ACxBT,SAAgB,WACd,SACqB;AACrB,QAAO,QAAQ,EAAE,SAAS,EAAE,aAAa"}