Skip to content

Do not copy app files to platform dir if using --bundle #2147

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 1 commit into from
Oct 19, 2016
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
2 changes: 2 additions & 0 deletions lib/bootstrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ $injector.requireCommand("debug|ios", "./commands/debug");
$injector.requireCommand("debug|android", "./commands/debug");

$injector.requireCommand("prepare", "./commands/prepare");
$injector.requireCommand("clean-app|ios", "./commands/clean-app");
$injector.requireCommand("clean-app|android", "./commands/clean-app");
$injector.requireCommand("build|ios", "./commands/build");
$injector.requireCommand("build|android", "./commands/build");
$injector.requireCommand("deploy", "./commands/deploy");
Expand Down
40 changes: 40 additions & 0 deletions lib/commands/clean-app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
export class CleanAppCommandBase {
constructor(protected $options: IOptions,
private $platformService: IPlatformService) { }

execute(args: string[]): IFuture<void> {
let platform = args[0].toLowerCase();
return this.$platformService.cleanDestinationApp(platform);
}
}

export class CleanAppIosCommand extends CleanAppCommandBase implements ICommand {
constructor(protected $options: IOptions,
private $platformsData: IPlatformsData,
$platformService: IPlatformService) {
super($options, $platformService);
}

public allowedParameters: ICommandParameter[] = [];

public execute(args: string[]): IFuture<void> {
return super.execute([this.$platformsData.availablePlatforms.iOS]);
}
}
$injector.registerCommand("clean-app|ios", CleanAppIosCommand);

export class CleanAppAndroidCommand extends CleanAppCommandBase implements ICommand {
constructor(protected $options: IOptions,
private $platformsData: IPlatformsData,
private $errors: IErrors,
$platformService: IPlatformService) {
super($options, $platformService);
}

public allowedParameters: ICommandParameter[] = [];

public execute(args: string[]): IFuture<void> {
return super.execute([this.$platformsData.availablePlatforms.Android]);
}
}
$injector.registerCommand("clean-app|android", CleanAppAndroidCommand);
1 change: 1 addition & 0 deletions lib/definitions/platform.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ interface IPlatformService {
updatePlatforms(platforms: string[]): IFuture<void>;
runPlatform(platform: string, buildConfig?: IBuildConfig): IFuture<void>;
preparePlatform(platform: string): IFuture<boolean>;
cleanDestinationApp(platform: string): IFuture<void>;
buildPlatform(platform: string, buildConfig?: IBuildConfig): IFuture<void>;
buildForDeploy(platform: string, buildConfig?: IBuildConfig): IFuture<void>;
installOnDevice(platform: string, buildConfig?: IBuildConfig): IFuture<void>;
Expand Down
86 changes: 86 additions & 0 deletions lib/services/app-files-updater.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import * as path from "path";
import * as minimatch from "minimatch";
import * as constants from "../constants";
import Future = require("fibers/future");

export class AppFilesUpdater {
constructor(
private appSourceDirectoryPath: string,
private appDestinationDirectoryPath: string,
public options: IOptions,
public fs: IFileSystem
) {
}

public updateApp(beforeCopyAction: (sourceFiles: string[]) => void): void {
this.cleanDestinationApp();
const sourceFiles = this.resolveAppSourceFiles();

beforeCopyAction(sourceFiles);
this.copyAppSourceFiles(sourceFiles);
}

public cleanDestinationApp(): void {
if (this.options.bundle) {
//Assuming an the bundle has updated the dest folder already.
//Skip cleaning up completely.
return;
}

// Delete the destination app in order to prevent EEXIST errors when symlinks are used.
let destinationAppContents = this.readDestinationDir();
destinationAppContents = destinationAppContents.filter(
(directoryName: string) => directoryName !== constants.TNS_MODULES_FOLDER_NAME);

_(destinationAppContents).each((directoryItem: string) => {
this.deleteDestinationItem(directoryItem);
});
}

protected readDestinationDir(): string[] {
if (this.fs.exists(this.appDestinationDirectoryPath).wait()) {
return this.fs.readDirectory(this.appDestinationDirectoryPath).wait();
} else {
return [];
}
}

protected deleteDestinationItem(directoryItem: string): void {
this.fs.deleteDirectory(path.join(this.appDestinationDirectoryPath, directoryItem)).wait();
}

protected readSourceDir(): string[] {
return this.fs.enumerateFilesInDirectorySync(this.appSourceDirectoryPath, null, { includeEmptyDirectories: true });
}

protected resolveAppSourceFiles(): string[] {
// Copy all files from app dir, but make sure to exclude tns_modules
let sourceFiles = this.readSourceDir();

if (this.options.release) {
let testsFolderPath = path.join(this.appSourceDirectoryPath, 'tests');
sourceFiles = sourceFiles.filter(source => source.indexOf(testsFolderPath) === -1);
}

// Remove .ts and .js.map files in release
if (this.options.release) {
constants.LIVESYNC_EXCLUDED_FILE_PATTERNS.forEach(pattern => sourceFiles = sourceFiles.filter(file => !minimatch(file, pattern, { nocase: true })));
}

if (this.options.bundle) {
sourceFiles = sourceFiles.filter(file => minimatch(file, "**/App_Resources/**", {nocase: true}));
}
return sourceFiles;
}

protected copyAppSourceFiles(sourceFiles: string[]): void {
let copyFileFutures = sourceFiles.map(source => {
let destinationPath = path.join(this.appDestinationDirectoryPath, path.relative(this.appSourceDirectoryPath, source));
if (this.fs.getFsStats(source).wait().isDirectory()) {
return this.fs.createDirectory(destinationPath);
}
return this.fs.copyFile(source, destinationPath);
});
Future.wait(copyFileFutures);
}
}
51 changes: 17 additions & 34 deletions lib/services/platform-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ import * as shell from "shelljs";
import * as constants from "../constants";
import * as helpers from "../common/helpers";
import * as semver from "semver";
import * as minimatch from "minimatch";
import Future = require("fibers/future");
import {AppFilesUpdater} from "./app-files-updater";
import * as temp from "temp";
temp.track();
let clui = require("clui");
Expand Down Expand Up @@ -244,44 +243,18 @@ export class PlatformService implements IPlatformService {
this.$fs.ensureDirectoryExists(appDestinationDirectoryPath).wait();
let appSourceDirectoryPath = path.join(this.$projectData.projectDir, constants.APP_FOLDER_NAME);

// Delete the destination app in order to prevent EEXIST errors when symlinks are used.
let contents = this.$fs.readDirectory(appDestinationDirectoryPath).wait();

_(contents)
.filter(directoryName => directoryName !== constants.TNS_MODULES_FOLDER_NAME)
.each(directoryName => this.$fs.deleteDirectory(path.join(appDestinationDirectoryPath, directoryName)).wait());

// Copy all files from app dir, but make sure to exclude tns_modules
let sourceFiles = this.$fs.enumerateFilesInDirectorySync(appSourceDirectoryPath, null, { includeEmptyDirectories: true });

if (this.$options.release) {
let testsFolderPath = path.join(appSourceDirectoryPath, 'tests');
sourceFiles = sourceFiles.filter(source => source.indexOf(testsFolderPath) === -1);
}

// verify .xml files are well-formed
this.$xmlValidator.validateXmlFiles(sourceFiles).wait();

// Remove .ts and .js.map files in release
if (this.$options.release) {
constants.LIVESYNC_EXCLUDED_FILE_PATTERNS.forEach(pattern => sourceFiles = sourceFiles.filter(file => !minimatch(file, pattern, { nocase: true })));
}

let copyFileFutures = sourceFiles.map(source => {
let destinationPath = path.join(appDestinationDirectoryPath, path.relative(appSourceDirectoryPath, source));
if (this.$fs.getFsStats(source).wait().isDirectory()) {
return this.$fs.createDirectory(destinationPath);
}
return this.$fs.copyFile(source, destinationPath);
const appUpdater = new AppFilesUpdater(appSourceDirectoryPath, appDestinationDirectoryPath, this.$options, this.$fs);
appUpdater.updateApp(sourceFiles => {
this.$xmlValidator.validateXmlFiles(sourceFiles).wait();
});
Future.wait(copyFileFutures);

// Copy App_Resources to project root folder
this.$fs.ensureDirectoryExists(platformData.platformProjectService.getAppResourcesDestinationDirectoryPath().wait()).wait(); // Should be deleted
const appResourcesDestination = platformData.platformProjectService.getAppResourcesDestinationDirectoryPath().wait();
this.$fs.ensureDirectoryExists(appResourcesDestination).wait(); // Should be deleted
let appResourcesDirectoryPath = path.join(appDestinationDirectoryPath, constants.APP_RESOURCES_FOLDER_NAME);
if (this.$fs.exists(appResourcesDirectoryPath).wait()) {
platformData.platformProjectService.prepareAppResources(appResourcesDirectoryPath).wait();
shell.cp("-Rf", path.join(appResourcesDirectoryPath, platformData.normalizedPlatformName, "*"), platformData.platformProjectService.getAppResourcesDestinationDirectoryPath().wait());
shell.cp("-Rf", path.join(appResourcesDirectoryPath, platformData.normalizedPlatformName, "*"), appResourcesDestination);
this.$fs.deleteDirectory(appResourcesDirectoryPath).wait();
}

Expand Down Expand Up @@ -316,6 +289,16 @@ export class PlatformService implements IPlatformService {
}).future<boolean>()();
}

public cleanDestinationApp(platform: string): IFuture<void> {
return (() => {
const appSourceDirectoryPath = path.join(this.$projectData.projectDir, constants.APP_FOLDER_NAME);
let platformData = this.$platformsData.getPlatformData(platform);
let appDestinationDirectoryPath = path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME);
const appUpdater = new AppFilesUpdater(appSourceDirectoryPath, appDestinationDirectoryPath, this.$options, this.$fs);
appUpdater.cleanDestinationApp();
}).future<void>()();
}

public buildPlatform(platform: string, buildConfig?: IBuildConfig): IFuture<void> {
return (() => {
platform = platform.toLowerCase();
Expand Down
82 changes: 82 additions & 0 deletions test/app-files-updates.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import {assert} from "chai";
import {AppFilesUpdater} from "../lib/services/app-files-updater";

require("should");

describe("App files cleanup", () => {
class CleanUpAppFilesUpdater extends AppFilesUpdater {
public deletedDestinationItems: string[] = [];

constructor(
public destinationFiles: string[],
options: any
) {
super("<source>", "<destination>", options, null);
}

public clean() {
this.cleanDestinationApp();
}

protected readDestinationDir(): string[] {
return this.destinationFiles;
}

protected deleteDestinationItem(directoryItem: string): void {
this.deletedDestinationItems.push(directoryItem);
}
}

it("cleans up entire app when not bundling", () => {
const updater = new CleanUpAppFilesUpdater([
"file1", "dir1/file2", "App_Resources/Android/blah.png"
], {bundle: false});
updater.clean();
assert.deepEqual(["file1", "dir1/file2", "App_Resources/Android/blah.png"], updater.deletedDestinationItems);
});

it("does not clean up destination when bundling", () => {
const updater = new CleanUpAppFilesUpdater([
"file1", "dir1/file2", "App_Resources/Android/blah.png"
], {bundle: true});
updater.clean();
assert.deepEqual([], updater.deletedDestinationItems);
});
});

describe("App files copy", () => {
class CopyAppFilesUpdater extends AppFilesUpdater {
public copiedDestinationItems: string[] = [];

constructor(
public sourceFiles: string[],
options: any
) {
super("<source>", "<destination>", options, null);
}

protected readSourceDir(): string[] {
return this.sourceFiles;
}

public copy(): void {
this.copiedDestinationItems = this.resolveAppSourceFiles();
}
}

it("copies all app files when not bundling", () => {
const updater = new CopyAppFilesUpdater([
"file1", "dir1/file2", "App_Resources/Android/blah.png"
], {bundle: false});
updater.copy();
assert.deepEqual(["file1", "dir1/file2", "App_Resources/Android/blah.png"], updater.copiedDestinationItems);
});

it("skips copying non-App_Resource files when bundling", () => {
const updater = new CopyAppFilesUpdater([
"file1", "dir1/file2", "App_Resources/Android/blah.png"
], {bundle: true});
updater.copy();
assert.deepEqual(["App_Resources/Android/blah.png"], updater.copiedDestinationItems);
});
});
6 changes: 5 additions & 1 deletion test/test-bootstrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ global._ = require("lodash");
global.$injector = require("../lib/common/yok").injector;

$injector.register("analyticsService", {
trackException: (): IFuture<void> => undefined
trackException: (): {wait(): void} => {
return {
wait: () => undefined
};
}
});

// Converts the js callstack to typescript
Expand Down