Skip to content

Commit 6374d38

Browse files
Merge pull request #3365 from NativeScript/vladimirov/extensions-help
feat(extensions): Allow generation of help content for extension commands
2 parents 580f238 + e44029f commit 6374d38

File tree

4 files changed

+83
-107
lines changed

4 files changed

+83
-107
lines changed

lib/definitions/extensibility.d.ts

-56
This file was deleted.

lib/services/extensibility-service.ts

+32-4
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export class ExtensibilityService implements IExtensibilityService {
3535
const installResultInfo = await this.$npm.install(packageName, this.pathToExtensions, npmOpts);
3636
this.$logger.trace(`Finished installation of extension '${extensionName}'. Trying to load it now.`);
3737

38-
return { extensionName: installResultInfo.name };
38+
return this.getInstalledExtensionData(installResultInfo.name);
3939
}
4040

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

52+
public getInstalledExtensionsData(): IExtensionData[] {
53+
const installedExtensions = this.getInstalledExtensions();
54+
return _.keys(installedExtensions).map(installedExtension => this.getInstalledExtensionData(installedExtension));
55+
}
56+
5257
@exported("extensibilityService")
53-
public loadExtensions(): Promise<any>[] {
58+
public loadExtensions(): Promise<IExtensionData>[] {
5459
this.$logger.trace("Loading extensions.");
5560

5661
let dependencies: IStringDictionary = null;
@@ -74,14 +79,26 @@ export class ExtensibilityService implements IExtensibilityService {
7479
return null;
7580
}
7681

82+
private getInstalledExtensionData(extensionName: string): IExtensionData {
83+
const packageJsonData = this.getExtensionPackageJsonData(extensionName);
84+
const pathToExtension = this.getPathToExtension(extensionName);
85+
const docs = packageJsonData && packageJsonData.nativescript && packageJsonData.nativescript.docs && path.join(pathToExtension, packageJsonData.nativescript.docs);
86+
return {
87+
extensionName: packageJsonData.name,
88+
version: packageJsonData.version,
89+
docs,
90+
pathToExtension
91+
};
92+
}
93+
7794
@exported("extensibilityService")
7895
public async loadExtension(extensionName: string): Promise<IExtensionData> {
7996
try {
8097
await this.assertExtensionIsInstalled(extensionName);
8198

82-
const pathToExtension = path.join(this.pathToExtensions, constants.NODE_MODULES_FOLDER_NAME, extensionName);
99+
const pathToExtension = this.getPathToExtension(extensionName);
83100
this.$requireService.require(pathToExtension);
84-
return { extensionName };
101+
return this.getInstalledExtensionData(extensionName);
85102
} catch (error) {
86103
this.$logger.warn(`Error while loading ${extensionName} is: ${error.message}`);
87104
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}`);
@@ -90,6 +107,17 @@ export class ExtensibilityService implements IExtensibilityService {
90107
}
91108
}
92109

110+
private getPathToExtension(extensionName: string): string {
111+
return path.join(this.pathToExtensions, constants.NODE_MODULES_FOLDER_NAME, extensionName);
112+
}
113+
114+
private getExtensionPackageJsonData(extensionName: string): any {
115+
const pathToExtension = this.getPathToExtension(extensionName);
116+
const pathToPackageJson = path.join(pathToExtension, constants.PACKAGE_JSON_FILE_NAME);
117+
const jsonData = this.$fs.readJson(pathToPackageJson);
118+
return jsonData;
119+
}
120+
93121
private async assertExtensionIsInstalled(extensionName: string): Promise<void> {
94122
this.$logger.trace(`Asserting extension ${extensionName} is installed.`);
95123
const installedExtensions = this.$fs.readDirectory(path.join(this.pathToExtensions, constants.NODE_MODULES_FOLDER_NAME));

test/services/extensibility-service.ts

+50-46
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ describe("extensibilityService", () => {
1818

1919
const getTestInjector = (): IInjector => {
2020
const testInjector = new Yok();
21-
testInjector.register("fs", {});
21+
testInjector.register("fs", {
22+
readJson: (pathToFile: string): any => ({})
23+
});
2224
testInjector.register("logger", stubs.LoggerStub);
2325
testInjector.register("npm", {});
2426
testInjector.register("settingsService", SettingsService);
@@ -28,6 +30,33 @@ describe("extensibilityService", () => {
2830
return testInjector;
2931
};
3032

33+
const getExpectedInstallationPathForExtension = (testInjector: IInjector, extensionName: string): string => {
34+
const settingsService = testInjector.resolve<ISettingsService>("settingsService");
35+
const profileDir = settingsService.getProfileDir();
36+
37+
return path.join(profileDir, "extensions", "node_modules", extensionName);
38+
};
39+
40+
const mockFsReadJson = (testInjector: IInjector, extensionNames: string[]): void => {
41+
const fs = testInjector.resolve<IFileSystem>("fs");
42+
fs.readJson = (filename: string, encoding?: string): any => {
43+
const extensionName = _.find(extensionNames, extName => filename.indexOf(extName) !== -1);
44+
if (extensionName) {
45+
return {
46+
name: extensionName,
47+
version: "1.0.0"
48+
};
49+
}
50+
51+
const dependencies: any = {};
52+
_.each(extensionNames, name => {
53+
dependencies[name] = "1.0.0";
54+
});
55+
56+
return { dependencies };
57+
};
58+
};
59+
3160
describe("installExtension", () => {
3261
describe("fails", () => {
3362
it("when extensions dir does not exist and trying to create it fails", async () => {
@@ -133,17 +162,20 @@ describe("extensibilityService", () => {
133162
it("returns the name of the installed extension", async () => {
134163
const extensionName = "extension1";
135164
const testInjector = getTestInjector();
165+
136166
const fs: IFileSystem = testInjector.resolve("fs");
137167
fs.exists = (pathToCheck: string): boolean => path.basename(pathToCheck) !== extensionName;
138168

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

171+
fs.readJson = () => ({ name: extensionName, version: "1.0.0" });
172+
141173
const npm: INodePackageManager = testInjector.resolve("npm");
142-
npm.install = async (packageName: string, pathToSave: string, config?: any): Promise<any> => ({ name: extensionName });
174+
npm.install = async (packageName: string, pathToSave: string, config?: any): Promise<any> => ({ name: extensionName, version: "1.0.0" });
143175

144176
const extensibilityService: IExtensibilityService = testInjector.resolve(ExtensibilityService);
145177
const actualResult = await extensibilityService.installExtension(extensionName);
146-
assert.deepEqual(actualResult, { extensionName });
178+
assert.deepEqual(actualResult, { extensionName, version: "1.0.0", docs: undefined, pathToExtension: getExpectedInstallationPathForExtension(testInjector, extensionName) });
147179
});
148180
});
149181

@@ -160,16 +192,16 @@ describe("extensibilityService", () => {
160192
return extensionNames;
161193
};
162194

163-
fs.readJson = (filename: string, encoding?: string): any => {
164-
const dependencies: any = {};
165-
_.each(extensionNames, name => {
166-
dependencies[name] = "1.0.0";
167-
});
168-
169-
return { dependencies };
170-
};
195+
mockFsReadJson(testInjector, extensionNames);
171196

172-
const expectedResults: IExtensionData[] = _.map(extensionNames, extensionName => ({ extensionName }));
197+
const expectedResults: IExtensionData[] = _.map(extensionNames, extensionName => (
198+
{
199+
extensionName,
200+
version: "1.0.0",
201+
pathToExtension: getExpectedInstallationPathForExtension(testInjector, extensionName),
202+
docs: undefined
203+
}
204+
));
173205

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

197-
fs.readJson = (filename: string, encoding?: string): any => {
198-
const dependencies: any = {};
199-
_.each(extensionNames, name => {
200-
dependencies[name] = "1.0.0";
201-
});
202-
203-
return { dependencies };
204-
};
229+
mockFsReadJson(testInjector, extensionNames);
205230

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

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

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

233-
fs.readJson = (filename: string, encoding?: string): any => {
234-
const dependencies: any = {};
235-
_.each(extensionNames, name => {
236-
dependencies[name] = "1.0.0";
237-
});
238-
239-
return { dependencies };
240-
};
258+
mockFsReadJson(testInjector, extensionNames);
241259

242260
const requireService: IRequireService = testInjector.resolve("requireService");
243261
requireService.require = (module: string) => {
@@ -246,7 +264,7 @@ describe("extensibilityService", () => {
246264
}
247265
};
248266

249-
const expectedResults: any[] = _.map(extensionNames, extensionName => ({ extensionName }));
267+
const expectedResults: any[] = _.map(extensionNames, extensionName => ({ extensionName, version: "1.0.0", pathToExtension: getExpectedInstallationPathForExtension(testInjector, extensionName), docs: undefined }));
250268
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.");
251269
const extensibilityService: IExtensibilityService = testInjector.resolve(ExtensibilityService);
252270
const promises = extensibilityService.loadExtensions();
@@ -269,14 +287,7 @@ describe("extensibilityService", () => {
269287
const fs: IFileSystem = testInjector.resolve("fs");
270288
const expectedErrorMessage = `Unable to read ${constants.NODE_MODULES_FOLDER_NAME} dir.`;
271289
fs.exists = (pathToCheck: string): boolean => path.basename(pathToCheck) === "extensions" || path.basename(pathToCheck) === constants.PACKAGE_JSON_FILE_NAME;
272-
fs.readJson = (filename: string, encoding?: string): any => {
273-
const dependencies: any = {};
274-
_.each(extensionNames, name => {
275-
dependencies[name] = "1.0.0";
276-
});
277-
278-
return { dependencies };
279-
};
290+
mockFsReadJson(testInjector, extensionNames);
280291

281292
let isReadDirCalled = false;
282293
fs.readDirectory = (dir: string): string[] => {
@@ -310,14 +321,7 @@ describe("extensibilityService", () => {
310321
"expected 'extension3' to deeply equal 'extension1'"];
311322
const fs: IFileSystem = testInjector.resolve("fs");
312323
fs.exists = (pathToCheck: string): boolean => path.basename(pathToCheck) === "extensions" || path.basename(pathToCheck) === constants.PACKAGE_JSON_FILE_NAME;
313-
fs.readJson = (filename: string, encoding?: string): any => {
314-
const dependencies: any = {};
315-
_.each(extensionNames, name => {
316-
dependencies[name] = "1.0.0";
317-
});
318-
319-
return { dependencies };
320-
};
324+
mockFsReadJson(testInjector, extensionNames);
321325

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

0 commit comments

Comments
 (0)