Skip to content

Commit 9596aa1

Browse files
Implement extensibility model for CLI
Implement extensibilty of CLI that allows anyone to add easily create packages that add new functionality to NativeScript CLI. The packages are installed in a specific directory, so they are persisted through CLI's updated. The directory where extensions are installed contains a package.json and each extension is npm package installed there. The extensions can be mainatined in two different ways: - navigate to the directory where extensions are installed and use `npm` for install/uninstall/update of packages. - use CLI's commands to update them: `tns extension install <name>`, `tns extension uninstall <name>`, `tns extension` Implement extensibilityService that executes all operations and expose it to public API. In {N} CLI the extensions are loaded in the entry point, before parsing command line arguments. This way extensions can add new commands. In Fusion, after CLI is required as a library, the `extensibilityService.loadExtensions` method should be called. It returns array of Promises - one for each installed extension. Add help for the new commands, but do not link the new commands in other commands help for the moment. Add unit tests for the new service.
1 parent a9afa64 commit 9596aa1

16 files changed

+1110
-86
lines changed

PublicAPI.md

+121-80
Original file line numberDiff line numberDiff line change
@@ -3,35 +3,17 @@ Public API
33

44
This document describes all methods that can be invoked when NativeScript CLI is required as library, i.e.
55

6-
<table>
7-
<tr>
8-
<td>
9-
JavaScript
10-
</td>
11-
<td>
12-
TypeScript
13-
</td>
14-
</tr>
15-
<tr>
16-
<td>
17-
<pre lang="javascript">
6+
```JavaScript
187
const tns = require("nativescript");
19-
</pre>
20-
</td>
21-
<td>
22-
<pre lang="typescript">
23-
import * as tns from "nativescript";
24-
</pre>
25-
</td>
26-
</tr>
27-
28-
</table>
8+
```
299

3010
## Module projectService
3111

3212
`projectService` modules allow you to create new NativeScript application.
3313

34-
* `createProject(projectSettings: IProjectSettings): Promise<void>` - Creates new NativeScript application. By passing `projectSettings` argument you specify the name of the application, the template that will be used, etc.:
14+
### createProject
15+
* Description: `createProject(projectSettings: IProjectSettings): Promise<void>` - Creates new NativeScript application. By passing `projectSettings` argument you specify the name of the application, the template that will be used, etc.:
16+
3517
```TypeScript
3618
/**
3719
* Describes available settings when creating new NativeScript application.
@@ -73,71 +55,130 @@ interface IProjectSettings {
7355
}
7456
```
7557

76-
Sample usage:
77-
<table>
78-
<tr>
79-
<td>
80-
JavaScript
81-
</td>
82-
<td>
83-
TypeScript
84-
</td>
85-
</tr>
86-
<tr>
87-
<td>
88-
<pre lang="javascript">
58+
* Sample usage:
59+
```JavaScript
8960
const projectSettings = {
9061
projectName: "my-ns-app",
91-
template: "ng",
92-
pathToProject: "/home/my-user/project-dir"
62+
template: "ng",
63+
pathToProject: "/home/my-user/project-dir"
9364
};
9465

9566
tns.projectService.createProject(projectSettings)
9667
.then(() => console.log("Project successfully created."))
97-
.catch((err) => console.log("Unable to create project, reason: ", err);
98-
</pre>
99-
</td>
100-
<td>
101-
<pre lang="typescript">
102-
const projectSettings: IProjectSettings = {
103-
projectName: "my-ns-app",
104-
template: "ng",
105-
pathToProject: "/home/my-user/project-dir"
106-
};
68+
.catch((err) => console.log("Unable to create project, reason: ", err);
69+
```
10770
108-
tns.projectService.createProject(projectSettings)
109-
.then(() => console.log("Project successfully created."))
110-
.catch((err) => console.log("Unable to create project, reason: ", err);
111-
</pre>
112-
</td>
113-
</tr>
114-
</table>
115-
116-
* `isValidNativeScriptProject(projectDir: string): boolean` - Checks if the specified path is a valid NativeScript project. Returns `true` in case the directory is a valid project, `false` otherwise.
117-
118-
Sample usage:
119-
<table>
120-
<tr>
121-
<td>
122-
JavaScript
123-
</td>
124-
<td>
125-
TypeScript
126-
</td>
127-
</tr>
128-
<tr>
129-
<td>
130-
<pre lang="javascript">
131-
const isValidProject = tns.projectService.isValidNativeScriptProject("/tmp/myProject");
132-
</pre>
133-
</td>
134-
<td>
135-
<pre lang="typescript">
71+
### isValidNativeScriptProject
72+
* Definition: `isValidNativeScriptProject(projectDir: string): boolean` - Checks if the specified path is a valid NativeScript project. Returns `true` in case the directory is a valid project, `false` otherwise.
73+
74+
* Sample usage:
75+
```JavaScript
13676
const isValidProject = tns.projectService.isValidNativeScriptProject("/tmp/myProject");
137-
</pre>
138-
</td>
139-
</tr>
140-
</table>
77+
console.log(isValidProject); // true or false
78+
```
79+
80+
## extensibilityService
81+
`extensibilityService` module gives access to methods for working with CLI's extensions - list, install, uninstall, load them. The extensions add new functionality to CLI, so once an extension is loaded, all methods added to it's public API are accessible directly through CLI when it is used as a library. Extensions may also add new commands, so they are accessible through command line when using NativeScript CLI.
82+
83+
A common interface describing the results of a method is `IExtensionData`:
84+
```TypeScript
85+
/**
86+
* Describes each extension.
87+
*/
88+
interface IExtensionData {
89+
/**
90+
* The name of the extension.
91+
*/
92+
extensionName: string;
93+
}
94+
```
95+
96+
### installExtension
97+
Installs specified extension and loads it in the current process, so the functionality that it adds can be used immediately.
98+
99+
* Definition:
100+
```TypeScript
101+
/**
102+
* Installs and loads specified extension.
103+
* @param {string} extensionName Name of the extension to be installed. It may contain version as well, i.e. myPackage, [email protected], myPackage.tgz, https://github.com/myOrganization/myPackage/tarball/master, https://github.com/myOrganization/myPackage etc.
104+
* @returns {Promise<IExtensionData>} Information about installed extensions.
105+
*/
106+
installExtension(extensionName: string): Promise<IExtensionData>;
107+
```
108+
109+
* Usage:
110+
```JavaScript
111+
tns.extensibilityService.installExtension("extension-package")
112+
.then(extensionData => console.log(`Successfully installed extension ${extensionData.extensionName}.`))
113+
.catch(err => console.log("Failed to install extension."));
114+
```
115+
116+
### uninstallExtension
117+
Uninstalls specified extensions, so its functionality will no longer be available through CLI.
118+
119+
* Definition:
120+
```TypeScript
121+
/**
122+
* Uninstalls extension from the installation.
123+
* @param {string} extensionName Name of the extension to be uninstalled.
124+
* @returns {Promise<void>}
125+
*/
126+
uninstallExtension(extensionName: string): Promise<void>;
127+
```
128+
129+
* Usage:
130+
```JavaScript
131+
tns.extensibilityService.uninstallExtension("extension-package")
132+
.then(() => console.log("Successfully uninstalled extension."))
133+
.catch(err => console.log("Failed to uninstall extension."));
134+
```
135+
136+
### getInstalledExtensions
137+
Gets information about all installed extensions.
138+
139+
* Definition:
140+
```TypeScript
141+
/**
142+
* Gets information about installed dependencies - names and versions.
143+
* @returns {IStringDictionary}
144+
*/
145+
getInstalledExtensions(): IStringDictionary;
146+
```
147+
148+
* Usage:
149+
```JavaScript
150+
const installedExtensions = tns.extensibilityService.getInstalledExtensions();
151+
for (let extensionName in installedExtensions) {
152+
const version = installedExtensions[extensionName];
153+
console.log(`The extension ${extensionName} is installed with version ${version}.`);
154+
}
155+
```
156+
157+
### loadExtensions
158+
Loads all currently installed extensions. The method returns array of Promises, one for each installed extension. In case any of the extensions cannot be loaded, only its Promise is rejected.
159+
160+
* Definition
161+
```TypeScript
162+
/**
163+
* Loads all extensions, so their methods and commands can be used from CLI.
164+
* For each of the extensions, a new Promise is returned. It will be rejected in case the extension cannot be loaded. However other promises will not be reflected by this failure.
165+
* In case a promise is rejected, the error will have additional property (extensionName) that shows which is the extension that cannot be loaded in the process.
166+
* @returns {Promise<IExtensionData>[]} Array of promises, each is resolved with information about loaded extension.
167+
*/
168+
loadExtensions(): Promise<IExtensionData>[];
169+
```
170+
171+
* Usage:
172+
```JavaScript
173+
const loadExtensionsPromises = tns.extensibilityService.loadExtensions();
174+
for (let promise of loadExtensionsPromises) {
175+
promise.then(extensionData => console.log(`Loaded extension: ${extensionData.extensionName}.`),
176+
err => {
177+
console.log(`Failed to load extension: ${err.extensionName}`);
178+
console.log(err);
179+
});
180+
}
181+
```
141182
142183
## How to add a new method to Public API
143184
CLI is designed as command line tool and when it is used as a library, it does not give you access to all of the methods. This is mainly implementation detail. Most of the CLI's code is created to work in command line, not as a library, so before adding method to public API, most probably it will require some modification.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
extension install
2+
==========
3+
4+
Usage | Synopsis
5+
------|-------
6+
General | `$ tns extension install <Extension>`
7+
8+
Installs specified extension. Each extension adds additional functionality that's accessible directly from NativeScript CLI.
9+
10+
### Attributes
11+
12+
* `<Extension>` is any of the following.
13+
* A `<Name>` or `<Name>@<Version>` where `<Name>` is the name of a package that is published in the npm registry and `<Version>` is a valid version of this plugin.
14+
* A `<Local Path>` to the directory which contains the extension, including its `package.json` file.
15+
* A `<Local Path>` to a `.tar.gz` archive containing a directory with the extension and its `package.json` file.
16+
* A `<URL>` which resolves to a `.tar.gz` archive containing a directory with the extension and its `package.json` file.
17+
* A `<git Remote URL>` which resolves to a `.tar.gz` archive containing a directory with the extension and its `package.json` file.
18+
19+
<% if(isHtml) { %>
20+
### Related Commands
21+
22+
Command | Description
23+
----------|----------
24+
[extension](extension.html) | Prints information about all installed extensions.
25+
[extension-uninstall](extension-uninstall.html) | Uninstalls specified extension.
26+
[autocomplete-status](autocomplete-status.html) | Prints the current status of your command-line completion settings.
27+
[autocomplete-enable](autocomplete-enable.html) | Configures your current command-line completion settings.
28+
[autocomplete-disable](autocomplete-disable.html) | Disables command-line completion for bash and zsh shells.
29+
[usage-reporting](usage-reporting.html) | Configures anonymous usage reporting for the NativeScript CLI.
30+
[error-reporting](error-reporting.html) | Configures anonymous error reporting for the NativeScript CLI.
31+
[doctor](doctor.html) | Checks your system for configuration problems which might prevent the NativeScript CLI from working properly.
32+
[proxy](proxy.html) | Displays proxy settings.
33+
[proxy clear](proxy-clear.html) | Clears proxy settings.
34+
[proxy set](proxy-set.html) | Sets proxy settings.
35+
<% } %>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
extension uninstall
2+
==========
3+
4+
Usage | Synopsis
5+
------|-------
6+
General | `$ tns extension uninstall <Extension>`
7+
8+
Uninstalls specified extension. After that you will not be able to use the functionality that this extensions adds to NativeScript CLI.
9+
10+
### Attributes
11+
12+
* `<Extension>` is the name of the extension as listed in its `package.json` file.
13+
14+
<% if(isHtml) { %>
15+
### Related Commands
16+
17+
Command | Description
18+
----------|----------
19+
[extension](extension.html) | Prints information about all installed extensions.
20+
[extension-uninstall](extension-uninstall.html) | Uninstalls specified extension.
21+
[extension-install](extension-install.html) | Installs specified extension.
22+
[autocomplete-status](autocomplete-status.html) | Prints the current status of your command-line completion settings.
23+
[autocomplete-enable](autocomplete-enable.html) | Configures your current command-line completion settings.
24+
[autocomplete-disable](autocomplete-disable.html) | Disables command-line completion for bash and zsh shells.
25+
[usage-reporting](usage-reporting.html) | Configures anonymous usage reporting for the NativeScript CLI.
26+
[error-reporting](error-reporting.html) | Configures anonymous error reporting for the NativeScript CLI.
27+
[doctor](doctor.html) | Checks your system for configuration problems which might prevent the NativeScript CLI from working properly.
28+
[proxy](proxy.html) | Displays proxy settings.
29+
[proxy clear](proxy-clear.html) | Clears proxy settings.
30+
[proxy set](proxy-set.html) | Sets proxy settings.
31+
<% } %>

docs/man_pages/general/extension.md

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
extension
2+
==========
3+
4+
Usage | Synopsis
5+
------|-------
6+
General | `$ tns extension`
7+
8+
Prints information about all installed extensions.
9+
10+
<% if(isHtml) { %>
11+
### Related Commands
12+
13+
Command | Description
14+
----------|----------
15+
[extension-install](extension-install.html) | Installs specified extension.
16+
[autocomplete-status](autocomplete-status.html) | Prints the current status of your command-line completion settings.
17+
[autocomplete-enable](autocomplete-enable.html) | Configures your current command-line completion settings.
18+
[autocomplete-disable](autocomplete-disable.html) | Disables command-line completion for bash and zsh shells.
19+
[usage-reporting](usage-reporting.html) | Configures anonymous usage reporting for the NativeScript CLI.
20+
[error-reporting](error-reporting.html) | Configures anonymous error reporting for the NativeScript CLI.
21+
[doctor](doctor.html) | Checks your system for configuration problems which might prevent the NativeScript CLI from working properly.
22+
[proxy](proxy.html) | Displays proxy settings.
23+
[proxy clear](proxy-clear.html) | Clears proxy settings.
24+
[proxy set](proxy-set.html) | Sets proxy settings.
25+
<% } %>

lib/bootstrap.ts

+7
Original file line numberDiff line numberDiff line change
@@ -125,3 +125,10 @@ $injector.require("projectChangesService", "./services/project-changes-service")
125125
$injector.require("emulatorPlatformService", "./services/emulator-platform-service");
126126

127127
$injector.require("staticConfig", "./config");
128+
129+
$injector.require("requireService", "./services/require-service");
130+
131+
$injector.requireCommand("extension|*list", "./commands/extensibility/list-extensions");
132+
$injector.requireCommand("extension|install", "./commands/extensibility/install-extension");
133+
$injector.requireCommand("extension|uninstall", "./commands/extensibility/uninstall-extension");
134+
$injector.requirePublic("extensibilityService", "./services/extensibility-service");
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
export class InstallExtensionCommand implements ICommand {
2+
constructor(private $extensibilityService: IExtensibilityService,
3+
private $stringParameterBuilder: IStringParameterBuilder,
4+
private $logger: ILogger) { }
5+
6+
public async execute(args: string[]): Promise<void> {
7+
const extensionData = await this.$extensibilityService.installExtension(args[0]);
8+
this.$logger.info(`Successfully installed extension ${extensionData.extensionName}.`);
9+
}
10+
11+
allowedParameters: ICommandParameter[] = [this.$stringParameterBuilder.createMandatoryParameter("You have to provide a valid name for extension that you want to install.")];
12+
}
13+
$injector.registerCommand("extension|install", InstallExtensionCommand);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import * as helpers from "../../common/helpers";
2+
3+
export class ListExtensionsCommand implements ICommand {
4+
constructor(private $extensibilityService: IExtensibilityService,
5+
private $logger: ILogger) { }
6+
7+
public async execute(args: string[]): Promise<void> {
8+
const installedExtensions = this.$extensibilityService.getInstalledExtensions();
9+
if (_.keys(installedExtensions).length) {
10+
this.$logger.info("Installed extensions:");
11+
const data = _.map(installedExtensions, (version, name) => {
12+
return [name, version];
13+
});
14+
15+
const table = helpers.createTable(["Name", "Version"], data);
16+
this.$logger.out(table.toString());
17+
} else {
18+
this.$logger.info("No extensions installed.");
19+
}
20+
}
21+
22+
allowedParameters: ICommandParameter[] = [];
23+
}
24+
$injector.registerCommand("extension|*list", ListExtensionsCommand);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
export class UninstallExtensionCommand implements ICommand {
2+
constructor(private $extensibilityService: IExtensibilityService,
3+
private $stringParameterBuilder: IStringParameterBuilder,
4+
private $logger: ILogger) { }
5+
6+
public async execute(args: string[]): Promise<void> {
7+
const extensionName = args[0];
8+
await this.$extensibilityService.uninstallExtension(extensionName);
9+
this.$logger.info(`Successfully uninstalled extension ${extensionName}`);
10+
}
11+
12+
allowedParameters: ICommandParameter[] = [this.$stringParameterBuilder.createMandatoryParameter("You have to provide a valid name for extension that you want to uninstall.")];
13+
}
14+
$injector.registerCommand("extension|uninstall", UninstallExtensionCommand);

0 commit comments

Comments
 (0)