import * as ts from 'typescript' import * as fs from 'fs-extra' import * as _ from 'lodash' import { ServerlessFunction } from './types' import * as path from 'path' export function makeDefaultTypescriptConfig() { const defaultTypescriptConfig: ts.CompilerOptions = { preserveConstEnums: true, strictNullChecks: true, sourceMap: true, target: ts.ScriptTarget.ES5, moduleResolution: ts.ModuleResolutionKind.NodeJs, lib: ['lib.es2015.d.ts'], rootDir: './', } return defaultTypescriptConfig } export function extractFileNames(cwd: string, provider: string, functions?: { [key: string]: ServerlessFunction }): string[] { // The Google provider will use the entrypoint not from the definition of the // handler function, but instead from the package.json:main field, or via a // index.js file. This check reads the current package.json in the same way // that we already read the tsconfig.json file, by inspecting the current // working directory. If the packageFile does not contain a valid main, then // it instead selects the index.js file. if (provider === 'google') { const packageFilePath = path.join(cwd, 'package.json') if (fs.existsSync(packageFilePath)) { // Load in the package.json file. const packageFile = JSON.parse(fs.readFileSync(packageFilePath).toString()) // Either grab the package.json:main field, or use the index.ts file. // (This will be transpiled to index.js). const main = packageFile.main ? packageFile.main.replace(/\.js$/, '.ts') : 'index.ts' // Check that the file indeed exists. if (!fs.existsSync(path.join(cwd, main))) { console.log(`Cannot locate entrypoint, ${main} not found`) throw new Error('Typescript compilation failed') } return [main] } } return _.values(functions) .map(fn => fn.handler) .map(h => { const fnName = _.last(h.split('.')) const fnNameLastAppearanceIndex = h.lastIndexOf(fnName) // replace only last instance to allow the same name for file and handler return h.substring(0, fnNameLastAppearanceIndex) + 'ts' }) } export async function run(fileNames: string[], options: ts.CompilerOptions): Promise { options.listEmittedFiles = true const program = ts.createProgram(fileNames, options) const emitResult = program.emit() const allDiagnostics = ts.getPreEmitDiagnostics(program).concat(emitResult.diagnostics) allDiagnostics.forEach(diagnostic => { if (!diagnostic.file) { console.log(diagnostic) } const {line, character} = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start) const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n') console.log(`${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message}`) }) if (emitResult.emitSkipped) { throw new Error('Typescript compilation failed') } return emitResult.emittedFiles.filter(filename => filename.endsWith('.js')) } /* * based on rootFileNames returns list of all related (e.g. imported) source files */ export function getSourceFiles( rootFileNames: string[], options: ts.CompilerOptions ): string[] { const program = ts.createProgram(rootFileNames, options) const programmFiles = program.getSourceFiles() .map(file => file.fileName) .filter(file => { return file.split(path.sep).indexOf('node_modules') < 0 }) return programmFiles } export function getTypescriptConfig( cwd: string, logger?: { log: (str: string) => void } ): ts.CompilerOptions { const configFilePath = path.join(cwd, 'tsconfig.json') if (fs.existsSync(configFilePath)) { const configFileText = fs.readFileSync(configFilePath).toString() const result = ts.parseConfigFileTextToJson(configFilePath, configFileText) if (result.error) { throw new Error(JSON.stringify(result.error)) } const configParseResult = ts.parseJsonConfigFileContent(result.config, ts.sys, path.dirname(configFilePath)) if (configParseResult.errors.length > 0) { throw new Error(JSON.stringify(configParseResult.errors)) } if (logger) { logger.log(`Using local tsconfig.json`) } // disallow overrriding rootDir if (configParseResult.options.rootDir && path.resolve(configParseResult.options.rootDir) !== path.resolve(cwd) && logger) { logger.log('Warning: "rootDir" from local tsconfig.json is overriden') } configParseResult.options.rootDir = cwd return configParseResult.options } return makeDefaultTypescriptConfig() }