Skip to content

Commit 572acff

Browse files
FatmeFatme
Fatme
authored and
Fatme
committed
Merge pull request #593 from NativeScript/fatme/fix-symlinks
Fix symlinks that are added to output package and points to not existing location
2 parents 83c3d5c + 7513ad6 commit 572acff

File tree

6 files changed

+163
-64
lines changed

6 files changed

+163
-64
lines changed

lib/services/platform-service.ts

+15-6
Original file line numberDiff line numberDiff line change
@@ -149,17 +149,26 @@ export class PlatformService implements IPlatformService {
149149
platform = platform.toLowerCase();
150150

151151
var platformData = this.$platformsData.getPlatformData(platform);
152+
let appDestinationDirectoryPath = path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME);
153+
let lastModifiedTime = this.$fs.exists(appDestinationDirectoryPath).wait() ?
154+
this.$fs.getFsStats(appDestinationDirectoryPath).wait().mtime : null;
152155

153156
// Copy app folder to native project
157+
this.$fs.ensureDirectoryExists(appDestinationDirectoryPath).wait();
154158
var appSourceDirectoryPath = path.join(this.$projectData.projectDir, constants.APP_FOLDER_NAME);
155-
159+
156160
// Delete the destination app in order to prevent EEXIST errors when symlinks are used.
157-
this.$fs.deleteDirectory(path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME)).wait();
158-
shell.cp("-R", appSourceDirectoryPath, platformData.appDestinationDirectoryPath);
161+
let contents = this.$fs.readDirectory(appDestinationDirectoryPath).wait();
162+
163+
_(contents)
164+
.filter(directoryName => directoryName !== "tns_modules")
165+
.each(directoryName => this.$fs.deleteDirectory(path.join(appDestinationDirectoryPath, directoryName)).wait())
166+
.value();
167+
shell.cp("-Rf", appSourceDirectoryPath, platformData.appDestinationDirectoryPath);
159168

160169
// Copy App_Resources to project root folder
161-
var appResourcesDirectoryPath = path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME, constants.APP_RESOURCES_FOLDER_NAME);
162-
this.$fs.ensureDirectoryExists(platformData.appResourcesDestinationDirectoryPath).wait();
170+
this.$fs.ensureDirectoryExists(platformData.appResourcesDestinationDirectoryPath).wait(); // Should be deleted
171+
var appResourcesDirectoryPath = path.join(appDestinationDirectoryPath, constants.APP_RESOURCES_FOLDER_NAME);
163172
if (this.$fs.exists(appResourcesDirectoryPath).wait()) {
164173
platformData.platformProjectService.prepareAppResources(appResourcesDirectoryPath).wait();
165174
shell.cp("-R", path.join(appResourcesDirectoryPath, platformData.normalizedPlatformName, "*"), platformData.appResourcesDestinationDirectoryPath);
@@ -176,7 +185,7 @@ export class PlatformService implements IPlatformService {
176185
// Process node_modules folder
177186
this.$pluginsService.ensureAllDependenciesAreInstalled().wait();
178187
var tnsModulesDestinationPath = path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME, PlatformService.TNS_MODULES_FOLDER_NAME);
179-
this.$broccoliBuilder.prepareNodeModules(tnsModulesDestinationPath, this.$projectData.projectDir).wait();
188+
this.$broccoliBuilder.prepareNodeModules(tnsModulesDestinationPath, this.$projectData.projectDir, platform, lastModifiedTime).wait();
180189

181190
this.$logger.out("Project successfully prepared");
182191
}).future<void>()();

lib/tools/broccoli/broccoli.d.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ interface BroccoliNode {
152152
}
153153

154154
interface IBroccoliBuilder {
155-
prepareNodeModules(outputPath: string, projectDir: string): IFuture<void>;
155+
prepareNodeModules(outputPath: string, projectDir: string, platform: string, lastModifiedTime?: Date): IFuture<void>;
156156
}
157157

158158
interface IDiffResult {
@@ -162,6 +162,7 @@ interface IDiffResult {
162162

163163
interface IBroccoliPlugin {
164164
rebuild(diff: IDiffResult): any;
165+
rebuildChangedDirectories?(changedDirectories: string[], platform: string): void;
165166
cleanup? () : void;
166167
}
167168

lib/tools/broccoli/builder.ts

+67-14
Original file line numberDiff line numberDiff line change
@@ -5,29 +5,82 @@ let broccoli = require('broccoli');
55
let path = require('path');
66
import Future = require("fibers/future");
77
import {TreeDiffer} from './tree-differ';
8-
import destCopy = require('./node-modules-dest-copy');
8+
import destCopyLib = require('./node-modules-dest-copy');
9+
10+
var gulp = require("gulp");
11+
var vinylFilterSince = require('vinyl-filter-since')
12+
var through = require("through2");
913

1014
export class Builder implements IBroccoliBuilder {
15+
private nodeModules: any = {};
16+
1117
constructor(private $fs: IFileSystem,
1218
private $nodeModulesTree: INodeModulesTree,
1319
private $projectDataService: IProjectDataService,
20+
private $injector: IInjector,
1421
private $logger: ILogger) { }
1522

16-
public prepareNodeModules(absoluteOutputPath: string, projectDir: string): IFuture<void> {
23+
public prepareNodeModules(absoluteOutputPath: string, projectDir: string, platform: string, lastModifiedTime?: Date): IFuture<void> {
1724
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();
25+
let isNodeModulesModified = false;
26+
let nodeModulesPath = path.join(projectDir, "node_modules");
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+
this.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: any, 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 tnsModulesInApp = this.$fs.readDirectory(path.join(projectDir, "app", "tns_modules")).wait();
62+
let modulesToDelete = _.difference(currentPreparedTnsModules, tnsModulesInApp);
63+
_.each(modulesToDelete, moduleName => this.$fs.deleteDirectory(path.join(absoluteOutputPath, moduleName)).wait())
64+
}
65+
66+
if(!lastModifiedTime || isNodeModulesModified) {
67+
let nodeModulesDirectories = this.$fs.readDirectory(nodeModulesPath).wait();
68+
_.each(nodeModulesDirectories, nodeModuleDirectoryName => {
69+
let nodeModuleFullPath = path.join(nodeModulesPath, nodeModuleDirectoryName);
70+
this.nodeModules[nodeModuleFullPath] = nodeModuleFullPath;
71+
});
3072
}
73+
74+
let destCopy = this.$injector.resolve(destCopyLib.DestCopy, {
75+
inputPath: projectDir,
76+
cachePath: "",
77+
outputRoot: absoluteOutputPath,
78+
projectDir: projectDir,
79+
platform: platform
80+
});
81+
82+
destCopy.rebuildChangedDirectories(_.keys(this.nodeModules));
83+
3184
}).future<void>()();
3285
}
3386

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

+33-32
Original file line numberDiff line numberDiff line change
@@ -14,23 +14,32 @@ import constants = require("./../../constants");
1414
* and tees a copy to the given path outside the tmp dir.
1515
*/
1616
export class DestCopy implements IBroccoliPlugin {
17-
constructor(private inputPath: string, private cachePath: string, private outputRoot: string, private projectDir: string) {}
18-
19-
public rebuild(treeDiff: IDiffResult): void {
20-
let dependencies = this.getDependencies();
21-
let devDependencies = this.getDevDependencies(this.projectDir);
17+
private dependencies: IDictionary<any> = null;
18+
private devDependencies: IDictionary<any> = null;
2219

23-
treeDiff.changedDirectories.forEach(changedDirectory => {
24-
let changedDirectoryAbsolutePath = path.join(this.inputPath, constants.NODE_MODULES_FOLDER_NAME, changedDirectory);
25-
let packageJsonFiles = [path.join(changedDirectoryAbsolutePath, "package.json")];
20+
constructor(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+
this.dependencies = Object.create(null);
28+
this.devDependencies = this.getDevDependencies(projectDir);
29+
}
30+
31+
public rebuildChangedDirectories(changedDirectories: string[], platform: string): void {
32+
_.each(changedDirectories, changedDirectoryAbsolutePath => {
33+
let pathToPackageJson = path.join(changedDirectoryAbsolutePath, "package.json");
34+
let packageJsonFiles = fs.existsSync(pathToPackageJson) ? [pathToPackageJson] : [];
2635
let nodeModulesFolderPath = path.join(changedDirectoryAbsolutePath, "node_modules");
2736
packageJsonFiles = packageJsonFiles.concat(this.enumeratePackageJsonFilesSync(nodeModulesFolderPath));
28-
37+
2938
_.each(packageJsonFiles, packageJsonFilePath => {
3039
let fileContent = require(packageJsonFilePath);
3140
let isPlugin = fileContent.nativescript;
3241

33-
if(!devDependencies[fileContent.name]) { // Don't flatten dev dependencies
42+
if(!this.devDependencies[fileContent.name]) { // Don't flatten dev dependencies
3443

3544
let currentDependency = {
3645
name: fileContent.name,
@@ -39,7 +48,7 @@ export class DestCopy implements IBroccoliPlugin {
3948
isPlugin: isPlugin
4049
};
4150

42-
let addedDependency = dependencies[currentDependency.name];
51+
let addedDependency = this.dependencies[currentDependency.name];
4352
if (addedDependency) {
4453
if (semver.gt(currentDependency.version, addedDependency.version)) {
4554
let currentDependencyMajorVersion = semver.major(currentDependency.version);
@@ -49,22 +58,27 @@ export class DestCopy implements IBroccoliPlugin {
4958
let logger = $injector.resolve("$logger");
5059
currentDependencyMajorVersion === addedDependencyMajorVersion ? logger.out(message) : logger.warn(message);
5160

52-
dependencies[currentDependency.name] = currentDependency;
61+
this.dependencies[currentDependency.name] = currentDependency;
5362
}
5463
} else {
55-
dependencies[currentDependency.name] = currentDependency;
64+
this.dependencies[currentDependency.name] = currentDependency;
5665
}
5766
}
5867
});
5968
});
6069

61-
_.each(dependencies, dependency => {
62-
shelljs.cp("-R", dependency.directory, this.outputRoot);
70+
_.each(this.dependencies, dependency => {
71+
shelljs.cp("-Rf", dependency.directory, this.outputRoot);
6372
shelljs.rm("-rf", path.join(this.outputRoot, dependency.name, "node_modules"));
6473
if(dependency.isPlugin) {
74+
this.$projectFilesManager.processPlatformSpecificFiles(path.join(this.outputRoot, dependency.name), platform).wait();
6575
shelljs.rm("-rf", path.join(this.outputRoot, dependency.name, "platforms"));
6676
}
6777
});
78+
}
79+
80+
public rebuild(treeDiff: IDiffResult): void {
81+
this.rebuildChangedDirectories(treeDiff.changedDirectories, "");
6882

6983
// Cache input tree
7084
let projectFilePath = path.join(this.projectDir, constants.PACKAGE_JSON_FILE_NAME);
@@ -73,22 +87,6 @@ export class DestCopy implements IBroccoliPlugin {
7387
fs.writeFileSync(projectFilePath, JSON.stringify(projectFileContent, null, "\t"), { encoding: "utf8" });
7488
}
7589

76-
private getDependencies(): IDictionary<any> {
77-
let result = Object.create(null);
78-
if(fs.existsSync(this.outputRoot)) {
79-
let dirs = fs.readdirSync(this.outputRoot);
80-
_.each(dirs, dir => {
81-
let filePath = path.join(dir, constants.PACKAGE_JSON_FILE_NAME);
82-
if(fs.existsSync(filePath)) {
83-
let fileContent = require(filePath);
84-
result[fileContent.name] = fileContent;
85-
}
86-
});
87-
}
88-
89-
return result;
90-
}
91-
9290
private getDevDependencies(projectDir: string): IDictionary<any> {
9391
let projectFilePath = path.join(projectDir, constants.PACKAGE_JSON_FILE_NAME);
9492
let projectFileContent = require(projectFilePath);
@@ -100,7 +98,10 @@ export class DestCopy implements IBroccoliPlugin {
10098
if(fs.existsSync(nodeModulesDirectoryPath)) {
10199
let contents = fs.readdirSync(nodeModulesDirectoryPath);
102100
for (let i = 0; i < contents.length; ++i) {
103-
foundFiles.push(path.join(nodeModulesDirectoryPath, contents[i], "package.json"));
101+
let packageJsonFilePath = path.join(nodeModulesDirectoryPath, contents[i], "package.json");
102+
if (fs.existsSync(packageJsonFilePath)) {
103+
foundFiles.push(packageJsonFilePath);
104+
}
104105

105106
var directoryPath = path.join(nodeModulesDirectoryPath, contents[i], "node_modules");
106107
if (fs.existsSync(directoryPath)) {

package.json

+3
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
"fibers": "https://github.com/icenium/node-fibers/tarball/v1.0.5.1",
3838
"filesize": "2.0.3",
3939
"gaze": "0.5.1",
40+
"gulp": "3.9.0",
4041
"iconv-lite": "0.4.4",
4142
"inquirer": "0.8.2",
4243
"ios-sim-portable": "1.0.8",
@@ -65,7 +66,9 @@
6566
"shelljs": "0.3.0",
6667
"tabtab": "https://github.com/Icenium/node-tabtab/tarball/master",
6768
"temp": "0.8.1",
69+
"through2": "2.0.0",
6870
"utf-8-validate": "https://github.com/telerik/utf-8-validate/tarball/master",
71+
"vinyl-filter-since": "2.0.0",
6972
"winreg": "0.0.12",
7073
"ws": "0.7.1",
7174
"xcode": "https://github.com/NativeScript/node-xcode/archive/NativeScript-1.1.tar.gz",

test/npm-support.ts

+43-11
Original file line numberDiff line numberDiff line change
@@ -88,15 +88,10 @@ function createProject(testInjector: IInjector, dependencies?: any): string {
8888
return tempFolder;
8989
}
9090

91-
describe("Npm support tests", () => {
92-
it("Ensures that the installed dependencies are prepared correctly", () => {
93-
let dependencies = {
94-
"bplist": "0.0.4",
95-
"lodash": "3.9.3"
96-
};
97-
91+
function setupProject(): IFuture<any> {
92+
return (() => {
9893
let testInjector = createTestInjector();
99-
let projectFolder = createProject(testInjector, dependencies);
94+
let projectFolder = createProject(testInjector);
10095

10196
let fs = testInjector.resolve("fs");
10297

@@ -112,7 +107,6 @@ describe("Npm support tests", () => {
112107
let androidFolderPath = path.join(projectFolder, "platforms", "android");
113108
fs.ensureDirectoryExists(androidFolderPath).wait();
114109

115-
116110
// Mock platform data
117111
let appDestinationFolderPath = path.join(androidFolderPath, "assets");
118112
let platformsData = testInjector.resolve("platformsData");
@@ -129,8 +123,45 @@ describe("Npm support tests", () => {
129123
}
130124
};
131125

132-
let platformService = testInjector.resolve("platformService");
133-
platformService.preparePlatform("android").wait();
126+
return {
127+
testInjector: testInjector,
128+
projectFolder: projectFolder,
129+
appDestinationFolderPath: appDestinationFolderPath,
130+
};
131+
}).future<any>()();
132+
}
133+
134+
function addDependencies(testInjector: IInjector, projectFolder: string, dependencies: any): IFuture<void> {
135+
return (() => {
136+
let fs = testInjector.resolve("fs");
137+
let packageJsonPath = path.join(projectFolder, "package.json");
138+
let packageJsonData = fs.readJson(packageJsonPath).wait();
139+
140+
let currentDependencies = packageJsonData.dependencies;
141+
_.extend(currentDependencies, dependencies);
142+
fs.writeJson(packageJsonPath, packageJsonData).wait();
143+
}).future<void>()();
144+
}
145+
146+
function preparePlatform(testInjector: IInjector): IFuture<void> {
147+
let platformService = testInjector.resolve("platformService");
148+
return platformService.preparePlatform("android");
149+
}
150+
151+
describe("Npm support tests", () => {
152+
let testInjector: IInjector, projectFolder: string, appDestinationFolderPath: string;
153+
before(() => {
154+
let projectSetup = setupProject().wait();
155+
testInjector = projectSetup.testInjector;
156+
projectFolder = projectSetup.projectFolder;
157+
appDestinationFolderPath = projectSetup.appDestinationFolderPath;
158+
});
159+
it("Ensures that the installed dependencies are prepared correctly", () => {
160+
// Setup
161+
addDependencies(testInjector, projectFolder, {"bplist": "0.0.4"}).wait();
162+
163+
// Act
164+
preparePlatform(testInjector).wait();
134165

135166
// Assert
136167
let tnsModulesFolderPath = path.join(appDestinationFolderPath, "app", "tns_modules");
@@ -139,6 +170,7 @@ describe("Npm support tests", () => {
139170
let bplistCreatorFolderPath = path.join(tnsModulesFolderPath, "bplist-creator");
140171
let bplistParserFolderPath = path.join(tnsModulesFolderPath, "bplist-parser");
141172

173+
let fs = testInjector.resolve("fs");
142174
assert.isTrue(fs.exists(lodashFolderPath).wait());
143175
assert.isTrue(fs.exists(bplistFolderPath).wait());
144176
assert.isTrue(fs.exists(bplistCreatorFolderPath).wait());

0 commit comments

Comments
 (0)