Skip to content

Commit b926ed1

Browse files
committed
Merge pull request #1072 from NativeScript/npm-scope-support
Npm scope support
2 parents 01b8af5 + dfc39b6 commit b926ed1

File tree

4 files changed

+148
-101
lines changed

4 files changed

+148
-101
lines changed

lib/common

lib/tools/broccoli/builder.ts

Lines changed: 95 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -11,88 +11,100 @@ let vinylFilterSince = require("vinyl-filter-since");
1111
let through = require("through2");
1212

1313
export class Builder implements IBroccoliBuilder {
14-
constructor(private $fs: IFileSystem,
15-
private $nodeModulesTree: INodeModulesTree,
16-
private $projectData: IProjectData,
17-
private $projectDataService: IProjectDataService,
18-
private $injector: IInjector,
19-
private $logger: ILogger) { }
20-
21-
public getChangedNodeModules(absoluteOutputPath: string, platform: string, lastModifiedTime?: Date): IFuture<any> {
22-
return (() => {
23-
let projectDir = this.$projectData.projectDir;
24-
let isNodeModulesModified = false;
25-
let nodeModulesPath = path.join(projectDir, constants.NODE_MODULES_FOLDER_NAME);
26-
let nodeModules: any = {};
27-
28-
if(lastModifiedTime) {
29-
let pipeline = gulp.src(path.join(projectDir, "node_modules/**"))
30-
.pipe(vinylFilterSince(lastModifiedTime))
31-
.pipe(through.obj( (chunk: any, enc: any, cb: Function) => {
32-
if(chunk.path === nodeModulesPath) {
33-
isNodeModulesModified = true;
34-
}
35-
36-
if(!isNodeModulesModified) {
37-
let rootModuleName = chunk.path.split(nodeModulesPath)[1].split(path.sep)[1];
38-
let rootModuleFullPath = path.join(nodeModulesPath, rootModuleName);
39-
nodeModules[rootModuleFullPath] = rootModuleFullPath;
40-
}
41-
42-
cb(null);
43-
}))
44-
.pipe(gulp.dest(absoluteOutputPath));
45-
46-
let future = new Future<void>();
47-
48-
pipeline.on('end', (err: Error, data: any) => {
49-
if(err) {
50-
future.throw(err);
51-
} else {
52-
future.return();
53-
}
54-
});
55-
56-
future.wait();
57-
}
58-
59-
if(isNodeModulesModified && this.$fs.exists(absoluteOutputPath).wait()) {
60-
let currentPreparedTnsModules = this.$fs.readDirectory(absoluteOutputPath).wait();
61-
let tnsModulesPath = path.join(projectDir, constants.APP_FOLDER_NAME, constants.TNS_MODULES_FOLDER_NAME);
62-
if(!this.$fs.exists(tnsModulesPath).wait()) {
63-
tnsModulesPath = path.join(projectDir, constants.NODE_MODULES_FOLDER_NAME, constants.TNS_CORE_MODULES_NAME);
64-
}
65-
let tnsModulesInApp = this.$fs.readDirectory(tnsModulesPath).wait();
66-
let modulesToDelete = _.difference(currentPreparedTnsModules, tnsModulesInApp);
67-
_.each(modulesToDelete, moduleName => this.$fs.deleteDirectory(path.join(absoluteOutputPath, moduleName)).wait());
68-
}
69-
70-
if(!lastModifiedTime || isNodeModulesModified) {
71-
let nodeModulesDirectories = this.$fs.exists(nodeModulesPath).wait() ? this.$fs.readDirectory(nodeModulesPath).wait() : [];
72-
_.each(nodeModulesDirectories, nodeModuleDirectoryName => {
73-
let nodeModuleFullPath = path.join(nodeModulesPath, nodeModuleDirectoryName);
74-
nodeModules[nodeModuleFullPath] = nodeModuleFullPath;
75-
});
76-
}
77-
78-
return nodeModules;
79-
}).future<any>()();
80-
}
81-
82-
public prepareNodeModules(absoluteOutputPath: string, platform: string, lastModifiedTime?: Date): IFuture<void> {
83-
return (() => {
84-
let nodeModules = this.getChangedNodeModules(absoluteOutputPath, platform, lastModifiedTime).wait();
85-
let destCopy = this.$injector.resolve(destCopyLib.DestCopy, {
86-
inputPath: this.$projectData.projectDir,
87-
cachePath: "",
88-
outputRoot: absoluteOutputPath,
89-
projectDir: this.$projectData.projectDir,
90-
platform: platform
91-
});
92-
93-
destCopy.rebuildChangedDirectories(_.keys(nodeModules));
94-
95-
}).future<void>()();
96-
}
14+
constructor(
15+
private $fs: IFileSystem,
16+
private $nodeModulesTree: INodeModulesTree,
17+
private $projectData: IProjectData,
18+
private $projectDataService: IProjectDataService,
19+
private $injector: IInjector,
20+
private $logger: ILogger
21+
) {
22+
}
23+
24+
public getChangedNodeModules(absoluteOutputPath: string, platform: string, lastModifiedTime?: Date): IFuture<any> {
25+
return (() => {
26+
let projectDir = this.$projectData.projectDir;
27+
let isNodeModulesModified = false;
28+
let nodeModulesPath = path.join(projectDir, constants.NODE_MODULES_FOLDER_NAME);
29+
let nodeModules: any = {};
30+
31+
if(lastModifiedTime) {
32+
let pipeline = gulp.src(path.join(projectDir, "node_modules/**"))
33+
.pipe(vinylFilterSince(lastModifiedTime))
34+
.pipe(through.obj( (chunk: any, enc: any, cb: Function) => {
35+
if(chunk.path === nodeModulesPath) {
36+
isNodeModulesModified = true;
37+
}
38+
39+
if(!isNodeModulesModified) {
40+
let rootModuleName = chunk.path.split(nodeModulesPath)[1].split(path.sep)[1];
41+
let rootModuleFullPath = path.join(nodeModulesPath, rootModuleName);
42+
nodeModules[rootModuleFullPath] = rootModuleFullPath;
43+
}
44+
45+
cb(null);
46+
}))
47+
.pipe(gulp.dest(absoluteOutputPath));
48+
49+
let future = new Future<void>();
50+
51+
pipeline.on('end', (err: Error, data: any) => {
52+
if(err) {
53+
future.throw(err);
54+
} else {
55+
future.return();
56+
}
57+
});
58+
59+
future.wait();
60+
}
61+
62+
if(isNodeModulesModified && this.$fs.exists(absoluteOutputPath).wait()) {
63+
let currentPreparedTnsModules = this.$fs.readDirectory(absoluteOutputPath).wait();
64+
let tnsModulesPath = path.join(projectDir, constants.APP_FOLDER_NAME, constants.TNS_MODULES_FOLDER_NAME);
65+
if(!this.$fs.exists(tnsModulesPath).wait()) {
66+
tnsModulesPath = path.join(projectDir, constants.NODE_MODULES_FOLDER_NAME, constants.TNS_CORE_MODULES_NAME);
67+
}
68+
let tnsModulesInApp = this.$fs.readDirectory(tnsModulesPath).wait();
69+
let modulesToDelete = _.difference(currentPreparedTnsModules, tnsModulesInApp);
70+
_.each(modulesToDelete, moduleName => this.$fs.deleteDirectory(path.join(absoluteOutputPath, moduleName)).wait());
71+
}
72+
73+
if(!lastModifiedTime || isNodeModulesModified) {
74+
this.listModules(nodeModulesPath, nodeModules);
75+
}
76+
77+
return nodeModules;
78+
}).future<any>()();
79+
}
80+
81+
private listModules(nodeModulesPath: string, nodeModules: any): void {
82+
let nodeModulesDirectories = this.$fs.exists(nodeModulesPath).wait() ? this.$fs.readDirectory(nodeModulesPath).wait() : [];
83+
_.each(nodeModulesDirectories, nodeModuleDirectoryName => {
84+
let isNpmScope = /^@/.test(nodeModuleDirectoryName);
85+
let nodeModuleFullPath = path.join(nodeModulesPath, nodeModuleDirectoryName);
86+
if (isNpmScope) {
87+
this.listModules(nodeModuleFullPath, nodeModules);
88+
} else {
89+
nodeModules[nodeModuleFullPath] = nodeModuleFullPath;
90+
}
91+
});
92+
}
93+
94+
public prepareNodeModules(absoluteOutputPath: string, platform: string, lastModifiedTime?: Date): IFuture<void> {
95+
return (() => {
96+
let nodeModules = this.getChangedNodeModules(absoluteOutputPath, platform, lastModifiedTime).wait();
97+
let destCopy = this.$injector.resolve(destCopyLib.DestCopy, {
98+
inputPath: this.$projectData.projectDir,
99+
cachePath: "",
100+
outputRoot: absoluteOutputPath,
101+
projectDir: this.$projectData.projectDir,
102+
platform: platform
103+
});
104+
105+
destCopy.rebuildChangedDirectories(_.keys(nodeModules));
106+
107+
}).future<void>()();
108+
}
97109
}
98110
$injector.register("broccoliBuilder", Builder);

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

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,19 @@ export class DestCopy implements IBroccoliPlugin {
1616
private dependencies: IDictionary<any> = null;
1717
private devDependencies: IDictionary<any> = null;
1818

19-
constructor(private inputPath: string,
20-
private cachePath: string,
21-
private outputRoot: string,
22-
private projectDir: string,
23-
private platform: string,
24-
private $fs: IFileSystem,
25-
private $projectFilesManager: IProjectFilesManager,
26-
private $pluginsService: IPluginsService) {
27-
this.dependencies = Object.create(null);
28-
this.devDependencies = this.getDevDependencies(projectDir);
29-
}
19+
constructor(
20+
private inputPath: string,
21+
private cachePath: string,
22+
private outputRoot: string,
23+
private projectDir: string,
24+
private platform: string,
25+
private $fs: IFileSystem,
26+
private $projectFilesManager: IProjectFilesManager,
27+
private $pluginsService: IPluginsService
28+
) {
29+
this.dependencies = Object.create(null);
30+
this.devDependencies = this.getDevDependencies(projectDir);
31+
}
3032

3133
public rebuildChangedDirectories(changedDirectories: string[], platform: string): void {
3234
_.each(changedDirectories, changedDirectoryAbsolutePath => {
@@ -68,8 +70,8 @@ export class DestCopy implements IBroccoliPlugin {
6870
});
6971

7072
_.each(this.dependencies, dependency => {
71-
shelljs.cp("-Rf", dependency.directory, this.outputRoot);
72-
shelljs.rm("-rf", path.join(this.outputRoot, dependency.name, "node_modules"));
73+
this.copyDependencyDir(dependency);
74+
7375
let isPlugin = !!dependency.nativescript;
7476
if(isPlugin) {
7577
this.$pluginsService.prepare(dependency).wait();
@@ -87,6 +89,18 @@ export class DestCopy implements IBroccoliPlugin {
8789
}
8890
}
8991

92+
private copyDependencyDir(dependency: any): void {
93+
let dependencyDir = path.dirname(dependency.name || "");
94+
let insideNpmScope = /^@/.test(dependencyDir);
95+
let targetDir = this.outputRoot;
96+
if (insideNpmScope) {
97+
targetDir = path.join(this.outputRoot, dependencyDir);
98+
}
99+
shelljs.mkdir("-p", targetDir);
100+
shelljs.cp("-Rf", dependency.directory, targetDir);
101+
shelljs.rm("-rf", path.join(targetDir, dependency.name, "node_modules"));
102+
}
103+
90104
public rebuild(treeDiff: IDiffResult): void {
91105
this.rebuildChangedDirectories(treeDiff.changedDirectories, "");
92106

@@ -113,10 +127,10 @@ export class DestCopy implements IBroccoliPlugin {
113127
foundFiles.push(packageJsonFilePath);
114128
}
115129

116-
let directoryPath = path.join(nodeModulesDirectoryPath, contents[i], constants.NODE_MODULES_FOLDER_NAME);
117-
if (fs.existsSync(directoryPath)) {
118-
this.enumeratePackageJsonFilesSync(directoryPath, foundFiles);
119-
}
130+
let directoryPath = path.join(nodeModulesDirectoryPath, contents[i], constants.NODE_MODULES_FOLDER_NAME);
131+
if (fs.existsSync(directoryPath)) {
132+
this.enumeratePackageJsonFilesSync(directoryPath, foundFiles);
133+
}
120134
}
121135
}
122136
return foundFiles;

test/npm-support.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import Future = require("fibers/future");
2424

2525
import path = require("path");
2626
import temp = require("temp");
27+
import shelljs = require("shelljs");
2728
temp.track();
2829

2930
let assert = require("chai").assert;
@@ -185,6 +186,26 @@ describe("Npm support tests", () => {
185186
assert.isTrue(fs.exists(bplistCreatorFolderPath).wait());
186187
assert.isTrue(fs.exists(bplistParserFolderPath).wait());
187188
});
189+
it("Ensures that scoped dependencies are prepared correctly", () => {
190+
// Setup
191+
let fs = testInjector.resolve("fs");
192+
let scopedName = "@reactivex/rxjs";
193+
let scopedModule = path.join(projectFolder, "node_modules", "@reactivex/rxjs");
194+
let scopedPackageJson = path.join(scopedModule, "package.json");
195+
addDependencies(testInjector, projectFolder, {scopedName: "0.0.0-prealpha.3"}).wait();
196+
//create module dir, and add a package.json
197+
shelljs.mkdir("-p", scopedModule);
198+
fs.writeFile(scopedPackageJson, JSON.stringify({name: scopedName})).wait();
199+
200+
// Act
201+
preparePlatform(testInjector).wait();
202+
203+
// Assert
204+
let tnsModulesFolderPath = path.join(appDestinationFolderPath, "app", "tns_modules");
205+
206+
let scopedDependencyPath = path.join(tnsModulesFolderPath, "@reactivex", "rxjs");
207+
assert.isTrue(fs.exists(scopedDependencyPath).wait());
208+
});
188209
});
189210

190211
describe("Flatten npm modules tests", () => {

0 commit comments

Comments
 (0)