@kidd-cli/core 0.1.0 → 0.1.1
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 +10 -10
- package/dist/create-store-BQUX0tAn.js.map +1 -1
- package/dist/lib/store.d.ts.map +1 -1
- package/dist/middleware/auth.js.map +1 -1
- package/package.json +8 -3
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ pnpm add kidd
|
|
|
11
11
|
## Usage
|
|
12
12
|
|
|
13
13
|
```ts
|
|
14
|
-
import { cli, command } from 'kidd'
|
|
14
|
+
import { cli, command } from '@kidd-cli/core'
|
|
15
15
|
import { z } from 'zod'
|
|
16
16
|
|
|
17
17
|
const greet = command({
|
|
@@ -99,7 +99,7 @@ cli({
|
|
|
99
99
|
Type-safe helper for `kidd.config.ts` files.
|
|
100
100
|
|
|
101
101
|
```ts
|
|
102
|
-
import { defineConfig } from 'kidd'
|
|
102
|
+
import { defineConfig } from '@kidd-cli/core'
|
|
103
103
|
|
|
104
104
|
export default defineConfig({
|
|
105
105
|
build: { out: 'dist' },
|
|
@@ -113,7 +113,7 @@ export default defineConfig({
|
|
|
113
113
|
Interactive terminal prompts backed by `@clack/prompts`.
|
|
114
114
|
|
|
115
115
|
```ts
|
|
116
|
-
import { prompts, spinner } from 'kidd/prompts'
|
|
116
|
+
import { prompts, spinner } from '@kidd-cli/core/prompts'
|
|
117
117
|
|
|
118
118
|
const name = await prompts.text({ message: 'Project name?' })
|
|
119
119
|
|
|
@@ -126,7 +126,7 @@ spinner.stop('Done')
|
|
|
126
126
|
Structured terminal logger backed by `@clack/prompts`.
|
|
127
127
|
|
|
128
128
|
```ts
|
|
129
|
-
import { log } from 'kidd/logger'
|
|
129
|
+
import { log } from '@kidd-cli/core/logger'
|
|
130
130
|
|
|
131
131
|
log.intro('My CLI')
|
|
132
132
|
log.info('Processing...')
|
|
@@ -139,7 +139,7 @@ log.outro('Done')
|
|
|
139
139
|
Structured output for JSON, templates, and files.
|
|
140
140
|
|
|
141
141
|
```ts
|
|
142
|
-
import { output } from 'kidd/output'
|
|
142
|
+
import { output } from '@kidd-cli/core/output'
|
|
143
143
|
|
|
144
144
|
output.json({ status: 'ok' })
|
|
145
145
|
output.write({ path: './out.json', content: output.toJson(data) })
|
|
@@ -150,7 +150,7 @@ output.write({ path: './out.json', content: output.toJson(data) })
|
|
|
150
150
|
Error creation, formatting, and sanitization utilities.
|
|
151
151
|
|
|
152
152
|
```ts
|
|
153
|
-
import { createErrorUtil, sanitize } from 'kidd/errors'
|
|
153
|
+
import { createErrorUtil, sanitize } from '@kidd-cli/core/errors'
|
|
154
154
|
|
|
155
155
|
const errors = createErrorUtil({ prefix: 'deploy', sanitize: true })
|
|
156
156
|
const err = errors.create('Connection refused')
|
|
@@ -163,7 +163,7 @@ const clean = sanitize('token=abc123&secret=xyz')
|
|
|
163
163
|
Typed config client for JSON/JSONC/YAML files.
|
|
164
164
|
|
|
165
165
|
```ts
|
|
166
|
-
import { createConfigClient } from 'kidd/config'
|
|
166
|
+
import { createConfigClient } from '@kidd-cli/core/config'
|
|
167
167
|
|
|
168
168
|
const config = createConfigClient({ name: 'my-app', schema: MySchema })
|
|
169
169
|
const [error, result] = await config.load()
|
|
@@ -174,7 +174,7 @@ const [error, result] = await config.load()
|
|
|
174
174
|
File-backed JSON store with local and global resolution.
|
|
175
175
|
|
|
176
176
|
```ts
|
|
177
|
-
import { createStore } from 'kidd/store'
|
|
177
|
+
import { createStore } from '@kidd-cli/core/store'
|
|
178
178
|
|
|
179
179
|
const store = createStore({ dirName: '.my-app' })
|
|
180
180
|
const settings = store.load('settings.json')
|
|
@@ -185,7 +185,7 @@ const settings = store.load('settings.json')
|
|
|
185
185
|
Zod-based validation returning Result tuples.
|
|
186
186
|
|
|
187
187
|
```ts
|
|
188
|
-
import { validate } from 'kidd/validate'
|
|
188
|
+
import { validate } from '@kidd-cli/core/validate'
|
|
189
189
|
|
|
190
190
|
const [error, value] = validate(
|
|
191
191
|
MySchema,
|
|
@@ -199,7 +199,7 @@ const [error, value] = validate(
|
|
|
199
199
|
Git project root resolution, submodule detection, and dotenv loading.
|
|
200
200
|
|
|
201
201
|
```ts
|
|
202
|
-
import { findProjectRoot, createDotEnv, isInSubmodule } from 'kidd/project'
|
|
202
|
+
import { findProjectRoot, createDotEnv, isInSubmodule } from '@kidd-cli/core/project'
|
|
203
203
|
|
|
204
204
|
const root = findProjectRoot()
|
|
205
205
|
const env = createDotEnv({ dirName: '.my-app' })
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-store-BQUX0tAn.js","names":[],"sources":["../src/lib/store/create-store.ts"],"sourcesContent":["import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'\nimport { join } from 'node:path'\n\nimport { attempt, err, match, ok } from '@kidd-cli/utils/fp'\nimport type { Result } from '@kidd-cli/utils/fp'\nimport { jsonParse, jsonStringify } from '@kidd-cli/utils/json'\n\nimport { resolveGlobalPath, resolveLocalPath } from '@/lib/project/index.js'\nimport type { PathSource } from '@/lib/project/types.js'\n\nimport type { FileStore, LoadOptions, SaveOptions, StoreOptions } from './types.js'\n\n/**\n * Create a file-backed {@link FileStore} that resolves JSON files from project-local\n * or global home directories.\n *\n * @param options - Store configuration.\n * @returns A FileStore instance.\n */\nexport function createStore<TData = unknown>(options: StoreOptions<TData>): FileStore<TData> {\n const { dirName, defaults } = options\n\n /**\n * Resolve the local project directory for the store.\n *\n * @private\n * @param startDir - Optional directory to start searching from.\n * @returns The local directory path, or null if no project root is found.\n */\n function getLocalDir(startDir?: string): string | null {\n return resolveLocalPath({ dirName, startDir })\n }\n\n /**\n * Resolve the global home directory for the store.\n *\n * @private\n * @returns The global directory path.\n */\n function getGlobalDir(): string {\n return resolveGlobalPath({ dirName })\n }\n\n /**\n * Read the raw string content from a file path.\n *\n * @private\n * @param filePath - The file path to read.\n * @returns The file content, or null if the file does not exist or cannot be read.\n */\n function loadFromPath(filePath: string): string | null {\n if (!existsSync(filePath)) {\n return null\n }\n const [error, content] = attempt(() => readFileSync(filePath, 'utf8'))\n if (error) {\n return null\n }\n return content\n }\n\n /**\n * Resolve a file from local or global directories based on the source strategy.\n *\n * @private\n * @param resolveOptions - Resolution options.\n * @returns The resolved result, or null if not found.\n */\n function resolveFromSource<T>(resolveOptions: {\n source: PathSource\n localDir: string | null\n globalDir: string\n filename: string\n handler: (filePath: string) => T | null\n }): T | null {\n return match(resolveOptions.source)\n .with('local', (): T | null => {\n if (!resolveOptions.localDir) {\n return null\n }\n return resolveOptions.handler(join(resolveOptions.localDir, resolveOptions.filename))\n })\n .with('global', () =>\n resolveOptions.handler(join(resolveOptions.globalDir, resolveOptions.filename))\n )\n .with('resolve', (): T | null => {\n if (resolveOptions.localDir) {\n const localResult = resolveOptions.handler(\n join(resolveOptions.localDir, resolveOptions.filename)\n )\n if (localResult !== null) {\n return localResult\n }\n }\n return resolveOptions.handler(join(resolveOptions.globalDir, resolveOptions.filename))\n })\n .exhaustive()\n }\n\n /**\n * Load the raw string content of a store file.\n *\n * @private\n * @param filename - The filename to load.\n * @param loadOptions - Options controlling source resolution.\n * @returns The raw file content, or null if not found.\n */\n function loadRaw(filename: string, loadOptions: LoadOptions = {}): string | null {\n const { source: loadSource = 'resolve', startDir } = loadOptions\n const localDir = getLocalDir(startDir)\n const globalDir = getGlobalDir()\n\n return resolveFromSource<string>({\n filename,\n globalDir,\n handler: loadFromPath,\n localDir,\n source: loadSource,\n })\n }\n\n /**\n * Load and parse a store file as JSON, merging with defaults if available.\n *\n * @private\n * @param filename - The filename to load.\n * @param loadOptions - Options controlling source resolution.\n * @returns The parsed data, defaults, or null.\n */\n function load(filename: string, loadOptions: LoadOptions = {}): TData | null {\n const raw = loadRaw(filename, loadOptions)\n\n if (raw === null) {\n return defaults ?? null\n }\n\n const [parseError, parsed] = jsonParse(raw)\n if (parseError) {\n return defaults ?? null\n }\n\n if (defaults) {\n return { ...defaults, ...(parsed as Partial<TData>) }\n }\n return parsed as TData\n }\n\n /**\n * Check if a file exists at the given path and return the path if so.\n *\n * @private\n * @param filePath - The file path to check.\n * @returns The file path if it exists, or null.\n */\n function checkFileExists(filePath: string): string | null {\n if (existsSync(filePath)) {\n return filePath\n }\n return null\n }\n\n /**\n * Resolve the file path for a store file without reading its content.\n *\n * @private\n * @param filename - The filename to resolve.\n * @param loadOptions - Options controlling source resolution.\n * @returns The resolved file path, or null if not found.\n */\n function getFilePath(filename: string, loadOptions: LoadOptions = {}): string | null {\n const { source: fileSource = 'resolve', startDir } = loadOptions\n const localDir = getLocalDir(startDir)\n const globalDir = getGlobalDir()\n\n return resolveFromSource<string>({\n filename,\n globalDir,\n handler: checkFileExists,\n localDir,\n source: fileSource,\n })\n }\n\n /**\n * Serialize data to JSON and write it to a store file.\n *\n * Creates the target directory if it does not exist. Defaults to\n * the global home directory when no source is specified.\n *\n * @private\n * @param filename - The filename to write.\n * @param data - The data to serialize.\n * @param saveOptions - Options controlling the write target.\n * @returns A Result with the written file path on success.\n */\n function save(filename: string, data: unknown, saveOptions: SaveOptions = {}): Result<string> {\n const { source: saveSource = 'global', startDir } = saveOptions\n\n const dir = resolveSaveDir({ globalDir: getGlobalDir(), localDir: getLocalDir(startDir), source: saveSource })\n\n if (dir === null) {\n return err(new Error(`Cannot save to \"${saveSource}\" — no local project directory found`))\n }\n\n const [stringifyError, json] = jsonStringify(data, { pretty: true })\n\n if (stringifyError) {\n return err(stringifyError)\n }\n\n const filePath = join(dir, filename)\n\n const [writeError] = attempt(() => {\n mkdirSync(dir, { mode: 0o700, recursive: true })\n writeFileSync(filePath, json, { encoding: 'utf8', mode: 0o600 })\n })\n\n if (writeError) {\n return err(writeError)\n }\n\n return ok(filePath)\n }\n\n return {\n getFilePath,\n getGlobalDir,\n getLocalDir,\n load,\n loadRaw,\n save,\n }\n}\n\n// ---------------------------------------------------------------------------\n// Private helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve the target directory for a save operation.\n *\n * @private\n * @param options - Resolution options.\n * @returns The directory path, or null when `local` is requested but unavailable.\n */\nfunction resolveSaveDir(options: {\n readonly localDir: string | null\n readonly globalDir: string\n readonly source: 'local' | 'global'\n}): string | null {\n return match(options.source)\n .with('local', (): string | null => options.localDir)\n .with('global', () => options.globalDir)\n .exhaustive()\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"create-store-BQUX0tAn.js","names":[],"sources":["../src/lib/store/create-store.ts"],"sourcesContent":["import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'\nimport { join } from 'node:path'\n\nimport { attempt, err, match, ok } from '@kidd-cli/utils/fp'\nimport type { Result } from '@kidd-cli/utils/fp'\nimport { jsonParse, jsonStringify } from '@kidd-cli/utils/json'\n\nimport { resolveGlobalPath, resolveLocalPath } from '@/lib/project/index.js'\nimport type { PathSource } from '@/lib/project/types.js'\n\nimport type { FileStore, LoadOptions, SaveOptions, StoreOptions } from './types.js'\n\n/**\n * Create a file-backed {@link FileStore} that resolves JSON files from project-local\n * or global home directories.\n *\n * @param options - Store configuration.\n * @returns A FileStore instance.\n */\nexport function createStore<TData = unknown>(options: StoreOptions<TData>): FileStore<TData> {\n const { dirName, defaults } = options\n\n /**\n * Resolve the local project directory for the store.\n *\n * @private\n * @param startDir - Optional directory to start searching from.\n * @returns The local directory path, or null if no project root is found.\n */\n function getLocalDir(startDir?: string): string | null {\n return resolveLocalPath({ dirName, startDir })\n }\n\n /**\n * Resolve the global home directory for the store.\n *\n * @private\n * @returns The global directory path.\n */\n function getGlobalDir(): string {\n return resolveGlobalPath({ dirName })\n }\n\n /**\n * Read the raw string content from a file path.\n *\n * @private\n * @param filePath - The file path to read.\n * @returns The file content, or null if the file does not exist or cannot be read.\n */\n function loadFromPath(filePath: string): string | null {\n if (!existsSync(filePath)) {\n return null\n }\n const [error, content] = attempt(() => readFileSync(filePath, 'utf8'))\n if (error) {\n return null\n }\n return content\n }\n\n /**\n * Resolve a file from local or global directories based on the source strategy.\n *\n * @private\n * @param resolveOptions - Resolution options.\n * @returns The resolved result, or null if not found.\n */\n function resolveFromSource<T>(resolveOptions: {\n source: PathSource\n localDir: string | null\n globalDir: string\n filename: string\n handler: (filePath: string) => T | null\n }): T | null {\n return match(resolveOptions.source)\n .with('local', (): T | null => {\n if (!resolveOptions.localDir) {\n return null\n }\n return resolveOptions.handler(join(resolveOptions.localDir, resolveOptions.filename))\n })\n .with('global', () =>\n resolveOptions.handler(join(resolveOptions.globalDir, resolveOptions.filename))\n )\n .with('resolve', (): T | null => {\n if (resolveOptions.localDir) {\n const localResult = resolveOptions.handler(\n join(resolveOptions.localDir, resolveOptions.filename)\n )\n if (localResult !== null) {\n return localResult\n }\n }\n return resolveOptions.handler(join(resolveOptions.globalDir, resolveOptions.filename))\n })\n .exhaustive()\n }\n\n /**\n * Load the raw string content of a store file.\n *\n * @private\n * @param filename - The filename to load.\n * @param loadOptions - Options controlling source resolution.\n * @returns The raw file content, or null if not found.\n */\n function loadRaw(filename: string, loadOptions: LoadOptions = {}): string | null {\n const { source: loadSource = 'resolve', startDir } = loadOptions\n const localDir = getLocalDir(startDir)\n const globalDir = getGlobalDir()\n\n return resolveFromSource<string>({\n filename,\n globalDir,\n handler: loadFromPath,\n localDir,\n source: loadSource,\n })\n }\n\n /**\n * Load and parse a store file as JSON, merging with defaults if available.\n *\n * @private\n * @param filename - The filename to load.\n * @param loadOptions - Options controlling source resolution.\n * @returns The parsed data, defaults, or null.\n */\n function load(filename: string, loadOptions: LoadOptions = {}): TData | null {\n const raw = loadRaw(filename, loadOptions)\n\n if (raw === null) {\n return defaults ?? null\n }\n\n const [parseError, parsed] = jsonParse(raw)\n if (parseError) {\n return defaults ?? null\n }\n\n if (defaults) {\n return { ...defaults, ...(parsed as Partial<TData>) }\n }\n return parsed as TData\n }\n\n /**\n * Check if a file exists at the given path and return the path if so.\n *\n * @private\n * @param filePath - The file path to check.\n * @returns The file path if it exists, or null.\n */\n function checkFileExists(filePath: string): string | null {\n if (existsSync(filePath)) {\n return filePath\n }\n return null\n }\n\n /**\n * Resolve the file path for a store file without reading its content.\n *\n * @private\n * @param filename - The filename to resolve.\n * @param loadOptions - Options controlling source resolution.\n * @returns The resolved file path, or null if not found.\n */\n function getFilePath(filename: string, loadOptions: LoadOptions = {}): string | null {\n const { source: fileSource = 'resolve', startDir } = loadOptions\n const localDir = getLocalDir(startDir)\n const globalDir = getGlobalDir()\n\n return resolveFromSource<string>({\n filename,\n globalDir,\n handler: checkFileExists,\n localDir,\n source: fileSource,\n })\n }\n\n /**\n * Serialize data to JSON and write it to a store file.\n *\n * Creates the target directory if it does not exist. Defaults to\n * the global home directory when no source is specified.\n *\n * @private\n * @param filename - The filename to write.\n * @param data - The data to serialize.\n * @param saveOptions - Options controlling the write target.\n * @returns A Result with the written file path on success.\n */\n function save(filename: string, data: unknown, saveOptions: SaveOptions = {}): Result<string> {\n const { source: saveSource = 'global', startDir } = saveOptions\n\n const dir = resolveSaveDir({\n globalDir: getGlobalDir(),\n localDir: getLocalDir(startDir),\n source: saveSource,\n })\n\n if (dir === null) {\n return err(new Error(`Cannot save to \"${saveSource}\" — no local project directory found`))\n }\n\n const [stringifyError, json] = jsonStringify(data, { pretty: true })\n\n if (stringifyError) {\n return err(stringifyError)\n }\n\n const filePath = join(dir, filename)\n\n const [writeError] = attempt(() => {\n mkdirSync(dir, { mode: 0o700, recursive: true })\n writeFileSync(filePath, json, { encoding: 'utf8', mode: 0o600 })\n })\n\n if (writeError) {\n return err(writeError)\n }\n\n return ok(filePath)\n }\n\n return {\n getFilePath,\n getGlobalDir,\n getLocalDir,\n load,\n loadRaw,\n save,\n }\n}\n\n// ---------------------------------------------------------------------------\n// Private helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve the target directory for a save operation.\n *\n * @private\n * @param options - Resolution options.\n * @returns The directory path, or null when `local` is requested but unavailable.\n */\nfunction resolveSaveDir(options: {\n readonly localDir: string | null\n readonly globalDir: string\n readonly source: 'local' | 'global'\n}): string | null {\n return match(options.source)\n .with('local', (): string | null => options.localDir)\n .with('global', () => options.globalDir)\n .exhaustive()\n}\n"],"mappings":"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"}
|
package/dist/lib/store.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.d.ts","names":[],"sources":["../../src/lib/store/types.ts","../../src/lib/store/create-store.ts"],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"store.d.ts","names":[],"sources":["../../src/lib/store/types.ts","../../src/lib/store/create-store.ts"],"mappings":";;;;;;AAUA;;;;KAAY,UAAA;AAKZ;;;AAAA,UAAiB,YAAA;EACf,OAAA;EACA,QAAA,GAAW,KAAA;AAAA;;;;UAMI,WAAA;EACf,MAAA,GAAS,UAAA;EACT,QAAA;AAAA;;;;UAMe,WAAA;EACf,MAAA,GAAS,UAAA;EACT,QAAA;AAAA;;;;UAMe,SAAA;EACf,WAAA,CAAY,QAAA;EACZ,YAAA;EACA,IAAA,CAAK,QAAA,UAAkB,OAAA,GAAU,WAAA,GAAc,KAAA;EAC/C,OAAA,CAAQ,QAAA,UAAkB,OAAA,GAAU,WAAA;EACpC,WAAA,CAAY,QAAA,UAAkB,OAAA,GAAU,WAAA;EACxC,IAAA,CAAK,QAAA,UAAkB,IAAA,WAAe,OAAA,GAAU,WAAA,GAAc,MAAA;AAAA;;;;;;AAnChE;;;;iBCSgB,WAAA,iBAAA,CAA6B,OAAA,EAAS,YAAA,CAAa,KAAA,IAAS,SAAA,CAAU,KAAA"}
|
|
@@ -1 +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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"}
|
|
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({\n message: 'No credential resolved from any source',\n type: 'no_credential',\n })\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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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kidd-cli/core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "An opinionated CLI framework for Node.js",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
@@ -11,6 +11,11 @@
|
|
|
11
11
|
"zod"
|
|
12
12
|
],
|
|
13
13
|
"license": "MIT",
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "https://github.com/joggrdocs/kidd.git",
|
|
17
|
+
"directory": "packages/core"
|
|
18
|
+
},
|
|
14
19
|
"files": [
|
|
15
20
|
"dist"
|
|
16
21
|
],
|
|
@@ -65,8 +70,8 @@
|
|
|
65
70
|
"yaml": "^2.8.2",
|
|
66
71
|
"yargs": "^18.0.0",
|
|
67
72
|
"zod": "^4.3.6",
|
|
68
|
-
"@kidd-cli/config": "0.1.
|
|
69
|
-
"@kidd-cli/utils": "0.1.
|
|
73
|
+
"@kidd-cli/config": "0.1.1",
|
|
74
|
+
"@kidd-cli/utils": "0.1.1"
|
|
70
75
|
},
|
|
71
76
|
"devDependencies": {
|
|
72
77
|
"@types/node": "^25.3.3",
|