// Copyright (c) 2019 Shellyl_N and Authors // license: ISC // https://github.com/shellyln import * as fs from 'fs'; import * as path from 'path'; import { TypeAssertionMap } from '../types'; import { compile } from '../compiler'; import { serialize } from '../serializer'; import { generateTypeScriptCode, generateJsonSchema, generateProto3Code, generateGraphQlCode, generateCSharpCode } from '../codegen'; interface CliOptions { srcExt: string; destExt: string; } function compileTo(fn: (types: TypeAssertionMap) => string, srcDir: string, destDir: string, options: CliOptions) { if (! fs.existsSync(destDir)) { fs.mkdirSync(destDir, { recursive: true }); } if (fs.lstatSync(srcDir).isDirectory()) { const files = fs.readdirSync(srcDir); for (const entry of files) { const srcEntryPath = path.join(srcDir, entry); if (fs.lstatSync(srcEntryPath).isDirectory()) { compileTo(fn, srcEntryPath, path.join(destDir, entry), options); } else { if (entry.toLowerCase().endsWith(options.srcExt)) { const code = fs.readFileSync(srcEntryPath, {encoding: 'utf8'}); let trans = ''; try { const typeMap = compile(code); trans = fn(typeMap); } catch (error) { throw SyntaxError(`${srcEntryPath} - ${error.message}`); } fs.writeFileSync( path.join(destDir, entry.slice(0, -(options.srcExt.length)) + options.destExt), trans, {encoding: 'utf8'}, ); } } } } } function compileToTynderCompiled(srcDir: string, destDir: string, options: Partial) { const opts: CliOptions = Object.assign({}, { srcExt: '.tss', destExt: '.json', }, options || {}); return compileTo(serialize, srcDir, destDir, opts); } function compileToTynderCompiledAsTs(srcDir: string, destDir: string, options: Partial) { const opts: CliOptions = Object.assign({}, { srcExt: '.tss', destExt: '.ts', }, options || {}); return compileTo((types: TypeAssertionMap) => serialize(types, true), srcDir, destDir, opts); } function compileToTypeScript(srcDir: string, destDir: string, options: Partial) { const opts: CliOptions = Object.assign({}, { srcExt: '.tss', destExt: '.d.ts', }, options || {}); return compileTo(generateTypeScriptCode, srcDir, destDir, opts); } function compileToJsonSchema(srcDir: string, destDir: string, options: Partial) { const opts: CliOptions = Object.assign({}, { srcExt: '.tss', destExt: '.json', }, options || {}); return compileTo(generateJsonSchema, srcDir, destDir, opts); } function compileToJsonSchemaAsTs(srcDir: string, destDir: string, options: Partial) { const opts: CliOptions = Object.assign({}, { srcExt: '.tss', destExt: '.ts', }, options || {}); return compileTo((types: TypeAssertionMap) => generateJsonSchema(types, true), srcDir, destDir, opts); } function compileToProto3(srcDir: string, destDir: string, options: Partial) { const opts: CliOptions = Object.assign({}, { srcExt: '.tss', destExt: '.proto', }, options || {}); return compileTo(generateProto3Code, srcDir, destDir, opts); } function compileToGraphQl(srcDir: string, destDir: string, options: Partial) { const opts: CliOptions = Object.assign({}, { srcExt: '.tss', destExt: '.graphql', }, options || {}); return compileTo(generateGraphQlCode, srcDir, destDir, opts); } function compileToCSharp(srcDir: string, destDir: string, options: Partial) { const opts: CliOptions = Object.assign({}, { srcExt: '.tss', destExt: '.cs', }, options || {}); return compileTo(generateCSharpCode, srcDir, destDir, opts); } export function printHelp() { console.log( `tynder - TypeScript friendly Data validator for JavaScript. Usage: tynder subcommand options... Subcommands: help Show this help. compile Compile schema and output as JSON files. * default input file extension is *.tss * default output file extension is *.json compile-as-ts Compile schema and output as JavaScript|TypeScript files. * default input file extension is *.tss * default output file extension is *.ts Generated code is: const schema = {...}; export default schema; gen-ts Compile schema and generate TypeScript type definition files. * default input file extension is *.tss * default output file extension is *.d.ts gen-json-schema Compile schema and generate 'JSON Schema' files. * default input file extension is *.tss * default output file extension is *.json gen-json-schema-as-ts Compile schema and generate 'JSON Schema' as JavaScript|TypeScript files. * default input file extension is *.tss * default output file extension is *.ts Generated code is: const schema = {...}; export default schema; gen-csharp Compile schema and generate 'C#' type definition files. * default input file extension is *.tss * default output file extension is *.cs gen-proto3 Compile schema and generate 'Protocol Buffers 3' type definition files. * default input file extension is *.tss * default output file extension is *.proto gen-graphql Compile schema and generate 'GraphQL' type definition files. * default input file extension is *.tss * default output file extension is *.graphql Options: --indir dirname Input directory --outdir dirname Output directory --inext fileExtensionName Input files' extension --outext fileExtensionName Output files' extension ` ); } export function run(argv: string[]) { let inDir: string | null = null; let outDir: string | null = null; let inExt: string | null = null; let outExt: string | null = null; if (argv.length < 3) { printHelp(); process.exit(0); } try { for (let i = 3; i < argv.length; i++) { switch (argv[i]) { case '--indir': i++; if (argv.length <= i) { throw new Error(`Parameters are too short: ${argv[i]}.`); } inDir = argv[i]; break; case '--outdir': i++; if (argv.length <= i) { throw new Error(`Parameters are too short: ${argv[i]}.`); } outDir = argv[i]; break; case '--inext': i++; if (argv.length <= i) { throw new Error(`Parameters are too short: ${argv[i]}.`); } inExt = argv[i]; break; case '--outext': i++; if (argv.length <= i) { throw new Error(`Parameters are too short: ${argv[i]}.`); } outExt = argv[i]; break; default: throw new Error(`Unknown option: ${argv[i]}.`); } } if (! inDir) { throw new Error(`"--indir" is not set.`); } if (! outDir) { throw new Error(`"--indir" is not set.`); } const options: Partial = {}; if (inExt) { options.srcExt = inExt; } if (outExt) { options.destExt = outExt; } switch (argv[2]) { case 'compile': compileToTynderCompiled(inDir, outDir, options); break; case 'compile-as-ts': compileToTynderCompiledAsTs(inDir, outDir, options); break; case 'gen-ts': compileToTypeScript(inDir, outDir, options); break; case 'gen-json-schema': compileToJsonSchema(inDir, outDir, options); break; case 'gen-json-schema-as-ts': compileToJsonSchemaAsTs(inDir, outDir, options); break; case 'gen-proto3': compileToProto3(inDir, outDir, options); break; case 'gen-graphql': compileToGraphQl(inDir, outDir, options); break; case 'gen-csharp': compileToCSharp(inDir, outDir, options); break; case 'help': printHelp(); process.exit(0); default: throw new Error(`Unknown subcommand: ${argv[0]}.`); } } catch (e) { console.error(e.message); printHelp(); process.exit(-1); } }