import { EOL } from "os" ; import resolveFrom from "resolve-from" ; import { FatalError } from "../../errors" ; import * as fs from "fs-extra" ; import path from "path" ; import normalizePath from "normalize-path" ; export type DeclarationFile = { name : string ; content : string ; } ; export type EmittedDeclarationOutput = { /** The emitted d.ts types file. */ types : DeclarationFile ; /** The emitted d.ts.map declaration map file. */ map ? : DeclarationFile ; /** The input TypeScript file */ filename : string ; } ; export type TS = typeof import ( "typescript" ) ; export function getDiagnosticsHost ( ts , projectDir ) { return { getCanonicalFileName : ( x ) => ts . sys . useCaseSensitiveFileNames ? x : x . toLowerCase ( ) , getCurrentDirectory : ( ) => projectDir , getNewLine : ( ) => EOL , } ; } export function loadTypeScript ( packageDir , projectDir , pkgName ) { try { return require ( resolveFrom ( packageDir , "typescript" ) ) ; } catch ( err ) { if ( err . code === "MODULE_NOT_FOUND" ) { try { return require ( resolveFrom ( projectDir , "typescript" ) ) ; } catch ( err ) { throw new FatalError ( "an entrypoint source file ends with the .ts or .tsx extension but the typescript module could not be resolved from the project directory, please install it." , pkgName ) ; } } throw err ; } } function weakMemoize < Arg extends object , Return > ( func ) { let cache = new WeakMap ( ) ; return ( arg ) => { if ( cache . has ( arg ) ) { return cache . get ( arg ) ! ; } let ret = func ( arg ) ; cache . set ( arg , ret ) ; return ret ; } ; } function memoize < V > ( fn ) { const cache = { } ; return ( arg ) => { if ( cache [ arg ] === undefined ) cache [ arg ] = fn ( arg ) ; return cache [ arg ] ; } ; } async function nonMemoizedGetProgram ( typescript , configFileName ) { let configFileContents = await fs . readFile ( configFileName , "utf8" ) ; const result = typescript . parseConfigFileTextToJson ( configFileName , configFileContents ) ; let thing = typescript . parseJsonConfigFileContent ( result . config , typescript . sys , process . cwd ( ) , undefined , configFileName ) ; thing . options . declaration = true ; thing . options . emitDeclarationOnly = true ; thing . options . noEmit = false ; let program = typescript . createProgram ( thing . fileNames , thing . options ) ; return { options : thing . options , program } ; } let memoizedGetProgram = weakMemoize ( ( typescript ) => memoize ( async ( configFileName ) => { return nonMemoizedGetProgram ( typescript , configFileName ) ; } ) ) ; export async function getProgram ( dirname , pkgName , ts ) { let configFileName = ts . findConfigFile ( dirname , ts . sys . fileExists ) ; if ( ! configFileName ) { throw new FatalError ( "an entrypoint source file ends with the .ts or tsx extension but no TypeScript config exists, please create one." , pkgName ) ; } return normalizePath ( configFileName ) === normalizePath ( path . join ( dirname , "tsconfig.json" ) ) ? nonMemoizedGetProgram ( ts , configFileName ) : memoizedGetProgram ( ts ) ( configFileName ) ; } export const getDeclarationsForFile = async ( filename , typescript , program , normalizedPkgDir , projectDir , diagnosticsHost ) : Promise < EmittedDeclarationOutput > => { if ( filename . endsWith ( ".d.ts" ) ) { return { types : { name : filename . replace ( normalizedPkgDir , normalizePath ( path . join ( normalizedPkgDir , "dist" , "declarations" ) ) ) , content : await fs . readFile ( filename , "utf8" ) , } , filename , } ; } const sourceFile = program . getSourceFile ( typescript . sys . useCaseSensitiveFileNames ? filename : filename . toLowerCase ( ) ) ; if ( ! sourceFile ) { throw new Error ( `Could not find source file at ${ filename } in TypeScript declaration generation, this is likely a bug in Preconstruct` ) ; } const emitted = { } ; const otherEmitted = [ ] ; const { diagnostics } = program . emit ( sourceFile , ( name , text ) => { if ( name . endsWith ( ".d.ts" ) ) { emitted . types = { name : name . replace ( normalizedPkgDir , normalizePath ( path . join ( normalizedPkgDir , "dist" , "declarations" ) ) ) , content : text , } ; } else if ( name . endsWith ( ".d.ts.map" ) ) { emitted . map = { name : name . replace ( normalizedPkgDir , normalizePath ( path . join ( normalizedPkgDir , "dist" , "declarations" ) ) ) , content : text , } ; } else { otherEmitted . push ( { name , text } ) ; } } , undefined , true ) ; if ( ! emitted . types || diagnostics . length ) { throw new FatalError ( `Generating TypeScript declarations for ${ normalizePath ( path . relative ( projectDir , filename ) ) } failed:\n${ typescript . formatDiagnosticsWithColorAndContext ( diagnostics , diagnosticsHost ) }${ otherEmitted . length ? `\n\nTypeScript emitted other files when attempting to emit .d.ts files:\n${ otherEmitted . map ( ( x ) => `${ x . name }\n\n${ x . text }` ) . join ( "\n\n" ) }` : "" }` , "" ) ; } return { types : emitted . types , map : emitted . map , filename } ; } ; export function overwriteDeclarationMapSourceRoot ( content , actualSourceRoot ) { const src = JSON . parse ( content ) ; src . sourceRoot = actualSourceRoot ; return JSON . stringify ( src ) ; }