|
1 | 1 | #!/usr/bin/env bun
|
2 | 2 |
|
3 |
| -import type { DtsGenerationOption } from './types' |
| 3 | +import type { DtsGenerationConfig } from './types' |
4 | 4 | import { resolve } from 'node:path'
|
5 | 5 | import process from 'node:process'
|
6 |
| -import { parseArgs } from 'node:util' |
| 6 | +import { existsSync } from 'node:fs' |
| 7 | +import { CAC } from 'cac' |
7 | 8 | import { config as defaultConfig } from './config'
|
8 | 9 | import { generate } from './generator'
|
| 10 | +import { version } from '../package.json' |
9 | 11 |
|
10 |
| -// Parse command line arguments |
11 |
| -const { values, positionals } = parseArgs({ |
12 |
| - args: Bun.argv, |
13 |
| - options: { |
14 |
| - 'help': { |
15 |
| - type: 'boolean', |
16 |
| - short: 'h', |
17 |
| - }, |
18 |
| - 'version': { |
19 |
| - type: 'boolean', |
20 |
| - short: 'v', |
21 |
| - }, |
22 |
| - 'root': { |
23 |
| - type: 'string', |
24 |
| - short: 'r', |
25 |
| - }, |
26 |
| - 'outdir': { |
27 |
| - type: 'string', |
28 |
| - short: 'o', |
29 |
| - }, |
30 |
| - 'clean': { |
31 |
| - type: 'boolean', |
32 |
| - short: 'c', |
33 |
| - }, |
34 |
| - 'keep-comments': { |
35 |
| - type: 'boolean', |
36 |
| - }, |
37 |
| - 'tsconfig': { |
38 |
| - type: 'string', |
39 |
| - }, |
40 |
| - 'verbose': { |
41 |
| - type: 'boolean', |
42 |
| - }, |
43 |
| - 'output-structure': { |
44 |
| - type: 'string', |
45 |
| - }, |
46 |
| - }, |
47 |
| - allowPositionals: true, |
48 |
| -}) |
49 |
| - |
50 |
| -// Show help |
51 |
| -if (values.help) { |
52 |
| - console.log(` |
53 |
| -dtsx - A modern, fast .d.ts generation tool |
54 |
| -
|
55 |
| -Usage: |
56 |
| - dtsx [options] [entrypoints...] |
57 |
| -
|
58 |
| -Options: |
59 |
| - -h, --help Show this help message |
60 |
| - -v, --version Show version |
61 |
| - -r, --root <dir> Root directory (default: ./src) |
62 |
| - -o, --outdir <dir> Output directory (default: ./dist) |
63 |
| - -c, --clean Clean output directory before generation |
64 |
| - --keep-comments Keep comments in output (default: true) |
65 |
| - --tsconfig <path> Path to tsconfig.json |
66 |
| - --verbose Verbose output |
67 |
| - --output-structure Output structure: 'mirror' or 'flat' (default: mirror) |
68 |
| -
|
69 |
| -Examples: |
70 |
| - dtsx Generate .d.ts files for all .ts files in src/ |
71 |
| - dtsx -r lib -o types Generate from lib/ to types/ |
72 |
| - dtsx src/index.ts Generate only for specific file |
73 |
| -`) |
74 |
| - process.exit(0) |
| 12 | +// Validate and resolve paths |
| 13 | +function validateAndResolvePath(path: string, description: string): string { |
| 14 | + const resolved = resolve(process.cwd(), path) |
| 15 | + |
| 16 | + // For tsconfig, check if file exists |
| 17 | + if (description.includes('tsconfig') && !existsSync(resolved)) { |
| 18 | + console.warn(`Warning: ${description} not found at ${resolved}`) |
| 19 | + } |
| 20 | + |
| 21 | + return resolved |
75 | 22 | }
|
76 | 23 |
|
77 |
| -// Show version |
78 |
| -if (values.version) { |
79 |
| - const pkg = await import('../package.json') |
80 |
| - console.log(pkg.version) |
81 |
| - process.exit(0) |
| 24 | +// Parse entrypoints from string or array |
| 25 | +function parseEntrypoints(entrypoints: string | string[] | undefined): string[] { |
| 26 | + if (!entrypoints) { |
| 27 | + return defaultConfig.entrypoints |
| 28 | + } |
| 29 | + |
| 30 | + if (typeof entrypoints === 'string') { |
| 31 | + return entrypoints.split(',').map(e => e.trim()).filter(Boolean) |
| 32 | + } |
| 33 | + |
| 34 | + return Array.isArray(entrypoints) ? entrypoints : [entrypoints] |
82 | 35 | }
|
83 | 36 |
|
84 |
| -// Build configuration |
85 |
| -const options: DtsGenerationOption = { |
86 |
| - root: values.root || defaultConfig.root, |
87 |
| - outdir: values.outdir || defaultConfig.outdir, |
88 |
| - clean: values.clean ?? defaultConfig.clean, |
89 |
| - keepComments: values['keep-comments'] ?? defaultConfig.keepComments, |
90 |
| - tsconfigPath: values.tsconfig || defaultConfig.tsconfigPath, |
91 |
| - verbose: values.verbose ?? defaultConfig.verbose, |
92 |
| - outputStructure: (values['output-structure'] as 'mirror' | 'flat') || defaultConfig.outputStructure, |
93 |
| -} |
| 37 | +// Main CLI setup |
| 38 | +async function setupCLI() { |
| 39 | + const cli = new CAC('dtsx') |
| 40 | + |
| 41 | + // Set version |
| 42 | + cli.version(version) |
| 43 | + |
| 44 | + // Default command (generate) |
| 45 | + cli |
| 46 | + .command('[entrypoints...]', 'Generate TypeScript declaration files', { |
| 47 | + allowUnknownOptions: false, |
| 48 | + }) |
| 49 | + .option('-r, --root <dir>', 'Root directory to scan for TypeScript files', { |
| 50 | + default: defaultConfig.root, |
| 51 | + }) |
| 52 | + .option('-o, --outdir <dir>', 'Output directory for generated .d.ts files', { |
| 53 | + default: defaultConfig.outdir, |
| 54 | + }) |
| 55 | + .option('-c, --clean', 'Clean output directory before generation', { |
| 56 | + default: defaultConfig.clean, |
| 57 | + }) |
| 58 | + .option('--keep-comments [value]', 'Keep comments in generated .d.ts files (default: true)', { |
| 59 | + default: defaultConfig.keepComments, |
| 60 | + }) |
| 61 | + .option('--tsconfig <path>', 'Path to tsconfig.json file', { |
| 62 | + default: defaultConfig.tsconfigPath, |
| 63 | + }) |
| 64 | + .option('--verbose', 'Enable verbose logging', { |
| 65 | + default: defaultConfig.verbose, |
| 66 | + }) |
| 67 | + .option('--output-structure <type>', 'Output structure: "mirror" or "flat"', { |
| 68 | + default: defaultConfig.outputStructure || 'mirror', |
| 69 | + }) |
| 70 | + .example('dtsx') |
| 71 | + .example('dtsx src/index.ts src/utils.ts') |
| 72 | + .example('dtsx -r lib -o types --clean') |
| 73 | + .example('dtsx --keep-comments=false --verbose') |
| 74 | + .example('dtsx "src/**/*.ts" --output-structure flat') |
| 75 | + .action(async (entrypoints: string[], options: any) => { |
| 76 | + try { |
| 77 | + // Handle comment options |
| 78 | + let keepComments = defaultConfig.keepComments |
| 79 | + if (options.keepComments !== undefined) { |
| 80 | + // Handle --keep-comments=false or --keep-comments false |
| 81 | + if (options.keepComments === 'false' || options.keepComments === false) { |
| 82 | + keepComments = false |
| 83 | + } else if (options.keepComments === 'true' || options.keepComments === true) { |
| 84 | + keepComments = true |
| 85 | + } |
| 86 | + } |
| 87 | + |
| 88 | + // Validate output structure |
| 89 | + const validStructures = ['mirror', 'flat'] |
| 90 | + if (options.outputStructure && !validStructures.includes(options.outputStructure)) { |
| 91 | + console.error(`Error: Invalid output structure "${options.outputStructure}". Must be one of: ${validStructures.join(', ')}`) |
| 92 | + process.exit(1) |
| 93 | + } |
| 94 | + |
| 95 | + // Build configuration |
| 96 | + const finalEntrypoints = entrypoints.length > 0 ? entrypoints : parseEntrypoints(defaultConfig.entrypoints) |
| 97 | + const config: DtsGenerationConfig = { |
| 98 | + cwd: process.cwd(), |
| 99 | + root: validateAndResolvePath(options.root, 'root directory'), |
| 100 | + entrypoints: Array.isArray(finalEntrypoints) ? finalEntrypoints : [finalEntrypoints], |
| 101 | + outdir: validateAndResolvePath(options.outdir, 'output directory'), |
| 102 | + keepComments, |
| 103 | + clean: options.clean, |
| 104 | + tsconfigPath: validateAndResolvePath(options.tsconfig, 'tsconfig.json'), |
| 105 | + verbose: options.verbose, |
| 106 | + outputStructure: options.outputStructure as 'mirror' | 'flat', |
| 107 | + } |
| 108 | + |
| 109 | + // Show configuration if verbose |
| 110 | + if (config.verbose) { |
| 111 | + console.log('🔧 Configuration:') |
| 112 | + console.log(` Root: ${config.root}`) |
| 113 | + console.log(` Output: ${config.outdir}`) |
| 114 | + console.log(` Entrypoints: ${config.entrypoints.join(', ')}`) |
| 115 | + console.log(` Keep comments: ${config.keepComments}`) |
| 116 | + console.log(` Clean: ${config.clean}`) |
| 117 | + console.log(` Output structure: ${config.outputStructure}`) |
| 118 | + console.log(` TSConfig: ${config.tsconfigPath}`) |
| 119 | + console.log('') |
| 120 | + } |
| 121 | + |
| 122 | + // Run generation |
| 123 | + await generate(config) |
| 124 | + |
| 125 | + if (!config.verbose) { |
| 126 | + console.log('✅ DTS generation completed successfully!') |
| 127 | + } |
| 128 | + } catch (error) { |
| 129 | + console.error('❌ Error generating .d.ts files:', error) |
| 130 | + process.exit(1) |
| 131 | + } |
| 132 | + }) |
| 133 | + |
| 134 | + // Explicit generate command for compatibility |
| 135 | + cli |
| 136 | + .command('generate [entrypoints...]', 'Generate TypeScript declaration files') |
| 137 | + .option('--cwd <path>', 'Current working directory', { |
| 138 | + default: process.cwd(), |
| 139 | + }) |
| 140 | + .option('-r, --root <path>', 'Root directory to scan for TypeScript files', { |
| 141 | + default: defaultConfig.root, |
| 142 | + }) |
| 143 | + .option('-o, --outdir <path>', 'Output directory for generated .d.ts files', { |
| 144 | + default: defaultConfig.outdir, |
| 145 | + }) |
| 146 | + .option('-c, --clean', 'Clean output directory before generation', { |
| 147 | + default: defaultConfig.clean, |
| 148 | + }) |
| 149 | + .option('--keep-comments [value]', 'Keep comments in generated .d.ts files (default: true)', { |
| 150 | + default: defaultConfig.keepComments, |
| 151 | + }) |
| 152 | + .option('--tsconfig <path>', 'Path to tsconfig.json file', { |
| 153 | + default: defaultConfig.tsconfigPath, |
| 154 | + }) |
| 155 | + .option('--verbose', 'Enable verbose logging', { |
| 156 | + default: defaultConfig.verbose, |
| 157 | + }) |
| 158 | + .option('--output-structure <type>', 'Output structure: "mirror" or "flat"', { |
| 159 | + default: defaultConfig.outputStructure || 'mirror', |
| 160 | + }) |
| 161 | + .example('dtsx generate') |
| 162 | + .example('dtsx generate src/index.ts --outdir dist/types') |
| 163 | + .example('dtsx generate --keep-comments=false --verbose') |
| 164 | + .action(async (entrypoints: string[], options: any) => { |
| 165 | + try { |
| 166 | + // Handle comment options |
| 167 | + let keepComments = defaultConfig.keepComments |
| 168 | + if (options.keepComments !== undefined) { |
| 169 | + // Handle --keep-comments=false or --keep-comments false |
| 170 | + if (options.keepComments === 'false' || options.keepComments === false) { |
| 171 | + keepComments = false |
| 172 | + } else if (options.keepComments === 'true' || options.keepComments === true) { |
| 173 | + keepComments = true |
| 174 | + } |
| 175 | + } |
94 | 176 |
|
95 |
| -// Handle entrypoints |
96 |
| -if (positionals.length > 2) { // First two are bun and script path |
97 |
| - const entrypoints = positionals.slice(2) |
98 |
| - options.entrypoints = entrypoints.map((e) => { |
99 |
| - // If it's a file path, convert to glob pattern relative to root |
100 |
| - if (e.endsWith('.ts')) { |
101 |
| - const relativePath = resolve(process.cwd(), e) |
102 |
| - return relativePath |
103 |
| - } |
104 |
| - return e |
| 177 | + // Validate output structure |
| 178 | + const validStructures = ['mirror', 'flat'] |
| 179 | + if (options.outputStructure && !validStructures.includes(options.outputStructure)) { |
| 180 | + console.error(`Error: Invalid output structure "${options.outputStructure}". Must be one of: ${validStructures.join(', ')}`) |
| 181 | + process.exit(1) |
| 182 | + } |
| 183 | + |
| 184 | + // Build configuration |
| 185 | + const finalEntrypoints = entrypoints.length > 0 ? entrypoints : parseEntrypoints(defaultConfig.entrypoints) |
| 186 | + const config: DtsGenerationConfig = { |
| 187 | + cwd: validateAndResolvePath(options.cwd, 'working directory'), |
| 188 | + root: validateAndResolvePath(options.root, 'root directory'), |
| 189 | + entrypoints: Array.isArray(finalEntrypoints) ? finalEntrypoints : [finalEntrypoints], |
| 190 | + outdir: validateAndResolvePath(options.outdir, 'output directory'), |
| 191 | + keepComments, |
| 192 | + clean: options.clean, |
| 193 | + tsconfigPath: validateAndResolvePath(options.tsconfig, 'tsconfig.json'), |
| 194 | + verbose: options.verbose, |
| 195 | + outputStructure: options.outputStructure as 'mirror' | 'flat', |
| 196 | + } |
| 197 | + |
| 198 | + // Show configuration if verbose |
| 199 | + if (config.verbose) { |
| 200 | + console.log('🔧 Configuration:') |
| 201 | + console.log(` CWD: ${config.cwd}`) |
| 202 | + console.log(` Root: ${config.root}`) |
| 203 | + console.log(` Output: ${config.outdir}`) |
| 204 | + console.log(` Entrypoints: ${config.entrypoints.join(', ')}`) |
| 205 | + console.log(` Keep comments: ${config.keepComments}`) |
| 206 | + console.log(` Clean: ${config.clean}`) |
| 207 | + console.log(` Output structure: ${config.outputStructure}`) |
| 208 | + console.log(` TSConfig: ${config.tsconfigPath}`) |
| 209 | + console.log('') |
| 210 | + } |
| 211 | + |
| 212 | + // Run generation |
| 213 | + await generate(config) |
| 214 | + |
| 215 | + if (!config.verbose) { |
| 216 | + console.log('✅ DTS generation completed successfully!') |
| 217 | + } |
| 218 | + } catch (error) { |
| 219 | + console.error('❌ Error generating .d.ts files:', error) |
| 220 | + process.exit(1) |
| 221 | + } |
| 222 | + }) |
| 223 | + |
| 224 | + // Version command |
| 225 | + cli |
| 226 | + .command('version', 'Show version information') |
| 227 | + .action(() => { |
| 228 | + console.log(`dtsx v${version}`) |
| 229 | + console.log('A modern, fast TypeScript declaration file generator') |
| 230 | + }) |
| 231 | + |
| 232 | + // Help customization |
| 233 | + cli.help((sections) => { |
| 234 | + sections.splice(1, 0, { |
| 235 | + title: 'About', |
| 236 | + body: 'dtsx is a modern, fast .d.ts generation tool that preserves comments and provides excellent TypeScript support.', |
| 237 | + }) |
105 | 238 | })
|
106 |
| -} |
107 |
| -else { |
108 |
| - options.entrypoints = defaultConfig.entrypoints |
| 239 | + |
| 240 | + return cli |
109 | 241 | }
|
110 | 242 |
|
111 |
| -// Run generation |
112 |
| -try { |
113 |
| - await generate(options) |
| 243 | +// Run CLI |
| 244 | +async function main() { |
| 245 | + try { |
| 246 | + const cli = await setupCLI() |
| 247 | + cli.parse() |
| 248 | + } catch (error) { |
| 249 | + console.error('❌ CLI setup error:', error) |
| 250 | + process.exit(1) |
| 251 | + } |
114 | 252 | }
|
115 |
| -catch (error) { |
116 |
| - console.error('Error generating .d.ts files:', error) |
117 |
| - process.exit(1) |
| 253 | + |
| 254 | +// Only run if this file is executed directly |
| 255 | +if (import.meta.main) { |
| 256 | + main() |
118 | 257 | }
|
0 commit comments