163 lines
4.2 KiB
JavaScript
163 lines
4.2 KiB
JavaScript
#!/usr/bin/env node
|
|
import { readFile } from 'node:fs/promises'
|
|
import { resolve } from 'node:path'
|
|
|
|
import { RevolutionaryTypedFetch } from './core/typed-fetch.js'
|
|
import type { TypedFetchConfig } from './types/config.js'
|
|
import type { TypeSnapshotOptions } from './discovery/type-generator.js'
|
|
|
|
interface ParsedArgs {
|
|
command: string
|
|
base?: string
|
|
out?: string
|
|
namespace?: string
|
|
banner?: string
|
|
config?: string
|
|
verbose?: boolean
|
|
}
|
|
|
|
const SHORT_FLAGS: Record<string, keyof ParsedArgs> = {
|
|
b: 'base',
|
|
o: 'out',
|
|
n: 'namespace',
|
|
c: 'config'
|
|
}
|
|
|
|
async function main(): Promise<void> {
|
|
const parsed = parseArgs(process.argv.slice(2))
|
|
const command = parsed.command || 'sync'
|
|
|
|
switch (command) {
|
|
case 'sync':
|
|
await handleSync(parsed)
|
|
break
|
|
case 'help':
|
|
case '--help':
|
|
printHelp()
|
|
break
|
|
default:
|
|
console.error(`Unknown command: ${command}`)
|
|
printHelp()
|
|
process.exitCode = 1
|
|
}
|
|
}
|
|
|
|
function parseArgs(argv: string[]): ParsedArgs {
|
|
const args: ParsedArgs = { command: '' }
|
|
const positionals: string[] = []
|
|
|
|
for (let i = 0; i < argv.length; i++) {
|
|
const token = argv[i]!
|
|
if (!token.startsWith('-')) {
|
|
positionals.push(token)
|
|
continue
|
|
}
|
|
|
|
if (token.startsWith('--')) {
|
|
const segment = token.slice(2)
|
|
const [rawFlag, maybeValue] = segment.split('=', 2)
|
|
const flag = rawFlag ?? ''
|
|
if (!flag) continue
|
|
if (flag.startsWith('no-')) {
|
|
;(args as any)[flag.slice(3)] = false
|
|
continue
|
|
}
|
|
|
|
if (maybeValue !== undefined) {
|
|
;(args as any)[flag] = maybeValue
|
|
continue
|
|
}
|
|
|
|
const next = argv[i + 1]
|
|
if (!next || next.startsWith('-')) {
|
|
;(args as any)[flag] = true
|
|
} else {
|
|
;(args as any)[flag] = next
|
|
i++
|
|
}
|
|
continue
|
|
}
|
|
|
|
const short = token.replace(/^-+/, '')
|
|
const mapped = SHORT_FLAGS[short]
|
|
if (mapped) {
|
|
const next = argv[i + 1]
|
|
if (!next || next.startsWith('-')) {
|
|
throw new Error(`Flag -${short} requires a value`)
|
|
}
|
|
;(args as any)[mapped] = next
|
|
i++
|
|
}
|
|
}
|
|
|
|
args.command = positionals[0] || args.command || 'sync'
|
|
if (!args.base && positionals[1]) {
|
|
args.base = positionals[1]
|
|
}
|
|
if (!args.out && positionals[2]) {
|
|
args.out = positionals[2]
|
|
}
|
|
|
|
return args
|
|
}
|
|
|
|
async function handleSync(args: ParsedArgs): Promise<void> {
|
|
const config = await loadConfig(args.config)
|
|
const client = new RevolutionaryTypedFetch(config)
|
|
|
|
const baseURL = args.base || config?.request?.baseURL
|
|
if (!baseURL) {
|
|
throw new Error('A base URL is required. Pass --base or set request.baseURL in your config file.')
|
|
}
|
|
|
|
if (args.verbose) {
|
|
console.log(`🔍 Discovering schema from ${baseURL}`)
|
|
}
|
|
|
|
await client.discover(baseURL)
|
|
|
|
const snapshotOptions: TypeSnapshotOptions = {}
|
|
if (args.out) snapshotOptions.outFile = args.out
|
|
if (args.namespace) snapshotOptions.namespace = args.namespace
|
|
if (args.banner) snapshotOptions.banner = args.banner
|
|
|
|
const code = await client.exportTypes(snapshotOptions)
|
|
if (args.out) {
|
|
if (args.verbose) {
|
|
console.log(`📄 Type snapshot written to ${resolve(args.out)}`)
|
|
} else {
|
|
console.log(`📄 Wrote ${args.out}`)
|
|
}
|
|
} else {
|
|
process.stdout.write(code)
|
|
}
|
|
}
|
|
|
|
async function loadConfig(path?: string): Promise<TypedFetchConfig | undefined> {
|
|
if (!path) return undefined
|
|
const resolved = resolve(path)
|
|
const raw = await readFile(resolved, 'utf8')
|
|
return JSON.parse(raw)
|
|
}
|
|
|
|
function printHelp(): void {
|
|
console.log(`typedfetch <command> [options]
|
|
|
|
Commands:
|
|
sync [--base <url>] [--out <file>] Discover the API and emit TypeScript definitions
|
|
help Show this help message
|
|
|
|
Options:
|
|
--base, -b Base URL to discover (falls back to config request.baseURL)
|
|
--out, -o File path for the generated declaration file (prints to stdout if omitted)
|
|
--namespace, -n Wrap declarations in a namespace
|
|
--banner Add a custom banner comment to the snapshot
|
|
--config, -c Path to a JSON config file matching TypedFetchConfig
|
|
--verbose Print progress information
|
|
`)
|
|
}
|
|
|
|
main().catch((error) => {
|
|
console.error(error instanceof Error ? error.message : error)
|
|
process.exitCode = 1
|
|
})
|