sardines-compile-time-tools
Version:
sardines.compile-time-tools.js is part of the sardines.io project
233 lines (210 loc) • 10.1 kB
text/typescript
/**
* @author Robin Sun
* @email robin@naturewake.com
* @create date 2019-06-21 14:19:18
* @modify date 2019-06-21 14:19:18
* @desc [description]
*/
// Compiler
// Reference: https://github.com/Microsoft/TypeScript/wiki/Using-the-Compiler-API
import * as fs from "fs"
import * as path from 'path'
import { gatherExports } from './parser'
import { transform, Service, getServiceName } from './transformer'
export const compile = async (compilerSettings: any, targetFiles: string[]) => {
const processedFiles: {[key: string]: boolean} = {}
const sardineExtName = `.sardine.ts`
interface JobResult {
services: Service[]
error: any|null
filePath: string
}
const processFile = async (targetFilePath: string): Promise<JobResult> => {
let services: Service[] = []
let filePath = targetFilePath
let error = null
if (processedFiles[filePath]) return {services,error,filePath}
// recursively process directory
if (fs.lstatSync(filePath).isDirectory()) {
for(let item of fs.readdirSync(filePath)) {
const subFilePath = path.join(filePath, `./${item}`)
let subjobResult = await processFile(subFilePath)
if (subjobResult.error) {
if (compilerSettings.validate || compilerSettings.only_validate) {
error = subjobResult.error
filePath = subjobResult.filePath
break
}
if (compilerSettings.verbose) {
console.error(`ERROR for file ${subjobResult.filePath}:`, subjobResult.error, '\n')
}
} else {
Array.prototype.push.apply(services, subjobResult.services)
}
}
processedFiles[filePath] = true
return {services,error,filePath}
}
// only process files, excluding socket/fifo/device files
if (!fs.lstatSync(filePath).isFile()) return {services,error,filePath}
// Prepare the file names
const dir = path.dirname(filePath)
let baseName = path.basename(filePath)
const extName = path.extname(baseName)
if (extName.toLowerCase() !== '.ts') {
error = `unsupported file type '${extName}' for file ${filePath}`
return {services,error,filePath}
}
// check if the file is source file
let fileName = '', // the pure file base name WITHOUT any extname nor the prefix dir path
sardineFileName = '', // ${fileName}.sardine
sardineBaseName = '', // ${sardineFileName}.ts
sardineFilePath = '', // file path like ${dir}/${sardineFileName}.ts
intermediateFilePath = '' // ${dir}/${sardineFileName}.tmp
if (baseName.indexOf(sardineExtName) < 0) {
fileName = path.basename(baseName, extName)
sardineBaseName = `${fileName}${sardineExtName}`
sardineFilePath = `${dir}/${sardineBaseName}`
} else {
sardineFilePath = filePath
sardineBaseName = baseName
fileName = path.basename(baseName, sardineExtName)
filePath = `${dir}/${fileName}${extName}`
}
sardineFileName = path.basename(sardineBaseName, extName)
intermediateFilePath = `${dir}/${sardineFileName}.tmp`
// make sure no duplicated processing
if (!compilerSettings.recompile && !compilerSettings.reverse) {
if (fs.existsSync(sardineFilePath)) return {services,error,filePath}
} else if (fs.existsSync(sardineFilePath)) {
if (compilerSettings.verbose) {
console.log(`restoring source file ${filePath} from sardine file ${sardineFilePath}`)
}
fs.renameSync(sardineFilePath, filePath)
}
if (compilerSettings.reverse) return {services,error,filePath}
if (targetFilePath === sardineFilePath) return {services,error,filePath}
let sourceFilePath = filePath
if (processedFiles[filePath]) return {services,error,filePath}
processedFiles[filePath] = true
if (compilerSettings.verbose) {
console.log(`processing file: ${sourceFilePath}`)
}
// compile it
try {
// process the source file
// Parse a file
const [identifiers, referencedTypes, importedIds, proxyIds] = gatherExports(sourceFilePath);
const appName = compilerSettings.application
// console.log('parsed application:', appName)
// let utils = require('sardines-core').utils
// console.log('identifiers:')
// utils.inspectedLog(identifiers)
// console.log('referencedTypes:')
// utils.inspectedLog(referencedTypes)
// console.log('importedIds:')
// utils.inspectedLog(importedIds)
// console.log('proxyIds:')
// utils.inspectedLog(proxyIds)
services = await transform(appName, fileName, sardineFileName, sourceFilePath, identifiers, referencedTypes, importedIds, proxyIds, (line:string, lineIndex:number) => {
if (lineIndex === 0) {
if (!compilerSettings.only_validate) {
fs.writeFileSync(intermediateFilePath, line)
}
if (compilerSettings.print) {
console.log(line)
}
} else if (line && !compilerSettings.only_validate) {
if (compilerSettings.print) {
console.log(line)
}
fs.appendFileSync(intermediateFilePath, line)
}
})
//
if (!compilerSettings.only_validate && fs.existsSync(intermediateFilePath)) {
if (compilerSettings.verbose) {
console.log(`renaming source file ${filePath} to sardine file ${sardineFilePath}`)
}
fs.renameSync(filePath, sardineFilePath)
if (compilerSettings.verbose) {
console.log(`moving intermediate file ${intermediateFilePath} to replace source file ${filePath}`)
}
fs.renameSync(intermediateFilePath, filePath)
}
if (compilerSettings.verbose || compilerSettings.only_validate) {
console.log(`successfully processed source file: ${sourceFilePath}\n`)
}
return {services,error,filePath}
} catch (err) {
error = err
} finally {
if (fs.existsSync(intermediateFilePath)) {
fs.unlinkSync(intermediateFilePath)
}
return {services,error,filePath}
}
}
// Prepare service definition file
let service_definition_file_content: any = {}
let sardineServices: Map<string, Service>|null = null
if (compilerSettings.gen_services) {
if (fs.existsSync(compilerSettings.gen_services)) {
try {
service_definition_file_content = JSON.parse(fs.readFileSync(compilerSettings.gen_services).toString())
if (service_definition_file_content.services) {
sardineServices = new Map()
for (let s of service_definition_file_content.services) {
const name = getServiceName(s)
if (!sardineServices.has(name)) {
sardineServices.set(name, s)
}
}
service_definition_file_content.services = []
}
} catch(e) {
if (compilerSettings.verbose) {
console.error(`error when loading service definition file at [${compilerSettings.gen_services}]:`, e, '\n')
}
}
} else {
sardineServices = new Map()
service_definition_file_content.services = []
}
}
Promise.all(targetFiles.map(filePath => processFile(filePath))).then(results => {
let hasError = false
for (let {services, error, filePath} of <JobResult[]>results) {
if (error) {
hasError = true
if (compilerSettings.verbose || compilerSettings.only_validate || compilerSettings.validate) {
console.error(`ERROR while processing ${filePath}:`, error, '\n')
}
} else if(sardineServices) {
for (let s of services) {
const name = getServiceName(s)
sardineServices.set(name, s)
}
}
}
if (!hasError && !compilerSettings.only_validate && sardineServices) {
if (compilerSettings.gen_services && Array.isArray(service_definition_file_content.services)) {
service_definition_file_content.services = Array.from(sardineServices.values())
if (compilerSettings.application) {
service_definition_file_content.application = compilerSettings.application
}
try {
fs.writeFileSync(compilerSettings.gen_services, JSON.stringify(service_definition_file_content, null, 4))
if (compilerSettings.verbose) {
console.log(`${service_definition_file_content.services.length} services stored in the sardine definition file at [${compilerSettings.gen_services}]`)
}
} catch (e) {
console.error(`ERROR when writing sardine service definition file at [${compilerSettings.gen_services}]`, e, '\n')
}
}
}
return sardineServices
}).catch(e => {
console.error('UNKNOW ERROR:', e, '\n')
})
}