Skip to content

Fix symlinks that are added to output package and points to not existing location #593

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 29, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 15 additions & 6 deletions lib/services/platform-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,17 +149,26 @@ export class PlatformService implements IPlatformService {
platform = platform.toLowerCase();

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

// Copy app folder to native project
this.$fs.ensureDirectoryExists(appDestinationDirectoryPath).wait();
var appSourceDirectoryPath = path.join(this.$projectData.projectDir, constants.APP_FOLDER_NAME);

// Delete the destination app in order to prevent EEXIST errors when symlinks are used.
this.$fs.deleteDirectory(path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME)).wait();
shell.cp("-R", appSourceDirectoryPath, platformData.appDestinationDirectoryPath);
let contents = this.$fs.readDirectory(appDestinationDirectoryPath).wait();

_(contents)
.filter(directoryName => directoryName !== "tns_modules")
.each(directoryName => this.$fs.deleteDirectory(path.join(appDestinationDirectoryPath, directoryName)).wait())
.value();
shell.cp("-Rf", appSourceDirectoryPath, platformData.appDestinationDirectoryPath);

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

this.$logger.out("Project successfully prepared");
}).future<void>()();
Expand Down
3 changes: 2 additions & 1 deletion lib/tools/broccoli/broccoli.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ interface BroccoliNode {
}

interface IBroccoliBuilder {
prepareNodeModules(outputPath: string, projectDir: string): IFuture<void>;
prepareNodeModules(outputPath: string, projectDir: string, platform: string, lastModifiedTime?: Date): IFuture<void>;
}

interface IDiffResult {
Expand All @@ -162,6 +162,7 @@ interface IDiffResult {

interface IBroccoliPlugin {
rebuild(diff: IDiffResult): any;
rebuildChangedDirectories?(changedDirectories: string[], platform: string): void;
cleanup? () : void;
}

Expand Down
81 changes: 67 additions & 14 deletions lib/tools/broccoli/builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,82 @@ let broccoli = require('broccoli');
let path = require('path');
import Future = require("fibers/future");
import {TreeDiffer} from './tree-differ';
import destCopy = require('./node-modules-dest-copy');
import destCopyLib = require('./node-modules-dest-copy');

var gulp = require("gulp");
var vinylFilterSince = require('vinyl-filter-since')
var through = require("through2");

export class Builder implements IBroccoliBuilder {
private nodeModules: any = {};

constructor(private $fs: IFileSystem,
private $nodeModulesTree: INodeModulesTree,
private $projectDataService: IProjectDataService,
private $injector: IInjector,
private $logger: ILogger) { }

public prepareNodeModules(absoluteOutputPath: string, projectDir: string): IFuture<void> {
public prepareNodeModules(absoluteOutputPath: string, projectDir: string, platform: string, lastModifiedTime?: Date): IFuture<void> {
return (() => {
// TODO: figure out a better way for doing this
this.$projectDataService.initialize(projectDir);
let cachedNodeModulesPath = this.$projectDataService.getValue("node-modules-cache-path").wait();
if (cachedNodeModulesPath && this.$fs.exists(cachedNodeModulesPath).wait()) {
let diffTree = new TreeDiffer(cachedNodeModulesPath);
let diffTreeResult = diffTree.diffTree(path.join(projectDir, absoluteOutputPath, "node_modules"));

if(diffTreeResult.changedDirectories.length > 0 || diffTreeResult.removedDirectories.length > 0) {
this.rebuildNodeModulesTree(absoluteOutputPath, projectDir).wait();
}
} else {
this.rebuildNodeModulesTree(absoluteOutputPath, projectDir).wait();
let isNodeModulesModified = false;
let nodeModulesPath = path.join(projectDir, "node_modules");

if(lastModifiedTime) {
let pipeline = gulp.src(path.join(projectDir, "node_modules/**"))
.pipe(vinylFilterSince(lastModifiedTime))
.pipe(through.obj( (chunk: any, enc: any, cb: Function) => {
if(chunk.path === nodeModulesPath) {
isNodeModulesModified = true;
}

if(!isNodeModulesModified) {
let rootModuleName = chunk.path.split(nodeModulesPath)[1].split(path.sep)[1];
let rootModuleFullPath = path.join(nodeModulesPath, rootModuleName);
this.nodeModules[rootModuleFullPath] = rootModuleFullPath;
}

cb(null);
}))
.pipe(gulp.dest(absoluteOutputPath));

let future = new Future<void>();

pipeline.on('end', (err: any, data: any) => {
if(err) {
future.throw(err);
} else {
future.return();
}
});

future.wait();
}

if(isNodeModulesModified && this.$fs.exists(absoluteOutputPath).wait()) {
let currentPreparedTnsModules = this.$fs.readDirectory(absoluteOutputPath).wait();
let tnsModulesInApp = this.$fs.readDirectory(path.join(projectDir, "app", "tns_modules")).wait();
let modulesToDelete = _.difference(currentPreparedTnsModules, tnsModulesInApp);
_.each(modulesToDelete, moduleName => this.$fs.deleteDirectory(path.join(absoluteOutputPath, moduleName)).wait())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing ; at the end

}

if(!lastModifiedTime || isNodeModulesModified) {
let nodeModulesDirectories = this.$fs.readDirectory(nodeModulesPath).wait();
_.each(nodeModulesDirectories, nodeModuleDirectoryName => {
let nodeModuleFullPath = path.join(nodeModulesPath, nodeModuleDirectoryName);
this.nodeModules[nodeModuleFullPath] = nodeModuleFullPath;
});
}

let destCopy = this.$injector.resolve(destCopyLib.DestCopy, {
inputPath: projectDir,
cachePath: "",
outputRoot: absoluteOutputPath,
projectDir: projectDir,
platform: platform
});

destCopy.rebuildChangedDirectories(_.keys(this.nodeModules));

}).future<void>()();
}

Expand Down
65 changes: 33 additions & 32 deletions lib/tools/broccoli/node-modules-dest-copy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,32 @@ import constants = require("./../../constants");
* and tees a copy to the given path outside the tmp dir.
*/
export class DestCopy implements IBroccoliPlugin {
constructor(private inputPath: string, private cachePath: string, private outputRoot: string, private projectDir: string) {}

public rebuild(treeDiff: IDiffResult): void {
let dependencies = this.getDependencies();
let devDependencies = this.getDevDependencies(this.projectDir);
private dependencies: IDictionary<any> = null;
private devDependencies: IDictionary<any> = null;

treeDiff.changedDirectories.forEach(changedDirectory => {
let changedDirectoryAbsolutePath = path.join(this.inputPath, constants.NODE_MODULES_FOLDER_NAME, changedDirectory);
let packageJsonFiles = [path.join(changedDirectoryAbsolutePath, "package.json")];
constructor(private inputPath: string,
private cachePath: string,
private outputRoot: string,
private projectDir: string,
private platform: string,
private $fs: IFileSystem,
private $projectFilesManager: IProjectFilesManager) {
this.dependencies = Object.create(null);
this.devDependencies = this.getDevDependencies(projectDir);
}

public rebuildChangedDirectories(changedDirectories: string[], platform: string): void {
_.each(changedDirectories, changedDirectoryAbsolutePath => {
let pathToPackageJson = path.join(changedDirectoryAbsolutePath, "package.json");
let packageJsonFiles = fs.existsSync(pathToPackageJson) ? [pathToPackageJson] : [];
let nodeModulesFolderPath = path.join(changedDirectoryAbsolutePath, "node_modules");
packageJsonFiles = packageJsonFiles.concat(this.enumeratePackageJsonFilesSync(nodeModulesFolderPath));

_.each(packageJsonFiles, packageJsonFilePath => {
let fileContent = require(packageJsonFilePath);
let isPlugin = fileContent.nativescript;

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

let currentDependency = {
name: fileContent.name,
Expand All @@ -39,7 +48,7 @@ export class DestCopy implements IBroccoliPlugin {
isPlugin: isPlugin
};

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

dependencies[currentDependency.name] = currentDependency;
this.dependencies[currentDependency.name] = currentDependency;
}
} else {
dependencies[currentDependency.name] = currentDependency;
this.dependencies[currentDependency.name] = currentDependency;
}
}
});
});

_.each(dependencies, dependency => {
shelljs.cp("-R", dependency.directory, this.outputRoot);
_.each(this.dependencies, dependency => {
shelljs.cp("-Rf", dependency.directory, this.outputRoot);
shelljs.rm("-rf", path.join(this.outputRoot, dependency.name, "node_modules"));
if(dependency.isPlugin) {
this.$projectFilesManager.processPlatformSpecificFiles(path.join(this.outputRoot, dependency.name), platform).wait();
shelljs.rm("-rf", path.join(this.outputRoot, dependency.name, "platforms"));
}
});
}

public rebuild(treeDiff: IDiffResult): void {
this.rebuildChangedDirectories(treeDiff.changedDirectories, "");

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

private getDependencies(): IDictionary<any> {
let result = Object.create(null);
if(fs.existsSync(this.outputRoot)) {
let dirs = fs.readdirSync(this.outputRoot);
_.each(dirs, dir => {
let filePath = path.join(dir, constants.PACKAGE_JSON_FILE_NAME);
if(fs.existsSync(filePath)) {
let fileContent = require(filePath);
result[fileContent.name] = fileContent;
}
});
}

return result;
}

private getDevDependencies(projectDir: string): IDictionary<any> {
let projectFilePath = path.join(projectDir, constants.PACKAGE_JSON_FILE_NAME);
let projectFileContent = require(projectFilePath);
Expand All @@ -100,7 +98,10 @@ export class DestCopy implements IBroccoliPlugin {
if(fs.existsSync(nodeModulesDirectoryPath)) {
let contents = fs.readdirSync(nodeModulesDirectoryPath);
for (let i = 0; i < contents.length; ++i) {
foundFiles.push(path.join(nodeModulesDirectoryPath, contents[i], "package.json"));
let packageJsonFilePath = path.join(nodeModulesDirectoryPath, contents[i], "package.json");
if (fs.existsSync(packageJsonFilePath)) {
foundFiles.push(packageJsonFilePath);
}

var directoryPath = path.join(nodeModulesDirectoryPath, contents[i], "node_modules");
if (fs.existsSync(directoryPath)) {
Expand Down
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"fibers": "https://github.com/icenium/node-fibers/tarball/v1.0.5.1",
"filesize": "2.0.3",
"gaze": "0.5.1",
"gulp": "3.9.0",
"iconv-lite": "0.4.4",
"inquirer": "0.8.2",
"ios-sim-portable": "1.0.8",
Expand Down Expand Up @@ -65,7 +66,9 @@
"shelljs": "0.3.0",
"tabtab": "https://github.com/Icenium/node-tabtab/tarball/master",
"temp": "0.8.1",
"through2": "2.0.0",
"utf-8-validate": "https://github.com/telerik/utf-8-validate/tarball/master",
"vinyl-filter-since": "2.0.0",
"winreg": "0.0.12",
"ws": "0.7.1",
"xcode": "https://github.com/NativeScript/node-xcode/archive/NativeScript-1.1.tar.gz",
Expand Down
54 changes: 43 additions & 11 deletions test/npm-support.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,15 +88,10 @@ function createProject(testInjector: IInjector, dependencies?: any): string {
return tempFolder;
}

describe("Npm support tests", () => {
it("Ensures that the installed dependencies are prepared correctly", () => {
let dependencies = {
"bplist": "0.0.4",
"lodash": "3.9.3"
};

function setupProject(): IFuture<any> {
return (() => {
let testInjector = createTestInjector();
let projectFolder = createProject(testInjector, dependencies);
let projectFolder = createProject(testInjector);

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

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


// Mock platform data
let appDestinationFolderPath = path.join(androidFolderPath, "assets");
let platformsData = testInjector.resolve("platformsData");
Expand All @@ -129,8 +123,45 @@ describe("Npm support tests", () => {
}
};

let platformService = testInjector.resolve("platformService");
platformService.preparePlatform("android").wait();
return {
testInjector: testInjector,
projectFolder: projectFolder,
appDestinationFolderPath: appDestinationFolderPath,
};
}).future<any>()();
}

function addDependencies(testInjector: IInjector, projectFolder: string, dependencies: any): IFuture<void> {
return (() => {
let fs = testInjector.resolve("fs");
let packageJsonPath = path.join(projectFolder, "package.json");
let packageJsonData = fs.readJson(packageJsonPath).wait();

let currentDependencies = packageJsonData.dependencies;
_.extend(currentDependencies, dependencies);
fs.writeJson(packageJsonPath, packageJsonData).wait();
}).future<void>()();
}

function preparePlatform(testInjector: IInjector): IFuture<void> {
let platformService = testInjector.resolve("platformService");
return platformService.preparePlatform("android");
}

describe("Npm support tests", () => {
let testInjector: IInjector, projectFolder: string, appDestinationFolderPath: string;
before(() => {
let projectSetup = setupProject().wait();
testInjector = projectSetup.testInjector;
projectFolder = projectSetup.projectFolder;
appDestinationFolderPath = projectSetup.appDestinationFolderPath;
});
it("Ensures that the installed dependencies are prepared correctly", () => {
// Setup
addDependencies(testInjector, projectFolder, {"bplist": "0.0.4"}).wait();

// Act
preparePlatform(testInjector).wait();

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

let fs = testInjector.resolve("fs");
assert.isTrue(fs.exists(lodashFolderPath).wait());
assert.isTrue(fs.exists(bplistFolderPath).wait());
assert.isTrue(fs.exists(bplistCreatorFolderPath).wait());
Expand Down