diff --git a/src/executable/TypiaGenerateWizard.ts b/src/executable/TypiaGenerateWizard.ts index a12c737f9d..47e8944b18 100644 --- a/src/executable/TypiaGenerateWizard.ts +++ b/src/executable/TypiaGenerateWizard.ts @@ -24,6 +24,7 @@ export namespace TypiaGenerateWizard { action, ) => { // PREPARE ASSETS + command.argument("[files...]", "input .ts files (alternative to --input)"); command.option("--input [path]", "input directory"); command.option("--output [directory]", "output directory"); command.option("--project [project]", "tsconfig.json file location"); @@ -68,7 +69,11 @@ export namespace TypiaGenerateWizard { }; return action(async (options) => { - options.input ??= await input("input")("input directory"); + // If files are provided, input directory is not required + const hasFiles = options.files && options.files.length > 0; + if (!hasFiles) { + options.input ??= await input("input")("input directory"); + } options.output ??= await input("output")("output directory"); options.project ??= await configure(); return options as IArguments; @@ -79,5 +84,6 @@ export namespace TypiaGenerateWizard { input: string; output: string; project: string; + files?: string[]; } } diff --git a/src/executable/setup/ArgumentParser.ts b/src/executable/setup/ArgumentParser.ts index f72cf92358..ccd154f14d 100644 --- a/src/executable/setup/ArgumentParser.ts +++ b/src/executable/setup/ArgumentParser.ts @@ -23,8 +23,15 @@ export namespace ArgumentParser { // TAKE OPTIONS const action = (closure: (options: Partial) => Promise) => new Promise((resolve, reject) => { - commander.program.action(async (options) => { + commander.program.action(async (...args: unknown[]) => { try { + // Commander passes: (positionalArgs..., options, command) + // Use program.opts() for reliable option extraction + const options = commander.program.opts() as Partial; + // If there are positional arguments (files), attach them to options + if (args.length > 0 && Array.isArray(args[0])) { + (options as Partial & { files?: string[] }).files = args[0]; + } resolve(await closure(options)); } catch (exp) { reject(exp); diff --git a/src/programmers/TypiaProgrammer.ts b/src/programmers/TypiaProgrammer.ts index 59cf68de62..f986766ddc 100644 --- a/src/programmers/TypiaProgrammer.ts +++ b/src/programmers/TypiaProgrammer.ts @@ -11,19 +11,28 @@ export namespace TypiaProgrammer { input: string; output: string; project: string; + files?: string[]; } export const build = async ( location: TypiaProgrammer.ILocation, ): Promise => { - location.input = path.resolve(location.input); + const hasFiles = location.files && location.files.length > 0; + + // Resolve paths + if (!hasFiles) { + location.input = path.resolve(location.input); + } location.output = path.resolve(location.output); - if ((await is_directory(location.input)) === false) - throw new URIError( - "Error on TypiaGenerator.generate(): input path is not a directory.", - ); - else if (fs.existsSync(location.output) === false) + // Validate directories + if (!hasFiles) { + if ((await is_directory(location.input)) === false) + throw new URIError( + "Error on TypiaGenerator.generate(): input path is not a directory.", + ); + } + if (fs.existsSync(location.output) === false) await fs.promises.mkdir(location.output, { recursive: true }); else if ((await is_directory(location.output)) === false) { const parent: string = path.join(location.output, ".."); @@ -34,6 +43,11 @@ export namespace TypiaProgrammer { await fs.promises.mkdir(location.output); } + // Resolve file paths + const resolvedFiles = hasFiles + ? location.files!.map((f) => path.resolve(f)) + : []; + // CREATE PROGRAM const { options: compilerOptions } = ts.parseJsonConfigFileContent( ts.readConfigFile(location.project, ts.sys.readFile).config, @@ -47,16 +61,18 @@ export namespace TypiaProgrammer { ); const program: ts.Program = ts.createProgram( - await (async () => { - const container: string[] = []; - await gather({ - location, - container, - from: location.input, - to: location.output, - }); - return container; - })(), + hasFiles + ? resolvedFiles + : await (async () => { + const container: string[] = []; + await gather({ + location, + container, + from: location.input, + to: location.output, + }); + return container; + })(), compilerOptions, ); @@ -65,16 +81,23 @@ export namespace TypiaProgrammer { const result: ts.TransformationResult = ts.transform( program .getSourceFiles() - .filter( - (file) => - !file.isDeclarationFile && - path.resolve(file.fileName).indexOf(location.input) !== -1, - ), - [ - ImportTransformer.transform({ - from: location.input, - to: location.output, + .filter((file) => { + if (file.isDeclarationFile) return false; + const resolved = path.resolve(file.fileName); + return hasFiles + ? resolvedFiles.includes(resolved) + : resolved.indexOf(location.input) !== -1; }), + [ + // Skip import transformation when processing individual files + ...(hasFiles + ? [] + : [ + ImportTransformer.transform({ + from: location.input, + to: location.output, + }), + ]), transform( program, ((compilerOptions.plugins as any[]) ?? []).find( @@ -125,9 +148,9 @@ export namespace TypiaProgrammer { newLine: ts.NewLineKind.LineFeed, }); for (const file of result.transformed) { - const to: string = path - .resolve(file.fileName) - .replace(location.input, location.output); + const to: string = hasFiles + ? path.join(location.output, path.basename(file.fileName)) + : path.resolve(file.fileName).replace(location.input, location.output); const content: string = printer.printFile(file); await fs.promises.writeFile(to, content, "utf8");