Skip to content

Commit 4c579d0

Browse files
Fatme HavaluovaFatme
authored andcommitted
Enable npm as package manager for ns applications
1 parent cc9fca3 commit 4c579d0

15 files changed

+498
-14
lines changed

.idea/nativescript-cli.iml

Lines changed: 0 additions & 10 deletions
This file was deleted.

lib/bootstrap.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,3 +50,7 @@ $injector.require("mobilePlatformsCapabilities", "./mobile-platforms-capabilitie
5050
$injector.require("commandsServiceProvider", "./providers/commands-service-provider");
5151

5252
$injector.require("logcatPrinter", "./providers/logcat-printer");
53+
54+
$injector.require("broccoliBuilder", "./tools/broccoli/builder");
55+
$injector.require("nodeModulesTree", "./tools/broccoli/trees/node-modules-tree");
56+
$injector.require("broccoliPluginWrapper", "./tools/broccoli/broccoli-plugin-wrapper");

lib/constants.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
export var APP_FOLDER_NAME = "app";
44
export var APP_RESOURCES_FOLDER_NAME = "App_Resources";
55
export var PROJECT_FRAMEWORK_FOLDER_NAME = "framework";
6+
export var NATIVESCRIPT_KEY_NAME = "nativescript";
7+
export var NODE_MODULES_FOLDER_NAME = "node_modules";
8+
export var PACKAGE_JSON_FILE_NAME = "package.json";
9+
export var NODE_MODULE_CACHE_PATH_KEY_NAME = "node-modules-cache-path";
610

711
export class ReleaseType {
812
static MAJOR = "major";

lib/definitions/shelljs.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,6 @@ declare module "shelljs" {
33
function cp(arg: string, sourcePath: string[], destinationPath: string): void;
44
function sed(arg: string, oldValue: any, newValue: string, filePath: string): void;
55
function mv(source: string[], destination: string): void;
6+
function rm(option: string, filePath: string): void;
67
function grep(what: any, where: string): any;
78
}

lib/services/platform-service.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import helpers = require("./../common/helpers");
99
import semver = require("semver");
1010

1111
export class PlatformService implements IPlatformService {
12+
private static TNS_MODULES_FOLDER_NAME = "tns_modules";
13+
1214
constructor(private $devicesServices: Mobile.IDevicesServices,
1315
private $errors: IErrors,
1416
private $fs: IFileSystem,
@@ -19,7 +21,8 @@ export class PlatformService implements IPlatformService {
1921
private $projectDataService: IProjectDataService,
2022
private $prompter: IPrompter,
2123
private $commandsService: ICommandsService,
22-
private $options: IOptions) { }
24+
private $options: IOptions,
25+
private $broccoliBuilder: IBroccoliBuilder) { }
2326

2427
public addPlatforms(platforms: string[]): IFuture<void> {
2528
return (() => {
@@ -169,14 +172,16 @@ export class PlatformService implements IPlatformService {
169172
files.push(filePath);
170173
}
171174
});
172-
173175
this.processPlatformSpecificFiles(platform, files).wait();
174176

175-
this.$logger.out("Project successfully prepared");
177+
// Process node_modules folder
178+
var tnsModulesDestinationPath = path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME, PlatformService.TNS_MODULES_FOLDER_NAME);
179+
this.$broccoliBuilder.prepareNodeModules(tnsModulesDestinationPath, this.$projectData.projectDir).wait();
176180

181+
this.$logger.out("Project successfully prepared");
177182
}).future<void>()();
178183
}
179-
184+
180185
public buildPlatform(platform: string): IFuture<void> {
181186
return (() => {
182187
platform = platform.toLowerCase();
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
///<reference path="../../.d.ts"/>
2+
"use strict";
3+
4+
import broccoliPluginWrapperLib = require("./broccoli-plugin-wrapper");
5+
6+
/**
7+
* Makes writing plugins easy.
8+
*
9+
* Factory method that takes a class that implements the BroccoliPluginWrapper interface and returns
10+
* an instance of BroccoliTree.
11+
*
12+
* @param pluginClass
13+
* @returns {BroccoliPluginWrapper}
14+
*/
15+
16+
type BroccoliPluginWrapperFactory = (inputTree: BroccoliTree, ...options: any[]) => BroccoliTree;
17+
18+
export function wrapBroccoliPlugin(pluginClass: any): BroccoliPluginWrapperFactory {
19+
return function() { return new broccoliPluginWrapperLib.BroccoliPluginWrapper(pluginClass, arguments); };
20+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
///<reference path="../../.d.ts"/>
2+
"use strict";
3+
4+
import fs = require('fs');
5+
import path = require('path');
6+
import {TreeDiffer} from './tree-differ';
7+
8+
export class BroccoliPluginWrapper implements BroccoliTree {
9+
treeDiffer: TreeDiffer;
10+
wrappedPlugin: IBroccoliPlugin = null;
11+
inputTree: any = null;
12+
description: string = null;
13+
absoluteOutputPath: string = null;
14+
treeRootDirName: string = null;
15+
projectDir: string = null;
16+
$injector: IInjector = null;
17+
18+
// props monkey-patched by broccoli builder:
19+
inputPath: string = null;
20+
cachePath: string = null;
21+
outputPath: string = null;
22+
23+
constructor(private pluginClass: any, wrappedPluginArguments: any) {
24+
this.inputTree = wrappedPluginArguments[0];
25+
this.description = this.pluginClass.name;
26+
this.absoluteOutputPath = wrappedPluginArguments[1];
27+
this.treeRootDirName = wrappedPluginArguments[2];
28+
this.projectDir = wrappedPluginArguments[3];
29+
this.$injector = $injector.resolve("injector");
30+
}
31+
32+
rebuild(): void {
33+
try {
34+
this.init();
35+
36+
let diffResult = this.treeDiffer.diffTree(this.absoluteOutputPath, this.treeRootDirName);
37+
this.wrappedPlugin.rebuild(diffResult);
38+
39+
} catch (e) {
40+
e.message = `[${this.description}]: ${e.message}`;
41+
throw e;
42+
}
43+
}
44+
45+
private init() {
46+
this.treeDiffer = new TreeDiffer(this.inputPath);
47+
this.wrappedPlugin = this.$injector.resolve(this.pluginClass,
48+
{ inputPath: this.inputPath,
49+
cachePath: this.cachePath,
50+
outputRoot: this.absoluteOutputPath,
51+
projectDir: this.projectDir });
52+
}
53+
54+
cleanup() {
55+
if (this.wrappedPlugin.cleanup) {
56+
this.wrappedPlugin.cleanup();
57+
}
58+
}
59+
}

lib/tools/broccoli/broccoli.d.ts

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
/// <reference path="../../.d.ts" />
2+
3+
4+
interface BroccoliTree {
5+
/**
6+
* Contains the fs path for the input tree when the plugin takes only one input tree.
7+
*
8+
* For plugins that take multiple trees see the `inputPaths` property.
9+
*
10+
* This property is set just before the first rebuild and doesn't change afterwards.
11+
*/
12+
inputPath: string;
13+
14+
/**
15+
* Contains the array of fs paths for input trees.
16+
*
17+
* For plugins that take only one input tree, it might be more convenient to use the `inputPath`
18+
*property instead.
19+
*
20+
* This property is set just before the first rebuild and doesn't change afterwards.
21+
*
22+
* If the inputPath is outside of broccoli's temp directory, then it's lifetime is not managed by
23+
*the builder.
24+
* If the inputPath is within broccoli's temp directory it is an outputPath (and output directory)
25+
*of another plugin.
26+
* This means that while the `outputPath` doesn't change, the underlying directory is frequently
27+
*recreated.
28+
*/
29+
inputPaths?: string[];
30+
31+
/**
32+
* Contains the fs paths for the output trees.
33+
*
34+
* This property is set just before the first rebuild and doesn't change afterwards.
35+
*
36+
* The underlying directory is also created by the builder just before the first rebuild.
37+
* This directory is destroyed and recreated upon each rebuild.
38+
*/
39+
outputPath?: string;
40+
41+
/**
42+
* Contains the fs paths for a cache directory available to the plugin.
43+
*
44+
* This property is set just before the first rebuild and doesn't change afterwards.
45+
*
46+
* The underlying directory is also created by the builder just before the first rebuild.
47+
* The lifetime of the directory is associated with the lifetime of the plugin.
48+
*/
49+
cachePath?: string;
50+
51+
inputTree?: BroccoliTree;
52+
inputTrees?: BroccoliTree[];
53+
54+
/**
55+
* Description or name of the plugin used for reporting.
56+
*
57+
* If missing `tree.constructor.name` is usually used instead.
58+
*/
59+
description?: string;
60+
61+
rebuild(): any;
62+
cleanup(): void;
63+
}
64+
65+
66+
interface OldBroccoliTree {
67+
read?(readTree: (tree: BroccoliTree) => any): any;
68+
}
69+
70+
71+
72+
interface BroccoliBuilder {
73+
/**
74+
* Triggers a build and returns a promise for the build result
75+
*/
76+
build(): IFuture<BuildResult>;
77+
78+
79+
/**
80+
* Cleans up the whole build tree by calling `.cleanup()` method on all trees that are part of the
81+
* pipeline.
82+
*/
83+
cleanup(): IFuture<any>;
84+
}
85+
86+
87+
interface BuildResult {
88+
/**
89+
* Directory that contains result of the build.
90+
*
91+
* This directory will contains symlinks, so it is not safe to just use it as is.
92+
*
93+
* Use `copy-dereference` npm package to create a safe-to-use replica of the build artifacts.
94+
*/
95+
directory: string;
96+
97+
98+
/**
99+
* The DAG (graph) of all trees in the build pipeline.
100+
*/
101+
graph: BroccoliNode;
102+
103+
/**
104+
* Total time it took to make the build.
105+
*/
106+
totalTime: number;
107+
}
108+
109+
110+
111+
interface BroccoliNode {
112+
///**
113+
// * Id of the current node
114+
// */
115+
// id: number; //only in master
116+
117+
/**
118+
* Time spent processing the current node during a single rebuild.
119+
*/
120+
selfTime: number;
121+
122+
123+
/**
124+
* Time spent processing the current node and its subtrees during a single rebuild.
125+
*/
126+
totalTime: number;
127+
128+
129+
/**
130+
* Tree associated with the current node.
131+
*/
132+
tree: BroccoliTree;
133+
134+
135+
/**
136+
* Child nodes with references to trees that are input for the tree of the current node.
137+
*/
138+
subtrees: BroccoliNode[];
139+
140+
141+
/**
142+
* Parent nodes with references to trees that are consume the output of processing the current
143+
* tree.
144+
*/
145+
parents: BroccoliNode[];
146+
147+
148+
/**
149+
* Path to the directory containing the output of processing the current tree.
150+
*/
151+
directory: string;
152+
}
153+
154+
interface IBroccoliBuilder {
155+
prepareNodeModules(outputPath: string, projectDir: string): IFuture<void>;
156+
}
157+
158+
interface IDiffResult {
159+
changedDirectories: string[];
160+
removedDirectories: string[];
161+
}
162+
163+
interface IBroccoliPlugin {
164+
rebuild(diff: IDiffResult): any;
165+
cleanup? () : void;
166+
}
167+
168+
interface INodeModulesTree {
169+
makeNodeModulesTree(absoluteOutputPath: string, projectDir: string): any;
170+
}

lib/tools/broccoli/builder.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
///<reference path="../../.d.ts"/>
2+
"use strict";
3+
4+
let broccoli = require('broccoli');
5+
let path = require('path');
6+
import Future = require("fibers/future");
7+
import {TreeDiffer} from './tree-differ';
8+
import destCopy = require('./node-modules-dest-copy');
9+
10+
export class Builder implements IBroccoliBuilder {
11+
constructor(private $fs: IFileSystem,
12+
private $nodeModulesTree: INodeModulesTree,
13+
private $projectDataService: IProjectDataService,
14+
private $logger: ILogger) { }
15+
16+
public prepareNodeModules(absoluteOutputPath: string, projectDir: string): IFuture<void> {
17+
return (() => {
18+
// TODO: figure out a better way for doing this
19+
this.$projectDataService.initialize(projectDir);
20+
let cachedNodeModulesPath = this.$projectDataService.getValue("node-modules-cache-path").wait();
21+
if (cachedNodeModulesPath && this.$fs.exists(cachedNodeModulesPath).wait()) {
22+
let diffTree = new TreeDiffer(cachedNodeModulesPath);
23+
let diffTreeResult = diffTree.diffTree(path.join(projectDir, absoluteOutputPath, "node_modules"));
24+
25+
if(diffTreeResult.changedDirectories.length > 0 || diffTreeResult.removedDirectories.length > 0) {
26+
this.rebuildNodeModulesTree(absoluteOutputPath, projectDir).wait();
27+
}
28+
} else {
29+
this.rebuildNodeModulesTree(absoluteOutputPath, projectDir).wait();
30+
}
31+
}).future<void>()();
32+
}
33+
34+
private rebuildNodeModulesTree(outputPath: string, projectDir: string): IFuture<any> {
35+
let nodeModulesBuilder = this.makeNodeModulesBuilder(outputPath, projectDir);
36+
return this.rebuild(nodeModulesBuilder);
37+
}
38+
39+
private makeNodeModulesBuilder(outputPath: string, projectDir: string): BroccoliBuilder {
40+
let tree = this.$nodeModulesTree.makeNodeModulesTree(outputPath, projectDir);
41+
return new broccoli.Builder(tree);
42+
}
43+
44+
private rebuild(builder: any): IFuture<any> {
45+
let future = new Future<any>();
46+
builder.build()
47+
.then((result: any) => {
48+
future.return(result);
49+
})
50+
.catch((err: any) => {
51+
if(err.file) {
52+
this.$logger.error("File: " + err.file);
53+
}
54+
if(err.statck) {
55+
this.$logger.error(err.stack);
56+
}
57+
future.throw(err.toString());
58+
});
59+
60+
return future;
61+
}
62+
}
63+
$injector.register("broccoliBuilder", Builder);

0 commit comments

Comments
 (0)