Skip to content

Commit 4101fe0

Browse files
committed
chore: wip
1 parent 71291fd commit 4101fe0

File tree

1 file changed

+240
-101
lines changed

1 file changed

+240
-101
lines changed

src/cli.ts

Lines changed: 240 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -1,118 +1,257 @@
11
#!/usr/bin/env bun
22

3-
import type { DtsGenerationOption } from './types'
3+
import type { DtsGenerationConfig } from './types'
44
import { resolve } from 'node:path'
55
import process from 'node:process'
6-
import { parseArgs } from 'node:util'
6+
import { existsSync } from 'node:fs'
7+
import { CAC } from 'cac'
78
import { config as defaultConfig } from './config'
89
import { generate } from './generator'
10+
import { version } from '../package.json'
911

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
7522
}
7623

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]
8235
}
8336

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+
}
94176

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+
})
105238
})
106-
}
107-
else {
108-
options.entrypoints = defaultConfig.entrypoints
239+
240+
return cli
109241
}
110242

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+
}
114252
}
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()
118257
}

0 commit comments

Comments
 (0)