From e6bcf22610417cd7d463b2ade7a37c03d0d9b937 Mon Sep 17 00:00:00 2001 From: Josh Thomas Date: Mon, 19 Dec 2016 22:52:02 -0600 Subject: [PATCH] fix(build): check to ensure tsconfig contains sourcemaps true. --- src/build.spec.ts | 199 +++++++++++++++++++++++++++++++++----------- src/build.ts | 68 +++++++++++---- src/util/helpers.ts | 7 +- 3 files changed, 205 insertions(+), 69 deletions(-) diff --git a/src/build.spec.ts b/src/build.spec.ts index d44c195d..2264dcae 100644 --- a/src/build.spec.ts +++ b/src/build.spec.ts @@ -14,7 +14,14 @@ import * as transpile from './transpile'; describe('build', () => { beforeEach(() => { spyOn(clean, 'clean'); - spyOn(helpers, 'readFileAsync').and.returnValue(Promise.resolve()); + spyOn(helpers, 'readFileAsync').and.callFake(() => { + return Promise.resolve(`{ + "compilerOptions": { + "sourceMap": true + } + } + `); + }); spyOn(copy, 'copy').and.returnValue(Promise.resolve()); spyOn(ngc, 'ngc').and.returnValue(Promise.resolve()); spyOn(bundle, 'bundle').and.returnValue(Promise.resolve()); @@ -26,56 +33,150 @@ describe('build', () => { }); it('should do a prod build', () => { - let context: BuildContext = { - isProd: true, - optimizeJs: true, - runMinifyJs: true, - runMinifyCss: true, - runAot: true - }; - - return build.build(context).then(() => { - expect(helpers.readFileAsync).toHaveBeenCalled(); - expect(copy.copy).toHaveBeenCalled(); - expect(ngc.ngc).toHaveBeenCalled(); - expect(bundle.bundle).toHaveBeenCalled(); - expect(minify.minifyJs).toHaveBeenCalled(); - expect(sass.sass).toHaveBeenCalled(); - expect(minify.minifyCss).toHaveBeenCalled(); - expect(lint.lint).toHaveBeenCalled(); - - expect(transpile.transpile).not.toHaveBeenCalled(); - }).catch(err => { - console.log(`err.message: `, err.message); - expect(true).toEqual(false); - }); + let context: BuildContext = { + isProd: true, + optimizeJs: true, + runMinifyJs: true, + runMinifyCss: true, + runAot: true + }; + + return build.build(context).then(() => { + expect(helpers.readFileAsync).toHaveBeenCalled(); + expect(copy.copy).toHaveBeenCalled(); + expect(ngc.ngc).toHaveBeenCalled(); + expect(bundle.bundle).toHaveBeenCalled(); + expect(minify.minifyJs).toHaveBeenCalled(); + expect(sass.sass).toHaveBeenCalled(); + expect(minify.minifyCss).toHaveBeenCalled(); + expect(lint.lint).toHaveBeenCalled(); + + expect(transpile.transpile).not.toHaveBeenCalled(); + }).catch(err => { + console.log(`err.message: `, err.message); + expect(true).toEqual(false); + }); + }); + + it('should do a dev build', () => { + let context: BuildContext = { + isProd: false, + optimizeJs: false, + runMinifyJs: false, + runMinifyCss: false, + runAot: false + }; + + return build.build(context).then(() => { + expect(helpers.readFileAsync).toHaveBeenCalled(); + expect(copy.copy).toHaveBeenCalled(); + expect(transpile.transpile).toHaveBeenCalled(); + expect(bundle.bundle).toHaveBeenCalled(); + expect(sass.sass).toHaveBeenCalled(); + expect(lint.lint).toHaveBeenCalled(); + + expect(ngc.ngc).not.toHaveBeenCalled(); + expect(minify.minifyJs).not.toHaveBeenCalled(); + expect(minify.minifyCss).not.toHaveBeenCalled(); + }).catch(err => { + console.log(`err.message: `, err.message); + expect(true).toEqual(false); + }); + }); +}); + +describe('test project requirements before building', () => { + it('should fail if APP_ENTRY_POINT file does not exist', () => { + process.env.IONIC_APP_ENTRY_POINT = 'src/app/main.ts'; + process.env.IONIC_TS_CONFIG = 'tsConfig.js'; + const error = new Error('App entry point was not found'); + + spyOn(helpers, 'readFileAsync').and.returnValue(Promise.reject(error)); + + return build.build({}).catch((e) => { + expect(helpers.readFileAsync).toHaveBeenCalledTimes(2); + expect(e).toEqual(error); }); + }); + + it('should fail if IONIC_TS_CONFIG file does not exist', () => { + process.env.IONIC_APP_ENTRY_POINT = 'src/app/main.ts'; + process.env.IONIC_TS_CONFIG = 'tsConfig.js'; + const error = new Error('App entry point was not found'); - it('should do a dev build', (done: Function) => { - let context: BuildContext = { - isProd: false, - optimizeJs: false, - runMinifyJs: false, - runMinifyCss: false, - runAot: false - }; - - build.build(context).then(() => { - expect(helpers.readFileAsync).toHaveBeenCalled(); - expect(copy.copy).toHaveBeenCalled(); - expect(transpile.transpile).toHaveBeenCalled(); - expect(bundle.bundle).toHaveBeenCalled(); - expect(sass.sass).toHaveBeenCalled(); - expect(lint.lint).toHaveBeenCalled(); - - expect(ngc.ngc).not.toHaveBeenCalled(); - expect(minify.minifyJs).not.toHaveBeenCalled(); - expect(minify.minifyCss).not.toHaveBeenCalled(); - done(); - }).catch(err => { - console.log(`err.message: `, err.message); - expect(true).toEqual(false); - }); + spyOn(helpers, 'readFileAsync').and.callFake((filePath: string) => { + if (filePath === 'src/app/main.ts') { + return Promise.resolve('allgood'); + } + return Promise.reject(error); }); + return build.build({}).catch((e) => { + expect(helpers.readFileAsync).toHaveBeenCalledTimes(2); + expect(e).toEqual(error); + }); + }); + + it('should fail fataly if IONIC_TS_CONFIG file does not contain valid JSON', () => { + process.env.IONIC_APP_ENTRY_POINT = 'src/app/main.ts'; + process.env.IONIC_TS_CONFIG = 'tsConfig.js'; + spyOn(helpers, 'readFileAsync').and.callFake(() => { + return Promise.resolve(`{ + "compilerOptions" { + "sourceMap": false + } + } + `); + }); + + return build.build({}).catch((e) => { + expect(helpers.readFileAsync).toHaveBeenCalledTimes(2); + expect(e.isFatal).toBeTruthy(); + }); + }); + + it('should fail fataly if IONIC_TS_CONFIG file does not contain compilerOptions.sourceMap === true', () => { + process.env.IONIC_APP_ENTRY_POINT = 'src/app/main.ts'; + process.env.IONIC_TS_CONFIG = 'tsConfig.js'; + spyOn(helpers, 'readFileAsync').and.callFake(() => { + return Promise.resolve(`{ + "compilerOptions": { + "sourceMap": false + } + } + `); + }); + + return build.build({}).catch((e) => { + expect(helpers.readFileAsync).toHaveBeenCalledTimes(2); + expect(e.isFatal).toBeTruthy(); + }); + }); + + it('should succeed if IONIC_TS_CONFIG file contains compilerOptions.sourceMap === true', () => { + process.env.IONIC_APP_ENTRY_POINT = 'src/app/main.ts'; + process.env.IONIC_TS_CONFIG = 'tsConfig.js'; + + spyOn(clean, 'clean'); + spyOn(copy, 'copy').and.returnValue(Promise.resolve()); + spyOn(ngc, 'ngc').and.returnValue(Promise.resolve()); + spyOn(bundle, 'bundle').and.returnValue(Promise.resolve()); + spyOn(minify, 'minifyJs').and.returnValue(Promise.resolve()); + spyOn(sass, 'sass').and.returnValue(Promise.resolve()); + spyOn(minify, 'minifyCss').and.returnValue(Promise.resolve()); + spyOn(lint, 'lint').and.returnValue(Promise.resolve()); + spyOn(transpile, 'transpile').and.returnValue(Promise.resolve()); + spyOn(helpers, 'readFileAsync').and.callFake(() => { + return Promise.resolve(`{ + "compilerOptions": { + "sourceMap": true + } + } + `); + }); + + return build.build({}).then(() => { + expect(helpers.readFileAsync).toHaveBeenCalledTimes(2); + }); + }); }); diff --git a/src/build.ts b/src/build.ts index 0d0f3b6b..f84080a0 100644 --- a/src/build.ts +++ b/src/build.ts @@ -24,35 +24,71 @@ export function build(context: BuildContext) { logger.finish(); }) .catch(err => { - handleDeprecations(err); + if (err.isFatal) { throw err; } throw logger.fail(err); }); } -function handleDeprecations(error: Error) { - if (error && error.message && error.message.indexOf('ENOENT') >= 0 && error.message.indexOf(process.env.IONIC_APP_ENTRY_POINT)) { - const error = new BuildError(`"main.dev.ts" and "main.prod.ts" have been deprecated. Please create a new file "main.ts" containing the content of "main.dev.ts", and then delete the deprecated files. - For more information, please see the default Ionic project main.ts file here: - https://github.com/driftyco/ionic2-app-base/tree/master/src/app/main.ts`); - error.isFatal = true; - throw error; - } -} - - function buildWorker(context: BuildContext) { return Promise.resolve().then(() => { // load any 100% required files to ensure they exist return validateRequiredFilesExist(); - }).then(() => { + }) + .then(([appEntryPointContents, tsConfigContents]) => { + return validateTsConfigSettings(tsConfigContents); + }) + .then(() => { return buildProject(context); }); } function validateRequiredFilesExist() { - // for now, just do the entry point - // eventually this could be Promise.all and load a bunch of stuff - return readFileAsync(process.env.IONIC_APP_ENTRY_POINT); + return Promise.all([ + readFileAsync(process.env.IONIC_APP_ENTRY_POINT), + readFileAsync(process.env.IONIC_TS_CONFIG) + ]).catch((error) => { + if (error.code === 'ENOENT' && error.path === process.env.IONIC_APP_ENTRY_POINT) { + error = new BuildError(`"main.dev.ts" and "main.prod.ts" have been deprecated. Please create a new file "main.ts" containing the content of "main.dev.ts", and then delete the deprecated files. + For more information, please see the default Ionic project main.ts file here: + https://github.com/driftyco/ionic2-app-base/tree/master/src/app/main.ts`); + error.isFatal = true; + throw error; + } + if (error.code === 'ENOENT' && error.path === process.env.IONIC_TS_CONFIG) { + error = new BuildError(['You are missing a "tsconfig.json" file. This file is required.', + 'For more information please see the default Ionic project tsconfig.json file here:', + 'https://github.com/driftyco/ionic2-app-base/blob/master/tsconfig.json'].join('\n')); + error.isFatal = true; + throw error; + } + error.isFatal = true; + throw error; + }); +} + +function validateTsConfigSettings(tsConfigFileContents: string) { + + return new Promise((resolve, reject) => { + try { + const tsConfigJson = JSON.parse(tsConfigFileContents); + const isValid = tsConfigJson.hasOwnProperty('compilerOptions') && + tsConfigJson.compilerOptions.hasOwnProperty('sourceMap') && + tsConfigJson.compilerOptions.sourceMap === true; + + if (!isValid) { + const error = new BuildError(['Your "tsconfig.json" file must have compilerOptions.sourceMap set to true.', + 'For more information please see the default Ionic project tsconfig.json file here:', + 'https://github.com/driftyco/ionic2-app-base/blob/master/tsconfig.json'].join('\n')); + error.isFatal = true; + return reject(error); + } + resolve(); + } catch (e) { + const error = new BuildError('Your "tsconfig.json" file contains malformed JSON.'); + error.isFatal = true; + return reject(error); + } + }); } function buildProject(context: BuildContext) { diff --git a/src/util/helpers.ts b/src/util/helpers.ts index 73d8b99f..5a698a67 100644 --- a/src/util/helpers.ts +++ b/src/util/helpers.ts @@ -1,6 +1,5 @@ import { basename, dirname, extname, join } from 'path'; import { BuildContext, File } from './interfaces'; -import { BuildError } from './errors'; import { createReadStream, createWriteStream, readFile, readFileSync, readJsonSync, remove, unlink, writeFile } from 'fs-extra'; import * as osName from 'os-name'; @@ -98,7 +97,7 @@ export function writeFileAsync(filePath: string, content: string): Promise return new Promise((resolve, reject) => { writeFile(filePath, content, (err) => { if (err) { - return reject(new BuildError(err)); + return reject(err); } return resolve(); }); @@ -109,7 +108,7 @@ export function readFileAsync(filePath: string): Promise { return new Promise((resolve, reject) => { readFile(filePath, 'utf-8', (err, buffer) => { if (err) { - return reject(new BuildError(err)); + return reject(err); } return resolve(buffer); }); @@ -120,7 +119,7 @@ export function unlinkAsync(filePath: string): Promise { return new Promise((resolve, reject) => { unlink(filePath, (err: Error) => { if (err) { - return reject(new BuildError(err)); + return reject(err); } return resolve(); });