add logging to typings installer, read npm bin path to start globally installed binaries

This commit is contained in:
Vladimir Matveev 2016-08-15 15:59:31 -07:00
parent 672813afb6
commit dd70fdbb76
2 changed files with 143 additions and 14 deletions

View File

@ -7,6 +7,10 @@ namespace ts.server.typingsInstaller {
homedir(): string
} = require("os");
const fs: {
appendFileSync(file: string, content: string): void
} = require("fs");
function getGlobalCacheLocation() {
let basePath: string;
switch (process.platform) {
@ -25,13 +29,29 @@ namespace ts.server.typingsInstaller {
return combinePaths(normalizeSlashes(basePath), "Microsoft/TypeScript");
}
class FileLog implements Log {
constructor(private readonly logFile?: string) {
}
isEnabled() {
return this.logFile !== undefined;
}
writeLine(text: string) {
fs.appendFileSync(this.logFile, text + sys.newLine);
}
}
export class NodeTypingsInstaller extends TypingsInstaller {
private execSync: { (command: string, options: { stdio: "ignore" }): any };
private execSync: { (command: string, options: { stdio: "ignore" | "pipe" }): Buffer | string };
private exec: { (command: string, options: { cwd: string }, callback?: (error: Error, stdout: string, stderr: string) => void): any };
private npmBinPath: string;
private tsdRunCount = 1;
readonly installTypingHost: InstallTypingHost = sys;
constructor() {
super(getGlobalCacheLocation(), toPath("typingSafeList.json", __dirname, createGetCanonicalFileName(sys.useCaseSensitiveFileNames)));
constructor(log?: Log) {
super(getGlobalCacheLocation(), toPath("typingSafeList.json", __dirname, createGetCanonicalFileName(sys.useCaseSensitiveFileNames)), log);
const { exec, execSync } = require("child_process");
this.execSync = execSync;
this.exec = exec;
@ -39,6 +59,18 @@ namespace ts.server.typingsInstaller {
init() {
super.init();
try {
this.npmBinPath = this.execSync("npm -g bin", { stdio: "pipe" }).toString().trim();
if (this.log.isEnabled()) {
this.log.writeLine(`Global npm bin path '${this.npmBinPath}'`);
}
}
catch(e) {
this.npmBinPath = "";
if (this.log.isEnabled()) {
this.log.writeLine(`Error when getting npm bin path: ${e}. Set bin path to ""`);
}
}
process.on("message", (req: InstallTypingsRequest) => {
this.install(req);
})
@ -65,11 +97,24 @@ namespace ts.server.typingsInstaller {
}
protected sendResponse(response: InstallTypingsResponse) {
if (this.log.isEnabled()) {
this.log.writeLine(`Sending response: ${JSON.stringify(response)}`)
}
process.send(response);
}
protected runTsd(cachePath: string, typingsToInstall: string[], postInstallAction: (installedTypings: string[]) => void): void {
this.exec(`tsd install ${typingsToInstall.join(" ")} -ros`, { cwd: cachePath }, (err, stdout, stderr) => {
const id = this.tsdRunCount;
this.tsdRunCount++;
const tsdPath = combinePaths(this.npmBinPath, "tsd");
if (this.log.isEnabled()) {
this.log.writeLine(`Running tsd ${id}, tsd path '${tsdPath}, typings to install: ${JSON.stringify(typingsToInstall)}. cache path '${cachePath}'`)
}
this.exec(`${tsdPath} install ${typingsToInstall.join(" ")} -ros`, { cwd: cachePath }, (err, stdout, stderr) => {
if (this.log.isEnabled()) {
this.log.writeLine(`TSD ${id} stdout: ${stdout}`);
this.log.writeLine(`TSD ${id} stderr: ${stderr}`)
}
const i = stdout.indexOf("running install");
if (i < 0) {
return;
@ -87,6 +132,12 @@ namespace ts.server.typingsInstaller {
}
}
const installer = new NodeTypingsInstaller();
const log = new FileLog(process.env.TI_LOG_FILE);
process.on("uncaughtException", (e: Error) => {
if (log.isEnabled()) {
log.writeLine(`Unhandled exception: ${e} at ${e.stack}`);
}
})
const installer = new NodeTypingsInstaller(log);
installer.init();
}

View File

@ -13,6 +13,16 @@ namespace ts.server.typingsInstaller {
installed: MapLike<any>;
}
export interface Log {
isEnabled(): boolean;
writeLine(text: string): void;
}
const nullLog: Log = {
isEnabled: () => false,
writeLine: () => {}
}
function tsdTypingToFileName(cachePath: string, tsdTypingFile: string) {
return combinePaths(cachePath, `typings/${tsdTypingFile}`);
}
@ -32,24 +42,47 @@ namespace ts.server.typingsInstaller {
abstract readonly installTypingHost: InstallTypingHost;
constructor(readonly globalCachePath: string, readonly safeListPath: Path) {
constructor(readonly globalCachePath: string, readonly safeListPath: Path, protected readonly log = nullLog) {
if (this.log.isEnabled()) {
this.log.writeLine(`Global cache location '${globalCachePath}', safe file path '${safeListPath}'`);
}
}
init() {
this.isTsdInstalled = this.isPackageInstalled("tsd");
if (this.log.isEnabled()) {
this.log.writeLine(`isTsdInstalled: ${this.isTsdInstalled}`);
}
if (!this.isTsdInstalled) {
if (this.log.isEnabled()) {
this.log.writeLine(`tsd is not installed, installing tsd...`);
}
this.isTsdInstalled = this.installPackage("tsd");
if (this.log.isEnabled()) {
this.log.writeLine(`isTsdInstalled: ${this.isTsdInstalled}`);
}
}
this.processCacheLocation(this.globalCachePath);
}
install(req: InstallTypingsRequest) {
if (!this.isTsdInstalled) {
if (this.log.isEnabled()) {
this.log.writeLine(`tsd is not installed, ignoring request...`);
}
return;
}
if (this.log.isEnabled()) {
this.log.writeLine(`Got install request ${JSON.stringify(req)}`);
}
// load existing typing information from the cache
if (req.cachePath) {
if (this.log.isEnabled()) {
this.log.writeLine(`Request specifies cache path '${req.cachePath}', loading cached information...`);
}
this.processCacheLocation(req.cachePath);
}
@ -61,6 +94,10 @@ namespace ts.server.typingsInstaller {
this.packageNameToTypingLocation,
req.typingOptions,
req.compilerOptions);
if (this.log.isEnabled()) {
this.log.writeLine(`Finished typings discovery: ${JSON.stringify(discoverTypingsResult)}`);
}
// respond with whatever cached typings we have now
this.sendResponse(this.createResponse(req, discoverTypingsResult.cachedTypingPaths));
@ -68,17 +105,29 @@ namespace ts.server.typingsInstaller {
// start watching files
this.watchFiles(discoverTypingsResult.filesToWatch);
// install typings and
this.installTypings(req, discoverTypingsResult.cachedTypingPaths, discoverTypingsResult.newTypingNames);
// install typings
this.installTypings(req, req.cachePath || this.globalCachePath, discoverTypingsResult.cachedTypingPaths, discoverTypingsResult.newTypingNames);
}
private processCacheLocation(cacheLocation: string) {
if (this.log.isEnabled()) {
this.log.writeLine(`Processing cache location '${cacheLocation}'`);
}
if (this.knownCachesSet[cacheLocation]) {
if (this.log.isEnabled()) {
this.log.writeLine(`Cache location was already processed...`)
}
return;
}
const tsdJson = combinePaths(cacheLocation, "tsd.json");
if (this.log.isEnabled()) {
this.log.writeLine(`Trying to find '${tsdJson}'...`);
}
if (this.installTypingHost.fileExists(tsdJson)) {
const tsdConfig = <TsdConfig>JSON.parse(this.installTypingHost.readFile(tsdJson));
if (this.log.isEnabled()) {
this.log.writeLine(`Loaded content of '${tsdJson}': ${JSON.stringify(tsdConfig)}`);
}
if (tsdConfig.installed) {
for (const key in tsdConfig.installed) {
// key is <package name>/<typing file>
@ -92,30 +141,53 @@ namespace ts.server.typingsInstaller {
continue;
}
if (existingTypingFile) {
// TODO: log warning
if (this.log.isEnabled()) {
this.log.writeLine(`New typing for package ${packageName} from '${typingFile}' conflicts with existing typing file '${existingTypingFile}'`);
}
}
if (this.log.isEnabled()) {
this.log.writeLine(`Adding entry into typings cache: '${packageName}' => '${typingFile}'`);
}
this.packageNameToTypingLocation[packageName] = typingFile;
}
}
}
if (this.log.isEnabled()) {
this.log.writeLine(`Finished processing cache location '${cacheLocation}'`);
}
this.knownCachesSet[cacheLocation] = true;
}
private installTypings(req: InstallTypingsRequest, currentlyCachedTypings: string[], typingsToInstall: string[]) {
private installTypings(req: InstallTypingsRequest, cachePath: string, currentlyCachedTypings: string[], typingsToInstall: string[]) {
if (this.log.isEnabled()) {
this.log.writeLine(`Installing typings ${JSON.stringify(typingsToInstall)}`);
}
typingsToInstall = filter(typingsToInstall, x => !this.missingTypingsSet[x]);
if (typingsToInstall.length === 0) {
if (this.log.isEnabled()) {
this.log.writeLine(`All typings are known to be missing - no need to go any further`);
}
return;
}
// TODO: install typings and send response when they are ready
const tsdPath = combinePaths(req.cachePath, "tsd.json");
const tsdPath = combinePaths(cachePath, "tsd.json");
if (this.log.isEnabled()) {
this.log.writeLine(`Tsd config file: ${tsdPath}`);
}
if (!this.installTypingHost.fileExists(tsdPath)) {
this.ensureDirectoryExists(req.cachePath, this.installTypingHost);
if (this.log.isEnabled()) {
this.log.writeLine(`Tsd config file '${tsdPath}' is missing, creating new one...`);
}
this.ensureDirectoryExists(cachePath, this.installTypingHost);
this.installTypingHost.writeFile(tsdPath, DefaultTsdSettings);
}
this.runTsd(req.cachePath, typingsToInstall, installedTypings => {
this.runTsd(cachePath, typingsToInstall, installedTypings => {
// TODO: watch project directory
if (this.log.isEnabled()) {
this.log.writeLine(`Requested to install typings ${JSON.stringify(typingsToInstall)}, installed typings ${JSON.stringify(installedTypings)}`);
}
const installedPackages: Map<true> = createMap<true>();
const installedTypingFiles: string[] = [];
for (const t of installedTypings) {
@ -124,10 +196,16 @@ namespace ts.server.typingsInstaller {
continue;
}
installedPackages[packageName] = true;
installedTypingFiles.push(tsdTypingToFileName(req.cachePath, t));
installedTypingFiles.push(tsdTypingToFileName(cachePath, t));
}
if (this.log.isEnabled()) {
this.log.writeLine(`Installed typing files ${JSON.stringify(installedTypingFiles)}`);
}
for (const toInstall of typingsToInstall) {
if (!installedPackages[toInstall]) {
if (this.log.isEnabled()) {
this.log.writeLine(`New missing typing package '${toInstall}'`);
}
this.missingTypingsSet[toInstall] = true;
}
}