Skip to content

Commit 6a5ea87

Browse files
Fatme HavaluovaFatme
authored andcommitted
Plugin commands
1 parent 4c579d0 commit 6a5ea87

File tree

9 files changed

+280
-10
lines changed

9 files changed

+280
-10
lines changed

lib/bootstrap.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,7 @@ $injector.require("logcatPrinter", "./providers/logcat-printer");
5454
$injector.require("broccoliBuilder", "./tools/broccoli/builder");
5555
$injector.require("nodeModulesTree", "./tools/broccoli/trees/node-modules-tree");
5656
$injector.require("broccoliPluginWrapper", "./tools/broccoli/broccoli-plugin-wrapper");
57+
58+
$injector.require("pluginsService", "./services/plugins-service");
59+
$injector.requireCommand("plugin|add", "./commands/plugin/add-plugin");
60+
$injector.requireCommand("plugin|remove", "./commands/plugin/remove-plugin");

lib/commands/plugin/add-plugin.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
///<reference path="../../.d.ts"/>
2+
"use strict";
3+
4+
export class AddPluginCommand implements ICommand {
5+
constructor(private $pluginsService: IPluginsService,
6+
private $errors: IErrors) { }
7+
8+
execute(args: string[]): IFuture<void> {
9+
return this.$pluginsService.add(args[0]);
10+
}
11+
12+
canExecute(args: string[]): IFuture<boolean> {
13+
return (() => {
14+
if(!args[0]) {
15+
this.$errors.fail("You must specify plugin name.");
16+
}
17+
18+
let installedPlugins = this.$pluginsService.getAllInstalledPlugins().wait();
19+
let pluginName = args[0].toLowerCase();
20+
if(_.any(installedPlugins, (plugin: IPluginData) => plugin.name.toLowerCase() === pluginName)) {
21+
this.$errors.failWithoutHelp(`Plugin "${pluginName}" is already installed.`);
22+
}
23+
24+
return true;
25+
}).future<boolean>()();
26+
}
27+
28+
public allowedParameters: ICommandParameter[] = [];
29+
}
30+
$injector.registerCommand("plugin|add", AddPluginCommand);

lib/commands/plugin/remove-plugin.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
///<reference path="../../.d.ts"/>
2+
"use strict";
3+
4+
export class RemovePluginCommand implements ICommand {
5+
constructor(private $pluginsService: IPluginsService,
6+
private $errors: IErrors) { }
7+
8+
execute(args: string[]): IFuture<void> {
9+
return this.$pluginsService.remove(args[0]);
10+
}
11+
12+
canExecute(args: string[]): IFuture<boolean> {
13+
return (() => {
14+
if(!args[0]) {
15+
this.$errors.fail("You must specify plugin name.");
16+
}
17+
18+
let installedPlugins = this.$pluginsService.getAllInstalledPlugins().wait();
19+
let pluginName = args[0].toLowerCase();
20+
if(!_.any(installedPlugins, (plugin: IPluginData) => plugin.name.toLowerCase() === pluginName)) {
21+
this.$errors.failWithoutHelp(`Plugin "${pluginName}" is not installed.`);
22+
}
23+
24+
return true;
25+
}).future<boolean>()();
26+
}
27+
28+
public allowedParameters: ICommandParameter[] = [];
29+
}
30+
$injector.registerCommand("plugin|remove", RemovePluginCommand);

lib/declarations.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ interface IOptions extends ICommonOptions {
3939
emulator: boolean;
4040
symlink: boolean;
4141
forDevice: boolean;
42-
client: boolean;
42+
client: boolean
43+
production: boolean;
4344
keyStorePath: string;
4445
keyStorePassword: string;
4546
keyStoreAlias: string;

lib/definitions/plugins.d.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
interface IPluginsService {
2+
add(plugin: string): IFuture<void>; // adds plugin by name, github url, local path and et.
3+
remove(pluginName: string): IFuture<void>; // removes plugin only by name
4+
prepare(pluginData: IPluginData): IFuture<void>;
5+
getAllInstalledPlugins(): IFuture<IPluginData[]>;
6+
}
7+
8+
interface IPluginData extends INodeModuleData {
9+
platformsData: IPluginPlatformsData;
10+
}
11+
12+
interface INodeModuleData {
13+
name: string;
14+
version: string;
15+
fullPath: string;
16+
isPlugin: boolean;
17+
}
18+
19+
interface IPluginPlatformsData {
20+
ios: string;
21+
android: string;
22+
}

lib/options.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export class Options extends commonOptionsLibPath.OptionsBase {
2020
symlink: { type: OptionType.Boolean },
2121
forDevice: { type: OptionType.Boolean },
2222
client: { type: OptionType.Boolean },
23+
production: { type: OptionType.Boolean },
2324
keyStorePath: { type: OptionType.String },
2425
keyStorePassword: { type: OptionType.String,},
2526
keyStoreAlias: { type: OptionType.String },

lib/services/platform-service.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ export class PlatformService implements IPlatformService {
2222
private $prompter: IPrompter,
2323
private $commandsService: ICommandsService,
2424
private $options: IOptions,
25-
private $broccoliBuilder: IBroccoliBuilder) { }
25+
private $broccoliBuilder: IBroccoliBuilder,
26+
private $pluginsService: IPluginsService) { }
2627

2728
public addPlatforms(platforms: string[]): IFuture<void> {
2829
return (() => {
@@ -86,6 +87,10 @@ export class PlatformService implements IPlatformService {
8687
this.$fs.deleteDirectory(platformPath).wait();
8788
throw err;
8889
}
90+
91+
// Prepare installed plugins
92+
let installedPlugins = this.$pluginsService.getAllInstalledPlugins().wait();
93+
_.each(installedPlugins, pluginData => this.$pluginsService.prepare(pluginData).wait());
8994

9095
this.$logger.out("Project successfully created.");
9196

@@ -311,6 +316,12 @@ export class PlatformService implements IPlatformService {
311316
}
312317
}).future<void>()();
313318
}
319+
320+
private isPlatformInstalled(platform: string): IFuture<boolean> {
321+
return (() => {
322+
return this.$fs.exists(path.join(this.$projectData.platformsDir, platform)).wait();
323+
}).future<boolean>()();
324+
}
314325

315326
private isValidPlatform(platform: string) {
316327
return this.$platformsData.getPlatformData(platform);
@@ -326,12 +337,6 @@ export class PlatformService implements IPlatformService {
326337
return false;
327338
}
328339

329-
private isPlatformInstalled(platform: string): IFuture<boolean> {
330-
return (() => {
331-
return this.$fs.exists(path.join(this.$projectData.platformsDir, platform)).wait();
332-
}).future<boolean>()();
333-
}
334-
335340
private isPlatformPrepared(platform: string): IFuture<boolean> {
336341
var platformData = this.$platformsData.getPlatformData(platform);
337342
return platformData.platformProjectService.isPlatformPrepared(platformData.projectRoot);

lib/services/plugins-service.ts

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
///<reference path="../.d.ts"/>
2+
"use strict";
3+
import path = require("path");
4+
import shelljs = require("shelljs");
5+
import semver = require("semver");
6+
import constants = require("./../constants");
7+
8+
export class PluginsService implements IPluginsService {
9+
private static INSTALL_COMMAND_NAME = "install";
10+
private static UNINSTALL_COMMAND_NAME = "uninstall";
11+
12+
constructor(private $npm: INodePackageManager,
13+
private $fs: IFileSystem,
14+
private $projectData: IProjectData,
15+
private $platformsData: IPlatformsData,
16+
private $projectDataService: IProjectDataService,
17+
private $childProcess: IChildProcess,
18+
private $options: IOptions,
19+
private $logger: ILogger,
20+
private $errors: IErrors) { }
21+
22+
public add(plugin: string): IFuture<void> {
23+
return (() => {
24+
let pluginName = this.executeNpmCommand(PluginsService.INSTALL_COMMAND_NAME, plugin).wait();
25+
let nodeModuleData = this.getNodeModuleData(pluginName);
26+
if(!nodeModuleData.isPlugin) {
27+
// We should remove already downloaded plugin and show an error message
28+
this.executeNpmCommand(PluginsService.UNINSTALL_COMMAND_NAME, pluginName).wait();
29+
this.$errors.failWithoutHelp(`The specified plugin ${plugin} is not a valid NativeScript plugin. Ensure that the plugin contains nativescript key in package.json file and try again.`);
30+
}
31+
32+
this.prepare(this.convertToPluginData(nodeModuleData)).wait();
33+
this.$logger.out(`Succsessfully installed plugin with name ${nodeModuleData.name}.`);
34+
}).future<void>()();
35+
}
36+
37+
public remove(pluginName: string): IFuture<void> {
38+
return (() => {
39+
this.executeNpmCommand(PluginsService.UNINSTALL_COMMAND_NAME, pluginName).wait();
40+
let showMessage = true;
41+
let action = (modulesDestinationPath: string, platform: string) => {
42+
shelljs.rm("-rf", path.join(modulesDestinationPath, pluginName));
43+
this.$logger.out(`Successfully removed plugin ${pluginName} for ${platform} platform`);
44+
showMessage = false;
45+
};
46+
this.executeForAllInstalledPlatforms(action);
47+
48+
if(showMessage) {
49+
this.$logger.out(`Succsessfully removed plugin ${pluginName}`);
50+
}
51+
}).future<void>()();
52+
}
53+
54+
public prepare(pluginData: IPluginData): IFuture<void> {
55+
return (() => {
56+
let action = (pluginDestinationPath: string, platform: string) => {
57+
let skipExecution = false;
58+
// Process .js files
59+
let installedFrameworkVersion = this.getInstalledFrameworkVersion(platform).wait();
60+
let pluginVersion = (<any>pluginData.platformsData)[platform];
61+
if(semver.gt(pluginVersion, installedFrameworkVersion)) {
62+
this.$logger.warn(`Plugin ${pluginData.name} with specified version ${pluginVersion} for ${platform} platform is not compatible for currently installed framework with version ${installedFrameworkVersion}.`);
63+
skipExecution = true;
64+
}
65+
66+
if(!skipExecution) {
67+
this.$fs.ensureDirectoryExists(pluginDestinationPath).wait();
68+
shelljs.cp("-R", pluginData.fullPath, pluginDestinationPath);
69+
70+
// TODO: Merge xmls - check if android.manifest or info.plist files exist and merge them
71+
let pluginPlatformsFolderPath = path.join(pluginDestinationPath, pluginData.name, "platforms");
72+
if(this.$fs.exists(pluginPlatformsFolderPath).wait()) {
73+
shelljs.rm("-rf", pluginPlatformsFolderPath);
74+
}
75+
76+
// TODO: Add libraries
77+
78+
// Show message
79+
this.$logger.out(`Successfully prepared plugin ${pluginData.name} for ${platform} platform`);
80+
}
81+
};
82+
83+
this.executeForAllInstalledPlatforms(action);
84+
}).future<void>()();
85+
}
86+
87+
public getAllInstalledPlugins(): IFuture<IPluginData[]> {
88+
return (() => {
89+
let nodeModules = this.$fs.readDirectory(this.nodeModulesPath).wait();
90+
let plugins: IPluginData[] = [];
91+
_.each(nodeModules, nodeModuleName => {
92+
var nodeModuleData = this.getNodeModuleData(nodeModuleName);
93+
if(nodeModuleData.isPlugin) {
94+
plugins.push(this.convertToPluginData(nodeModuleData));
95+
}
96+
});
97+
98+
return plugins;
99+
}).future<IPluginData[]>()();
100+
}
101+
102+
private executeNpmCommand(npmCommandName: string, npmCommandArguments: string): IFuture<string> {
103+
return (() => {
104+
let command = this.composeNpmCommand(npmCommandName, npmCommandArguments);
105+
let result = this.$childProcess.exec(command, { cwd: this.$projectData.projectDir }).wait();
106+
return this.parseNpmCommandResult(result);
107+
}).future<string>()();
108+
}
109+
110+
private composeNpmCommand(npmCommandName: string, npmCommandArguments: string): string {
111+
let command = `npm ${npmCommandName} ${npmCommandArguments} --save `;
112+
if(this.$options.production) {
113+
command += " --production ";
114+
}
115+
116+
return command;
117+
}
118+
119+
private parseNpmCommandResult(npmCommandResult: string): string { // The npmCommandResult is in the following format: [<name>@<version node_modules/<name>]
120+
return npmCommandResult.split("@")[0]; // returns plugin name
121+
}
122+
123+
private executeForAllInstalledPlatforms(action: (pluginDestinationPath: string, platform: string) => void): void {
124+
let availablePlatforms = _.keys(this.$platformsData.availablePlatforms);
125+
_.each(availablePlatforms, platform => {
126+
let isPlatformInstalled = this.$fs.exists(path.join(this.$projectData.platformsDir, platform.toLowerCase())).wait();
127+
if(isPlatformInstalled) {
128+
let platformData = this.$platformsData.getPlatformData(platform.toLowerCase());
129+
let pluginDestinationPath = path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME, "tns_modules");
130+
action(pluginDestinationPath, platform.toLowerCase());
131+
}
132+
});
133+
}
134+
135+
private getNodeModuleData(moduleName: string): INodeModuleData {
136+
let fullNodeModulePath = path.join(this.nodeModulesPath, moduleName);
137+
let packageJsonFilePath = path.join(fullNodeModulePath, "package.json");
138+
let data = require(packageJsonFilePath);
139+
let result = {
140+
name: data.name,
141+
version: data.version,
142+
isPlugin: data.nativescript,
143+
fullPath: fullNodeModulePath,
144+
};
145+
146+
return result;
147+
}
148+
149+
private convertToPluginData(nodeModuleData: INodeModuleData): IPluginData {
150+
let pluginData: any = _.extend({}, nodeModuleData);
151+
let data = <any>(nodeModuleData.isPlugin);
152+
if(data) {
153+
pluginData.platformsData = data.platforms;
154+
}
155+
156+
return pluginData;
157+
}
158+
159+
private get nodeModulesPath(): string {
160+
return path.join(this.$projectData.projectDir, "node_modules");
161+
}
162+
163+
private getInstalledFrameworkVersion(platform: string): IFuture<string> {
164+
return (() => {
165+
let platformData = this.$platformsData.getPlatformData(platform);
166+
this.$projectDataService.initialize(this.$projectData.projectDir);
167+
let frameworkData = this.$projectDataService.getValue(platformData.frameworkPackageName).wait();
168+
return frameworkData.version;
169+
}).future<string>()();
170+
}
171+
}
172+
$injector.register("pluginsService", PluginsService);

lib/tools/broccoli/node-modules-dest-copy.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,15 @@ export class DestCopy implements IBroccoliPlugin {
2828

2929
_.each(packageJsonFiles, packageJsonFilePath => {
3030
let fileContent = require(packageJsonFilePath);
31+
let isPlugin = fileContent.nativescript;
3132

3233
if(!devDependencies[fileContent.name]) { // Don't flatten dev dependencies
3334

3435
let currentDependency = {
3536
name: fileContent.name,
3637
version: fileContent.version,
37-
directory: path.dirname(packageJsonFilePath)
38+
directory: path.dirname(packageJsonFilePath),
39+
isPlugin: isPlugin
3840
};
3941

4042
let addedDependency = dependencies[currentDependency.name];
@@ -57,7 +59,10 @@ export class DestCopy implements IBroccoliPlugin {
5759

5860
_.each(dependencies, dependency => {
5961
shelljs.cp("-R", dependency.directory, this.outputRoot);
60-
shelljs.rm("-rf", path.join(this.outputRoot, dependency.name, "node_modules"));
62+
shelljs.rm("-rf", path.join(this.outputRoot, dependency.name, "node_modules"));
63+
if(dependency.isPlugin) {
64+
shelljs.rm("-rf", path.join(this.outputRoot, dependency.name, "platforms"));
65+
}
6166
});
6267

6368
// Cache input tree

0 commit comments

Comments
 (0)