Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 0 additions & 56 deletions lib/definitions/extensibility.d.ts

This file was deleted.

36 changes: 32 additions & 4 deletions lib/services/extensibility-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export class ExtensibilityService implements IExtensibilityService {
const installResultInfo = await this.$npm.install(packageName, this.pathToExtensions, npmOpts);
this.$logger.trace(`Finished installation of extension '${extensionName}'. Trying to load it now.`);

return { extensionName: installResultInfo.name };
return this.getInstalledExtensionData(installResultInfo.name);
}

@exported("extensibilityService")
Expand All @@ -49,8 +49,13 @@ export class ExtensibilityService implements IExtensibilityService {
this.$logger.trace(`Finished uninstallation of extension '${extensionName}'.`);
}

public getInstalledExtensionsData(): IExtensionData[] {
const installedExtensions = this.getInstalledExtensions();
return _.keys(installedExtensions).map(installedExtension => this.getInstalledExtensionData(installedExtension));
}

@exported("extensibilityService")
public loadExtensions(): Promise<any>[] {
public loadExtensions(): Promise<IExtensionData>[] {
this.$logger.trace("Loading extensions.");

let dependencies: IStringDictionary = null;
Expand All @@ -74,14 +79,26 @@ export class ExtensibilityService implements IExtensibilityService {
return null;
}

private getInstalledExtensionData(extensionName: string): IExtensionData {
const packageJsonData = this.getExtensionPackageJsonData(extensionName);
const pathToExtension = this.getPathToExtension(extensionName);
const docs = packageJsonData && packageJsonData.nativescript && packageJsonData.nativescript.docs && path.join(pathToExtension, packageJsonData.nativescript.docs);
return {
extensionName: packageJsonData.name,
version: packageJsonData.version,
docs,
pathToExtension
};
}

@exported("extensibilityService")
public async loadExtension(extensionName: string): Promise<IExtensionData> {
try {
await this.assertExtensionIsInstalled(extensionName);

const pathToExtension = path.join(this.pathToExtensions, constants.NODE_MODULES_FOLDER_NAME, extensionName);
const pathToExtension = this.getPathToExtension(extensionName);
this.$requireService.require(pathToExtension);
return { extensionName };
return this.getInstalledExtensionData(extensionName);
} catch (error) {
this.$logger.warn(`Error while loading ${extensionName} is: ${error.message}`);
const err = <IExtensionLoadingError>new Error(`Unable to load extension ${extensionName}. You will not be able to use the functionality that it adds. Error: ${error.message}`);
Expand All @@ -90,6 +107,17 @@ export class ExtensibilityService implements IExtensibilityService {
}
}

private getPathToExtension(extensionName: string): string {
return path.join(this.pathToExtensions, constants.NODE_MODULES_FOLDER_NAME, extensionName);
}

private getExtensionPackageJsonData(extensionName: string): any {
const pathToExtension = this.getPathToExtension(extensionName);
const pathToPackageJson = path.join(pathToExtension, constants.PACKAGE_JSON_FILE_NAME);
const jsonData = this.$fs.readJson(pathToPackageJson);
return jsonData;
}

private async assertExtensionIsInstalled(extensionName: string): Promise<void> {
this.$logger.trace(`Asserting extension ${extensionName} is installed.`);
const installedExtensions = this.$fs.readDirectory(path.join(this.pathToExtensions, constants.NODE_MODULES_FOLDER_NAME));
Expand Down
96 changes: 50 additions & 46 deletions test/services/extensibility-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ describe("extensibilityService", () => {

const getTestInjector = (): IInjector => {
const testInjector = new Yok();
testInjector.register("fs", {});
testInjector.register("fs", {
readJson: (pathToFile: string): any => ({})
});
testInjector.register("logger", stubs.LoggerStub);
testInjector.register("npm", {});
testInjector.register("settingsService", SettingsService);
Expand All @@ -28,6 +30,33 @@ describe("extensibilityService", () => {
return testInjector;
};

const getExpectedInstallationPathForExtension = (testInjector: IInjector, extensionName: string): string => {
const settingsService = testInjector.resolve<ISettingsService>("settingsService");
const profileDir = settingsService.getProfileDir();

return path.join(profileDir, "extensions", "node_modules", extensionName);
};

const mockFsReadJson = (testInjector: IInjector, extensionNames: string[]): void => {
const fs = testInjector.resolve<IFileSystem>("fs");
fs.readJson = (filename: string, encoding?: string): any => {
const extensionName = _.find(extensionNames, extName => filename.indexOf(extName) !== -1);
if (extensionName) {
return {
name: extensionName,
version: "1.0.0"
};
}

const dependencies: any = {};
_.each(extensionNames, name => {
dependencies[name] = "1.0.0";
});

return { dependencies };
};
};

describe("installExtension", () => {
describe("fails", () => {
it("when extensions dir does not exist and trying to create it fails", async () => {
Expand Down Expand Up @@ -133,17 +162,20 @@ describe("extensibilityService", () => {
it("returns the name of the installed extension", async () => {
const extensionName = "extension1";
const testInjector = getTestInjector();

const fs: IFileSystem = testInjector.resolve("fs");
fs.exists = (pathToCheck: string): boolean => path.basename(pathToCheck) !== extensionName;

fs.readDirectory = (dir: string): string[] => [extensionName];

fs.readJson = () => ({ name: extensionName, version: "1.0.0" });

const npm: INodePackageManager = testInjector.resolve("npm");
npm.install = async (packageName: string, pathToSave: string, config?: any): Promise<any> => ({ name: extensionName });
npm.install = async (packageName: string, pathToSave: string, config?: any): Promise<any> => ({ name: extensionName, version: "1.0.0" });

const extensibilityService: IExtensibilityService = testInjector.resolve(ExtensibilityService);
const actualResult = await extensibilityService.installExtension(extensionName);
assert.deepEqual(actualResult, { extensionName });
assert.deepEqual(actualResult, { extensionName, version: "1.0.0", docs: undefined, pathToExtension: getExpectedInstallationPathForExtension(testInjector, extensionName) });
});
});

Expand All @@ -160,16 +192,16 @@ describe("extensibilityService", () => {
return extensionNames;
};

fs.readJson = (filename: string, encoding?: string): any => {
const dependencies: any = {};
_.each(extensionNames, name => {
dependencies[name] = "1.0.0";
});

return { dependencies };
};
mockFsReadJson(testInjector, extensionNames);

const expectedResults: IExtensionData[] = _.map(extensionNames, extensionName => ({ extensionName }));
const expectedResults: IExtensionData[] = _.map(extensionNames, extensionName => (
{
extensionName,
version: "1.0.0",
pathToExtension: getExpectedInstallationPathForExtension(testInjector, extensionName),
docs: undefined
}
));

const extensibilityService: IExtensibilityService = testInjector.resolve(ExtensibilityService);
const actualResult = await Promise.all(extensibilityService.loadExtensions());
Expand All @@ -194,14 +226,7 @@ describe("extensibilityService", () => {
}
};

fs.readJson = (filename: string, encoding?: string): any => {
const dependencies: any = {};
_.each(extensionNames, name => {
dependencies[name] = "1.0.0";
});

return { dependencies };
};
mockFsReadJson(testInjector, extensionNames);

let isNpmInstallCalled = false;
const npm: INodePackageManager = testInjector.resolve("npm");
Expand All @@ -211,7 +236,7 @@ describe("extensibilityService", () => {
return { name: packageName };
};

const expectedResults: IExtensionData[] = _.map(extensionNames, extensionName => ({ extensionName }));
const expectedResults: IExtensionData[] = _.map(extensionNames, extensionName => ({ extensionName, version: "1.0.0", pathToExtension: getExpectedInstallationPathForExtension(testInjector, extensionName), docs: undefined }));

const extensibilityService: IExtensibilityService = testInjector.resolve(ExtensibilityService);
const actualResult = await Promise.all(extensibilityService.loadExtensions());
Expand All @@ -230,14 +255,7 @@ describe("extensibilityService", () => {
return extensionNames;
};

fs.readJson = (filename: string, encoding?: string): any => {
const dependencies: any = {};
_.each(extensionNames, name => {
dependencies[name] = "1.0.0";
});

return { dependencies };
};
mockFsReadJson(testInjector, extensionNames);

const requireService: IRequireService = testInjector.resolve("requireService");
requireService.require = (module: string) => {
Expand All @@ -246,7 +264,7 @@ describe("extensibilityService", () => {
}
};

const expectedResults: any[] = _.map(extensionNames, extensionName => ({ extensionName }));
const expectedResults: any[] = _.map(extensionNames, extensionName => ({ extensionName, version: "1.0.0", pathToExtension: getExpectedInstallationPathForExtension(testInjector, extensionName), docs: undefined }));
expectedResults[0] = new Error("Unable to load extension extension1. You will not be able to use the functionality that it adds. Error: Unable to load module.");
const extensibilityService: IExtensibilityService = testInjector.resolve(ExtensibilityService);
const promises = extensibilityService.loadExtensions();
Expand All @@ -269,14 +287,7 @@ describe("extensibilityService", () => {
const fs: IFileSystem = testInjector.resolve("fs");
const expectedErrorMessage = `Unable to read ${constants.NODE_MODULES_FOLDER_NAME} dir.`;
fs.exists = (pathToCheck: string): boolean => path.basename(pathToCheck) === "extensions" || path.basename(pathToCheck) === constants.PACKAGE_JSON_FILE_NAME;
fs.readJson = (filename: string, encoding?: string): any => {
const dependencies: any = {};
_.each(extensionNames, name => {
dependencies[name] = "1.0.0";
});

return { dependencies };
};
mockFsReadJson(testInjector, extensionNames);

let isReadDirCalled = false;
fs.readDirectory = (dir: string): string[] => {
Expand Down Expand Up @@ -310,14 +321,7 @@ describe("extensibilityService", () => {
"expected 'extension3' to deeply equal 'extension1'"];
const fs: IFileSystem = testInjector.resolve("fs");
fs.exists = (pathToCheck: string): boolean => path.basename(pathToCheck) === "extensions" || path.basename(pathToCheck) === constants.PACKAGE_JSON_FILE_NAME;
fs.readJson = (filename: string, encoding?: string): any => {
const dependencies: any = {};
_.each(extensionNames, name => {
dependencies[name] = "1.0.0";
});

return { dependencies };
};
mockFsReadJson(testInjector, extensionNames);

let isReadDirCalled = false;
fs.readDirectory = (dir: string): string[] => {
Expand Down