dchk
Version:
UNIX-style domain availability checker (RDAP-first)
4 lines • 262 kB
Source Map (JSON)
{
"version": 3,
"sources": ["../cli/lib/iana-rdap.ts", "../cli/lib/rdap.ts", "../cli/utils/pool.ts", "../cli/utils/stdin.ts", "../cli/commands/check.ts", "../node_modules/commander/lib/error.js", "../node_modules/commander/lib/argument.js", "../node_modules/commander/lib/help.js", "../node_modules/commander/lib/option.js", "../node_modules/commander/lib/suggestSimilar.js", "../node_modules/commander/lib/command.js", "../node_modules/commander/index.js", "../node_modules/picocolors/picocolors.js", "../cli/index.ts", "../node_modules/commander/esm.mjs", "../cli/cli.ts", "../cli/utils/colors.ts", "../node_modules/chalk/source/vendor/ansi-styles/index.js", "../node_modules/chalk/source/vendor/supports-color/index.js", "../node_modules/chalk/source/utilities.js", "../node_modules/chalk/source/index.js", "../cli/utils/logger.ts"],
"sourcesContent": ["interface IanaCache {\n timestamp: number;\n data: any;\n}\n\nlet ianaCache: IanaCache | null = null;\nconst CACHE_TTL_MS = 60 * 60 * 1000; // 1 hour\n\nasync function fetchIanaBootstrap(): Promise<any> {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), 8000);\n \n try {\n const response = await fetch('https://data.iana.org/rdap/dns.json', {\n signal: controller.signal,\n headers: {\n 'Accept': 'application/json',\n 'User-Agent': 'dchk/0.1.0'\n }\n });\n \n clearTimeout(timeoutId);\n \n if (!response.ok) {\n throw new Error(`IANA bootstrap fetch failed: ${response.status}`);\n }\n \n return await response.json();\n } catch (error) {\n clearTimeout(timeoutId);\n throw error;\n }\n}\n\n/**\n * Get cached IANA RDAP bootstrap data or fetch fresh copy\n */\nasync function getIanaBootstrap(): Promise<any> {\n const now = Date.now();\n \n // Return cached data if still valid\n if (ianaCache && (now - ianaCache.timestamp) < CACHE_TTL_MS) {\n return ianaCache.data;\n }\n\n // Fetch fresh data\n const data = await fetchIanaBootstrap();\n \n // Update cache\n ianaCache = {\n timestamp: now,\n data\n };\n\n return data;\n}\n\n/**\n * Extract TLD from domain\n */\nfunction extractTld(domain: string): string | null {\n const parts = domain.toLowerCase().split('.');\n if (parts.length > 1) {\n const tld = parts[parts.length - 1];\n return tld || null;\n }\n return null;\n}\n\n/**\n * Find authoritative RDAP URL for a domain based on its TLD\n */\nexport async function getAuthoritativeRdapUrl(domain: string): Promise<string | null> {\n try {\n const tld = extractTld(domain);\n if (!tld) {\n return null;\n }\n\n const bootstrap = await getIanaBootstrap();\n \n // Find matching service in IANA bootstrap data\n // Format: [[\"tld1\", \"tld2\"], [\"https://rdap.example.com/\", \"https://backup.example.com/\"]]\n const service = bootstrap.services?.find((svc: any) => {\n const tlds = svc[0];\n return Array.isArray(tlds) && tlds.includes(tld);\n });\n\n if (!service || !service[1] || !service[1][0]) {\n return null;\n }\n\n // Return the first (primary) RDAP URL\n return service[1][0] || null;\n } catch {\n return null;\n }\n}\n\n/**\n * Clear the IANA cache (useful for testing)\n */\nexport function clearIanaCache(): void {\n ianaCache = null;\n}", "import type { Status, CheckResult, CheckOptions } from '../types/domain.js';\n\ninterface FetchResult {\n status: number;\n json: any;\n finalUrl: string;\n responseTimeMs: number;\n}\n\nfunction parseErrorCode(json: any): number | undefined {\n if (!json || typeof json !== 'object') return undefined;\n const errorCode = json.errorCode;\n return typeof errorCode === 'number' ? errorCode : undefined;\n}\n\nasync function fetchJson(url: string, timeoutMs: number): Promise<FetchResult> {\n const abortController = new AbortController();\n const timeout = setTimeout(() => abortController.abort(), timeoutMs);\n const startTime = Date.now();\n\n try {\n const response = await fetch(url, {\n redirect: 'follow', // Handle 302 redirects automatically\n headers: { \n 'Accept': 'application/rdap+json',\n 'User-Agent': 'dchk/0.1.0'\n },\n signal: abortController.signal\n });\n\n const responseTimeMs = Date.now() - startTime;\n const finalUrl = response.url; // URL after all redirects\n\n let json: any = null;\n const contentType = response.headers.get('content-type') || '';\n \n try {\n if (contentType.includes('json')) {\n json = await response.json();\n } else {\n // Try to parse as JSON even if content-type is wrong\n const text = await response.text();\n json = text ? JSON.parse(text) : null;\n }\n } catch {\n // Ignore JSON parsing errors, leave json = null\n }\n\n return {\n status: response.status,\n json,\n finalUrl,\n responseTimeMs\n };\n } finally {\n clearTimeout(timeout);\n }\n}\n\nfunction interpretRdapResponse(\n httpStatus: number,\n json: any\n): { status: Status; errorCode?: number } {\n // HTTP 200 = registered (even with null/bad JSON)\n if (httpStatus === 200) {\n const errorCode = parseErrorCode(json);\n if (errorCode === 404) {\n return { status: 'available', errorCode };\n }\n return { status: 'registered' };\n }\n\n // HTTP 404 = available\n if (httpStatus === 404) {\n return { status: 'available', errorCode: 404 };\n }\n\n // Check for errorCode in JSON even with other HTTP status\n const errorCode = parseErrorCode(json);\n if (errorCode === 404) {\n return { status: 'available', errorCode };\n }\n\n // Everything else is unknown\n if (errorCode !== undefined) {\n return { status: 'unknown', errorCode };\n }\n return { status: 'unknown' };\n}\n\nasync function checkViaRdapOrg(\n domain: string,\n timeoutMs: number\n): Promise<CheckResult> {\n try {\n const url = `https://rdap.org/domain/${encodeURIComponent(domain)}`;\n const result = await fetchJson(url, timeoutMs);\n const interpretation = interpretRdapResponse(result.status, result.json);\n\n const checkResult: CheckResult = {\n domain,\n status: interpretation.status,\n httpStatus: result.status,\n source: result.finalUrl === url ? 'rdap.org' : result.finalUrl,\n responseTimeMs: result.responseTimeMs\n };\n\n if (interpretation.errorCode !== undefined) {\n checkResult.errorCode = interpretation.errorCode;\n }\n\n return checkResult;\n } catch (error) {\n return {\n domain,\n status: 'unknown',\n source: 'rdap.org',\n responseTimeMs: 0\n };\n }\n}\n\nasync function checkViaAuthoritative(\n domain: string,\n timeoutMs: number\n): Promise<CheckResult | null> {\n try {\n // Import IANA handler dynamically to avoid circular dependencies\n const { getAuthoritativeRdapUrl } = await import('./iana-rdap.js');\n const authUrl = await getAuthoritativeRdapUrl(domain);\n \n if (!authUrl) {\n return null;\n }\n\n const url = `${authUrl.replace(/\\/$/, '')}/domain/${encodeURIComponent(domain)}`;\n const result = await fetchJson(url, timeoutMs);\n const interpretation = interpretRdapResponse(result.status, result.json);\n\n const checkResult: CheckResult = {\n domain,\n status: interpretation.status,\n httpStatus: result.status,\n source: authUrl,\n responseTimeMs: result.responseTimeMs\n };\n\n if (interpretation.errorCode !== undefined) {\n checkResult.errorCode = interpretation.errorCode;\n }\n\n return checkResult;\n } catch {\n return null;\n }\n}\n\n/**\n * Check a single domain's availability via RDAP.\n * \n * @param domain - Domain to check (e.g., \"example.com\")\n * @param options - Check options\n * @returns Promise resolving to check result\n */\nexport async function checkDomain(\n domain: string,\n options: CheckOptions = {}\n): Promise<CheckResult> {\n const timeoutMs = options.timeoutMs ?? 5000;\n const fallback = options.fallback ?? true;\n\n // 1. Try rdap.org first (aggregator with redirects)\n const primaryResult = await checkViaRdapOrg(domain, timeoutMs);\n \n // If we got a definitive answer (available or registered), return it\n if (primaryResult.status !== 'unknown') {\n return primaryResult;\n }\n\n // 2. Try authoritative RDAP fallback if enabled\n if (fallback) {\n const authResult = await checkViaAuthoritative(domain, timeoutMs);\n if (authResult && authResult.status !== 'unknown') {\n return authResult;\n }\n }\n\n // 3. Return the best result we have (probably unknown)\n return primaryResult;\n}\n\n/**\n * Validate domain format (basic check)\n */\nexport function isValidDomain(domain: string): boolean {\n if (!domain || typeof domain !== 'string') {\n return false;\n }\n\n // Domain must have at least one dot (TLD required)\n if (!domain.includes('.')) {\n return false;\n }\n\n // Basic domain validation - must have at least 2 parts (name.tld)\n const domainRegex = /^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+$/;\n return domainRegex.test(domain) && domain.length <= 253;\n}", "/**\n * Process items concurrently with a maximum concurrency limit\n * @param items - Array of items to process\n * @param concurrency - Maximum number of concurrent operations\n * @param worker - Function to process each item\n * @returns Promise resolving to array of results in original order\n */\nexport async function runConcurrent<T, R>(\n items: T[],\n concurrency: number,\n worker: (item: T) => Promise<R>\n): Promise<R[]> {\n if (items.length === 0) {\n return [];\n }\n\n if (concurrency <= 0) {\n concurrency = 1;\n }\n\n // If concurrency is higher than items length, process all at once\n if (concurrency >= items.length) {\n return Promise.all(items.map(worker));\n }\n\n const results: R[] = new Array(items.length);\n let index = 0;\n const active: Promise<void>[] = [];\n\n const processNext = async (): Promise<void> => {\n while (index < items.length) {\n const currentIndex = index++;\n const item = items[currentIndex];\n \n if (item === undefined) {\n continue;\n }\n \n const promise = worker(item)\n .then((result) => {\n results[currentIndex] = result;\n })\n .finally(() => {\n // Remove this promise from active list\n const promiseIndex = active.indexOf(promise);\n if (promiseIndex > -1) {\n active.splice(promiseIndex, 1);\n }\n });\n\n active.push(promise);\n\n // If we've reached the concurrency limit, wait for one to complete\n if (active.length >= concurrency) {\n await Promise.race(active);\n }\n }\n };\n\n // Start processing\n await processNext();\n\n // Wait for all remaining promises to complete\n await Promise.all(active);\n\n return results;\n}\n\n/**\n * Process items concurrently but return results as they complete (not in order)\n * @param items - Array of items to process\n * @param concurrency - Maximum number of concurrent operations\n * @param worker - Function to process each item\n * @param onResult - Callback for each completed result\n * @returns Promise that resolves when all items are processed\n */\nexport async function runConcurrentStreaming<T, R>(\n items: T[],\n concurrency: number,\n worker: (item: T) => Promise<R>,\n onResult: (result: R, item: T) => void\n): Promise<void> {\n if (items.length === 0) {\n return;\n }\n\n if (concurrency <= 0) {\n concurrency = 1;\n }\n\n let index = 0;\n const active: Promise<void>[] = [];\n\n const processNext = async (): Promise<void> => {\n while (index < items.length) {\n const currentIndex = index++;\n const item = items[currentIndex];\n \n if (item === undefined) {\n continue;\n }\n \n const promise = worker(item)\n .then((result) => {\n onResult(result, item);\n })\n .catch(() => {\n // Silently handle worker errors - streaming should continue\n // Individual failures don't stop the overall process\n });\n\n // Create a wrapper promise that handles cleanup\n const wrappedPromise = promise\n .finally(() => {\n // Remove this promise from active list\n const promiseIndex = active.indexOf(wrappedPromise);\n if (promiseIndex > -1) {\n active.splice(promiseIndex, 1);\n }\n });\n\n active.push(wrappedPromise);\n\n // If we've reached the concurrency limit, wait for one to complete\n if (active.length >= concurrency) {\n await Promise.race(active);\n }\n }\n };\n\n // Start processing\n await processNext();\n\n // Wait for all remaining promises to complete\n await Promise.all(active);\n}", "import { createInterface } from 'node:readline';\nimport { stdin } from 'node:process';\n\n/**\n * Read domain names from stdin, one per line\n * @returns Promise resolving to array of domain strings\n */\nexport async function readStdinDomains(): Promise<string[]> {\n // Skip stdin reading if we're in an interactive terminal (TTY)\n if (stdin.isTTY) {\n return [];\n }\n\n const readline = createInterface({\n input: stdin,\n crlfDelay: Infinity // Handle Windows line endings properly\n });\n\n const domains: string[] = [];\n\n try {\n for await (const line of readline) {\n const domain = line.trim();\n if (domain) {\n domains.push(domain);\n }\n }\n } finally {\n readline.close();\n }\n\n return domains;\n}\n\n/**\n * Check if stdin has data available (non-TTY)\n * @returns boolean indicating if stdin should be read\n */\nexport function shouldReadStdin(): boolean {\n return !stdin.isTTY;\n}", "import { checkDomain, isValidDomain } from '../lib/rdap.js';\nimport { readStdinDomains, shouldReadStdin } from '../utils/stdin.js';\nimport { runConcurrent } from '../utils/pool.js';\n\ninterface CheckOptions {\n verbose?: boolean;\n quiet?: boolean;\n concurrency?: number;\n timeout?: number;\n fallback?: boolean;\n}\n\nexport async function check(domains: string[], options: CheckOptions = {}): Promise<void> {\n try {\n // Collect all domains from arguments and stdin\n const allDomains = [...domains];\n \n // Read from stdin if available (non-TTY)\n if (shouldReadStdin()) {\n const stdinDomains = await readStdinDomains();\n allDomains.push(...stdinDomains);\n }\n\n // Validate we have domains to check\n if (allDomains.length === 0) {\n if (!options.quiet) {\n console.error('Error: No domains provided. Use --help for usage information.');\n }\n process.exit(2);\n }\n\n // Validate all domains\n const invalidDomains = allDomains.filter(domain => !isValidDomain(domain));\n if (invalidDomains.length > 0) {\n if (!options.quiet) {\n console.error(`Error: Invalid domain(s): ${invalidDomains.join(', ')}`);\n }\n process.exit(2);\n }\n\n // Configure check options\n const checkOptions = {\n timeoutMs: options.timeout || 5000,\n fallback: options.fallback !== false\n };\n\n // Process domains concurrently\n const concurrency = options.concurrency || Math.min(10, allDomains.length);\n const results = await runConcurrent(\n allDomains,\n concurrency,\n (domain) => checkDomain(domain, checkOptions)\n );\n\n // Process results and determine overall status\n let overallStatus: 'available' | 'registered' | 'mixed' | 'error' = 'available';\n let hasErrors = false;\n\n for (const result of results) {\n if (result.status === 'unknown') {\n hasErrors = true;\n } else if (result.status === 'registered') {\n if (overallStatus === 'available') {\n overallStatus = 'registered';\n } else if (overallStatus !== 'registered') {\n overallStatus = 'mixed';\n }\n }\n }\n\n if (hasErrors) {\n overallStatus = 'error';\n }\n\n // Output results\n if (options.verbose) {\n // Verbose mode: table format\n const tableData = results.map(result => ({\n domain: result.domain,\n status: result.status === 'available' ? 'AVAILABLE' : \n result.status === 'registered' ? 'REGISTERED' : 'UNKNOWN',\n responseTime: result.responseTimeMs ? `${result.responseTimeMs}ms` : '-',\n source: (() => {\n if (!result.source) return '-';\n if (!result.source.startsWith('http')) return result.source;\n try { \n return new URL(result.source).hostname; \n } catch { \n return result.source; \n }\n })(),\n result\n }));\n\n // Calculate column widths\n const domainWidth = Math.max(6, ...tableData.map(r => r.domain.length));\n const statusWidth = Math.max(6, ...tableData.map(r => r.status.length));\n const timeWidth = Math.max(4, ...tableData.map(r => r.responseTime.length));\n const sourceWidth = Math.max(6, ...tableData.map(r => r.source.length));\n\n // Print table header\n const header = `${'DOMAIN'.padEnd(domainWidth)} ${'STATUS'.padEnd(statusWidth)} ${'TIME'.padEnd(timeWidth)} ${'SOURCE'.padEnd(sourceWidth)}`;\n console.log(header);\n console.log('\u2500'.repeat(header.length));\n\n // Print table rows\n for (const row of tableData) {\n const line = `${row.domain.padEnd(domainWidth)} ${row.status.padEnd(statusWidth)} ${row.responseTime.padEnd(timeWidth)} ${row.source.padEnd(sourceWidth)}`;\n console.log(line);\n \n if (row.result.status === 'unknown' && row.result.httpStatus) {\n console.error(`${''.padEnd(domainWidth)} HTTP ${row.result.httpStatus}${row.result.errorCode ? `, Error ${row.result.errorCode}` : ''}`);\n }\n }\n } else {\n // Simple mode: just the status\n if (allDomains.length === 1) {\n // Single domain: output true/false\n const result = results[0];\n if (!result) {\n if (!options.quiet) {\n console.error('Error: No result returned for domain');\n }\n process.exit(2);\n }\n \n const available = result.status === 'available';\n \n if (!options.quiet) {\n console.log(available ? 'available' : result.status === 'registered' ? 'registered' : 'unknown');\n }\n } else {\n // Multiple domains: output each result\n for (const result of results) {\n if (!result) continue;\n const statusText = result.status === 'available' ? 'available' : \n result.status === 'registered' ? 'registered' : 'unknown';\n console.log(`${result.domain}: ${statusText}`);\n }\n }\n }\n\n // Set exit code based on overall status\n if (overallStatus === 'available') {\n process.exit(0); // All available\n } else if (overallStatus === 'registered' || overallStatus === 'mixed') {\n process.exit(1); // All registered or mixed results\n } else {\n process.exit(2); // Errors occurred\n }\n\n } catch (error) {\n if (!options.quiet) {\n console.error(`Error: ${error instanceof Error ? error.message : 'Unknown error occurred'}`);\n }\n process.exit(2);\n }\n}", "/**\n * CommanderError class\n */\nclass CommanderError extends Error {\n /**\n * Constructs the CommanderError class\n * @param {number} exitCode suggested exit code which could be used with process.exit\n * @param {string} code an id string representing the error\n * @param {string} message human-readable description of the error\n */\n constructor(exitCode, code, message) {\n super(message);\n // properly capture stack trace in Node.js\n Error.captureStackTrace(this, this.constructor);\n this.name = this.constructor.name;\n this.code = code;\n this.exitCode = exitCode;\n this.nestedError = undefined;\n }\n}\n\n/**\n * InvalidArgumentError class\n */\nclass InvalidArgumentError extends CommanderError {\n /**\n * Constructs the InvalidArgumentError class\n * @param {string} [message] explanation of why argument is invalid\n */\n constructor(message) {\n super(1, 'commander.invalidArgument', message);\n // properly capture stack trace in Node.js\n Error.captureStackTrace(this, this.constructor);\n this.name = this.constructor.name;\n }\n}\n\nexports.CommanderError = CommanderError;\nexports.InvalidArgumentError = InvalidArgumentError;\n", "const { InvalidArgumentError } = require('./error.js');\n\nclass Argument {\n /**\n * Initialize a new command argument with the given name and description.\n * The default is that the argument is required, and you can explicitly\n * indicate this with <> around the name. Put [] around the name for an optional argument.\n *\n * @param {string} name\n * @param {string} [description]\n */\n\n constructor(name, description) {\n this.description = description || '';\n this.variadic = false;\n this.parseArg = undefined;\n this.defaultValue = undefined;\n this.defaultValueDescription = undefined;\n this.argChoices = undefined;\n\n switch (name[0]) {\n case '<': // e.g. <required>\n this.required = true;\n this._name = name.slice(1, -1);\n break;\n case '[': // e.g. [optional]\n this.required = false;\n this._name = name.slice(1, -1);\n break;\n default:\n this.required = true;\n this._name = name;\n break;\n }\n\n if (this._name.length > 3 && this._name.slice(-3) === '...') {\n this.variadic = true;\n this._name = this._name.slice(0, -3);\n }\n }\n\n /**\n * Return argument name.\n *\n * @return {string}\n */\n\n name() {\n return this._name;\n }\n\n /**\n * @package\n */\n\n _concatValue(value, previous) {\n if (previous === this.defaultValue || !Array.isArray(previous)) {\n return [value];\n }\n\n return previous.concat(value);\n }\n\n /**\n * Set the default value, and optionally supply the description to be displayed in the help.\n *\n * @param {*} value\n * @param {string} [description]\n * @return {Argument}\n */\n\n default(value, description) {\n this.defaultValue = value;\n this.defaultValueDescription = description;\n return this;\n }\n\n /**\n * Set the custom handler for processing CLI command arguments into argument values.\n *\n * @param {Function} [fn]\n * @return {Argument}\n */\n\n argParser(fn) {\n this.parseArg = fn;\n return this;\n }\n\n /**\n * Only allow argument value to be one of choices.\n *\n * @param {string[]} values\n * @return {Argument}\n */\n\n choices(values) {\n this.argChoices = values.slice();\n this.parseArg = (arg, previous) => {\n if (!this.argChoices.includes(arg)) {\n throw new InvalidArgumentError(\n `Allowed choices are ${this.argChoices.join(', ')}.`,\n );\n }\n if (this.variadic) {\n return this._concatValue(arg, previous);\n }\n return arg;\n };\n return this;\n }\n\n /**\n * Make argument required.\n *\n * @returns {Argument}\n */\n argRequired() {\n this.required = true;\n return this;\n }\n\n /**\n * Make argument optional.\n *\n * @returns {Argument}\n */\n argOptional() {\n this.required = false;\n return this;\n }\n}\n\n/**\n * Takes an argument and returns its human readable equivalent for help usage.\n *\n * @param {Argument} arg\n * @return {string}\n * @private\n */\n\nfunction humanReadableArgName(arg) {\n const nameOutput = arg.name() + (arg.variadic === true ? '...' : '');\n\n return arg.required ? '<' + nameOutput + '>' : '[' + nameOutput + ']';\n}\n\nexports.Argument = Argument;\nexports.humanReadableArgName = humanReadableArgName;\n", "const { humanReadableArgName } = require('./argument.js');\n\n/**\n * TypeScript import types for JSDoc, used by Visual Studio Code IntelliSense and `npm run typescript-checkJS`\n * https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html#import-types\n * @typedef { import(\"./argument.js\").Argument } Argument\n * @typedef { import(\"./command.js\").Command } Command\n * @typedef { import(\"./option.js\").Option } Option\n */\n\n// Although this is a class, methods are static in style to allow override using subclass or just functions.\nclass Help {\n constructor() {\n this.helpWidth = undefined;\n this.minWidthToWrap = 40;\n this.sortSubcommands = false;\n this.sortOptions = false;\n this.showGlobalOptions = false;\n }\n\n /**\n * prepareContext is called by Commander after applying overrides from `Command.configureHelp()`\n * and just before calling `formatHelp()`.\n *\n * Commander just uses the helpWidth and the rest is provided for optional use by more complex subclasses.\n *\n * @param {{ error?: boolean, helpWidth?: number, outputHasColors?: boolean }} contextOptions\n */\n prepareContext(contextOptions) {\n this.helpWidth = this.helpWidth ?? contextOptions.helpWidth ?? 80;\n }\n\n /**\n * Get an array of the visible subcommands. Includes a placeholder for the implicit help command, if there is one.\n *\n * @param {Command} cmd\n * @returns {Command[]}\n */\n\n visibleCommands(cmd) {\n const visibleCommands = cmd.commands.filter((cmd) => !cmd._hidden);\n const helpCommand = cmd._getHelpCommand();\n if (helpCommand && !helpCommand._hidden) {\n visibleCommands.push(helpCommand);\n }\n if (this.sortSubcommands) {\n visibleCommands.sort((a, b) => {\n // @ts-ignore: because overloaded return type\n return a.name().localeCompare(b.name());\n });\n }\n return visibleCommands;\n }\n\n /**\n * Compare options for sort.\n *\n * @param {Option} a\n * @param {Option} b\n * @returns {number}\n */\n compareOptions(a, b) {\n const getSortKey = (option) => {\n // WYSIWYG for order displayed in help. Short used for comparison if present. No special handling for negated.\n return option.short\n ? option.short.replace(/^-/, '')\n : option.long.replace(/^--/, '');\n };\n return getSortKey(a).localeCompare(getSortKey(b));\n }\n\n /**\n * Get an array of the visible options. Includes a placeholder for the implicit help option, if there is one.\n *\n * @param {Command} cmd\n * @returns {Option[]}\n */\n\n visibleOptions(cmd) {\n const visibleOptions = cmd.options.filter((option) => !option.hidden);\n // Built-in help option.\n const helpOption = cmd._getHelpOption();\n if (helpOption && !helpOption.hidden) {\n // Automatically hide conflicting flags. Bit dubious but a historical behaviour that is convenient for single-command programs.\n const removeShort = helpOption.short && cmd._findOption(helpOption.short);\n const removeLong = helpOption.long && cmd._findOption(helpOption.long);\n if (!removeShort && !removeLong) {\n visibleOptions.push(helpOption); // no changes needed\n } else if (helpOption.long && !removeLong) {\n visibleOptions.push(\n cmd.createOption(helpOption.long, helpOption.description),\n );\n } else if (helpOption.short && !removeShort) {\n visibleOptions.push(\n cmd.createOption(helpOption.short, helpOption.description),\n );\n }\n }\n if (this.sortOptions) {\n visibleOptions.sort(this.compareOptions);\n }\n return visibleOptions;\n }\n\n /**\n * Get an array of the visible global options. (Not including help.)\n *\n * @param {Command} cmd\n * @returns {Option[]}\n */\n\n visibleGlobalOptions(cmd) {\n if (!this.showGlobalOptions) return [];\n\n const globalOptions = [];\n for (\n let ancestorCmd = cmd.parent;\n ancestorCmd;\n ancestorCmd = ancestorCmd.parent\n ) {\n const visibleOptions = ancestorCmd.options.filter(\n (option) => !option.hidden,\n );\n globalOptions.push(...visibleOptions);\n }\n if (this.sortOptions) {\n globalOptions.sort(this.compareOptions);\n }\n return globalOptions;\n }\n\n /**\n * Get an array of the arguments if any have a description.\n *\n * @param {Command} cmd\n * @returns {Argument[]}\n */\n\n visibleArguments(cmd) {\n // Side effect! Apply the legacy descriptions before the arguments are displayed.\n if (cmd._argsDescription) {\n cmd.registeredArguments.forEach((argument) => {\n argument.description =\n argument.description || cmd._argsDescription[argument.name()] || '';\n });\n }\n\n // If there are any arguments with a description then return all the arguments.\n if (cmd.registeredArguments.find((argument) => argument.description)) {\n return cmd.registeredArguments;\n }\n return [];\n }\n\n /**\n * Get the command term to show in the list of subcommands.\n *\n * @param {Command} cmd\n * @returns {string}\n */\n\n subcommandTerm(cmd) {\n // Legacy. Ignores custom usage string, and nested commands.\n const args = cmd.registeredArguments\n .map((arg) => humanReadableArgName(arg))\n .join(' ');\n return (\n cmd._name +\n (cmd._aliases[0] ? '|' + cmd._aliases[0] : '') +\n (cmd.options.length ? ' [options]' : '') + // simplistic check for non-help option\n (args ? ' ' + args : '')\n );\n }\n\n /**\n * Get the option term to show in the list of options.\n *\n * @param {Option} option\n * @returns {string}\n */\n\n optionTerm(option) {\n return option.flags;\n }\n\n /**\n * Get the argument term to show in the list of arguments.\n *\n * @param {Argument} argument\n * @returns {string}\n */\n\n argumentTerm(argument) {\n return argument.name();\n }\n\n /**\n * Get the longest command term length.\n *\n * @param {Command} cmd\n * @param {Help} helper\n * @returns {number}\n */\n\n longestSubcommandTermLength(cmd, helper) {\n return helper.visibleCommands(cmd).reduce((max, command) => {\n return Math.max(\n max,\n this.displayWidth(\n helper.styleSubcommandTerm(helper.subcommandTerm(command)),\n ),\n );\n }, 0);\n }\n\n /**\n * Get the longest option term length.\n *\n * @param {Command} cmd\n * @param {Help} helper\n * @returns {number}\n */\n\n longestOptionTermLength(cmd, helper) {\n return helper.visibleOptions(cmd).reduce((max, option) => {\n return Math.max(\n max,\n this.displayWidth(helper.styleOptionTerm(helper.optionTerm(option))),\n );\n }, 0);\n }\n\n /**\n * Get the longest global option term length.\n *\n * @param {Command} cmd\n * @param {Help} helper\n * @returns {number}\n */\n\n longestGlobalOptionTermLength(cmd, helper) {\n return helper.visibleGlobalOptions(cmd).reduce((max, option) => {\n return Math.max(\n max,\n this.displayWidth(helper.styleOptionTerm(helper.optionTerm(option))),\n );\n }, 0);\n }\n\n /**\n * Get the longest argument term length.\n *\n * @param {Command} cmd\n * @param {Help} helper\n * @returns {number}\n */\n\n longestArgumentTermLength(cmd, helper) {\n return helper.visibleArguments(cmd).reduce((max, argument) => {\n return Math.max(\n max,\n this.displayWidth(\n helper.styleArgumentTerm(helper.argumentTerm(argument)),\n ),\n );\n }, 0);\n }\n\n /**\n * Get the command usage to be displayed at the top of the built-in help.\n *\n * @param {Command} cmd\n * @returns {string}\n */\n\n commandUsage(cmd) {\n // Usage\n let cmdName = cmd._name;\n if (cmd._aliases[0]) {\n cmdName = cmdName + '|' + cmd._aliases[0];\n }\n let ancestorCmdNames = '';\n for (\n let ancestorCmd = cmd.parent;\n ancestorCmd;\n ancestorCmd = ancestorCmd.parent\n ) {\n ancestorCmdNames = ancestorCmd.name() + ' ' + ancestorCmdNames;\n }\n return ancestorCmdNames + cmdName + ' ' + cmd.usage();\n }\n\n /**\n * Get the description for the command.\n *\n * @param {Command} cmd\n * @returns {string}\n */\n\n commandDescription(cmd) {\n // @ts-ignore: because overloaded return type\n return cmd.description();\n }\n\n /**\n * Get the subcommand summary to show in the list of subcommands.\n * (Fallback to description for backwards compatibility.)\n *\n * @param {Command} cmd\n * @returns {string}\n */\n\n subcommandDescription(cmd) {\n // @ts-ignore: because overloaded return type\n return cmd.summary() || cmd.description();\n }\n\n /**\n * Get the option description to show in the list of options.\n *\n * @param {Option} option\n * @return {string}\n */\n\n optionDescription(option) {\n const extraInfo = [];\n\n if (option.argChoices) {\n extraInfo.push(\n // use stringify to match the display of the default value\n `choices: ${option.argChoices.map((choice) => JSON.stringify(choice)).join(', ')}`,\n );\n }\n if (option.defaultValue !== undefined) {\n // default for boolean and negated more for programmer than end user,\n // but show true/false for boolean option as may be for hand-rolled env or config processing.\n const showDefault =\n option.required ||\n option.optional ||\n (option.isBoolean() && typeof option.defaultValue === 'boolean');\n if (showDefault) {\n extraInfo.push(\n `default: ${option.defaultValueDescription || JSON.stringify(option.defaultValue)}`,\n );\n }\n }\n // preset for boolean and negated are more for programmer than end user\n if (option.presetArg !== undefined && option.optional) {\n extraInfo.push(`preset: ${JSON.stringify(option.presetArg)}`);\n }\n if (option.envVar !== undefined) {\n extraInfo.push(`env: ${option.envVar}`);\n }\n if (extraInfo.length > 0) {\n const extraDescription = `(${extraInfo.join(', ')})`;\n if (option.description) {\n return `${option.description} ${extraDescription}`;\n }\n return extraDescription;\n }\n\n return option.description;\n }\n\n /**\n * Get the argument description to show in the list of arguments.\n *\n * @param {Argument} argument\n * @return {string}\n */\n\n argumentDescription(argument) {\n const extraInfo = [];\n if (argument.argChoices) {\n extraInfo.push(\n // use stringify to match the display of the default value\n `choices: ${argument.argChoices.map((choice) => JSON.stringify(choice)).join(', ')}`,\n );\n }\n if (argument.defaultValue !== undefined) {\n extraInfo.push(\n `default: ${argument.defaultValueDescription || JSON.stringify(argument.defaultValue)}`,\n );\n }\n if (extraInfo.length > 0) {\n const extraDescription = `(${extraInfo.join(', ')})`;\n if (argument.description) {\n return `${argument.description} ${extraDescription}`;\n }\n return extraDescription;\n }\n return argument.description;\n }\n\n /**\n * Format a list of items, given a heading and an array of formatted items.\n *\n * @param {string} heading\n * @param {string[]} items\n * @param {Help} helper\n * @returns string[]\n */\n formatItemList(heading, items, helper) {\n if (items.length === 0) return [];\n\n return [helper.styleTitle(heading), ...items, ''];\n }\n\n /**\n * Group items by their help group heading.\n *\n * @param {Command[] | Option[]} unsortedItems\n * @param {Command[] | Option[]} visibleItems\n * @param {Function} getGroup\n * @returns {Map<string, Command[] | Option[]>}\n */\n groupItems(unsortedItems, visibleItems, getGroup) {\n const result = new Map();\n // Add groups in order of appearance in unsortedItems.\n unsortedItems.forEach((item) => {\n const group = getGroup(item);\n if (!result.has(group)) result.set(group, []);\n });\n // Add items in order of appearance in visibleItems.\n visibleItems.forEach((item) => {\n const group = getGroup(item);\n if (!result.has(group)) {\n result.set(group, []);\n }\n result.get(group).push(item);\n });\n return result;\n }\n\n /**\n * Generate the built-in help text.\n *\n * @param {Command} cmd\n * @param {Help} helper\n * @returns {string}\n */\n\n formatHelp(cmd, helper) {\n const termWidth = helper.padWidth(cmd, helper);\n const helpWidth = helper.helpWidth ?? 80; // in case prepareContext() was not called\n\n function callFormatItem(term, description) {\n return helper.formatItem(term, termWidth, description, helper);\n }\n\n // Usage\n let output = [\n `${helper.styleTitle('Usage:')} ${helper.styleUsage(helper.commandUsage(cmd))}`,\n '',\n ];\n\n // Description\n const commandDescription = helper.commandDescription(cmd);\n if (commandDescription.length > 0) {\n output = output.concat([\n helper.boxWrap(\n helper.styleCommandDescription(commandDescription),\n helpWidth,\n ),\n '',\n ]);\n }\n\n // Arguments\n const argumentList = helper.visibleArguments(cmd).map((argument) => {\n return callFormatItem(\n helper.styleArgumentTerm(helper.argumentTerm(argument)),\n helper.styleArgumentDescription(helper.argumentDescription(argument)),\n );\n });\n output = output.concat(\n this.formatItemList('Arguments:', argumentList, helper),\n );\n\n // Options\n const optionGroups = this.groupItems(\n cmd.options,\n helper.visibleOptions(cmd),\n (option) => option.helpGroupHeading ?? 'Options:',\n );\n optionGroups.forEach((options, group) => {\n const optionList = options.map((option) => {\n return callFormatItem(\n helper.styleOptionTerm(helper.optionTerm(option)),\n helper.styleOptionDescription(helper.optionDescription(option)),\n );\n });\n output = output.concat(this.formatItemList(group, optionList, helper));\n });\n\n if (helper.showGlobalOptions) {\n const globalOptionList = helper\n .visibleGlobalOptions(cmd)\n .map((option) => {\n return callFormatItem(\n helper.styleOptionTerm(helper.optionTerm(option)),\n helper.styleOptionDescription(helper.optionDescription(option)),\n );\n });\n output = output.concat(\n this.formatItemList('Global Options:', globalOptionList, helper),\n );\n }\n\n // Commands\n const commandGroups = this.groupItems(\n cmd.commands,\n helper.visibleCommands(cmd),\n (sub) => sub.helpGroup() || 'Commands:',\n );\n commandGroups.forEach((commands, group) => {\n const commandList = commands.map((sub) => {\n return callFormatItem(\n helper.styleSubcommandTerm(helper.subcommandTerm(sub)),\n helper.styleSubcommandDescription(helper.subcommandDescription(sub)),\n );\n });\n output = output.concat(this.formatItemList(group, commandList, helper));\n });\n\n return output.join('\\n');\n }\n\n /**\n * Return display width of string, ignoring ANSI escape sequences. Used in padding and wrapping calculations.\n *\n * @param {string} str\n * @returns {number}\n */\n displayWidth(str) {\n return stripColor(str).length;\n }\n\n /**\n * Style the title for displaying in the help. Called with 'Usage:', 'Options:', etc.\n *\n * @param {string} str\n * @returns {string}\n */\n styleTitle(str) {\n return str;\n }\n\n styleUsage(str) {\n // Usage has lots of parts the user might like to color separately! Assume default usage string which is formed like:\n // command subcommand [options] [command] <foo> [bar]\n return str\n .split(' ')\n .map((word) => {\n if (word === '[options]') return this.styleOptionText(word);\n if (word === '[command]') return this.styleSubcommandText(word);\n if (word[0] === '[' || word[0] === '<')\n return this.styleArgumentText(word);\n return this.styleCommandText(word); // Restrict to initial words?\n })\n .join(' ');\n }\n styleCommandDescription(str) {\n return this.styleDescriptionText(str);\n }\n styleOptionDescription(str) {\n return this.styleDescriptionText(str);\n }\n styleSubcommandDescription(str) {\n return this.styleDescriptionText(str);\n }\n styleArgumentDescription(str) {\n return this.styleDescriptionText(str);\n }\n styleDescriptionText(str) {\n return str;\n }\n styleOptionTerm(str) {\n return this.styleOptionText(str);\n }\n styleSubcommandTerm(str) {\n // This is very like usage with lots of parts! Assume default string which is formed like:\n // subcommand [options] <foo> [bar]\n return str\n .split(' ')\n .map((word) => {\n if (word === '[options]') return this.styleOptionText(word);\n if (word[0] === '[' || word[0] === '<')\n return this.styleArgumentText(word);\n return this.styleSubcommandText(word); // Restrict to initial words?\n })\n .join(' ');\n }\n styleArgumentTerm(str) {\n return this.styleArgumentText(str);\n }\n styleOptionText(str) {\n return str;\n }\n styleArgumentText(str) {\n return str;\n }\n styleSubcommandText(str) {\n return str;\n }\n styleCommandText(str) {\n return str;\n }\n\n /**\n * Calculate the pad width from the maximum term length.\n *\n * @param {Command} cmd\n * @param {Help} helper\n * @returns {number}\n */\n\n padWidth(cmd, helper) {\n return Math.max(\n helper.longestOptionTermLength(cmd, helper),\n helper.longestGlobalOptionTermLength(cmd, helper),\n helper.longestSubcommandTermLength(cmd, helper),\n helper.longestArgumentTermLength(cmd, helper),\n );\n }\n\n /**\n * Detect manually wrapped and indented strings by checking for line break followed by whitespace.\n *\n * @param {string} str\n * @returns {boolean}\n */\n preformatted(str) {\n return /\\n[^\\S\\r\\n]/.test(str);\n }\n\n /**\n * Format the \"item\", which consists of a term and description. Pad the term and wrap the description, indenting the following lines.\n *\n * So \"TTT\", 5, \"DDD DDDD DD DDD\" might be formatted for this.helpWidth=17 like so:\n * TTT DDD DDDD\n * DD DDD\n *\n * @param {string} term\n * @param {number} termWidth\n * @param {string} description\n * @param {Help} helper\n * @returns {string}\n */\n formatItem(term, termWidth, description, helper) {\n const itemIndent = 2;\n const itemIndentStr = ' '.repeat(itemIndent);\n if (!description) return itemIndentStr + term;\n\n // Pad the term out to a consistent width, so descriptions are aligned.\n const paddedTerm = term.padEnd(\n termWidth + term.length - helper.displayWidth(term),\n );\n\n // Format the description.\n const spacerWidth = 2; // between term and description\n const helpWidth = this.helpWidth ?? 80; // in case prepareContext() was not called\n const remainingWidth = helpWidth - termWidth - spacerWidth - itemIndent;\n let formattedDescription;\n if (\n remainingWidth < this.minWidthToWrap ||\n helper.preformatted(description)\n ) {\n formattedDescription = description;\n } else {\n const wrappedDescription = helper.boxWrap(description, remainingWidth);\n formattedDescription = wrappedDescription.replace(\n /\\n/g,\n '\\n' + ' '.repeat(termWidth + spacerWidth),\n );\n }\n\n // Construct and overall indent.\n return (\n itemIndentStr +\n paddedTerm +\n ' '.repeat(spacerWidth) +\n formattedDescription.replace(/\\n/g, `\\n${itemIndentStr}`)\n );\n }\n\n /**\n * Wrap a string at whitespace, preserving existing line breaks.\n * Wrapping is skipped if the width is less than `minWidthToWrap`.\n *\n * @param {string} str\n * @param {number} width\n * @returns {string}\n */\n boxWrap(str, width) {\n if (width < this.minWidthToWrap) return str;\n\n const rawLines = str.split(/\\r\\n|\\n/);\n // split up text by whitespace\n const chunkPattern = /[\\s]*[^\\s]+/g;\n const wrappedLines = [];\n rawLines.forEach((line) => {\n const chunks = line.match(chunkPattern);\n if (chunks === null) {\n wrappedLines.push('');\n return;\n }\n\n let sumChunks = [chunks.shift()];\n let sumWidth = this.displayWidth(sumChunks[0]);\n chunks.forEach((chunk) => {\n const visibleWidth = this.displayWidth(chunk);\n // Accumulate chunks while they fit into width.\n if (sumWidth + visibleWidth <= width) {\n sumChunks.push(chunk);\n sumWidth += visibleWidth;\n return;\n }\n wrappedLines.push(sumChunks.join(''));\n\n const nextChunk = chunk.trimStart(); // trim space at line break\n sumChunks = [nextChunk];\n sumWidth = this.displayWidth(nextChunk);\n });\n wrappedLines.push(sumChunks.join(''));\n });\n\n return wrappedLines.join('\\n');\n }\n}\n\n/**\n * Strip style ANSI escape sequences from the string. In particular, SGR (Select Graphic Rendition) codes.\n *\n * @param {string} str\n * @returns {string}\n * @package\n */\n\nfunction stripColor(str) {\n // eslint-disable-next-line no-control-regex\n const sgrPattern = /\\x1b\\[\\d*(;\\d*)*m/g;\n return str.replace(sgrPattern, '');\n}\n\nexports.Help = Help;\nexports.stripColor = stripColor;\n", "const { InvalidArgumentError } = require('./error.js');\n\nclass Option {\n /**\n * Initialize a new `Option` with the given `flags` and `description`.\n *\n * @param {string} flags\n * @param {string} [description]\n */\n\n constructor(flags, description) {\n this.flags = flags;\n this.description = description || '';\n\n this.required = flags.includes('<'); // A value must be supplied when the option is specified.\n this.optional = flags.includes('['); // A value is optional when the option is specified.\n // variadic test ignores <value,...> et al which might be used to describe custom splitting of single argument\n this.variadic = /\\w\\.\\.\\.[>\\]]$/.test(flags); // The option can take multiple values.\n this.mandatory = false; // The option must have a value after parsing, which usually means it must be specified on command line.\n const optionFlags = splitOptionFlags(flags);\n this.short = optionFlags.shortFlag; // May be a short flag, undefined, or even a long flag (if option has two long flags).\n this.long = optionFlags.longFlag;\n this.negate = false;\n if (this.long) {\n this.negate = this.long.startsWith('--no-');\n }\n this.defaultValue = undefined;\n this.defaultValueDescription = undefined;\n this.presetArg = undefined;\n this.envVar = undefined;\n this.parseArg = undefined;\n this.hidden = false;\n this.argChoices = undefined;\n this.conflictsWith = [];\n this.implied = undefined;\n this.helpGroupHeading = undefined; // soft initialised when option added to command\n }\n\n /**\n * Set the default value, and optionally supply the description to be displayed in the help.\n *\n * @param {*} value\n * @param {string} [description]\n * @return {Option}\n */\n\n default(value, description) {\n this.defaultValue = value;\n this.defaultValueDescription = description;\n return this;\n }\n\n /**\n * Preset to use when option used without option-argument, especially optional but also boolean and negated.\n * The custom processing (parseArg) is called.\n *\n * @example\n * new Option('--color').default('GREYSCALE').preset('RGB');\n * new Option('--donate [amount]').preset('20').argParser(parseFloat);\n *\n * @param {*} arg\n * @return {Option}\n */\n\n preset(arg) {\n this.presetArg = arg;\n return this;\n }\n\n /**\n * Add option name(s) that conflict with this option.\n * An error will be displayed if conflicting options are found during parsing.\n *\n * @example\n * new Option('--rgb').conflicts('cmyk');\n * new Option('--js').conflicts(['ts', 'jsx']);\n *\n * @param {(string | string[])} names\n * @return {Option}\n */\n\n conflicts(names) {\n this.conflictsWith = this.conflictsWith.concat(names);\n return this;\n }\n\n /**\n * Specify implied option values for when this option is set and the implied options are not.\n *\n * The custom processing (parseArg) is not called on the implied values.\n *\n * @example\n * program\n * .addOption(new Option('--log', 'write logging information to file'))\n * .addOption(new Option('--trace', 'log extra details').implies({ log: 'trace.txt' }));\n *\n * @param {object} impliedOptionValues\n * @return {Option}\n */\n implies(impliedOptionValues) {\n let newImplied = impliedOptionValues;\n if (typeof impliedOptionValues === 'string') {\n // string is not documented, but easy mistake and we can do what user probably intended.\n newImplied = { [impliedOptionValues]: true };\n }\n this.implied = Object.assign(this.implied || {}, newImplied);\n return this;\n }\n\n /**\n * Set environment variable to check for option value.\n *\n * An environment variable is only used if when processed the current option value is\n * undefined, or the source of the current value is '