UNPKG

pubnub

Version:

Publish & Subscribe Real-time Messaging with PubNub

827 lines (710 loc) 31.4 kB
import { program } from 'commander'; import * as ts from 'typescript'; import prettier from 'prettier'; import * as fs from 'fs'; import path from 'path'; // -------------------------------------------------------- // ------------------------ Types ------------------------- // -------------------------------------------------------- // region Types type BaseSourceFileImport = { /** Imported type name. */ value: string; /** Name or path to the module from which type imported. */ module: string; /** Import statement. */ statement: string; /** Whether this is package type or not. */ isPackageType: boolean; }; /** Import created with type name and module path. */ type NamedSourceFileImport = BaseSourceFileImport & { /** Import type. */ type: 'name'; }; /** Import created as alias (namespace) to the `*` and module path. */ type NamespacedSourceFileImport = BaseSourceFileImport & { /** Import type. */ type: 'namespace'; }; /** Import created with type name, name as it imported into the code and module path. */ type AliasedSourceFileImport = BaseSourceFileImport & { /** Import type. */ type: 'alias'; /** Imported type alias name. */ alias: string; }; /** Package types imported in source file. */ type SourceFileImport = NamedSourceFileImport | NamespacedSourceFileImport | AliasedSourceFileImport; // endregion // -------------------------------------------------------- // ---------------------- Helpers ------------------------- // -------------------------------------------------------- // region Helpers /** * Load target project TS configuration. * * @param configurationPath - Path to the configuration file to load. * * @returns Parsed TS configuration file object. */ const loadTSConfiguration = (configurationPath: string) => { const configFile = ts.readConfigFile(configurationPath, ts.sys.readFile); if (configFile.error) { throw new Error(`${path.basename(configurationPath)} load error: ${configFile.error.messageText}`); } return ts.parseJsonConfigFileContent(configFile.config, ts.sys, path.dirname(configurationPath)); }; /** * Normalize path of resource relative to the file where it used. * * @param workingDirectory - Full path to the working directory. * @param resourcePath - Source from which another resource accessed relatively. * @param resourceRelativePath - Path to the target resource which is relative to the source. * * @returns Path to the target resource which is relative to the working directory. */ const relativeToWorkingDirectoryPath = ( workingDirectory: string, resourcePath: string, resourceRelativePath: string, ) => { const resourceFullPath = path.resolve(path.dirname(resourcePath), resourceRelativePath); return `.${resourceFullPath.split(workingDirectory).pop()}`; }; // endregion /** * Project package. * * Package used to aggregate definition files in the working directory. */ class Package { /** Package source files with types. */ private readonly files: SourceFile[] = []; /** * Create package bundle. * * Create package object which contains information about types used in public interface. * * @param name - Project name. * @param workingDirectory - Root folder with subfolders which represent project structure and contains types definition files. * @param tsConfiguration - Loaded project's TS configuration */ constructor( public readonly name: string, private readonly workingDirectory: string, private readonly tsConfiguration: ts.ParsedCommandLine, ) { this.processTypesInDirectory(workingDirectory); } /** * Retrieve list of type definition files identified for package. * * @returns List of type definition {@link SourceFile} instances. */ get typeSourceFiles(): SourceFile[] { return this.files; } /** * Retrieve list of all external imports used in the project. * * @returns Map of imported module to the module information object. */ get externalImports(): Record<string, SourceFileImport> { const imports: Record<string, SourceFileImport> = {}; this.files.forEach((file) => Object.entries(file.externalImports).forEach(([module, type]) => (imports[module] ??= type)), ); return imports; } /** * Retrieve source file information for file by its path. * * @param filePath - Path to the inside of the project. * * @returns SourceFile instance or `undefined` if it can't be found at specified path. */ sourceFileAtPath(filePath: string): SourceFile | undefined { if (filePath.startsWith('./') || filePath.startsWith('../')) filePath = path.resolve(this.workingDirectory, filePath); return this.files.find((file) => file.filePath === filePath); } /** * Process types in specified directory. * * @param directory - Directory inside which types and subfolder should be processed. */ private processTypesInDirectory(directory: string) { if (!fs.existsSync(directory)) return; fs.readdirSync(directory).forEach((pathComponent) => { const resourcePath = path.join(directory, pathComponent); if (fs.statSync(resourcePath).isDirectory()) this.processTypesInDirectory(resourcePath); else { // TODO: IGNORE FILES IN A DIFFERENT WAY if (pathComponent === 'hmac-sha256.d.ts') return; if (!fs.existsSync(resourcePath)) return; // Filter out internal type definition placeholders. const fileContent = fs.readFileSync(resourcePath, 'utf8'); const internalModuleMatch = fileContent.match(/^\/\*\*[\s\S]*?@internal[\s\S]*?\*\//); if (internalModuleMatch && internalModuleMatch.index !== undefined && internalModuleMatch.index === 0) return; this.files.push(new SourceFile(resourcePath, this.workingDirectory, this.tsConfiguration.options, this)); } }); // Gather type aliases. const aliases: Record<string, string[]> = {}; this.files.forEach((file) => { file.imports .filter((typeImport) => typeImport.isPackageType && typeImport.type === 'alias') .forEach((typeImport) => { const { value, alias } = typeImport as AliasedSourceFileImport; (aliases[value] ??= []).push(alias); }); }); this.files.forEach((file) => { file.types .filter((type) => Object.keys(aliases).includes(type.name)) .forEach((type) => aliases[type.name].forEach((alias) => type.addAlias(alias))); }); } } /** * Type definition file. * * Object contain information about types and imports declared in it. */ class SourceFile { /** List of package type imports in source file */ private readonly _imports: SourceFileImport[] = []; private readonly _types: TypeDefinition[] = []; /** * Create a source file from type definition file in the package. * * @param filePath - Path to the type definition file which will be analyzed. * @param workingDirectory - Root folder with subfolders which represent project structure and contains types definition files. * @param tsCompileOptions - Package's TS parsed configuration object. * @param projectPackage - Project with processed files information. */ constructor( public readonly filePath: string, private readonly workingDirectory: string, private readonly tsCompileOptions: ts.CompilerOptions, readonly projectPackage: Package, ) { const source = this.tsSourceFile(); this.processImports(source); this.processTypes(source); } /** * Retrieve list of imported types and modules. * * @returns List of import description objects with details about type and source file location. */ get imports() { return this._imports; } /** * Retrieve list of types imported from external dependencies. * * @returns List of import description objects with details about type and source file location. */ get externalImports() { const imports: Record<string, SourceFileImport> = {}; this._imports .filter((importedType) => !importedType.isPackageType) .forEach((importedType) => { imports[importedType.value] = importedType; }); return imports; } /** * Retrieve list of types declared in this source file. * * @returns List of pre-processed type definition object instances. */ get types() { return this._types; } /** * Retrieve type definition by its name. * * @param typeName - Name of the type for which type definition should be found. * * @returns Type definition object instance or `undefined` if specified type name is not part of source file. */ typeByName(typeName: string) { return this._types.find((type) => type.name === typeName); } /** * Analyze source file imports for easier types processing during type definition files merge. * * @param sourceFile - TypeScript SourceFile object with pre-processed type definition file content. */ private processImports(sourceFile: ts.SourceFile) { const storeImport = ( type: SourceFileImport['type'], module: string, statement: string, value: string, alias?: string, ) => { const isPackageType = module.startsWith('./') || module.startsWith('../'); if (isPackageType) module = relativeToWorkingDirectoryPath(this.workingDirectory, this.filePath, module); if (type !== 'alias') this._imports.push({ type, value, module, statement, isPackageType }); else if (alias) this._imports.push({ type, value, module, statement, isPackageType, alias }); else throw new Error('Alias is required for alias import'); }; ts.forEachChild(sourceFile, (node) => { if (!ts.isImportDeclaration(node)) return; const { importClause } = node; if (!importClause) return; const moduleName = node.moduleSpecifier.getText(sourceFile).replace(/['"]/g, ''); const statement = node.getText(sourceFile); // Process simple named import (import type specified with default export). if (importClause.name) storeImport('name', moduleName, statement, importClause.name.getText(sourceFile)); // Check whether there is named binding specified for import or not. const { namedBindings } = importClause; if (!namedBindings) return; if (ts.isNamedImports(namedBindings)) { namedBindings.elements.forEach((element) => { const alias = element.name.getText(sourceFile); const name = element.propertyName ? element.propertyName.getText(sourceFile) : alias; if (name === alias) storeImport('name', moduleName, statement, name); else storeImport('alias', moduleName, statement, name, alias); }); } else if (ts.isNamespaceImport(namedBindings) && namedBindings.name) { storeImport('namespace', moduleName, statement, namedBindings.name.getText(sourceFile)); } }); } /** * Analyze source file types for easier types processing during type definition files merge. * * @param sourceFile - TypeScript SourceFile object with pre-processed type definition file content. */ private processTypes(sourceFile: ts.SourceFile) { ts.forEachChild(sourceFile, (node) => { if (!ts.isDeclarationStatement(node)) return; if ( !ts.isClassDeclaration(node) && !ts.isInterfaceDeclaration(node) && !ts.isTypeAliasDeclaration(node) && !ts.isEnumDeclaration(node) ) return; if (!node.name) return; // Stringify node type. let type: TypeDefinition['type'] = 'type'; if (ts.isClassDeclaration(node)) type = 'class'; else if (ts.isInterfaceDeclaration(node)) type = 'interface'; else if (ts.isEnumDeclaration(node)) type = 'enum'; // Extract type documentation. const jsDocComments = ts.getJSDocCommentsAndTags(node); const documentation = jsDocComments.map((comment) => comment.getText(node.getSourceFile())).join('\n'); this._types.push( new TypeDefinition( node.name.getText(sourceFile), type, this.tsSourceFile(`${documentation}\n${node.getText(sourceFile)}`), this, ), ); }); } /** * Create TypeScript source file with same path as instance but potentially different content (if passed). * * @param content - Content which should be used for source file instance instead of current file content. * @private */ private tsSourceFile(content?: string) { content ??= fs.readFileSync(this.filePath, 'utf8'); // Ensure that `default export` will be carried with type definition to the resulting definition type. if (content && /^export default\s[a-zA-Z]+;/gm.test(content)) { const matchType = /^export default\s([a-zA-Z]+);/gm.exec(content); if (matchType && matchType.length > 0) { const replaceRegexp = new RegExp(`^((declare\\s)?(enum|class|type|interface)\\s${matchType[1]})`, 'gm'); content = content.replace(replaceRegexp, `export $1`); } } return ts.createSourceFile(this.filePath, content, this.tsCompileOptions.target ?? ts.ScriptTarget.ES2015, true); } } /** * Type definition from the source file. */ class TypeDefinition { public readonly superclass?: string; /** List of aliases under which type has been imported by other types. */ private readonly aliases: string[] = []; constructor( public readonly name: string, public type: 'class' | 'interface' | 'type' | 'enum', private readonly sourceFile: ts.SourceFile, private readonly projectSourceFile: SourceFile, ) { if (type === 'class') { const match = sourceFile.getText(this.sourceFile).match(/class PubNub extends ([a-zA-Z]+)[\s|<]/) ?? []; if (match.length > 1) this.superclass = match[1]; } } get filePath() { return this.sourceFile.fileName; } addAlias(alias: string) { if (!this.aliases.includes(alias)) this.aliases.push(alias); } toString(packageTypes: string[], namespace?: string): string { const isPackageType = (type: string) => { if (type.indexOf('.') === -1) return packageTypes.includes(type); else return packageTypes.includes(type.split('.').pop()!); }; const typeReferenceTransformer = <T extends ts.Node>(context: ts.TransformationContext) => { const visit: ts.Visitor = (node) => { let replacement: ts.TypeReferenceNode | ts.ExpressionWithTypeArguments | undefined; if (namespace && ts.isTypeReferenceNode(node) && isPackageType(node.typeName.getText(this.sourceFile))) { const typeName = node.typeName.getText(this.sourceFile); if (namespace) { const reference = ts.factory.createQualifiedName(ts.factory.createIdentifier(namespace), typeName); replacement = ts.factory.createTypeReferenceNode( reference, node.typeArguments ?.map((typeArg) => ts.visitNode(typeArg, visit)) .filter((typeArg): typeArg is ts.TypeNode => typeArg !== undefined), ); } } else if (!namespace && ts.isTypeReferenceNode(node)) { const typeName = node.typeName.getText(this.sourceFile); let typeNamespace = namespace; this.projectSourceFile.imports.forEach((importType) => { if (importType.type === 'alias' && importType.alias === typeName && !typeNamespace) typeNamespace = 'PubNub'; }); if (typeNamespace) { const reference = ts.factory.createQualifiedName(ts.factory.createIdentifier(typeNamespace), typeName); replacement = ts.factory.createTypeReferenceNode( reference, node.typeArguments ?.map((typeArg) => ts.visitNode(typeArg, visit)) .filter((typeArg): typeArg is ts.TypeNode => typeArg !== undefined), ); } } if (ts.isTypeQueryNode(node)) { if (namespace) { const qualifiedName = ts.factory.createQualifiedName( ts.factory.createIdentifier(namespace), node.exprName as ts.Identifier, ); return ts.factory.createTypeQueryNode(qualifiedName); } } // Checking whether processing superclass information or not. if (node.parent && ts.isHeritageClause(node.parent) && ts.isExpressionWithTypeArguments(node)) { const typeName = node.expression.getText(this.sourceFile); if (namespace) { let reference = node.expression; if (this.superclass !== typeName) { reference = ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier(namespace), typeName); } replacement = ts.factory.createExpressionWithTypeArguments( reference, node.typeArguments ?.map((typeArg) => ts.visitNode(typeArg, visit)) .filter((typeArg): typeArg is ts.TypeNode => typeArg !== undefined), ); } } return replacement ?? ts.visitEachChild(node, visit, context); }; return (node: T): T => ts.visitNode(node, visit) as T; }; const aliasedTypeStrings: string[] = []; if (this.aliases.length > 0) { const matches = this.sourceFile .getText(this.sourceFile) .match(/^(\s*(export\s+)?(declare\s+)?(type|class|enum)\s)/gm); if (!matches) throw new Error(`Unable to match prefix for '${this.name}' ${this.type}`); this.aliases.forEach((alias) => { if (this.type === 'class' || this.type === 'interface' || this.type === 'enum') aliasedTypeStrings.push(`/** Re-export aliased type. */\nexport {${this.name} as ${alias}};\n`); else aliasedTypeStrings.push(`${matches[0]}${alias} = ${this.name};`); }); } const transformed = ts.transform(this.sourceFile, [typeReferenceTransformer]); const formatted = ts.createPrinter().printFile(transformed.transformed[0] as ts.SourceFile); return aliasedTypeStrings.length === 0 ? formatted : `${formatted}\n${aliasedTypeStrings.join('\n')}`; } } class PackageTypesDefinition { /** Types grouped by namespaces. */ private readonly namespaces: Record<string, { type: string; file: string }[]> = {}; /** List of type names which is defined by package. */ private readonly packageTypes: string[] = []; /** List of type names which already has been added to the output. */ private writtenTypes: string[] = []; private writingPackageNamespace: boolean = false; /** * Create package types definition. * * Responsible for merged types definition generation. * * @param projectPackage - Project package object with types information. * @param workingDirectory - Root folder with subfolders which represent project structure and contains types definition files. * @param entryPoint - Path to the source file which is package's entry point and source of the public interface. */ constructor( private readonly projectPackage: Package, private readonly workingDirectory: string, private readonly entryPoint: string, ) {} writeToFile(filePath: string) { this.writingPackageNamespace = false; const resultContent: string[] = []; this.writtenTypes = []; this.associateTypesWithNamespaces(); this.addExternalImports(resultContent); // Retrieve reference to the entry point source file. const entryPoint = this.projectPackage.sourceFileAtPath(this.entryPoint); if (!entryPoint) throw new Error(`Can't load type for entry point at path: ${this.entryPoint}`); // Identify and add root types. entryPoint.types.forEach((type) => { this.addTypeDefinition(type, resultContent, this.projectPackage.name); if (type.type !== 'class' || !type.superclass) return; const superClassImport = entryPoint.imports.find( (importType) => importType.isPackageType && importType.value === type.superclass, ); if (!superClassImport) return; const sourceFile = this.sourceFileForModule(superClassImport.module); if (!sourceFile) return; const superclassType = sourceFile.typeByName(superClassImport.value); if (superclassType) this.addTypeDefinition(superclassType, resultContent, this.projectPackage.name); }); this.writingPackageNamespace = true; resultContent.push(`declare namespace ${this.projectPackage.name} {`); this.typeSourceFiles .filter((sourceFile) => sourceFile !== entryPoint) .forEach((sourceFile) => { sourceFile.types.forEach((type) => { this.addTypeDefinition(type, resultContent); }); }); for (const namespace in this.namespaces) { resultContent.push(`export namespace ${namespace} {`); this.namespaces[namespace].forEach((type) => { // Try to retrieve proper source file for imported module. const sourceFile = this.sourceFileForModule(type.file); if (!sourceFile) return; const typeDefinition = sourceFile.typeByName(type.type); if (typeDefinition) this.addTypeDefinition(typeDefinition, resultContent, undefined, true); // if (typeDefinition) this.addTypeDefinition(typeDefinition, resultContent, namespace, true); }); resultContent.push(`}\n\n`); } resultContent.push(`}\n\nexport = ${this.projectPackage.name}`); prettier .format(resultContent.join('\n'), { parser: 'typescript', semi: true, printWidth: 120, singleQuote: true, trailingComma: 'all', }) .then((result) => { fs.writeFileSync(filePath, result, 'utf-8'); }); } /** * Retrieve list of type source files which is specific to the entry point. */ private get typeSourceFiles() { const files: SourceFile[] = []; const entryPoint = this.sourceFileForModule(this.entryPoint); if (!entryPoint) throw new Error(`Can't load type for entry point at path: ${this.entryPoint}`); const flattenImportedTypes = (sourceFile: SourceFile) => { sourceFile.imports .filter((importType) => importType.isPackageType) .forEach((type) => { const importedTypeSourceFile = this.sourceFileForModule(type.module); if (!importedTypeSourceFile) { console.warn( `Source file for ${type.module} is missing. File can be ignored as internal, but import is still there.`, ); return; } if (!files.includes(importedTypeSourceFile)) { files.push(importedTypeSourceFile); flattenImportedTypes(importedTypeSourceFile); } }); }; flattenImportedTypes(entryPoint); return files; } /** * Aggregate types into corresponding namespaces. * * Some types can be imported and used through namespace import syntax and this function allows to bind types to the corresponding namespace. * * @returns Record where list of types (name and file path) stored under namespace name as key. */ private associateTypesWithNamespaces() { const project = this.projectPackage; const namespacedTypes: string[] = []; project.typeSourceFiles.forEach((file) => { file.types.forEach((type) => !this.packageTypes.includes(type.name) && this.packageTypes.push(type.name)); }); project.typeSourceFiles.forEach((file) => { file.imports .filter((fileImport) => fileImport.isPackageType) .forEach((fileImport) => { if (fileImport.type === 'namespace') { if (namespacedTypes.includes(fileImport.module)) return; // Try to retrieve proper source file for imported module. const sourceFile = this.sourceFileForModule(fileImport.module); if (!sourceFile) return; namespacedTypes.push(fileImport.module); sourceFile.types.forEach((sourceFileType) => { this.namespaces[fileImport.value] = this.namespaces[fileImport.value] ?? []; this.namespaces[fileImport.value].push({ type: sourceFileType.name, file: fileImport.module }); }); } else if (namespacedTypes.includes(fileImport.module)) { throw new Error(`${fileImport.module} already added as namespace and can't be used separately.`); } }); }); } /** * Adding external imports required for the package. * * @param result - Types definition output lines. */ private addExternalImports(result: string[]) { Object.entries(this.projectPackage.externalImports).forEach(([_, typeImport]) => { if (!result.includes(typeImport.statement)) result.push(typeImport.statement); }); if (result.length > 1) result.push('\n\n'); } private addPackageImports(imports: SourceFileImport[], result: string[]) { const processedSourceFiles: SourceFile[] = []; imports .filter((fileImport) => fileImport.isPackageType) .forEach((type) => { // Try to retrieve proper source file for imported module. const sourceFile = this.sourceFileForModule(type.module); if (!sourceFile) return; const typeDefinition = sourceFile.typeByName(type.value); if (typeDefinition) this.addTypeDefinition(typeDefinition, result); processedSourceFiles.push(sourceFile); }); processedSourceFiles.forEach((sourceFile) => this.addPackageImports(sourceFile.imports, result)); } private addTypeDefinition( typeDefinition: TypeDefinition, result: string[], namespace?: string, skipNamespaceCheck: boolean = false, ) { const typesCacheId = `${typeDefinition.name}-${typeDefinition.filePath}`; // Check whether type already has been added to the output or not. if (this.writtenTypes.includes(typesCacheId)) return; // Check whether namespace member has been added without namespace name. if (!skipNamespaceCheck && this.isNamespaceMember(typeDefinition)) return; let definition = typeDefinition.toString(this.packageTypes, namespace); if (this.writingPackageNamespace) { definition = definition.replace(/\bexport\s+(default\s+)?class\s+/gm, `export class `); definition = definition.replace(/\bdeclare\s+class\s+/gm, `class `); definition = definition.replace(/\bexport\s+declare\s+enum\s+/gm, `export enum `); definition = definition.replace(/\bexport\s+declare\s+abstract\s+class\s+/gm, `export abstract class `); } else definition = definition.replace(/\bexport\s+declare/g, 'declare'); result.push(definition); this.writtenTypes.push(typesCacheId); } /** * Retrieve module source file. * * @param modulePath - Relative path to the module (relative to the working directory). */ private sourceFileForModule(modulePath: string) { const project = this.projectPackage; // Try to retrieve proper source file for imported module. let sourceFile = project.sourceFileAtPath(modulePath); if (!sourceFile && path.extname(modulePath).length === 0) { sourceFile = project.sourceFileAtPath(`${modulePath}.d.ts`); if (!sourceFile) { let updatedPath = path.join(modulePath, 'index.d.ts'); if ((modulePath.startsWith('./') || modulePath.startsWith('../')) && !updatedPath.startsWith('.')) updatedPath = `./${updatedPath}`; sourceFile = project.sourceFileAtPath(updatedPath); } } if (!sourceFile) { console.warn( `Source file for ${modulePath} is missing. File can be ignored as internal, but import is still there.`, ); } return sourceFile; } /** * Check whether specified type is member of any known namespaces. * * @param typeDefinition - Type definition which should be checked. * @returns `true` in case if type has been associated with one of the namespaces. */ private isNamespaceMember(typeDefinition: TypeDefinition) { if (Object.keys(this.namespaces).length === 0) return false; // Normalize type source file path. const extensionMatch = new RegExp(`(.d.ts|${path.extname(typeDefinition.filePath)})$`); let typeFilePath = typeDefinition.filePath.replace(extensionMatch, ''); if (!typeFilePath.startsWith('./') && !typeFilePath.startsWith('../')) typeFilePath = `.${typeFilePath.split(this.workingDirectory).pop()!}`; for (const name in this.namespaces) { const namespace = this.namespaces[name]; if (namespace.find((type) => type.type === typeDefinition.name && type.file === typeFilePath)) return true; } return false; } } export function syntaxKindToName(kind: ts.SyntaxKind) { return (<any>ts).SyntaxKind[kind]; } // -------------------------------------------------------- // -------------------- Configuration ---------------------- // -------------------------------------------------------- // region Configuration // Parse script launch arguments const options = program .option('--package <name>', 'Name of the package to aggregate types for.') .option('--working-dir <path>', 'Path to the processed definition type files root directory.') .option('--input <path>', 'Path to the main type definition file.') .option('--output <path>', 'Path to the folder or filepath where aggregated type definition file should be saved.') .option('--ts-config <path>', "Path to the project's tsconfig.json file.") .parse() .opts(); if (options.package === undefined || options.package.length === 0) throw new Error('Package file is missing.'); if (options.workingDir === undefined || options.workingDir.length === 0) throw new Error('Working directory is missing.'); if (options.input === undefined || options.input.length === 0) throw new Error('Entry point file is missing.'); if (options.output === undefined || options.output.length === 0) throw new Error('Output file is missing.'); if (options.tsConfig === undefined || options.tsConfig.length === 0) throw new Error('tsconfig.json file is missing.'); const fullPathFromRelative = (relativePath: string) => /^(.\/|..\/)/.test(relativePath) ? path.resolve(process.cwd(), relativePath) : relativePath; // Processing input arguments. const packageName = options.package; const workingDir = fullPathFromRelative(options.workingDir); const tsConfigFilePath = fullPathFromRelative(options.tsConfig); const inputFilePath = fullPathFromRelative(options.input); let outputFilePath = fullPathFromRelative(options.output); if (path.extname(outputFilePath).length === 0) outputFilePath = path.resolve(outputFilePath, inputFilePath.split('/').pop()!); // endregion // -------------------------------------------------------- // --------------------- Processing ----------------------- // -------------------------------------------------------- const projectPackage = new Package(packageName, workingDir, loadTSConfiguration(tsConfigFilePath)); const entryPointSourceFile = projectPackage.sourceFileAtPath(inputFilePath); if (!entryPointSourceFile) throw new Error('Entry point source file not found.'); // Files loaded into the project. Clean up working directory. fs.rmSync(workingDir, { recursive: true, force: true }); fs.mkdirSync(workingDir); const definition = new PackageTypesDefinition(projectPackage, workingDir, inputFilePath); definition.writeToFile(outputFilePath);