11// @ts -check
22import * as fs from 'fs' ;
3- import { TypeProcessor } from './processor.js' ;
3+ import os from 'os' ;
4+ import path from 'path' ;
45import { parseArgs } from 'util' ;
56import ts from 'typescript' ;
6- import path from 'path ' ;
7+ import { TypeProcessor } from './processor.js ' ;
78
89class DiagnosticEngine {
910 /**
10- * @param {string } level
11+ * @param {keyof typeof DiagnosticEngine.LEVELS } level
1112 */
1213 constructor ( level ) {
1314 const levelInfo = DiagnosticEngine . LEVELS [ level ] ;
@@ -73,20 +74,35 @@ class DiagnosticEngine {
7374}
7475
7576function printUsage ( ) {
76- console . error ( 'Usage: ts2swift <d.ts file path> -p <tsconfig.json path> [--global <d.ts>]... [-o output.swift]' ) ;
77+ console . error ( `Usage: ts2swift <input> [options]
78+
79+ <input> Path to a .d.ts file, or "-" to read from stdin
80+
81+ Options:
82+ -o, --output <path> Write Swift to <path>. Use "-" for stdout (default).
83+ -p, --project <path> Path to tsconfig.json (default: tsconfig.json).
84+ --global <path> Add a .d.ts as a global declaration file (repeatable).
85+ --log-level <level> One of: verbose, info, warning, error (default: info).
86+ -h, --help Show this help.
87+
88+ Examples:
89+ ts2swift lib.d.ts
90+ ts2swift lib.d.ts -o Generated.swift
91+ ts2swift lib.d.ts -p ./tsconfig.build.json -o Sources/Bridge/API.swift
92+ cat lib.d.ts | ts2swift - -o Generated.swift
93+ ts2swift lib.d.ts --global dom.d.ts --global lib.d.ts
94+ ` ) ;
7795}
7896
7997/**
8098 * Run ts2swift for a single input file (programmatic API, no process I/O).
81- * @param {string } filePath - Path to the .d.ts file
82- * @param {{ tsconfigPath: string, logLevel?: string , globalFiles?: string[] } } options
99+ * @param {string[] } filePaths - Paths to the .d.ts files
100+ * @param {{ tsconfigPath: string, logLevel?: keyof typeof DiagnosticEngine.LEVELS , globalFiles?: string[] } } options
83101 * @returns {string } Generated Swift source
84102 * @throws {Error } on parse/type-check errors (diagnostics are included in the message)
85103 */
86- export function run ( filePath , options ) {
87- const { tsconfigPath, logLevel = 'info' , globalFiles : globalFilesOpt = [ ] } = options ;
88- const globalFiles = Array . isArray ( globalFilesOpt ) ? globalFilesOpt : ( globalFilesOpt ? [ globalFilesOpt ] : [ ] ) ;
89-
104+ export function run ( filePaths , options ) {
105+ const { tsconfigPath, logLevel = 'info' , globalFiles = [ ] } = options ;
90106 const diagnosticEngine = new DiagnosticEngine ( logLevel ) ;
91107
92108 const configFile = ts . readConfigFile ( tsconfigPath , ts . sys . readFile ) ;
@@ -105,7 +121,7 @@ export function run(filePath, options) {
105121 throw new Error ( `TypeScript config/parse errors:\n${ message } ` ) ;
106122 }
107123
108- const program = TypeProcessor . createProgram ( [ filePath , ...globalFiles ] , configParseResult . options ) ;
124+ const program = TypeProcessor . createProgram ( [ ... filePaths , ...globalFiles ] , configParseResult . options ) ;
109125 const diagnostics = program . getSemanticDiagnostics ( ) ;
110126 if ( diagnostics . length > 0 ) {
111127 const message = ts . formatDiagnosticsWithColorAndContext ( diagnostics , {
@@ -131,7 +147,7 @@ export function run(filePath, options) {
131147 /** @type {string[] } */
132148 const bodies = [ ] ;
133149 const globalFileSet = new Set ( globalFiles ) ;
134- for ( const inputPath of [ filePath , ...globalFiles ] ) {
150+ for ( const inputPath of [ ... filePaths , ...globalFiles ] ) {
135151 const processor = new TypeProcessor ( program . getTypeChecker ( ) , diagnosticEngine , {
136152 defaultImportFromGlobal : globalFileSet . has ( inputPath ) ,
137153 } ) ;
@@ -169,39 +185,67 @@ export function main(args) {
169185 type : 'string' ,
170186 default : 'info' ,
171187 } ,
188+ help : {
189+ type : 'boolean' ,
190+ short : 'h' ,
191+ } ,
172192 } ,
173193 allowPositionals : true
174194 } )
175195
176- if ( options . positionals . length !== 1 ) {
196+ if ( options . values . help ) {
177197 printUsage ( ) ;
178- process . exit ( 1 ) ;
198+ process . exit ( 0 ) ;
179199 }
180200
181- const tsconfigPath = options . values . project ;
182- if ( ! tsconfigPath ) {
201+ if ( options . positionals . length !== 1 ) {
183202 printUsage ( ) ;
184203 process . exit ( 1 ) ;
185204 }
186205
187- const filePath = options . positionals [ 0 ] ;
188- const logLevel = options . values [ "log-level" ] || "info" ;
189206 /** @type {string[] } */
190- const globalFiles = Array . isArray ( options . values . global )
191- ? options . values . global
192- : ( options . values . global ? [ options . values . global ] : [ ] ) ;
207+ let filePaths = options . positionals ;
208+ /** @type {(() => void)[] } cleanup functions to run after completion */
209+ const cleanups = [ ] ;
210+
211+ if ( filePaths [ 0 ] === '-' ) {
212+ const content = fs . readFileSync ( 0 , 'utf-8' ) ;
213+ const stdinTempPath = path . join ( os . tmpdir ( ) , `ts2swift-stdin-${ process . pid } -${ Date . now ( ) } .d.ts` ) ;
214+ fs . writeFileSync ( stdinTempPath , content ) ;
215+ cleanups . push ( ( ) => fs . unlinkSync ( stdinTempPath ) ) ;
216+ filePaths = [ stdinTempPath ] ;
217+ }
218+ const logLevel = /** @type {keyof typeof DiagnosticEngine.LEVELS } */ ( ( ( ) => {
219+ const logLevel = options . values [ "log-level" ] || "info" ;
220+ if ( ! Object . keys ( DiagnosticEngine . LEVELS ) . includes ( logLevel ) ) {
221+ console . error ( `Invalid log level: ${ logLevel } . Valid levels are: ${ Object . keys ( DiagnosticEngine . LEVELS ) . join ( ", " ) } ` ) ;
222+ process . exit ( 1 ) ;
223+ }
224+ return logLevel ;
225+ } ) ( ) ) ;
226+ const globalFiles = options . values . global || [ ] ;
227+ const tsconfigPath = options . values . project || "tsconfig.json" ;
193228
194229 const diagnosticEngine = new DiagnosticEngine ( logLevel ) ;
195- diagnosticEngine . print ( "verbose" , `Processing ${ filePath } ... ` ) ;
230+ diagnosticEngine . print ( "verbose" , `Processing ${ filePaths . join ( ", " ) } ` ) ;
196231
197232 let swiftOutput ;
198233 try {
199- swiftOutput = run ( filePath , { tsconfigPath, logLevel, globalFiles } ) ;
200- } catch ( err ) {
201- console . error ( err . message ) ;
234+ swiftOutput = run ( filePaths , { tsconfigPath, logLevel, globalFiles } ) ;
235+ } catch ( /** @type {unknown } */ err ) {
236+ if ( err instanceof Error ) {
237+ diagnosticEngine . print ( "error" , err . message ) ;
238+ } else {
239+ diagnosticEngine . print ( "error" , String ( err ) ) ;
240+ }
202241 process . exit ( 1 ) ;
242+ } finally {
243+ for ( const cleanup of cleanups ) {
244+ cleanup ( ) ;
245+ }
203246 }
204- if ( options . values . output ) {
247+ // Write to file or stdout
248+ if ( options . values . output && options . values . output !== "-" ) {
205249 if ( swiftOutput . length > 0 ) {
206250 fs . mkdirSync ( path . dirname ( options . values . output ) , { recursive : true } ) ;
207251 fs . writeFileSync ( options . values . output , swiftOutput ) ;
0 commit comments