move global cache location to node typings installer

This commit is contained in:
Vladimir Matveev 2016-08-15 11:48:28 -07:00
parent f603f024ba
commit 672813afb6
6 changed files with 120 additions and 64 deletions

View file

@ -29,15 +29,12 @@ namespace ts {
abstract class TestTypingsInstaller extends server.typingsInstaller.TypingsInstaller implements server.ITypingsInstaller { abstract class TestTypingsInstaller extends server.typingsInstaller.TypingsInstaller implements server.ITypingsInstaller {
protected projectService: server.ProjectService; protected projectService: server.ProjectService;
constructor(private readonly host: server.ServerHost) { constructor(readonly cachePath: string, readonly installTypingHost: server.ServerHost) {
super(); super(cachePath, <Path>"");
this.init(); this.init();
} }
abstract cachePath: string;
safeFileList = <Path>""; safeFileList = <Path>"";
packageNameToTypingLocation: Map<string> = createMap<string>();
postInstallActions: (( map: (t: string[]) => string[]) => void)[] = []; postInstallActions: (( map: (t: string[]) => string[]) => void)[] = [];
runPostInstallActions(map: (t: string[]) => string[]) { runPostInstallActions(map: (t: string[]) => string[]) {
@ -52,7 +49,7 @@ namespace ts {
} }
getInstallTypingHost() { getInstallTypingHost() {
return this.host; return this.installTypingHost;
} }
installPackage(packageName: string) { installPackage(packageName: string) {
@ -74,7 +71,7 @@ namespace ts {
} }
enqueueInstallTypingsRequest(project: server.Project, typingOptions: TypingOptions) { enqueueInstallTypingsRequest(project: server.Project, typingOptions: TypingOptions) {
const request = server.createInstallTypingsRequest(project, typingOptions, this.safeFileList, this.packageNameToTypingLocation, this.cachePath); const request = server.createInstallTypingsRequest(project, typingOptions, this.cachePath);
this.install(request); this.install(request);
} }
} }
@ -1519,9 +1516,8 @@ namespace ts {
const host = createServerHost([file1, tsconfig, packageJson]); const host = createServerHost([file1, tsconfig, packageJson]);
class TypingInstaller extends TestTypingsInstaller { class TypingInstaller extends TestTypingsInstaller {
cachePath = "/a/data/";
constructor(host: server.ServerHost) { constructor(host: server.ServerHost) {
super(host); super("/a/data/", host);
} }
}; };
const installer = new TypingInstaller(host); const installer = new TypingInstaller(host);

View file

@ -18,10 +18,6 @@ namespace ts.server {
fork(modulePath: string): NodeChildProcess; fork(modulePath: string): NodeChildProcess;
} = require("child_process"); } = require("child_process");
const os: {
homedir(): string
} = require("os");
interface ReadLineOptions { interface ReadLineOptions {
input: NodeJS.ReadableStream; input: NodeJS.ReadableStream;
output?: NodeJS.WritableStream; output?: NodeJS.WritableStream;
@ -167,24 +163,8 @@ namespace ts.server {
class NodeTypingsInstaller implements ITypingsInstaller { class NodeTypingsInstaller implements ITypingsInstaller {
private installer: NodeChildProcess; private installer: NodeChildProcess;
private projectService: ProjectService; private projectService: ProjectService;
private cachePath: string;
constructor(private readonly logger: server.Logger) { constructor(private readonly logger: server.Logger) {
let basePath: string;
switch (process.platform) {
case "win32":
basePath = process.env.LOCALAPPDATA || process.env.APPDATA || os.homedir();
break;
case "linux":
basePath = os.homedir();
break;
case "darwin":
basePath = combinePaths(os.homedir(), "Library/Application Support/")
break;
}
if (basePath) {
this.cachePath = combinePaths(normalizeSlashes(basePath), "Microsoft/TypeScript");
}
} }
attach(projectService: ProjectService) { attach(projectService: ProjectService) {
@ -198,13 +178,7 @@ namespace ts.server {
} }
enqueueInstallTypingsRequest(project: Project, typingOptions: TypingOptions): void { enqueueInstallTypingsRequest(project: Project, typingOptions: TypingOptions): void {
const request = createInstallTypingsRequest( const request = createInstallTypingsRequest(project, typingOptions);
project,
typingOptions,
/*safeListPath*/ <Path>(combinePaths(process.cwd(), "typingSafeList.json")), // TODO: fixme
/*packageNameToTypingLocation*/ createMap<string>(), // TODO: fixme
this.cachePath
);
if (this.logger.hasLevel(LogLevel.verbose)) { if (this.logger.hasLevel(LogLevel.verbose)) {
this.logger.info(`Sending request: ${JSON.stringify(request)}`); this.logger.info(`Sending request: ${JSON.stringify(request)}`);
} }

View file

@ -7,11 +7,9 @@ declare namespace ts.server {
readonly projectName: string; readonly projectName: string;
readonly fileNames: string[]; readonly fileNames: string[];
readonly projectRootPath: ts.Path; readonly projectRootPath: ts.Path;
readonly safeListPath: ts.Path;
readonly packageNameToTypingLocation: ts.Map<string>;
readonly typingOptions: ts.TypingOptions; readonly typingOptions: ts.TypingOptions;
readonly compilerOptions: ts.CompilerOptions; readonly compilerOptions: ts.CompilerOptions;
readonly cachePath: string; readonly cachePath?: string;
} }
export interface CompressedData { export interface CompressedData {

View file

@ -2,13 +2,39 @@
/// <reference types="node" /> /// <reference types="node" />
namespace ts.server.typingsInstaller { namespace ts.server.typingsInstaller {
const os: {
homedir(): string
} = require("os");
function getGlobalCacheLocation() {
let basePath: string;
switch (process.platform) {
case "win32":
basePath = process.env.LOCALAPPDATA || process.env.APPDATA || os.homedir();
break;
case "linux":
basePath = os.homedir();
break;
case "darwin":
basePath = combinePaths(os.homedir(), "Library/Application Support/")
break;
}
Debug.assert(basePath !== undefined);
return combinePaths(normalizeSlashes(basePath), "Microsoft/TypeScript");
}
export class NodeTypingsInstaller extends TypingsInstaller { export class NodeTypingsInstaller extends TypingsInstaller {
private execSync: { (command: string, options: { stdio: "ignore" }): any }; private execSync: { (command: string, options: { stdio: "ignore" }): any };
private exec: { (command: string, options: { cwd: string }, callback?: (error: Error, stdout: string, stderr: string) => void): any }; private exec: { (command: string, options: { cwd: string }, callback?: (error: Error, stdout: string, stderr: string) => void): any };
readonly installTypingHost: InstallTypingHost = sys;
constructor() { constructor() {
super(); super(getGlobalCacheLocation(), toPath("typingSafeList.json", __dirname, createGetCanonicalFileName(sys.useCaseSensitiveFileNames)));
this.execSync = require("child_process").execSync; const { exec, execSync } = require("child_process");
this.exec = require("child_process").exec; this.execSync = execSync;
this.exec = exec;
} }
init() { init() {
@ -38,10 +64,6 @@ namespace ts.server.typingsInstaller {
} }
} }
protected getInstallTypingHost() {
return sys;
}
protected sendResponse(response: InstallTypingsResponse) { protected sendResponse(response: InstallTypingsResponse) {
process.send(response); process.send(response);
} }

View file

@ -9,16 +9,38 @@ namespace ts.server.typingsInstaller {
path: "typings" path: "typings"
}, /*replacer*/undefined, /*space*/4); }, /*replacer*/undefined, /*space*/4);
interface TsdConfig {
installed: MapLike<any>;
}
function tsdTypingToFileName(cachePath: string, tsdTypingFile: string) {
return combinePaths(cachePath, `typings/${tsdTypingFile}`);
}
function getPackageName(tsdTypingFile: string) {
const idx = tsdTypingFile.indexOf("/");
return idx > 0 ? tsdTypingFile.substr(0, idx) : undefined;
}
export abstract class TypingsInstaller { export abstract class TypingsInstaller {
private isTsdInstalled: boolean; private isTsdInstalled: boolean;
private missingTypings: Map<string> = createMap<string>();
private packageNameToTypingLocation: Map<string> = createMap<string>();
private missingTypingsSet: Map<true> = createMap<true>();
private knownCachesSet: Map<true> = createMap<true>();
abstract readonly installTypingHost: InstallTypingHost;
constructor(readonly globalCachePath: string, readonly safeListPath: Path) {
}
init() { init() {
this.isTsdInstalled = this.isPackageInstalled("tsd"); this.isTsdInstalled = this.isPackageInstalled("tsd");
if (!this.isTsdInstalled) { if (!this.isTsdInstalled) {
this.isTsdInstalled = this.installPackage("tsd"); this.isTsdInstalled = this.installPackage("tsd");
} }
this.processCacheLocation(this.globalCachePath);
} }
install(req: InstallTypingsRequest) { install(req: InstallTypingsRequest) {
@ -26,43 +48,91 @@ namespace ts.server.typingsInstaller {
return; return;
} }
// load existing typing information from the cache
if (req.cachePath) {
this.processCacheLocation(req.cachePath);
}
const discoverTypingsResult = JsTyping.discoverTypings( const discoverTypingsResult = JsTyping.discoverTypings(
this.getInstallTypingHost(), this.installTypingHost,
req.fileNames, req.fileNames,
req.projectRootPath, req.projectRootPath,
req.safeListPath, this.safeListPath,
req.packageNameToTypingLocation, this.packageNameToTypingLocation,
req.typingOptions, req.typingOptions,
req.compilerOptions); req.compilerOptions);
// respond with whatever cached typings we have now // respond with whatever cached typings we have now
this.sendResponse(this.createResponse(req, discoverTypingsResult.cachedTypingPaths)); this.sendResponse(this.createResponse(req, discoverTypingsResult.cachedTypingPaths));
// start watching files // start watching files
this.watchFiles(discoverTypingsResult.filesToWatch); this.watchFiles(discoverTypingsResult.filesToWatch);
// install typings and // install typings and
this.installTypings(req, discoverTypingsResult.cachedTypingPaths, discoverTypingsResult.newTypingNames); this.installTypings(req, discoverTypingsResult.cachedTypingPaths, discoverTypingsResult.newTypingNames);
} }
private processCacheLocation(cacheLocation: string) {
if (this.knownCachesSet[cacheLocation]) {
return;
}
const tsdJson = combinePaths(cacheLocation, "tsd.json");
if (this.installTypingHost.fileExists(tsdJson)) {
const tsdConfig = <TsdConfig>JSON.parse(this.installTypingHost.readFile(tsdJson));
if (tsdConfig.installed) {
for (const key in tsdConfig.installed) {
// key is <package name>/<typing file>
const packageName = getPackageName(key);
if (!packageName) {
continue;
}
const typingFile = tsdTypingToFileName(cacheLocation, key);
const existingTypingFile = this.packageNameToTypingLocation[packageName];
if (existingTypingFile === typingFile) {
continue;
}
if (existingTypingFile) {
// TODO: log warning
}
this.packageNameToTypingLocation[packageName] = typingFile;
}
}
}
this.knownCachesSet[cacheLocation] = true;
}
private installTypings(req: InstallTypingsRequest, currentlyCachedTypings: string[], typingsToInstall: string[]) { private installTypings(req: InstallTypingsRequest, currentlyCachedTypings: string[], typingsToInstall: string[]) {
typingsToInstall = filter(typingsToInstall, x => !hasProperty(this.missingTypings, x)); typingsToInstall = filter(typingsToInstall, x => !this.missingTypingsSet[x]);
if (typingsToInstall.length === 0) { if (typingsToInstall.length === 0) {
return; return;
} }
// TODO: install typings and send response when they are ready // TODO: install typings and send response when they are ready
const host = this.getInstallTypingHost();
const tsdPath = combinePaths(req.cachePath, "tsd.json"); const tsdPath = combinePaths(req.cachePath, "tsd.json");
if (!host.fileExists(tsdPath)) { if (!this.installTypingHost.fileExists(tsdPath)) {
this.ensureDirectoryExists(req.cachePath, host); this.ensureDirectoryExists(req.cachePath, this.installTypingHost);
host.writeFile(tsdPath, DefaultTsdSettings); this.installTypingHost.writeFile(tsdPath, DefaultTsdSettings);
} }
this.runTsd(req.cachePath, typingsToInstall, installedTypings => { this.runTsd(req.cachePath, typingsToInstall, installedTypings => {
// TODO: record new missing package names
// TODO: watch project directory // TODO: watch project directory
const typingDirectory = combinePaths(req.cachePath, "typings"); const installedPackages: Map<true> = createMap<true>();
installedTypings = installedTypings.map(x => combinePaths(typingDirectory, x)); const installedTypingFiles: string[] = [];
this.sendResponse(this.createResponse(req, currentlyCachedTypings.concat(installedTypings))); for (const t of installedTypings) {
const packageName = getPackageName(t);
if (!packageName) {
continue;
}
installedPackages[packageName] = true;
installedTypingFiles.push(tsdTypingToFileName(req.cachePath, t));
}
for (const toInstall of typingsToInstall) {
if (!installedPackages[toInstall]) {
this.missingTypingsSet[toInstall] = true;
}
}
this.sendResponse(this.createResponse(req, currentlyCachedTypings.concat(installedTypingFiles)));
}); });
} }
@ -91,7 +161,6 @@ namespace ts.server.typingsInstaller {
protected abstract isPackageInstalled(packageName: string): boolean; protected abstract isPackageInstalled(packageName: string): boolean;
protected abstract installPackage(packageName: string): boolean; protected abstract installPackage(packageName: string): boolean;
protected abstract getInstallTypingHost(): InstallTypingHost;
protected abstract sendResponse(response: InstallTypingsResponse): void; protected abstract sendResponse(response: InstallTypingsResponse): void;
protected abstract runTsd(cachePath: string, typingsToInstall: string[], postInstallAction: (installedTypings: string[]) => void): void; protected abstract runTsd(cachePath: string, typingsToInstall: string[], postInstallAction: (installedTypings: string[]) => void): void;
} }

View file

@ -1,4 +1,3 @@
/// <reference path="..\services\services.ts" />
/// <reference path="types.d.ts" /> /// <reference path="types.d.ts" />
namespace ts.server { namespace ts.server {
@ -30,15 +29,13 @@ namespace ts.server {
export type Types = Err | Info | Perf; export type Types = Err | Info | Perf;
} }
export function createInstallTypingsRequest(project: Project, typingOptions: TypingOptions, safeListPath: Path, packageNameToTypingLocation: Map<string>, cachePath: string): InstallTypingsRequest { export function createInstallTypingsRequest(project: Project, typingOptions: TypingOptions, cachePath?: string): InstallTypingsRequest {
return { return {
projectName: project.getProjectName(), projectName: project.getProjectName(),
fileNames: project.getFileNames(), fileNames: project.getFileNames(),
compilerOptions: project.getCompilerOptions(), compilerOptions: project.getCompilerOptions(),
typingOptions, typingOptions,
projectRootPath: <Path>(project.projectKind === ProjectKind.Inferred ? "" : getDirectoryPath(project.getProjectName())), // TODO: fixme projectRootPath: <Path>(project.projectKind === ProjectKind.Inferred ? "" : getDirectoryPath(project.getProjectName())), // TODO: fixme
safeListPath,
packageNameToTypingLocation,
cachePath cachePath
}; };
} }