@kidd-cli/core 0.1.0 → 0.1.2
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/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.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/index.d.ts
CHANGED
|
@@ -20,7 +20,7 @@ declare function cli<TSchema extends z.ZodType = z.ZodType>(options: CliOptions<
|
|
|
20
20
|
* @param def - Command definition including description, args schema, and handler.
|
|
21
21
|
* @returns A resolved Command object for registration in the command map.
|
|
22
22
|
*/
|
|
23
|
-
declare function command<TArgsDef extends ArgsDef = ArgsDef, TConfig extends Record<string, unknown> = Record<string, unknown>>(def: CommandDef<TArgsDef, TConfig>): Command
|
|
23
|
+
declare function command<TArgsDef extends ArgsDef = ArgsDef, TConfig extends Record<string, unknown> = Record<string, unknown>>(def: CommandDef<TArgsDef, TConfig>): Command;
|
|
24
24
|
//#endregion
|
|
25
25
|
//#region src/autoloader.d.ts
|
|
26
26
|
/**
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/cli.ts","../src/command.ts","../src/autoloader.ts","../src/context/decorate.ts","../src/middleware.ts"],"mappings":";;;;;;;;;AAyBA;;;;iBAAsB,GAAA,iBAAoB,CAAA,CAAE,OAAA,GAAU,CAAA,CAAE,OAAA,CAAA,CACtD,OAAA,EAAS,UAAA,CAAW,OAAA,IACnB,OAAA;;;;;;;;AAFH;iBCfgB,OAAA,kBACG,OAAA,GAAU,OAAA,kBACX,MAAA,oBAA0B,MAAA,kBAAA,CAC1C,GAAA,EAAK,UAAA,CAAW,QAAA,EAAU,OAAA,IAAW,OAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/cli.ts","../src/command.ts","../src/autoloader.ts","../src/context/decorate.ts","../src/middleware.ts"],"mappings":";;;;;;;;;AAyBA;;;;iBAAsB,GAAA,iBAAoB,CAAA,CAAE,OAAA,GAAU,CAAA,CAAE,OAAA,CAAA,CACtD,OAAA,EAAS,UAAA,CAAW,OAAA,IACnB,OAAA;;;;;;;;AAFH;iBCfgB,OAAA,kBACG,OAAA,GAAU,OAAA,kBACX,MAAA,oBAA0B,MAAA,kBAAA,CAC1C,GAAA,EAAK,UAAA,CAAW,QAAA,EAAU,OAAA,IAAW,OAAA;;;;;;;;ADYvC;iBEPsB,QAAA,CAAS,OAAA,GAAU,eAAA,GAAkB,OAAA,CAAQ,UAAA;;;;;;;;AFOnE;;;;;;;;;;;;;;;;;;;;;iBGGgB,eAAA,6BAAA,CACd,GAAA,EAAK,OAAA,EACL,GAAA,EAAK,IAAA,EACL,KAAA,EAAO,MAAA,GACN,OAAA;;;;;;;;AHPH;iBIfgB,UAAA,iBAA2B,MAAA,oBAA0B,MAAA,kBAAA,CACnE,OAAA,EAAS,YAAA,CAAa,OAAA,IACrB,UAAA,CAAW,OAAA"}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["match"],"sources":["../src/context/error.ts","../src/context/output.ts","../src/context/prompts.ts","../src/context/store.ts","../src/context/create-context.ts","../src/autoloader.ts","../src/runtime/args/zod.ts","../src/runtime/args/parser.ts","../src/runtime/args/register.ts","../src/runtime/register.ts","../src/runtime/runner.ts","../src/runtime/runtime.ts","../src/cli.ts","../src/command.ts"],"sourcesContent":["import type { Tagged } from '@kidd-cli/utils/tag'\nimport { TAG, hasTag, withTag } from '@kidd-cli/utils/tag'\n\nimport { DEFAULT_EXIT_CODE } from '@/utils/constants.js'\n\nexport { DEFAULT_EXIT_CODE }\n\n/**\n * Plain data representation of a ContextError (no Error prototype).\n *\n * Useful for serializing error data without carrying the Error prototype chain,\n * for example when logging or forwarding errors across process boundaries.\n */\nexport type ContextErrorData = Tagged<\n {\n readonly code: string | undefined\n readonly exitCode: number\n readonly message: string\n },\n 'ContextError'\n>\n\n/**\n * An Error subtype carrying an exit code and optional error code.\n *\n * Created by {@link createContextError} and thrown by `ctx.fail()`.\n * The CLI boundary catches these to produce clean, user-facing error output\n * with the correct process exit code.\n */\nexport type ContextError = Error &\n Tagged<\n {\n readonly code: string | undefined\n readonly exitCode: number\n },\n 'ContextError'\n >\n\n/**\n * Create a ContextError with an exit code and optional error code.\n *\n * Used to surface user-facing CLI errors with clean messages.\n * The error carries a Symbol-based tag for reliable type-narrowing\n * via {@link isContextError}.\n *\n * @param message - Human-readable error message.\n * @param options - Optional error code and exit code overrides.\n * @returns A ContextError instance.\n */\nexport function createContextError(\n message: string,\n options?: { code?: string; exitCode?: number }\n): ContextError {\n const data = createContextErrorData(message, options)\n const error = new Error(data.message) as ContextError\n error.name = 'ContextError'\n Object.defineProperty(error, TAG, { enumerable: false, value: 'ContextError', writable: false })\n Object.defineProperty(error, 'code', { enumerable: true, value: data.code, writable: false })\n Object.defineProperty(error, 'exitCode', {\n enumerable: true,\n value: data.exitCode,\n writable: false,\n })\n return error\n}\n\n/**\n * Type guard that narrows an unknown value to {@link ContextError}.\n *\n * Checks that the value is an Error instance whose `[TAG]` property\n * equals `'ContextError'`, which distinguishes CLI-layer errors from\n * unexpected exceptions.\n *\n * @param error - The value to check.\n * @returns `true` when the value is a ContextError.\n */\nexport function isContextError(error: unknown): error is ContextError {\n if (error instanceof Error) {\n return hasTag(error, 'ContextError')\n }\n return false\n}\n\n// ---------------------------------------------------------------------------\n// Private helpers\n// ---------------------------------------------------------------------------\n\nfunction resolveExitCode(options: { code?: string; exitCode?: number } | undefined): number {\n if (options && options.exitCode !== undefined) {\n return options.exitCode\n }\n return DEFAULT_EXIT_CODE\n}\n\nfunction resolveCode(\n options: { code?: string; exitCode?: number } | undefined\n): string | undefined {\n if (options && options.code !== undefined) {\n return options.code\n }\n return undefined\n}\n\nfunction createContextErrorData(\n message: string,\n options?: { code?: string; exitCode?: number }\n): ContextErrorData {\n return withTag(\n {\n code: resolveCode(options),\n exitCode: resolveExitCode(options),\n message,\n },\n 'ContextError'\n )\n}\n","import { jsonStringify } from '@kidd-cli/utils/json'\n\nimport type { Output, OutputOptions } from './types.js'\n\n/**\n * Create the structured output methods for a context.\n *\n * @private\n * @param stream - The writable stream to write output to.\n * @returns An Output instance backed by the given stream.\n */\nexport function createContextOutput(stream: NodeJS.WriteStream): Output {\n return {\n markdown(content: string): void {\n stream.write(`${content}\\n`)\n },\n raw(content: string): void {\n stream.write(content)\n },\n table(rows: Record<string, unknown>[], options?: OutputOptions): void {\n if (options && options.json) {\n const [, json] = jsonStringify(rows, { pretty: true })\n stream.write(`${json}\\n`)\n return\n }\n if (rows.length === 0) {\n return\n }\n const [firstRow] = rows\n if (!firstRow) {\n return\n }\n writeTableToStream(stream, rows, Object.keys(firstRow))\n },\n write(data: unknown, options?: OutputOptions): void {\n if ((options && options.json) || (typeof data === 'object' && data !== null)) {\n const [, json] = jsonStringify(data, { pretty: true })\n stream.write(`${json}\\n`)\n } else {\n stream.write(`${String(data)}\\n`)\n }\n },\n }\n}\n\n// ---------------------------------------------------------------------------\n// Private helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Format an unknown value as a string for table cell display.\n *\n * @private\n * @param val - The value to format.\n * @returns The stringified value, or empty string for undefined.\n */\nfunction formatStringValue(val: unknown): string {\n if (val === undefined) {\n return ''\n }\n return String(val)\n}\n\n/**\n * Options for creating a table header string.\n */\ninterface TableHeaderOptions {\n keys: string[]\n widths: (number | undefined)[]\n}\n\n/**\n * Create a padded header row string from column keys and widths.\n *\n * @private\n * @param options - The keys and column widths.\n * @returns A formatted header string.\n */\nfunction createTableHeader(options: TableHeaderOptions): string {\n const { keys, widths } = options\n return keys\n .map((key, idx) => {\n const width = widths[idx]\n if (width === undefined) {\n return key\n }\n return key.padEnd(width)\n })\n .join(' ')\n}\n\n/**\n * Options for creating a table row string.\n */\ninterface TableRowOptions {\n row: Record<string, unknown>\n keys: string[]\n widths: (number | undefined)[]\n}\n\n/**\n * Create a padded row string from a data record, column keys, and widths.\n *\n * @private\n * @param options - The row data, keys, and column widths.\n * @returns A formatted row string.\n */\nfunction createTableRow(options: TableRowOptions): string {\n const { row, keys, widths } = options\n return keys\n .map((key, idx) => {\n const width = widths[idx]\n const val = formatStringValue(row[key])\n if (width === undefined) {\n return val\n }\n return val.padEnd(width)\n })\n .join(' ')\n}\n\n/**\n * Compute the maximum column width for each key across all rows.\n *\n * @private\n * @param rows - The data rows.\n * @param keys - The column keys.\n * @returns An array of column widths.\n */\nfunction computeColumnWidths(rows: Record<string, unknown>[], keys: string[]): number[] {\n return keys.map((key) => {\n const values = rows.map((row) => formatStringValue(row[key]))\n return Math.max(key.length, ...values.map((val) => val.length))\n })\n}\n\n/**\n * Write a formatted table (header, separator, rows) to a writable stream.\n *\n * @private\n * @param stream - The writable stream.\n * @param rows - The data rows.\n * @param keys - The column keys.\n */\nfunction writeTableToStream(\n stream: NodeJS.WriteStream,\n rows: Record<string, unknown>[],\n keys: string[]\n): void {\n const widths = computeColumnWidths(rows, keys)\n const header = createTableHeader({ keys, widths })\n const separator = widths.map((width) => '-'.repeat(width)).join(' ')\n const dataRows = rows.map((row) => createTableRow({ keys, row, widths }))\n const content = [header, separator, ...dataRows].join('\\n')\n stream.write(`${content}\\n`)\n}\n","import { createPromptUtils } from '@/lib/prompts/index.js'\n\nimport { DEFAULT_EXIT_CODE, createContextError } from './error.js'\nimport type { Prompts } from './types.js'\n\n/**\n * Create the interactive prompt methods for a context.\n *\n * @private\n * @returns A Prompts instance backed by clack.\n */\nexport function createContextPrompts(): Prompts {\n const utils = createPromptUtils()\n\n return {\n async confirm(opts): Promise<boolean> {\n const result = await utils.confirm(opts)\n return unwrapCancelSignal(utils, result)\n },\n async multiselect<Type>(opts: Parameters<Prompts['multiselect']>[0]): Promise<Type[]> {\n const result = await utils.multiselect<Type>(\n opts as Parameters<typeof utils.multiselect<Type>>[0]\n )\n return unwrapCancelSignal(utils, result)\n },\n async password(opts): Promise<string> {\n const result = await utils.password(opts)\n return unwrapCancelSignal(utils, result)\n },\n async select<Type>(opts: Parameters<Prompts['select']>[0]): Promise<Type> {\n const result = await utils.select<Type>(opts as Parameters<typeof utils.select<Type>>[0])\n return unwrapCancelSignal(utils, result)\n },\n async text(opts): Promise<string> {\n const result = await utils.text(opts)\n return unwrapCancelSignal(utils, result)\n },\n }\n}\n\n// ---------------------------------------------------------------------------\n// Private helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Unwrap a prompt result that may be a cancel symbol.\n *\n * If the user cancelled (Ctrl-C), throws a ContextError. Otherwise returns\n * the typed result value.\n *\n * @private\n * @param utils - The prompt utils instance (for isCancel and cancel).\n * @param result - The raw prompt result (value or cancel symbol).\n * @returns The unwrapped typed value.\n */\nfunction unwrapCancelSignal<Type>(\n utils: ReturnType<typeof createPromptUtils>,\n result: Type | symbol\n): Type {\n if (utils.isCancel(result)) {\n utils.cancel('Operation cancelled.')\n throw createContextError('Prompt cancelled by user', {\n code: 'PROMPT_CANCELLED',\n exitCode: DEFAULT_EXIT_CODE,\n })\n }\n return result as Type\n}\n","import type { Store } from './types.js'\n\n/**\n * Create an in-memory key-value store.\n *\n * @private\n * @returns A Store instance backed by a Map.\n */\nexport function createMemoryStore<TMap extends Record<string, unknown>>(): Store<TMap> {\n const map = new Map<string, unknown>()\n\n return {\n clear(): void {\n map.clear()\n },\n delete(key: string): boolean {\n return map.delete(key)\n },\n get<Key extends Extract<keyof TMap, string>>(key: Key): TMap[Key] | undefined {\n return map.get(key) as TMap[Key] | undefined\n },\n has(key: string): boolean {\n return map.has(key)\n },\n set<Key extends Extract<keyof TMap, string>>(key: Key, value: TMap[Key]): void {\n map.set(key, value)\n },\n }\n}\n","import { createCliLogger } from '@/lib/logger.js'\nimport type { CliLogger } from '@/lib/logger.js'\nimport { createSpinner } from '@/lib/prompts/index.js'\nimport type { AnyRecord, KiddStore, Merge } from '@/types.js'\n\nimport { createContextError } from './error.js'\nimport { createContextOutput } from './output.js'\nimport { createContextPrompts } from './prompts.js'\nimport { createMemoryStore } from './store.js'\nimport type { Context, Meta, Output, Prompts, Spinner, Store, StoreMap } from './types.js'\n\n/**\n * Options for creating a {@link Context} instance via {@link createContext}.\n *\n * Carries the parsed args, validated config, and CLI metadata needed to\n * assemble a fully-wired context. An optional `logger` override allows\n * callers to inject a custom {@link CliLogger}; when omitted a default\n * @clack/prompts-backed instance is used.\n */\nexport interface CreateContextOptions<TArgs extends AnyRecord, TConfig extends AnyRecord> {\n readonly args: TArgs\n readonly config: TConfig\n readonly meta: { readonly name: string; readonly version: string; readonly command: string[] }\n readonly logger?: CliLogger\n readonly output?: NodeJS.WriteStream\n}\n\n/**\n * Create the {@link Context} object threaded through middleware and command handlers.\n *\n * Assembles logger, spinner, output, store, prompts, and meta from\n * the provided options into a single immutable context. Each sub-system is\n * constructed via its own factory so this function remains a lean orchestrator.\n *\n * @param options - Args, config, and meta for the current invocation.\n * @returns A fully constructed Context.\n */\nexport function createContext<TArgs extends AnyRecord, TConfig extends AnyRecord>(\n options: CreateContextOptions<TArgs, TConfig>\n): Context<TArgs, TConfig> {\n const ctxLogger: CliLogger = options.logger ?? createCliLogger()\n const ctxSpinner: Spinner = createSpinner()\n const ctxOutput: Output = createContextOutput(options.output ?? process.stdout)\n const ctxStore: Store<Merge<KiddStore, StoreMap>> = createMemoryStore()\n const ctxPrompts: Prompts = createContextPrompts()\n const ctxMeta: Meta = {\n command: options.meta.command,\n name: options.meta.name,\n version: options.meta.version,\n }\n\n // Middleware-augmented properties (e.g. `auth`) are added at runtime.\n // See `decorateContext` — they are intentionally absent here.\n return {\n args: options.args as Context<TArgs, TConfig>['args'],\n config: options.config as Context<TArgs, TConfig>['config'],\n fail(message: string, failOptions?: { code?: string; exitCode?: number }): never {\n throw createContextError(message, failOptions)\n },\n logger: ctxLogger,\n meta: ctxMeta as Context<TArgs, TConfig>['meta'],\n output: ctxOutput,\n prompts: ctxPrompts,\n spinner: ctxSpinner,\n store: ctxStore,\n } as Context<TArgs, TConfig>\n}\n","import type { Dirent } from 'node:fs'\nimport { readdir } from 'node:fs/promises'\nimport { basename, extname, join, resolve } from 'node:path'\n\nimport { isPlainObject, isString } from '@kidd-cli/utils/fp'\nimport { hasTag, withTag } from '@kidd-cli/utils/tag'\n\nimport type { AutoloadOptions, Command, CommandMap } from './types.js'\n\nconst VALID_EXTENSIONS = new Set(['.ts', '.js', '.mjs'])\nconst INDEX_NAME = 'index'\n\n/**\n * Scan a directory for command files and produce a CommandMap.\n *\n * @param options - Autoload configuration (directory override, etc.).\n * @returns A promise resolving to a CommandMap built from the directory tree.\n */\nexport async function autoload(options?: AutoloadOptions): Promise<CommandMap> {\n const dir = resolveDir(options)\n const entries = await readdir(dir, { withFileTypes: true })\n\n const fileEntries = entries.filter(isCommandFile)\n const dirEntries = entries.filter(isCommandDir)\n\n const fileResults = await Promise.all(\n fileEntries.map(async (entry): Promise<[string, Command] | undefined> => {\n const cmd = await importCommand(join(dir, entry.name))\n if (!cmd) {\n return undefined\n }\n return [deriveCommandName(entry), cmd]\n })\n )\n\n const dirResults = await Promise.all(\n dirEntries.map((entry) => buildDirCommand(join(dir, entry.name)))\n )\n\n const allResults = [...fileResults, ...dirResults]\n const validPairs = allResults.filter((pair): pair is [string, Command] => pair !== undefined)\n\n return Object.fromEntries(validPairs) as CommandMap\n}\n\n// ---------------------------------------------------------------------------\n// Private\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve the target directory from autoload options.\n *\n * @private\n * @param options - Optional autoload configuration.\n * @returns The resolved absolute directory path.\n */\nfunction resolveDir(options?: AutoloadOptions): string {\n if (options && isString(options.dir)) {\n return resolve(options.dir)\n }\n return resolve('./commands')\n}\n\n/**\n * Scan a subdirectory and assemble it as a parent command with subcommands.\n *\n * If the directory contains an `index.ts`/`index.js`, that becomes the parent\n * handler. Otherwise a handler-less group command is created that demands a\n * subcommand.\n *\n * @private\n * @param dir - Absolute path to the subdirectory.\n * @returns A tuple of [name, Command] or undefined if the directory is empty.\n */\nasync function buildDirCommand(dir: string): Promise<[string, Command] | undefined> {\n const name = basename(dir)\n const dirEntries = await readdir(dir, { withFileTypes: true })\n const subCommands = await buildSubCommands(dir, dirEntries)\n const indexFile = findIndexInEntries(dirEntries)\n\n if (indexFile) {\n const parentCommand = await importCommand(join(dir, indexFile.name))\n if (parentCommand) {\n return [name, withTag({ ...parentCommand, commands: subCommands }, 'Command')]\n }\n }\n\n if (Object.keys(subCommands).length === 0) {\n return undefined\n }\n\n return [name, withTag({ commands: subCommands }, 'Command')]\n}\n\n/**\n * Build subcommands from already-read directory entries, avoiding a redundant readdir call.\n *\n * @private\n * @param dir - Absolute path to the directory.\n * @param entries - Pre-read directory entries.\n * @returns A CommandMap built from the entries.\n */\nasync function buildSubCommands(dir: string, entries: Dirent[]): Promise<CommandMap> {\n const fileEntries = entries.filter(isCommandFile)\n const dirEntries = entries.filter(isCommandDir)\n\n const fileResults = await Promise.all(\n fileEntries.map(async (entry): Promise<[string, Command] | undefined> => {\n const cmd = await importCommand(join(dir, entry.name))\n if (!cmd) {\n return undefined\n }\n return [deriveCommandName(entry), cmd]\n })\n )\n\n const dirResults = await Promise.all(\n dirEntries.map((entry) => buildDirCommand(join(dir, entry.name)))\n )\n\n const allResults = [...fileResults, ...dirResults]\n const validPairs = allResults.filter((pair): pair is [string, Command] => pair !== undefined)\n\n return Object.fromEntries(validPairs) as CommandMap\n}\n\n/**\n * Find the index file (index.ts or index.js) in pre-read directory entries.\n *\n * @private\n * @param entries - Pre-read directory entries.\n * @returns The index file's Dirent or undefined.\n */\nfunction findIndexInEntries(entries: Dirent[]): Dirent | undefined {\n return entries.find(\n (entry) =>\n entry.isFile() &&\n VALID_EXTENSIONS.has(extname(entry.name)) &&\n basename(entry.name, extname(entry.name)) === INDEX_NAME\n )\n}\n\n/**\n * Dynamically import a file and validate that its default export is a Command.\n *\n * @private\n * @param filePath - Absolute path to the file to import.\n * @returns The Command if valid, or undefined.\n */\nasync function importCommand(filePath: string): Promise<Command | undefined> {\n const mod: unknown = await import(filePath)\n if (isCommandExport(mod)) {\n return mod.default\n }\n return undefined\n}\n\n/**\n * Check whether a module's default export is a Command object.\n *\n * ES module namespace objects have a null prototype, so isPlainObject\n * rejects them. We only need to verify the namespace is a non-null\n * object with a default export that is a plain Command object.\n *\n * @private\n * @param mod - The imported module to inspect.\n * @returns True when the module has a Command as its default export.\n */\nfunction isCommandExport(mod: unknown): mod is { default: Command } {\n if (typeof mod !== 'object' || mod === null) {\n return false\n }\n const def = (mod as Record<string, unknown>)['default']\n if (!isPlainObject(def)) {\n return false\n }\n return hasTag(def, 'Command')\n}\n\n/**\n * Derive a command name from a directory entry by stripping its extension.\n *\n * @private\n * @param entry - The directory entry to derive the name from.\n * @returns The file name without its extension.\n */\nfunction deriveCommandName(entry: Dirent): string {\n return basename(entry.name, extname(entry.name))\n}\n\n/**\n * Predicate: entry is a command file (.ts/.js, not index, not _/. prefixed).\n *\n * @private\n * @param entry - The directory entry to check.\n * @returns True when the entry is a valid command file.\n */\nfunction isCommandFile(entry: Dirent): boolean {\n if (!entry.isFile()) {\n return false\n }\n if (entry.name.startsWith('_') || entry.name.startsWith('.')) {\n return false\n }\n if (!VALID_EXTENSIONS.has(extname(entry.name))) {\n return false\n }\n return deriveCommandName(entry) !== INDEX_NAME\n}\n\n/**\n * Predicate: entry is a scannable command directory (not _/. prefixed).\n *\n * @private\n * @param entry - The directory entry to check.\n * @returns True when the entry is a valid command directory.\n */\nfunction isCommandDir(entry: Dirent): boolean {\n if (!entry.isDirectory()) {\n return false\n }\n return !entry.name.startsWith('_') && !entry.name.startsWith('.')\n}\n","import { match } from 'ts-pattern'\nimport type { Options as YargsOptions } from 'yargs'\nimport type { z } from 'zod'\n\n/**\n * Type guard that checks whether a value is a zod object schema.\n *\n * @param args - The value to check.\n * @returns True when args is a ZodObject.\n */\nexport function isZodSchema(args: unknown): args is z.ZodObject<z.ZodRawShape> {\n return (\n typeof args === 'object' &&\n args !== null &&\n '_def' in args &&\n typeof (args as { _def: unknown })._def === 'object' &&\n (args as { _def: { type?: string } })._def !== null &&\n (args as { _def: { type?: string } })._def.type === 'object'\n )\n}\n\n/**\n * Convert an entire zod object schema into a record of yargs options.\n *\n * @param schema - The zod object schema.\n * @returns A record mapping field names to yargs option definitions.\n */\nexport function zodSchemaToYargsOptions(\n schema: z.ZodObject<z.ZodRawShape>\n): Record<string, YargsOptions> {\n const shape = schema.shape as Record<string, z.ZodTypeAny>\n return Object.fromEntries(\n Object.entries(shape).map(([key, fieldSchema]): [string, YargsOptions] => [\n key,\n getZodTypeOption(fieldSchema),\n ])\n )\n}\n\n// ---------------------------------------------------------------------------\n// Private\n// ---------------------------------------------------------------------------\n\ninterface ZodDef {\n type?: string\n innerType?: z.ZodTypeAny\n defaultValue?: unknown\n}\n\ninterface ZodTypeInfo {\n defaultValue: unknown\n inner: z.ZodTypeAny\n isOptional: boolean\n}\n\ninterface UnwrapOptions {\n def: ZodDef\n current: z.ZodTypeAny\n defaultValue: unknown\n}\n\ninterface UnwrapRecursiveOptions {\n current: z.ZodTypeAny\n isOptional: boolean\n defaultValue: unknown\n}\n\n/**\n * Extract a default value from a zod definition, falling back to the provided value.\n *\n * @private\n * @param def - The zod definition to inspect.\n * @param fallback - Value to return when no default is defined.\n * @returns The resolved default value.\n */\nfunction resolveDefaultValue(def: ZodDef, fallback: unknown): unknown {\n if (def.defaultValue !== undefined) {\n return def.defaultValue\n }\n return fallback\n}\n\n/**\n * Unwrap a ZodOptional type, recursing into the inner type.\n *\n * @private\n * @param options - The unwrap options containing def, current type, and default value.\n * @returns Unwrapped type information.\n */\nfunction unwrapOptional(options: UnwrapOptions): ZodTypeInfo {\n const { def, current, defaultValue } = options\n if (def.innerType) {\n return unwrapZodTypeRecursive({ current: def.innerType, defaultValue, isOptional: true })\n }\n return { defaultValue, inner: current, isOptional: true }\n}\n\n/**\n * Unwrap a ZodDefault type, resolving its default value and recursing.\n *\n * @private\n * @param options - The unwrap options containing def, current type, and default value.\n * @returns Unwrapped type information with the resolved default.\n */\nfunction unwrapDefault(options: UnwrapOptions): ZodTypeInfo {\n const { def, current, defaultValue } = options\n const newDefault = resolveDefaultValue(def, defaultValue)\n if (def.innerType) {\n return unwrapZodTypeRecursive({\n current: def.innerType,\n defaultValue: newDefault,\n isOptional: true,\n })\n }\n return { defaultValue: newDefault, inner: current, isOptional: true }\n}\n\n/**\n * Recursively unwrap optional and default wrappers from a zod type.\n *\n * @private\n * @param options - The recursive unwrap options containing current type, optionality flag, and default value.\n * @returns The fully unwrapped type information.\n */\nfunction unwrapZodTypeRecursive(options: UnwrapRecursiveOptions): ZodTypeInfo {\n const { current, isOptional, defaultValue } = options\n const def = (current as { _def: ZodDef })._def\n if (def.type === 'optional') {\n return unwrapOptional({ current, def, defaultValue })\n }\n if (def.type === 'default') {\n return unwrapDefault({ current, def, defaultValue })\n }\n return { defaultValue, inner: current, isOptional }\n}\n\n/**\n * Unwrap a zod schema to extract its base type, optionality, and default value.\n *\n * @private\n * @param schema - The zod type to unwrap.\n * @returns The unwrapped type information.\n */\nfunction unwrapZodType(schema: z.ZodTypeAny): ZodTypeInfo {\n return unwrapZodTypeRecursive({ current: schema, defaultValue: undefined, isOptional: false })\n}\n\n/**\n * Map a zod type name to a yargs option type string.\n *\n * @private\n * @param typeName - The zod type name (e.g. 'string', 'number').\n * @returns The corresponding yargs type.\n */\nfunction resolveZodYargsType(\n typeName: string | undefined\n): 'string' | 'number' | 'boolean' | 'array' {\n return match(typeName)\n .with('string', () => 'string' as const)\n .with('number', () => 'number' as const)\n .with('boolean', () => 'boolean' as const)\n .with('array', () => 'array' as const)\n .otherwise(() => 'string' as const)\n}\n\n/**\n * Build a base yargs option from a zod schema's description and default.\n *\n * @private\n * @param inner - The unwrapped zod schema instance.\n * @param defaultValue - The resolved default value.\n * @returns A partial yargs option object.\n */\nfunction buildBaseOption(inner: z.ZodTypeAny, defaultValue: unknown): YargsOptions {\n const base: YargsOptions = {}\n const { description } = inner as { description?: string }\n if (description) {\n base.describe = description\n }\n if (defaultValue !== undefined) {\n base.default = defaultValue\n }\n return base\n}\n\n/**\n * Convert a single zod field schema into a complete yargs option definition.\n *\n * @private\n * @param schema - A single zod field type.\n * @returns A complete yargs option object.\n */\nfunction getZodTypeOption(schema: z.ZodTypeAny): YargsOptions {\n const { inner, isOptional, defaultValue } = unwrapZodType(schema)\n const innerDef = (inner as { _def: ZodDef })._def\n const base = {\n ...buildBaseOption(inner, defaultValue),\n type: resolveZodYargsType(innerDef.type),\n }\n if (!isOptional) {\n return { ...base, demandOption: true }\n }\n return base\n}\n","import { err, ok } from '@kidd-cli/utils/fp'\nimport type { Result } from '@kidd-cli/utils/fp'\nimport { formatZodIssues } from '@kidd-cli/utils/validate'\n\nimport type { Command } from '@/types.js'\n\nimport type { ArgsParser } from '../types.js'\nimport { isZodSchema } from './zod.js'\n\n/**\n * Create an args parser that cleans and validates raw parsed arguments.\n *\n * Captures the argument definition in a closure and returns an ArgsParser\n * whose `parse` method strips yargs-internal keys and validates against\n * a zod schema when one is defined.\n *\n * @param argsDef - The argument definition from the command.\n * @returns An ArgsParser with a parse method.\n */\nexport function createArgsParser(argsDef: Command['args']): ArgsParser {\n return {\n parse(rawArgs: Record<string, unknown>): Result<Record<string, unknown>, Error> {\n const cleaned = cleanParsedArgs(rawArgs)\n return validateArgs(argsDef, cleaned)\n },\n }\n}\n\n// ---------------------------------------------------------------------------\n// Private\n// ---------------------------------------------------------------------------\n\n/**\n * Strip yargs-internal keys (`_`, `$0`) and camelCase-duplicated hyphenated keys\n * from a parsed argv record, returning only user-defined arguments.\n *\n * @private\n * @param argv - Raw parsed argv from yargs.\n * @returns A cleaned record containing only user-defined arguments.\n */\nfunction cleanParsedArgs(argv: Record<string, unknown>): Record<string, unknown> {\n return Object.fromEntries(\n Object.entries(argv).filter(([key]) => key !== '_' && key !== '$0' && !key.includes('-'))\n )\n}\n\n/**\n * Validate parsed arguments against a zod schema when one is defined.\n *\n * If the command uses yargs-native args (no zod schema), the parsed args are\n * returned as-is. When a zod schema is present, validation is performed and\n * a Result error is returned on failure.\n *\n * @private\n * @param argsDef - The argument definition from the command.\n * @param parsedArgs - The cleaned parsed arguments.\n * @returns A Result containing validated arguments (zod-parsed when applicable).\n */\nfunction validateArgs(\n argsDef: Command['args'],\n parsedArgs: Record<string, unknown>\n): Result<Record<string, unknown>, Error> {\n if (!argsDef || !isZodSchema(argsDef)) {\n return ok(parsedArgs)\n }\n const result = argsDef.safeParse(parsedArgs)\n if (!result.success) {\n return err(new Error(`Invalid arguments:\\n ${formatZodIssues(result.error.issues).message}`))\n }\n return ok(result.data as Record<string, unknown>)\n}\n","import type { Argv, Options as YargsOptions } from 'yargs'\n\nimport type { Command, YargsArgDef } from '@/types.js'\n\nimport { isZodSchema, zodSchemaToYargsOptions } from './zod.js'\n\n/**\n * Register argument definitions on a yargs builder.\n *\n * Accepts either a zod object schema or a record of yargs-native arg definitions\n * and wires them as yargs options on the given builder instance.\n *\n * @param builder - The yargs Argv instance to register options on.\n * @param args - Argument definitions from a Command.\n */\nexport function registerCommandArgs(builder: Argv, args: Command['args']): void {\n if (!args) {\n return\n }\n if (isZodSchema(args)) {\n const options = zodSchemaToYargsOptions(args)\n for (const [key, opt] of Object.entries(options)) {\n builder.option(key, opt)\n }\n } else {\n const argsDef = args as Record<string, YargsArgDef>\n for (const [key, def] of Object.entries(argsDef)) {\n builder.option(key, yargsArgDefToOption(def))\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Private\n// ---------------------------------------------------------------------------\n\n/**\n * Convert a yargs-native arg definition into a yargs option object.\n *\n * @private\n * @param def - The yargs arg definition.\n * @returns A yargs option object.\n */\nfunction yargsArgDefToOption(def: YargsArgDef): YargsOptions {\n return {\n alias: def.alias,\n choices: def.choices as string[],\n default: def.default,\n demandOption: def.required ?? false,\n describe: def.description,\n type: def.type,\n }\n}\n","import { hasTag } from '@kidd-cli/utils/tag'\nimport type { Argv } from 'yargs'\n\nimport type { Context } from '@/context/types.js'\nimport type { Command, CommandMap, Middleware } from '@/types.js'\n\nimport { registerCommandArgs } from './args/index.js'\nimport type { ResolvedCommand, ResolvedRef } from './types.js'\n\n/**\n * Type guard that checks whether a value is a Command object.\n *\n * @param value - The value to test.\n * @returns True when the value has `[TAG] === 'Command'`.\n */\nexport function isCommand(value: unknown): value is Command {\n return hasTag(value, 'Command')\n}\n\n/**\n * Register all commands from a CommandMap on a yargs instance.\n *\n * Iterates over the command map, filters for valid Command objects,\n * and recursively registers each command (including subcommands) on\n * the provided yargs Argv instance.\n *\n * @param options - Registration options including the command map, yargs instance, and resolution ref.\n */\nexport function registerCommands(options: RegisterCommandsOptions): void {\n const { instance, commands, resolved, parentPath } = options\n const commandEntries = Object.entries(commands).filter(([, entry]) => isCommand(entry))\n\n for (const [name, entry] of commandEntries) {\n registerResolvedCommand({\n builder: instance,\n cmd: entry as Command,\n instance,\n name,\n parentPath,\n resolved,\n })\n }\n}\n\nexport type { ResolvedCommand, ResolvedRef } from './types.js'\n\n// ---------------------------------------------------------------------------\n// Private\n// ---------------------------------------------------------------------------\n\ninterface RegisterResolvedCommandOptions {\n builder: Argv\n cmd: Command\n instance: Argv\n name: string\n parentPath: string[]\n resolved: ResolvedRef\n}\n\ninterface RegisterCommandsOptions {\n commands: CommandMap\n instance: Argv\n parentPath: string[]\n resolved: ResolvedRef\n}\n\n/**\n * Register a single resolved command (and its subcommands) with yargs.\n *\n * Sets up the yargs command handler, wires argument definitions, and\n * recursively registers any nested subcommands. On match, stores the\n * resolved handler and command path in the shared ref.\n *\n * @private\n * @param options - Command registration context.\n */\nfunction registerResolvedCommand(options: RegisterResolvedCommandOptions): void {\n const { instance, name, cmd, resolved, parentPath } = options\n const description = cmd.description ?? ''\n\n instance.command(\n name,\n description,\n (builder: Argv) => {\n registerCommandArgs(builder, cmd.args)\n\n if (cmd.commands) {\n const subCommands = Object.entries(cmd.commands).filter(([, entry]) => isCommand(entry))\n\n for (const [subName, subEntry] of subCommands) {\n registerResolvedCommand({\n builder,\n cmd: subEntry as Command,\n instance: builder,\n name: subName,\n parentPath: [...parentPath, name],\n resolved,\n })\n }\n\n if (cmd.handler) {\n builder.demandCommand(0)\n } else {\n builder.demandCommand(1, 'You must specify a subcommand.')\n }\n }\n\n return builder\n },\n () => {\n resolved.ref = {\n args: cmd.args,\n commandPath: [...parentPath, name],\n handler: cmd.handler as ((ctx: Context) => Promise<void> | void) | undefined,\n middleware: (cmd.middleware ?? []) as Middleware[],\n }\n }\n )\n}\n","import type { Context } from '@/context/types.js'\nimport type { Middleware } from '@/types.js'\n\nimport type { Runner } from './types.js'\n\n/**\n * Create a runner that executes root and command middleware chains.\n *\n * Root middleware wraps the command middleware chain, which in turn wraps\n * the command handler — producing a nested onion lifecycle:\n *\n * ```\n * root middleware start →\n * command middleware start →\n * handler\n * command middleware end\n * root middleware end\n * ```\n *\n * @param rootMiddleware - Root-level middleware from `cli({ middleware })`.\n * @returns A Runner with an execute method.\n */\nexport function createRunner(rootMiddleware: Middleware[]): Runner {\n return {\n async execute({ ctx, handler, middleware }): Promise<void> {\n const commandHandler = async (innerCtx: Context): Promise<void> => {\n await runMiddlewareChain(middleware, innerCtx, handler)\n }\n await runMiddlewareChain(rootMiddleware, ctx, commandHandler)\n },\n }\n}\n\n// ---------------------------------------------------------------------------\n// Private\n// ---------------------------------------------------------------------------\n\n/**\n * Execute a middleware chain followed by a final handler.\n *\n * Runs each middleware in order, passing `ctx` and a `next` callback.\n * When all middleware have called `next()`, the final handler is invoked.\n * A middleware can short-circuit by not calling `next()`.\n *\n * @private\n * @param middlewares - Ordered array of middleware to execute.\n * @param ctx - The context object threaded through middleware and handler.\n * @param finalHandler - The command handler to invoke after all middleware.\n */\nasync function runMiddlewareChain(\n middlewares: Middleware[],\n ctx: Context,\n finalHandler: (ctx: Context) => Promise<void> | void\n): Promise<void> {\n async function executeChain(index: number): Promise<void> {\n if (index >= middlewares.length) {\n await finalHandler(ctx)\n return\n }\n const mw = middlewares[index]\n if (mw) {\n await mw.handler(ctx, () => executeChain(index + 1))\n }\n }\n\n await executeChain(0)\n}\n","import { attemptAsync, err, ok } from '@kidd-cli/utils/fp'\nimport type { AsyncResult } from '@kidd-cli/utils/fp'\nimport type { z } from 'zod'\n\nimport { createContext } from '@/context/index.js'\nimport type { Context } from '@/context/types.js'\nimport { createConfigClient } from '@/lib/config/index.js'\nimport type { CliConfigOptions, Middleware } from '@/types.js'\n\nimport { createArgsParser } from './args/index.js'\nimport { createRunner } from './runner.js'\nimport type { Runtime, RuntimeOptions } from './types.js'\n\n/**\n * Create a runtime that orchestrates config loading and middleware execution.\n *\n * Loads config up front, then captures it in a closure alongside a runner.\n * The returned `runtime.execute` method handles arg parsing, context creation,\n * and middleware chain execution for each command invocation.\n *\n * @param options - Runtime configuration including name, version, config, and middleware.\n * @returns An AsyncResult containing the runtime or an error.\n */\nexport async function createRuntime<TSchema extends z.ZodType>(\n options: RuntimeOptions<TSchema>\n): AsyncResult<Runtime, Error> {\n const config = await resolveConfig(options.config, options.name)\n\n const runner = createRunner((options.middleware ?? []) as Middleware[])\n\n const runtime: Runtime = {\n async execute(command): AsyncResult<void, Error> {\n const parser = createArgsParser(command.args)\n const [argsError, validatedArgs] = parser.parse(command.rawArgs)\n if (argsError) {\n return err(argsError)\n }\n\n const ctx = createContext({\n args: validatedArgs,\n config,\n meta: {\n command: command.commandPath as string[],\n name: options.name,\n version: options.version,\n },\n })\n\n const finalHandler = command.handler ?? (async () => {})\n\n const [execError] = await attemptAsync(() =>\n runner.execute({\n ctx: ctx as Context,\n handler: finalHandler as (ctx: Context) => Promise<void> | void,\n middleware: command.middleware,\n })\n )\n if (execError) {\n return err(execError)\n }\n\n return ok()\n },\n }\n\n return ok(runtime)\n}\n\n// ---------------------------------------------------------------------------\n// Private\n// ---------------------------------------------------------------------------\n\n/**\n * Load and validate a config file via the config client.\n *\n * Returns the validated config record or an empty object when no config\n * options are provided or when loading fails.\n *\n * @private\n * @param configOptions - Config loading options with schema and optional name override.\n * @param defaultName - Fallback config file name derived from the CLI name.\n * @returns The loaded config record or an empty object.\n */\nasync function resolveConfig<TSchema extends z.ZodType>(\n configOptions: CliConfigOptions<TSchema> | undefined,\n defaultName: string\n): Promise<Record<string, unknown>> {\n if (!configOptions || !configOptions.schema) {\n return {}\n }\n const client = createConfigClient({\n name: configOptions.name ?? defaultName,\n schema: configOptions.schema,\n })\n const [configError, configResult] = await client.load()\n if (configError || !configResult) {\n return {}\n }\n return configResult.config as Record<string, unknown>\n}\n","import { resolve } from 'node:path'\n\nimport { loadConfig } from '@kidd-cli/config/loader'\nimport { attemptAsync, isPlainObject, isString } from '@kidd-cli/utils/fp'\nimport yargs from 'yargs'\nimport type { z } from 'zod'\n\nimport { DEFAULT_EXIT_CODE, isContextError } from '@/context/index.js'\nimport { createCliLogger } from '@/lib/logger.js'\nimport type { CliOptions, CommandMap } from '@/types.js'\n\nimport { autoload } from './autoloader.js'\nimport { createRuntime, registerCommands } from './runtime/index.js'\nimport type { ResolvedRef } from './runtime/index.js'\n\nconst ARGV_SLICE_START = 2\n\n/**\n * Bootstrap and run the CLI application.\n *\n * Parses argv, resolves the matched command, loads config, runs the\n * middleware chain, and invokes the command handler.\n *\n * @param options - CLI configuration including name, version, commands, and middleware.\n */\nexport async function cli<TSchema extends z.ZodType = z.ZodType>(\n options: CliOptions<TSchema>\n): Promise<void> {\n const logger = createCliLogger()\n\n const [uncaughtError, result] = await attemptAsync(async () => {\n const program = yargs(process.argv.slice(ARGV_SLICE_START))\n .scriptName(options.name)\n .version(options.version)\n .strict()\n .help()\n .option('cwd', {\n describe: 'Set the working directory',\n global: true,\n type: 'string',\n })\n\n if (options.description) {\n program.usage(options.description)\n }\n\n const resolved: ResolvedRef = { ref: undefined }\n\n const commands = await resolveCommands(options.commands)\n\n if (commands) {\n registerCommands({ commands, instance: program, parentPath: [], resolved })\n program.demandCommand(1, 'You must specify a command.')\n }\n\n const argv = await program.parseAsync()\n\n applyCwd(argv as Record<string, unknown>)\n\n if (!resolved.ref) {\n return undefined\n }\n\n const [runtimeError, runtime] = await createRuntime({\n config: options.config,\n middleware: options.middleware,\n name: options.name,\n version: options.version,\n })\n\n if (runtimeError) {\n return runtimeError\n }\n\n const [executeError] = await runtime.execute({\n args: resolved.ref.args,\n commandPath: resolved.ref.commandPath,\n handler: resolved.ref.handler,\n middleware: resolved.ref.middleware,\n rawArgs: argv as Record<string, unknown>,\n })\n\n return executeError\n })\n\n if (uncaughtError) {\n exitOnError(uncaughtError, logger)\n return\n }\n\n if (result) {\n exitOnError(result, logger)\n }\n}\n\nexport default cli\n\n// ---------------------------------------------------------------------------\n// Private\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve the commands option to a CommandMap.\n *\n * Accepts a directory string (triggers autoload), a static CommandMap,\n * a Promise<CommandMap> (from autoload() called at the call site),\n * or undefined (loads `kidd.config.ts` and autoloads from its `commands` field,\n * falling back to `'./commands'`).\n *\n * @private\n * @param commands - The commands option from CliOptions.\n * @returns A CommandMap or undefined.\n */\nasync function resolveCommands(\n commands: string | CommandMap | Promise<CommandMap> | undefined\n): Promise<CommandMap | undefined> {\n if (isString(commands)) {\n return autoload({ dir: commands })\n }\n if (commands instanceof Promise) {\n return commands\n }\n if (isPlainObject(commands)) {\n return commands as CommandMap\n }\n return resolveCommandsFromConfig()\n}\n\n/**\n * Load `kidd.config.ts` and autoload commands from its `commands` field.\n *\n * Falls back to `'./commands'` when the config file is missing, fails to load,\n * or does not specify a `commands` field.\n *\n * @private\n * @returns A CommandMap autoloaded from the configured commands directory.\n */\nasync function resolveCommandsFromConfig(): Promise<CommandMap> {\n const DEFAULT_COMMANDS_DIR = './commands'\n\n const [configError, configResult] = await loadConfig()\n if (configError || !configResult) {\n return autoload({ dir: DEFAULT_COMMANDS_DIR })\n }\n\n const dir = configResult.config.commands ?? DEFAULT_COMMANDS_DIR\n return autoload({ dir })\n}\n\n/**\n * Change the process working directory when `--cwd` is provided.\n *\n * Resolves the value to an absolute path and calls `process.chdir()` so\n * that all downstream `process.cwd()` calls reflect the override.\n *\n * @private\n * @param argv - The parsed argv record from yargs.\n */\nfunction applyCwd(argv: Record<string, unknown>): void {\n if (isString(argv.cwd)) {\n process.chdir(resolve(argv.cwd))\n }\n}\n\n/**\n * Handle a CLI error by logging the message and exiting with the appropriate code.\n *\n * ContextErrors carry a custom exit code; all other errors exit with code 1.\n *\n * @private\n * @param error - The caught error value.\n * @param logger - Logger with an error method for output.\n */\nfunction exitOnError(error: unknown, logger: { error(msg: string): void }): void {\n if (isContextError(error)) {\n logger.error(error.message)\n process.exit(error.exitCode)\n } else if (error instanceof Error) {\n logger.error(error.message)\n process.exit(DEFAULT_EXIT_CODE)\n } else {\n logger.error(String(error))\n process.exit(DEFAULT_EXIT_CODE)\n }\n}\n","import { withTag } from '@kidd-cli/utils/tag'\n\nimport type { ArgsDef, CommandDef, Command as CommandType } from './types.js'\n\n/**\n * Define a CLI command with typed args, config, and handler.\n *\n * @param def - Command definition including description, args schema, and handler.\n * @returns A resolved Command object for registration in the command map.\n */\nexport function command<\n TArgsDef extends ArgsDef = ArgsDef,\n TConfig extends Record<string, unknown> = Record<string, unknown>,\n>(def: CommandDef<TArgsDef, TConfig>): CommandType<TArgsDef, TConfig> {\n return withTag({ ...def }, 'Command')\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["match"],"sources":["../src/context/error.ts","../src/context/output.ts","../src/context/prompts.ts","../src/context/store.ts","../src/context/create-context.ts","../src/autoloader.ts","../src/runtime/args/zod.ts","../src/runtime/args/parser.ts","../src/runtime/args/register.ts","../src/runtime/register.ts","../src/runtime/runner.ts","../src/runtime/runtime.ts","../src/cli.ts","../src/command.ts"],"sourcesContent":["import type { Tagged } from '@kidd-cli/utils/tag'\nimport { TAG, hasTag, withTag } from '@kidd-cli/utils/tag'\n\nimport { DEFAULT_EXIT_CODE } from '@/utils/constants.js'\n\nexport { DEFAULT_EXIT_CODE }\n\n/**\n * Plain data representation of a ContextError (no Error prototype).\n *\n * Useful for serializing error data without carrying the Error prototype chain,\n * for example when logging or forwarding errors across process boundaries.\n */\nexport type ContextErrorData = Tagged<\n {\n readonly code: string | undefined\n readonly exitCode: number\n readonly message: string\n },\n 'ContextError'\n>\n\n/**\n * An Error subtype carrying an exit code and optional error code.\n *\n * Created by {@link createContextError} and thrown by `ctx.fail()`.\n * The CLI boundary catches these to produce clean, user-facing error output\n * with the correct process exit code.\n */\nexport type ContextError = Error &\n Tagged<\n {\n readonly code: string | undefined\n readonly exitCode: number\n },\n 'ContextError'\n >\n\n/**\n * Create a ContextError with an exit code and optional error code.\n *\n * Used to surface user-facing CLI errors with clean messages.\n * The error carries a Symbol-based tag for reliable type-narrowing\n * via {@link isContextError}.\n *\n * @param message - Human-readable error message.\n * @param options - Optional error code and exit code overrides.\n * @returns A ContextError instance.\n */\nexport function createContextError(\n message: string,\n options?: { code?: string; exitCode?: number }\n): ContextError {\n const data = createContextErrorData(message, options)\n const error = new Error(data.message) as ContextError\n error.name = 'ContextError'\n Object.defineProperty(error, TAG, { enumerable: false, value: 'ContextError', writable: false })\n Object.defineProperty(error, 'code', { enumerable: true, value: data.code, writable: false })\n Object.defineProperty(error, 'exitCode', {\n enumerable: true,\n value: data.exitCode,\n writable: false,\n })\n return error\n}\n\n/**\n * Type guard that narrows an unknown value to {@link ContextError}.\n *\n * Checks that the value is an Error instance whose `[TAG]` property\n * equals `'ContextError'`, which distinguishes CLI-layer errors from\n * unexpected exceptions.\n *\n * @param error - The value to check.\n * @returns `true` when the value is a ContextError.\n */\nexport function isContextError(error: unknown): error is ContextError {\n if (error instanceof Error) {\n return hasTag(error, 'ContextError')\n }\n return false\n}\n\n// ---------------------------------------------------------------------------\n// Private helpers\n// ---------------------------------------------------------------------------\n\nfunction resolveExitCode(options: { code?: string; exitCode?: number } | undefined): number {\n if (options && options.exitCode !== undefined) {\n return options.exitCode\n }\n return DEFAULT_EXIT_CODE\n}\n\nfunction resolveCode(\n options: { code?: string; exitCode?: number } | undefined\n): string | undefined {\n if (options && options.code !== undefined) {\n return options.code\n }\n return undefined\n}\n\nfunction createContextErrorData(\n message: string,\n options?: { code?: string; exitCode?: number }\n): ContextErrorData {\n return withTag(\n {\n code: resolveCode(options),\n exitCode: resolveExitCode(options),\n message,\n },\n 'ContextError'\n )\n}\n","import { jsonStringify } from '@kidd-cli/utils/json'\n\nimport type { Output, OutputOptions } from './types.js'\n\n/**\n * Create the structured output methods for a context.\n *\n * @private\n * @param stream - The writable stream to write output to.\n * @returns An Output instance backed by the given stream.\n */\nexport function createContextOutput(stream: NodeJS.WriteStream): Output {\n return {\n markdown(content: string): void {\n stream.write(`${content}\\n`)\n },\n raw(content: string): void {\n stream.write(content)\n },\n table(rows: Record<string, unknown>[], options?: OutputOptions): void {\n if (options && options.json) {\n const [, json] = jsonStringify(rows, { pretty: true })\n stream.write(`${json}\\n`)\n return\n }\n if (rows.length === 0) {\n return\n }\n const [firstRow] = rows\n if (!firstRow) {\n return\n }\n writeTableToStream(stream, rows, Object.keys(firstRow))\n },\n write(data: unknown, options?: OutputOptions): void {\n if ((options && options.json) || (typeof data === 'object' && data !== null)) {\n const [, json] = jsonStringify(data, { pretty: true })\n stream.write(`${json}\\n`)\n } else {\n stream.write(`${String(data)}\\n`)\n }\n },\n }\n}\n\n// ---------------------------------------------------------------------------\n// Private helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Format an unknown value as a string for table cell display.\n *\n * @private\n * @param val - The value to format.\n * @returns The stringified value, or empty string for undefined.\n */\nfunction formatStringValue(val: unknown): string {\n if (val === undefined) {\n return ''\n }\n return String(val)\n}\n\n/**\n * Options for creating a table header string.\n */\ninterface TableHeaderOptions {\n keys: string[]\n widths: (number | undefined)[]\n}\n\n/**\n * Create a padded header row string from column keys and widths.\n *\n * @private\n * @param options - The keys and column widths.\n * @returns A formatted header string.\n */\nfunction createTableHeader(options: TableHeaderOptions): string {\n const { keys, widths } = options\n return keys\n .map((key, idx) => {\n const width = widths[idx]\n if (width === undefined) {\n return key\n }\n return key.padEnd(width)\n })\n .join(' ')\n}\n\n/**\n * Options for creating a table row string.\n */\ninterface TableRowOptions {\n row: Record<string, unknown>\n keys: string[]\n widths: (number | undefined)[]\n}\n\n/**\n * Create a padded row string from a data record, column keys, and widths.\n *\n * @private\n * @param options - The row data, keys, and column widths.\n * @returns A formatted row string.\n */\nfunction createTableRow(options: TableRowOptions): string {\n const { row, keys, widths } = options\n return keys\n .map((key, idx) => {\n const width = widths[idx]\n const val = formatStringValue(row[key])\n if (width === undefined) {\n return val\n }\n return val.padEnd(width)\n })\n .join(' ')\n}\n\n/**\n * Compute the maximum column width for each key across all rows.\n *\n * @private\n * @param rows - The data rows.\n * @param keys - The column keys.\n * @returns An array of column widths.\n */\nfunction computeColumnWidths(rows: Record<string, unknown>[], keys: string[]): number[] {\n return keys.map((key) => {\n const values = rows.map((row) => formatStringValue(row[key]))\n return Math.max(key.length, ...values.map((val) => val.length))\n })\n}\n\n/**\n * Write a formatted table (header, separator, rows) to a writable stream.\n *\n * @private\n * @param stream - The writable stream.\n * @param rows - The data rows.\n * @param keys - The column keys.\n */\nfunction writeTableToStream(\n stream: NodeJS.WriteStream,\n rows: Record<string, unknown>[],\n keys: string[]\n): void {\n const widths = computeColumnWidths(rows, keys)\n const header = createTableHeader({ keys, widths })\n const separator = widths.map((width) => '-'.repeat(width)).join(' ')\n const dataRows = rows.map((row) => createTableRow({ keys, row, widths }))\n const content = [header, separator, ...dataRows].join('\\n')\n stream.write(`${content}\\n`)\n}\n","import { createPromptUtils } from '@/lib/prompts/index.js'\n\nimport { DEFAULT_EXIT_CODE, createContextError } from './error.js'\nimport type { Prompts } from './types.js'\n\n/**\n * Create the interactive prompt methods for a context.\n *\n * @private\n * @returns A Prompts instance backed by clack.\n */\nexport function createContextPrompts(): Prompts {\n const utils = createPromptUtils()\n\n return {\n async confirm(opts): Promise<boolean> {\n const result = await utils.confirm(opts)\n return unwrapCancelSignal(utils, result)\n },\n async multiselect<Type>(opts: Parameters<Prompts['multiselect']>[0]): Promise<Type[]> {\n const result = await utils.multiselect<Type>(\n opts as Parameters<typeof utils.multiselect<Type>>[0]\n )\n return unwrapCancelSignal(utils, result)\n },\n async password(opts): Promise<string> {\n const result = await utils.password(opts)\n return unwrapCancelSignal(utils, result)\n },\n async select<Type>(opts: Parameters<Prompts['select']>[0]): Promise<Type> {\n const result = await utils.select<Type>(opts as Parameters<typeof utils.select<Type>>[0])\n return unwrapCancelSignal(utils, result)\n },\n async text(opts): Promise<string> {\n const result = await utils.text(opts)\n return unwrapCancelSignal(utils, result)\n },\n }\n}\n\n// ---------------------------------------------------------------------------\n// Private helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Unwrap a prompt result that may be a cancel symbol.\n *\n * If the user cancelled (Ctrl-C), throws a ContextError. Otherwise returns\n * the typed result value.\n *\n * @private\n * @param utils - The prompt utils instance (for isCancel and cancel).\n * @param result - The raw prompt result (value or cancel symbol).\n * @returns The unwrapped typed value.\n */\nfunction unwrapCancelSignal<Type>(\n utils: ReturnType<typeof createPromptUtils>,\n result: Type | symbol\n): Type {\n if (utils.isCancel(result)) {\n utils.cancel('Operation cancelled.')\n throw createContextError('Prompt cancelled by user', {\n code: 'PROMPT_CANCELLED',\n exitCode: DEFAULT_EXIT_CODE,\n })\n }\n return result as Type\n}\n","import type { Store } from './types.js'\n\n/**\n * Create an in-memory key-value store.\n *\n * @private\n * @returns A Store instance backed by a Map.\n */\nexport function createMemoryStore<TMap extends Record<string, unknown>>(): Store<TMap> {\n const map = new Map<string, unknown>()\n\n return {\n clear(): void {\n map.clear()\n },\n delete(key: string): boolean {\n return map.delete(key)\n },\n get<Key extends Extract<keyof TMap, string>>(key: Key): TMap[Key] | undefined {\n return map.get(key) as TMap[Key] | undefined\n },\n has(key: string): boolean {\n return map.has(key)\n },\n set<Key extends Extract<keyof TMap, string>>(key: Key, value: TMap[Key]): void {\n map.set(key, value)\n },\n }\n}\n","import { createCliLogger } from '@/lib/logger.js'\nimport type { CliLogger } from '@/lib/logger.js'\nimport { createSpinner } from '@/lib/prompts/index.js'\nimport type { AnyRecord, KiddStore, Merge } from '@/types.js'\n\nimport { createContextError } from './error.js'\nimport { createContextOutput } from './output.js'\nimport { createContextPrompts } from './prompts.js'\nimport { createMemoryStore } from './store.js'\nimport type { Context, Meta, Output, Prompts, Spinner, Store, StoreMap } from './types.js'\n\n/**\n * Options for creating a {@link Context} instance via {@link createContext}.\n *\n * Carries the parsed args, validated config, and CLI metadata needed to\n * assemble a fully-wired context. An optional `logger` override allows\n * callers to inject a custom {@link CliLogger}; when omitted a default\n * @clack/prompts-backed instance is used.\n */\nexport interface CreateContextOptions<TArgs extends AnyRecord, TConfig extends AnyRecord> {\n readonly args: TArgs\n readonly config: TConfig\n readonly meta: { readonly name: string; readonly version: string; readonly command: string[] }\n readonly logger?: CliLogger\n readonly output?: NodeJS.WriteStream\n}\n\n/**\n * Create the {@link Context} object threaded through middleware and command handlers.\n *\n * Assembles logger, spinner, output, store, prompts, and meta from\n * the provided options into a single immutable context. Each sub-system is\n * constructed via its own factory so this function remains a lean orchestrator.\n *\n * @param options - Args, config, and meta for the current invocation.\n * @returns A fully constructed Context.\n */\nexport function createContext<TArgs extends AnyRecord, TConfig extends AnyRecord>(\n options: CreateContextOptions<TArgs, TConfig>\n): Context<TArgs, TConfig> {\n const ctxLogger: CliLogger = options.logger ?? createCliLogger()\n const ctxSpinner: Spinner = createSpinner()\n const ctxOutput: Output = createContextOutput(options.output ?? process.stdout)\n const ctxStore: Store<Merge<KiddStore, StoreMap>> = createMemoryStore()\n const ctxPrompts: Prompts = createContextPrompts()\n const ctxMeta: Meta = {\n command: options.meta.command,\n name: options.meta.name,\n version: options.meta.version,\n }\n\n // Middleware-augmented properties (e.g. `auth`) are added at runtime.\n // See `decorateContext` — they are intentionally absent here.\n return {\n args: options.args as Context<TArgs, TConfig>['args'],\n config: options.config as Context<TArgs, TConfig>['config'],\n fail(message: string, failOptions?: { code?: string; exitCode?: number }): never {\n throw createContextError(message, failOptions)\n },\n logger: ctxLogger,\n meta: ctxMeta as Context<TArgs, TConfig>['meta'],\n output: ctxOutput,\n prompts: ctxPrompts,\n spinner: ctxSpinner,\n store: ctxStore,\n } as Context<TArgs, TConfig>\n}\n","import type { Dirent } from 'node:fs'\nimport { readdir } from 'node:fs/promises'\nimport { basename, extname, join, resolve } from 'node:path'\n\nimport { isPlainObject, isString } from '@kidd-cli/utils/fp'\nimport { hasTag, withTag } from '@kidd-cli/utils/tag'\n\nimport type { AutoloadOptions, Command, CommandMap } from './types.js'\n\nconst VALID_EXTENSIONS = new Set(['.ts', '.js', '.mjs'])\nconst INDEX_NAME = 'index'\n\n/**\n * Scan a directory for command files and produce a CommandMap.\n *\n * @param options - Autoload configuration (directory override, etc.).\n * @returns A promise resolving to a CommandMap built from the directory tree.\n */\nexport async function autoload(options?: AutoloadOptions): Promise<CommandMap> {\n const dir = resolveDir(options)\n const entries = await readdir(dir, { withFileTypes: true })\n\n const fileEntries = entries.filter(isCommandFile)\n const dirEntries = entries.filter(isCommandDir)\n\n const fileResults = await Promise.all(\n fileEntries.map(async (entry): Promise<[string, Command] | undefined> => {\n const cmd = await importCommand(join(dir, entry.name))\n if (!cmd) {\n return undefined\n }\n return [deriveCommandName(entry), cmd]\n })\n )\n\n const dirResults = await Promise.all(\n dirEntries.map((entry) => buildDirCommand(join(dir, entry.name)))\n )\n\n const allResults = [...fileResults, ...dirResults]\n const validPairs = allResults.filter((pair): pair is [string, Command] => pair !== undefined)\n\n return Object.fromEntries(validPairs) as CommandMap\n}\n\n// ---------------------------------------------------------------------------\n// Private\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve the target directory from autoload options.\n *\n * @private\n * @param options - Optional autoload configuration.\n * @returns The resolved absolute directory path.\n */\nfunction resolveDir(options?: AutoloadOptions): string {\n if (options && isString(options.dir)) {\n return resolve(options.dir)\n }\n return resolve('./commands')\n}\n\n/**\n * Scan a subdirectory and assemble it as a parent command with subcommands.\n *\n * If the directory contains an `index.ts`/`index.js`, that becomes the parent\n * handler. Otherwise a handler-less group command is created that demands a\n * subcommand.\n *\n * @private\n * @param dir - Absolute path to the subdirectory.\n * @returns A tuple of [name, Command] or undefined if the directory is empty.\n */\nasync function buildDirCommand(dir: string): Promise<[string, Command] | undefined> {\n const name = basename(dir)\n const dirEntries = await readdir(dir, { withFileTypes: true })\n const subCommands = await buildSubCommands(dir, dirEntries)\n const indexFile = findIndexInEntries(dirEntries)\n\n if (indexFile) {\n const parentCommand = await importCommand(join(dir, indexFile.name))\n if (parentCommand) {\n return [name, withTag({ ...parentCommand, commands: subCommands }, 'Command')]\n }\n }\n\n if (Object.keys(subCommands).length === 0) {\n return undefined\n }\n\n return [name, withTag({ commands: subCommands }, 'Command')]\n}\n\n/**\n * Build subcommands from already-read directory entries, avoiding a redundant readdir call.\n *\n * @private\n * @param dir - Absolute path to the directory.\n * @param entries - Pre-read directory entries.\n * @returns A CommandMap built from the entries.\n */\nasync function buildSubCommands(dir: string, entries: Dirent[]): Promise<CommandMap> {\n const fileEntries = entries.filter(isCommandFile)\n const dirEntries = entries.filter(isCommandDir)\n\n const fileResults = await Promise.all(\n fileEntries.map(async (entry): Promise<[string, Command] | undefined> => {\n const cmd = await importCommand(join(dir, entry.name))\n if (!cmd) {\n return undefined\n }\n return [deriveCommandName(entry), cmd]\n })\n )\n\n const dirResults = await Promise.all(\n dirEntries.map((entry) => buildDirCommand(join(dir, entry.name)))\n )\n\n const allResults = [...fileResults, ...dirResults]\n const validPairs = allResults.filter((pair): pair is [string, Command] => pair !== undefined)\n\n return Object.fromEntries(validPairs) as CommandMap\n}\n\n/**\n * Find the index file (index.ts or index.js) in pre-read directory entries.\n *\n * @private\n * @param entries - Pre-read directory entries.\n * @returns The index file's Dirent or undefined.\n */\nfunction findIndexInEntries(entries: Dirent[]): Dirent | undefined {\n return entries.find(\n (entry) =>\n entry.isFile() &&\n VALID_EXTENSIONS.has(extname(entry.name)) &&\n basename(entry.name, extname(entry.name)) === INDEX_NAME\n )\n}\n\n/**\n * Dynamically import a file and validate that its default export is a Command.\n *\n * @private\n * @param filePath - Absolute path to the file to import.\n * @returns The Command if valid, or undefined.\n */\nasync function importCommand(filePath: string): Promise<Command | undefined> {\n const mod: unknown = await import(filePath)\n if (isCommandExport(mod)) {\n return mod.default\n }\n return undefined\n}\n\n/**\n * Check whether a module's default export is a Command object.\n *\n * ES module namespace objects have a null prototype, so isPlainObject\n * rejects them. We only need to verify the namespace is a non-null\n * object with a default export that is a plain Command object.\n *\n * @private\n * @param mod - The imported module to inspect.\n * @returns True when the module has a Command as its default export.\n */\nfunction isCommandExport(mod: unknown): mod is { default: Command } {\n if (typeof mod !== 'object' || mod === null) {\n return false\n }\n const def = (mod as Record<string, unknown>)['default']\n if (!isPlainObject(def)) {\n return false\n }\n return hasTag(def, 'Command')\n}\n\n/**\n * Derive a command name from a directory entry by stripping its extension.\n *\n * @private\n * @param entry - The directory entry to derive the name from.\n * @returns The file name without its extension.\n */\nfunction deriveCommandName(entry: Dirent): string {\n return basename(entry.name, extname(entry.name))\n}\n\n/**\n * Predicate: entry is a command file (.ts/.js, not index, not _/. prefixed).\n *\n * @private\n * @param entry - The directory entry to check.\n * @returns True when the entry is a valid command file.\n */\nfunction isCommandFile(entry: Dirent): boolean {\n if (!entry.isFile()) {\n return false\n }\n if (entry.name.startsWith('_') || entry.name.startsWith('.')) {\n return false\n }\n if (!VALID_EXTENSIONS.has(extname(entry.name))) {\n return false\n }\n return deriveCommandName(entry) !== INDEX_NAME\n}\n\n/**\n * Predicate: entry is a scannable command directory (not _/. prefixed).\n *\n * @private\n * @param entry - The directory entry to check.\n * @returns True when the entry is a valid command directory.\n */\nfunction isCommandDir(entry: Dirent): boolean {\n if (!entry.isDirectory()) {\n return false\n }\n return !entry.name.startsWith('_') && !entry.name.startsWith('.')\n}\n","import { match } from 'ts-pattern'\nimport type { Options as YargsOptions } from 'yargs'\nimport type { z } from 'zod'\n\n/**\n * Type guard that checks whether a value is a zod object schema.\n *\n * @param args - The value to check.\n * @returns True when args is a ZodObject.\n */\nexport function isZodSchema(args: unknown): args is z.ZodObject<z.ZodRawShape> {\n return (\n typeof args === 'object' &&\n args !== null &&\n '_def' in args &&\n typeof (args as { _def: unknown })._def === 'object' &&\n (args as { _def: { type?: string } })._def !== null &&\n (args as { _def: { type?: string } })._def.type === 'object'\n )\n}\n\n/**\n * Convert an entire zod object schema into a record of yargs options.\n *\n * @param schema - The zod object schema.\n * @returns A record mapping field names to yargs option definitions.\n */\nexport function zodSchemaToYargsOptions(\n schema: z.ZodObject<z.ZodRawShape>\n): Record<string, YargsOptions> {\n const shape = schema.shape as Record<string, z.ZodTypeAny>\n return Object.fromEntries(\n Object.entries(shape).map(([key, fieldSchema]): [string, YargsOptions] => [\n key,\n getZodTypeOption(fieldSchema),\n ])\n )\n}\n\n// ---------------------------------------------------------------------------\n// Private\n// ---------------------------------------------------------------------------\n\ninterface ZodDef {\n type?: string\n innerType?: z.ZodTypeAny\n defaultValue?: unknown\n}\n\ninterface ZodTypeInfo {\n defaultValue: unknown\n inner: z.ZodTypeAny\n isOptional: boolean\n}\n\ninterface UnwrapOptions {\n def: ZodDef\n current: z.ZodTypeAny\n defaultValue: unknown\n}\n\ninterface UnwrapRecursiveOptions {\n current: z.ZodTypeAny\n isOptional: boolean\n defaultValue: unknown\n}\n\n/**\n * Extract a default value from a zod definition, falling back to the provided value.\n *\n * @private\n * @param def - The zod definition to inspect.\n * @param fallback - Value to return when no default is defined.\n * @returns The resolved default value.\n */\nfunction resolveDefaultValue(def: ZodDef, fallback: unknown): unknown {\n if (def.defaultValue !== undefined) {\n return def.defaultValue\n }\n return fallback\n}\n\n/**\n * Unwrap a ZodOptional type, recursing into the inner type.\n *\n * @private\n * @param options - The unwrap options containing def, current type, and default value.\n * @returns Unwrapped type information.\n */\nfunction unwrapOptional(options: UnwrapOptions): ZodTypeInfo {\n const { def, current, defaultValue } = options\n if (def.innerType) {\n return unwrapZodTypeRecursive({ current: def.innerType, defaultValue, isOptional: true })\n }\n return { defaultValue, inner: current, isOptional: true }\n}\n\n/**\n * Unwrap a ZodDefault type, resolving its default value and recursing.\n *\n * @private\n * @param options - The unwrap options containing def, current type, and default value.\n * @returns Unwrapped type information with the resolved default.\n */\nfunction unwrapDefault(options: UnwrapOptions): ZodTypeInfo {\n const { def, current, defaultValue } = options\n const newDefault = resolveDefaultValue(def, defaultValue)\n if (def.innerType) {\n return unwrapZodTypeRecursive({\n current: def.innerType,\n defaultValue: newDefault,\n isOptional: true,\n })\n }\n return { defaultValue: newDefault, inner: current, isOptional: true }\n}\n\n/**\n * Recursively unwrap optional and default wrappers from a zod type.\n *\n * @private\n * @param options - The recursive unwrap options containing current type, optionality flag, and default value.\n * @returns The fully unwrapped type information.\n */\nfunction unwrapZodTypeRecursive(options: UnwrapRecursiveOptions): ZodTypeInfo {\n const { current, isOptional, defaultValue } = options\n const def = (current as { _def: ZodDef })._def\n if (def.type === 'optional') {\n return unwrapOptional({ current, def, defaultValue })\n }\n if (def.type === 'default') {\n return unwrapDefault({ current, def, defaultValue })\n }\n return { defaultValue, inner: current, isOptional }\n}\n\n/**\n * Unwrap a zod schema to extract its base type, optionality, and default value.\n *\n * @private\n * @param schema - The zod type to unwrap.\n * @returns The unwrapped type information.\n */\nfunction unwrapZodType(schema: z.ZodTypeAny): ZodTypeInfo {\n return unwrapZodTypeRecursive({ current: schema, defaultValue: undefined, isOptional: false })\n}\n\n/**\n * Map a zod type name to a yargs option type string.\n *\n * @private\n * @param typeName - The zod type name (e.g. 'string', 'number').\n * @returns The corresponding yargs type.\n */\nfunction resolveZodYargsType(\n typeName: string | undefined\n): 'string' | 'number' | 'boolean' | 'array' {\n return match(typeName)\n .with('string', () => 'string' as const)\n .with('number', () => 'number' as const)\n .with('boolean', () => 'boolean' as const)\n .with('array', () => 'array' as const)\n .otherwise(() => 'string' as const)\n}\n\n/**\n * Build a base yargs option from a zod schema's description and default.\n *\n * @private\n * @param inner - The unwrapped zod schema instance.\n * @param defaultValue - The resolved default value.\n * @returns A partial yargs option object.\n */\nfunction buildBaseOption(inner: z.ZodTypeAny, defaultValue: unknown): YargsOptions {\n const base: YargsOptions = {}\n const { description } = inner as { description?: string }\n if (description) {\n base.describe = description\n }\n if (defaultValue !== undefined) {\n base.default = defaultValue\n }\n return base\n}\n\n/**\n * Convert a single zod field schema into a complete yargs option definition.\n *\n * @private\n * @param schema - A single zod field type.\n * @returns A complete yargs option object.\n */\nfunction getZodTypeOption(schema: z.ZodTypeAny): YargsOptions {\n const { inner, isOptional, defaultValue } = unwrapZodType(schema)\n const innerDef = (inner as { _def: ZodDef })._def\n const base = {\n ...buildBaseOption(inner, defaultValue),\n type: resolveZodYargsType(innerDef.type),\n }\n if (!isOptional) {\n return { ...base, demandOption: true }\n }\n return base\n}\n","import { err, ok } from '@kidd-cli/utils/fp'\nimport type { Result } from '@kidd-cli/utils/fp'\nimport { formatZodIssues } from '@kidd-cli/utils/validate'\n\nimport type { Command } from '@/types.js'\n\nimport type { ArgsParser } from '../types.js'\nimport { isZodSchema } from './zod.js'\n\n/**\n * Create an args parser that cleans and validates raw parsed arguments.\n *\n * Captures the argument definition in a closure and returns an ArgsParser\n * whose `parse` method strips yargs-internal keys and validates against\n * a zod schema when one is defined.\n *\n * @param argsDef - The argument definition from the command.\n * @returns An ArgsParser with a parse method.\n */\nexport function createArgsParser(argsDef: Command['args']): ArgsParser {\n return {\n parse(rawArgs: Record<string, unknown>): Result<Record<string, unknown>, Error> {\n const cleaned = cleanParsedArgs(rawArgs)\n return validateArgs(argsDef, cleaned)\n },\n }\n}\n\n// ---------------------------------------------------------------------------\n// Private\n// ---------------------------------------------------------------------------\n\n/**\n * Strip yargs-internal keys (`_`, `$0`) and camelCase-duplicated hyphenated keys\n * from a parsed argv record, returning only user-defined arguments.\n *\n * @private\n * @param argv - Raw parsed argv from yargs.\n * @returns A cleaned record containing only user-defined arguments.\n */\nfunction cleanParsedArgs(argv: Record<string, unknown>): Record<string, unknown> {\n return Object.fromEntries(\n Object.entries(argv).filter(([key]) => key !== '_' && key !== '$0' && !key.includes('-'))\n )\n}\n\n/**\n * Validate parsed arguments against a zod schema when one is defined.\n *\n * If the command uses yargs-native args (no zod schema), the parsed args are\n * returned as-is. When a zod schema is present, validation is performed and\n * a Result error is returned on failure.\n *\n * @private\n * @param argsDef - The argument definition from the command.\n * @param parsedArgs - The cleaned parsed arguments.\n * @returns A Result containing validated arguments (zod-parsed when applicable).\n */\nfunction validateArgs(\n argsDef: Command['args'],\n parsedArgs: Record<string, unknown>\n): Result<Record<string, unknown>, Error> {\n if (!argsDef || !isZodSchema(argsDef)) {\n return ok(parsedArgs)\n }\n const result = argsDef.safeParse(parsedArgs)\n if (!result.success) {\n return err(new Error(`Invalid arguments:\\n ${formatZodIssues(result.error.issues).message}`))\n }\n return ok(result.data as Record<string, unknown>)\n}\n","import type { Argv, Options as YargsOptions } from 'yargs'\n\nimport type { Command, YargsArgDef } from '@/types.js'\n\nimport { isZodSchema, zodSchemaToYargsOptions } from './zod.js'\n\n/**\n * Register argument definitions on a yargs builder.\n *\n * Accepts either a zod object schema or a record of yargs-native arg definitions\n * and wires them as yargs options on the given builder instance.\n *\n * @param builder - The yargs Argv instance to register options on.\n * @param args - Argument definitions from a Command.\n */\nexport function registerCommandArgs(builder: Argv, args: Command['args']): void {\n if (!args) {\n return\n }\n if (isZodSchema(args)) {\n const options = zodSchemaToYargsOptions(args)\n for (const [key, opt] of Object.entries(options)) {\n builder.option(key, opt)\n }\n } else {\n const argsDef = args as Record<string, YargsArgDef>\n for (const [key, def] of Object.entries(argsDef)) {\n builder.option(key, yargsArgDefToOption(def))\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Private\n// ---------------------------------------------------------------------------\n\n/**\n * Convert a yargs-native arg definition into a yargs option object.\n *\n * @private\n * @param def - The yargs arg definition.\n * @returns A yargs option object.\n */\nfunction yargsArgDefToOption(def: YargsArgDef): YargsOptions {\n return {\n alias: def.alias,\n choices: def.choices as string[],\n default: def.default,\n demandOption: def.required ?? false,\n describe: def.description,\n type: def.type,\n }\n}\n","import { hasTag } from '@kidd-cli/utils/tag'\nimport type { Argv } from 'yargs'\n\nimport type { Context } from '@/context/types.js'\nimport type { Command, CommandMap, Middleware } from '@/types.js'\n\nimport { registerCommandArgs } from './args/index.js'\nimport type { ResolvedCommand, ResolvedRef } from './types.js'\n\n/**\n * Type guard that checks whether a value is a Command object.\n *\n * @param value - The value to test.\n * @returns True when the value has `[TAG] === 'Command'`.\n */\nexport function isCommand(value: unknown): value is Command {\n return hasTag(value, 'Command')\n}\n\n/**\n * Register all commands from a CommandMap on a yargs instance.\n *\n * Iterates over the command map, filters for valid Command objects,\n * and recursively registers each command (including subcommands) on\n * the provided yargs Argv instance.\n *\n * @param options - Registration options including the command map, yargs instance, and resolution ref.\n */\nexport function registerCommands(options: RegisterCommandsOptions): void {\n const { instance, commands, resolved, parentPath } = options\n const commandEntries = Object.entries(commands).filter(([, entry]) => isCommand(entry))\n\n for (const [name, entry] of commandEntries) {\n registerResolvedCommand({\n builder: instance,\n cmd: entry as Command,\n instance,\n name,\n parentPath,\n resolved,\n })\n }\n}\n\nexport type { ResolvedCommand, ResolvedRef } from './types.js'\n\n// ---------------------------------------------------------------------------\n// Private\n// ---------------------------------------------------------------------------\n\ninterface RegisterResolvedCommandOptions {\n builder: Argv\n cmd: Command\n instance: Argv\n name: string\n parentPath: string[]\n resolved: ResolvedRef\n}\n\ninterface RegisterCommandsOptions {\n commands: CommandMap\n instance: Argv\n parentPath: string[]\n resolved: ResolvedRef\n}\n\n/**\n * Register a single resolved command (and its subcommands) with yargs.\n *\n * Sets up the yargs command handler, wires argument definitions, and\n * recursively registers any nested subcommands. On match, stores the\n * resolved handler and command path in the shared ref.\n *\n * @private\n * @param options - Command registration context.\n */\nfunction registerResolvedCommand(options: RegisterResolvedCommandOptions): void {\n const { instance, name, cmd, resolved, parentPath } = options\n const description = cmd.description ?? ''\n\n instance.command(\n name,\n description,\n (builder: Argv) => {\n registerCommandArgs(builder, cmd.args)\n\n if (cmd.commands) {\n const subCommands = Object.entries(cmd.commands).filter(([, entry]) => isCommand(entry))\n\n for (const [subName, subEntry] of subCommands) {\n registerResolvedCommand({\n builder,\n cmd: subEntry as Command,\n instance: builder,\n name: subName,\n parentPath: [...parentPath, name],\n resolved,\n })\n }\n\n if (cmd.handler) {\n builder.demandCommand(0)\n } else {\n builder.demandCommand(1, 'You must specify a subcommand.')\n }\n }\n\n return builder\n },\n () => {\n resolved.ref = {\n args: cmd.args,\n commandPath: [...parentPath, name],\n handler: cmd.handler as ((ctx: Context) => Promise<void> | void) | undefined,\n middleware: (cmd.middleware ?? []) as Middleware[],\n }\n }\n )\n}\n","import type { Context } from '@/context/types.js'\nimport type { Middleware } from '@/types.js'\n\nimport type { Runner } from './types.js'\n\n/**\n * Create a runner that executes root and command middleware chains.\n *\n * Root middleware wraps the command middleware chain, which in turn wraps\n * the command handler — producing a nested onion lifecycle:\n *\n * ```\n * root middleware start →\n * command middleware start →\n * handler\n * command middleware end\n * root middleware end\n * ```\n *\n * @param rootMiddleware - Root-level middleware from `cli({ middleware })`.\n * @returns A Runner with an execute method.\n */\nexport function createRunner(rootMiddleware: Middleware[]): Runner {\n return {\n async execute({ ctx, handler, middleware }): Promise<void> {\n const commandHandler = async (innerCtx: Context): Promise<void> => {\n await runMiddlewareChain(middleware, innerCtx, handler)\n }\n await runMiddlewareChain(rootMiddleware, ctx, commandHandler)\n },\n }\n}\n\n// ---------------------------------------------------------------------------\n// Private\n// ---------------------------------------------------------------------------\n\n/**\n * Execute a middleware chain followed by a final handler.\n *\n * Runs each middleware in order, passing `ctx` and a `next` callback.\n * When all middleware have called `next()`, the final handler is invoked.\n * A middleware can short-circuit by not calling `next()`.\n *\n * @private\n * @param middlewares - Ordered array of middleware to execute.\n * @param ctx - The context object threaded through middleware and handler.\n * @param finalHandler - The command handler to invoke after all middleware.\n */\nasync function runMiddlewareChain(\n middlewares: Middleware[],\n ctx: Context,\n finalHandler: (ctx: Context) => Promise<void> | void\n): Promise<void> {\n async function executeChain(index: number): Promise<void> {\n if (index >= middlewares.length) {\n await finalHandler(ctx)\n return\n }\n const mw = middlewares[index]\n if (mw) {\n await mw.handler(ctx, () => executeChain(index + 1))\n }\n }\n\n await executeChain(0)\n}\n","import { attemptAsync, err, ok } from '@kidd-cli/utils/fp'\nimport type { AsyncResult } from '@kidd-cli/utils/fp'\nimport type { z } from 'zod'\n\nimport { createContext } from '@/context/index.js'\nimport type { Context } from '@/context/types.js'\nimport { createConfigClient } from '@/lib/config/index.js'\nimport type { CliConfigOptions, Middleware } from '@/types.js'\n\nimport { createArgsParser } from './args/index.js'\nimport { createRunner } from './runner.js'\nimport type { Runtime, RuntimeOptions } from './types.js'\n\n/**\n * Create a runtime that orchestrates config loading and middleware execution.\n *\n * Loads config up front, then captures it in a closure alongside a runner.\n * The returned `runtime.execute` method handles arg parsing, context creation,\n * and middleware chain execution for each command invocation.\n *\n * @param options - Runtime configuration including name, version, config, and middleware.\n * @returns An AsyncResult containing the runtime or an error.\n */\nexport async function createRuntime<TSchema extends z.ZodType>(\n options: RuntimeOptions<TSchema>\n): AsyncResult<Runtime, Error> {\n const config = await resolveConfig(options.config, options.name)\n\n const runner = createRunner((options.middleware ?? []) as Middleware[])\n\n const runtime: Runtime = {\n async execute(command): AsyncResult<void, Error> {\n const parser = createArgsParser(command.args)\n const [argsError, validatedArgs] = parser.parse(command.rawArgs)\n if (argsError) {\n return err(argsError)\n }\n\n const ctx = createContext({\n args: validatedArgs,\n config,\n meta: {\n command: command.commandPath as string[],\n name: options.name,\n version: options.version,\n },\n })\n\n const finalHandler = command.handler ?? (async () => {})\n\n const [execError] = await attemptAsync(() =>\n runner.execute({\n ctx: ctx as Context,\n handler: finalHandler as (ctx: Context) => Promise<void> | void,\n middleware: command.middleware,\n })\n )\n if (execError) {\n return err(execError)\n }\n\n return ok()\n },\n }\n\n return ok(runtime)\n}\n\n// ---------------------------------------------------------------------------\n// Private\n// ---------------------------------------------------------------------------\n\n/**\n * Load and validate a config file via the config client.\n *\n * Returns the validated config record or an empty object when no config\n * options are provided or when loading fails.\n *\n * @private\n * @param configOptions - Config loading options with schema and optional name override.\n * @param defaultName - Fallback config file name derived from the CLI name.\n * @returns The loaded config record or an empty object.\n */\nasync function resolveConfig<TSchema extends z.ZodType>(\n configOptions: CliConfigOptions<TSchema> | undefined,\n defaultName: string\n): Promise<Record<string, unknown>> {\n if (!configOptions || !configOptions.schema) {\n return {}\n }\n const client = createConfigClient({\n name: configOptions.name ?? defaultName,\n schema: configOptions.schema,\n })\n const [configError, configResult] = await client.load()\n if (configError || !configResult) {\n return {}\n }\n return configResult.config as Record<string, unknown>\n}\n","import { resolve } from 'node:path'\n\nimport { loadConfig } from '@kidd-cli/config/loader'\nimport { attemptAsync, isPlainObject, isString } from '@kidd-cli/utils/fp'\nimport yargs from 'yargs'\nimport type { z } from 'zod'\n\nimport { DEFAULT_EXIT_CODE, isContextError } from '@/context/index.js'\nimport { createCliLogger } from '@/lib/logger.js'\nimport type { CliOptions, CommandMap } from '@/types.js'\n\nimport { autoload } from './autoloader.js'\nimport { createRuntime, registerCommands } from './runtime/index.js'\nimport type { ResolvedRef } from './runtime/index.js'\n\nconst ARGV_SLICE_START = 2\n\n/**\n * Bootstrap and run the CLI application.\n *\n * Parses argv, resolves the matched command, loads config, runs the\n * middleware chain, and invokes the command handler.\n *\n * @param options - CLI configuration including name, version, commands, and middleware.\n */\nexport async function cli<TSchema extends z.ZodType = z.ZodType>(\n options: CliOptions<TSchema>\n): Promise<void> {\n const logger = createCliLogger()\n\n const [uncaughtError, result] = await attemptAsync(async () => {\n const program = yargs(process.argv.slice(ARGV_SLICE_START))\n .scriptName(options.name)\n .version(options.version)\n .strict()\n .help()\n .option('cwd', {\n describe: 'Set the working directory',\n global: true,\n type: 'string',\n })\n\n if (options.description) {\n program.usage(options.description)\n }\n\n const resolved: ResolvedRef = { ref: undefined }\n\n const commands = await resolveCommands(options.commands)\n\n if (commands) {\n registerCommands({ commands, instance: program, parentPath: [], resolved })\n program.demandCommand(1, 'You must specify a command.')\n }\n\n const argv = await program.parseAsync()\n\n applyCwd(argv as Record<string, unknown>)\n\n if (!resolved.ref) {\n return undefined\n }\n\n const [runtimeError, runtime] = await createRuntime({\n config: options.config,\n middleware: options.middleware,\n name: options.name,\n version: options.version,\n })\n\n if (runtimeError) {\n return runtimeError\n }\n\n const [executeError] = await runtime.execute({\n args: resolved.ref.args,\n commandPath: resolved.ref.commandPath,\n handler: resolved.ref.handler,\n middleware: resolved.ref.middleware,\n rawArgs: argv as Record<string, unknown>,\n })\n\n return executeError\n })\n\n if (uncaughtError) {\n exitOnError(uncaughtError, logger)\n return\n }\n\n if (result) {\n exitOnError(result, logger)\n }\n}\n\nexport default cli\n\n// ---------------------------------------------------------------------------\n// Private\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve the commands option to a CommandMap.\n *\n * Accepts a directory string (triggers autoload), a static CommandMap,\n * a Promise<CommandMap> (from autoload() called at the call site),\n * or undefined (loads `kidd.config.ts` and autoloads from its `commands` field,\n * falling back to `'./commands'`).\n *\n * @private\n * @param commands - The commands option from CliOptions.\n * @returns A CommandMap or undefined.\n */\nasync function resolveCommands(\n commands: string | CommandMap | Promise<CommandMap> | undefined\n): Promise<CommandMap | undefined> {\n if (isString(commands)) {\n return autoload({ dir: commands })\n }\n if (commands instanceof Promise) {\n return commands\n }\n if (isPlainObject(commands)) {\n return commands as CommandMap\n }\n return resolveCommandsFromConfig()\n}\n\n/**\n * Load `kidd.config.ts` and autoload commands from its `commands` field.\n *\n * Falls back to `'./commands'` when the config file is missing, fails to load,\n * or does not specify a `commands` field.\n *\n * @private\n * @returns A CommandMap autoloaded from the configured commands directory.\n */\nasync function resolveCommandsFromConfig(): Promise<CommandMap> {\n const DEFAULT_COMMANDS_DIR = './commands'\n\n const [configError, configResult] = await loadConfig()\n if (configError || !configResult) {\n return autoload({ dir: DEFAULT_COMMANDS_DIR })\n }\n\n const dir = configResult.config.commands ?? DEFAULT_COMMANDS_DIR\n return autoload({ dir })\n}\n\n/**\n * Change the process working directory when `--cwd` is provided.\n *\n * Resolves the value to an absolute path and calls `process.chdir()` so\n * that all downstream `process.cwd()` calls reflect the override.\n *\n * @private\n * @param argv - The parsed argv record from yargs.\n */\nfunction applyCwd(argv: Record<string, unknown>): void {\n if (isString(argv.cwd)) {\n process.chdir(resolve(argv.cwd))\n }\n}\n\n/**\n * Handle a CLI error by logging the message and exiting with the appropriate code.\n *\n * ContextErrors carry a custom exit code; all other errors exit with code 1.\n *\n * @private\n * @param error - The caught error value.\n * @param logger - Logger with an error method for output.\n */\nfunction exitOnError(error: unknown, logger: { error(msg: string): void }): void {\n if (isContextError(error)) {\n logger.error(error.message)\n process.exit(error.exitCode)\n } else if (error instanceof Error) {\n logger.error(error.message)\n process.exit(DEFAULT_EXIT_CODE)\n } else {\n logger.error(String(error))\n process.exit(DEFAULT_EXIT_CODE)\n }\n}\n","import { withTag } from '@kidd-cli/utils/tag'\n\nimport type { ArgsDef, CommandDef, Command as CommandType } from './types.js'\n\n/**\n * Define a CLI command with typed args, config, and handler.\n *\n * @param def - Command definition including description, args schema, and handler.\n * @returns A resolved Command object for registration in the command map.\n */\nexport function command<\n TArgsDef extends ArgsDef = ArgsDef,\n TConfig extends Record<string, unknown> = Record<string, unknown>,\n>(def: CommandDef<TArgsDef, TConfig>): CommandType {\n return withTag({ ...def }, 'Command') as CommandType\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.2",
|
|
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",
|