@lipsurf/cli
Version:
Build LipSurf plugins to add custom voice commands to any website.
151 lines (136 loc) • 4.71 kB
text/typescript
import {
sys,
findConfigFile,
createProgram,
flattenDiagnosticMessageText,
formatDiagnostic,
createWatchCompilerHost,
createEmitAndSemanticDiagnosticsBuilderProgram,
createWatchProgram,
readConfigFile,
getPreEmitDiagnostics,
parseJsonConfigFileContent,
createBuilderStatusReporter,
} from "typescript";
const configPath = findConfigFile(
process.cwd(),
sys.fileExists,
"tsconfig.json"
);
const parseConfigHost /* : any */ = {
fileExists: sys.fileExists,
readDirectory: sys.readDirectory,
readFile: sys.readFile,
useCaseSensitiveFileNames: true,
};
export function compile(fileNames: string[]) {
// const options = <CompilerOptions>parseJsonConfigFileContent(configPath, parseConfigHost, "./");
if (!configPath) throw 'Could not find "tsconfig.json"';
const configFile = readConfigFile(configPath, sys.readFile);
const parsedConfig = parseJsonConfigFileContent(
configFile.config,
parseConfigHost,
"./"
);
// rootDir: src ensures that we consistently make the plugin directories in dist/tmp/
let program = createProgram(fileNames, {
...parsedConfig.options,
rootDir: "src",
});
let emitResult = program.emit();
let allDiagnostics = getPreEmitDiagnostics(program).concat(
emitResult.diagnostics
);
allDiagnostics.forEach((diagnostic) => {
if (diagnostic.file) {
let { line, character } = diagnostic.file.getLineAndCharacterOfPosition(
diagnostic.start!
);
let message = flattenDiagnosticMessageText(diagnostic.messageText, "\n");
console.log(
`${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message}`
);
} else {
console.log(
`${flattenDiagnosticMessageText(diagnostic.messageText, "\n")}`
);
}
});
if (emitResult.emitSkipped) throw "TypeScript compile errors.";
}
const formatHost = {
getCanonicalFileName: (path) => path,
getCurrentDirectory: sys.getCurrentDirectory,
getNewLine: () => sys.newLine,
};
// TODO: this might have room for perf. improvement with incremental building
export function watch(fileNames: string[], cb?: () => void) {
if (!configPath) {
throw new Error('Could not find a valid "tsconfig.json".');
}
// TypeScript can use several different program creation "strategies":
// * ts.createEmitAndSemanticDiagnosticsBuilderProgram,
// * ts.createSemanticDiagnosticsBuilderProgram
// * ts.createAbstractBuilder
// The first two produce "builder programs". These use an incremental strategy
// to only re-check and emit files whose contents may have changed, or whose
// dependencies may have changes which may impact change the result of prior
// type-check and emit.
// The last uses an ordinary program which does a full type check after every
// change.
// Between `createEmitAndSemanticDiagnosticsBuilderProgram` and
// `createSemanticDiagnosticsBuilderProgram`, the only difference is emit.
// For pure type-checking scenarios, or when another tool/process handles emit,
// using `createSemanticDiagnosticsBuilderProgram` may be more desirable.
const createProgram = createEmitAndSemanticDiagnosticsBuilderProgram;
// Note that there is another overload for `createWatchCompilerHost` that takes
// a set of root files.
const host = createWatchCompilerHost(
configPath,
{},
sys,
createProgram,
reportDiagnostic,
reportWatchStatusChanged
);
// You can technically override any given hook on the host, though you probably
// don't need to.
// Note that we're assuming `origCreateProgram` and `origPostProgramCreate`
// doesn't use `this` at all.
const origCreateProgram = host.createProgram;
host.createProgram = (rootNames, options, host, oldProgram) => {
return origCreateProgram(
fileNames,
{ ...options, rootDir: "src" },
host,
oldProgram
);
};
const origPostProgramCreate = host.afterProgramCreate;
host.afterProgramCreate = (program) => {
origPostProgramCreate!(program);
console.log("Done compiling changed typescript.");
if (cb) cb();
};
// `createWatchProgram` creates an initial program, watches files, and updates
// the program over time.
createWatchProgram(host);
}
function reportDiagnostic(diagnostic) {
console.error(
"Error",
diagnostic.code,
":",
flattenDiagnosticMessageText(
diagnostic.messageText,
formatHost.getNewLine()
)
);
}
/**
* Prints a diagnostic every time the watch status changes.
* This is mainly for messages like "Starting compilation" or "Compilation completed".
*/
function reportWatchStatusChanged(diagnostic) {
console.info(formatDiagnostic(diagnostic, formatHost));
}